mirror of
https://github.com/FreeTubeApp/FreeTube
synced 2025-01-26 11:51:14 +01:00
Subscriptions: Add community tab (#3973)
* Add subscription community tab * fix up community tab * simplify if statement * use global.community for all references to community * dont show community when useRss is set * check visibleTabs for showing the community tab * fix caching, decrease datalimit for community, add missing translations * L: Hide shared posts, IV: Don't show errors for empty community tab * add links to related issues
This commit is contained in:
parent
324bdb608a
commit
47ef3e5746
@ -92,6 +92,9 @@ export default defineComponent({
|
||||
hideSubscriptionsLive: function () {
|
||||
return this.$store.getters.getHideSubscriptionsLive
|
||||
},
|
||||
hideSubscriptionsCommunity: function() {
|
||||
return this.$store.getters.getHideSubscriptionsCommunity
|
||||
},
|
||||
showDistractionFreeTitles: function () {
|
||||
return this.$store.getters.getShowDistractionFreeTitles
|
||||
},
|
||||
@ -155,6 +158,7 @@ export default defineComponent({
|
||||
'updateHideSubscriptionsVideos',
|
||||
'updateHideSubscriptionsShorts',
|
||||
'updateHideSubscriptionsLive',
|
||||
'updateHideSubscriptionsCommunity',
|
||||
'updateBlurThumbnails'
|
||||
])
|
||||
}
|
||||
|
@ -66,6 +66,12 @@
|
||||
:tooltip="hideLiveStreams ? hideSubscriptionsLiveTooltip : ''"
|
||||
v-on="!hideLiveStreams ? { change: updateHideSubscriptionsLive } : {}"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Community')"
|
||||
:compact="true"
|
||||
:default-value="hideSubscriptionsCommunity"
|
||||
@change="updateHideSubscriptionsCommunity"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h4
|
||||
|
@ -6,7 +6,7 @@ import FtCommunityPoll from '../ft-community-poll/ft-community-poll.vue'
|
||||
import autolinker from 'autolinker'
|
||||
import VueTinySlider from 'vue-tiny-slider'
|
||||
|
||||
import { toLocalePublicationString } from '../../helpers/utils'
|
||||
import { deepCopy, toLocalePublicationString } from '../../helpers/utils'
|
||||
import { youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
|
||||
|
||||
import 'tiny-slider/dist/tiny-slider.css'
|
||||
@ -40,6 +40,7 @@ export default defineComponent({
|
||||
commentCount: '',
|
||||
isLoading: true,
|
||||
author: '',
|
||||
authorId: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -77,18 +78,16 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
this.postText = autolinker.link(this.data.postText)
|
||||
let authorThumbnails = this.data.authorThumbnails
|
||||
const authorThumbnails = deepCopy(this.data.authorThumbnails)
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
authorThumbnails = authorThumbnails.map(thumbnail => {
|
||||
authorThumbnails.forEach(thumbnail => {
|
||||
thumbnail.url = youtubeImageUrlToInvidious(thumbnail.url)
|
||||
return thumbnail
|
||||
})
|
||||
} else {
|
||||
authorThumbnails = authorThumbnails.map(thumbnail => {
|
||||
authorThumbnails.forEach(thumbnail => {
|
||||
if (thumbnail.url.startsWith('//')) {
|
||||
thumbnail.url = 'https:' + thumbnail.url
|
||||
}
|
||||
return thumbnail
|
||||
})
|
||||
}
|
||||
this.authorThumbnails = authorThumbnails
|
||||
@ -104,6 +103,7 @@ export default defineComponent({
|
||||
this.commentCount = this.data.commentCount
|
||||
this.type = (this.data.postContent !== null && this.data.postContent !== undefined) ? this.data.postContent.type : 'text'
|
||||
this.author = this.data.author
|
||||
this.authorId = this.data.authorId
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
|
@ -34,6 +34,10 @@
|
||||
font-weight: bold;
|
||||
margin-block: 5px 0;
|
||||
margin-inline: 5px 6px;
|
||||
.authorNameLink {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.publishedText {
|
||||
|
@ -8,16 +8,43 @@
|
||||
<div
|
||||
class="author-div"
|
||||
>
|
||||
<img
|
||||
<template
|
||||
v-if="authorThumbnails.length > 0"
|
||||
:src="getBestQualityImage(authorThumbnails)"
|
||||
class="communityThumbnail"
|
||||
alt=""
|
||||
>
|
||||
<router-link
|
||||
v-if="authorId"
|
||||
:to="`/channel/${authorId}`"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img
|
||||
:src="getBestQualityImage(authorThumbnails)"
|
||||
class="communityThumbnail"
|
||||
alt=""
|
||||
>
|
||||
</router-link>
|
||||
<img
|
||||
v-else
|
||||
:src="getBestQualityImage(authorThumbnails)"
|
||||
class="communityThumbnail"
|
||||
alt=""
|
||||
>
|
||||
</template>
|
||||
<p
|
||||
class="authorName"
|
||||
>
|
||||
{{ author }}
|
||||
<router-link
|
||||
v-if="authorId"
|
||||
:to="`/channel/${authorId}`"
|
||||
class="authorNameLink"
|
||||
>
|
||||
{{ author }}
|
||||
</router-link>
|
||||
<template
|
||||
v-else
|
||||
>
|
||||
{{ author }}
|
||||
</template>
|
||||
</p>
|
||||
<p
|
||||
class="publishedText"
|
||||
|
@ -0,0 +1,221 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { mapActions, mapMutations } from 'vuex'
|
||||
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
|
||||
|
||||
import { calculatePublishedDate, copyToClipboard, showToast } from '../../helpers/utils'
|
||||
import { getLocalChannelCommunity } from '../../helpers/api/local'
|
||||
import { invidiousGetCommunityPosts } from '../../helpers/api/invidious'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubscriptionsCommunity',
|
||||
components: {
|
||||
'subscriptions-tab-ui': SubscriptionsTabUI
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isLoading: false,
|
||||
postList: [],
|
||||
errorChannels: [],
|
||||
attemptedFetch: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeProfileId: function () {
|
||||
return this.activeProfile._id
|
||||
},
|
||||
|
||||
cacheEntriesForAllActiveProfileChannels() {
|
||||
const entries = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const cacheEntry = this.$store.getters.getPostsCacheByChannel(channel.id)
|
||||
if (cacheEntry == null) { return }
|
||||
|
||||
entries.push(cacheEntry)
|
||||
})
|
||||
return entries
|
||||
},
|
||||
postCacheForAllActiveProfileChannelsPresent() {
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
|
||||
|
||||
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
|
||||
return cacheEntry.posts != null
|
||||
})
|
||||
},
|
||||
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
|
||||
fetchSubscriptionsAutomatically: function() {
|
||||
return this.$store.getters.getFetchSubscriptionsAutomatically
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeProfile: async function (_) {
|
||||
this.isLoading = true
|
||||
this.loadpostsFromCacheSometimes()
|
||||
},
|
||||
},
|
||||
mounted: async function () {
|
||||
this.isLoading = true
|
||||
|
||||
this.loadpostsFromCacheSometimes()
|
||||
},
|
||||
methods: {
|
||||
loadpostsFromCacheSometimes() {
|
||||
// This method is called on view visible
|
||||
if (this.postCacheForAllActiveProfileChannelsPresent) {
|
||||
this.loadPostsFromCacheForAllActiveProfileChannels()
|
||||
return
|
||||
}
|
||||
|
||||
this.maybeLoadPostsForSubscriptionsFromRemote()
|
||||
},
|
||||
|
||||
async loadPostsFromCacheForAllActiveProfileChannels() {
|
||||
const postList = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const channelCacheEntry = this.$store.getters.getPostsCacheByChannel(channel.id)
|
||||
|
||||
postList.push(...channelCacheEntry.posts)
|
||||
})
|
||||
|
||||
postList.sort((a, b) => {
|
||||
return calculatePublishedDate(b.publishedText) - calculatePublishedDate(a.publishedText)
|
||||
})
|
||||
|
||||
this.postList = postList
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
loadPostsForSubscriptionsFromRemote: async function () {
|
||||
if (this.activeSubscriptionList.length === 0) {
|
||||
this.isLoading = false
|
||||
this.postList = []
|
||||
return
|
||||
}
|
||||
|
||||
const channelsToLoadFromRemote = this.activeSubscriptionList
|
||||
const postList = []
|
||||
let channelCount = 0
|
||||
this.isLoading = true
|
||||
|
||||
this.updateShowProgressBar(true)
|
||||
this.setProgressBarPercentage(0)
|
||||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const postListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let posts = []
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
posts = await this.getChannelPostsInvidious(channel)
|
||||
} else {
|
||||
posts = await this.getChannelPostsLocal(channel)
|
||||
}
|
||||
|
||||
channelCount++
|
||||
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
|
||||
this.setProgressBarPercentage(percentageComplete)
|
||||
|
||||
this.updateSubscriptionPostsCacheByChannel({
|
||||
channelId: channel.id,
|
||||
posts: posts,
|
||||
})
|
||||
return posts
|
||||
}))).flatMap((o) => o)
|
||||
postList.push(...postListFromRemote)
|
||||
postList.sort((a, b) => {
|
||||
return calculatePublishedDate(b.publishedText) - calculatePublishedDate(a.publishedText)
|
||||
})
|
||||
|
||||
this.postList = postList
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
},
|
||||
|
||||
maybeLoadPostsForSubscriptionsFromRemote: async function () {
|
||||
if (this.fetchSubscriptionsAutomatically) {
|
||||
// `this.isLoading = false` is called inside `loadPostsForSubscriptionsFromRemote` when needed
|
||||
await this.loadPostsForSubscriptionsFromRemote()
|
||||
} else {
|
||||
this.postList = []
|
||||
this.attemptedFetch = false
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
getChannelPostsLocal: async function (channel) {
|
||||
try {
|
||||
const entries = await getLocalChannelCommunity(channel.id)
|
||||
|
||||
if (entries === null) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
entries.forEach(post => {
|
||||
post.authorId = channel.id
|
||||
})
|
||||
return entries
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (this.backendPreference === 'local' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return await this.getChannelPostsInvidious(channel)
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
getChannelPostsInvidious: function (channel) {
|
||||
return new Promise((resolve, reject) => {
|
||||
invidiousGetCommunityPosts(channel.id).then(result => {
|
||||
result.posts.forEach(post => {
|
||||
post.authorId = channel.id
|
||||
})
|
||||
resolve(result.posts)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (process.env.IS_ELECTRON && this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
resolve(this.getChannelPostsLocal(channel))
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionPostsCacheByChannel',
|
||||
]),
|
||||
|
||||
...mapMutations([
|
||||
'setProgressBarPercentage'
|
||||
])
|
||||
}
|
||||
})
|
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<subscriptions-tab-ui
|
||||
:is-loading="isLoading"
|
||||
:video-list="postList"
|
||||
:error-channels="errorChannels"
|
||||
:attempted-fetch="attemptedFetch"
|
||||
:is-community="true"
|
||||
:initial-data-limit="20"
|
||||
@refresh="loadPostsForSubscriptionsFromRemote"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./subscriptions-community.js" />
|
@ -28,6 +28,10 @@ export default defineComponent({
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
isCommunity: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
errorChannels: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
@ -36,6 +40,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
initialDataLimit: {
|
||||
type: Number,
|
||||
default: 100
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@ -68,6 +76,8 @@ export default defineComponent({
|
||||
|
||||
if (dataLimit !== null) {
|
||||
this.dataLimit = dataLimit
|
||||
} else {
|
||||
this.dataLimit = this.initialDataLimit
|
||||
}
|
||||
},
|
||||
mounted: async function () {
|
||||
@ -78,7 +88,7 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
increaseLimit: function () {
|
||||
this.dataLimit += 100
|
||||
this.dataLimit += this.initialDataLimit
|
||||
sessionStorage.setItem('subscriptionLimit', this.dataLimit)
|
||||
},
|
||||
|
||||
|
@ -36,19 +36,20 @@
|
||||
v-else
|
||||
class="message"
|
||||
>
|
||||
{{ $t("Subscriptions.Empty Channels") }}
|
||||
{{ isCommunity ? $t("Subscriptions.Empty Posts") : $t("Subscriptions.Empty Channels") }}
|
||||
</p>
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-if="!isLoading && activeVideoList.length > 0"
|
||||
:data="activeVideoList"
|
||||
:use-channels-hidden-preference="false"
|
||||
:display="isCommunity ? 'list' : ''"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="!isLoading && videoList.length > dataLimit"
|
||||
>
|
||||
<ft-button
|
||||
:label="$t('Subscriptions.Load More Videos')"
|
||||
:label="isCommunity ? $t('Subscriptions.Load More Posts') : $t('Subscriptions.Load More Videos')"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="increaseLimit"
|
||||
|
@ -24,7 +24,13 @@ export function invidiousAPICall({ resource, id = '', params = {}, doLogError =
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (json.error !== undefined) {
|
||||
throw new Error(json.error)
|
||||
// community is empty, no need to display error.
|
||||
// This code can be removed when: https://github.com/iv-org/invidious/issues/3814 is reolved
|
||||
if (json.error === 'This channel hasn\'t posted yet') {
|
||||
resolve({ comments: [] })
|
||||
} else {
|
||||
throw new Error(json.error)
|
||||
}
|
||||
}
|
||||
resolve(json)
|
||||
})
|
||||
|
@ -286,6 +286,36 @@ export async function getLocalChannelLiveStreams(id) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLocalChannelCommunity(id) {
|
||||
const innertube = await createInnertube()
|
||||
|
||||
try {
|
||||
const response = await innertube.actions.execute(Endpoints.BrowseEndpoint.PATH, Endpoints.BrowseEndpoint.build({
|
||||
browse_id: id,
|
||||
params: 'Egljb21tdW5pdHnyBgQKAkoA'
|
||||
// protobuf for the community tab (this is the one that YouTube uses,
|
||||
// it has some empty fields in the protobuf but it doesn't work if you remove them)
|
||||
}))
|
||||
|
||||
const communityTab = new YT.Channel(null, response)
|
||||
|
||||
// if the channel doesn't have a community tab, YouTube returns the home tab instead
|
||||
// so we need to check that we got the right tab
|
||||
if (communityTab.current_tab?.endpoint.metadata.url?.endsWith('/community')) {
|
||||
return parseLocalCommunityPosts(communityTab.posts)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
if (error instanceof Utils.ChannelError) {
|
||||
return null
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('youtubei.js').YTNodes.Video[]} videos
|
||||
* @param {Misc.Author} author
|
||||
@ -879,9 +909,29 @@ export function parseLocalSubscriberCount(text) {
|
||||
|
||||
/**
|
||||
* Parse community posts
|
||||
* @param {import('youtubei.js').YTNodes.BackstagePost[] | import('youtubei.js').YTNodes.SharedPost[] | import('youtubei.js').YTNodes.Post[] } posts
|
||||
*/
|
||||
export function parseLocalCommunityPosts(posts) {
|
||||
const foundIds = []
|
||||
// `posts` includes the SharedPost's attached post for some reason so we need to filter that out.
|
||||
// see: https://github.com/FreeTubeApp/FreeTube/issues/3252#issuecomment-1546675781
|
||||
// we don't currently support SharedPost's so that is also filtered out
|
||||
for (const post of posts) {
|
||||
if (post.type === 'SharedPost') {
|
||||
foundIds.push(post.original_post.id, post.id)
|
||||
}
|
||||
}
|
||||
|
||||
return posts.filter(post => {
|
||||
return !foundIds.includes(post.id)
|
||||
}).map(parseLocalCommunityPost)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse community post
|
||||
* @param {import('youtubei.js').YTNodes.BackstagePost} post
|
||||
*/
|
||||
export function parseLocalCommunityPost(post) {
|
||||
function parseLocalCommunityPost(post) {
|
||||
let replyCount = post.action_buttons?.reply_button?.text ?? null
|
||||
if (replyCount !== null) {
|
||||
replyCount = parseLocalSubscriberCount(post?.action_buttons.reply_button.text)
|
||||
|
@ -216,6 +216,7 @@ const state = {
|
||||
hideSubscriptionsVideos: false,
|
||||
hideSubscriptionsShorts: false,
|
||||
hideSubscriptionsLive: false,
|
||||
hideSubscriptionsCommunity: false,
|
||||
hideTrendingVideos: false,
|
||||
hideUnsubscribeButton: false,
|
||||
hideUpcomingPremieres: false,
|
||||
|
@ -7,7 +7,8 @@ const defaultCacheEntryValueForForOneChannel = {
|
||||
const state = {
|
||||
videoCache: {},
|
||||
liveCache: {},
|
||||
shortsCache: {}
|
||||
shortsCache: {},
|
||||
postsCache: {}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
@ -34,6 +35,14 @@ const getters = {
|
||||
getLiveCacheByChannel: (state) => (channelId) => {
|
||||
return state.liveCache[channelId]
|
||||
},
|
||||
|
||||
getPostsCache: (state) => {
|
||||
return state.postsCache
|
||||
},
|
||||
|
||||
getPostsCacheByChannel: (state) => (channelId) => {
|
||||
return state.postsCache[channelId]
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
@ -49,10 +58,15 @@ const actions = {
|
||||
commit('updateLiveCacheByChannel', payload)
|
||||
},
|
||||
|
||||
updateSubscriptionPostsCacheByChannel: ({ commit }, payload) => {
|
||||
commit('updatePostsCacheByChannel', payload)
|
||||
},
|
||||
|
||||
clearSubscriptionsCache: ({ commit }, payload) => {
|
||||
commit('clearVideoCache', payload)
|
||||
commit('clearShortsCache', payload)
|
||||
commit('clearLiveCache', payload)
|
||||
commit('clearPostsCache', payload)
|
||||
},
|
||||
}
|
||||
|
||||
@ -84,6 +98,15 @@ const mutations = {
|
||||
clearLiveCache(state) {
|
||||
state.liveCache = {}
|
||||
},
|
||||
updatePostsCacheByChannel(state, { channelId, posts }) {
|
||||
const existingObject = state.postsCache[channelId]
|
||||
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
|
||||
if (posts != null) { newObject.posts = posts }
|
||||
state.postsCache[channelId] = newObject
|
||||
},
|
||||
clearPostsCache(state) {
|
||||
state.postsCache = {}
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
getLocalChannelId,
|
||||
parseLocalChannelShorts,
|
||||
parseLocalChannelVideos,
|
||||
parseLocalCommunityPost,
|
||||
parseLocalCommunityPosts,
|
||||
parseLocalListPlaylist,
|
||||
parseLocalListVideo,
|
||||
parseLocalSubscriberCount
|
||||
@ -1576,7 +1576,7 @@ export default defineComponent({
|
||||
posts = communityTab.posts
|
||||
}
|
||||
|
||||
this.latestCommunityPosts = posts.map(parseLocalCommunityPost)
|
||||
this.latestCommunityPosts = parseLocalCommunityPosts(posts)
|
||||
this.communityContinuationData = communityTab.has_continuation ? communityTab : null
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@ -1608,7 +1608,7 @@ export default defineComponent({
|
||||
posts = continuation.posts
|
||||
}
|
||||
|
||||
this.latestCommunityPosts = this.latestCommunityPosts.concat(posts.map(parseLocalCommunityPost))
|
||||
this.latestCommunityPosts = this.latestCommunityPosts.concat(parseLocalCommunityPosts(posts))
|
||||
this.communityContinuationData = continuation.has_continuation ? continuation : null
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
@ -186,7 +186,7 @@
|
||||
@click="changeTab('community')"
|
||||
@keydown.left.right.enter.space="changeTab('community', $event)"
|
||||
>
|
||||
{{ $t("Channel.Community.Community").toUpperCase() }}
|
||||
{{ $t("Global.Community").toUpperCase() }}
|
||||
</div>
|
||||
<!-- eslint-disable-next-line vuejs-accessibility/interactive-supports-focus -->
|
||||
<div
|
||||
|
@ -3,6 +3,7 @@ import { defineComponent } from 'vue'
|
||||
import SubscriptionsVideos from '../../components/subscriptions-videos/subscriptions-videos.vue'
|
||||
import SubscriptionsLive from '../../components/subscriptions-live/subscriptions-live.vue'
|
||||
import SubscriptionsShorts from '../../components/subscriptions-shorts/subscriptions-shorts.vue'
|
||||
import SubscriptionsCommunity from '../../components/subscriptions-community/subscriptions-community.vue'
|
||||
|
||||
import FtCard from '../../components/ft-card/ft-card.vue'
|
||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
||||
@ -14,6 +15,7 @@ export default defineComponent({
|
||||
'subscriptions-videos': SubscriptionsVideos,
|
||||
'subscriptions-live': SubscriptionsLive,
|
||||
'subscriptions-shorts': SubscriptionsShorts,
|
||||
'subscriptions-community': SubscriptionsCommunity,
|
||||
'ft-card': FtCard,
|
||||
'ft-flex-box': FtFlexBox
|
||||
},
|
||||
@ -35,6 +37,19 @@ export default defineComponent({
|
||||
hideSubscriptionsLive: function () {
|
||||
return this.$store.getters.getHideLiveStreams || this.$store.getters.getHideSubscriptionsLive
|
||||
},
|
||||
hideSubscriptionsCommunity: function() {
|
||||
return this.$store.getters.getHideSubscriptionsCommunity
|
||||
},
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
useRssFeeds: function () {
|
||||
return this.$store.getters.getUseRssFeeds
|
||||
},
|
||||
|
||||
visibleTabs: function () {
|
||||
const tabs = []
|
||||
|
||||
@ -50,6 +65,11 @@ export default defineComponent({
|
||||
tabs.push('live')
|
||||
}
|
||||
|
||||
// community does not support rss
|
||||
if (!this.hideSubscriptionsCommunity && !this.useRssFeeds && this.activeSubscriptionList.length < 125) {
|
||||
tabs.push('community')
|
||||
}
|
||||
|
||||
return tabs
|
||||
}
|
||||
},
|
||||
|
@ -55,6 +55,22 @@
|
||||
>
|
||||
{{ $t("Global.Live").toUpperCase() }}
|
||||
</div>
|
||||
<!-- eslint-disable-next-line vuejs-accessibility/interactive-supports-focus -->
|
||||
<div
|
||||
v-if="visibleTabs.includes('community')"
|
||||
ref="community"
|
||||
class="tab"
|
||||
role="tab"
|
||||
:aria-selected="String(currentTab === 'community')"
|
||||
aria-controls="subscriptionsPanel"
|
||||
:tabindex="currentTab === 'community' ? 0 : -1"
|
||||
:class="{ selectedTab: currentTab === 'community' }"
|
||||
@click="changeTab('community')"
|
||||
@keydown.space.enter.prevent="changeTab('community')"
|
||||
@keydown.left.right="focusTab($event, 'community')"
|
||||
>
|
||||
{{ $t("Global.Community").toUpperCase() }}
|
||||
</div>
|
||||
</ft-flex-box>
|
||||
<subscriptions-videos
|
||||
v-if="currentTab === 'videos'"
|
||||
@ -71,6 +87,11 @@
|
||||
id="subscriptionsPanel"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<subscriptions-community
|
||||
v-else-if="currentTab === 'community'"
|
||||
id="subscriptionsPanel"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<p v-else-if="currentTab === null">
|
||||
{{ $t("Subscriptions.All Subscription Tabs Hidden", {
|
||||
subsection: $t('Settings.Distraction Free Settings.Sections.Subscriptions Page'),
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'الفيديوهات'
|
||||
Shorts: القصيرة
|
||||
Live: مباشر
|
||||
Community: المجتمع
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -640,7 +641,6 @@ Channel:
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: هذه
|
||||
القناة مصنفة حسب العمر ولا يمكن عرضها حاليا في FreeTube.
|
||||
Community:
|
||||
Community: المجتمع
|
||||
This channel currently does not have any posts: لا تحتوي هذه القناة حاليا على
|
||||
أي مشاركات
|
||||
Hide Answers: إخفاء الأجوبة
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: 'видеа'
|
||||
Shorts: Кратки видеа
|
||||
Live: На живо
|
||||
Community: Общност
|
||||
|
||||
Counts:
|
||||
Video Count: 1 видео | {count} видеа
|
||||
@ -652,7 +653,6 @@ Channel:
|
||||
Joined: Присъединен на
|
||||
Location: Местоположение
|
||||
Community:
|
||||
Community: Общност
|
||||
This channel currently does not have any posts: В момента този канал няма никакви
|
||||
публикации
|
||||
votes: '{votes} гласа'
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Videa'
|
||||
Shorts: Shorts
|
||||
Live: Živě
|
||||
Community: Komunita
|
||||
|
||||
Counts:
|
||||
Video Count: 1 video | {count} videí
|
||||
@ -642,7 +643,6 @@ Channel:
|
||||
This channel does not allow searching: Tento kanál neumožňuje vyhledávání
|
||||
Channel Tabs: Karty kanálů
|
||||
Community:
|
||||
Community: Komunita
|
||||
This channel currently does not have any posts: Tento kanál v současné době nemá
|
||||
žádné příspěvky
|
||||
Hide Answers: Skrýt odpovědi
|
||||
|
@ -34,6 +34,7 @@ Global:
|
||||
Videos: Videos
|
||||
Shorts: Kurzvideos
|
||||
Live: Live
|
||||
Community: Gemeinschaft
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -631,7 +632,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: Dieser Kanal enthält derzeit keine
|
||||
Beiträge
|
||||
Community: Gemeinschaft
|
||||
votes: '{votes} Stimmen'
|
||||
Reveal Answers: Antworten aufzeigen
|
||||
Hide Answers: Antworten verbergen
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: 'Βίντεο'
|
||||
Shorts: Shorts
|
||||
Live: Ζωντανά
|
||||
Community: Κοινότητα
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -671,7 +672,6 @@ Channel:
|
||||
Channel Tabs: Καρτέλες Καναλιών
|
||||
This channel does not exist: Αυτό το κανάλι δεν υπάρχει
|
||||
Community:
|
||||
Community: Κοινότητα
|
||||
This channel currently does not have any posts: Αυτό το κανάλι προς το παρόν δεν
|
||||
έχει αναρτήσεις
|
||||
Reveal Answers: Εμφάνιση Απαντήσεων
|
||||
|
@ -45,6 +45,7 @@ Global:
|
||||
Videos: Videos
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
Community: Community
|
||||
Counts:
|
||||
Video Count: 1 video | {count} videos
|
||||
Channel Count: 1 channel | {count} channels
|
||||
@ -104,8 +105,10 @@ Subscriptions:
|
||||
Disabled Automatic Fetching: You have disabled automatic subscription fetching. Refresh subscriptions to see them here.
|
||||
Empty Channels: Your subscribed channels currently does not have any videos.
|
||||
'Getting Subscriptions. Please wait.': Getting Subscriptions. Please wait.
|
||||
Empty Posts: Your subscribed channels currently do not have any posts.
|
||||
Refresh Subscriptions: Refresh Subscriptions
|
||||
Load More Videos: Load More Videos
|
||||
Load More Posts: Load More Posts
|
||||
Subscriptions Tabs: Subscriptions Tabs
|
||||
All Subscription Tabs Hidden: 'All subscription tabs are hidden. To see content here, please unhide some tabs in the "{subsection}" section in "{settingsSection}".'
|
||||
More: More
|
||||
@ -371,6 +374,7 @@ Settings:
|
||||
Hide Subscriptions Videos: Hide Subscriptions Videos
|
||||
Hide Subscriptions Shorts: Hide Subscriptions Shorts
|
||||
Hide Subscriptions Live: Hide Subscriptions Live
|
||||
Hide Subscriptions Community: Hide Subscriptions Community
|
||||
Data Settings:
|
||||
Data Settings: Data Settings
|
||||
Select Import Type: Select Import Type
|
||||
@ -597,7 +601,6 @@ Channel:
|
||||
Location: Location
|
||||
Featured Channels: Featured Channels
|
||||
Community:
|
||||
Community: Community
|
||||
This channel currently does not have any posts: This channel currently does not have any posts
|
||||
votes: '{votes} votes'
|
||||
Reveal Answers: Reveal Answers
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: Videos
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
Community: Community
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Version {versionNumber}
|
||||
is now available! Click for more details'
|
||||
@ -620,7 +621,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: This channel currently does not
|
||||
have any posts
|
||||
Community: Community
|
||||
Live:
|
||||
This channel does not currently have any live streams: This channel does not currently
|
||||
have any live streams
|
||||
|
@ -21,6 +21,9 @@ Close: 'Fermi'
|
||||
Back: 'Reen'
|
||||
Forward: 'Antaŭen'
|
||||
|
||||
Globals:
|
||||
Community: Komunumo
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versio {versionNumber}
|
||||
disponeblas nun! Alklaki por pli informoj.'
|
||||
Download From Site: 'Elŝuti el retejo'
|
||||
@ -105,8 +108,6 @@ About:
|
||||
Channel:
|
||||
About:
|
||||
Details: Detaloj
|
||||
Community:
|
||||
Community: Komunumo
|
||||
Video: {}
|
||||
More: Pli
|
||||
Search Bar:
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: 'Vídeos'
|
||||
Shorts: Cortos
|
||||
Live: En directo
|
||||
Community: Comunidad
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -654,7 +655,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: Este canal no tiene actualmente
|
||||
ningún mensaje
|
||||
Community: Comunidad
|
||||
Reveal Answers: Revelar las respuestas
|
||||
Hide Answers: Ocultar las respuestas
|
||||
votes: '{votes} votos'
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Videod'
|
||||
Shorts: Lühivideod
|
||||
Live: Otseeeter
|
||||
Community: Kogukond
|
||||
|
||||
Counts:
|
||||
Video Count: 1 video | {count} videot
|
||||
@ -596,7 +597,6 @@ Channel:
|
||||
This channel does not currently have any live streams: Sellel kanalil pole hetkel
|
||||
ühtegi otseeetrit
|
||||
Community:
|
||||
Community: Kogukond
|
||||
This channel currently does not have any posts: Sellel kanalil pole hetkel postitusi
|
||||
Reveal Answers: Näita vastuseid
|
||||
Hide Answers: Peida vastused
|
||||
|
@ -33,14 +33,14 @@ Global:
|
||||
Videos: 'Videot'
|
||||
Shorts: Lyhyet
|
||||
Live: Livenä
|
||||
|
||||
# Search Bar
|
||||
Community: Yhteisö
|
||||
Counts:
|
||||
Video Count: 1 video | {count} videota
|
||||
Subscriber Count: 1 tilaaja | {count} tilaajaa
|
||||
View Count: 1 näyttökerta | {count} näyttökertaa
|
||||
Watching Count: 1 katselee | {count} katselee
|
||||
Channel Count: 1 kanava | {count} kanavaa
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Etsi / Mene osoitteeseen'
|
||||
# In Filter Button
|
||||
Search Filters:
|
||||
@ -598,7 +598,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: Tällä kanavalla ei ole tällä hetkellä
|
||||
mitään
|
||||
Community: Yhteisö
|
||||
Reveal Answers: Näytä vastaukset
|
||||
Hide Answers: Piilota vastaukset
|
||||
votes: '{votes} ääntä'
|
||||
|
@ -34,6 +34,7 @@ Global:
|
||||
Videos: 'Vidéos'
|
||||
Shorts: Shorts
|
||||
Live: En direct
|
||||
Community: Communauté
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -642,7 +643,6 @@ Channel:
|
||||
chaîne est limitée par l'âge et ne peut actuellement pas être visionnée dans FreeTube.
|
||||
Channel Tabs: Onglets des chaînes
|
||||
Community:
|
||||
Community: Communauté
|
||||
This channel currently does not have any posts: Cette chaîne n'a actuellement
|
||||
aucune publication
|
||||
votes: '{votes} votes'
|
||||
|
@ -34,6 +34,7 @@ Forward: 'Adiante'
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vídeos'
|
||||
Community: Comunidade
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'A versión {versionNumber}
|
||||
está dispoñible! Fai clic para veres máis detalles'
|
||||
@ -619,7 +620,6 @@ Channel:
|
||||
canle ten unha limitación de idade e actualmente non se pode ver en FreeTube.
|
||||
Channel Tabs: Pestanas das canles
|
||||
Community:
|
||||
Community: Comunidade
|
||||
This channel currently does not have any posts: Esta canle actualmente non ten
|
||||
publicacións
|
||||
Video:
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'סרטונים'
|
||||
Shorts: Shorts
|
||||
Live: חי
|
||||
Community: קהילה
|
||||
|
||||
Counts:
|
||||
Video Count: סרטון אחד | {count} סרטונים
|
||||
@ -640,7 +641,6 @@ Channel:
|
||||
מוגבל לפי גיל וכרגע אי אפשר לצפות בו ב־FreeTube.
|
||||
Community:
|
||||
This channel currently does not have any posts: אין רשומות בערוץ הזה כרגע
|
||||
Community: קהילה
|
||||
votes: '{votes} הצבעות'
|
||||
Reveal Answers: חשיפת התשובות
|
||||
Hide Answers: הסתרת התשובות
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Videa'
|
||||
Shorts: Kratka videa
|
||||
Live: Uživo
|
||||
Community: Zajednica
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -659,7 +660,6 @@ Channel:
|
||||
prijenosa uživo
|
||||
Community:
|
||||
This channel currently does not have any posts: Ovaj kanal trenutačno nema objava
|
||||
Community: Zajednica
|
||||
Reveal Answers: Prikaži odgovore
|
||||
Hide Answers: Sakrij odgovore
|
||||
votes: '{votes} glasanja'
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: 'Videók'
|
||||
Shorts: Rövidfilmek
|
||||
Live: Élő
|
||||
Community: Közösség
|
||||
|
||||
Counts:
|
||||
Video Count: 1 videó | {count} videó
|
||||
@ -665,7 +666,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: Ezen a csatornán jelenleg nincsenek
|
||||
bejegyzések
|
||||
Community: Közösség
|
||||
Reveal Answers: Válaszok feltárása
|
||||
Hide Answers: Válaszok elrejtése
|
||||
votes: '{votes} szavazat'
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: 'Myndskeið'
|
||||
Shorts: Stuttmyndir
|
||||
Live: Í beinni
|
||||
Community: Samfélag
|
||||
|
||||
Counts:
|
||||
Video Count: 1 myndskeið | {count} myndskeið
|
||||
@ -599,7 +600,6 @@ Channel:
|
||||
This channel does not currently have any live streams: Þessi rás er í augnablikinu
|
||||
ekki með nein bein streymi
|
||||
Community:
|
||||
Community: Samfélag
|
||||
This channel currently does not have any posts: Þessi rás er ekki með neinar færslur
|
||||
Reveal Answers: Birta svör
|
||||
Hide Answers: Fela svör
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: 'Video'
|
||||
Shorts: Video brevi
|
||||
Live: Dal vivo
|
||||
Community: Comunità
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -619,7 +620,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: Questo canale attualmente non
|
||||
ha alcun post
|
||||
Community: Comunità
|
||||
votes: '{votes} voti'
|
||||
Reveal Answers: Rivela le risposte
|
||||
Hide Answers: Nascondi le risposte
|
||||
|
@ -33,6 +33,7 @@ Global:
|
||||
Videos: '動画'
|
||||
Shorts: ショート動画
|
||||
Live: ライブ配信
|
||||
Community: コミュニティ
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -555,7 +556,6 @@ Channel:
|
||||
Removed subscription from {count} other channel(s): ほかの {count} チャンネルから登録を削除しました
|
||||
Community:
|
||||
This channel currently does not have any posts: このチャンネルには現在投稿がありません
|
||||
Community: コミュニティ
|
||||
votes: '{votes} 投票'
|
||||
Reveal Answers: 回答を表示
|
||||
Hide Answers: 回答の非表示
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Videoer'
|
||||
Shorts: Kortvideoer
|
||||
Live: Direkte
|
||||
Community: Gemenskap
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Søk/gå til nettadresse'
|
||||
@ -558,7 +559,6 @@ Channel:
|
||||
kanalen er aldersbegrenset og kan derfor ikke vises i FreeTube.
|
||||
Channel Tabs: Kanalfaner
|
||||
Community:
|
||||
Community: Gemenskap
|
||||
This channel currently does not have any posts: Denne kanalen har ingen oppføringer
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Denne kanalen har ingen kortvideoer
|
||||
|
@ -34,6 +34,7 @@ Global:
|
||||
Videos: 'Filmy'
|
||||
Shorts: Filmy Short
|
||||
Live: Transmisje
|
||||
Community: Społeczność
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -626,7 +627,6 @@ Channel:
|
||||
Community:
|
||||
This channel currently does not have any posts: Ten kanał nie ma obecnie żadnych
|
||||
publikacji
|
||||
Community: Społeczność
|
||||
votes: '{votes} głosów'
|
||||
Reveal Answers: Pokaż odpowiedzi
|
||||
Hide Answers: Schowaj odpowiedzi
|
||||
|
@ -34,6 +34,7 @@ Global:
|
||||
Videos: 'Vídeos'
|
||||
Shorts: Shorts
|
||||
Live: Ao vivo
|
||||
Community: Comunidade
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -612,7 +613,6 @@ Channel:
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: Este
|
||||
canal tem restrição de idade e atualmente não pode ser visualizado no FreeTube.
|
||||
Community:
|
||||
Community: Comunidade
|
||||
This channel currently does not have any posts: Neste momento, este canal não
|
||||
tem publicações
|
||||
votes: '{votes} Votos'
|
||||
|
@ -36,6 +36,7 @@ Global:
|
||||
Videos: Vídeos
|
||||
Shorts: Curtas
|
||||
Live: Em directo
|
||||
Community: Comunidade
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: A versão {versionNumber}
|
||||
já está disponível! Clique para mais detalhes
|
||||
@ -594,7 +595,6 @@ Channel:
|
||||
canal tem restrição de idade e atualmente não pode ser visualizado no Free Tube.
|
||||
Channel Tabs: Separadores de canais
|
||||
Community:
|
||||
Community: Comunidade
|
||||
This channel currently does not have any posts: Neste momento, este canal não
|
||||
tem publicações
|
||||
Live:
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Vídeos'
|
||||
Shorts: Curtas
|
||||
Live: Em directo
|
||||
Community: Comunidade
|
||||
|
||||
Counts:
|
||||
Channel Count: 1 canal | {count} canais
|
||||
|
@ -33,9 +33,9 @@ Forward: 'Înainte'
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videoclipuri'
|
||||
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
Community: Comunitate
|
||||
Counts:
|
||||
Video Count: 1 videoclip | {count} videoclipuri
|
||||
Subscriber Count: 1 abonat | {count} de abonați
|
||||
|
@ -33,6 +33,7 @@ Global:
|
||||
Videos: 'Видео'
|
||||
Shorts: Короткие видео
|
||||
Live: Трансляции
|
||||
Community: Сообщество
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Поиск / Перейти по адресу'
|
||||
@ -598,7 +599,6 @@ Channel:
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: Этот
|
||||
канал ограничен по возрасту и в настоящее время не может быть просмотрен во FreeTube.
|
||||
Community:
|
||||
Community: Сообщество
|
||||
This channel currently does not have any posts: На этом канале в настоящее время
|
||||
нет никаких записей
|
||||
votes: 'Голосов: {votes}'
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Videor'
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
Community: Gemenskap
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versionen {versionNumber}
|
||||
är nu tillgänglig! Klicka för mer detaljer'
|
||||
@ -636,7 +637,6 @@ Channel:
|
||||
kanalen är ålderbegränsad och kan inte ses i FreeTube.
|
||||
Channel Tabs: Kanalflikar
|
||||
Community:
|
||||
Community: Gemenskap
|
||||
This channel currently does not have any posts: Denna kanal har för närvarande
|
||||
inga inlägg
|
||||
votes: '{votes} röster'
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Videolar'
|
||||
Shorts: Kısa Videolar
|
||||
Live: Canlı
|
||||
Community: Topluluk
|
||||
|
||||
Counts:
|
||||
Video Count: 1 video | {count} video
|
||||
@ -662,7 +663,6 @@ Channel:
|
||||
This channel does not allow searching: Bu kanal aramaya izin vermiyor
|
||||
This channel does not exist: Bu kanal mevcut değil
|
||||
Community:
|
||||
Community: Topluluk
|
||||
This channel currently does not have any posts: Bu kanalda şu anda herhangi bir
|
||||
gönderi yok
|
||||
votes: '{votes} oy'
|
||||
|
@ -35,6 +35,7 @@ Global:
|
||||
Videos: 'Відео'
|
||||
Shorts: Shorts
|
||||
Live: Наживо
|
||||
Community: Спільнота
|
||||
|
||||
Counts:
|
||||
Video Count: 1 відео | {count} відео
|
||||
@ -593,7 +594,6 @@ Channel:
|
||||
канал має вікові обмеження і наразі не може бути переглянутий на FreeTube.
|
||||
Channel Tabs: Вкладки каналів
|
||||
Community:
|
||||
Community: Спільнота
|
||||
This channel currently does not have any posts: Зараз на цьому каналі немає публікацій
|
||||
votes: 'Голосів: {votes}'
|
||||
Reveal Answers: Розгорнути відповіді
|
||||
|
@ -33,6 +33,7 @@ Global:
|
||||
Videos: '视频'
|
||||
Shorts: 短视频
|
||||
Live: 直播
|
||||
Community: 社区
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -555,7 +556,6 @@ Channel:
|
||||
This channel does not exist: 此频道不存在
|
||||
This channel does not allow searching: 此频道不允许搜索
|
||||
Community:
|
||||
Community: 社区
|
||||
This channel currently does not have any posts: 此频道当前没有任何帖子
|
||||
votes: '{votes} 票'
|
||||
Reveal Answers: 揭晓答案
|
||||
|
@ -33,6 +33,7 @@ Global:
|
||||
Videos: '影片'
|
||||
Shorts: 短片
|
||||
Live: 直播
|
||||
Community: 社群
|
||||
|
||||
# Search Bar
|
||||
Counts:
|
||||
@ -565,7 +566,6 @@ Channel:
|
||||
This channel does not exist: 此頻道不存在
|
||||
This channel does not allow searching: 此頻道不允許搜尋
|
||||
Community:
|
||||
Community: 社群
|
||||
This channel currently does not have any posts: 此頻道目前沒有任何貼文
|
||||
votes: '{votes} 票'
|
||||
Reveal Answers: 揭露答案
|
||||
|
Loading…
x
Reference in New Issue
Block a user