Merge branch 'development' into piped-support

This commit is contained in:
ChunkyProgrammer 2023-07-26 11:58:46 -07:00
commit d9e208c555
86 changed files with 3845 additions and 1275 deletions

View File

@ -91,6 +91,7 @@ body:
- .exe - .exe
- Flathub - Flathub
- MPR - MPR
- Nix
- .pacman - .pacman
- Portable - Portable
- PortableApps - PortableApps

View File

@ -17,7 +17,7 @@ jobs:
"labels": ["B: visual"] "labels": ["B: visual"]
}, },
{ {
"keywords": ["AUR", "Chocolatey", "FreeTubeCordova", "PortableApps", "winget", "Scoop", "Snapcraft", "MPR"], "keywords": ["AUR", "Chocolatey", "FreeTubeCordova", "PortableApps", "winget", "Scoop", "Snapcraft", "MPR", "Nix"],
"labels": ["B: Unofficial Download"] "labels": ["B: Unofficial Download"]
}, },
{ {

View File

@ -91,6 +91,8 @@ These builds are maintained by the community. While they should be safe, downloa
* makedeb Package Repository (MPR): [Download](https://mpr.makedeb.org/packages/freetube-bin) * makedeb Package Repository (MPR): [Download](https://mpr.makedeb.org/packages/freetube-bin)
* Nix Packages: [Download](https://search.nixos.org/packages?query=freetube)
* PortableApps (Windows Only): [Download](https://github.com/rddim/FreeTubePortable/releases) and [Source Code](https://github.com/rddim/FreeTubePortable) * PortableApps (Windows Only): [Download](https://github.com/rddim/FreeTubePortable/releases) and [Source Code](https://github.com/rddim/FreeTubePortable)
* Scoop (Windows Only): [Usage](https://github.com/ScoopInstaller/Scoop) * Scoop (Windows Only): [Usage](https://github.com/ScoopInstaller/Scoop)

View File

@ -66,7 +66,7 @@
"nedb-promises": "^6.2.1", "nedb-promises": "^6.2.1",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"process": "^0.11.10", "process": "^0.11.10",
"video.js": "7.21.4", "video.js": "7.21.5",
"videojs-contrib-quality-levels": "^3.0.0", "videojs-contrib-quality-levels": "^3.0.0",
"videojs-http-source-selector": "^1.1.6", "videojs-http-source-selector": "^1.1.6",
"videojs-mobile-ui": "^0.8.0", "videojs-mobile-ui": "^0.8.0",
@ -78,21 +78,21 @@
"vue-router": "^3.6.5", "vue-router": "^3.6.5",
"vue-tiny-slider": "^0.1.39", "vue-tiny-slider": "^0.1.39",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"youtubei.js": "^5.2.0" "youtubei.js": "^5.6.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.9",
"@babel/eslint-parser": "^7.22.5", "@babel/eslint-parser": "^7.22.9",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.22.5", "@babel/preset-env": "^7.22.9",
"@double-great/stylelint-a11y": "^2.0.2", "@double-great/stylelint-a11y": "^2.0.2",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.3",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1", "css-minimizer-webpack-plugin": "^5.0.1",
"electron": "^22.3.15", "electron": "^22.3.18",
"electron-builder": "^23.6.0", "electron-builder": "^23.6.0",
"eslint": "^8.44.0", "eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.1.0", "eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
@ -100,31 +100,31 @@
"eslint-plugin-n": "^16.0.1", "eslint-plugin-n": "^16.0.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-unicorn": "^47.0.0", "eslint-plugin-unicorn": "^48.0.0",
"eslint-plugin-vue": "^9.15.1", "eslint-plugin-vue": "^9.15.1",
"eslint-plugin-vuejs-accessibility": "^2.1.0", "eslint-plugin-vuejs-accessibility": "^2.1.0",
"eslint-plugin-yml": "^1.8.0", "eslint-plugin-yml": "^1.8.0",
"html-webpack-plugin": "^5.5.3", "html-webpack-plugin": "^5.5.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-minimizer-webpack-plugin": "^4.0.0", "json-minimizer-webpack-plugin": "^4.0.0",
"lefthook": "^1.4.3", "lefthook": "^1.4.6",
"mini-css-extract-plugin": "^2.7.6", "mini-css-extract-plugin": "^2.7.6",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.4.24", "postcss": "^8.4.26",
"postcss-scss": "^4.0.6", "postcss-scss": "^4.0.6",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"sass": "^1.63.6", "sass": "^1.64.1",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"stylelint": "^14.16.1", "stylelint": "^15.10.2",
"stylelint-config-sass-guidelines": "^9.0.1", "stylelint-config-sass-guidelines": "^10.0.0",
"stylelint-config-standard": "^29.0.0", "stylelint-config-standard": "^34.0.0",
"stylelint-high-performance-animation": "^1.8.0", "stylelint-high-performance-animation": "^1.8.0",
"tree-kill": "1.2.2", "tree-kill": "1.2.2",
"vue-devtools": "^5.1.4", "vue-devtools": "^5.1.4",
"vue-eslint-parser": "^9.3.1", "vue-eslint-parser": "^9.3.1",
"vue-loader": "^15.10.0", "vue-loader": "^15.10.0",
"webpack": "^5.88.1", "webpack": "^5.88.2",
"webpack-cli": "^5.1.4", "webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1", "webpack-dev-server": "^4.15.1",
"yaml-eslint-parser": "^1.2.2" "yaml-eslint-parser": "^1.2.2"

View File

@ -71,14 +71,36 @@ export default defineComponent({
hideChannelPlaylists: function() { hideChannelPlaylists: function() {
return this.$store.getters.getHideChannelPlaylists return this.$store.getters.getHideChannelPlaylists
}, },
hideChannelPodcasts: function() {
return this.$store.getters.getHideChannelPodcasts
},
hideChannelReleases: function() {
return this.$store.getters.getHideChannelReleases
},
hideChannelCommunity: function() { hideChannelCommunity: function() {
return this.$store.getters.getHideChannelCommunity return this.$store.getters.getHideChannelCommunity
}, },
hideSubscriptionsVideos: function() {
return this.$store.getters.getHideSubscriptionsVideos
},
hideSubscriptionsShorts: function() {
return this.$store.getters.getHideSubscriptionsShorts
},
hideSubscriptionsLive: function() {
return this.$store.getters.getHideSubscriptionsLive
},
showDistractionFreeTitles: function () { showDistractionFreeTitles: function () {
return this.$store.getters.getShowDistractionFreeTitles return this.$store.getters.getShowDistractionFreeTitles
}, },
channelsHidden: function () { channelsHidden: function () {
return JSON.parse(this.$store.getters.getChannelsHidden) return JSON.parse(this.$store.getters.getChannelsHidden)
},
hideSubscriptionsLiveTooltip: function () {
return this.$t('Tooltips.Distraction Free Settings.Hide Subscriptions Live', {
appWideSetting: this.$t('Settings.Distraction Free Settings.Hide Live Streams'),
subsection: this.$t('Settings.Distraction Free Settings.Sections.General'),
settingsSection: this.$t('Settings.Distraction Free Settings.Distraction Free Settings')
})
} }
}, },
methods: { methods: {
@ -117,7 +139,12 @@ export default defineComponent({
'updateHideFeaturedChannels', 'updateHideFeaturedChannels',
'updateHideChannelShorts', 'updateHideChannelShorts',
'updateHideChannelPlaylists', 'updateHideChannelPlaylists',
'updateHideChannelCommunity' 'updateHideChannelCommunity',
'updateHideChannelPodcasts',
'updateHideChannelReleases',
'updateHideSubscriptionsVideos',
'updateHideSubscriptionsShorts',
'updateHideSubscriptionsLive'
]) ])
} }
}) })

View File

@ -37,6 +37,37 @@
/> />
</div> </div>
</div> </div>
<h4
class="groupTitle"
>
{{ $t('Settings.Distraction Free Settings.Sections.Subscriptions Page') }}
</h4>
<div class="switchColumnGrid">
<div class="switchColumn">
<ft-toggle-switch
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Videos')"
:compact="true"
:default-value="hideSubscriptionsVideos"
@change="updateHideSubscriptionsVideos"
/>
<ft-toggle-switch
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Shorts')"
:compact="true"
:default-value="hideSubscriptionsShorts"
@change="updateHideSubscriptionsShorts"
/>
</div>
<div class="switchColumn">
<ft-toggle-switch
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Live')"
:compact="true"
:disabled="hideLiveStreams"
:default-value="hideLiveStreams || hideSubscriptionsLive"
:tooltip="hideLiveStreams ? hideSubscriptionsLiveTooltip : ''"
v-on="!hideLiveStreams ? { change: updateHideSubscriptionsLive } : {}"
/>
</div>
</div>
<h4 <h4
class="groupTitle" class="groupTitle"
> >
@ -56,6 +87,12 @@
:default-value="hideChannelPlaylists" :default-value="hideChannelPlaylists"
@change="updateHideChannelPlaylists" @change="updateHideChannelPlaylists"
/> />
<ft-toggle-switch
:label="$t('Settings.Distraction Free Settings.Hide Channel Podcasts')"
:compact="true"
:default-value="hideChannelPodcasts"
@change="updateHideChannelPodcasts"
/>
</div> </div>
<div class="switchColumn"> <div class="switchColumn">
<ft-toggle-switch <ft-toggle-switch
@ -70,6 +107,12 @@
:default-value="hideFeaturedChannels" :default-value="hideFeaturedChannels"
@change="updateHideFeaturedChannels" @change="updateHideFeaturedChannels"
/> />
<ft-toggle-switch
:label="$t('Settings.Distraction Free Settings.Hide Channel Releases')"
:compact="true"
:default-value="hideChannelReleases"
@change="updateHideChannelReleases"
/>
</div> </div>
</div> </div>
<h4 <h4

View File

@ -111,7 +111,8 @@ export default defineComponent({
return Number.parseInt(b.width) - Number.parseInt(a.width) return Number.parseInt(b.width) - Number.parseInt(a.width)
}) })
return imageArrayCopy.at(0)?.url ?? '' // Remove cropping directives when applicable
return imageArrayCopy.at(0)?.url?.replace(/-c-fcrop64=.*/i, '') ?? ''
} }
} }
}) })

View File

@ -21,7 +21,11 @@ export default defineComponent({
showVideoWithLastViewedPlaylist: { showVideoWithLastViewedPlaylist: {
type: Boolean, type: Boolean,
default: false default: false
} },
useChannelsHiddenPreference: {
type: Boolean,
default: true,
},
}, },
computed: { computed: {
listType: function () { listType: function () {

View File

@ -10,6 +10,7 @@
:first-screen="index < 16" :first-screen="index < 16"
:layout="displayValue" :layout="displayValue"
:show-video-with-last-viewed-playlist="showVideoWithLastViewedPlaylist" :show-video-with-last-viewed-playlist="showVideoWithLastViewedPlaylist"
:use-channels-hidden-preference="useChannelsHiddenPreference"
/> />
</ft-auto-grid> </ft-auto-grid>
</template> </template>

View File

@ -33,6 +33,10 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false default: false
}, },
useChannelsHiddenPreference: {
type: Boolean,
default: true,
},
}, },
data: function () { data: function () {
return { return {
@ -44,6 +48,9 @@ export default defineComponent({
return this.$store.getters.getHideLiveStreams return this.$store.getters.getHideLiveStreams
}, },
channelsHidden: function() { channelsHidden: function() {
// Some component users like channel view will have this disabled
if (!this.useChannelsHiddenPreference) { return [] }
return JSON.parse(this.$store.getters.getChannelsHidden) return JSON.parse(this.$store.getters.getChannelsHidden)
}, },
hideUpcomingPremieres: function () { hideUpcomingPremieres: function () {
@ -59,7 +66,7 @@ export default defineComponent({
if (!data.type) { if (!data.type) {
return false return false
} }
if (data.type === 'video') { if (data.type === 'video' || data.type === 'shortVideo') {
if (this.hideLiveStreams && (data.liveNow || data.lengthSeconds == null)) { if (this.hideLiveStreams && (data.liveNow || data.lengthSeconds == null)) {
// hide livestreams // hide livestreams
return false return false
@ -67,6 +74,10 @@ export default defineComponent({
if (this.hideUpcomingPremieres && if (this.hideUpcomingPremieres &&
// Observed for premieres in Local API Channels. // Observed for premieres in Local API Channels.
(data.premiereDate != null || (data.premiereDate != null ||
// Invidious API
// `premiereTimestamp` only available on premiered videos
// https://docs.invidious.io/api/common_types/#videoobject
data.premiereTimestamp != null ||
// viewCount is our only method of detecting premieres in RSS // viewCount is our only method of detecting premieres in RSS
// data without sending an additional request. // data without sending an additional request.
// If we ever get a better flag, use it here instead. // If we ever get a better flag, use it here instead.
@ -79,12 +90,30 @@ export default defineComponent({
return false return false
} }
} else if (data.type === 'channel') { } else if (data.type === 'channel') {
if (this.channelsHidden.includes(data.channelID) || this.channelsHidden.includes(data.name)) { const attrsToCheck = [
// Local API
data.id,
data.name,
// Invidious API
// https://docs.invidious.io/api/common_types/#channelobject
data.author,
data.authorId,
]
if (attrsToCheck.some(a => a != null && this.channelsHidden.includes(a))) {
// hide channels by author // hide channels by author
return false return false
} }
} else if (data.type === 'playlist') { } else if (data.type === 'playlist') {
if (this.channelsHidden.includes(data.authorId) || this.channelsHidden.includes(data.author)) { const attrsToCheck = [
// Local API
data.channelId,
data.channelName,
// Invidious API
// https://docs.invidious.io/api/common_types/#playlistobject
data.author,
data.authorId,
]
if (attrsToCheck.some(a => a != null && this.channelsHidden.includes(a))) {
// hide playlists by author // hide playlists by author
return false return false
} }

View File

@ -43,18 +43,35 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
useChannelsHiddenPreference: {
type: Boolean,
default: false,
},
}, },
data: function () { data: function () {
return { return {
visible: false visible: false
} }
}, },
computed: {
channelsHidden() {
// Some component users like channel view will have this disabled
if (!this.useChannelsHiddenPreference) { return [] }
return JSON.parse(this.$store.getters.getChannelsHidden)
},
shouldBeVisible() {
return !(this.channelsHidden.includes(this.data.authorId) ||
this.channelsHidden.includes(this.data.author))
}
},
created() { created() {
this.visible = this.initialVisibleState this.visible = this.initialVisibleState
}, },
methods: { methods: {
onVisibilityChanged: function (visible) { onVisibilityChanged: function (visible) {
if (visible) { if (visible && this.shouldBeVisible) {
this.visible = visible this.visible = visible
} }
} }

View File

@ -16,7 +16,12 @@ import { IpcChannels } from '../../../constants'
import { sponsorBlockSkipSegments } from '../../helpers/sponsorblock' import { sponsorBlockSkipSegments } from '../../helpers/sponsorblock'
import { calculateColorLuminance, colors } from '../../helpers/colors' import { calculateColorLuminance, colors } from '../../helpers/colors'
import { pathExists } from '../../helpers/filesystem' import { pathExists } from '../../helpers/filesystem'
import { getPicturesPath, showSaveDialog, showToast } from '../../helpers/utils' import {
copyToClipboard,
getPicturesPath,
showSaveDialog,
showToast,
} from '../../helpers/utils'
import { getProxyUrl } from '../../helpers/api/invidious' import { getProxyUrl } from '../../helpers/api/invidious'
import store from '../../store' import store from '../../store'
@ -367,7 +372,7 @@ export default defineComponent({
this.determineFormatType() this.determineFormatType()
if ('mediaSession' in navigator) { if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', () => this.player.play()) navigator.mediaSession.setActionHandler('play', () => this.playVideo())
navigator.mediaSession.setActionHandler('pause', () => this.player.pause()) navigator.mediaSession.setActionHandler('pause', () => this.player.pause())
} }
@ -531,7 +536,8 @@ export default defineComponent({
if (this.autoplayVideos) { if (this.autoplayVideos) {
// Calling play() won't happen right away, so a quick timeout will make it function properly. // Calling play() won't happen right away, so a quick timeout will make it function properly.
setTimeout(() => { setTimeout(() => {
this.player.play() // `this.player` can be destroyed before this runs
this.playVideo()
}, 200) }, 200)
} }
@ -869,7 +875,7 @@ export default defineComponent({
this.player.playbackRate(this.defaultPlayback) this.player.playbackRate(this.defaultPlayback)
} else { } else {
if (this.player.paused() || !this.player.hasStarted()) { if (this.player.paused() || !this.player.hasStarted()) {
this.player.play() this.playVideo()
} else { } else {
this.player.pause() this.player.pause()
} }
@ -908,7 +914,7 @@ export default defineComponent({
this.player.playbackRate(playbackRate) this.player.playbackRate(playbackRate)
// need to call play to restore the player state, even if we want to pause afterwards // need to call play to restore the player state, even if we want to pause afterwards
this.player.play().then(() => { this.playVideo(() => {
if (isPaused) { this.player.pause() } if (isPaused) { this.player.pause() }
}) })
}) })
@ -1248,7 +1254,7 @@ export default defineComponent({
togglePlayPause: function () { togglePlayPause: function () {
if (this.player.paused()) { if (this.player.paused()) {
this.player.play() this.playVideo()
} else { } else {
this.player.pause() this.player.pause()
} }
@ -1571,7 +1577,7 @@ export default defineComponent({
const response = await showSaveDialog(options) const response = await showSaveDialog(options)
if (wasPlaying) { if (wasPlaying) {
this.player.play() this.playVideo()
} }
if (response.canceled || response.filePath === '') { if (response.canceled || response.filePath === '') {
canvas.remove() canvas.remove()
@ -1950,6 +1956,34 @@ export default defineComponent({
(process.platform === 'darwin' && event.metaKey)) (process.platform === 'darwin' && event.metaKey))
}, },
playVideo(thenFunc = null) {
// It can be called in `setTimeout` & user can navigate to other pages before it runs
// Which makes `this.player` become `null`
if (this.player == null) { return }
let promise = this.player.play()
if (typeof thenFunc === 'function') {
promise = promise.then(thenFunc)
}
promise
.catch(err => {
if (err.message.includes('The play() request was interrupted by a new load request.')) {
// Ignoring expected exception
// This is thrown when `play()` called but user already viewing another page
// console.debug('Ignoring expected error')
// console.debug(err)
return
}
// Unexpected errors should be reported
console.error(err)
const errorMessage = this.$t('play() request Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
})
},
// This function should always be at the bottom of this file // This function should always be at the bottom of this file
/** /**
* @param {KeyboardEvent} event * @param {KeyboardEvent} event

View File

@ -190,7 +190,11 @@ export default defineComponent({
}, },
methods: { methods: {
handleInvidiousInstanceInput: function (input) { handleInvidiousInstanceInput: function (input) {
const instance = input.replace(/\/$/, '') let instance = input
// If NOT something like https:// (1-2 slashes), remove trailing slash
if (!/^(https?):(\/){1,2}$/.test(input)) {
instance = input.replace(/\/$/, '')
}
this.setCurrentInvidiousInstanceBounce(instance) this.setCurrentInvidiousInstanceBounce(instance)
}, },

View File

@ -33,8 +33,9 @@ export default defineComponent({
return this.$store.getters.getActiveProfile return this.$store.getters.getActiveProfile
}, },
activeSubscriptions: function () { activeSubscriptions: function () {
const profile = JSON.parse(JSON.stringify(this.activeProfile)) const subscriptions = JSON.parse(JSON.stringify(this.activeProfile.subscriptions))
return profile.subscriptions.sort((a, b) => {
subscriptions.sort((a, b) => {
const nameA = a.name.toLowerCase() const nameA = a.name.toLowerCase()
const nameB = b.name.toLowerCase() const nameB = b.name.toLowerCase()
if (nameA < nameB) { if (nameA < nameB) {
@ -44,13 +45,15 @@ export default defineComponent({
return 1 return 1
} }
return 0 return 0
}).map((channel) => {
if (this.backendPreference === 'invidious') {
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
}
return channel
}) })
if (this.backendPreference === 'invidious') {
subscriptions.forEach((channel) => {
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
})
}
return subscriptions
}, },
hidePopularVideos: function () { hidePopularVideos: function () {
return this.$store.getters.getHidePopularVideos return this.$store.getters.getHidePopularVideos

View File

@ -0,0 +1,335 @@
import { defineComponent } from 'vue'
import { mapActions, mapMutations } from 'vuex'
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
import { copyToClipboard, showToast } from '../../helpers/utils'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { getLocalChannelLiveStreams } from '../../helpers/api/local'
import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
export default defineComponent({
name: 'SubscriptionsLive',
components: {
'subscriptions-tab-ui': SubscriptionsTabUI
},
data: function () {
return {
isLoading: false,
videoList: [],
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
},
useRssFeeds: function () {
return this.$store.getters.getUseRssFeeds
},
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.getLiveCacheByChannel(channel.id)
if (cacheEntry == null) { return }
entries.push(cacheEntry)
})
return entries
},
videoCacheForAllActiveProfileChannelsPresent() {
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
return cacheEntry.videos != null
})
},
activeSubscriptionList: function () {
return this.activeProfile.subscriptions
},
fetchSubscriptionsAutomatically: function() {
return this.$store.getters.getFetchSubscriptionsAutomatically
},
},
watch: {
activeProfile: async function (_) {
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
},
mounted: async function () {
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
methods: {
loadVideosFromCacheSometimes() {
// This method is called on view visible
if (this.videoCacheForAllActiveProfileChannelsPresent) {
this.loadVideosFromCacheForAllActiveProfileChannels()
return
}
this.maybeLoadVideosForSubscriptionsFromRemote()
},
async loadVideosFromCacheForAllActiveProfileChannels() {
const videoList = []
this.activeSubscriptionList.forEach((channel) => {
const channelCacheEntry = this.$store.getters.getLiveCacheByChannel(channel.id)
videoList.push(...channelCacheEntry.videos)
})
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
},
loadVideosForSubscriptionsFromRemote: async function () {
if (this.activeSubscriptionList.length === 0) {
this.isLoading = false
this.videoList = []
return
}
const channelsToLoadFromRemote = this.activeSubscriptionList
const videoList = []
let channelCount = 0
this.isLoading = true
let useRss = this.useRssFeeds
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
showToast(
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
10000
)
useRss = true
}
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
this.attemptedFetch = true
this.errorChannels = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
if (useRss) {
videos = await this.getChannelLiveInvidiousRSS(channel)
} else {
videos = await this.getChannelLiveInvidious(channel)
}
} else {
if (useRss) {
videos = await this.getChannelLiveLocalRSS(channel)
} else {
videos = await this.getChannelLiveLocal(channel)
}
}
channelCount++
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
this.setProgressBarPercentage(percentageComplete)
this.updateSubscriptionLiveCacheByChannel({
channelId: channel.id,
videos: videos,
})
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
if (this.fetchSubscriptionsAutomatically) {
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
await this.loadVideosForSubscriptionsFromRemote()
} else {
this.videoList = []
this.attemptedFetch = false
this.isLoading = false
}
},
getChannelLiveLocal: async function (channel, failedAttempts = 0) {
try {
const entries = await getLocalChannelLiveStreams(channel.id)
if (entries === null) {
this.errorChannels.push(channel)
return []
}
addPublishedDatesLocal(entries)
return entries
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
switch (failedAttempts) {
case 0:
return await this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
case 1:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return await this.getChannelLiveInvidious(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return await this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
default:
return []
}
}
},
getChannelLiveLocalRSS: async function (channel, failedAttempts = 0) {
const playlistId = channel.id.replace('UC', 'UULV')
const feedUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`
try {
const response = await fetch(feedUrl)
if (response.status === 404) {
return []
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
return this.getChannelLiveLocal(channel, failedAttempts + 1)
case 1:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return this.getChannelLiveLocal(channel, failedAttempts + 1)
default:
return []
}
}
},
getChannelLiveInvidious: function (channel, failedAttempts = 0) {
return new Promise((resolve, reject) => {
const subscriptionsPayload = {
resource: 'channels',
id: channel.id,
subResource: 'streams',
params: {}
}
invidiousAPICall(subscriptionsPayload).then((result) => {
const videos = result.videos.filter(e => e.type === 'video')
addPublishedDatesInvidious(videos)
resolve(videos)
}).catch((err) => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
copyToClipboard(err.responseText)
})
switch (failedAttempts) {
case 0:
resolve(this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1))
break
case 1:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
resolve(this.getChannelLiveLocal(channel, failedAttempts + 1))
} else {
resolve([])
}
break
case 2:
resolve(this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1))
break
default:
resolve([])
}
})
})
},
getChannelLiveInvidiousRSS: async function (channel, failedAttempts = 0) {
const playlistId = channel.id.replace('UC', 'UULV')
const feedUrl = `${this.currentInvidiousInstance}/feed/playlist/${playlistId}`
try {
const response = await fetch(feedUrl)
if (response.status === 500) {
return []
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
return this.getChannelLiveInvidious(channel, failedAttempts + 1)
case 1:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
return this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return this.getChannelLiveInvidious(channel, failedAttempts + 1)
default:
return []
}
}
},
...mapActions([
'updateShowProgressBar',
'updateSubscriptionLiveCacheByChannel',
]),
...mapMutations([
'setProgressBarPercentage'
])
}
})

View File

@ -0,0 +1,11 @@
<template>
<subscriptions-tab-ui
:is-loading="isLoading"
:video-list="videoList"
:error-channels="errorChannels"
:attempted-fetch="attemptedFetch"
@refresh="loadVideosForSubscriptionsFromRemote"
/>
</template>
<script src="./subscriptions-live.js" />

View File

@ -0,0 +1,225 @@
import { defineComponent } from 'vue'
import { mapActions, mapMutations } from 'vuex'
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
import { parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
import { copyToClipboard, showToast } from '../../helpers/utils'
export default defineComponent({
name: 'SubscriptionsShorts',
components: {
'subscriptions-tab-ui': SubscriptionsTabUI
},
data: function () {
return {
isLoading: false,
videoList: [],
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.getShortsCacheByChannel(channel.id)
if (cacheEntry == null) { return }
entries.push(cacheEntry)
})
return entries
},
videoCacheForAllActiveProfileChannelsPresent() {
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
return cacheEntry.videos != null
})
},
activeSubscriptionList: function () {
return this.activeProfile.subscriptions
},
fetchSubscriptionsAutomatically: function() {
return this.$store.getters.getFetchSubscriptionsAutomatically
},
},
watch: {
activeProfile: async function (_) {
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
},
mounted: async function () {
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
methods: {
loadVideosFromCacheSometimes() {
// This method is called on view visible
if (this.videoCacheForAllActiveProfileChannelsPresent) {
this.loadVideosFromCacheForAllActiveProfileChannels()
return
}
this.maybeLoadVideosForSubscriptionsFromRemote()
},
async loadVideosFromCacheForAllActiveProfileChannels() {
const videoList = []
this.activeSubscriptionList.forEach((channel) => {
const channelCacheEntry = this.$store.getters.getShortsCacheByChannel(channel.id)
videoList.push(...channelCacheEntry.videos)
})
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
},
loadVideosForSubscriptionsFromRemote: async function () {
if (this.activeSubscriptionList.length === 0) {
this.isLoading = false
this.videoList = []
return
}
const channelsToLoadFromRemote = this.activeSubscriptionList
const videoList = []
let channelCount = 0
this.isLoading = true
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
this.attemptedFetch = true
this.errorChannels = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
videos = await this.getChannelShortsInvidious(channel)
} else {
videos = await this.getChannelShortsLocal(channel)
}
channelCount++
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
this.setProgressBarPercentage(percentageComplete)
this.updateSubscriptionShortsCacheByChannel({
channelId: channel.id,
videos: videos,
})
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
if (this.fetchSubscriptionsAutomatically) {
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
await this.loadVideosForSubscriptionsFromRemote()
} else {
this.videoList = []
this.attemptedFetch = false
this.isLoading = false
}
},
getChannelShortsLocal: async function (channel, failedAttempts = 0) {
const playlistId = channel.id.replace('UC', 'UUSH')
const feedUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`
try {
const response = await fetch(feedUrl)
if (response.status === 404) {
return []
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelShortsInvidious(channel, failedAttempts + 1)
} else {
return []
}
default:
return []
}
}
},
getChannelShortsInvidious: async function (channel, failedAttempts = 0) {
const playlistId = channel.id.replace('UC', 'UUSH')
const feedUrl = `${this.currentInvidiousInstance}/feed/playlist/${playlistId}`
try {
const response = await fetch(feedUrl)
if (response.status === 500) {
return []
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
return this.getChannelShortsLocal(channel, failedAttempts + 1)
} else {
return []
}
default:
return []
}
}
},
...mapActions([
'updateShowProgressBar',
'updateSubscriptionShortsCacheByChannel',
]),
...mapMutations([
'setProgressBarPercentage'
])
}
})

View File

@ -0,0 +1,11 @@
<template>
<subscriptions-tab-ui
:is-loading="isLoading"
:video-list="videoList"
:error-channels="errorChannels"
:attempted-fetch="attemptedFetch"
@refresh="loadVideosForSubscriptionsFromRemote"
/>
</template>
<script src="./subscriptions-shorts.js" />

View File

@ -0,0 +1,31 @@
.card {
width: 85%;
margin: 0 auto;
margin-bottom: 60px;
}
.message {
color: var(--tertiary-text-color);
}
.floatingTopButton {
position: fixed;
top: 70px;
right: 10px;
}
.channelBubble {
display: inline-block;
}
@media only screen and (max-width: 350px) {
.floatingTopButton {
position: absolute
}
}
@media only screen and (max-width: 680px) {
.card {
width: 90%;
}
}

View File

@ -0,0 +1,111 @@
import { defineComponent } from 'vue'
import FtLoader from '../ft-loader/ft-loader.vue'
import FtCard from '../ft-card/ft-card.vue'
import FtButton from '../ft-button/ft-button.vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtElementList from '../ft-element-list/ft-element-list.vue'
import FtChannelBubble from '../ft-channel-bubble/ft-channel-bubble.vue'
export default defineComponent({
name: 'SubscriptionsTabUI',
components: {
'ft-loader': FtLoader,
'ft-card': FtCard,
'ft-button': FtButton,
'ft-icon-button': FtIconButton,
'ft-flex-box': FtFlexBox,
'ft-element-list': FtElementList,
'ft-channel-bubble': FtChannelBubble
},
props: {
isLoading: {
type: Boolean,
default: false
},
videoList: {
type: Array,
default: () => ([])
},
errorChannels: {
type: Array,
default: () => ([])
},
attemptedFetch: {
type: Boolean,
default: false
},
},
data: function () {
return {
dataLimit: 100,
}
},
computed: {
activeVideoList: function () {
if (this.videoList.length < this.dataLimit) {
return this.videoList
} else {
return this.videoList.slice(0, this.dataLimit)
}
},
activeProfile: function () {
return this.$store.getters.getActiveProfile
},
activeSubscriptionList: function () {
return this.activeProfile.subscriptions
},
fetchSubscriptionsAutomatically: function() {
return this.$store.getters.getFetchSubscriptionsAutomatically
},
},
created: function () {
const dataLimit = sessionStorage.getItem('subscriptionLimit')
if (dataLimit !== null) {
this.dataLimit = dataLimit
}
},
mounted: async function () {
document.addEventListener('keydown', this.keyboardShortcutHandler)
},
beforeDestroy: function () {
document.removeEventListener('keydown', this.keyboardShortcutHandler)
},
methods: {
goToChannel: function (id) {
this.$router.push({ path: `/channel/${id}` })
},
increaseLimit: function () {
this.dataLimit += 100
sessionStorage.setItem('subscriptionLimit', this.dataLimit)
},
/**
* This function `keyboardShortcutHandler` should always be at the bottom of this file
* @param {KeyboardEvent} event the keyboard event
*/
keyboardShortcutHandler: function (event) {
if (event.ctrlKey || document.activeElement.classList.contains('ft-input')) {
return
}
// Avoid handling events due to user holding a key (not released)
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat
if (event.repeat) { return }
switch (event.key) {
case 'r':
case 'R':
if (!this.isLoading) {
this.$emit('refresh')
}
break
}
}
}
})

View File

@ -0,0 +1,72 @@
<template>
<div>
<ft-loader
v-if="isLoading"
/>
<div
v-if="!isLoading && errorChannels.length !== 0"
>
<h3> {{ $t("Subscriptions.Error Channels") }}</h3>
<div>
<ft-channel-bubble
v-for="(channel, index) in errorChannels"
:key="index"
:channel-name="channel.name"
:channel-id="channel.id"
:channel-thumbnail="channel.thumbnail"
class="channelBubble"
@click="goToChannel(channel.id)"
/>
</div>
</div>
<ft-flex-box
v-if="!isLoading && activeVideoList.length === 0"
>
<p
v-if="activeSubscriptionList.length === 0"
class="message"
>
{{ $t("Subscriptions['Your Subscription list is currently empty. Start adding subscriptions to see them here.']") }}
</p>
<p
v-else-if="!fetchSubscriptionsAutomatically && !attemptedFetch"
class="message"
>
{{ $t("Subscriptions.Disabled Automatic Fetching") }}
</p>
<p
v-else
class="message"
>
{{ $t("Subscriptions.Empty Channels") }}
</p>
</ft-flex-box>
<ft-element-list
v-if="!isLoading && activeVideoList.length > 0"
:data="activeVideoList"
:use-channels-hidden-preference="false"
/>
<ft-flex-box
v-if="!isLoading && videoList.length > dataLimit"
>
<ft-button
:label="$t('Subscriptions.Load More Videos')"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="increaseLimit"
/>
</ft-flex-box>
<ft-icon-button
v-if="!isLoading"
:icon="['fas', 'sync']"
class="floatingTopButton"
:title="$t('Subscriptions.Refresh Subscriptions')"
:size="12"
theme="primary"
@click="$emit('refresh')"
/>
</div>
</template>
<script src="./subscriptions-tab-ui.js" />
<style scoped src="./subscriptions-tab-ui.css" />

View File

@ -0,0 +1,334 @@
import { defineComponent } from 'vue'
import { mapActions, mapMutations } from 'vuex'
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
import { copyToClipboard, showToast } from '../../helpers/utils'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { getLocalChannelVideos } from '../../helpers/api/local'
import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
export default defineComponent({
name: 'SubscriptionsVideos',
components: {
'subscriptions-tab-ui': SubscriptionsTabUI
},
data: function () {
return {
isLoading: false,
videoList: [],
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
},
useRssFeeds: function () {
return this.$store.getters.getUseRssFeeds
},
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.getVideoCacheByChannel(channel.id)
if (cacheEntry == null) { return }
entries.push(cacheEntry)
})
return entries
},
videoCacheForAllActiveProfileChannelsPresent() {
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
return cacheEntry.videos != null
})
},
activeSubscriptionList: function () {
return this.activeProfile.subscriptions
},
fetchSubscriptionsAutomatically: function() {
return this.$store.getters.getFetchSubscriptionsAutomatically
},
},
watch: {
activeProfile: async function (_) {
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
},
mounted: async function () {
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
methods: {
loadVideosFromCacheSometimes() {
// This method is called on view visible
if (this.videoCacheForAllActiveProfileChannelsPresent) {
this.loadVideosFromCacheForAllActiveProfileChannels()
return
}
this.maybeLoadVideosForSubscriptionsFromRemote()
},
async loadVideosFromCacheForAllActiveProfileChannels() {
const videoList = []
this.activeSubscriptionList.forEach((channel) => {
const channelCacheEntry = this.$store.getters.getVideoCacheByChannel(channel.id)
videoList.push(...channelCacheEntry.videos)
})
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
},
loadVideosForSubscriptionsFromRemote: async function () {
if (this.activeSubscriptionList.length === 0) {
this.isLoading = false
this.videoList = []
return
}
const channelsToLoadFromRemote = this.activeSubscriptionList
const videoList = []
let channelCount = 0
this.isLoading = true
let useRss = this.useRssFeeds
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
showToast(
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
10000
)
useRss = true
}
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
this.attemptedFetch = true
this.errorChannels = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
if (useRss) {
videos = await this.getChannelVideosInvidiousRSS(channel)
} else {
videos = await this.getChannelVideosInvidiousScraper(channel)
}
} else {
if (useRss) {
videos = await this.getChannelVideosLocalRSS(channel)
} else {
videos = await this.getChannelVideosLocalScraper(channel)
}
}
channelCount++
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
this.setProgressBarPercentage(percentageComplete)
this.updateSubscriptionVideosCacheByChannel({
channelId: channel.id,
videos: videos,
})
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
if (this.fetchSubscriptionsAutomatically) {
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
await this.loadVideosForSubscriptionsFromRemote()
} else {
this.videoList = []
this.attemptedFetch = false
this.isLoading = false
}
},
getChannelVideosLocalScraper: async function (channel, failedAttempts = 0) {
try {
const videos = await getLocalChannelVideos(channel.id)
if (videos === null) {
this.errorChannels.push(channel)
return []
}
addPublishedDatesLocal(videos)
return videos
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
switch (failedAttempts) {
case 0:
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
case 1:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return await this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
default:
return []
}
}
},
getChannelVideosLocalRSS: async function (channel, failedAttempts = 0) {
const playlistId = channel.id.replace('UC', 'UULF')
const feedUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`
try {
const response = await fetch(feedUrl)
if (response.status === 404) {
this.errorChannels.push(channel)
return []
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
case 1:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
default:
return []
}
}
},
getChannelVideosInvidiousScraper: function (channel, failedAttempts = 0) {
return new Promise((resolve, reject) => {
const subscriptionsPayload = {
resource: 'channels/latest',
id: channel.id,
params: {}
}
invidiousAPICall(subscriptionsPayload).then((result) => {
addPublishedDatesInvidious(result.videos)
resolve(result.videos)
}).catch((err) => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
copyToClipboard(err.responseText)
})
switch (failedAttempts) {
case 0:
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
break
case 1:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
} else {
resolve([])
}
break
case 2:
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
break
default:
resolve([])
}
})
})
},
getChannelVideosInvidiousRSS: async function (channel, failedAttempts = 0) {
const playlistId = channel.id.replace('UC', 'UULF')
const feedUrl = `${this.currentInvidiousInstance}/feed/playlist/${playlistId}`
try {
const response = await fetch(feedUrl)
if (response.status === 500) {
this.errorChannels.push(channel)
return []
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
case 1:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
default:
return []
}
}
},
...mapActions([
'updateShowProgressBar',
'updateSubscriptionVideosCacheByChannel',
]),
...mapMutations([
'setProgressBarPercentage'
])
}
})

View File

@ -0,0 +1,11 @@
<template>
<subscriptions-tab-ui
:is-loading="isLoading"
:video-list="videoList"
:error-channels="errorChannels"
:attempted-fetch="attemptedFetch"
@refresh="loadVideosForSubscriptionsFromRemote"
/>
</template>
<script src="./subscriptions-videos.js" />

View File

@ -22,6 +22,7 @@
:data="video" :data="video"
appearance="recommendation" appearance="recommendation"
force-list-type="list" force-list-type="list"
:use-channels-hidden-preference="true"
/> />
</ft-card> </ft-card>
</template> </template>

View File

@ -152,27 +152,6 @@ export async function getLocalVideoInfo(id, attemptBypass = false) {
player = innertube.actions.session.player player = innertube.actions.session.player
info = await innertube.getInfo(id) info = await innertube.getInfo(id)
// // the android streaming formats don't seem to be throttled at the moment so we use those if they are availabe
try {
const androidInnertube = await createInnertube({ clientType: ClientType.ANDROID, generateSessionLocally: false })
const androidInfo = await androidInnertube.getBasicInfo(id, 'ANDROID')
// Sometimes when YouTube detects a third party client or has applied an IP-ratelimit,
// they replace the response with a different video id
// https://github.com/TeamNewPipe/NewPipe/issues/8713
// https://github.com/TeamPiped/Piped/issues/2487
if (androidInfo.basic_info.id !== id) {
console.error(`Failed to fetch android formats. Wrong video ID in response: ${androidInfo.basic_info.id}, expected: ${id}`)
} else if (androidInfo.playability_status.status !== 'OK') {
console.error('Failed to fetch android formats', JSON.stringify(androidInfo.playability_status))
} else {
info.streaming_data = androidInfo.streaming_data
}
} catch (error) {
console.error('Failed to fetch android formats')
console.error(error)
}
} }
if (info.streaming_data) { if (info.streaming_data) {
@ -188,17 +167,24 @@ export async function getLocalComments(id, sortByNewest = false) {
return innertube.getComments(id, sortByNewest ? 'NEWEST_FIRST' : 'TOP_COMMENTS') return innertube.getComments(id, sortByNewest ? 'NEWEST_FIRST' : 'TOP_COMMENTS')
} }
// I know `type & type` is typescript syntax and not valid jsdoc but I couldn't get @extends or @augments to work
/**
* @typedef {object} _LocalFormat
* @property {string} freeTubeUrl deciphered streaming URL, stored in a custom property so the DASH manifest generation doesn't break
*
* @typedef {Misc.Format & _LocalFormat} LocalFormat
*/
/** /**
* @param {Misc.Format[]} formats * @param {Misc.Format[]} formats
* @param {import('youtubei.js').Player} player * @param {import('youtubei.js').Player} player
*/ */
function decipherFormats(formats, player) { function decipherFormats(formats, player) {
for (const format of formats) { for (const format of formats) {
format.url = format.decipher(player) // toDash deciphers the format again, so if we overwrite the original URL,
// it breaks because the n param would get deciphered twice and then be incorrect
// set these to undefined so that toDash doesn't try to decipher them again, throwing an error format.freeTubeUrl = format.decipher(player)
format.cipher = undefined
format.signature_cipher = undefined
} }
} }
@ -270,6 +256,36 @@ export async function getLocalChannelVideos(id) {
} }
} }
export async function getLocalChannelLiveStreams(id) {
const innertube = await createInnertube()
try {
const response = await innertube.actions.execute(Endpoints.BrowseEndpoint.PATH, Endpoints.BrowseEndpoint.build({
browse_id: id,
params: 'EgdzdHJlYW1z8gYECgJ6AA%3D%3D'
// protobuf for the live 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 liveStreamsTab = new YT.Channel(null, response)
// if the channel doesn't have a live tab, YouTube returns the home tab instead
// so we need to check that we got the right tab
if (liveStreamsTab.current_tab?.endpoint.metadata.url?.endsWith('/streams')) {
return parseLocalChannelVideos(liveStreamsTab.videos, liveStreamsTab.header.author)
} 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 {import('youtubei.js').YTNodes.Video[]} videos
* @param {Misc.Author} author * @param {Misc.Author} author
@ -377,8 +393,7 @@ export function parseLocalListPlaylist(playlist, author = undefined) {
let channelId = null let channelId = null
/** @type {import('youtubei.js').YTNodes.PlaylistVideoThumbnail} */ /** @type {import('youtubei.js').YTNodes.PlaylistVideoThumbnail} */
const thumbnailRenderer = playlist.thumbnail_renderer const thumbnailRenderer = playlist.thumbnail_renderer
if (playlist.author && playlist.author.id !== 'N/A') {
if (playlist.author) {
if (playlist.author instanceof Misc.Text) { if (playlist.author instanceof Misc.Text) {
channelName = playlist.author.text channelName = playlist.author.text
@ -425,7 +440,8 @@ function handleSearchResponse(response) {
return { return {
results, results,
continuationData: response.has_continuation ? response : null // check the length of the results, as there can be continuations for things that we've filtered out, which we don't want
continuationData: response.has_continuation && results.length > 0 ? response : null
} }
} }
@ -451,11 +467,47 @@ export function parseLocalPlaylistVideo(video) {
/** @type {import('youtubei.js').YTNodes.PlaylistVideo} */ /** @type {import('youtubei.js').YTNodes.PlaylistVideo} */
const video_ = video const video_ = video
let viewCount = null
// the accessiblity label contains the full view count
// the video info only contains the short view count
if (video_.accessibility_label) {
const match = video_.accessibility_label.match(/([\d,.]+|no) views?$/i)
if (match) {
const count = match[1]
// as it's rare that a video has no views,
// checking the length allows us to avoid running toLowerCase unless we have to
if (count.length === 2 && count.toLowerCase() === 'no') {
viewCount = 0
} else {
const views = extractNumberFromString(count)
if (!isNaN(views)) {
viewCount = views
}
}
}
}
let publishedText = null
// normal videos have 3 text runs with the last one containing the published date
// live videos have 2 text runs with the number of people watching
// upcoming either videos don't have any info text or the number of people waiting,
// but we have the premiere date for those, so we don't need the published date
if (video_.video_info.runs && video_.video_info.runs.length === 3) {
publishedText = video_.video_info.runs[2].text
}
return { return {
videoId: video_.id, videoId: video_.id,
title: video_.title.text, title: video_.title.text,
author: video_.author.name, author: video_.author.name,
authorId: video_.author.id, authorId: video_.author.id,
viewCount,
publishedText,
lengthSeconds: isNaN(video_.duration.seconds) ? '' : video_.duration.seconds, lengthSeconds: isNaN(video_.duration.seconds) ? '' : video_.duration.seconds,
liveNow: video_.is_live, liveNow: video_.is_live,
isUpcoming: video_.is_upcoming, isUpcoming: video_.is_upcoming,
@ -705,7 +757,7 @@ export function parseLocalTextRuns(runs, emojiSize = 16, options = { looseChanne
} }
/** /**
* @param {Misc.Format} format * @param {LocalFormat} format
*/ */
export function mapLocalFormat(format) { export function mapLocalFormat(format) {
return { return {
@ -716,7 +768,7 @@ export function mapLocalFormat(format) {
mimeType: format.mime_type, mimeType: format.mime_type,
height: format.height, height: format.height,
width: format.width, width: format.width,
url: format.url url: format.freeTubeUrl
} }
} }

View File

@ -0,0 +1,146 @@
import store from '../store/index'
import { calculatePublishedDate } from './utils'
/**
* Filtering and sort based on user preferences
* @param {any[]} videos
*/
export function updateVideoListAfterProcessing(videos) {
let videoList = videos
// Filtering and sorting based in preference
videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
})
if (store.getters.getHideLiveStreams) {
videoList = videoList.filter(item => {
return (!item.liveNow && !item.isUpcoming)
})
}
if (store.getters.getHideUpcomingPremieres) {
videoList = videoList.filter(item => {
if (item.isRSS) {
// viewCount is our only method of detecting premieres in RSS
// data without sending an additional request.
// If we ever get a better flag, use it here instead.
return item.viewCount !== '0'
}
// Observed for premieres in Local API Subscriptions.
return (item.premiereDate == null ||
// Invidious API
// `premiereTimestamp` only available on premiered videos
// https://docs.invidious.io/api/common_types/#videoobject
item.premiereTimestamp == null
)
})
}
if (store.getters.getHideWatchedSubs) {
const historyCache = store.getters.getHistoryCache
videoList = videoList.filter((video) => {
const historyIndex = historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return historyIndex === -1
})
}
return videoList
}
/**
* @param {string} rssString
* @param {string} channelId
*/
export async function parseYouTubeRSSFeed(rssString, channelId) {
// doesn't need to be asynchronous, but doing it allows us to do the relatively slow DOM querying in parallel
try {
const xmlDom = new DOMParser().parseFromString(rssString, 'application/xml')
const channelName = xmlDom.querySelector('author > name').textContent
const entries = xmlDom.querySelectorAll('entry')
const promises = []
for (const entry of entries) {
promises.push(parseRSSEntry(entry, channelId, channelName))
}
return await Promise.all(promises)
} catch (e) {
return []
}
}
/**
* @param {Element} entry
* @param {string} channelId
* @param {string} channelName
*/
async function parseRSSEntry(entry, channelId, channelName) {
// doesn't need to be asynchronous, but doing it allows us to do the relatively slow DOM querying in parallel
const published = new Date(entry.querySelector('published').textContent)
return {
authorId: channelId,
author: channelName,
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
title: entry.querySelector('title').textContent,
publishedDate: published,
publishedText: published.toLocaleString(),
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
type: 'video',
lengthSeconds: '0:00',
isRSS: true
}
}
/**
* @param {{
* liveNow: boolean,
* isUpcoming: boolean,
* premiereDate: Date,
* publishedText: string,
* publishedDate: number
* }[]} videos publishedDate is added by this function,
* but adding it to the type definition stops vscode warning that the property doesn't exist
*/
export function addPublishedDatesLocal(videos) {
videos.forEach(video => {
if (video.liveNow) {
video.publishedDate = new Date().getTime()
} else if (video.isUpcoming) {
video.publishedDate = video.premiereDate
} else {
video.publishedDate = calculatePublishedDate(video.publishedText)
}
return video
})
}
/**
* @param {{
* liveNow: boolean,
* isUpcoming: boolean,
* premiereTimestamp: number,
* published: number,
* publishedDate: number
* }[]} videos publishedDate is added by this function,
* but adding it to the type definition stops vscode warning that the property doesn't exist
*/
export function addPublishedDatesInvidious(videos) {
videos.forEach(video => {
if (video.liveNow) {
video.publishedDate = new Date().getTime()
} else if (video.isUpcoming) {
video.publishedDate = new Date(video.premiereTimestamp * 1000)
} else {
video.publishedDate = new Date(video.published * 1000)
}
return video
})
}

View File

@ -197,10 +197,10 @@ export function showToast(message, time = null, action = null) {
* a toast with the error is shown. If the copy is successful and * a toast with the error is shown. If the copy is successful and
* there is a success message, a toast with that message is shown. * there is a success message, a toast with that message is shown.
* @param {string} content the content to be copied to the clipboard * @param {string} content the content to be copied to the clipboard
* @param {string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional) * @param {null|string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
* @param {string} messageOnError the message to be displayed as a toast when the copy fails (optional) * @param {null|string} messageOnError the message to be displayed as a toast when the copy fails (optional)
*/ */
export async function copyToClipboard(content, { messageOnSuccess = null, messageOnError = null }) { export async function copyToClipboard(content, { messageOnSuccess = null, messageOnError = null } = {}) {
if (navigator.clipboard !== undefined && window.isSecureContext) { if (navigator.clipboard !== undefined && window.isSecureContext) {
try { try {
await navigator.clipboard.writeText(content) await navigator.clipboard.writeText(content)

View File

@ -228,6 +228,7 @@ $watched-transition-duration: 0.5s;
font-size: 14px; font-size: 14px;
grid-area: infoLine; grid-area: infoLine;
margin-top: 5px; margin-top: 5px;
overflow-wrap: anywhere;
@include is-sidebar-item { @include is-sidebar-item {
font-size: 12px; font-size: 12px;

View File

@ -195,6 +195,8 @@ const state = {
hideActiveSubscriptions: false, hideActiveSubscriptions: false,
hideChannelCommunity: false, hideChannelCommunity: false,
hideChannelPlaylists: false, hideChannelPlaylists: false,
hideChannelReleases: false,
hideChannelPodcasts: false,
hideChannelShorts: false, hideChannelShorts: false,
hideChannelSubscriptions: false, hideChannelSubscriptions: false,
hideCommentLikes: false, hideCommentLikes: false,
@ -210,6 +212,9 @@ const state = {
hideRecommendedVideos: false, hideRecommendedVideos: false,
hideSearchBar: false, hideSearchBar: false,
hideSharingActions: false, hideSharingActions: false,
hideSubscriptionsVideos: false,
hideSubscriptionsShorts: false,
hideSubscriptionsLive: false,
hideTrendingVideos: false, hideTrendingVideos: false,
hideUnsubscribeButton: false, hideUnsubscribeButton: false,
hideUpcomingPremieres: false, hideUpcomingPremieres: false,

View File

@ -7,35 +7,91 @@ function deepCopy(obj) {
} }
const state = { const state = {
subscriptionsCachePerChannel: {}, videoCache: {},
liveCache: {},
shortsCache: {}
} }
const getters = { const getters = {
getSubscriptionsCacheEntriesForOneChannel: (state) => (channelId) => { getVideoCache: (state) => {
return state.subscriptionsCachePerChannel[channelId] return state.videoCache
}, },
getVideoCacheByChannel: (state) => (channelId) => {
return state.videoCache[channelId]
},
getShortsCache: (state) => {
return state.shortsCache
},
getShortsCacheByChannel: (state) => (channelId) => {
return state.shortsCache[channelId]
},
getLiveCache: (state) => {
return state.liveCache
},
getLiveCacheByChannel: (state) => (channelId) => {
return state.liveCache[channelId]
}
} }
const actions = { const actions = {
clearSubscriptionsCache: ({ commit }) => { clearSubscriptionVideosCache: ({ commit }) => {
commit('clearSubscriptionsCachePerChannel') commit('clearVideoCache')
}, },
updateSubscriptionsCacheForOneChannel: ({ commit }, payload) => { updateSubscriptionVideosCacheByChannel: ({ commit }, payload) => {
commit('updateSubscriptionsCacheForOneChannel', payload) commit('updateVideoCacheByChannel', payload)
}, },
clearSubscriptionShortsCache: ({ commit }) => {
commit('clearShortsCache')
},
updateSubscriptionShortsCacheByChannel: ({ commit }, payload) => {
commit('updateShortsCacheByChannel', payload)
},
clearSubscriptionLiveCache: ({ commit }) => {
commit('clearLiveCache')
},
updateSubscriptionLiveCacheByChannel: ({ commit }, payload) => {
commit('updateLiveCacheByChannel', payload)
}
} }
const mutations = { const mutations = {
updateSubscriptionsCacheForOneChannel(state, { channelId, videos }) { updateVideoCacheByChannel(state, { channelId, videos }) {
const existingObject = state.subscriptionsCachePerChannel[channelId] const existingObject = state.videoCache[channelId]
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel) const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
if (videos != null) { newObject.videos = videos } if (videos != null) { newObject.videos = videos }
state.subscriptionsCachePerChannel[channelId] = newObject state.videoCache[channelId] = newObject
}, },
clearSubscriptionsCachePerChannel(state) { clearVideoCache(state) {
state.subscriptionsCachePerChannel = {} state.videoCache = {}
}, },
updateShortsCacheByChannel(state, { channelId, videos }) {
const existingObject = state.shortsCache[channelId]
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
if (videos != null) { newObject.videos = videos }
state.shortsCache[channelId] = newObject
},
clearShortsCache(state) {
state.shortsCache = {}
},
updateLiveCacheByChannel(state, { channelId, videos }) {
const existingObject = state.liveCache[channelId]
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
if (videos != null) { newObject.videos = videos }
state.liveCache[channelId] = newObject
},
clearLiveCache(state) {
state.liveCache = {}
}
} }
export default { export default {

View File

@ -317,7 +317,7 @@ const actions = {
let urlType = 'unknown' let urlType = 'unknown'
const channelPattern = const channelPattern =
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|shorts|live|streams|playlists|about|community|channels))?\/?$/ /^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|shorts|live|streams|podcasts|releases|playlists|about|community|channels))?\/?$/
const hashtagPattern = /^\/hashtag\/(?<tag>[^#&/?]+)$/ const hashtagPattern = /^\/hashtag\/(?<tag>[^#&/?]+)$/
@ -439,6 +439,12 @@ const actions = {
case 'playlists': case 'playlists':
subPath = 'playlists' subPath = 'playlists'
break break
case 'podcasts':
subPath = 'podcasts'
break
case 'releases':
subPath = 'releases'
break
case 'channels': case 'channels':
case 'about': case 'about':
subPath = 'about' subPath = 'about'
@ -523,7 +529,15 @@ const actions = {
if (payload.watchProgress > 0 && payload.watchProgress < payload.videoLength - 10) { if (payload.watchProgress > 0 && payload.watchProgress < payload.videoLength - 10) {
if (typeof cmdArgs.startOffset === 'string') { if (typeof cmdArgs.startOffset === 'string') {
args.push(`${cmdArgs.startOffset}${payload.watchProgress}`) if (cmdArgs.startOffset.endsWith('=')) {
// For players using `=` in arguments
// e.g. vlc --start-time=xxxxx
args.push(`${cmdArgs.startOffset}${payload.watchProgress}`)
} else {
// For players using space in arguments
// e.g. smplayer -start xxxxx
args.push(cmdArgs.startOffset, Math.trunc(payload.watchProgress))
}
} else if (!ignoreWarnings) { } else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, 'starting video at offset') showExternalPlayerUnsupportedActionToast(externalPlayer, 'starting video at offset')
} }

View File

@ -62,6 +62,8 @@ export default defineComponent({
videoContinuationData: null, videoContinuationData: null,
shortContinuationData: null, shortContinuationData: null,
liveContinuationData: null, liveContinuationData: null,
releaseContinuationData: null,
podcastContinuationData: null,
playlistContinuationData: null, playlistContinuationData: null,
searchContinuationData: null, searchContinuationData: null,
communityContinuationData: null, communityContinuationData: null,
@ -83,6 +85,8 @@ export default defineComponent({
latestVideos: [], latestVideos: [],
latestShorts: [], latestShorts: [],
latestLive: [], latestLive: [],
latestReleases: [],
latestPodcasts: [],
latestPlaylists: [], latestPlaylists: [],
latestCommunityPosts: [], latestCommunityPosts: [],
searchResults: [], searchResults: [],
@ -104,6 +108,28 @@ export default defineComponent({
playlistSelectValues: [ playlistSelectValues: [
'newest', 'newest',
'last' 'last'
],
autoRefreshOnSortByChangeEnabled: false,
supportedChannelTabs: [
'videos',
'shorts',
'live',
'releases',
'podcasts',
'playlists',
'community',
'about'
],
channelTabs: [
'videos',
'shorts',
'live',
'releases',
'podcasts',
'playlists',
'community',
'about'
] ]
} }
}, },
@ -183,6 +209,10 @@ export default defineComponent({
return !isNullOrEmpty(this.shortContinuationData) return !isNullOrEmpty(this.shortContinuationData)
case 'live': case 'live':
return !isNullOrEmpty(this.liveContinuationData) return !isNullOrEmpty(this.liveContinuationData)
case 'releases':
return !isNullOrEmpty(this.releaseContinuationData)
case 'podcasts':
return !isNullOrEmpty(this.podcastContinuationData)
case 'playlists': case 'playlists':
return !isNullOrEmpty(this.playlistContinuationData) return !isNullOrEmpty(this.playlistContinuationData)
case 'community': case 'community':
@ -209,6 +239,14 @@ export default defineComponent({
return this.$store.getters.getHideLiveStreams return this.$store.getters.getHideLiveStreams
}, },
hideChannelPodcasts: function() {
return this.$store.getters.getHideChannelPodcasts
},
hideChannelReleases: function() {
return this.$store.getters.getHideChannelReleases
},
hideChannelPlaylists: function() { hideChannelPlaylists: function() {
return this.$store.getters.getHideChannelPlaylists return this.$store.getters.getHideChannelPlaylists
}, },
@ -218,36 +256,40 @@ export default defineComponent({
}, },
tabInfoValues: function () { tabInfoValues: function () {
const values = [ const values = [...this.channelTabs]
'videos',
'shorts',
'live',
'playlists',
'community',
'about'
]
const indexToRemove = []
// remove tabs from the array based on user settings // remove tabs from the array based on user settings
if (this.hideChannelShorts) { if (this.hideChannelShorts) {
const index = values.indexOf('shorts') indexToRemove.push(values.indexOf('shorts'))
values.splice(index, 1)
} }
if (this.hideLiveStreams) { if (this.hideLiveStreams) {
const index = values.indexOf('live') indexToRemove.push(values.indexOf('live'))
values.splice(index, 1)
} }
if (this.hideChannelPlaylists) { if (this.hideChannelPlaylists) {
const index = values.indexOf('playlists') indexToRemove.push(values.indexOf('playlists'))
values.splice(index, 1)
} }
if (this.hideChannelCommunity) { if (this.hideChannelCommunity) {
const index = values.indexOf('community') indexToRemove.push(values.indexOf('community'))
values.splice(index, 1)
} }
if (this.hideChannelPodcasts) {
indexToRemove.push(values.indexOf('podcasts'))
}
if (this.hideChannelReleases) {
indexToRemove.push(values.indexOf('releases'))
}
indexToRemove.forEach(index => {
if (index !== -1) {
values.splice(index, 1)
}
})
return values return values
} }
}, },
@ -261,6 +303,9 @@ export default defineComponent({
return return
} }
// Disable auto refresh on sort value change during state reset
this.autoRefreshOnSortByChangeEnabled = false
this.id = this.$route.params.id this.id = this.$route.params.id
this.searchPage = 2 this.searchPage = 2
this.relatedChannels = [] this.relatedChannels = []
@ -272,6 +317,8 @@ export default defineComponent({
this.liveSortBy = 'newest' this.liveSortBy = 'newest'
this.playlistSortBy = 'newest' this.playlistSortBy = 'newest'
this.latestPlaylists = [] this.latestPlaylists = []
this.latestPodcasts = []
this.latestReleases = []
this.latestCommunityPosts = [] this.latestCommunityPosts = []
this.searchResults = [] this.searchResults = []
this.shownElementList = [] this.shownElementList = []
@ -281,6 +328,8 @@ export default defineComponent({
this.shortContinuationData = null this.shortContinuationData = null
this.liveContinuationData = null this.liveContinuationData = null
this.playlistContinuationData = null this.playlistContinuationData = null
this.podcastContinuationData = null
this.releaseContinuationData = null
this.searchContinuationData = null this.searchContinuationData = null
this.communityContinuationData = null this.communityContinuationData = null
this.showSearchBar = true this.showSearchBar = true
@ -300,14 +349,20 @@ export default defineComponent({
this.showShareMenu = true this.showShareMenu = true
this.errorMessage = '' this.errorMessage = ''
// Re-enable auto refresh on sort value change AFTER update done
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') { if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
this.getChannelInfoInvidious() this.getChannelInfoInvidious()
this.autoRefreshOnSortByChangeEnabled = true
} else { } else {
this.getChannelLocal() this.getChannelLocal().finally(() => {
this.autoRefreshOnSortByChangeEnabled = true
})
} }
}, },
videoSortBy () { videoSortBy () {
if (!this.autoRefreshOnSortByChangeEnabled) { return }
this.isElementListLoading = true this.isElementListLoading = true
this.latestVideos = [] this.latestVideos = []
switch (this.apiUsed) { switch (this.apiUsed) {
@ -323,6 +378,8 @@ export default defineComponent({
}, },
shortSortBy() { shortSortBy() {
if (!this.autoRefreshOnSortByChangeEnabled) { return }
this.isElementListLoading = true this.isElementListLoading = true
this.latestShorts = [] this.latestShorts = []
switch (this.apiUsed) { switch (this.apiUsed) {
@ -338,6 +395,8 @@ export default defineComponent({
}, },
liveSortBy () { liveSortBy () {
if (!this.autoRefreshOnSortByChangeEnabled) { return }
this.isElementListLoading = true this.isElementListLoading = true
this.latestLive = [] this.latestLive = []
switch (this.apiUsed) { switch (this.apiUsed) {
@ -353,6 +412,8 @@ export default defineComponent({
}, },
playlistSortBy () { playlistSortBy () {
if (!this.autoRefreshOnSortByChangeEnabled) { return }
this.isElementListLoading = true this.isElementListLoading = true
this.latestPlaylists = [] this.latestPlaylists = []
this.playlistContinuationData = null this.playlistContinuationData = null
@ -386,10 +447,14 @@ export default defineComponent({
return return
} }
// Enable auto refresh on sort value change AFTER initial update done
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') { if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
this.getChannelInfoInvidious() this.getChannelInfoInvidious()
this.autoRefreshOnSortByChangeEnabled = true
} else { } else {
this.getChannelLocal() this.getChannelLocal().finally(() => {
this.autoRefreshOnSortByChangeEnabled = true
})
} }
}, },
methods: { methods: {
@ -593,27 +658,48 @@ export default defineComponent({
this.joined = 0 this.joined = 0
this.location = null this.location = null
} }
const tabs = ['about']
if (channel.has_videos) { if (channel.has_videos) {
tabs.push('videos')
this.getChannelVideosLocal() this.getChannelVideosLocal()
} }
if (!this.hideChannelShorts && channel.has_shorts) { if (!this.hideChannelShorts && channel.has_shorts) {
tabs.push('shorts')
this.getChannelShortsLocal() this.getChannelShortsLocal()
} }
if (!this.hideLiveStreams && channel.has_live_streams) { if (!this.hideLiveStreams && channel.has_live_streams) {
tabs.push('live')
this.getChannelLiveLocal() this.getChannelLiveLocal()
} }
if (!this.hideChannelPodcasts && channel.has_podcasts) {
tabs.push('podcasts')
this.getChannelPodcastsLocal()
}
if (!this.hideChannelReleases && channel.has_releases) {
tabs.push('releases')
this.getChannelReleasesLocal()
}
if (!this.hideChannelPlaylists && channel.has_playlists) { if (!this.hideChannelPlaylists && channel.has_playlists) {
tabs.push('playlists')
this.getChannelPlaylistsLocal() this.getChannelPlaylistsLocal()
} }
if (!this.hideChannelCommunity && channel.has_community) { if (!this.hideChannelCommunity && channel.has_community) {
tabs.push('community')
this.getCommunityPostsLocal() this.getCommunityPostsLocal()
} }
this.channelTabs = this.supportedChannelTabs.filter(tab => {
return tabs.includes(tab)
})
this.currentTab = this.currentOrFirstTab(this.$route.params.currentTab)
this.showSearchBar = channel.has_search this.showSearchBar = channel.has_search
this.isLoading = false this.isLoading = false
@ -882,6 +968,19 @@ export default defineComponent({
// some channels only have a few tabs // some channels only have a few tabs
// here are all possible values: home, videos, shorts, streams, playlists, community, channels, about // here are all possible values: home, videos, shorts, streams, playlists, community, channels, about
const tabs = response.tabs.map(tab => {
if (tab === 'streams') {
return 'live'
}
return tab
})
this.channelTabs = this.supportedChannelTabs.filter(tab => {
return tabs.includes(tab)
})
this.currentTab = this.currentOrFirstTab(this.$route.params.currentTab)
if (response.tabs.includes('videos')) { if (response.tabs.includes('videos')) {
this.channelInvidiousVideos() this.channelInvidiousVideos()
} }
@ -894,6 +993,14 @@ export default defineComponent({
this.channelInvidiousLive() this.channelInvidiousLive()
} }
if (!this.hideChannelPodcasts && response.tabs.includes('podcasts')) {
this.channelInvidiousPodcasts()
}
if (!this.hideChannelReleases && response.tabs.includes('releases')) {
this.channelInvidiousReleases()
}
if (!this.hideChannelPlaylists && response.tabs.includes('playlists')) { if (!this.hideChannelPlaylists && response.tabs.includes('playlists')) {
this.getPlaylistsInvidious() this.getPlaylistsInvidious()
} }
@ -921,8 +1028,9 @@ export default defineComponent({
channelInvidiousVideos: function (sortByChanged) { channelInvidiousVideos: function (sortByChanged) {
const payload = { const payload = {
resource: 'channels/videos', resource: 'channels',
id: this.id, id: this.id,
subResource: 'videos',
params: { params: {
sort_by: this.videoSortBy, sort_by: this.videoSortBy,
} }
@ -1128,7 +1236,8 @@ export default defineComponent({
getPlaylistsInvidious: function () { getPlaylistsInvidious: function () {
this.isElementListLoading = true this.isElementListLoading = true
const payload = { const payload = {
resource: 'channels/playlists', resource: 'channels',
subResource: 'playlists',
id: this.id, id: this.id,
params: { params: {
sort_by: this.playlistSortBy sort_by: this.playlistSortBy
@ -1164,7 +1273,8 @@ export default defineComponent({
} }
const payload = { const payload = {
resource: 'channels/playlists', resource: 'channels',
subResource: 'playlists',
id: this.id, id: this.id,
params: { params: {
sort_by: this.playlistSortBy sort_by: this.playlistSortBy
@ -1194,6 +1304,232 @@ export default defineComponent({
}) })
}, },
getChannelReleasesLocal: async function () {
this.isElementListLoading = true
const expectedId = this.id
try {
/**
* @type {import('youtubei.js').YT.Channel}
*/
const channel = this.channelInstance
const releaseTab = await channel.getReleases()
if (expectedId !== this.id) {
return
}
this.latestReleases = releaseTab.playlists.map(playlist => parseLocalListPlaylist(playlist, channel.header.author))
this.releaseContinuationData = releaseTab.has_continuation ? releaseTab : null
this.isElementListLoading = false
} 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'))
this.getChannelReleasesInvidious()
} else {
this.isLoading = false
}
}
},
getChannelReleasesLocalMore: async function () {
try {
/**
* @type {import('youtubei.js').YT.ChannelListContinuation}
*/
const continuation = await this.releaseContinuationData.getContinuation()
const parsedReleases = continuation.playlists.map(playlist => parseLocalListPlaylist(playlist, this.channelInstance.header.author))
this.latestReleases = this.latestReleases.concat(parsedReleases)
this.releaseContinuationData = continuation.has_continuation ? continuation : null
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
}
},
channelInvidiousReleases: function() {
this.isElementListLoading = true
const payload = {
resource: 'channels',
subResource: 'releases',
id: this.id,
}
invidiousAPICall(payload).then((response) => {
this.releaseContinuationData = response.continuation || null
this.latestReleases = response.playlists
this.isElementListLoading = false
}).catch(async (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 Local API'))
if (!this.channelInstance) {
this.channelInstance = await getLocalChannel(this.id)
}
this.getChannelReleasesLocal()
} else {
this.isLoading = false
}
})
},
channelInvidiousReleasesMore: function () {
if (this.releaseContinuationData === null) {
console.warn('There are no more podcasts available for this channel')
return
}
const payload = {
resource: 'channels',
subResource: 'releases',
id: this.id
}
invidiousAPICall(payload).then((response) => {
this.releaseContinuationData = response.continuation || null
this.latestReleases = this.latestReleases.concat(response.playlists)
this.isElementListLoading = false
}).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 Local API'))
this.getChannelLocal()
} else {
this.isLoading = false
}
})
},
getChannelPodcastsLocal: async function () {
this.isElementListLoading = true
const expectedId = this.id
try {
/**
* @type {import('youtubei.js').YT.Channel}
*/
const channel = this.channelInstance
const podcastTab = await channel.getPodcasts()
if (expectedId !== this.id) {
return
}
this.latestPodcasts = podcastTab.playlists.map(playlist => parseLocalListPlaylist(playlist, channel.header.author))
this.podcastContinuationData = podcastTab.has_continuation ? podcastTab : null
this.isElementListLoading = false
} 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'))
this.getChannelPodcastsInvidious()
} else {
this.isLoading = false
}
}
},
getChannelPodcastsLocalMore: async function () {
try {
/**
* @type {import('youtubei.js').YT.ChannelListContinuation}
*/
const continuation = await this.podcastContinuationData.getContinuation()
const parsedPodcasts = continuation.playlists.map(playlist => parseLocalListPlaylist(playlist, this.channelInstance.header.author))
this.latestPodcasts = this.latestPodcasts.concat(parsedPodcasts)
this.releaseContinuationData = continuation.has_continuation ? continuation : null
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
}
},
channelInvidiousPodcasts: function() {
this.isElementListLoading = true
const payload = {
resource: 'channels',
subResource: 'podcasts',
id: this.id,
}
invidiousAPICall(payload).then((response) => {
this.podcastContinuationData = response.continuation || null
this.latestPodcasts = response.playlists
this.isElementListLoading = false
}).catch(async (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 Local API'))
if (!this.channelInstance) {
this.channelInstance = await getLocalChannel(this.id)
}
this.getChannelPodcastsLocal()
} else {
this.isLoading = false
}
})
},
channelInvidiousPodcastsMore: function () {
if (this.podcastContinuationData === null) {
console.warn('There are no more podcasts available for this channel')
return
}
const payload = {
resource: 'channels',
subResource: 'podcasts',
id: this.id
}
invidiousAPICall(payload).then((response) => {
this.podcastContinuationData = response.continuation || null
this.latestPodcasts = this.latestPodcasts.concat(response.playlists)
this.isElementListLoading = false
}).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 Local API'))
this.getChannelLocal()
} else {
this.isLoading = false
}
})
},
getCommunityPostsLocal: async function () { getCommunityPostsLocal: async function () {
const expectedId = this.id const expectedId = this.id
@ -1333,6 +1669,12 @@ export default defineComponent({
break break
} }
break break
case 'releases':
this.getChannelReleasesLocalMore()
break
case 'podcasts':
this.getChannelPodcastsLocalMore()
break
case 'playlists': case 'playlists':
switch (this.apiUsed) { switch (this.apiUsed) {
case 'local': case 'local':
@ -1470,8 +1812,9 @@ export default defineComponent({
searchChannelInvidious: function () { searchChannelInvidious: function () {
const payload = { const payload = {
resource: 'channels/search', resource: 'channels',
id: this.id, id: this.id,
subResource: 'search',
params: { params: {
q: this.lastSearchQuery, q: this.lastSearchQuery,
page: this.searchPage page: this.searchPage

View File

@ -86,6 +86,7 @@
:aria-label="$t('Channel.Channel Tabs')" :aria-label="$t('Channel.Channel Tabs')"
> >
<div <div
v-if="tabInfoValues.includes('videos')"
id="videosTab" id="videosTab"
class="tab" class="tab"
:class="(currentTab==='videos')?'selectedTab':''" :class="(currentTab==='videos')?'selectedTab':''"
@ -99,7 +100,7 @@
{{ $t("Channel.Videos.Videos").toUpperCase() }} {{ $t("Channel.Videos.Videos").toUpperCase() }}
</div> </div>
<div <div
v-if="!hideChannelShorts" v-if="tabInfoValues.includes('shorts') && !hideChannelShorts"
id="shortsTab" id="shortsTab"
class="tab" class="tab"
:class="(currentTab==='shorts')?'selectedTab':''" :class="(currentTab==='shorts')?'selectedTab':''"
@ -110,10 +111,10 @@
@click="changeTab('shorts')" @click="changeTab('shorts')"
@keydown.left.right.enter.space="changeTab('shorts', $event)" @keydown.left.right.enter.space="changeTab('shorts', $event)"
> >
{{ $t("Channel.Shorts.Shorts").toUpperCase() }} {{ $t("Global.Shorts").toUpperCase() }}
</div> </div>
<div <div
v-if="!hideLiveStreams" v-if="tabInfoValues.includes('live') && !hideLiveStreams"
id="liveTab" id="liveTab"
class="tab" class="tab"
:class="(currentTab==='live')?'selectedTab':''" :class="(currentTab==='live')?'selectedTab':''"
@ -127,7 +128,35 @@
{{ $t("Channel.Live.Live").toUpperCase() }} {{ $t("Channel.Live.Live").toUpperCase() }}
</div> </div>
<div <div
v-if="!hideChannelPlaylists" v-if="tabInfoValues.includes('releases') && !hideChannelReleases"
id="releasesTab"
class="tab"
role="tab"
:aria-selected="String(currentTab === 'releases')"
aria-controls="releasePanel"
:tabindex="currentTab === 'releases' ? 0 : -1"
:class="(currentTab==='releases')?'selectedTab':''"
@click="changeTab('releases')"
@keydown.left.right.enter.space="changeTab('releases', $event)"
>
{{ $t("Channel.Releases.Releases").toUpperCase() }}
</div>
<div
v-if="tabInfoValues.includes('podcasts') && !hideChannelPodcasts"
id="podcastsTab"
class="tab"
role="tab"
:aria-selected="String(currentTab === 'podcasts')"
aria-controls="podcastPanel"
:tabindex="currentTab === 'podcasts' ? 0 : -1"
:class="(currentTab==='podcasts')?'selectedTab':''"
@click="changeTab('podcasts')"
@keydown.left.right.enter.space="changeTab('podcasts', $event)"
>
{{ $t("Channel.Podcasts.Podcasts").toUpperCase() }}
</div>
<div
v-if="tabInfoValues.includes('playlists') && !hideChannelPlaylists"
id="playlistsTab" id="playlistsTab"
class="tab" class="tab"
role="tab" role="tab"
@ -141,7 +170,7 @@
{{ $t("Channel.Playlists.Playlists").toUpperCase() }} {{ $t("Channel.Playlists.Playlists").toUpperCase() }}
</div> </div>
<div <div
v-if="!hideChannelCommunity" v-if="tabInfoValues.includes('community') && !hideChannelCommunity"
id="communityTab" id="communityTab"
class="tab" class="tab"
role="tab" role="tab"
@ -244,6 +273,7 @@
v-show="currentTab === 'videos'" v-show="currentTab === 'videos'"
id="videoPanel" id="videoPanel"
:data="latestVideos" :data="latestVideos"
:use-channels-hidden-preference="false"
role="tabpanel" role="tabpanel"
aria-labelledby="videosTab" aria-labelledby="videosTab"
/> />
@ -258,6 +288,7 @@
v-if="!hideChannelShorts && currentTab === 'shorts'" v-if="!hideChannelShorts && currentTab === 'shorts'"
id="shortPanel" id="shortPanel"
:data="latestShorts" :data="latestShorts"
:use-channels-hidden-preference="false"
role="tabpanel" role="tabpanel"
aria-labelledby="shortsTab" aria-labelledby="shortsTab"
/> />
@ -273,6 +304,7 @@
v-show="currentTab === 'live'" v-show="currentTab === 'live'"
id="livePanel" id="livePanel"
:data="latestLive" :data="latestLive"
:use-channels-hidden-preference="false"
role="tabpanel" role="tabpanel"
aria-labelledby="liveTab" aria-labelledby="liveTab"
/> />
@ -283,10 +315,41 @@
{{ $t("Channel.Live.This channel does not currently have any live streams") }} {{ $t("Channel.Live.This channel does not currently have any live streams") }}
</p> </p>
</ft-flex-box> </ft-flex-box>
<ft-element-list
v-if="!hideChannelPodcasts && currentTab === 'podcasts'"
id="podcastPanel"
:data="latestPodcasts"
:use-channels-hidden-preference="false"
role="tabpanel"
aria-labelledby="podcastsTab"
/>
<ft-flex-box
v-if="!hideChannelPodcasts && currentTab === 'podcasts' && latestPodcasts.length === 0"
>
<p class="message">
{{ $t("Channel.Podcasts.This channel does not currently have any podcasts") }}
</p>
</ft-flex-box>
<ft-element-list
v-if="!hideChannelReleases && currentTab === 'releases'"
id="releasePanel"
:data="latestReleases"
:use-channels-hidden-preference="false"
role="tabpanel"
aria-labelledby="releasesTab"
/>
<ft-flex-box
v-if="!hideChannelReleases && currentTab === 'releases' && latestReleases.length === 0"
>
<p class="message">
{{ $t("Channel.Releases.This channel does not currently have any releases") }}
</p>
</ft-flex-box>
<ft-element-list <ft-element-list
v-if="!hideChannelPlaylists && currentTab === 'playlists'" v-if="!hideChannelPlaylists && currentTab === 'playlists'"
id="playlistPanel" id="playlistPanel"
:data="latestPlaylists" :data="latestPlaylists"
:use-channels-hidden-preference="false"
role="tabpanel" role="tabpanel"
aria-labelledby="playlistsTab" aria-labelledby="playlistsTab"
/> />
@ -301,6 +364,7 @@
v-if="!hideChannelCommunity && currentTab === 'community'" v-if="!hideChannelCommunity && currentTab === 'community'"
id="communityPanel" id="communityPanel"
:data="latestCommunityPosts" :data="latestCommunityPosts"
:use-channels-hidden-preference="false"
role="tabpanel" role="tabpanel"
aria-labelledby="communityTab" aria-labelledby="communityTab"
display="list" display="list"
@ -315,6 +379,7 @@
<ft-element-list <ft-element-list
v-show="currentTab === 'search'" v-show="currentTab === 'search'"
:data="searchResults" :data="searchResults"
:use-channels-hidden-preference="false"
/> />
<ft-flex-box <ft-flex-box
v-if="currentTab === 'search' && searchResults.length === 0" v-if="currentTab === 'search' && searchResults.length === 0"

View File

@ -36,6 +36,7 @@
v-if="activeData.length > 0 && !isLoading" v-if="activeData.length > 0 && !isLoading"
:data="activeData" :data="activeData"
:show-video-with-last-viewed-playlist="true" :show-video-with-last-viewed-playlist="true"
:use-channels-hidden-preference="false"
/> />
<ft-flex-box <ft-flex-box
v-if="showLoadMoreButton" v-if="showLoadMoreButton"

View File

@ -118,10 +118,6 @@ export default defineComponent({
try { try {
const { results, continuationData } = await getLocalSearchResults(payload.query, payload.searchSettings, this.showFamilyFriendlyOnly) const { results, continuationData } = await getLocalSearchResults(payload.query, payload.searchSettings, this.showFamilyFriendlyOnly)
if (results.length === 0) {
return
}
this.apiUsed = 'local' this.apiUsed = 'local'
this.shownResults = results this.shownResults = results

View File

@ -8,20 +8,31 @@
color: var(--tertiary-text-color); color: var(--tertiary-text-color);
} }
.floatingTopButton { .subscriptionTabs {
position: fixed; width: 100%;
top: 70px; margin-top: -3px;
right: 10px; color: var(--tertiary-text-color);
margin-bottom: 10px;
} }
.channelBubble { .selectedTab {
display: inline-block; border-bottom: 3px solid var(--primary-color);
color: var(--primary-text-color);
font-weight: bold;
box-sizing: border-box;
margin-bottom: -3px;
} }
@media only screen and (max-width: 350px) { .tab {
.floatingTopButton { text-align: center;
position: absolute padding: 15px;
} font-size: 15px;
cursor: pointer;
align-self: flex-end;
}
.tab:hover {
font-weight: bold;
} }
@media only screen and (max-width: 680px) { @media only screen and (max-width: 680px) {

View File

@ -1,496 +1,128 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { mapActions, mapMutations } from 'vuex'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtButton from '../../components/ft-button/ft-button.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue'
import { calculatePublishedDate, copyToClipboard, showToast } from '../../helpers/utils' import SubscriptionsVideos from '../../components/subscriptions-videos/subscriptions-videos.vue'
import { invidiousAPICall } from '../../helpers/api/invidious' import SubscriptionsLive from '../../components/subscriptions-live/subscriptions-live.vue'
import { getLocalChannelVideos } from '../../helpers/api/local' import SubscriptionsShorts from '../../components/subscriptions-shorts/subscriptions-shorts.vue'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
export default defineComponent({ export default defineComponent({
name: 'Subscriptions', name: 'Subscriptions',
components: { components: {
'ft-loader': FtLoader, 'subscriptions-videos': SubscriptionsVideos,
'subscriptions-live': SubscriptionsLive,
'subscriptions-shorts': SubscriptionsShorts,
'ft-card': FtCard, 'ft-card': FtCard,
'ft-button': FtButton, 'ft-flex-box': FtFlexBox
'ft-icon-button': FtIconButton,
'ft-flex-box': FtFlexBox,
'ft-element-list': FtElementList,
'ft-channel-bubble': FtChannelBubble
}, },
data: function () { data: function () {
return { return {
isLoading: false, currentTab: 'videos'
dataLimit: 100,
videoList: [],
errorChannels: [],
attemptedFetch: false,
} }
}, },
computed: { computed: {
backendPreference: function () { hideSubscriptionsVideos: function () {
let preference = this.$store.getters.getBackendPreference return this.$store.getters.getHideSubscriptionsVideos
if (preference === 'piped') { },
preference = this.$store.getters.getFallbackPreference hideSubscriptionsShorts: function () {
return this.$store.getters.getHideSubscriptionsShorts
},
hideSubscriptionsLive: function () {
return this.$store.getters.getHideLiveStreams || this.$store.getters.getHideSubscriptionsLive
},
visibleTabs: function () {
const tabs = []
if (!this.hideSubscriptionsVideos) {
tabs.push('videos')
} }
return preference
},
backendFallback: function () { if (!this.hideSubscriptionsShorts) {
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped' tabs.push('shorts')
},
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance
},
hideWatchedSubs: function () {
return this.$store.getters.getHideWatchedSubs
},
useRssFeeds: function () {
return this.$store.getters.getUseRssFeeds
},
activeVideoList: function () {
if (this.videoList.length < this.dataLimit) {
return this.videoList
} else {
return this.videoList.slice(0, this.dataLimit)
} }
},
activeProfile: function () { if (!this.hideSubscriptionsLive) {
return this.$store.getters.getActiveProfile tabs.push('live')
}, }
activeProfileId: function () {
return this.activeProfile._id
},
cacheEntriesForAllActiveProfileChannels() { return tabs
const entries = [] }
this.activeSubscriptionList.forEach((channel) => {
const cacheEntry = this.$store.getters.getSubscriptionsCacheEntriesForOneChannel(channel.id)
if (cacheEntry == null) { return }
entries.push(cacheEntry)
})
return entries
},
videoCacheForAllActiveProfileChannelsPresent() {
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
return cacheEntry.videos != null
})
},
historyCache: function () {
return this.$store.getters.getHistoryCache
},
activeSubscriptionList: function () {
return this.activeProfile.subscriptions
},
hideLiveStreams: function() {
return this.$store.getters.getHideLiveStreams
},
hideUpcomingPremieres: function () {
return this.$store.getters.getHideUpcomingPremieres
},
fetchSubscriptionsAutomatically: function() {
return this.$store.getters.getFetchSubscriptionsAutomatically
},
}, },
watch: { watch: {
activeProfile: async function (_) { currentTab(value) {
this.isLoading = true if (value !== null) {
this.loadVideosFromCacheSometimes() // Save last used tab, restore when view mounted again
sessionStorage.setItem('Subscriptions/currentTab', value)
} else {
sessionStorage.removeItem('Subscriptions/currentTab')
}
}, },
}, /**
mounted: async function () { * @param {string[]} newValue
document.addEventListener('keydown', this.keyboardShortcutHandler) */
visibleTabs: function (newValue) {
this.isLoading = true if (newValue.length === 0) {
const dataLimit = sessionStorage.getItem('subscriptionLimit') this.currentTab = null
if (dataLimit !== null) { } else if (!newValue.includes(this.currentTab)) {
this.dataLimit = dataLimit this.currentTab = newValue[0]
}
} }
this.loadVideosFromCacheSometimes()
}, },
beforeDestroy: function () { created: async function () {
document.removeEventListener('keydown', this.keyboardShortcutHandler) if (this.visibleTabs.length === 0) {
this.currentTab = null
} else {
// Restore currentTab
const lastCurrentTabId = sessionStorage.getItem('Subscriptions/currentTab')
if (lastCurrentTabId !== null) { this.changeTab(lastCurrentTabId) }
}
}, },
methods: { methods: {
loadVideosFromCacheSometimes() { changeTab: function (tab) {
// This method is called on view visible if (tab === this.currentTab) {
if (this.videoCacheForAllActiveProfileChannelsPresent) {
this.loadVideosFromCacheForAllActiveProfileChannels()
return return
} }
this.maybeLoadVideosForSubscriptionsFromRemote() if (this.visibleTabs.includes(tab)) {
}, this.currentTab = tab
async loadVideosFromCacheForAllActiveProfileChannels() {
const videoList = []
this.activeSubscriptionList.forEach((channel) => {
const channelCacheEntry = this.$store.getters.getSubscriptionsCacheEntriesForOneChannel(channel.id)
videoList.push(...channelCacheEntry.videos)
})
this.updateVideoListAfterProcessing(videoList)
this.isLoading = false
},
goToChannel: function (id) {
this.$router.push({ path: `/channel/${id}` })
},
loadVideosForSubscriptionsFromRemote: async function () {
if (this.activeSubscriptionList.length === 0) {
this.isLoading = false
this.videoList = []
return
}
const channelsToLoadFromRemote = this.activeSubscriptionList
const videoList = []
let channelCount = 0
this.isLoading = true
let useRss = this.useRssFeeds
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
showToast(
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
10000
)
useRss = true
}
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
this.attemptedFetch = true
this.errorChannels = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
if (useRss) {
videos = await this.getChannelVideosInvidiousRSS(channel)
} else {
videos = await this.getChannelVideosInvidiousScraper(channel)
}
} else {
if (useRss) {
videos = await this.getChannelVideosLocalRSS(channel)
} else {
videos = await this.getChannelVideosLocalScraper(channel)
}
}
channelCount++
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
this.setProgressBarPercentage(percentageComplete)
this.updateSubscriptionsCacheForOneChannel({
channelId: channel.id,
videos: videos,
})
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
this.updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
},
updateVideoListAfterProcessing(videoList) {
// Filtering and sorting based in preference
videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
})
if (this.hideLiveStreams) {
videoList = videoList.filter(item => {
return (!item.liveNow && !item.isUpcoming)
})
}
if (this.hideUpcomingPremieres) {
videoList = videoList.filter(item => {
if (item.isRSS) {
// viewCount is our only method of detecting premieres in RSS
// data without sending an additional request.
// If we ever get a better flag, use it here instead.
return item.viewCount !== '0'
}
// Observed for premieres in Local API Subscriptions.
return item.premiereDate == null
})
}
this.videoList = videoList.filter((video) => {
if (this.hideWatchedSubs) {
const historyIndex = this.historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return historyIndex === -1
} else {
return true
}
})
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
if (this.fetchSubscriptionsAutomatically) {
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
await this.loadVideosForSubscriptionsFromRemote()
} else { } else {
this.videoList = [] this.currentTab = null
this.attemptedFetch = false
this.isLoading = false
} }
}, },
getChannelVideosLocalScraper: async function (channel, failedAttempts = 0) {
try {
const videos = await getLocalChannelVideos(channel.id)
if (videos === null) {
this.errorChannels.push(channel)
return []
}
videos.map(video => {
if (video.liveNow) {
video.publishedDate = new Date().getTime()
} else if (video.isUpcoming) {
video.publishedDate = video.premiereDate
} else {
video.publishedDate = calculatePublishedDate(video.publishedText)
}
return video
})
return videos
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
switch (failedAttempts) {
case 0:
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
case 1:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return await this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
default:
return []
}
}
},
getChannelVideosLocalRSS: async function (channel, failedAttempts = 0) {
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}`
try {
const response = await fetch(feedUrl)
if (response.status === 404) {
this.errorChannels.push(channel)
return []
}
return await this.parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
case 1:
if (this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
default:
return []
}
}
},
getChannelVideosInvidiousScraper: function (channel, failedAttempts = 0) {
return new Promise((resolve, reject) => {
const subscriptionsPayload = {
resource: 'channels/latest',
id: channel.id,
params: {}
}
invidiousAPICall(subscriptionsPayload).then(async (result) => {
resolve(await Promise.all(result.videos.map((video) => {
if (video.liveNow) {
video.publishedDate = new Date().getTime()
} else if (video.isUpcoming) {
video.publishedDate = new Date(video.premiereTimestamp * 1000)
} else {
video.publishedDate = new Date(video.published * 1000)
}
return video
})))
}).catch((err) => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
copyToClipboard(err.responseText)
})
switch (failedAttempts) {
case 0:
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
break
case 1:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
} else {
resolve([])
}
break
case 2:
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
break
default:
resolve([])
}
})
})
},
getChannelVideosInvidiousRSS: async function (channel, failedAttempts = 0) {
const feedUrl = `${this.currentInvidiousInstance}/feed/channel/${channel.id}`
try {
const response = await fetch(feedUrl)
if (response.status === 500) {
this.errorChannels.push(channel)
return []
}
return await this.parseYouTubeRSSFeed(await response.text(), channel.id)
} catch (error) {
console.error(error)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => {
copyToClipboard(error)
})
switch (failedAttempts) {
case 0:
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
case 1:
if (process.env.IS_ELECTRON && this.backendFallback) {
showToast(this.$t('Falling back to the local API'))
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
} else {
return []
}
case 2:
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
default:
return []
}
}
},
async parseYouTubeRSSFeed(rssString, channelId) {
const xmlDom = new DOMParser().parseFromString(rssString, 'application/xml')
const channelName = xmlDom.querySelector('author > name').textContent
const entries = xmlDom.querySelectorAll('entry')
const promises = []
for (const entry of entries) {
promises.push(this.parseRSSEntry(entry, channelId, channelName))
}
return await Promise.all(promises)
},
async parseRSSEntry(entry, channelId, channelName) {
const published = new Date(entry.querySelector('published').textContent)
return {
authorId: channelId,
author: channelName,
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
title: entry.querySelector('title').textContent,
publishedDate: published,
publishedText: published.toLocaleString(),
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
type: 'video',
lengthSeconds: '0:00',
isRSS: true
}
},
increaseLimit: function () {
this.dataLimit += 100
sessionStorage.setItem('subscriptionLimit', this.dataLimit)
},
/** /**
* This function `keyboardShortcutHandler` should always be at the bottom of this file * @param {KeyboardEvent} event
* @param {KeyboardEvent} event the keyboard event * @param {string} currentTab
*/ */
keyboardShortcutHandler: function (event) { focusTab: function (event, currentTab) {
if (event.ctrlKey || document.activeElement.classList.contains('ft-input')) { if (!event.altKey) {
return event.preventDefault()
const visibleTabs = this.visibleTabs
if (visibleTabs.length === 1) {
this.$emit('showOutlines')
return
}
let index = visibleTabs.indexOf(currentTab)
if (event.key === 'ArrowLeft') {
index--
} else {
index++
}
if (index < 0) {
index = visibleTabs.length - 1
} else if (index > visibleTabs.length - 1) {
index = 0
}
this.$refs[visibleTabs[index]].focus()
this.$emit('showOutlines')
} }
// Avoid handling events due to user holding a key (not released) }
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat
if (event.repeat) { return }
switch (event.key) {
case 'r':
case 'R':
if (!this.isLoading) {
this.loadVideosForSubscriptionsFromRemote()
}
break
}
},
...mapActions([
'updateShowProgressBar',
'updateSubscriptionsCacheForOneChannel',
]),
...mapMutations([
'setProgressBarPercentage'
])
} }
}) })

View File

@ -1,75 +1,80 @@
<template> <template>
<div> <div>
<ft-loader <ft-card class="card">
v-if="isLoading"
:fullscreen="true"
/>
<ft-card
v-else
class="card"
>
<div
v-if="errorChannels.length !== 0"
>
<h3> {{ $t("Subscriptions.Error Channels") }}</h3>
<div>
<ft-channel-bubble
v-for="(channel, index) in errorChannels"
:key="index"
:channel-name="channel.name"
:channel-id="channel.id"
:channel-thumbnail="channel.thumbnail"
class="channelBubble"
@click="goToChannel(channel.id)"
/>
</div>
</div>
<h3>{{ $t("Subscriptions.Subscriptions") }}</h3> <h3>{{ $t("Subscriptions.Subscriptions") }}</h3>
<ft-flex-box <ft-flex-box
v-if="activeVideoList.length === 0" class="subscriptionTabs"
role="tablist"
:aria-label="$t('Subscriptions.Subscriptions Tabs')"
> >
<p <div
v-if="activeSubscriptionList.length === 0" v-if="!hideSubscriptionsVideos"
class="message" ref="videos"
class="tab"
role="tab"
:aria-selected="String(currentTab === 'videos')"
aria-controls="subscriptionsPanel"
:tabindex="currentTab === 'videos' ? 0 : -1"
:class="{ selectedTab: currentTab === 'videos' }"
@click="changeTab('videos')"
@keydown.space.enter.prevent="changeTab('videos')"
@keydown.left.right="focusTab($event, 'videos')"
> >
{{ $t("Subscriptions['Your Subscription list is currently empty. Start adding subscriptions to see them here.']") }} {{ $t("Global.Videos").toUpperCase() }}
</p> </div>
<p <div
v-else-if="!fetchSubscriptionsAutomatically && !attemptedFetch" v-if="!hideSubscriptionsShorts"
class="message" ref="shorts"
class="tab"
role="tab"
:aria-selected="String(currentTab === 'shorts')"
aria-controls="subscriptionsPanel"
:tabindex="currentTab === 'shorts' ? 0 : -1"
:class="{ selectedTab: currentTab === 'shorts' }"
@click="changeTab('shorts')"
@keydown.space.enter.prevent="changeTab('shorts')"
@keydown.left.right="focusTab($event, 'shorts')"
> >
{{ $t("Subscriptions.Disabled Automatic Fetching") }} {{ $t("Global.Shorts").toUpperCase() }}
</p> </div>
<p <div
v-else v-if="!hideSubscriptionsLive"
class="message" ref="live"
class="tab"
role="tab"
:aria-selected="String(currentTab === 'live')"
aria-controls="subscriptionsPanel"
:tabindex="currentTab === 'live' ? 0 : -1"
:class="{ selectedTab: currentTab === 'live' }"
@click="changeTab('live')"
@keydown.space.enter.prevent="changeTab('live')"
@keydown.left.right="focusTab($event, 'live')"
> >
{{ $t("Subscriptions.Empty Channels") }} {{ $t("Global.Live").toUpperCase() }}
</p> </div>
</ft-flex-box> </ft-flex-box>
<ft-element-list <subscriptions-videos
v-else v-if="currentTab === 'videos'"
:data="activeVideoList" id="subscriptionsPanel"
role="tabpanel"
/> />
<ft-flex-box> <subscriptions-shorts
<ft-button v-if="currentTab === 'shorts'"
v-if="videoList.length > dataLimit" id="subscriptionsPanel"
:label="$t('Subscriptions.Load More Videos')" role="tabpanel"
background-color="var(--primary-color)" />
text-color="var(--text-with-main-color)" <subscriptions-live
@click="increaseLimit" v-if="currentTab === 'live'"
/> id="subscriptionsPanel"
</ft-flex-box> role="tabpanel"
/>
<p v-if="currentTab === null">
{{ $t("Subscriptions.All Subscription Tabs Hidden", {
subsection: $t('Settings.Distraction Free Settings.Sections.Subscriptions Page'),
settingsSection: $t('Settings.Distraction Free Settings.Distraction Free Settings')
}) }}
</p>
</ft-card> </ft-card>
<ft-icon-button
v-if="!isLoading"
:icon="['fas', 'sync']"
class="floatingTopButton"
:title="$t('Subscriptions.Refresh Subscriptions')"
:size="12"
theme="primary"
@click="loadVideosForSubscriptionsFromRemote"
/>
</div> </div>
</template> </template>

View File

@ -42,6 +42,7 @@
<ft-element-list <ft-element-list
v-if="activeData.length > 0 && !isLoading" v-if="activeData.length > 0 && !isLoading"
:data="activeData" :data="activeData"
:use-channels-hidden-preference="false"
/> />
<ft-flex-box <ft-flex-box
v-if="showLoadMoreButton" v-if="showLoadMoreButton"

View File

@ -361,7 +361,7 @@ export default defineComponent({
this.isUpcoming = !!result.basic_info.is_upcoming this.isUpcoming = !!result.basic_info.is_upcoming
this.isLiveContent = !!result.basic_info.is_live_content this.isLiveContent = !!result.basic_info.is_live_content
const subCount = parseLocalSubscriberCount(result.secondary_info.owner.subscriber_count.text) const subCount = !result.secondary_info.owner.subscriber_count.isEmpty() ? parseLocalSubscriberCount(result.secondary_info.owner.subscriber_count.text) : NaN
if (!isNaN(subCount)) { if (!isNaN(subCount)) {
this.channelSubscriptionCountText = formatNumber(subCount, subCount >= 10000 ? { notation: 'compact' } : undefined) this.channelSubscriptionCountText = formatNumber(subCount, subCount >= 10000 ? { notation: 'compact' } : undefined)
@ -407,6 +407,17 @@ export default defineComponent({
this.liveChat = null this.liveChat = null
} }
// region No comment detection
// For videos without any comment (comment disabled?)
// e.g. https://youtu.be/8NBSwDEf8a8
//
// `comments_entry_point_header` is null probably when comment disabled
// e.g. https://youtu.be/8NBSwDEf8a8
// However videos with comments enabled but have no comment
// are different (which is not detected here)
this.commentsEnabled = result.comments_entry_point_header != null
// endregion No comment detection
// the bypassed result is missing some of the info that we extract in the code above // the bypassed result is missing some of the info that we extract in the code above
// so we only overwrite the result here // so we only overwrite the result here
// we need the bypassed result for the streaming data and the subtitles // we need the bypassed result for the streaming data and the subtitles
@ -509,6 +520,7 @@ export default defineComponent({
} }
this.adaptiveFormats = this.videoSourceList this.adaptiveFormats = this.videoSourceList
/** @type {import('../../helpers/api/local').LocalFormat[]} */
const formats = [...result.streaming_data.formats, ...result.streaming_data.adaptive_formats] const formats = [...result.streaming_data.formats, ...result.streaming_data.adaptive_formats]
this.downloadLinks = formats.map((format) => { this.downloadLinks = formats.map((format) => {
const qualityLabel = format.quality_label ?? format.bitrate const qualityLabel = format.quality_label ?? format.bitrate
@ -525,7 +537,7 @@ export default defineComponent({
} }
return { return {
url: format.url, url: format.freeTubeUrl,
label: label label: label
} }
}) })
@ -670,17 +682,6 @@ export default defineComponent({
} }
} }
// region No comment detection
// For videos without any comment (comment disabled?)
// e.g. https://youtu.be/8NBSwDEf8a8
//
// `comments_entry_point_header` is null probably when comment disabled
// e.g. https://youtu.be/8NBSwDEf8a8
// However videos with comments enabled but have no comment
// are different (which is not detected here)
this.commentsEnabled = result.comments_entry_point_header != null
// endregion No comment detection
this.isLoading = false this.isLoading = false
this.updateTitle() this.updateTitle()
} catch (err) { } catch (err) {
@ -716,7 +717,7 @@ export default defineComponent({
this.videoTitle = result.title this.videoTitle = result.title
this.videoViewCount = result.viewCount this.videoViewCount = result.viewCount
this.channelSubscriptionCountText = result.subCountText || 'FT-0' this.channelSubscriptionCountText = isNaN(result.subCountText) ? '' : result.subCountText
if (this.hideVideoLikesAndDislikes) { if (this.hideVideoLikesAndDislikes) {
this.videoLikeCount = null this.videoLikeCount = null
this.videoDislikeCount = null this.videoDislikeCount = null
@ -946,7 +947,7 @@ export default defineComponent({
}, },
/** /**
* @param {import('youtubei.js').Misc.Format[]} audioFormats * @param {import('../../helpers/api/local').LocalFormat[]} audioFormats
* @returns {AudioSource[]} * @returns {AudioSource[]}
*/ */
createLocalAudioSourceList: function (audioFormats) { createLocalAudioSourceList: function (audioFormats) {
@ -973,7 +974,7 @@ export default defineComponent({
} }
return { return {
url: format.url, url: format.freeTubeUrl,
type: format.mime_type, type: format.mime_type,
label: 'Audio', label: 'Audio',
qualityLabel: label qualityLabel: label

View File

@ -113,6 +113,16 @@
height: 500px; height: 500px;
} }
.watchVideoPlaylist {
:deep(.videoThumbnail) {
margin-top: auto;
margin-bottom: auto;
}
@media (max-width: 768px) {
height: auto;
}
}
.watchVideoRecommendations, .watchVideoRecommendations,
.theatreRecommendations { .theatreRecommendations {
margin: 0 0 16px; margin: 0 0 16px;

View File

@ -58,5 +58,23 @@
"playlistShuffle": "--mpv-shuffle", "playlistShuffle": "--mpv-shuffle",
"playlistLoop": "--mpv-loop-playlist" "playlistLoop": "--mpv-loop-playlist"
} }
},
{
"name": "SMPlayer",
"nameTranslationKey": "Settings.External Player Settings.Players.SMPlayer.Name",
"value": "smplayer",
"cmdArguments": {
"defaultExecutable": "smplayer",
"defaultCustomArguments": null,
"supportsYtdlProtocol": true,
"videoUrl": "",
"playlistUrl": "",
"startOffset": "-start",
"playbackRate": null,
"playlistIndex": null,
"playlistReverse": null,
"playlistShuffle": null,
"playlistLoop": null
}
} }
] ]

View File

@ -29,6 +29,13 @@ Close: 'إغلاق'
Back: 'رجوع' Back: 'رجوع'
Forward: 'إلى الأمام' Forward: 'إلى الأمام'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'الفيديوهات'
Shorts: القصيرة
Live: مباشر
# Search Bar # Search Bar
Search / Go to URL: 'ابحث / اذهب إلى رابط' Search / Go to URL: 'ابحث / اذهب إلى رابط'
# In Filter Button # In Filter Button
@ -82,6 +89,9 @@ Subscriptions:
Disabled Automatic Fetching: لقد قمت بتعطيل الجلب التلقائي للاشتراك. قم بتحديث الاشتراكات Disabled Automatic Fetching: لقد قمت بتعطيل الجلب التلقائي للاشتراك. قم بتحديث الاشتراكات
لرؤيتها هنا. لرؤيتها هنا.
Empty Channels: لا تحتوي قنواتك التي اشتركت فيها حاليا على أي مقاطع فيديو. Empty Channels: لا تحتوي قنواتك التي اشتركت فيها حاليا على أي مقاطع فيديو.
All Subscription Tabs Hidden: جميع علامات تبويب الاشتراك مخفية. لمشاهدة المحتوى
هنا ، يرجى إظهار بعض علامات التبويب في قسم "{subection}" في "{settingsSection}".
Subscriptions Tabs: تبويب الاشتراكات
Trending: Trending:
Trending: 'المحتوى الرائج' Trending: 'المحتوى الرائج'
Trending Tabs: علامات التبويب الشائعة Trending Tabs: علامات التبويب الشائعة
@ -402,6 +412,12 @@ Settings:
Channel Page: صفحة القناة Channel Page: صفحة القناة
Watch Page: صفحة المشاهدة Watch Page: صفحة المشاهدة
General: عام General: عام
Subscriptions Page: صفحة الاشتراكات
Hide Channel Podcasts: إخفاء بودكاست القناة
Hide Channel Releases: إخفاء إصدارات القناة
Hide Subscriptions Live: إخفاء الاشتراكات مباشرة
Hide Subscriptions Shorts: إخفاء الاشتراكات الفيدوهات القصيرة
Hide Subscriptions Videos: إخفاء مقاطع فيديو الاشتراكات
The app needs to restart for changes to take effect. Restart and apply change?: البرنامج The app needs to restart for changes to take effect. Restart and apply change?: البرنامج
يحتاج لإعادة التشغيل كي يسري مفعول التغييرات. هل تريد إعادة التشغيل و تطبيق التغييرات؟ يحتاج لإعادة التشغيل كي يسري مفعول التغييرات. هل تريد إعادة التشغيل و تطبيق التغييرات؟
Proxy Settings: Proxy Settings:
@ -621,9 +637,16 @@ Channel:
This channel does not currently have any live streams: لا يوجد حاليا أي بث مباشر This channel does not currently have any live streams: لا يوجد حاليا أي بث مباشر
على هذه القناة على هذه القناة
Shorts: Shorts:
Shorts: القصيرة
This channel does not currently have any shorts: هذه القناة ليس لديها حاليا أي This channel does not currently have any shorts: هذه القناة ليس لديها حاليا أي
أفلام قصيرة (shorts) أفلام قصيرة (shorts)
Podcasts:
Podcasts: البودكاست
This channel does not currently have any podcasts: لا تحتوي هذه القناة حاليا على
أي بودكاست
Releases:
Releases: الاصدارات
This channel does not currently have any releases: هذه القناة ليس لديها أي إصدارات
حاليا
Video: Video:
Mark As Watched: 'علّمه كفيديو تمت مشاهدته' Mark As Watched: 'علّمه كفيديو تمت مشاهدته'
Remove From History: 'إزالة من سجلّ المشاهدة' Remove From History: 'إزالة من سجلّ المشاهدة'
@ -924,9 +947,11 @@ Tooltips:
وتمكين ذاكرة تخزين مؤقت للصور في الذاكرة. سيؤدي إلى زيادة استخدام ذاكرة الوصول وتمكين ذاكرة تخزين مؤقت للصور في الذاكرة. سيؤدي إلى زيادة استخدام ذاكرة الوصول
العشوائي. العشوائي.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: أدخل اسم القناة أو رقم تعريف القناة لإخفاء كل الفيديوهات وقوائم Hide Channels: أدخل اسم قناة أو معرّف القناة لإخفاء جميع مقاطع الفيديو وقوائم
التشغيل والقناة نفسها من الظهور في "بحث Google" أو "المحتوى الرائج". يجب أن التشغيل والقناة نفسها من الظهور في البحث والشهرة والأكثر شعبية والموصى بها.
يكون اسم القناة الذي تم إدخاله مطابقا تماما وحساسا لحالة الأحرف. يجب أن يكون اسم القناة الذي تم إدخاله مطابقًا تمامًا وحساسًا لحالة الأحرف.
Hide Subscriptions Live: يتم تجاوز هذا الإعداد من خلال إعداد "{appWideSetting}"
على مستوى التطبيق، في قسم "{subsection}" من "{settingsSection}"
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: استبدل عناوين الفيديو بالعناوين التي أرسلها المستخدم من DeArrow. UseDeArrowTitles: استبدل عناوين الفيديو بالعناوين التي أرسلها المستخدم من DeArrow.
This video is unavailable because of missing formats. This can happen due to country unavailability.: هذا This video is unavailable because of missing formats. This can happen due to country unavailability.: هذا

View File

@ -30,6 +30,13 @@ Close: 'Затваряне'
Back: 'Назад' Back: 'Назад'
Forward: 'Напред' Forward: 'Напред'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'видеа'
Shorts: Кратки видеа
Live: На живо
Version {versionNumber} is now available! Click for more details: 'Версия {versionNumber} Version {versionNumber} is now available! Click for more details: 'Версия {versionNumber}
е вече налична! Щракнете за повече детайли' е вече налична! Щракнете за повече детайли'
Download From Site: 'Сваляне от сайта' Download From Site: 'Сваляне от сайта'
@ -90,6 +97,10 @@ Subscriptions:
Disabled Automatic Fetching: Автоматичното извличане на абонаменти е деактивирано. Disabled Automatic Fetching: Автоматичното извличане на абонаменти е деактивирано.
Обновете абонаментите, за да ги видите тук. Обновете абонаментите, за да ги видите тук.
Empty Channels: Каналите, за които сте абонирани, в момента нямат никакви видеа. Empty Channels: Каналите, за които сте абонирани, в момента нямат никакви видеа.
Subscriptions Tabs: Раздели с абонаменти
All Subscription Tabs Hidden: Всички раздели на абонамента са скрити. За да виждате
съдържанието тук, моля, премахнете скриването на някои раздели в секция "{subsection}"
на "{settingsSection}".
Trending: Trending:
Trending: 'Набиращи популярност' Trending: 'Набиращи популярност'
Trending Tabs: Раздели за набиращи популярност Trending Tabs: Раздели за набиращи популярност
@ -102,11 +113,11 @@ Playlists: 'Плейлисти'
User Playlists: User Playlists:
Your Playlists: 'Вашите плейлисти' Your Playlists: 'Вашите плейлисти'
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: Няма Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: Няма
запазени видеоклипове. За запазване щракнете в ъгъла на видеоклипа запазени видеа. За запазване щракнете в ъгъла на видеото
Playlist Message: Тази страница не отразява напълно работещи плейлисти. В него са Playlist Message: Тази страница не отразява напълно работещи плейлисти. В него са
изброени само видеоклипове, които сте запазили или избрали за любими. Когато работата изброени само видеа, които сте запазили или избрали за предпочитани. Когато работата
приключи, всички видеоклипове, които в момента са тук, ще бъдат мигрирани в плейлист приключи, всички видеа, които в момента са тук, ще бъдат преместени в плейлист
Любими“. Предпочитани“.
Search bar placeholder: Търсене в плейлиста Search bar placeholder: Търсене в плейлиста
Empty Search Message: В този плейлист няма видеа, които да отговарят на търсенето Empty Search Message: В този плейлист няма видеа, които да отговарят на търсенето
ви ви
@ -412,6 +423,12 @@ Settings:
Side Bar: Странична лента Side Bar: Странична лента
Watch Page: Страница за гледане Watch Page: Страница за гледане
General: Общи General: Общи
Subscriptions Page: Страница с абонаменти
Hide Channel Releases: Скриване на изданията на канала
Hide Subscriptions Videos: Скриване на видеата в абонаментите
Hide Subscriptions Shorts: Скриване на кратките видеа в абонаментите
Hide Subscriptions Live: Скриване на абонаментите на живо
Hide Channel Podcasts: Скриване на подкастите на канала
The app needs to restart for changes to take effect. Restart and apply change?: Приложението The app needs to restart for changes to take effect. Restart and apply change?: Приложението
трябва да се рестартира за да се приложат промените. Рестартиране? трябва да се рестартира за да се приложат промените. Рестартиране?
Proxy Settings: Proxy Settings:
@ -445,6 +462,7 @@ Settings:
Prompt To Skip: Подкана за пропускане Prompt To Skip: Подкана за пропускане
Do Nothing: Не правете нищо Do Nothing: Не правете нищо
Category Color: Категория Цвят Category Color: Категория Цвят
UseDeArrowTitles: Използване на DeArrow за заглавия на видео
External Player Settings: External Player Settings:
Custom External Player Arguments: Персонализирани аргументи за външен плейър Custom External Player Arguments: Персонализирани аргументи за външен плейър
Custom External Player Executable: Персонализирано изпълнение на външен плейър Custom External Player Executable: Персонализирано изпълнение на външен плейър
@ -638,7 +656,14 @@ Channel:
Shorts: Shorts:
This channel does not currently have any shorts: В момента този канал няма никакви This channel does not currently have any shorts: В момента този канал няма никакви
кратки видеа кратки видеа
Shorts: Кратки видеа Podcasts:
Podcasts: Подкасти
This channel does not currently have any podcasts: В момента този канал няма никакви
подкасти
Releases:
Releases: Издания
This channel does not currently have any releases: В момента този канал няма никакви
издания
Video: Video:
Mark As Watched: 'Отбелязване като гледано' Mark As Watched: 'Отбелязване като гледано'
Remove From History: 'Премахване от историята' Remove From History: 'Премахване от историята'
@ -725,8 +750,8 @@ Video:
Open Channel in YouTube: Отваряне на канала в YouTube Open Channel in YouTube: Отваряне на канала в YouTube
Started streaming on: Начало на предаването Started streaming on: Начало на предаването
Streamed on: На живо на Streamed on: На живо на
Video has been removed from your saved list: Видеоклипът е премахнат от вашия запазен Video has been removed from your saved list: Видеото е премахнато от вашия списък
списък със запазени
Video has been saved: Видеото е запазено Video has been saved: Видеото е запазено
Save Video: Запазване на видео Save Video: Запазване на видео
translated from English: преведено от английски translated from English: преведено от английски
@ -765,7 +790,7 @@ Video:
buffered: Буферирани buffered: Буферирани
out of: от out of: от
Resolution: Резолюция Resolution: Резолюция
Video ID: Идентификатор на видеоклипа Video ID: Идентификатор на видеото
Player Dimensions: Размери на плейъра Player Dimensions: Размери на плейъра
Bitrate: Побитова скорост Bitrate: Побитова скорост
Bandwidth: Пропускателна способност Bandwidth: Пропускателна способност
@ -774,7 +799,7 @@ Video:
Mimetype: MIME тип Mimetype: MIME тип
Buffered: Буферирани Buffered: Буферирани
Video statistics are not available for legacy videos: Статистиката не е налична Video statistics are not available for legacy videos: Статистиката не е налична
за наследени видеоклипове за наследени видеа
Premieres on: Премиера на Premieres on: Премиера на
Premieres in: Премиери в Premieres in: Премиери в
Premieres: Премиерa Premieres: Премиерa
@ -898,7 +923,7 @@ Tooltips:
избран Invidious интерфейс. Когато е активиран, локалният интерфейс ще използва избран Invidious интерфейс. Когато е активиран, локалният интерфейс ще използва
старите формати вместо тези на Invidious. Това помага когато видеата, получавани старите формати вместо тези на Invidious. Това помага когато видеата, получавани
от Invidious не вървят поради регионални ограничения. от Invidious не вървят поради регионални ограничения.
Scroll Playback Rate Over Video Player: Докато курсорът е върху видеоклипа, натиснете Scroll Playback Rate Over Video Player: Докато курсорът е върху видеото, натиснете
и задръжте клавиша Control (Command Key за Mac) и превъртете колелцето на мишката и задръжте клавиша Control (Command Key за Mac) и превъртете колелцето на мишката
напред или назад, за да контролирате скоростта на възпроизвеждане. Натиснете напред или назад, за да контролирате скоростта на възпроизвеждане. Натиснете
и задръжте клавиша Control (Command Key за Mac) и щракнете с левия бутон на и задръжте клавиша Control (Command Key за Mac) и щракнете с левия бутон на
@ -948,9 +973,14 @@ Tooltips:
на RAM паметта. на RAM паметта.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Въведете име или идентификатор на канал, за да скриете всички видеа, Hide Channels: Въведете име или идентификатор на канал, за да скриете всички видеа,
плейлисти и самия канал от показване в търсенето или набиращите популярност. плейлисти и самия канал от показване в търсенето, тенденциите, най-популярните
Въведеното име трябва да съвпада напълно и е чувствително към главни и малки и препоръчаните. Въведеното име трябва да съвпада напълно и е чувствително към
букви. главни и малки букви.
Hide Subscriptions Live: Тази настройка се отменя от настройката за цялото приложение
"{appWideSetting}" в секция "{subsection}" на "{settingsSection}"
SponsorBlock Settings:
UseDeArrowTitles: Заменя заглавията на видеата с подадени от потребителите заглавия
от DeArrow.
More: Още More: Още
Playing Next Video Interval: Пускане на следващото видео веднага. Щракнете за отказ. Playing Next Video Interval: Пускане на следващото видео веднага. Щракнете за отказ.
| Пускане на следващото видео след {nextVideoInterval} секунда. Щракнете за отказ. | Пускане на следващото видео след {nextVideoInterval} секунда. Щракнете за отказ.

View File

@ -30,6 +30,11 @@ Close: 'Tancar'
Back: 'Enrere' Back: 'Enrere'
Forward: 'Endavant' Forward: 'Endavant'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vídeos'
Version {versionNumber} is now available! Click for more details: 'La versió {versionNumber} Version {versionNumber} is now available! Click for more details: 'La versió {versionNumber}
està disponible! Fes clic per a més detalls' està disponible! Fes clic per a més detalls'
Download From Site: 'Descarrega des del web' Download From Site: 'Descarrega des del web'

View File

@ -29,6 +29,13 @@ Close: 'Zavřít'
Back: 'Zpět' Back: 'Zpět'
Forward: 'Dopředu' Forward: 'Dopředu'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videa'
Shorts: Shorts
Live: Živě
Version {versionNumber} is now available! Click for more details: 'Verze {versionNumber} Version {versionNumber} is now available! Click for more details: 'Verze {versionNumber}
je k dispozici! Klikněte pro více informací' je k dispozici! Klikněte pro více informací'
Download From Site: 'Stáhnout ze stránky' Download From Site: 'Stáhnout ze stránky'
@ -89,6 +96,10 @@ Subscriptions:
Empty Channels: Vaše odebírané kanály momentálně nemají žádná videa. Empty Channels: Vaše odebírané kanály momentálně nemají žádná videa.
Disabled Automatic Fetching: Máte zakázané automatické získávání odběrů. Obnovte Disabled Automatic Fetching: Máte zakázané automatické získávání odběrů. Obnovte
odběry, abyste je zde mohli vidět. odběry, abyste je zde mohli vidět.
Subscriptions Tabs: Karty s odběry
All Subscription Tabs Hidden: Všechny karty předplatného jsou skryté. Chcete-li
zde zobrazit obsah, zrušte prosím skrytí některých záložek v sekci „{subsection}“
v „{settingsSection}“.
Trending: Trending:
Trending: 'Trendy' Trending: 'Trendy'
Trending Tabs: Tabulka trendů Trending Tabs: Tabulka trendů
@ -334,6 +345,12 @@ Settings:
Channel Page: Stránka kanálu Channel Page: Stránka kanálu
Watch Page: Stránka sledování Watch Page: Stránka sledování
General: Obecné General: Obecné
Subscriptions Page: Stránka s odběry
Hide Channel Podcasts: Skrýt podcasty kanálu
Hide Channel Releases: Skrýt vydání kanálu
Hide Subscriptions Shorts: Skrýt Shorts odběrů
Hide Subscriptions Videos: Skrýt videa odběrů
Hide Subscriptions Live: Skrýt živá vysílání odběrů
Data Settings: Data Settings:
Data Settings: 'Nastavení dat' Data Settings: 'Nastavení dat'
Select Import Type: 'Vybrat typ importu' Select Import Type: 'Vybrat typ importu'
@ -436,6 +453,7 @@ Settings:
Prompt To Skip: Zeptat se na přeskočení Prompt To Skip: Zeptat se na přeskočení
Show In Seek Bar: Zobrazit v liště Show In Seek Bar: Zobrazit v liště
Category Color: Barva kategorie Category Color: Barva kategorie
UseDeArrowTitles: Použít názvy videí z DeArrow
External Player Settings: External Player Settings:
Custom External Player Arguments: Argumenty vlastního externího přehrávače Custom External Player Arguments: Argumenty vlastního externího přehrávače
Custom External Player Executable: Spustitelný vlastní externí přehrávač Custom External Player Executable: Spustitelný vlastní externí přehrávač
@ -622,7 +640,14 @@ Channel:
době nemá žádné živé přenosy době nemá žádné živé přenosy
Shorts: Shorts:
This channel does not currently have any shorts: Tento kanál nemá žádné shorts This channel does not currently have any shorts: Tento kanál nemá žádné shorts
Shorts: Shorts Releases:
Releases: Vydání
This channel does not currently have any releases: Tento kanál momentálně nemá
žádná vydání
Podcasts:
This channel does not currently have any podcasts: Tento kanál momentálně nemá
žádné podcasty
Podcasts: Podcasty
Video: Video:
Mark As Watched: 'Označit jako zhlédnuto' Mark As Watched: 'Označit jako zhlédnuto'
Remove From History: 'Odstranit z historie' Remove From History: 'Odstranit z historie'
@ -905,8 +930,13 @@ Tooltips:
mezipaměť pro obrázky v paměti. Povede to ke zvýšenému využití RAM. mezipaměť pro obrázky v paměti. Povede to ke zvýšenému využití RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Zadejte název nebo ID kanálu pro skrytí všech videí, playlistů Hide Channels: Zadejte název nebo ID kanálu pro skrytí všech videí, playlistů
a samotného kanálu před zobrazením ve vyhledávání nebo trendech. Zadaný název a samotného kanálu před zobrazením ve vyhledávání, trendech, nejpopulárnějších
kanálu se musí zcela shodovat a rozlišují se v něm velká a malá písmena. a doporučených. Zadaný název kanálu se musí zcela shodovat a rozlišují se v
něm velká a malá písmena.
Hide Subscriptions Live: Toto nastavení je nadřazeno nastavením celé aplikace
„{appWideSetting}“ v části „{subsection}“ v části „{settingsSection}“
SponsorBlock Settings:
UseDeArrowTitles: Nahradit názvy videí vlastními názvy od uživatelů DeArrow.
Local API Error (Click to copy): 'Chyba lokálního API (kliknutím zkopírujete)' Local API Error (Click to copy): 'Chyba lokálního API (kliknutím zkopírujete)'
Invidious API Error (Click to copy): 'Chyba Invidious API (kliknutím zkopírujete)' Invidious API Error (Click to copy): 'Chyba Invidious API (kliknutím zkopírujete)'
Falling back to Invidious API: 'Přepínám na Invidious API' Falling back to Invidious API: 'Přepínám na Invidious API'

View File

@ -29,6 +29,11 @@ Close: 'Luk'
Back: 'Tilbage' Back: 'Tilbage'
Forward: 'Fremad' Forward: 'Fremad'
# Global
# Anything shared among components / views should be put here
Global:
Videos: Videoer
Version {versionNumber} is now available! Click for more details: 'Version {versionNumber} Version {versionNumber} is now available! Click for more details: 'Version {versionNumber}
er nu tilgængelig! Klik for flere detaljer' er nu tilgængelig! Klik for flere detaljer'
Download From Site: 'Hent Fra Netsted' Download From Site: 'Hent Fra Netsted'
@ -60,12 +65,14 @@ Search Filters:
Videos: 'Videoer' Videos: 'Videoer'
Channels: 'Kanaler' Channels: 'Kanaler'
#& Playlists #& Playlists
Movies: Film
Duration: Duration:
Duration: 'Længde' Duration: 'Længde'
All Durations: 'Alle Længder' All Durations: 'Alle Længder'
Short (< 4 minutes): 'Kort (< 4 minutter)' Short (< 4 minutes): 'Kort (< 4 minutter)'
Long (> 20 minutes): 'Lang (> 20 minutter)' Long (> 20 minutes): 'Lang (> 20 minutter)'
# On Search Page # On Search Page
Medium (4 - 20 minutes): Medium (4-20 minutter)
Search Results: 'Søgeresultater' Search Results: 'Søgeresultater'
Fetching results. Please wait: 'Skaffer resultater. Vent venligst' Fetching results. Please wait: 'Skaffer resultater. Vent venligst'
Fetch more results: 'Skaf flere resultater' Fetch more results: 'Skaf flere resultater'
@ -83,8 +90,11 @@ Subscriptions:
Refresh Subscriptions: 'Genopfrisk Abonnementer' Refresh Subscriptions: 'Genopfrisk Abonnementer'
Load More Videos: Indlæs Flere Videoer Load More Videos: Indlæs Flere Videoer
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: Denne This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: Denne
profil har et stort antal abonnementer. Tvinger RSS for at undgå adgangsbegrænsning profil har et stort antal abonnementer. Gennemtvinger RSS for at undgå adgangsbegrænsning
Error Channels: Kanaler med Fejl Error Channels: Kanaler med Fejl
Empty Channels: De kanaler, du abonnerer på, har i øjeblikket ingen videoer.
Disabled Automatic Fetching: Du har deaktiveret automatisk hentning af abonnementer.
Opdater abonnementer for at se dem her.
Trending: Trending:
Trending: 'Trender' Trending: 'Trender'
Music: Musik Music: Musik
@ -263,6 +273,7 @@ Settings:
Video Playback Rate Interval: Videoafspilningshastighed Interval Video Playback Rate Interval: Videoafspilningshastighed Interval
Scroll Playback Rate Over Video Player: Scroll-afspilningshastighed i Videoafspiller Scroll Playback Rate Over Video Player: Scroll-afspilningshastighed i Videoafspiller
Scroll Volume Over Video Player: Scroll-lydstyrke i Videoafspiller Scroll Volume Over Video Player: Scroll-lydstyrke i Videoafspiller
Skip by Scrolling Over Video Player: Spring over ved at scrolle over videoafspilleren
Privacy Settings: Privacy Settings:
Privacy Settings: 'Privatlivsindstillinger' Privacy Settings: 'Privatlivsindstillinger'
Remember History: 'Husk Historik' Remember History: 'Husk Historik'
@ -592,8 +603,8 @@ Video:
i øjeblikket ikke i dette build.' i øjeblikket ikke i dette build.'
'Chat is disabled or the Live Stream has ended.': 'Chat er deaktiveret, eller Livestream 'Chat is disabled or the Live Stream has ended.': 'Chat er deaktiveret, eller Livestream
er slut.' er slut.'
Live chat is enabled. Chat messages will appear here once sent.: 'Direkte chat Live chat is enabled. Chat messages will appear here once sent.: 'Direkte chat er
er aktiveret. Chatbeskeder vil fremgå her, når de er sendt.' aktiveret. Chatbeskeder vil fremgå her, når de er sendt.'
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Direkte 'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Direkte
Chat understøttes i øjeblikket ikke med Invidious-APIen. Direkte forbindelse Chat understøttes i øjeblikket ikke med Invidious-APIen. Direkte forbindelse
til YouTube kræves.' til YouTube kræves.'
@ -815,9 +826,9 @@ Tooltips:
Fallback to Non-Preferred Backend on Failure: Når din foretrukne API oplever et Fallback to Non-Preferred Backend on Failure: Når din foretrukne API oplever et
problem, vil FreeTube automatisk forsøge at bruge din ikke-foretrukne API som problem, vil FreeTube automatisk forsøge at bruge din ikke-foretrukne API som
reservemetode, når det er aktiveret. reservemetode, når det er aktiveret.
External Link Handling: "Vælg standardopførslen for når du klikker på et link,\ External Link Handling: "Vælg standardopførslen for når du klikker på et link,
\ der ikke kan blive åbnet i FreeTube.\nSom standard åbner FreeTube det link,\ der ikke kan blive åbnet i FreeTube.\nSom standard åbner FreeTube det link,
\ du klikker på, i din standardbrowser.\n" du klikker på, i din standardbrowser.\n"
External Player Settings: External Player Settings:
Custom External Player Executable: Som standard antager FreeTube at den valgte Custom External Player Executable: Som standard antager FreeTube at den valgte
eksterne afspiller kan findes via miljøvariablet PATH. En brugerdefineret sti eksterne afspiller kan findes via miljøvariablet PATH. En brugerdefineret sti
@ -871,3 +882,4 @@ Screenshot Success: Gemte skærmbillede som "{filePath}"
Playing Next Video Interval: Afspiller næste video om lidt. Klik for at afbryde. | Playing Next Video Interval: Afspiller næste video om lidt. Klik for at afbryde. |
Afspiller næste video om {nextVideoInterval} sekund. Klik for at afbryde. | Afspiller Afspiller næste video om {nextVideoInterval} sekund. Klik for at afbryde. | Afspiller
næste video om {nextVideoInterval} sekunder. Klik for at afbryde. næste video om {nextVideoInterval} sekunder. Klik for at afbryde.
Preferences: Præferencer

View File

@ -28,6 +28,13 @@ Close: Schließen
Back: Zurück Back: Zurück
Forward: Vorwärts Forward: Vorwärts
# Global
# Anything shared among components / views should be put here
Global:
Videos: Videos
Shorts: Shorts
Live: Live
# Search Bar # Search Bar
Search / Go to URL: Suche / Geh zu URL Search / Go to URL: Suche / Geh zu URL
# In Filter Button # In Filter Button
@ -85,6 +92,10 @@ Subscriptions:
Disabled Automatic Fetching: Du hast den automatischen Abruf von Abonnements deaktiviert. Disabled Automatic Fetching: Du hast den automatischen Abruf von Abonnements deaktiviert.
Aktualisiere die Abonnements, um sie hier zu sehen. Aktualisiere die Abonnements, um sie hier zu sehen.
Empty Channels: Deine abonnierten Kanäle haben derzeit keine Videos. Empty Channels: Deine abonnierten Kanäle haben derzeit keine Videos.
Subscriptions Tabs: Registerkarten für Abonnements
All Subscription Tabs Hidden: Alle Registerkarten für Abonnements sind ausgeblendet.
Um den Inhalt hier zu sehen, blenden Sie bitte einige Registerkarten im Abschnitt
„{subsection}“ in „{settingsSection}“ ein.
Trending: Trending:
Trending: Trends Trending: Trends
Trending Tabs: Trendtabs Trending Tabs: Trendtabs
@ -416,6 +427,12 @@ Settings:
Channel Page: Kanalseite Channel Page: Kanalseite
General: Allgemein General: Allgemein
Watch Page: Seite beobachten Watch Page: Seite beobachten
Subscriptions Page: Abonnements-Seite
Hide Channel Podcasts: Kanal-Podcasts ausblenden
Hide Subscriptions Videos: Videos der Abonnements ausblenden
Hide Subscriptions Shorts: Shorts der Abonnements ausblenden
Hide Channel Releases: Kanalveröffentlichungen ausblenden
Hide Subscriptions Live: Live der Abonnements ausblenden
The app needs to restart for changes to take effect. Restart and apply change?: Um The app needs to restart for changes to take effect. Restart and apply change?: Um
die Änderungen anzuwenden muss die Anwendung neustarten. Jetzt neustarten und die Änderungen anzuwenden muss die Anwendung neustarten. Jetzt neustarten und
Änderungen aktivieren? Änderungen aktivieren?
@ -449,6 +466,7 @@ Settings:
Prompt To Skip: Aufforderung zum Überspringen Prompt To Skip: Aufforderung zum Überspringen
Do Nothing: Nichts tun Do Nothing: Nichts tun
Category Color: Kategoriefarbe Category Color: Kategoriefarbe
UseDeArrowTitles: DeArrow-Video-Titel verwenden
External Player Settings: External Player Settings:
Ignore Unsupported Action Warnings: Nicht unterstützte Aktionswarnungen ignorieren Ignore Unsupported Action Warnings: Nicht unterstützte Aktionswarnungen ignorieren
External Player: Externer Player External Player: Externer Player
@ -609,9 +627,16 @@ Channel:
This channel does not currently have any live streams: Dieser Kanal hat derzeit This channel does not currently have any live streams: Dieser Kanal hat derzeit
keine Live-Streams keine Live-Streams
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: Dieser Kanal hat derzeit keine This channel does not currently have any shorts: Dieser Kanal hat derzeit keine
Shorts Shorts
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: Dieser Kanal hat aktuell keine
Podcasts
Releases:
Releases: Veröffentlichungen
This channel does not currently have any releases: Dieser Kanel hat aktuell keine
Veröffentlichungen
Video: Video:
Open in YouTube: In YouTube öffnen Open in YouTube: In YouTube öffnen
Copy YouTube Link: YouTube-Link kopieren Copy YouTube Link: YouTube-Link kopieren
@ -974,10 +999,17 @@ Tooltips:
und aktiviert einen benutzerdefinierten In-Memory-Image-Cache. Dies führt zu und aktiviert einen benutzerdefinierten In-Memory-Image-Cache. Dies führt zu
einer erhöhten Nutzung des Direktzugriffsspeichers. einer erhöhten Nutzung des Direktzugriffsspeichers.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Geben Sie einen Kanalnamen oder eine Kanal-ID ein, um alle Videos, Hide Channels: Geben Sie einen Kanalnamen oder eine Kanal-ID ein, um zu verhindern,
Wiedergabelisten und den Kanal selbst vor der Anzeige in der Suche oder den dass alle Videos, Wiedergabelisten und der Kanal selbst in der Suche, den Trends,
Trends zu verbergen. Der eingegebene Kanalname muss vollständig übereinstimmen den beliebtesten und den empfohlenen Videos angezeigt werden. Der eingegebene
und es wird zwischen Groß- und Kleinschreibung unterschieden. Kanalname muss vollständig übereinstimmen und es wird zwischen Groß- und Kleinschreibung
unterschieden.
Hide Subscriptions Live: Diese Einstellung wird durch die app-weite Einstellung
„{appWideSetting}“ im Abschnitt „{subsection}“ des Abschnitts „{settingsSection}“
außer Kraft gesetzt
SponsorBlock Settings:
UseDeArrowTitles: Ersetzen Sie Videotitel durch von Benutzern eingereichte Titel
von DeArrow.
Playing Next Video Interval: Nächstes Video wird sofort abgespielt. Zum Abbrechen Playing Next Video Interval: Nächstes Video wird sofort abgespielt. Zum Abbrechen
klicken. | Nächstes Video wird in {nextVideoInterval} Sekunden abgespielt. Zum Abbrechen klicken. | Nächstes Video wird in {nextVideoInterval} Sekunden abgespielt. Zum Abbrechen
klicken. | Nächstes Video wird in {nextVideoInterval} Sekunden abgespielt. Zum Abbrechen klicken. | Nächstes Video wird in {nextVideoInterval} Sekunden abgespielt. Zum Abbrechen

View File

@ -30,6 +30,13 @@ Close: 'Κλείσιμο'
Back: 'Μετάβαση πίσω' Back: 'Μετάβαση πίσω'
Forward: 'Μετάβαση μπροστά' Forward: 'Μετάβαση μπροστά'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Βίντεο'
Shorts: Shorts
Live: Ζωντανά
# Search Bar # Search Bar
Search / Go to URL: 'Αναζήτηση/Μετάβαση στη URL' Search / Go to URL: 'Αναζήτηση/Μετάβαση στη URL'
# In Filter Button # In Filter Button
@ -86,6 +93,10 @@ Subscriptions:
Disabled Automatic Fetching: Έχετε απενεργοποιήσει την αυτόματη ανάκτηση συνδρομής. Disabled Automatic Fetching: Έχετε απενεργοποιήσει την αυτόματη ανάκτηση συνδρομής.
Ανανεώστε τις συνδρομές για να τις δείτε εδώ. Ανανεώστε τις συνδρομές για να τις δείτε εδώ.
Empty Channels: Τα εγγεγραμμένα κανάλια σας προς το παρόν δεν έχουν βίντεο. Empty Channels: Τα εγγεγραμμένα κανάλια σας προς το παρόν δεν έχουν βίντεο.
Subscriptions Tabs: Καρτέλες Συνδρομών
All Subscription Tabs Hidden: Όλες οι καρτέλες συνδρομής είναι κρυφές. Για να δείτε
περιεχόμενο εδώ, αποκρύψτε ορισμένες καρτέλες στην ενότητα "{subsection}" στο
"{settingsSection}".
Trending: Trending:
Trending: 'Τάσεις' Trending: 'Τάσεις'
Gaming: Παιχνίδια Gaming: Παιχνίδια
@ -413,10 +424,16 @@ Settings:
Channel Page: Σελίδα Καναλιού Channel Page: Σελίδα Καναλιού
Watch Page: Σελίδα Παρακολούθησης Watch Page: Σελίδα Παρακολούθησης
General: Γενικά General: Γενικά
Subscriptions Page: Σελίδα Συνδρομών
Hide Featured Channels: Απόκρυψη Προτεινόμενων Καναλιών Hide Featured Channels: Απόκρυψη Προτεινόμενων Καναλιών
Hide Channel Playlists: Απόκρυψη Λιστών Αναπαραγωγής Καναλιών Hide Channel Playlists: Απόκρυψη Λιστών Αναπαραγωγής Καναλιών
Hide Channel Community: Απόκρυψη Κοινότητας Καναλιού Hide Channel Community: Απόκρυψη Κοινότητας Καναλιού
Hide Channel Shorts: Απόκρυψη Shorts Καναλιού Hide Channel Shorts: Απόκρυψη Shorts Καναλιού
Hide Channel Releases: Απόκρυψη Kυκλοφοριών Kαναλιού
Hide Channel Podcasts: Απόκρυψη Podcasts Καναλιού
Hide Subscriptions Videos: Απόκρυψη Βίντεο Συνδρομών
Hide Subscriptions Shorts: Απόκρυψη Shorts Συνδρομών
Hide Subscriptions Live: Απόκρυψη Live Συνδρομών
The app needs to restart for changes to take effect. Restart and apply change?: Η The app needs to restart for changes to take effect. Restart and apply change?: Η
εφαρμογή πρέπει να κάνει επανεκκίνηση για να εφαρμοστούν οι αλλαγές. Επανεκκίνηση εφαρμογή πρέπει να κάνει επανεκκίνηση για να εφαρμοστούν οι αλλαγές. Επανεκκίνηση
και εφαρμογή αλλαγών; και εφαρμογή αλλαγών;
@ -461,6 +478,7 @@ Settings:
Show In Seek Bar: Εμφάνιση Στη Γραμμή Αναζήτησης Show In Seek Bar: Εμφάνιση Στη Γραμμή Αναζήτησης
Prompt To Skip: Προτροπή Για Παράλειψη Prompt To Skip: Προτροπή Για Παράλειψη
Category Color: Χρώμα Κατηγορίας Category Color: Χρώμα Κατηγορίας
UseDeArrowTitles: Χρήση Τίτλων Βίντεο DeArrow
Download Settings: Download Settings:
Download Settings: Λήψη ρυθμίσεων Download Settings: Λήψη ρυθμίσεων
Ask Download Path: Ζητήστε τη διαδρομή λήψης Ask Download Path: Ζητήστε τη διαδρομή λήψης
@ -652,9 +670,16 @@ Channel:
This channel does not currently have any live streams: Αυτό το κανάλι δεν έχει This channel does not currently have any live streams: Αυτό το κανάλι δεν έχει
προς το παρόν ζωντανές ροές προς το παρόν ζωντανές ροές
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: Αυτό το κανάλι δεν έχει προς This channel does not currently have any shorts: Αυτό το κανάλι δεν έχει προς
το παρόν κανένα shorts το παρόν κανένα shorts
Releases:
Releases: Κυκλοφορίες
This channel does not currently have any releases: Αυτό το κανάλι δεν έχει προς
το παρόν κυκλοφορίες
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: Αυτό το κανάλι δεν έχει αυτήν
τη στιγμή podcasts
Video: Video:
Mark As Watched: 'Επισήμανση ως παρακολουθημένο' Mark As Watched: 'Επισήμανση ως παρακολουθημένο'
Remove From History: 'Κατάργηση από το ιστορικό' Remove From History: 'Κατάργηση από το ιστορικό'
@ -986,8 +1011,14 @@ Tooltips:
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Εισαγάγετε ένα όνομα καναλιού ή ένα αναγνωριστικό καναλιού για Hide Channels: Εισαγάγετε ένα όνομα καναλιού ή ένα αναγνωριστικό καναλιού για
να αποκρύψετε όλα τα βίντεο, τις λίστες αναπαραγωγής και το ίδιο το κανάλι ώστε να αποκρύψετε όλα τα βίντεο, τις λίστες αναπαραγωγής και το ίδιο το κανάλι ώστε
να μην εμφανίζονται στην αναζήτηση ή στις τάσεις. Το όνομα του καναλιού που να μην εμφανίζονται στην αναζήτηση, στις τάσεις, στα πιο δημοφιλή και προτεινόμενα.
καταχωρίσατε πρέπει να ταιριάζει απόλυτα και να κάνει διάκριση πεζών-κεφαλαίων. Το όνομα του καναλιού που καταχωρίσατε πρέπει να ταιριάζει απόλυτα και να κάνει
διάκριση πεζών-κεφαλαίων.
Hide Subscriptions Live: Αυτή η ρύθμιση παρακάμπτεται από τη ρύθμιση "{appWideSetting}"
σε όλη την εφαρμογή, στην ενότητα "{subsection}" του "{settingsSection}"
SponsorBlock Settings:
UseDeArrowTitles: Αντικαταστήστε τους τίτλους βίντεο με τίτλους που υποβλήθηκαν
από τους χρήστες από το DeArrow.
Playing Next Video Interval: Αναπαραγωγή επόμενου βίντεο άμεσα. Κάντε κλικ για ακύρωση. Playing Next Video Interval: Αναπαραγωγή επόμενου βίντεο άμεσα. Κάντε κλικ για ακύρωση.
| Αναπαραγωγή επόμενου βίντεο σε {nextVideoInterval} δευτερόλεπτο. Κάντε κλικ για | Αναπαραγωγή επόμενου βίντεο σε {nextVideoInterval} δευτερόλεπτο. Κάντε κλικ για
ακύρωση. | Αναπαραγωγή επόμενου βίντεο σε {nextVideoInterval} δευτερόλεπτα. Κάντε ακύρωση. | Αναπαραγωγή επόμενου βίντεο σε {nextVideoInterval} δευτερόλεπτα. Κάντε

View File

@ -39,6 +39,13 @@ A new blog is now available, {blogTitle}. Click to view more: A new blog is now
Click to view more Click to view more
Are you sure you want to open this link?: Are you sure you want to open this link? Are you sure you want to open this link?: Are you sure you want to open this link?
# Global
# Anything shared among components / views should be put here
Global:
Videos: Videos
Shorts: Shorts
Live: Live
# Search Bar # Search Bar
Search / Go to URL: Search / Go to URL Search / Go to URL: Search / Go to URL
Search Bar: Search Bar:
@ -93,6 +100,8 @@ Subscriptions:
'Getting Subscriptions. Please wait.': Getting Subscriptions. Please wait. 'Getting Subscriptions. Please wait.': Getting Subscriptions. Please wait.
Refresh Subscriptions: Refresh Subscriptions Refresh Subscriptions: Refresh Subscriptions
Load More Videos: Load More Videos Load More Videos: Load More Videos
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 More: More
Channels: Channels:
Channels: Channels Channels: Channels
@ -323,6 +332,7 @@ Settings:
Distraction Free Settings: Distraction Free Settings Distraction Free Settings: Distraction Free Settings
Sections: Sections:
Side Bar: Side Bar Side Bar: Side Bar
Subscriptions Page: Subscriptions Page
Channel Page: Channel Page Channel Page: Channel Page
Watch Page: Watch Page Watch Page: Watch Page
General: General General: General
@ -349,6 +359,11 @@ Settings:
Hide Channel Playlists: Hide Channel Playlists Hide Channel Playlists: Hide Channel Playlists
Hide Channel Community: Hide Channel Community Hide Channel Community: Hide Channel Community
Hide Channel Shorts: Hide Channel Shorts Hide Channel Shorts: Hide Channel Shorts
Hide Channel Podcasts: Hide Channel Podcasts
Hide Channel Releases: Hide Channel Releases
Hide Subscriptions Videos: Hide Subscriptions Videos
Hide Subscriptions Shorts: Hide Subscriptions Shorts
Hide Subscriptions Live: Hide Subscriptions Live
Data Settings: Data Settings:
Data Settings: Data Settings Data Settings: Data Settings
Select Import Type: Select Import Type Select Import Type: Select Import Type
@ -545,7 +560,6 @@ Channel:
Oldest: Oldest Oldest: Oldest
Most Popular: Most Popular Most Popular: Most Popular
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: This channel does not currently have any shorts This channel does not currently have any shorts: This channel does not currently have any shorts
Live: Live:
Live: Live Live: Live
@ -559,6 +573,12 @@ Channel:
Last Video Added: Last Video Added Last Video Added: Last Video Added
Newest: Newest Newest: Newest
Oldest: Oldest Oldest: Oldest
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: This channel does not currently have any podcasts
Releases:
Releases: Releases
This channel does not currently have any releases: This channel does not currently have any releases
About: About:
About: About About: About
Channel Description: Channel Description Channel Description: Channel Description
@ -836,8 +856,9 @@ Tooltips:
you want to be passed to the external player. you want to be passed to the external player.
DefaultCustomArgumentsTemplate: "(Default: '{defaultCustomArguments}')" DefaultCustomArgumentsTemplate: "(Default: '{defaultCustomArguments}')"
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists and the channel itself from appearing in search or trending. Hide Channels: Enter a channel name or channel ID to hide all videos, playlists and the channel itself from appearing in search, trending, most popular and recommended.
The channel name entered must be a complete match and is case sensitive. The channel name entered must be a complete match and is case sensitive.
Hide Subscriptions Live: 'This setting is overridden by the app-wide "{appWideSetting}" setting, in the "{subsection}" section of the "{settingsSection}"'
Subscription Settings: Subscription Settings:
Fetch Feeds from RSS: When enabled, FreeTube will use RSS instead of its default Fetch Feeds from RSS: When enabled, FreeTube will use RSS instead of its default
method for grabbing your subscription feed. RSS is faster and prevents IP blocking, method for grabbing your subscription feed. RSS is faster and prevents IP blocking,

View File

@ -29,6 +29,13 @@ Close: 'Close'
Back: 'Back' Back: 'Back'
Forward: 'Forward' Forward: 'Forward'
# Global
# Anything shared among components / views should be put here
Global:
Videos: Videos
Shorts: Shorts
Live: Live
Version {versionNumber} is now available! Click for more details: 'Version {versionNumber} Version {versionNumber} is now available! Click for more details: 'Version {versionNumber}
is now available! Click for more details' is now available! Click for more details'
Download From Site: 'Download From Site' Download From Site: 'Download From Site'
@ -88,6 +95,9 @@ Subscriptions:
Disabled Automatic Fetching: You have disabled automatic subscription fetching. Disabled Automatic Fetching: You have disabled automatic subscription fetching.
Refresh subscriptions to see them here. Refresh subscriptions to see them here.
Empty Channels: Your subscribed channels currently does not have any videos. Empty Channels: Your subscribed channels currently does not have any videos.
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}.
Trending: Trending:
Trending: 'Trending' Trending: 'Trending'
Trending Tabs: Trending Tabs Trending Tabs: Trending Tabs
@ -412,10 +422,16 @@ Settings:
Hide Channel Community: Hide channel community Hide Channel Community: Hide channel community
Hide Channel Shorts: Hide channel shorts Hide Channel Shorts: Hide channel shorts
Sections: Sections:
Side Bar: Side Bar Side Bar: Side bar
General: General General: General
Channel Page: Channel Page Channel Page: Channel Page
Watch Page: Watch Page Watch Page: Watch Page
Subscriptions Page: Subscriptions page
Hide Channel Podcasts: Hide channel podcasts
Hide Channel Releases: Hide channel releases
Hide Subscriptions Shorts: Hide subscriptions shorts
Hide Subscriptions Live: Hide subscriptions live
Hide Subscriptions Videos: Hide subscriptions videos
The app needs to restart for changes to take effect. Restart and apply change?: The The app needs to restart for changes to take effect. Restart and apply change?: The
app needs to restart for changes to take effect. Do you want to restart and apply app needs to restart for changes to take effect. Do you want to restart and apply
the changes? the changes?
@ -448,6 +464,7 @@ Settings:
Prompt To Skip: Prompt to skip Prompt To Skip: Prompt to skip
Do Nothing: Do nothing Do Nothing: Do nothing
Category Color: Category colour Category Color: Category colour
UseDeArrowTitles: Use DeArrow video titles
Download Settings: Download Settings:
Download Settings: Download Settings Download Settings: Download Settings
Ask Download Path: Ask for download path Ask Download Path: Ask for download path
@ -611,9 +628,16 @@ Channel:
have any live streams have any live streams
Live: Live Live: Live
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: This channel does not currently This channel does not currently have any shorts: This channel does not currently
have any shorts have any shorts
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: This channel does not currently
have any podcasts
Releases:
Releases: Releases
This channel does not currently have any releases: This channel does not currently
have any releases
Video: Video:
Mark As Watched: 'Mark As Watched' Mark As Watched: 'Mark As Watched'
Remove From History: 'Remove From History' Remove From History: 'Remove From History'
@ -918,8 +942,12 @@ Tooltips:
in-memory image cache. Will lead to increased RAM usage. in-memory image cache. Will lead to increased RAM usage.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists Hide Channels: Enter a channel name or channel ID to hide all videos, playlists
and the channel itself from appearing in search or trending. The channel name and the channel itself from appearing in search, trending, most popular and
entered must be a complete match and is case sensitive. recommended. The channel name entered must be a complete match and is case sensitive.
Hide Subscriptions Live: This setting is overridden by the app-wide {appWideSetting}
setting, in the {subsection} section of the {settingsSection}
SponsorBlock Settings:
UseDeArrowTitles: Replace video titles with user-submitted titles from DeArrow.
Playing Next Video Interval: Playing next video in no time. Click to cancel. | Playing Playing Next Video Interval: Playing next video in no time. Click to cancel. | Playing
next video in {nextVideoInterval} second. Click to cancel. | Playing next video next video in {nextVideoInterval} second. Click to cancel. | Playing next video
in {nextVideoInterval} seconds. Click to cancel. in {nextVideoInterval} seconds. Click to cancel.

View File

@ -27,6 +27,11 @@ Close: 'Cerrar'
Back: 'Volver' Back: 'Volver'
Forward: 'Adelante' Forward: 'Adelante'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videos'
# Search Bar # Search Bar
Search / Go to URL: 'Buscar / Ir a la URL' Search / Go to URL: 'Buscar / Ir a la URL'
# In Filter Button # In Filter Button

View File

@ -30,6 +30,13 @@ Close: 'Cerrar'
Back: 'Atrás' Back: 'Atrás'
Forward: 'Adelante' Forward: 'Adelante'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vídeos'
Shorts: Cortos
Live: En directo
# Search Bar # Search Bar
Search / Go to URL: 'Buscar / Ir a la dirección' Search / Go to URL: 'Buscar / Ir a la dirección'
# In Filter Button # In Filter Button
@ -84,6 +91,10 @@ Subscriptions:
Actualice las suscripciones para verlas aquí. Actualice las suscripciones para verlas aquí.
Empty Channels: Los canales a los que está suscrito no tienen actualmente ningún Empty Channels: Los canales a los que está suscrito no tienen actualmente ningún
vídeo. vídeo.
Subscriptions Tabs: Pestañas de suscripciones
All Subscription Tabs Hidden: Todas las pestañas de las suscripciones están ocultas.
Para ver el contenido, por favor, desoculta algunas pestañas en la sección «{subsection}»
en «{settingsSection}».
Trending: Trending:
Trending: 'Tendencias' Trending: 'Tendencias'
Default: Predeterminado Default: Predeterminado
@ -114,7 +125,7 @@ Settings:
# On Settings Page # On Settings Page
Settings: 'Ajustes' Settings: 'Ajustes'
General Settings: General Settings:
General Settings: 'General' General Settings: 'Ajustes generales'
Fallback to Non-Preferred Backend on Failure: 'Usar motor API secundario en caso Fallback to Non-Preferred Backend on Failure: 'Usar motor API secundario en caso
de fallo' de fallo'
Enable Search Suggestions: 'Activar sugerencias de búsqueda' Enable Search Suggestions: 'Activar sugerencias de búsqueda'
@ -267,8 +278,8 @@ Settings:
File Name Tooltip: Puede utilizar las siguientes variables. %Y Año 4 dígitos. File Name Tooltip: Puede utilizar las siguientes variables. %Y Año 4 dígitos.
%M Mes 2 dígitos. %D Día 2 dígitos. %H Hora 2 dígitos. %N Minuto 2 dígitos. %M Mes 2 dígitos. %D Día 2 dígitos. %H Hora 2 dígitos. %N Minuto 2 dígitos.
%S Segundo 2 dígitos. %T Milisegundo 3 dígitos. %s Video Segundo. %t Video %S Segundo 2 dígitos. %T Milisegundo 3 dígitos. %s Video Segundo. %t Video
Milisegundo 3 dígitos. %i Video ID. También puede utilizar "\" o "/" para Milisegundo 3 dígitos. %i Video ID. También puede utilizar \ o / para crear
crear subcarpetas. subcarpetas.
Enter Fullscreen on Display Rotate: Cambiar a pantalla completa al girar la pantalla Enter Fullscreen on Display Rotate: Cambiar a pantalla completa al girar la pantalla
Skip by Scrolling Over Video Player: Omitir al desplazarse sobre el reproductor Skip by Scrolling Over Video Player: Omitir al desplazarse sobre el reproductor
de vídeo de vídeo
@ -408,6 +419,12 @@ Settings:
Channel Page: Página del canal Channel Page: Página del canal
Watch Page: Ver la página Watch Page: Ver la página
General: General General: General
Subscriptions Page: Página de suscripciones
Hide Channel Releases: Ocultar las nuevas publicaciones de los canales
Hide Channel Podcasts: Ocultar los canales de podcasts
Hide Subscriptions Shorts: Ocultar las suscripciones para los vídeos cortos
Hide Subscriptions Videos: Ocultar las suscripciones de los Vídeos
Hide Subscriptions Live: Ocultar las suscripciones de los directos
The app needs to restart for changes to take effect. Restart and apply change?: ¿Quieres The app needs to restart for changes to take effect. Restart and apply change?: ¿Quieres
reiniciar FreeTube ahora para aplicar los cambios? reiniciar FreeTube ahora para aplicar los cambios?
Proxy Settings: Proxy Settings:
@ -632,9 +649,16 @@ Channel:
This channel does not currently have any live streams: Este canal no tiene actualmente This channel does not currently have any live streams: Este canal no tiene actualmente
ninguna retransmisión en directo ninguna retransmisión en directo
Shorts: Shorts:
Shorts: Cortos
This channel does not currently have any shorts: Este canal no tiene actualmente This channel does not currently have any shorts: Este canal no tiene actualmente
ningún vídeo corto ningún vídeo corto
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: Este canal actualmente no tiene
podcasts
Releases:
Releases: Publicaciones
This channel does not currently have any releases: Este canal no tiene actualmente
ninguna publicación
Video: Video:
Mark As Watched: 'Marcar como visto' Mark As Watched: 'Marcar como visto'
Remove From History: 'Borrar del historial' Remove From History: 'Borrar del historial'
@ -955,10 +979,12 @@ Tooltips:
una caché para la imagen en la memoria personalizada. Esto aumentará el uso una caché para la imagen en la memoria personalizada. Esto aumentará el uso
de la memoria RAM. de la memoria RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Introduzca un nombre o ID de canal para ocultar todos los vídeos, Hide Channels: Ingresa un nombre del canal o un ID del canal para ocultar todos
listas de reproducción y el propio canal y evitar que aparezcan en las búsquedas los videos, listas de reproducción y el propio canal para que no aparezcan en
o en las tendencias. El nombre del canal introducido debe coincidir completamente la búsqueda, tendencias, más populares y recomendados. El nombre del canal ingresado
y distingue entre mayúsculas y minúsculas. debe ser una coincidencia completa y distinguir entre mayúsculas y minúsculas.
Hide Subscriptions Live: Esta configuración se reemplaza por la configuración
«{appWideSetting}» de toda la aplicación, en la sección «{subsection}» de «{settingsSection}»
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: Sustituye los títulos de los vídeos por títulos enviados por UseDeArrowTitles: Sustituye los títulos de los vídeos por títulos enviados por
los usuarios desde DeArrow. los usuarios desde DeArrow.
@ -996,7 +1022,7 @@ Channels:
Empty: Tu lista de canales está actualmente vacía. Empty: Tu lista de canales está actualmente vacía.
Unsubscribe: Cancelar la suscripción Unsubscribe: Cancelar la suscripción
Unsubscribed: '{channelName} ha sido eliminado de tus suscripciones' Unsubscribed: '{channelName} ha sido eliminado de tus suscripciones'
Unsubscribe Prompt: ¿Está seguro de querer desuscribirse de "{channelName}"? Unsubscribe Prompt: ¿Está seguro/segura de querer desuscribirse de «{channelName}»?
Age Restricted: Age Restricted:
Type: Type:
Channel: Canal Channel: Canal

View File

@ -29,6 +29,13 @@ Close: 'Sulge'
Back: 'Tagasi' Back: 'Tagasi'
Forward: 'Edasi' Forward: 'Edasi'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videod'
Shorts: Lühivideod
Live: Otseeeter
Version {versionNumber} is now available! Click for more details: 'Versioon {versionNumber} Version {versionNumber} is now available! Click for more details: 'Versioon {versionNumber}
in nüüd saadaval! Lisateavet leiad siit' in nüüd saadaval! Lisateavet leiad siit'
Download From Site: 'Laadi veebisaidist alla' Download From Site: 'Laadi veebisaidist alla'
@ -89,6 +96,9 @@ Subscriptions:
Disabled Automatic Fetching: Sa oled lülitanud välja automaatse tellimuste laadimise. Disabled Automatic Fetching: Sa oled lülitanud välja automaatse tellimuste laadimise.
Uuendatud teabe nägemiseks laadi vaade uuesti. Uuendatud teabe nägemiseks laadi vaade uuesti.
Empty Channels: Sinu tellitud kanalites hetkel pole ühtegi videot. Empty Channels: Sinu tellitud kanalites hetkel pole ühtegi videot.
Subscriptions Tabs: Tellimuste vahekaardid
All Subscription Tabs Hidden: Kõik tellimuse vahekaardid on peidetud. Siinse sisu
nägemiseks palun eemalda kaartide peitmine jaotises „{subsection}“ / „{settingsSection}“.
Trending: Trending:
Trending: 'Populaarsust koguvad videod' Trending: 'Populaarsust koguvad videod'
Trending Tabs: Populaarsust koguvad kaardid Trending Tabs: Populaarsust koguvad kaardid
@ -274,6 +284,8 @@ Settings:
Skip by Scrolling Over Video Player: Jäta vahele, kerides üle videopleieri Skip by Scrolling Over Video Player: Jäta vahele, kerides üle videopleieri
Allow DASH AV1 formats: Luba DASH AV1 vormingud Allow DASH AV1 formats: Luba DASH AV1 vormingud
Enter Fullscreen on Display Rotate: Ekraani pööramisel ava täisekraanivaade Enter Fullscreen on Display Rotate: Ekraani pööramisel ava täisekraanivaade
Comment Auto Load:
Comment Auto Load: Kommentaaride automaatne laadimine
Privacy Settings: Privacy Settings:
Privacy Settings: 'Privaatsuse seadistused' Privacy Settings: 'Privaatsuse seadistused'
Remember History: 'Jäta ajalugu meelde' Remember History: 'Jäta ajalugu meelde'
@ -369,6 +381,21 @@ Settings:
Hide Channels Placeholder: Kanali nimi või tunnus Hide Channels Placeholder: Kanali nimi või tunnus
Display Titles Without Excessive Capitalisation: Näita pealkirju ilma liigsete Display Titles Without Excessive Capitalisation: Näita pealkirju ilma liigsete
suurtähtedeta suurtähtedeta
Sections:
General: Üldist
Side Bar: Külgpaan
Channel Page: Kanali vaade
Watch Page: Videovaade
Subscriptions Page: Tellimuste vaade
Hide Featured Channels: Peaida soovitatud kanalid
Hide Channel Playlists: Peida kanali esitusloendid
Hide Channel Shorts: Peida kanali lühivideod
Hide Channel Community: Peida kanali kogukond
Hide Channel Podcasts: Peida kanali taskuhäälingud
Hide Channel Releases: Peida kanali väljalasked
Hide Subscriptions Videos: Peida tellimuste videod
Hide Subscriptions Shorts: Peida tellimuste lühivideod
Hide Subscriptions Live: Peida tellimuste otse-eetrid
Proxy Settings: Proxy Settings:
Error getting network information. Is your proxy configured properly?: Võrguteavet Error getting network information. Is your proxy configured properly?: Võrguteavet
ei õnnestu leida. Kas sa oled puhverserveri ikka korralikult seadistanud? ei õnnestu leida. Kas sa oled puhverserveri ikka korralikult seadistanud?
@ -389,10 +416,10 @@ Settings:
rakendus vajab muudatuste jõustamiseks uuesti käivitamist. Kas teeme seda nüüd? rakendus vajab muudatuste jõustamiseks uuesti käivitamist. Kas teeme seda nüüd?
SponsorBlock Settings: SponsorBlock Settings:
Notify when sponsor segment is skipped: Anna teada, kui toetajate vaade jääb vahele Notify when sponsor segment is skipped: Anna teada, kui toetajate vaade jääb vahele
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock'i API 'SponsorBlock API Url (Default is https://sponsor.ajay.app)': Sponsorite blokeerija
URL (vaikimisi https://sponsor.ajay.app) API URL (vaikimisi https://sponsor.ajay.app)
Enable SponsorBlock: Kasuta SponsorBlock'i Enable SponsorBlock: Kasuta sponsorite blokeerijat
SponsorBlock Settings: SponsorBlock seadistused SponsorBlock Settings: Sponsorite blokeerija seadistused
Skip Options: Skip Options:
Auto Skip: Automaatne vahelejätmine Auto Skip: Automaatne vahelejätmine
Show In Seek Bar: Näita otsinguribal Show In Seek Bar: Näita otsinguribal
@ -400,6 +427,7 @@ Settings:
Skip Option: Jäta valik vahele Skip Option: Jäta valik vahele
Do Nothing: Ära tee midagi Do Nothing: Ära tee midagi
Category Color: Kategooria värv Category Color: Kategooria värv
UseDeArrowTitles: Laadi video pealkirjad DeArrow teenusest
External Player Settings: External Player Settings:
External Player: Väline meediamängija External Player: Väline meediamängija
External Player Settings: Välise meediamängija seadistused External Player Settings: Välise meediamängija seadistused
@ -424,7 +452,7 @@ Settings:
Experimental Settings: Experimental Settings:
Experimental Settings: Katselised seadistused Experimental Settings: Katselised seadistused
Warning: Tegemist on katseliste seadistustega ja sisselülitamisel võivad põhjustada Warning: Tegemist on katseliste seadistustega ja sisselülitamisel võivad põhjustada
rakenduse kokkujooksmist. Kindlasti ära unusta oma andmete vatundamist. Jätka rakenduse kokkujooksmist. Kindlasti ära unusta oma andmete varundamist. Jätka
omal vastutusel! omal vastutusel!
Replace HTTP Cache: Asenda HTTP vahemälu Replace HTTP Cache: Asenda HTTP vahemälu
Password Dialog: Password Dialog:
@ -542,6 +570,35 @@ Channel:
About: 'Kanali teave' About: 'Kanali teave'
Channel Description: 'Kanali kirjeldus' Channel Description: 'Kanali kirjeldus'
Featured Channels: 'Soovitatud kanalid' Featured Channels: 'Soovitatud kanalid'
Details: Üksikasjad
Location: Asukoht
Tags:
Search for: Otsi silti „{tag}“
Tags: Sildid
Joined: Liitunud
This channel does not allow searching: See kanal ei luba otsingu kasutamist
Channel Tabs: Kanali kaardid
Shorts:
This channel does not currently have any shorts: Sellel kanalil pole lühivideosid
Live:
Live: Otseeeter
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
This channel does not exist: Sellist kanalit ei leidu
This channel is age-restricted and currently cannot be viewed in FreeTube.: Sellel
kanalil on vanusega seotud piirangud ja teda ei saa hetkel FreeTube'i vahendusel
vaadata.
Podcasts:
Podcasts: Taskuhäälingud
This channel does not currently have any podcasts: Sellel kanalil hetkel pole
taskuhäälinguid
Releases:
This channel does not currently have any releases: Sellel kanalil pole hetkel
ühtegi väljalaset
Releases: Väljalasked
Video: Video:
Mark As Watched: 'Märgi vaadatuks' Mark As Watched: 'Märgi vaadatuks'
Remove From History: 'Kustuta ajaloost' Remove From History: 'Kustuta ajaloost'
@ -568,7 +625,7 @@ Video:
värskenda uuesti kontrollimiseks lehte' värskenda uuesti kontrollimiseks lehte'
# As in a Live Video # As in a Live Video
Live: 'Otse eetris' Live: 'Otse eetris'
Live Now: 'Otse eetris' Live Now: 'Hetkel otseeetris'
Live Chat: 'Vestlus reaalajas' Live Chat: 'Vestlus reaalajas'
Enable Live Chat: 'Luba reaalajas vestlust' Enable Live Chat: 'Luba reaalajas vestlust'
Live Chat is currently not supported in this build.: 'Reaalajas vestlus ei ole selles Live Chat is currently not supported in this build.: 'Reaalajas vestlus ei ole selles
@ -682,6 +739,8 @@ Video:
Show Super Chat Comment: Näita Super Chat'i kommentaare Show Super Chat Comment: Näita Super Chat'i kommentaare
Scroll to Bottom: Keri alla Scroll to Bottom: Keri alla
Upcoming: Tulemas Upcoming: Tulemas
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Otsevestlus
pole selle videovoo puhul saadaval. Võib-olla on üleslaadija vestluse keelanud.
Videos: Videos:
#& Sort By #& Sort By
Sort By: Sort By:
@ -794,9 +853,9 @@ Tooltips:
järjekorra pööramine) ei toeta siis ära näita hoiatusi. järjekorra pööramine) ei toeta siis ära näita hoiatusi.
Custom External Player Arguments: Semikoolonitega (;) eraldatud käsurea argumendid, Custom External Player Arguments: Semikoolonitega (;) eraldatud käsurea argumendid,
mida sa soovid välisele meediamängijale saata. mida sa soovid välisele meediamängijale saata.
External Player: "Seadistades välise meediamängija kuvame pisipildil ikooni video\ External Player: "Seadistades välise meediamängija kuvame pisipildil ikooni video
\ (või esitusloendi) esitamiseks välises meediamängijas. Hoiatus: Invidious'e\ (või esitusloendi) esitamiseks välises meediamängijas. Hoiatus: Invidious'e
\ seadistused ei mõjuta välise meediamängija kasutamist." seadistused ei mõjuta välise meediamängija kasutamist."
DefaultCustomArgumentsTemplate: "(Vaikimisi: '{defaultCustomArguments}')" DefaultCustomArgumentsTemplate: "(Vaikimisi: '{defaultCustomArguments}')"
Subscription Settings: Subscription Settings:
Fetch Feeds from RSS: Selle valiku kasutamisel FreeTube pruugib tellimuste andmete Fetch Feeds from RSS: Selle valiku kasutamisel FreeTube pruugib tellimuste andmete
@ -818,8 +877,8 @@ Tooltips:
kasutamist. kasutamist.
Invidious Instance: Invidious'e teenuse server, mida FreeTube kasutab API kutse Invidious Instance: Invidious'e teenuse server, mida FreeTube kasutab API kutse
tegemisel. tegemisel.
External Link Handling: "Vali vaikekäitumine, kui vajutatakse linki, mida ei saa\ External Link Handling: "Vali vaikekäitumine, kui vajutatakse linki, mida ei saa
\ avada FreeTubes.\nVaikimisi avaneb link kasutaja vaikebrauseris.\n" avada FreeTubes.\nVaikimisi avaneb link kasutaja vaikebrauseris.\n"
Player Settings: Player Settings:
Default Video Format: Vali kasutatavad videovormingud. DASH-vormingutel on üldjuhul Default Video Format: Vali kasutatavad videovormingud. DASH-vormingutel on üldjuhul
parem kvaliteet. Pärandvormingute kvaliteedi ülempiir on 720p ja seetõttu kasutavad parem kvaliteet. Pärandvormingute kvaliteedi ülempiir on 720p ja seetõttu kasutavad
@ -844,12 +903,18 @@ Tooltips:
hiire ratast. hiire ratast.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Sisesta kanali nimi või kanali ID, et kõik videod, esitusloendid Hide Channels: Sisesta kanali nimi või kanali ID, et kõik videod, esitusloendid
ja kanal ise ei oleks nähtav otsingus ega populaarsust koguvate videote vaates. ja kanal ise ei oleks nähtav otsingus, soovitatavate videote, populaarsete videote
Sisestatud kanali nimi peab vastama täielikult ja on tõstutundlik. ja populaarsust koguvate videote vaates. Sisestatud kanali nimi peab otsingule
vastama täielikult ja on tõstutundlik.
Hide Subscriptions Live: Selle seadistuse tühistab rakenduseülene „{appWideSetting}“
seadistus „{subsection}“/„{settingsSection}“
Experimental Settings: Experimental Settings:
Replace HTTP Cache: Sellega lülitatakse välja Electron'i standardne kettal paiknev Replace HTTP Cache: Sellega lülitatakse välja Electron'i standardne kettal paiknev
http-puhver ja võetakse kasutusele rakenduse mälupõhine puhver. Üheks tulemuseks http-puhver ja võetakse kasutusele rakenduse mälupõhine puhver. Üheks tulemuseks
saab olema suurem mälukasutus. saab olema suurem mälukasutus.
SponsorBlock Settings:
UseDeArrowTitles: Asenda video nimi kasutajate poolt DeArrow teenusesse lisatud
nimega (pealkirjaga).
Playing Next Video Interval: Kohe esitan järgmist videot. Tühistamiseks klõpsi. | Playing Next Video Interval: Kohe esitan järgmist videot. Tühistamiseks klõpsi. |
{nextVideoInterval} sekundi möödumisel esitan järgmist videot. Tühistamiseks klõpsi. {nextVideoInterval} sekundi möödumisel esitan järgmist videot. Tühistamiseks klõpsi.
| {nextVideoInterval} sekundi möödumisel esitan järgmist videot. Tühistamiseks klõpsi. | {nextVideoInterval} sekundi möödumisel esitan järgmist videot. Tühistamiseks klõpsi.
@ -894,3 +959,7 @@ Chapters:
praegune peatükk: {chapterName}' praegune peatükk: {chapterName}'
Ok: Sobib Ok: Sobib
Preferences: Eelistused Preferences: Eelistused
Hashtag:
Hashtag: Teemaviide
This hashtag does not currently have any videos: Selle teemaviite ehk haaksõna alusel
ei leidu hetkel ühtegi videot

View File

@ -29,6 +29,11 @@ Close: 'Itxi'
Back: 'Atzera' Back: 'Atzera'
Forward: 'Aurrera' Forward: 'Aurrera'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Bideoak'
Version {versionNumber} is now available! Click for more details: '{versionNumber} Version {versionNumber} is now available! Click for more details: '{versionNumber}
bertsioa erabilgarri! Klikatu azalpen gehiagorako' bertsioa erabilgarri! Klikatu azalpen gehiagorako'
Download From Site: 'Webgunetik jaitsi' Download From Site: 'Webgunetik jaitsi'

View File

@ -1,11 +1,11 @@
FreeTube: 'FreeTube' FreeTube: 'FreeTube'
# Currently on Subscriptions, Playlists, and History # Currently on Subscriptions, Playlists, and History
'This part of the app is not ready yet. Come back later when progress has been made.': >- 'This part of the app is not ready yet. Come back later when progress has been made.': >-
Tämä sovelluksen osa ei ole vielä valmis. Tule takaisin myöhemmin. Tämä sovelluksen osa ei ole vielä valmis. Tule takaisin myöhemmin kun olemme edistyneet.
# Webkit Menu Bar # Webkit Menu Bar
File: 'Tiedosto' File: 'Tiedosto'
Quit: 'Poistu' Quit: 'Lopeta'
Edit: 'Muokkaa' Edit: 'Muokkaa'
Undo: 'Kumoa' Undo: 'Kumoa'
Redo: 'Tee uudelleen' Redo: 'Tee uudelleen'
@ -15,18 +15,25 @@ Paste: 'Liitä'
Delete: 'Poista' Delete: 'Poista'
Select all: 'Valitse kaikki' Select all: 'Valitse kaikki'
Reload: 'Lataa uudelleen' Reload: 'Lataa uudelleen'
Force Reload: 'Pakota uudelleenlataus' Force Reload: 'Pakota Uudelleenlataus'
Toggle Developer Tools: 'Kehittäjän työkalut' Toggle Developer Tools: 'Vaihda Kehittäjän Työkaluihin'
Actual size: 'Todellinen koko' Actual size: 'Todellinen koko'
Zoom in: 'Lähennä' Zoom in: 'Lähennä'
Zoom out: 'Loitonna' Zoom out: 'Loitonna'
Toggle fullscreen: 'Koko näytön tila' Toggle fullscreen: 'Vaihda koko näyttöön'
Window: 'Ikkuna' Window: 'Ikkuna'
Minimize: 'Pienennä' Minimize: 'Pienennä'
Close: 'Sulje' Close: 'Sulje'
Back: 'Takaisin' Back: 'Takaisin'
Forward: 'Eteenpäin' Forward: 'Eteenpäin'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videot'
Shorts: Lyhyet
Live: Livenä
# Search Bar # Search Bar
Search / Go to URL: 'Etsi / Mene osoitteeseen' Search / Go to URL: 'Etsi / Mene osoitteeseen'
# In Filter Button # In Filter Button
@ -81,6 +88,10 @@ Subscriptions:
Empty Channels: Tilaamillasi kanavilla ei ole videoita tällä hetkellä. Empty Channels: Tilaamillasi kanavilla ei ole videoita tällä hetkellä.
Disabled Automatic Fetching: Olet poistanut käytöstä automaattisen tilaustennoutamisen. Disabled Automatic Fetching: Olet poistanut käytöstä automaattisen tilaustennoutamisen.
Virkistä tilaukset nähdäksesi ne täällä. Virkistä tilaukset nähdäksesi ne täällä.
Subscriptions Tabs: Tilaukset-välilehdet
All Subscription Tabs Hidden: Kaikki tilausvälilehdet on piilotettu. Jos haluat
nähdä sisällön täällä, poista joitakin välilehtiä ”{settingsSection}”-osion ”{settingsSection}”-osion
”{subsection}”-välilehdistä.
Trending: Trending:
Trending: 'Nousussa' Trending: 'Nousussa'
Trending Tabs: Nousussa olevat välilehdet Trending Tabs: Nousussa olevat välilehdet
@ -214,9 +225,9 @@ Settings:
Force Local Backend for Legacy Formats: 'Pakota paikallinen taustaohjelma vanhoille Force Local Backend for Legacy Formats: 'Pakota paikallinen taustaohjelma vanhoille
formaateille' formaateille'
Remember History: 'Muista historia' Remember History: 'Muista historia'
Play Next Video: 'Toista seuraava video' Play Next Video: 'Toista Seuraava Video'
Turn on Subtitles by Default: 'Ota tekstitys käyttöön oletusarvoisesti' Turn on Subtitles by Default: 'Ota tekstitys käyttöön oletusarvoisesti'
Autoplay Videos: 'Toista seuraava video' Autoplay Videos: 'Toista Automaattisesti Videot'
Proxy Videos Through Invidious: 'Välitä videot Invidiousin kautta' Proxy Videos Through Invidious: 'Välitä videot Invidiousin kautta'
Autoplay Playlists: 'Toista soittolistat automaattisesti' Autoplay Playlists: 'Toista soittolistat automaattisesti'
Enable Theatre Mode by Default: 'Ota teatteritila käyttöön oletusarvoisesti' Enable Theatre Mode by Default: 'Ota teatteritila käyttöön oletusarvoisesti'
@ -391,7 +402,7 @@ Settings:
Hide Sharing Actions: Piilota jakamistoiminnot Hide Sharing Actions: Piilota jakamistoiminnot
Hide Chapters: Piilota kappaleet Hide Chapters: Piilota kappaleet
Hide Channels: Piilota videot kanavilta Hide Channels: Piilota videot kanavilta
Hide Upcoming Premieres: Piilota tulevat ensiesitykset Hide Upcoming Premieres: Piilota Tulevat Ensiesitykset
Hide Channels Placeholder: Kanavan nimi tai tunnus Hide Channels Placeholder: Kanavan nimi tai tunnus
Display Titles Without Excessive Capitalisation: Näytä otsikot ilman liiallista Display Titles Without Excessive Capitalisation: Näytä otsikot ilman liiallista
isoja kirjaimia isoja kirjaimia
@ -404,6 +415,11 @@ Settings:
Channel Page: Kanavan sivu Channel Page: Kanavan sivu
Watch Page: Katso sivu Watch Page: Katso sivu
General: Yleiset General: Yleiset
Subscriptions Page: Tilaukset-sivu
Hide Channel Releases: Piilota kanavajulkaisut
Hide Channel Podcasts: Piilota kanavan podcastit
Hide Subscriptions Videos: Piilota tilausvideot
Hide Subscriptions Live: Piilota tilausten livet
The app needs to restart for changes to take effect. Restart and apply change?: Sovellus The app needs to restart for changes to take effect. Restart and apply change?: Sovellus
on käynnistettävä uudelleen, jotta muutokset tulevat voimaan. Käynnistetäänkö on käynnistettävä uudelleen, jotta muutokset tulevat voimaan. Käynnistetäänkö
uudelleen? uudelleen?
@ -436,6 +452,7 @@ Settings:
Do Nothing: Älä tee mitään Do Nothing: Älä tee mitään
Prompt To Skip: Ehdota ohittamista Prompt To Skip: Ehdota ohittamista
Category Color: Luokan väri Category Color: Luokan väri
UseDeArrowTitles: Käytä DeArrow Video Otsikoita
External Player Settings: External Player Settings:
Custom External Player Arguments: Omavalintaisen ulkoisen toisto-ohjelman määritykset Custom External Player Arguments: Omavalintaisen ulkoisen toisto-ohjelman määritykset
Custom External Player Executable: Omavalintaisen ulkoisen toisto-ohjelman ajettava Custom External Player Executable: Omavalintaisen ulkoisen toisto-ohjelman ajettava
@ -460,9 +477,9 @@ Settings:
Hide Search Bar: Piilota hakupalkki Hide Search Bar: Piilota hakupalkki
Experimental Settings: Experimental Settings:
Experimental Settings: Kokeelliset asetukset Experimental Settings: Kokeelliset asetukset
Warning: Nämä asetukset ovat kokeellisia ja ne aiheuttavat kaatumisia, kun ne Warning: Nämä asetukset ovat kokeellisia ja ne voivat aiheuttavat kaatumisia,
ovat käytössä. Varmuuskopioiden tekeminen on erittäin suositeltavaa. Käytä omalla kun ne ovat käytössä. Varmuuskopioiden tekeminen on erittäin suositeltavaa.
vastuullasi! Käytä omalla vastuullasi!
Replace HTTP Cache: Korvaa HTTP-välimuisti Replace HTTP Cache: Korvaa HTTP-välimuisti
Password Dialog: Password Dialog:
Password: Salasana Password: Salasana
@ -578,6 +595,17 @@ Channel:
Live: Livenä Live: Livenä
This channel does not currently have any live streams: Tällä kanavalla ei ole This channel does not currently have any live streams: Tällä kanavalla ei ole
tällä hetkellä yhtään suoraa lähetystä tällä hetkellä yhtään suoraa lähetystä
Shorts:
This channel does not currently have any shorts: Tällä kanavalla ei juuri nyt
ole lyhyitä
Podcasts:
Podcasts: Podcastit
This channel does not currently have any podcasts: Tällä kanavalla ei ole yhtäkään
podcastia
Releases:
Releases: Julkaisut
This channel does not currently have any releases: Tällä kanavalla ei ole yhtäkään
julkaisua
Video: Video:
Open in YouTube: 'Avaa Youtubessa' Open in YouTube: 'Avaa Youtubessa'
Copy YouTube Link: 'Kopioi Youtube-linkki' Copy YouTube Link: 'Kopioi Youtube-linkki'
@ -643,7 +671,7 @@ Video:
Mark As Watched: Merkitse katsotuksi Mark As Watched: Merkitse katsotuksi
Autoplay: Automaattinen toisto Autoplay: Automaattinen toisto
Play Previous Video: Toista edellinen video Play Previous Video: Toista edellinen video
Play Next Video: Toista seuraava video Play Next Video: Toista Seuraava Video
Reverse Playlist: Käänteinen soittolista Reverse Playlist: Käänteinen soittolista
Shuffle Playlist: Sekoita soittolistaa Shuffle Playlist: Sekoita soittolistaa
Loop Playlist: Kierrätä soittolistaa Loop Playlist: Kierrätä soittolistaa
@ -715,7 +743,7 @@ Video:
Premieres on: Julkaistaan Premieres on: Julkaistaan
Premieres: Ensilähetykset Premieres: Ensilähetykset
Show Super Chat Comment: Näytä Super Chat -kommentti Show Super Chat Comment: Näytä Super Chat -kommentti
Scroll to Bottom: Vieritä alaspäin Scroll to Bottom: Vieritä Alaspäin
Upcoming: Tuleva Upcoming: Tuleva
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Live-chat 'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Live-chat
ei ole käytettävissä tässä suoratoistossa. Lataaja on saattanut poistaa sen käytöstä. ei ole käytettävissä tässä suoratoistossa. Lataaja on saattanut poistaa sen käytöstä.
@ -778,8 +806,8 @@ Comments:
Reply: 'Vastaa' Reply: 'Vastaa'
There are no comments available for this video: 'Tähän videoon ei ole yhtään kommenttia' There are no comments available for this video: 'Tähän videoon ei ole yhtään kommenttia'
Load More Comments: 'Lataa lisää kommentteja' Load More Comments: 'Lataa lisää kommentteja'
There are no more comments for this video: Ei enempää kommentteja There are no more comments for this video: Ei ole enempää kommentteja tälle videolle
No more comments available: Ei enempää kommentteja No more comments available: Eiole enempää kommentteja
Newest first: Uusimmat ensin Newest first: Uusimmat ensin
Top comments: Suosituimmat kommentit Top comments: Suosituimmat kommentit
Sort by: Lajitteluperuste Sort by: Lajitteluperuste
@ -847,7 +875,7 @@ Profile:
Add Selected To Profile: Lisää valitut profiiliin Add Selected To Profile: Lisää valitut profiiliin
Delete Selected: Poista valitut Delete Selected: Poista valitut
Select None: Älä valitse mitään Select None: Älä valitse mitään
Select All: Valitse kaikki Select All: Valitse Kaikki
'{number} selected': '{number} valittu' '{number} selected': '{number} valittu'
Other Channels: Muut kanavat Other Channels: Muut kanavat
Subscription List: Tilauslista Subscription List: Tilauslista
@ -895,11 +923,12 @@ Tooltips:
toiston nopeutta. Palataksesi alkuperäiseen toistonopeuteen (1x ellei toisin toiston nopeutta. Palataksesi alkuperäiseen toistonopeuteen (1x ellei toisin
määritelty asetuksissa), pidä CTRL-painike (Komentopainike MAC-tietokoneessa) määritelty asetuksissa), pidä CTRL-painike (Komentopainike MAC-tietokoneessa)
painettuna ja paina hiiren vasenta näppäintä. painettuna ja paina hiiren vasenta näppäintä.
Skip by Scrolling Over Video Player: Käytä vierityspyörää videon selaamiseen MPV-tyyliin. Skip by Scrolling Over Video Player: Käytä vieritysrullaa videon selaamiseen,
Allow DASH AV1 formats: DASH AV1 -formaatit saattavat näyttää paremmalta kuin MPV-tyyliin.
DASH H.264 -formaatit. DASH AV1 -formaatit vaativat enemmän tehoa toistamiseen! Allow DASH AV1 formats: DASH AV1 formaatit saattavat näyttää paremmalta kuin DASH
Ne eivät ole käytettävissä kaikissa videoissa, ja näissä tapauksissa soitin H.264 formaatit. DASH AV1 formaatit vaativat enemmän tehoa toistamiseen! Ne
käyttää sen sijaan DASH H.264 -formaatteja. eivät ole käytettävissä kaikissa videoissa ja näissä tapauksissa soitin käyttää
sen sijaan DASH H.264 formaatteja.
Privacy Settings: Privacy Settings:
Remove Video Meta Files: Kun tämä on kytkettynä päälle, FreeTube poistaa automaattisesti Remove Video Meta Files: Kun tämä on kytkettynä päälle, FreeTube poistaa automaattisesti
meta-tiedostot jotka luotiin videon toiston aikana, katselusivu suljettaessa. meta-tiedostot jotka luotiin videon toiston aikana, katselusivu suljettaessa.
@ -922,8 +951,13 @@ Tooltips:
käyttöä. käyttöä.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Anna kanavan nimi tai kanavatunnus piilottaaksesi kaikki videot, Hide Channels: Anna kanavan nimi tai kanavatunnus piilottaaksesi kaikki videot,
soittolistat ja itse kanavan näkymästä haussa tai trendaamisessa. Annetun kanavan soittolistat ja itse kanavan näkymästä haussa, trendaamisessa, suosituimmissa
nimen on vastattava täydellisesti ja kirjainkoolla on merkitystä. ja suositelluissa. Annetun kanavan nimen on oltava täysin oikein ja kirjainkoolla
on merkitystä.
Hide Subscriptions Live: Tämä asetus ohitetaan koko sovelluksen laajuisella ”{appWideSetting}”-asetuksella,
joka on ”{settingsSection}”-osion ”{subsection}”-osiossa.
SponsorBlock Settings:
UseDeArrowTitles: Korvaa videon otsikot käyttäjien lähettämillä DeArrow'n otsikoilla.
More: Lisää More: Lisää
Playing Next Video Interval: Seuraava video alkaa. Klikkaa peruuttaaksesi. |Seuraava Playing Next Video Interval: Seuraava video alkaa. Klikkaa peruuttaaksesi. |Seuraava
video alkaa {nextVideoInterval} sekunnin kuluttua. Klikkaa peruuttaaksesi. | Seuraava video alkaa {nextVideoInterval} sekunnin kuluttua. Klikkaa peruuttaaksesi. | Seuraava
@ -946,7 +980,7 @@ Downloading failed: Videon "{videoTitle}" lataamisessa havaittiin ongelma
Downloading has completed: Videon "{videoTitle}" lataus on valmis Downloading has completed: Videon "{videoTitle}" lataus on valmis
Starting download: Aloitetaan lataamaan "{videoTitle}" Starting download: Aloitetaan lataamaan "{videoTitle}"
Screenshot Success: Kuvakaappaus tallennettu nimellä ”{filePath}” Screenshot Success: Kuvakaappaus tallennettu nimellä ”{filePath}”
New Window: Uusi ikkuna New Window: Uusi Ikkuna
Age Restricted: Age Restricted:
This {videoOrPlaylist} is age restricted: Tämä {videoOrPlaylist} on ikärajoitettu This {videoOrPlaylist} is age restricted: Tämä {videoOrPlaylist} on ikärajoitettu
Type: Type:

View File

@ -28,6 +28,13 @@ Close: 'Fermer'
Back: 'Retour' Back: 'Retour'
Forward: 'Avancer' Forward: 'Avancer'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vidéos'
Shorts: Shorts
Live: En direct
# Search Bar # Search Bar
Search / Go to URL: 'Rechercher / ouvrir l''URL' Search / Go to URL: 'Rechercher / ouvrir l''URL'
# In Filter Button # In Filter Button
@ -87,6 +94,10 @@ Subscriptions:
abonnements. Actualisez les abonnements pour les voir ici. abonnements. Actualisez les abonnements pour les voir ici.
Empty Channels: Les chaînes auxquelles vous êtes abonné(e) ne contiennent actuellement Empty Channels: Les chaînes auxquelles vous êtes abonné(e) ne contiennent actuellement
aucune vidéo. aucune vidéo.
Subscriptions Tabs: Onglets Abonnements
All Subscription Tabs Hidden: Tous les onglets d'abonnement sont cachés. Pour voir
le contenu ici, veuillez désactiver certains onglets dans la section « {subsection} »
dans « {settingsSection} ».
Trending: Trending:
Trending: 'Tendance' Trending: 'Tendance'
Trending Tabs: Onglets des Tendances Trending Tabs: Onglets des Tendances
@ -427,6 +438,12 @@ Settings:
Side Bar: Barre latérale Side Bar: Barre latérale
Watch Page: Page de lecture Watch Page: Page de lecture
General: Général General: Général
Subscriptions Page: Page Abonnements
Hide Channel Podcasts: Masquer les podcasts de la chaîne
Hide Channel Releases: Masquer les publications de la chaîne
Hide Subscriptions Videos: Masquer les vidéos des abonnements
Hide Subscriptions Shorts: Masquer les shorts des abonnements
Hide Subscriptions Live: Masquer les diffusions en direct des abonnements
The app needs to restart for changes to take effect. Restart and apply change?: L'application The app needs to restart for changes to take effect. Restart and apply change?: L'application
doit être redémarrée pour que les changements prennent effet. Redémarrer et appliquer doit être redémarrée pour que les changements prennent effet. Redémarrer et appliquer
les changements ? les changements ?
@ -623,9 +640,16 @@ Channel:
This channel does not currently have any live streams: Cette chaîne n'a actuellement This channel does not currently have any live streams: Cette chaîne n'a actuellement
aucun flux en direct aucun flux en direct
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: Cette chaîne n'a actuellement This channel does not currently have any shorts: Cette chaîne n'a actuellement
aucun shorts aucun shorts
Releases:
Releases: Publications
This channel does not currently have any releases: Cette chaîne n'a actuellement
aucune publication
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: Cette chaîne n'a pas encore
de podcasts
Video: Video:
Mark As Watched: 'Marquer comme vu' Mark As Watched: 'Marquer comme vu'
Remove From History: 'Retirer de l''historique' Remove From History: 'Retirer de l''historique'
@ -999,8 +1023,12 @@ Tooltips:
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Entrez un nom de chaîne ou un identifiant de chaîne pour empêcher Hide Channels: Entrez un nom de chaîne ou un identifiant de chaîne pour empêcher
toutes les vidéos, les listes de lecture et la chaîne elle-même d'apparaître toutes les vidéos, les listes de lecture et la chaîne elle-même d'apparaître
dans les recherches ou les tendances. Le nom du canal entré doit être une correspondance dans les recherches, dans les catégories Tendances, Plus populaires et Recommandés.
complète et est sensible à la casse. Le nom de la chaîne entré doit correspondre exactement et est sensible à la
casse.
Hide Subscriptions Live: Ce paramètre est remplacé par le paramètre « {appWideSetting} »
applicable à l'ensemble de l'application, dans la section « {subsection} » de
la section « {settingsSection} »
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: Remplacez les titres des vidéos par des titres proposés par UseDeArrowTitles: Remplacez les titres des vidéos par des titres proposés par
les utilisateurs de DeArrow. les utilisateurs de DeArrow.

View File

@ -30,6 +30,11 @@ Close: 'Pechar'
Back: 'Atrás' Back: 'Atrás'
Forward: 'Adiante' Forward: 'Adiante'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vídeos'
Version {versionNumber} is now available! Click for more details: 'A versión {versionNumber} Version {versionNumber} is now available! Click for more details: 'A versión {versionNumber}
está dispoñible! Fai clic para veres máis detalles' está dispoñible! Fai clic para veres máis detalles'
Download From Site: 'Descargar do sitio' Download From Site: 'Descargar do sitio'

View File

@ -29,6 +29,13 @@ Close: 'סגירה'
Back: 'אחורה' Back: 'אחורה'
Forward: 'קדימה' Forward: 'קדימה'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'סרטונים'
Shorts: Shorts
Live: חי
Version {versionNumber} is now available! Click for more details: 'גרסה {versionNumber} Version {versionNumber} is now available! Click for more details: 'גרסה {versionNumber}
זמינה מעתה! לחיצה תציג פרטים נוספים' זמינה מעתה! לחיצה תציג פרטים נוספים'
Download From Site: 'הורדה מהאתר' Download From Site: 'הורדה מהאתר'
@ -88,6 +95,9 @@ Subscriptions:
Disabled Automatic Fetching: השבתת משיכת מינויים אוטומטית. יש לרענן את המינויים Disabled Automatic Fetching: השבתת משיכת מינויים אוטומטית. יש לרענן את המינויים
כדי לצפות בהם כאן. כדי לצפות בהם כאן.
Empty Channels: בערוצים אליהם נרשמת אין כלל סרטונים. Empty Channels: בערוצים אליהם נרשמת אין כלל סרטונים.
All Subscription Tabs Hidden: כל לשוניות המינויים מותסרות. כדי לראות את התוכן כאן,
נא לבטל את הסתרתן של כמה מהלשוניות תחת הסעיף „{subsection}” שב„{settingsSection}”.
Subscriptions Tabs: לשוניות מינויים
Trending: Trending:
Trending: 'הסרטונים החמים' Trending: 'הסרטונים החמים'
Trending Tabs: לשוניות מובילים Trending Tabs: לשוניות מובילים
@ -398,6 +408,12 @@ Settings:
Channel Page: עמוד ערוץ Channel Page: עמוד ערוץ
Side Bar: סרגל צד Side Bar: סרגל צד
General: כללי General: כללי
Subscriptions Page: עמוד מינויים
Hide Channel Podcasts: הסתרת הסכתים של הערוץ
Hide Channel Releases: הסתרת שחרורים של הערוץ
Hide Subscriptions Live: הסתרת שידורים חיים של המינויים
Hide Subscriptions Shorts: הסתרת Shorts של המינויים
Hide Subscriptions Videos: הסתרת סרטוני מינוי
The app needs to restart for changes to take effect. Restart and apply change?: צריך The app needs to restart for changes to take effect. Restart and apply change?: צריך
להפעיל את היישומון מחדש כדי שהשינויים ייכנסו לתוקף. להפעיל מחדש ולהחיל את השינוי? להפעיל את היישומון מחדש כדי שהשינויים ייכנסו לתוקף. להפעיל מחדש ולהחיל את השינוי?
Proxy Settings: Proxy Settings:
@ -620,8 +636,13 @@ Channel:
This channel does not currently have any live streams: לערוץ הזה אין שידורים חיים This channel does not currently have any live streams: לערוץ הזה אין שידורים חיים
כרגע כרגע
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: אין כרגע Shorts בערוץ הזה This channel does not currently have any shorts: אין כרגע Shorts בערוץ הזה
Podcasts:
Podcasts: הסכתים
This channel does not currently have any podcasts: בערוץ הזה אין הסכתים כרגע
Releases:
Releases: שחרורים
This channel does not currently have any releases: בערוץ הזה אין שחרורים כרגע
Video: Video:
Mark As Watched: 'סמנו כנצפה' Mark As Watched: 'סמנו כנצפה'
Remove From History: 'מחקו מהיסטוריית הצפייה' Remove From History: 'מחקו מהיסטוריית הצפייה'
@ -913,8 +934,10 @@ Tooltips:
אישית בזיכרון. יגדיל את צריכת הזיכרון (RAM). אישית בזיכרון. יגדיל את צריכת הזיכרון (RAM).
Distraction Free Settings: Distraction Free Settings:
Hide Channels: יש למלא את שם או מזהה הערוץ כדי להסתיר את כל הסרטונים, רשימות הנגינה Hide Channels: יש למלא את שם או מזהה הערוץ כדי להסתיר את כל הסרטונים, רשימות הנגינה
ואת הערוץ עצמו כך שלא יופיע בחיפוש או במובילים. שם הערוץ שמילאת צריך להיות תואם ואת הערוץ עצמו כך שלא יופיע בחיפוש, במובילים, בנפוצים ביותר או במומלצים. שם
במלואו ותואם מבחינת רישיות (אותיות גדולות/קטנות). הערוץ שמילאת צריך להיות תואם במלואו ותואם מבחינת רישיות (אותיות גדולות/קטנות).
Hide Subscriptions Live: הגדרה זו נדרסת על ידי ההגדרה הכללית „{appWideSetting}”,
בסעיף „{subsection}” שב„{settingsSection}”
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: החלפת כותרות הסרטונים עם כותרות ששלחו משתמשים ב־DeArrow. UseDeArrowTitles: החלפת כותרות הסרטונים עם כותרות ששלחו משתמשים ב־DeArrow.
More: עוד More: עוד

View File

@ -29,6 +29,13 @@ Close: 'Zatvori'
Back: 'Natrag' Back: 'Natrag'
Forward: 'Naprijed' Forward: 'Naprijed'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videa'
Shorts: Kratka videa
Live: Uživo
# Search Bar # Search Bar
Search / Go to URL: 'Pretraži / Idi na URL' Search / Go to URL: 'Pretraži / Idi na URL'
# In Filter Button # In Filter Button
@ -408,6 +415,8 @@ Settings:
General: Opće General: Opće
Side Bar: Bočna traka Side Bar: Bočna traka
Channel Page: Stranica kanala Channel Page: Stranica kanala
Hide Channel Podcasts: Sakrij kanal podcastova
Hide Channel Releases: Sakrij kanal izdanja
The app needs to restart for changes to take effect. Restart and apply change?: Promjene The app needs to restart for changes to take effect. Restart and apply change?: Promjene
će se primijeniti nakon ponovnog pokeretanja programa. Ponovo pokrenuti program? će se primijeniti nakon ponovnog pokeretanja programa. Ponovo pokrenuti program?
Proxy Settings: Proxy Settings:
@ -439,6 +448,7 @@ Settings:
Prompt To Skip: Poziv za preskakanje Prompt To Skip: Poziv za preskakanje
Do Nothing: Ne čini ništa Do Nothing: Ne čini ništa
Category Color: Boja kategorije Category Color: Boja kategorije
UseDeArrowTitles: Koristi DeArrow Video naslove
External Player Settings: External Player Settings:
Custom External Player Arguments: Argumenti prilagođenog vanjskog playera Custom External Player Arguments: Argumenti prilagođenog vanjskog playera
Custom External Player Executable: Izvršna datoteka prilagođenog vanjskog playera Custom External Player Executable: Izvršna datoteka prilagođenog vanjskog playera
@ -635,7 +645,14 @@ Channel:
Shorts: Shorts:
This channel does not currently have any shorts: Ovaj kanal trenutačno nema kratka This channel does not currently have any shorts: Ovaj kanal trenutačno nema kratka
videa videa
Shorts: Kratka videa Releases:
Releases: Izdanja
This channel does not currently have any releases: Ovaj kanal trenutačno nema
izdanja
Podcasts:
Podcasts: Podcasti
This channel does not currently have any podcasts: Ovaj kanal trenutačno nema
podcastova
Video: Video:
Mark As Watched: 'Označi kao pogledano' Mark As Watched: 'Označi kao pogledano'
Remove From History: 'Ukloni iz povijesti' Remove From History: 'Ukloni iz povijesti'
@ -948,8 +965,10 @@ Tooltips:
i aktivira prilagođenu predmemoriju slika u memoriji. Povećava korištenje RAM-a. i aktivira prilagođenu predmemoriju slika u memoriji. Povećava korištenje RAM-a.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Upiši ime kanala ili ID kanala za skrivanje svih videa, zbirki Hide Channels: Upiši ime kanala ili ID kanala za skrivanje svih videa, zbirki
kao i sam kanal u pretrazi ili u trendovima. Upisano ime kanala se mora potpuno kao i samog kanala u pretrazi, trendovima popularnim i preporučenim. Upisano
poklapati i razlikuje velika i mala slova. ime kanala se mora potpuno poklapati i razlikuje velika i mala slova.
SponsorBlock Settings:
UseDeArrowTitles: Zamijeni naslove videa koje su poslali korisnici s DeArrow naslovima.
Playing Next Video Interval: Trenutna reprodukcija sljedećeg videa. Pritisni za prekid. Playing Next Video Interval: Trenutna reprodukcija sljedećeg videa. Pritisni za prekid.
| Reprodukcija sljedećeg videa za {nextVideoInterval} sekunde. Pritisni za prekid. | Reprodukcija sljedećeg videa za {nextVideoInterval} sekunde. Pritisni za prekid.
| Reprodukcija sljedećeg videa za {nextVideoInterval} sekundi. Pritisni za prekid. | Reprodukcija sljedećeg videa za {nextVideoInterval} sekundi. Pritisni za prekid.

View File

@ -30,6 +30,13 @@ Close: 'Bezárás'
Back: 'Vissza' Back: 'Vissza'
Forward: 'Előre' Forward: 'Előre'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videók'
Shorts: Rövidfilmek
Live: Élő
Version {versionNumber} is now available! Click for more details: 'A(z) {versionNumber} Version {versionNumber} is now available! Click for more details: 'A(z) {versionNumber}
verzió már elérhető! Kattintson a további részletekért' verzió már elérhető! Kattintson a további részletekért'
Download From Site: 'Letöltés a webhelyről' Download From Site: 'Letöltés a webhelyről'
@ -92,6 +99,10 @@ Subscriptions:
Disabled Automatic Fetching: Az önműködő feliratkozási kérés letiltva. Frissítse Disabled Automatic Fetching: Az önműködő feliratkozási kérés letiltva. Frissítse
a feliratkozást a megtekintéséhez. a feliratkozást a megtekintéséhez.
Empty Channels: A feliratkozott csatornák jelenleg nem tartalmaznak videókat. Empty Channels: A feliratkozott csatornák jelenleg nem tartalmaznak videókat.
All Subscription Tabs Hidden: Az összes feliratkozási lap el van rejtve. Az itteni
tartalom megtekintéséhez, kérjük, jelenítse meg néhány lap elrejtését a(z) „{settingsSection}”
„{subsection}” szakaszában.
Subscriptions Tabs: Feliratkozások lapok
Trending: Trending:
Trending: 'Népszerű' Trending: 'Népszerű'
Trending Tabs: Népszerű lapok Trending Tabs: Népszerű lapok
@ -416,6 +427,12 @@ Settings:
Channel Page: Csatornalap Channel Page: Csatornalap
Watch Page: Nézőlap Watch Page: Nézőlap
General: Általános General: Általános
Subscriptions Page: Feliratkozások oldal
Hide Channel Podcasts: Csatornapodcastok elrejtése
Hide Channel Releases: Csatornakiadások elrejtése
Hide Subscriptions Shorts: Feliratkozások rövidfilmek elrejtése
Hide Subscriptions Videos: Feliratkozási videók elrejtése
Hide Subscriptions Live: Feliratkozások élők elrejtése
The app needs to restart for changes to take effect. Restart and apply change?: Az The app needs to restart for changes to take effect. Restart and apply change?: Az
alkalmazásnak újra kell indulnia, hogy a változtatások életbe lépjenek. Indítsa alkalmazásnak újra kell indulnia, hogy a változtatások életbe lépjenek. Indítsa
újra és alkalmazza a módosítást? újra és alkalmazza a módosítást?
@ -638,11 +655,18 @@ Channel:
bejegyzések bejegyzések
Community: Közösség Community: Közösség
Shorts: Shorts:
Shorts: Rövidfilmek
This channel does not currently have any shorts: Ezen a csatornán jelenleg nincsenek This channel does not currently have any shorts: Ezen a csatornán jelenleg nincsenek
rövidfilmek rövidfilmek
This channel does not exist: Nem létezik ez a csatorna This channel does not exist: Nem létezik ez a csatorna
This channel does not allow searching: Keresés nem engedélyezett ezen a csatornán This channel does not allow searching: Keresés nem engedélyezett ezen a csatornán
Releases:
Releases: Kiadások
This channel does not currently have any releases: Ennek a csatornának jelenleg
nincsenek kiadásai
Podcasts:
Podcasts: Podcastok
This channel does not currently have any podcasts: Ez a csatorna jelenleg nem
rendelkezik podcastokkal
Video: Video:
Mark As Watched: 'Megjelölés megtekintettként' Mark As Watched: 'Megjelölés megtekintettként'
Remove From History: 'Eltávolítás az előzményekből' Remove From History: 'Eltávolítás az előzményekből'
@ -950,8 +974,10 @@ Tooltips:
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Adja meg a csatorna nevét vagy csatornaazonosítóját, hogy elrejtse Hide Channels: Adja meg a csatorna nevét vagy csatornaazonosítóját, hogy elrejtse
az összes videót, lejátszási listát és magát a csatornát, hogy ne jelenjen meg az összes videót, lejátszási listát és magát a csatornát, hogy ne jelenjen meg
a keresésben vagy a népszerűségben. A megadott csatornanévnek teljes egyezésnek a keresésben, illetve a felkapott, legnépszerűbb és legajánlottabb. A megadott
kell lennie, és megkülönbözteti a kis- és nagybetűket. csatornanévnek teljes egyezésnek kell lennie, és megkülönbözteti a kis- és nagybetűket.
Hide Subscriptions Live: Ezt a beállítást felülírja az alkalmazásszintű „{appWideSetting}”
beállítás a(z) „{settingsSection}” „{subsection}” szakaszában
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: Cserélje le a videocímeket a DeArrow által beküldött, felhasználó UseDeArrowTitles: Cserélje le a videocímeket a DeArrow által beküldött, felhasználó
által beküldött címekre. által beküldött címekre.

View File

@ -30,6 +30,11 @@ Close: 'Tutup'
Back: 'Kembali' Back: 'Kembali'
Forward: 'Maju' Forward: 'Maju'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Video'
Version {versionNumber} is now available! Click for more details: 'Versi {versionNumber} Version {versionNumber} is now available! Click for more details: 'Versi {versionNumber}
sekarang tersedia! Klik untuk detail lebih lanjut' sekarang tersedia! Klik untuk detail lebih lanjut'
Download From Site: 'Unduh dari Situs' Download From Site: 'Unduh dari Situs'

View File

@ -30,6 +30,13 @@ Close: 'Loka'
Back: 'Til baka' Back: 'Til baka'
Forward: 'Áfram' Forward: 'Áfram'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Myndskeið'
Shorts: Stuttmyndir
Live: Í beinni
Version {versionNumber} is now available! Click for more details: 'Útgáfa {versionNumber} Version {versionNumber} is now available! Click for more details: 'Útgáfa {versionNumber}
er tiltæk! Smelltu til að skoða nánar' er tiltæk! Smelltu til að skoða nánar'
Download From Site: 'Sækja af vefsvæði' Download From Site: 'Sækja af vefsvæði'
@ -92,6 +99,9 @@ Subscriptions:
Disabled Automatic Fetching: Þú hefur gert sjálfvirkt niðurhal áskrifta óvirkt. Disabled Automatic Fetching: Þú hefur gert sjálfvirkt niðurhal áskrifta óvirkt.
Endurlestu áskriftirnar og þær munu birtast hér. Endurlestu áskriftirnar og þær munu birtast hér.
Empty Channels: Rásirnar sem þú ert með í áskrift eru er ekki með nein myndskeið. Empty Channels: Rásirnar sem þú ert með í áskrift eru er ekki með nein myndskeið.
Subscriptions Tabs: Áskriftaflipar
All Subscription Tabs Hidden: Allir áskriftaflipar eru faldir. Til að sjá efni hér,
skaltu gera einhverja flipa sýnilega í "{subsection}" hlutanum í "{settingsSection}".
More: 'Meira' More: 'Meira'
Trending: Trending:
Trending: 'Í umræðunni' Trending: 'Í umræðunni'
@ -335,10 +345,16 @@ Settings:
Channel Page: Rásasíða Channel Page: Rásasíða
Watch Page: Áhorfssíða Watch Page: Áhorfssíða
General: Almennt General: Almennt
Subscriptions Page: Áskriftasíða
Hide Channel Shorts: Fela stuttmyndir rása Hide Channel Shorts: Fela stuttmyndir rása
Hide Channel Playlists: Fela spilunarlista rása Hide Channel Playlists: Fela spilunarlista rása
Hide Channel Community: Fela samfélag rása Hide Channel Community: Fela samfélag rása
Hide Featured Channels: Fela rásir í deiglunni Hide Featured Channels: Fela rásir í deiglunni
Hide Channel Podcasts: Fela hlaðvörp rása
Hide Channel Releases: Fela útgáfur rása
Hide Subscriptions Shorts: Fela stuttmyndir áskrifta
Hide Subscriptions Live: Fela bein streymi áskrifta
Hide Subscriptions Videos: Fela myndskeið áskrifta
Data Settings: Data Settings:
Data Settings: 'Stillingar gagna' Data Settings: 'Stillingar gagna'
Select Import Type: 'Veldu tegund innflutnings' Select Import Type: 'Veldu tegund innflutnings'
@ -417,6 +433,7 @@ Settings:
Prompt To Skip: Spyrja hvort eigi að sleppa Prompt To Skip: Spyrja hvort eigi að sleppa
Do Nothing: Gera ekkert Do Nothing: Gera ekkert
Category Color: Litur flokks Category Color: Litur flokks
UseDeArrowTitles: Nota DeArrow myndskeiðatitla
External Player Settings: External Player Settings:
Custom External Player Arguments: Sérsniðin viðföng fyrir utanaðkomandi spilara Custom External Player Arguments: Sérsniðin viðföng fyrir utanaðkomandi spilara
Custom External Player Executable: Sérsniðin skipun fyrir utanaðkomandi spilara Custom External Player Executable: Sérsniðin skipun fyrir utanaðkomandi spilara
@ -577,9 +594,16 @@ Channel:
Community: Samfélag Community: Samfélag
This channel currently does not have any posts: Þessi rás er ekki með neinar færslur This channel currently does not have any posts: Þessi rás er ekki með neinar færslur
Shorts: Shorts:
Shorts: Stuttmyndir
This channel does not currently have any shorts: Þessi rás er í augnablikinu ekki This channel does not currently have any shorts: Þessi rás er í augnablikinu ekki
með neinar stuttmyndir með neinar stuttmyndir
Releases:
Releases: Útgáfur
This channel does not currently have any releases: Þessi rás er ekki með neinar
útgáfur í augnablikinu
Podcasts:
Podcasts: Hlaðvörp
This channel does not currently have any podcasts: Þessi rás er ekki með nein
hlaðvörp í augnablikinu
Video: Video:
Mark As Watched: 'Merkja sem búið að horfa á' Mark As Watched: 'Merkja sem búið að horfa á'
Remove From History: 'Fjarlægja úr áhorfsferli' Remove From History: 'Fjarlægja úr áhorfsferli'
@ -873,8 +897,14 @@ Tooltips:
minnislæga skyndiminnis-diskmynd. Veldur aukinni notkun á vinnsluminni. minnislæga skyndiminnis-diskmynd. Veldur aukinni notkun á vinnsluminni.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Settu inn heiti eða auðkenni rásar til að fela öll myndskeið, spilunarlista Hide Channels: Settu inn heiti eða auðkenni rásar til að fela öll myndskeið, spilunarlista
og sjálfa rásina við leit eða vinsældaskráningu. Heiti rásarinnar sem sett er og sjálfa rásina við leit eða því sem er vinsælast, mest skoðað og mælt með.
inn þarf að vera nákvæmlega stafrétt og tekur tillit til hástafa/lágstafa. Heiti rásarinnar sem sett er inn þarf að vera nákvæmlega stafrétt og tekur tillit
til hástafa/lágstafa.
Hide Subscriptions Live: Þessa stillingu er hægt að taka yfir með "{appWideSetting}"
stillingunni fyrir allt forritið, í "{subsection}" hlutanum í "{settingsSection}"
SponsorBlock Settings:
UseDeArrowTitles: Skipta út titlum myndskeiða fyrir titla sem notendur hafa sent
inn á DeArrow.
Local API Error (Click to copy): 'Villa í staðværu API-kerfisviðmóti (smella til að Local API Error (Click to copy): 'Villa í staðværu API-kerfisviðmóti (smella til að
afrita)' afrita)'
Invidious API Error (Click to copy): 'Villa í Invidious API-kerfisviðmóti (smella Invidious API Error (Click to copy): 'Villa í Invidious API-kerfisviðmóti (smella

View File

@ -30,6 +30,13 @@ Close: 'Chiudi'
Back: 'Indietro' Back: 'Indietro'
Forward: 'Avanti' Forward: 'Avanti'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Video'
Shorts: Video brevi
Live: Dal vivo
# Search Bar # Search Bar
Search / Go to URL: 'Cerca o aggiungi URL YouTube' Search / Go to URL: 'Cerca o aggiungi URL YouTube'
# In Filter Button # In Filter Button
@ -87,6 +94,9 @@ Subscriptions:
Disabled Automatic Fetching: Hai disabilitato il recupero automatico dell'abbonamento. Disabled Automatic Fetching: Hai disabilitato il recupero automatico dell'abbonamento.
Aggiorna gli abbonamenti per vederli qui. Aggiorna gli abbonamenti per vederli qui.
Empty Channels: I canali a cui sei iscritto attualmente non hanno alcun video. Empty Channels: I canali a cui sei iscritto attualmente non hanno alcun video.
Subscriptions Tabs: Schede iscrizioni
All Subscription Tabs Hidden: Tutte le schede di iscrizione sono nascoste. Per vedere
i contenuti qui, scopri le schede nella sezione "{subsection}" in "{settingsSection}".
Trending: Trending:
Trending: 'Tendenze' Trending: 'Tendenze'
Music: Musica Music: Musica
@ -417,6 +427,12 @@ Settings:
Watch Page: Pagina di visualizzazione Watch Page: Pagina di visualizzazione
Side Bar: Barra laterale Side Bar: Barra laterale
Channel Page: Pagina del canale Channel Page: Pagina del canale
Subscriptions Page: Pagina delle iscrizioni
Hide Channel Podcasts: Nascondi i podcast del canale
Hide Channel Releases: Nascondi i rilasci del canale
Hide Subscriptions Live: Nascondi le iscrizioni dal vivo
Hide Subscriptions Videos: Nascondi i video delle iscrizioni
Hide Subscriptions Shorts: Nascondi le iscrizioni ai video brevi
The app needs to restart for changes to take effect. Restart and apply change?: L'app The app needs to restart for changes to take effect. Restart and apply change?: L'app
deve essere riavviata affinché le modifiche abbiano effetto. Riavviare e applicare deve essere riavviata affinché le modifiche abbiano effetto. Riavviare e applicare
la modifica? la modifica?
@ -491,7 +507,7 @@ Settings:
Remove Password: Rimuovi password Remove Password: Rimuovi password
About: About:
#On About page #On About page
About: 'Informazioni' About: 'Informazioni su'
#& About #& About
'This software is FOSS and released under the GNU Affero General Public License v3.0.': 'Questo 'This software is FOSS and released under the GNU Affero General Public License v3.0.': 'Questo
software è rilasciato con licenza gratuita AGPL-3.0.' software è rilasciato con licenza gratuita AGPL-3.0.'
@ -571,7 +587,7 @@ Channel:
Newest: 'Più nuovi' Newest: 'Più nuovi'
Oldest: 'Più vecchi' Oldest: 'Più vecchi'
About: About:
About: 'Informazioni' About: 'Informazioni su'
Channel Description: 'Descrizione canale' Channel Description: 'Descrizione canale'
Featured Channels: 'Canali in evidenza' Featured Channels: 'Canali in evidenza'
Tags: Tags:
@ -600,9 +616,16 @@ Channel:
This channel does not currently have any live streams: Questo canale attualmente This channel does not currently have any live streams: Questo canale attualmente
non ha alcun video dal vivo non ha alcun video dal vivo
Shorts: Shorts:
Shorts: Video brevi
This channel does not currently have any shorts: Questo canale attualmente non This channel does not currently have any shorts: Questo canale attualmente non
ha video brevi ha video brevi
Podcasts:
Podcasts: Podcast
This channel does not currently have any podcasts: Questo canale non ha attualmente
alcun podcast
Releases:
Releases: Rilasci
This channel does not currently have any releases: Questo canale non ha attualmente
alcun rilascio
Video: Video:
Mark As Watched: 'Segna come già visto' Mark As Watched: 'Segna come già visto'
Remove From History: 'Rimuovi dalla cronologia' Remove From History: 'Rimuovi dalla cronologia'
@ -629,7 +652,7 @@ Video:
'Chat is disabled or the Live Stream has ended.': 'La chat è disabilitata o la diretta 'Chat is disabled or the Live Stream has ended.': 'La chat è disabilitata o la diretta
è terminata.' è terminata.'
Live chat is enabled. Chat messages will appear here once sent.: 'La chat dal vivo Live chat is enabled. Chat messages will appear here once sent.: 'La chat dal vivo
è abilitata. I messaggi appariranno qui una volta inviati.' è abilitata. I messaggi appariranno qui una volta inviati.'
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'La 'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'La
chat dal vivo non è attualmente supportata con le API di Invidious. È necessaria chat dal vivo non è attualmente supportata con le API di Invidious. È necessaria
una connessione diretta a YouTube.' una connessione diretta a YouTube.'
@ -965,10 +988,12 @@ Tooltips:
una cache di immagini in memoria personalizzata. Comporta un aumento dell'utilizzo una cache di immagini in memoria personalizzata. Comporta un aumento dell'utilizzo
della RAM. della RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Inserisci il nome o l'ID di un canale per nascondere tutti i video, Hide Channels: Inserisci il nome o l'ID di un canale per impedire che tutti i
le playlist e il canale stesso dalla visualizzazione nelle ricerche o nelle video, le playlist e il canale stesso vengano visualizzati nelle ricerche, tendenze,
tendenze. Il nome del canale inserito deve essere una corrispondenza completa più popolari e consigliati. Il nome del canale inserito deve avere una corrispondenza
e fa distinzione tra maiuscole e minuscole. completa e fa distinzione tra maiuscole e minuscole.
Hide Subscriptions Live: Questa impostazione è sovrascritta dall'impostazione
"{appWideSetting}" a livello di app, nella sezione "{subsection}" di "{settingsSection}"
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: Sostituisci i titoli dei video con titoli inviati dagli utenti UseDeArrowTitles: Sostituisci i titoli dei video con titoli inviati dagli utenti
da DeArrow. da DeArrow.

View File

@ -27,6 +27,13 @@ Close: '閉じる'
Back: '戻る' Back: '戻る'
Forward: '進む' Forward: '進む'
# Global
# Anything shared among components / views should be put here
Global:
Videos: '動画'
Shorts: ショート動画
Live: ライブ配信
# Search Bar # Search Bar
Search / Go to URL: '検索 / URL の表示' Search / Go to URL: '検索 / URL の表示'
# In Filter Button # In Filter Button
@ -91,8 +98,7 @@ Playlists: '再生リスト'
User Playlists: User Playlists:
Your Playlists: 'あなたの再生リスト' Your Playlists: 'あなたの再生リスト'
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: 保存した動画はありません。一覧に表示させるには、ビデオの角にある保存ボタンをクリックします Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: 保存した動画はありません。一覧に表示させるには、ビデオの角にある保存ボタンをクリックします
Playlist Message: Playlist Message: このページは、完全に動作する動画リストではありません。保存またはお気に入りと設定した動画のみが表示されます。操作が完了すると、現在ここにあるすべての動画は「お気に入り」の動画リストに移動します。
このページは、完全に動作する動画リストではありません。保存またはお気に入りと設定した動画のみが表示されます。操作が完了すると、現在ここにあるすべての動画は「お気に入り」の動画リストに移動します。
Search bar placeholder: 動画リスト内の検索 Search bar placeholder: 動画リスト内の検索
Empty Search Message: この再生リストに、検索に一致する動画はありません Empty Search Message: この再生リストに、検索に一致する動画はありません
History: History:
@ -539,7 +545,6 @@ Channel:
This channel does not currently have any live streams: このチャンネルは現在、ライブ配信を行っていません This channel does not currently have any live streams: このチャンネルは現在、ライブ配信を行っていません
Live: ライブ配信 Live: ライブ配信
Shorts: Shorts:
Shorts: ショート動画
This channel does not currently have any shorts: このチャンネルには現在ショート動画がありません This channel does not currently have any shorts: このチャンネルには現在ショート動画がありません
Video: Video:
Open in YouTube: 'YouTube で表示' Open in YouTube: 'YouTube で表示'
@ -849,7 +854,7 @@ Tooltips:
Replace HTTP Cache: Electron のディスクに基づく HTTP キャッシュを無効化し、メモリ内で独自の画像キャッシュを使用します。このことにより Replace HTTP Cache: Electron のディスクに基づく HTTP キャッシュを無効化し、メモリ内で独自の画像キャッシュを使用します。このことにより
RAM の使用率は増加します。 RAM の使用率は増加します。
Distraction Free Settings: Distraction Free Settings:
Hide Channels: チャンネル名またはチャンネル ID Hide Channels: チャンネル名またはチャンネル ID
を入力すると、すべてのビデオ、再生リスト、およびチャンネル自体が検索や人気に表示されなくなります。入力するチャンネル名は完全に一致することが必要で、大文字と小文字を区別します。 を入力すると、すべてのビデオ、再生リスト、およびチャンネル自体が検索や人気に表示されなくなります。入力するチャンネル名は完全に一致することが必要で、大文字と小文字を区別します。
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: 動画のタイトルを DeArrow からユーザーが投稿したタイトルに置き換えます。 UseDeArrowTitles: 動画のタイトルを DeArrow からユーザーが投稿したタイトルに置き換えます。

View File

@ -29,6 +29,11 @@ Close: '닫기'
Back: '뒤로가기' Back: '뒤로가기'
Forward: '앞으로가기' Forward: '앞으로가기'
# Global
# Anything shared among components / views should be put here
Global:
Videos: '비디오'
Version {versionNumber} is now available! Click for more details: '{versionNumber} Version {versionNumber} is now available! Click for more details: '{versionNumber}
버전이 사용가능합니다! 클릭하여 자세한 정보를 확인하세요' 버전이 사용가능합니다! 클릭하여 자세한 정보를 확인하세요'
Download From Site: '사이트로부터 다운로드' Download From Site: '사이트로부터 다운로드'

View File

@ -30,6 +30,11 @@ Back: 'Atgal'
Forward: 'Pirmyn' Forward: 'Pirmyn'
Open New Window: 'Atidaryti naują langą' Open New Window: 'Atidaryti naują langą'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vaizdo įrašai'
Version {versionNumber} is now available! Click for more details: 'Versija {versionNumber} Version {versionNumber} is now available! Click for more details: 'Versija {versionNumber}
jau prieinama! Spustelėkite, jei norite gauti daugiau informacijos' jau prieinama! Spustelėkite, jei norite gauti daugiau informacijos'
Download From Site: 'Atsisiųsti iš svetainės' Download From Site: 'Atsisiųsti iš svetainės'

View File

@ -29,6 +29,13 @@ Close: 'Lukk'
Back: 'Tilbake' Back: 'Tilbake'
Forward: 'Framover' Forward: 'Framover'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videoer'
Shorts: Kortvideoer
Live: Direkte
# Search Bar # Search Bar
Search / Go to URL: 'Søk/gå til nettadresse' Search / Go to URL: 'Søk/gå til nettadresse'
# In Filter Button # In Filter Button
@ -545,7 +552,6 @@ Channel:
This channel currently does not have any posts: Denne kanalen har ingen oppføringer This channel currently does not have any posts: Denne kanalen har ingen oppføringer
Shorts: Shorts:
This channel does not currently have any shorts: Denne kanalen har ingen kortvideoer This channel does not currently have any shorts: Denne kanalen har ingen kortvideoer
Shorts: Kortvideoer
Live: Live:
Live: Direkte Live: Direkte
This channel does not currently have any live streams: Denne kanalen har ikke This channel does not currently have any live streams: Denne kanalen har ikke

View File

@ -30,6 +30,11 @@ Close: 'Sluiten'
Back: 'Ga terug' Back: 'Ga terug'
Forward: 'Ga vooruit' Forward: 'Ga vooruit'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Video''s'
# Search Bar # Search Bar
Search / Go to URL: 'Zoeken / Ga naar URL' Search / Go to URL: 'Zoeken / Ga naar URL'
# In Filter Button # In Filter Button

View File

@ -30,6 +30,11 @@ Close: 'Lukk'
Back: 'Tilbake' Back: 'Tilbake'
Forward: 'Framover' Forward: 'Framover'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videoar'
Version {versionNumber} is now available! Click for more details: 'Versjon {versionNumber} Version {versionNumber} is now available! Click for more details: 'Versjon {versionNumber}
er no tilgjengeleg! Klikk for meir informasjon' er no tilgjengeleg! Klikk for meir informasjon'
Download From Site: 'Last ned frå nettstaden' Download From Site: 'Last ned frå nettstaden'

View File

@ -28,6 +28,13 @@ Close: 'Zamknij'
Back: 'Wstecz' Back: 'Wstecz'
Forward: 'Naprzód' Forward: 'Naprzód'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Filmy'
Shorts: Filmy Short
Live: Transmisje
# Search Bar # Search Bar
Search / Go to URL: 'Szukaj / Przejdź do adresu URL' Search / Go to URL: 'Szukaj / Przejdź do adresu URL'
# In Filter Button # In Filter Button
@ -84,6 +91,9 @@ Subscriptions:
Disabled Automatic Fetching: Wyłączyłeś automatyczne pobieranie subskrypcji. Odśwież Disabled Automatic Fetching: Wyłączyłeś automatyczne pobieranie subskrypcji. Odśwież
subskrypcje, by je zobaczyć. subskrypcje, by je zobaczyć.
Empty Channels: Twoje subskrypcje nie mają obecnie żadnych filmów. Empty Channels: Twoje subskrypcje nie mają obecnie żadnych filmów.
All Subscription Tabs Hidden: Wszystkie karty subskrypcji są pochowane. Aby je zobaczyć,
proszę odznaczyć ich ukrycie w podgrupie „{subsection}” grupy „{settingsSection}”.
Subscriptions Tabs: Karty subskrypcji
Trending: Trending:
Trending: 'Na czasie' Trending: 'Na czasie'
Trending Tabs: Karty „Na czasie” Trending Tabs: Karty „Na czasie”
@ -415,6 +425,12 @@ Settings:
Channel Page: Strona kanału Channel Page: Strona kanału
General: Ogólne General: Ogólne
Watch Page: Strona odtwarzacza Watch Page: Strona odtwarzacza
Subscriptions Page: Strona subskrypcji
Hide Channel Releases: Schowaj wydawnictwa kanału
Hide Channel Podcasts: Schowaj podkasty kanału
Hide Subscriptions Videos: Schowaj filmy z subskrypcji
Hide Subscriptions Shorts: Schowaj filmy Short z subskrypcji
Hide Subscriptions Live: Schowaj transmisje live z subskrypcji
The app needs to restart for changes to take effect. Restart and apply change?: Aplikacja The app needs to restart for changes to take effect. Restart and apply change?: Aplikacja
musi zostać ponownie uruchomiona, aby zmiany zostały wprowadzone. Uruchomić ponownie musi zostać ponownie uruchomiona, aby zmiany zostały wprowadzone. Uruchomić ponownie
i zastosować zmiany? i zastosować zmiany?
@ -448,6 +464,7 @@ Settings:
Prompt To Skip: Zapytaj, czy pominąć Prompt To Skip: Zapytaj, czy pominąć
Do Nothing: Nic nie rób Do Nothing: Nic nie rób
Category Color: Kolor segmentu Category Color: Kolor segmentu
UseDeArrowTitles: Użyj tytułów filmów z DeArrow
External Player Settings: External Player Settings:
Custom External Player Arguments: Niestandardowe argumenty zewnętrznego odtwarzacza Custom External Player Arguments: Niestandardowe argumenty zewnętrznego odtwarzacza
Custom External Player Executable: Niestandardowy plik wykonywalny zewnętrznego Custom External Player Executable: Niestandardowy plik wykonywalny zewnętrznego
@ -603,9 +620,15 @@ Channel:
This channel does not currently have any live streams: Ten kanał nie ma obecnie This channel does not currently have any live streams: Ten kanał nie ma obecnie
żadnych transmisji żadnych transmisji
Shorts: Shorts:
Shorts: Filmy Short
This channel does not currently have any shorts: Ten kanał nie ma obecnie żadnych This channel does not currently have any shorts: Ten kanał nie ma obecnie żadnych
filmów Short filmów Short
Releases:
Releases: Wydania
This channel does not currently have any releases: Ten kanał nie ma obecnie żadnych
wydawnictw
Podcasts:
Podcasts: Podkasty
This channel does not currently have any podcasts: Ten kanał nie ma żadnych podkastów
Video: Video:
Mark As Watched: 'Oznacz jako obejrzany' Mark As Watched: 'Oznacz jako obejrzany'
Remove From History: 'Usuń z historii' Remove From History: 'Usuń z historii'
@ -676,7 +699,7 @@ Video:
Reverse Playlist: Odwróć playlistę Reverse Playlist: Odwróć playlistę
Shuffle Playlist: Losuj z playlisty Shuffle Playlist: Losuj z playlisty
Loop Playlist: Zapętl playlistę Loop Playlist: Zapętl playlistę
Starting soon, please refresh the page to check again: Wkrótce się zacznie, proszę Starting soon, please refresh the page to check again: Wkrótce się zacznie. Proszę
odświeżyć stronę, aby ponownie sprawdzić odświeżyć stronę, aby ponownie sprawdzić
Audio: Audio:
Best: Najlepsza Best: Najlepsza
@ -965,8 +988,14 @@ Tooltips:
to większe użycie pamięci RAM. to większe użycie pamięci RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Wprowadź nazwę albo ID kanału, aby schować wszystkie filmy i playlisty Hide Channels: Wprowadź nazwę albo ID kanału, aby schować wszystkie filmy i playlisty
tego kanału, oraz sam kanał z wyszukiwań, oraz z zakładki „Na czasie”. Nazwa tego kanału, oraz sam kanał z wyszukiwań, z zakładek „Na czasie” i „Popularne”
kanału musi być dokładnym dopasowaniem, z uwzględnieniem wielkości liter. oraz z polecanych. Nazwa kanału musi być dokładnym dopasowaniem, z uwzględnieniem
wielkości liter.
Hide Subscriptions Live: Ta opcja została nadpisana opcją ogólną „{appWideSetting}”
z podgrupy „{subsection}” grupy „{settingsSection}”
SponsorBlock Settings:
UseDeArrowTitles: Zastąp tytuły filmów tytułami zasugerowanymi przez użytkowników
DeArrow.
Playing Next Video Interval: Odtwarzanie kolejnego filmu już za chwilę. Wciśnij aby Playing Next Video Interval: Odtwarzanie kolejnego filmu już za chwilę. Wciśnij aby
przerwać. | Odtwarzanie kolejnego filmu za {nextVideoInterval} sekundę. Wciśnij przerwać. | Odtwarzanie kolejnego filmu za {nextVideoInterval} sekundę. Wciśnij
aby przerwać. | Odtwarzanie kolejnego filmu za {nextVideoInterval} sekund. Wciśnij aby przerwać. | Odtwarzanie kolejnego filmu za {nextVideoInterval} sekund. Wciśnij

View File

@ -28,6 +28,13 @@ Close: 'Fechar'
Back: 'Voltar' Back: 'Voltar'
Forward: 'Avançar' Forward: 'Avançar'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vídeos'
Shorts: Shorts
Live: Ao vivo
# Search Bar # Search Bar
Search / Go to URL: 'Buscar/Ir ao URL' Search / Go to URL: 'Buscar/Ir ao URL'
# In Filter Button # In Filter Button
@ -447,6 +454,7 @@ Settings:
Auto Skip: Pular automaticamente Auto Skip: Pular automaticamente
Skip Option: Opção de pular Skip Option: Opção de pular
Category Color: Cor da categoria Category Color: Cor da categoria
UseDeArrowTitles: Utilizar títulos de vídeo DeArrow
External Player Settings: External Player Settings:
Custom External Player Arguments: Argumentos de player externo personalizados Custom External Player Arguments: Argumentos de player externo personalizados
External Player: Player externo External Player: Player externo
@ -592,7 +600,6 @@ Channel:
This channel does not currently have any live streams: Este canal não tem nenhuma This channel does not currently have any live streams: Este canal não tem nenhuma
transmissão ao vivo no momento transmissão ao vivo no momento
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: Este canal não tem atualmente This channel does not currently have any shorts: Este canal não tem atualmente
nenhum short nenhum short
Video: Video:
@ -958,9 +965,13 @@ Tooltips:
Replace HTTP Cache: Desabilita o cache HTTP baseado em disco do Electron e habilita Replace HTTP Cache: Desabilita o cache HTTP baseado em disco do Electron e habilita
um cache de imagem em memória personalizado. Levará ao aumento do uso de RAM. um cache de imagem em memória personalizado. Levará ao aumento do uso de RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Digite um nome ou ID de canal para ocultar todos os vídeos, listas Hide Channels: Digite um nome ou ID de canal para ocultar todos os vídeos, as
de reprodução e o próprio canal dos resultados da busca ou nas tendências. O listas de reprodução e o próprio canal dos resultados da busca, tendências,
nome do canal digitado deve coincidir exatamente, observando maiúsculas e minúsculas. mais populares e recomendados. O nome do canal digitado deve coincidir exatamente,
observando maiúsculas e minúsculas.
SponsorBlock Settings:
UseDeArrowTitles: Substituir títulos de vídeo por títulos enviados pelo usuário
a partir do DeArrow.
More: Mais More: Mais
Playing Next Video Interval: Reproduzindo o próximo vídeo imediatamente. Clique para Playing Next Video Interval: Reproduzindo o próximo vídeo imediatamente. Clique para
cancelar. | Reproduzindo o próximo vídeo em {nextVideoInterval} segundo(s). Clique cancelar. | Reproduzindo o próximo vídeo em {nextVideoInterval} segundo(s). Clique

View File

@ -30,6 +30,13 @@ Close: Fechar
Back: Recuar Back: Recuar
Forward: Avançar Forward: Avançar
# Global
# Anything shared among components / views should be put here
Global:
Videos: Vídeos
Shorts: Curtas
Live: Em directo
Version {versionNumber} is now available! Click for more details: A versão {versionNumber} Version {versionNumber} is now available! Click for more details: A versão {versionNumber}
já está disponível! Clique para mais detalhes já está disponível! Clique para mais detalhes
Download From Site: Descarregar do site Download From Site: Descarregar do site
@ -341,6 +348,8 @@ Settings:
Channel Page: Página do canal Channel Page: Página do canal
Watch Page: Ver página Watch Page: Ver página
General: Geral General: Geral
Hide Channel Releases: Ocultar as libertações do canal
Hide Channel Podcasts: Ocultar podcasts do canal
Data Settings: Data Settings:
Data Settings: Definições de dados Data Settings: Definições de dados
Select Import Type: Escolher tipo de importação Select Import Type: Escolher tipo de importação
@ -420,6 +429,7 @@ Settings:
Prompt To Skip: Perguntar se quero ignorar Prompt To Skip: Perguntar se quero ignorar
Do Nothing: Nada fazer Do Nothing: Nada fazer
Category Color: Cor da categoria Category Color: Cor da categoria
UseDeArrowTitles: Utilizar títulos de vídeo DeArrow
External Player Settings: External Player Settings:
Custom External Player Arguments: Argumentos do reprodutor externo personalizado Custom External Player Arguments: Argumentos do reprodutor externo personalizado
Custom External Player Executable: Executável de reprodutor externo personalizado Custom External Player Executable: Executável de reprodutor externo personalizado
@ -584,9 +594,16 @@ Channel:
This channel does not currently have any live streams: Este canal não tem atualmente This channel does not currently have any live streams: Este canal não tem atualmente
nenhuma transmissão ao vivo nenhuma transmissão ao vivo
Shorts: Shorts:
Shorts: Curtas
This channel does not currently have any shorts: Este canal não tem atualmente This channel does not currently have any shorts: Este canal não tem atualmente
nenhum canal curto nenhum canal curto
Releases:
Releases: Lançamentos
This channel does not currently have any releases: Este canal não tem atualmente
nenhum lançamento
Podcasts:
This channel does not currently have any podcasts: Este canal não tem atualmente
podcasts
Podcasts: Podcasts
Video: Video:
Mark As Watched: Marcar como visto Mark As Watched: Marcar como visto
Remove From History: Remover do histórico Remove From History: Remover do histórico
@ -869,10 +886,13 @@ Tooltips:
Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem
na memória personalizada. Levará ao aumento da utilização de RAM. na memória personalizada. Levará ao aumento da utilização de RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Introduza o nome de um canal ou ID do canal para esconder todos Hide Channels: Introduza um nome de canal ou um ID de canal para ocultar todos
os vídeos, listas de reprodução e o próprio canal de aparecer em busca ou em os vídeos, listas de reprodução e o próprio canal de aparecerem na pesquisa,
tendências. O nome do canal introduzido tem de ser uma correspondência completa tendências, mais populares e recomendados. O nome do canal introduzido tem de
e é sensível a maiúsculas e minúsculas. corresponder na totalidade e é sensível a maiúsculas e minúsculas.
SponsorBlock Settings:
UseDeArrowTitles: Substituir títulos de vídeo por títulos enviados pelo utilizador
a partir do DeArrow.
Local API Error (Click to copy): API local encontrou um erro (clique para copiar) Local API Error (Click to copy): API local encontrou um erro (clique para copiar)
Invidious API Error (Click to copy): API Invidious encontrou um erro (clique para Invidious API Error (Click to copy): API Invidious encontrou um erro (clique para
copiar) copiar)

View File

@ -29,6 +29,13 @@ Close: 'Fechar'
Back: 'Recuar' Back: 'Recuar'
Forward: 'Avançar' Forward: 'Avançar'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Vídeos'
Shorts: Curtas
Live: Em directo
Version {versionNumber} is now available! Click for more details: 'A versão {versionNumber} Version {versionNumber} is now available! Click for more details: 'A versão {versionNumber}
está disponível! Clique aqui para mais informações' está disponível! Clique aqui para mais informações'
Download From Site: 'Descarregar do site' Download From Site: 'Descarregar do site'
@ -89,6 +96,10 @@ Subscriptions:
Empty Channels: Os canais subscritos não têm, atualmente, quaisquer vídeos. Empty Channels: Os canais subscritos não têm, atualmente, quaisquer vídeos.
Disabled Automatic Fetching: Desativou a atualização automática de subscrições. Disabled Automatic Fetching: Desativou a atualização automática de subscrições.
Atualize as subscrições para as ver aqui. Atualize as subscrições para as ver aqui.
Subscriptions Tabs: Separadores de subscrições
All Subscription Tabs Hidden: Todos os separadores de subscrição estão ocultos.
Para ver o conteúdo aqui, desoculte alguns separadores na secção "{subsection}"
em "{settingsSection}".
Trending: Trending:
Trending: 'Tendências' Trending: 'Tendências'
Trending Tabs: Separador de tendências Trending Tabs: Separador de tendências
@ -391,6 +402,7 @@ Settings:
Prompt To Skip: Perguntar se quero ignorar Prompt To Skip: Perguntar se quero ignorar
Do Nothing: Nada fazer Do Nothing: Nada fazer
Category Color: Cor da categoria Category Color: Cor da categoria
UseDeArrowTitles: Utilizar títulos de vídeo DeArrow
Proxy Settings: Proxy Settings:
Error getting network information. Is your proxy configured properly?: Erro ao Error getting network information. Is your proxy configured properly?: Erro ao
obter informações da rede. O seu proxy está configurado corretamente? obter informações da rede. O seu proxy está configurado corretamente?
@ -438,6 +450,12 @@ Settings:
Channel Page: Página do canal Channel Page: Página do canal
Watch Page: Ver página Watch Page: Ver página
General: Geral General: Geral
Subscriptions Page: Página de subscrições
Hide Channel Podcasts: Ocultar podcasts do canal
Hide Channel Releases: Ocultar as libertações do canal
Hide Subscriptions Videos: Ocultar subscrições de vídeos
Hide Subscriptions Shorts: Ocultar subscrições de vídeos curtos
Hide Subscriptions Live: Ocultar subscrições de vídeos em direto
External Player Settings: External Player Settings:
Custom External Player Arguments: Argumentos do reprodutor externo personalizado Custom External Player Arguments: Argumentos do reprodutor externo personalizado
Custom External Player Executable: Executável de reprodutor externo personalizado Custom External Player Executable: Executável de reprodutor externo personalizado
@ -631,9 +649,16 @@ Channel:
This channel does not currently have any live streams: Este canal não tem atualmente This channel does not currently have any live streams: Este canal não tem atualmente
nenhuma transmissão ao vivo nenhuma transmissão ao vivo
Shorts: Shorts:
Shorts: Curtas
This channel does not currently have any shorts: Este canal não tem atualmente This channel does not currently have any shorts: Este canal não tem atualmente
nenhum canal curto nenhum canal curto
Releases:
Releases: Lançamentos
This channel does not currently have any releases: Este canal não tem atualmente
nenhum lançamento
Podcasts:
Podcasts: Podcasts
This channel does not currently have any podcasts: Este canal não tem atualmente
podcasts
Video: Video:
Mark As Watched: 'Marcar como visto' Mark As Watched: 'Marcar como visto'
Remove From History: 'Remover do histórico' Remove From History: 'Remover do histórico'
@ -958,10 +983,15 @@ Tooltips:
Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem
na memória personalizada. Levará ao aumento da utilização de RAM. na memória personalizada. Levará ao aumento da utilização de RAM.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Digite um nome ou ID do canal para ocultar todos os vídeos, listas Hide Channels: Introduza um nome de canal ou um ID de canal para ocultar todos
de reprodução e o próprio canal, de forma a não aparecer em pesquisas ou tendências. os vídeos, listas de reprodução e o próprio canal de aparecerem na pesquisa,
O nome do canal inserido deve ser exatamente igual ao nome do canal e é sensível tendências, mais populares e recomendados. O nome do canal introduzido tem de
a maiúsculas e minúsculas. corresponder na totalidade e é sensível a maiúsculas e minúsculas.
Hide Subscriptions Live: Esta definição é substituída pela definição de toda a
aplicação "{appWideSetting}", na secção "{subsection}" da "{settingsSection}"
SponsorBlock Settings:
UseDeArrowTitles: Substituir títulos de vídeo por títulos enviados pelo utilizador
a partir do DeArrow.
Search Bar: Search Bar:
Clear Input: Limpar entrada Clear Input: Limpar entrada
Are you sure you want to open this link?: Tem a certeza de que deseja abrir a ligação? Are you sure you want to open this link?: Tem a certeza de que deseja abrir a ligação?

View File

@ -29,6 +29,13 @@ Close: 'Închideți'
Back: 'Înapoi' Back: 'Înapoi'
Forward: 'Înainte' Forward: 'Înainte'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videoclipuri'
Shorts: Shorts
Live: Live
Version {versionNumber} is now available! Click for more details: 'Versiunea {versionNumber} Version {versionNumber} is now available! Click for more details: 'Versiunea {versionNumber}
este acum disponibilă! Click pentru mai multe detalii' este acum disponibilă! Click pentru mai multe detalii'
Download From Site: 'Descărcați de pe site' Download From Site: 'Descărcați de pe site'
@ -60,12 +67,14 @@ Search Filters:
Videos: 'Videoclipuri' Videos: 'Videoclipuri'
Channels: 'Canale' Channels: 'Canale'
#& Playlists #& Playlists
Movies: Filme
Duration: Duration:
Duration: 'Durata' Duration: 'Durata'
All Durations: 'Toate duratele' All Durations: 'Toate duratele'
Short (< 4 minutes): 'Scurt (< 4 minute)' Short (< 4 minutes): 'Scurt (< 4 minute)'
Long (> 20 minutes): 'Lung (> 20 minute)' Long (> 20 minutes): 'Lung (> 20 minute)'
# On Search Page # On Search Page
Medium (4 - 20 minutes): Mediu (4 - 20 minute)
Search Results: 'Rezultatele căutării' Search Results: 'Rezultatele căutării'
Fetching results. Please wait: 'Se obțin rezultatele. Vă rugăm să așteptați' Fetching results. Please wait: 'Se obțin rezultatele. Vă rugăm să așteptați'
Fetch more results: 'Obțineți mai multe rezultate' Fetch more results: 'Obțineți mai multe rezultate'
@ -89,6 +98,7 @@ Subscriptions:
Empty Channels: Canalele la care sunteți abonat(ă) nu au în prezent clipuri video. Empty Channels: Canalele la care sunteți abonat(ă) nu au în prezent clipuri video.
Disabled Automatic Fetching: Ai dezactivat obținerea automată de abonamente. Reîmprospătează Disabled Automatic Fetching: Ai dezactivat obținerea automată de abonamente. Reîmprospătează
abonamentele pentru a le vedea aici. abonamentele pentru a le vedea aici.
Subscriptions Tabs: Filele Abonamente
Trending: Trending:
Trending: 'Tendințe' Trending: 'Tendințe'
Trending Tabs: File în tendințe Trending Tabs: File în tendințe
@ -216,6 +226,7 @@ Settings:
Disable Smooth Scrolling: Dezactivați derularea lină Disable Smooth Scrolling: Dezactivați derularea lină
Expand Side Bar by Default: Extindeți bara laterală în mod implicit Expand Side Bar by Default: Extindeți bara laterală în mod implicit
Hide Side Bar Labels: Ascunde etichetele din bara laterală Hide Side Bar Labels: Ascunde etichetele din bara laterală
Hide FreeTube Header Logo: Ascundeți logo-ul FreeTube Header
Player Settings: Player Settings:
Player Settings: 'Setări player' Player Settings: 'Setări player'
Force Local Backend for Legacy Formats: 'Forțați backend-ul local pentru formatele Force Local Backend for Legacy Formats: 'Forțați backend-ul local pentru formatele
@ -269,6 +280,11 @@ Settings:
Max Video Playback Rate: Viteza maximă de redare Max Video Playback Rate: Viteza maximă de redare
Scroll Playback Rate Over Video Player: Schimbă viteza de redare cu ajutorul rotiței Scroll Playback Rate Over Video Player: Schimbă viteza de redare cu ajutorul rotiței
de scroll de scroll
Enter Fullscreen on Display Rotate: Intrați pe ecran complet pe afișaj Rotire
Comment Auto Load:
Comment Auto Load: Comentariu Încărcare automată
Skip by Scrolling Over Video Player: Omiteți derulând peste player-ul video
Allow DASH AV1 formats: Permiteți formatele DASH AV1
Privacy Settings: Privacy Settings:
Privacy Settings: 'Setări de confidențialitate' Privacy Settings: 'Setări de confidențialitate'
Remember History: 'Reține istoric' Remember History: 'Reține istoric'
@ -410,6 +426,17 @@ Settings:
Parental Control Settings: Setările controlului parental Parental Control Settings: Setările controlului parental
Show Family Friendly Only: Arata numai conținut family friendly Show Family Friendly Only: Arata numai conținut family friendly
Hide Search Bar: Ascunde bara de căutare Hide Search Bar: Ascunde bara de căutare
Password Dialog:
Password Incorrect: Parolă incorectă
Unlock: Deblocați
Password: Parolă
Enter Password To Unlock: Introduceți parola pentru a debloca setările
Password Settings:
Remove Password: Eliminați parola
Password Settings: Setări parolă
Set Password To Prevent Access: Setați o parolă pentru a împiedica accesul la
setări
Set Password: Setați parola
About: About:
#On About page #On About page
About: 'Despre' About: 'Despre'
@ -518,6 +545,16 @@ Channel:
About: 'Despre' About: 'Despre'
Channel Description: 'Descrierea canalului' Channel Description: 'Descrierea canalului'
Featured Channels: 'Canale recomandate' Featured Channels: 'Canale recomandate'
Joined: S-a alăturat pe
Location: Locație
Community:
Community: Comunitate
This channel currently does not have any posts: Acest canal nu are momentan nicio
postare
This channel is age-restricted and currently cannot be viewed in FreeTube.: Acest
canal are restricții de vârstă și momentan nu poate fi vizionat în FreeTube.
This channel does not exist: Acest canal nu există
This channel does not allow searching: Acest canal nu permite căutarea
Video: Video:
Mark As Watched: 'Marcați ca vizionat' Mark As Watched: 'Marcați ca vizionat'
Remove From History: 'Eliminați din istoric' Remove From History: 'Eliminați din istoric'
@ -586,6 +623,7 @@ Video:
Ago: 'În urmă' Ago: 'În urmă'
Upcoming: 'În premieră la' Upcoming: 'În premieră la'
Less than a minute: Mai putin de un minut Less than a minute: Mai putin de un minut
In less than a minute: În mai puțin de un minut
Published on: 'Publicat pe' Published on: 'Publicat pe'
Publicationtemplate: 'acum {number} {unit}' Publicationtemplate: 'acum {number} {unit}'
#& Videos #& Videos
@ -655,6 +693,7 @@ Video:
Mimetype: Mimetype Mimetype: Mimetype
Premieres on: Premieră pe Premieres on: Premieră pe
Premieres in: Are premiera în Premieres in: Are premiera în
Scroll to Bottom: Derulați până jos
Videos: Videos:
#& Sort By #& Sort By
Sort By: Sort By:
@ -701,6 +740,7 @@ Share:
YouTube Channel URL copied to clipboard: URL-ul canalului YouTube copiat în clipboard YouTube Channel URL copied to clipboard: URL-ul canalului YouTube copiat în clipboard
Invidious Channel URL copied to clipboard: URL-ul Invidious al canalului a fost Invidious Channel URL copied to clipboard: URL-ul Invidious al canalului a fost
copiat în clipboard copiat în clipboard
Share Channel: Distribuie canalul
Mini Player: 'Mini Player' Mini Player: 'Mini Player'
Comments: Comments:
Comments: 'Comentarii' Comments: 'Comentarii'
@ -810,9 +850,9 @@ Tooltips:
a reveni rapid la rata de redare implicită (1x, cu excepția cazului în care a reveni rapid la rata de redare implicită (1x, cu excepția cazului în care
aceasta a fost modificată în setări). aceasta a fost modificată în setări).
General Settings: General Settings:
External Link Handling: "Alegeți comportamentul implicit atunci când se face clic\ External Link Handling: "Alegeți comportamentul implicit atunci când se face clic
\ pe un link care nu poate fi deschis în FreeTube.\nÎn mod implicit, FreeTube\ pe un link care nu poate fi deschis în FreeTube.\nÎn mod implicit, FreeTube
\ va deschide link-ul pe care s-a făcut clic în browserul dvs. implicit.\n" va deschide link-ul pe care s-a făcut clic în browserul dvs. implicit.\n"
Region for Trending: Regiunea de tendințe vă permite să alegeți ce videoclipuri Region for Trending: Regiunea de tendințe vă permite să alegeți ce videoclipuri
în tendințe din fiecare țară doriți să fie afișate. în tendințe din fiecare țară doriți să fie afișate.
Invidious Instance: Instanța Invidious la care FreeTube se va conecta pentru apelurile Invidious Instance: Instanța Invidious la care FreeTube se va conecta pentru apelurile
@ -848,3 +888,14 @@ Channels:
Unsubscribe Prompt: Ești sigur că dorești să te dezabonezi de la "{channelName}"? Unsubscribe Prompt: Ești sigur că dorești să te dezabonezi de la "{channelName}"?
Screenshot Success: Capturi de ecran salvate ca "{filePath}" Screenshot Success: Capturi de ecran salvate ca "{filePath}"
Screenshot Error: Captura de ecran a eșuat Screenshot Error: Captura de ecran a eșuat
Preferences: Preferințe
Chapters:
Chapters: Capitole
'Chapters list visible, current chapter: {chapterName}': 'Lista de capitole vizibilă,
capitolul curent: {chapterName}'
'Chapters list hidden, current chapter: {chapterName}': 'Lista de capitole ascunsă,
capitolul curent: {chapterName}'
Clipboard:
Cannot access clipboard without a secure connection: Nu se poate accesa clipboard-ul
fără o conexiune securizată
Copy failed: Copierea în clipboard a eșuat

View File

@ -27,6 +27,13 @@ Close: 'Закрыть'
Back: 'Назад' Back: 'Назад'
Forward: 'Вперёд' Forward: 'Вперёд'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Видео'
Shorts: Короткие видео
Live: Трансляции
# Search Bar # Search Bar
Search / Go to URL: 'Поиск / Перейти по адресу' Search / Go to URL: 'Поиск / Перейти по адресу'
# In Filter Button # In Filter Button
@ -83,6 +90,9 @@ Subscriptions:
Disabled Automatic Fetching: Вы отключили автоматическое получение подписок. Обновите Disabled Automatic Fetching: Вы отключили автоматическое получение подписок. Обновите
подписки, чтобы отобразить результат. подписки, чтобы отобразить результат.
Empty Channels: Ваши подписанные каналы в настоящее время не содержат видео. Empty Channels: Ваши подписанные каналы в настоящее время не содержат видео.
Subscriptions Tabs: Вкладки подписок
All Subscription Tabs Hidden: Все вкладки подписок скрыты. Чтобы увидеть содержимое
здесь, пожалуйста, раскройте некоторые вкладки в разделе «{subsection}» в «{settingsSection}».
Trending: Trending:
Trending: 'Тренды' Trending: 'Тренды'
Trending Tabs: Тренды Trending Tabs: Тренды
@ -409,6 +419,12 @@ Settings:
Channel Page: Страница канала Channel Page: Страница канала
Watch Page: Страница просмотра Watch Page: Страница просмотра
General: Основные General: Основные
Subscriptions Page: Страница подписок
Hide Channel Releases: Скрыть выпуски канала
Hide Subscriptions Videos: Скрыть видео из подписок
Hide Subscriptions Live: Скрыть трансляции из подписок
Hide Channel Podcasts: Скрыть звукопередачи канала
Hide Subscriptions Shorts: Скрыть короткие видео из подписок
The app needs to restart for changes to take effect. Restart and apply change?: Чтобы The app needs to restart for changes to take effect. Restart and apply change?: Чтобы
изменения вступили в силу, необходимо перезапустить приложение. Перезапустить изменения вступили в силу, необходимо перезапустить приложение. Перезапустить
и применить изменения? и применить изменения?
@ -441,6 +457,7 @@ Settings:
Show In Seek Bar: Показать сегмент Show In Seek Bar: Показать сегмент
Do Nothing: Ничего не делать Do Nothing: Ничего не делать
Category Color: Цвет категории Category Color: Цвет категории
UseDeArrowTitles: Использовать заголовки видео «DeArrow»
External Player Settings: External Player Settings:
Custom External Player Arguments: Аргументы внешнего проигрывателя Custom External Player Arguments: Аргументы внешнего проигрывателя
Custom External Player Executable: Исполняемый файл внешнего проигрывателя Custom External Player Executable: Исполняемый файл внешнего проигрывателя
@ -588,9 +605,16 @@ Channel:
This channel does not currently have any live streams: На этом канале в настоящее This channel does not currently have any live streams: На этом канале в настоящее
время нет прямых трансляций время нет прямых трансляций
Shorts: Shorts:
Shorts: Короткие видео
This channel does not currently have any shorts: На этом канале пока что нет коротких This channel does not currently have any shorts: На этом канале пока что нет коротких
видео видео
Podcasts:
Podcasts: Звукопередачи
This channel does not currently have any podcasts: На этом канале пока нет ни
одной звукопередачи
Releases:
Releases: Выпуски
This channel does not currently have any releases: На этом канале пока нет ни
одного выпуска
Video: Video:
Mark As Watched: 'Отметить как просмотренное' Mark As Watched: 'Отметить как просмотренное'
Remove From History: 'Удалить из истории' Remove From History: 'Удалить из истории'
@ -954,8 +978,14 @@ Tooltips:
кэш изображений в памяти. Приведёт к увеличению использования оперативной памяти. кэш изображений в памяти. Приведёт к увеличению использования оперативной памяти.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Введите название канала или его идентификатор, чтобы скрыть все Hide Channels: Введите название канала или его идентификатор, чтобы скрыть все
видео, подборки и сам канал от показа в поиске или трендах. Введённое название видео, подборки и сам канал от показа в поиске, трендах, наиболее просматриваемых
канала должно полностью совпадать и учитывать регистр. и желательных. Введённое название канала должно полностью совпадать и учитывать
регистр.
Hide Subscriptions Live: Эта настройка переопределена общей настройкой «{appWideSetting}»,
в подразделе «{subsection}» раздела «{settingsSection}»
SponsorBlock Settings:
UseDeArrowTitles: Заменить пользовательски-размещённые заголовки на заголовки,
предоставляемые «DeArrow».
More: Больше More: Больше
Playing Next Video Interval: Воспроизведение следующего видео без задержки. Нажмите Playing Next Video Interval: Воспроизведение следующего видео без задержки. Нажмите
для отмены. | Воспроизведение следующего видео через {nextVideoInterval} сек. Нажмите для отмены. | Воспроизведение следующего видео через {nextVideoInterval} сек. Нажмите

View File

@ -28,6 +28,11 @@ Close: 'Zavrieť'
Back: 'Späť' Back: 'Späť'
Forward: 'Vpred' Forward: 'Vpred'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videá'
# Search Bar # Search Bar
Search / Go to URL: 'Hľadať / Ísť na adresu URL' Search / Go to URL: 'Hľadať / Ísť na adresu URL'
# In Filter Button # In Filter Button

View File

@ -30,6 +30,11 @@ Close: 'Zapri'
Back: 'Nazaj' Back: 'Nazaj'
Forward: 'Naprej' Forward: 'Naprej'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videoposnetki'
Version {versionNumber} is now available! Click for more details: 'Na voljo je različica Version {versionNumber} is now available! Click for more details: 'Na voljo je različica
{versionNumber}!· Za več podrobnosti kliknite tukaj' {versionNumber}!· Za več podrobnosti kliknite tukaj'
Download From Site: 'Prenesi iz spletne strani' Download From Site: 'Prenesi iz spletne strani'

View File

@ -29,6 +29,11 @@ Close: 'Затвори'
Back: 'Назад' Back: 'Назад'
Forward: 'Напред' Forward: 'Напред'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Видео'
Version {versionNumber} is now available! Click for more details: 'Верзија {versionNumber} Version {versionNumber} is now available! Click for more details: 'Верзија {versionNumber}
је сада достуна! Кликните за више детаља' је сада достуна! Кликните за више детаља'
Download From Site: 'Преузми са сајта' Download From Site: 'Преузми са сајта'

View File

@ -29,6 +29,13 @@ Close: 'Stäng'
Back: 'Tillbaka' Back: 'Tillbaka'
Forward: 'Framåt' Forward: 'Framåt'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videor'
Shorts: Shorts
Live: Live
Version {versionNumber} is now available! Click for more details: 'Versionen {versionNumber} Version {versionNumber} is now available! Click for more details: 'Versionen {versionNumber}
är nu tillgänglig! Klicka för mer detaljer' är nu tillgänglig! Klicka för mer detaljer'
Download From Site: 'Ladda ner från sajten' Download From Site: 'Ladda ner från sajten'
@ -272,6 +279,8 @@ Settings:
Allow DASH AV1 formats: Tillåt DASH AV1-format Allow DASH AV1 formats: Tillåt DASH AV1-format
Scroll Playback Rate Over Video Player: Skrolla uppspelningshastighet över videospelaren Scroll Playback Rate Over Video Player: Skrolla uppspelningshastighet över videospelaren
Enter Fullscreen on Display Rotate: Fullskärm vid skärmrotation Enter Fullscreen on Display Rotate: Fullskärm vid skärmrotation
Comment Auto Load:
Comment Auto Load: Autoladda kommenterer
Privacy Settings: Privacy Settings:
Privacy Settings: 'Integritetsinställningar' Privacy Settings: 'Integritetsinställningar'
Remember History: 'Aktivera visningshistorik' Remember History: 'Aktivera visningshistorik'
@ -390,6 +399,15 @@ Settings:
Hide Upcoming Premieres: Dölj permiärer Hide Upcoming Premieres: Dölj permiärer
Display Titles Without Excessive Capitalisation: Visa titlar utan överdriven versalisering Display Titles Without Excessive Capitalisation: Visa titlar utan överdriven versalisering
Hide Channels Placeholder: Kanalnamn eller ID Hide Channels Placeholder: Kanalnamn eller ID
Hide Featured Channels: Dölj Utvalda kanaler
Hide Channel Shorts: Dölj Kanal Shorts
Sections:
Side Bar: Sidofält
General: Allmänt
Channel Page: Kanal sida
Watch Page: Titta sida
Hide Channel Community: Dölj Kanal gemenskap
Hide Channel Playlists: Dölj Kanal spellistor
The app needs to restart for changes to take effect. Restart and apply change?: Starta The app needs to restart for changes to take effect. Restart and apply change?: Starta
om FreeTube nu för att tillämpa ändringarna? om FreeTube nu för att tillämpa ändringarna?
Proxy Settings: Proxy Settings:
@ -419,7 +437,9 @@ Settings:
Show In Seek Bar: Visa i tidslinjen Show In Seek Bar: Visa i tidslinjen
Do Nothing: Gör ingenting Do Nothing: Gör ingenting
Auto Skip: Hoppa över automatiskt Auto Skip: Hoppa över automatiskt
Prompt To Skip: Prompt för att hoppa över
Category Color: Kategorifärg Category Color: Kategorifärg
UseDeArrowTitles: Använd DeArrow-videotitlar
External Player Settings: External Player Settings:
Ignore Unsupported Action Warnings: Ignorera händelsevarningar som inte stöds Ignore Unsupported Action Warnings: Ignorera händelsevarningar som inte stöds
External Player: Extern spelare External Player: Extern spelare
@ -604,6 +624,13 @@ Channel:
Community: Gemenskap Community: Gemenskap
This channel currently does not have any posts: Denna kanal har för närvarande This channel currently does not have any posts: Denna kanal har för närvarande
inga inlägg inga inlägg
Shorts:
This channel does not currently have any shorts: Den här kanalen har för närvarande
inga shorts
Live:
Live: Live
This channel does not currently have any live streams: Den här kanalen har för
närvarande inga liveströmmar
Video: Video:
Mark As Watched: 'Markera som sedd' Mark As Watched: 'Markera som sedd'
Remove From History: 'Ta bort från historik' Remove From History: 'Ta bort från historik'
@ -637,8 +664,8 @@ Video:
inte i denna version.' inte i denna version.'
'Chat is disabled or the Live Stream has ended.': 'Chatten är inaktiverad eller 'Chat is disabled or the Live Stream has ended.': 'Chatten är inaktiverad eller
så har Live strömmen avslutats.' så har Live strömmen avslutats.'
Live chat is enabled. Chat messages will appear here once sent.: 'Livechatt är Live chat is enabled. Chat messages will appear here once sent.: 'Livechatt är aktiverat.
aktiverat. Meddelanden visas här när de har skickats.' Meddelanden visas här när de har skickats.'
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Livechatt 'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Livechatt
stöds för närvarande inte med Invidious API. En direktanslutning till YouTube stöds för närvarande inte med Invidious API. En direktanslutning till YouTube
krävs.' krävs.'
@ -736,6 +763,8 @@ Video:
Upcoming: Kommande Upcoming: Kommande
Show Super Chat Comment: Visa Super Chat-kommentarer Show Super Chat Comment: Visa Super Chat-kommentarer
Scroll to Bottom: Skrolla till botten Scroll to Bottom: Skrolla till botten
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Livechatt
är inte tillgängligt för den här strömmen. Den kan ha inaktiverats av uppladdaren.
Videos: Videos:
#& Sort By #& Sort By
Sort By: Sort By:
@ -874,9 +903,9 @@ Tooltips:
Preferred API Backend: Välj den resurs som FreeTube använder för att få fram data. Preferred API Backend: Välj den resurs som FreeTube använder för att få fram data.
Det lokala API:et är en inbyggd utdragare. Invidious API kräver en Invidious Det lokala API:et är en inbyggd utdragare. Invidious API kräver en Invidious
server att ansluta till. server att ansluta till.
External Link Handling: "Välj standardförfarande när en länk klickas, som inte\ External Link Handling: "Välj standardförfarande när en länk klickas, som inte
\ kan öppnas i FreeTube är. \nStandard är att FreeTube kommer öppna länken i\ kan öppnas i FreeTube är. \nStandard är att FreeTube kommer öppna länken i din
\ din standardwebbläsare.\n" standardwebbläsare.\n"
Privacy Settings: Privacy Settings:
Remove Video Meta Files: Om vald, kommer FreeTube automatiskt att kasta metadata Remove Video Meta Files: Om vald, kommer FreeTube automatiskt att kasta metadata
filer som skapades under uppspelning, när sidan stängs. filer som skapades under uppspelning, när sidan stängs.
@ -893,9 +922,15 @@ Tooltips:
Invidiousinställningar påverkar inte externa videospelare. Invidiousinställningar påverkar inte externa videospelare.
DefaultCustomArgumentsTemplate: "(Standard: '{defaultCustomArguments}')" DefaultCustomArgumentsTemplate: "(Standard: '{defaultCustomArguments}')"
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Skriv in kanalnamn eller kanal-ID för att dölja alla videor, spellistor Hide Channels: Ange ett kanalnamn eller kanal-ID för att dölja alla videor, spellistor
och kanalen från att synas i sökfältet eller i populäraste kategorin. Kanalens och själva kanalen från att visas i sökningar, trender, populäraste och rekommenderade.
namn måste skrivas in i sin helhet och är skiftlägeskänsligt. Det angivna kanalnamnet måste vara en fullständig matchning och är skiftlägeskänsligt.
Experimental Settings:
Replace HTTP Cache: Inaktiverar Electrons diskbaserade HTTP-cache och aktiverar
en anpassad bildcache i minnet. Kommer att leda till ökad RAM-användning.
SponsorBlock Settings:
UseDeArrowTitles: Ersätt videotitlar med titlar som användare har skickat in från
DeArrow.
More: Mer More: Mer
Open New Window: Öppna nytt fönster Open New Window: Öppna nytt fönster
Playing Next Video Interval: Spelar strax nästa video. Klicka för att stoppa. | Spelar Playing Next Video Interval: Spelar strax nästa video. Klicka för att stoppa. | Spelar
@ -946,3 +981,7 @@ Chapters:
Downloading has completed: '"{videoTitle}" har laddats ner' Downloading has completed: '"{videoTitle}" har laddats ner'
Starting download: Påbörjar nerladdning av "{videoTitle}" Starting download: Påbörjar nerladdning av "{videoTitle}"
Downloading failed: Det uppstod ett problem med nerladdning av "{videoTitle}" Downloading failed: Det uppstod ett problem med nerladdning av "{videoTitle}"
Hashtag:
Hashtag: Hashtaggar
This hashtag does not currently have any videos: Den här hashtaggen har för närvarande
inga videor

View File

@ -29,6 +29,13 @@ Close: 'Kapat'
Back: 'Geri' Back: 'Geri'
Forward: 'İleri' Forward: 'İleri'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Videolar'
Shorts: Kısa Videolar
Live: Canlı
Version {versionNumber} is now available! Click for more details: '{versionNumber} Version {versionNumber} is now available! Click for more details: '{versionNumber}
sürümü çıktı! Daha fazla ayrıntı için tıklayın' sürümü çıktı! Daha fazla ayrıntı için tıklayın'
Download From Site: 'Siteden indir' Download From Site: 'Siteden indir'
@ -88,6 +95,10 @@ Subscriptions:
Disabled Automatic Fetching: Otomatik abonelik getirmeyi devre dışı bıraktınız. Disabled Automatic Fetching: Otomatik abonelik getirmeyi devre dışı bıraktınız.
Abonelikleri burada görmek için yenileyin. Abonelikleri burada görmek için yenileyin.
Empty Channels: Abone olduğunuz kanallarda şu anda herhangi bir video yok. Empty Channels: Abone olduğunuz kanallarda şu anda herhangi bir video yok.
Subscriptions Tabs: Abonelikler Sekmeleri
All Subscription Tabs Hidden: Tüm abonelik sekmeleri gizlidir. Buradaki içeriği
görmek için lütfen "{settingsSection}" içindeki "{subsection}" bölümündeki bazı
sekmelerin gizliliğini kaldırın.
Trending: Trending:
Trending: 'Öne Çıkanlar' Trending: 'Öne Çıkanlar'
Trending Tabs: Öne Çıkanlar Sekmeleri Trending Tabs: Öne Çıkanlar Sekmeleri
@ -410,6 +421,12 @@ Settings:
Channel Page: Kanal Sayfası Channel Page: Kanal Sayfası
Watch Page: İzleme Sayfası Watch Page: İzleme Sayfası
General: Genel General: Genel
Subscriptions Page: Abonelikler Sayfası
Hide Channel Releases: Kanal Yayınlarını Gizle
Hide Channel Podcasts: Kanal Podcast'lerini Gizle
Hide Subscriptions Videos: Abonelik Videolarını Gizle
Hide Subscriptions Shorts: Abonelik Kısa Videolarını Gizle
Hide Subscriptions Live: Abonelik Canlı Yayınlarını Gizle
The app needs to restart for changes to take effect. Restart and apply change?: Değişikliklerin The app needs to restart for changes to take effect. Restart and apply change?: Değişikliklerin
etkili olması için uygulamanın yeniden başlatılması gerekiyor. Yeniden başlatılsın etkili olması için uygulamanın yeniden başlatılması gerekiyor. Yeniden başlatılsın
ve değişiklikler uygulansın mı? ve değişiklikler uygulansın mı?
@ -640,9 +657,16 @@ Channel:
This channel does not currently have any live streams: Bu kanalda şu anda herhangi This channel does not currently have any live streams: Bu kanalda şu anda herhangi
bir canlı yayın yok bir canlı yayın yok
Shorts: Shorts:
Shorts: Kısa Videolar
This channel does not currently have any shorts: Bu kanalda şu anda hiç kısa video This channel does not currently have any shorts: Bu kanalda şu anda hiç kısa video
yok yok
Podcasts:
Podcasts: Podcast'ler
This channel does not currently have any podcasts: Bu kanalda şu anda herhangi
bir podcast yok
Releases:
This channel does not currently have any releases: Bu kanalda şu anda herhangi
bir yayın yok
Releases: Yayınlar
Video: Video:
Mark As Watched: 'İzlendi Olarak İşaretle' Mark As Watched: 'İzlendi Olarak İşaretle'
Remove From History: 'Geçmişden Kaldır' Remove From History: 'Geçmişden Kaldır'
@ -948,9 +972,13 @@ Tooltips:
ve özel bir bellek içi resim önbelleğini etkinleştirir. RAM kullanımının artmasına ve özel bir bellek içi resim önbelleğini etkinleştirir. RAM kullanımının artmasına
neden olacaktır. neden olacaktır.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Tüm videoların, oynatma listelerinin ve kanalın kendisinin arama Hide Channels: Tüm videoların, oynatma listelerinin ve kanalın kendisinin arama,
veya öne çıkanlarda görünmesini engellemek için bir kanal adı veya kanal kimliği öne çıkanlar, en popüler ve tavsiye edilenlerde görünmesini engellemek için
girin. Girilen kanal adı tam olarak eşleşmelidir ve büyük/küçük harfe duyarlıdır. bir kanal adı veya kanal kimliği girin. Girilen kanal adı tam olarak eşleşmelidir
ve büyük/küçük harfe duyarlıdır.
Hide Subscriptions Live: Bu ayar, "{settingsSection}" bölümünün "{subsection}"
kısmında yer alan uygulama genelindeki "{appWideSetting}" ayarı tarafından geçersiz
kılınıyor
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: Video başlıklarını DeArrow'dan kullanıcıların gönderdiği başlıklarla UseDeArrowTitles: Video başlıklarını DeArrow'dan kullanıcıların gönderdiği başlıklarla
değiştir. değiştir.

View File

@ -29,6 +29,13 @@ Close: 'Закрити'
Back: 'Назад' Back: 'Назад'
Forward: 'Вперед' Forward: 'Вперед'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Відео'
Shorts: Shorts
Live: Наживо
Version {versionNumber} is now available! Click for more details: 'Доступна нова Version {versionNumber} is now available! Click for more details: 'Доступна нова
версія {versionNumber} ! Натисніть щоб побачити деталі' версія {versionNumber} ! Натисніть щоб побачити деталі'
Download From Site: 'Завантажити з сайту' Download From Site: 'Завантажити з сайту'
@ -89,6 +96,9 @@ Subscriptions:
Disabled Automatic Fetching: Ви вимкнули автоматичне отримання підписок. Оновіть Disabled Automatic Fetching: Ви вимкнули автоматичне отримання підписок. Оновіть
підписки, щоб вони з'явились тут. підписки, щоб вони з'явились тут.
Empty Channels: Канали, на які ви підписалися, наразі не містять відео. Empty Channels: Канали, на які ви підписалися, наразі не містять відео.
Subscriptions Tabs: Вкладки підписок
All Subscription Tabs Hidden: Усі вкладки підписки сховані. Щоб побачити вміст,
будь ласка, відкрийте деякі вкладки в розділі "{subsection}" в "{settingsSection}".
Trending: Trending:
Trending: 'Популярне' Trending: 'Популярне'
Trending Tabs: Популярні вкладки Trending Tabs: Популярні вкладки
@ -337,6 +347,12 @@ Settings:
Channel Page: Сторінка каналу Channel Page: Сторінка каналу
Watch Page: Сторінка перегляду Watch Page: Сторінка перегляду
General: Загальні General: Загальні
Subscriptions Page: Сторінка підписок
Hide Subscriptions Videos: Сховати відео з підписок
Hide Channel Podcasts: Сховати канали подкастів
Hide Channel Releases: Сховати канали випусків
Hide Subscriptions Shorts: Сховати Shorts із підписок
Hide Subscriptions Live: Сховати трансляції з підписок
Data Settings: Data Settings:
Data Settings: 'Налаштування даних' Data Settings: 'Налаштування даних'
Select Import Type: 'Оберіть тип імпорту' Select Import Type: 'Оберіть тип імпорту'
@ -571,8 +587,15 @@ Channel:
немає прямих трансляцій немає прямих трансляцій
Live: Наживо Live: Наживо
Shorts: Shorts:
Shorts: Shorts
This channel does not currently have any shorts: На цьому каналі немає Shorts This channel does not currently have any shorts: На цьому каналі немає Shorts
Podcasts:
Podcasts: Подкасти
This channel does not currently have any podcasts: На цьому каналі наразі немає
подкастів
Releases:
This channel does not currently have any releases: Наразі на цьому каналі немає
випусків
Releases: Випуски
Video: Video:
Mark As Watched: 'Позначити переглянутим' Mark As Watched: 'Позначити переглянутим'
Remove From History: 'Прибрати з історії' Remove From History: 'Прибрати з історії'
@ -865,8 +888,10 @@ Tooltips:
у пам'яті. Призведе до збільшення використання оперативної пам'яті. у пам'яті. Призведе до збільшення використання оперативної пам'яті.
Distraction Free Settings: Distraction Free Settings:
Hide Channels: Введіть назву або ID каналу, щоб сховати всі відео, списки відтворення Hide Channels: Введіть назву або ID каналу, щоб сховати всі відео, списки відтворення
та сам канал від появи в пошуку або в тренді. Введена назва каналу повинна повністю та сам канал від появи в пошуку, тренді, найпопулярніших і рекомендованих. Введена
збігатися і чутлива до регістру. назва каналу повинна повністю збігатися і чутлива до регістру.
Hide Subscriptions Live: Цей параметр перевизначається загальнодоступним налаштуванням
"{appWideSetting}" у розділі "{subsection}" "{settingsSection}"
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: Замінити назви відео на надіслані користувачем назви з DeArrow. UseDeArrowTitles: Замінити назви відео на надіслані користувачем назви з DeArrow.
Local API Error (Click to copy): 'Помилка локального API (натисніть, щоб скопіювати)' Local API Error (Click to copy): 'Помилка локального API (натисніть, щоб скопіювати)'

View File

@ -28,6 +28,11 @@ Close: 'Đóng'
Back: 'Quay lại' Back: 'Quay lại'
Forward: 'Tiến tới' Forward: 'Tiến tới'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'Video'
# Search Bar # Search Bar
Search / Go to URL: 'Tìm kiếm / Đi đến URL' Search / Go to URL: 'Tìm kiếm / Đi đến URL'
# In Filter Button # In Filter Button

View File

@ -27,6 +27,13 @@ Close: '关闭'
Back: '后退' Back: '后退'
Forward: '前进' Forward: '前进'
# Global
# Anything shared among components / views should be put here
Global:
Videos: '视频'
Shorts: 短视频
Live: 直播
# Search Bar # Search Bar
Search / Go to URL: '搜索 / 前往URL' Search / Go to URL: '搜索 / 前往URL'
# In Filter Button # In Filter Button
@ -79,6 +86,9 @@ Subscriptions:
Error Channels: 有错误的频道 Error Channels: 有错误的频道
Disabled Automatic Fetching: 你仅用了自动订阅获取。刷新订阅在此处看到它们。 Disabled Automatic Fetching: 你仅用了自动订阅获取。刷新订阅在此处看到它们。
Empty Channels: 你订阅的频道当前没有任何视频。 Empty Channels: 你订阅的频道当前没有任何视频。
Subscriptions Tabs: 订阅标签页
All Subscription Tabs Hidden: 所有的订阅标签页均被隐藏。要在此查看内容,请在 "{subsection}" 部分取消隐藏某些标签页,此部分位于
"{settingsSection}"
Trending: Trending:
Trending: '热门' Trending: '热门'
Trending Tabs: 流行标签 Trending Tabs: 流行标签
@ -373,6 +383,12 @@ Settings:
Channel Page: 频道页 Channel Page: 频道页
Watch Page: 观看页 Watch Page: 观看页
General: 常规 General: 常规
Subscriptions Page: 订阅页
Hide Channel Podcasts: 隐藏频道播客
Hide Channel Releases: 隐藏频道发布
Hide Subscriptions Shorts: 隐藏订阅短视频
Hide Subscriptions Videos: 隐藏订阅视频
Hide Subscriptions Live: 隐藏订阅直播
The app needs to restart for changes to take effect. Restart and apply change?: 应用需要重启让修改生效。重启以应用修改? The app needs to restart for changes to take effect. Restart and apply change?: 应用需要重启让修改生效。重启以应用修改?
Proxy Settings: Proxy Settings:
Proxy Protocol: 代理协议 Proxy Protocol: 代理协议
@ -534,7 +550,12 @@ Channel:
This channel does not currently have any live streams: 此频道当前没有任何直播流 This channel does not currently have any live streams: 此频道当前没有任何直播流
Shorts: Shorts:
This channel does not currently have any shorts: 此频道目前没有任何短视频 This channel does not currently have any shorts: 此频道目前没有任何短视频
Shorts: 短视频 Podcasts:
This channel does not currently have any podcasts: 此频道当前无任何播客节目
Podcasts: 播客
Releases:
Releases: 发布
This channel does not currently have any releases: 此频道当前无任何发布
Video: Video:
Open in YouTube: '在YouTube中打开' Open in YouTube: '在YouTube中打开'
Copy YouTube Link: '复制YouTube链接' Copy YouTube Link: '复制YouTube链接'
@ -837,7 +858,9 @@ Tooltips:
Experimental Settings: Experimental Settings:
Replace HTTP Cache: 禁用 Electron 基于磁盘的 HTTP 缓存,启用自定义内存中图像缓存。会增加内存的使用。 Replace HTTP Cache: 禁用 Electron 基于磁盘的 HTTP 缓存,启用自定义内存中图像缓存。会增加内存的使用。
Distraction Free Settings: Distraction Free Settings:
Hide Channels: 输入频道名称或频道 ID 以隐藏所有视频、播放列表和频道本身,使其不出现在搜索结果或热门中。 输入的频道名称必须完全匹配,并且区分大小写。 Hide Channels: 输入频道名称或频道 ID 使其中的所有视频、播放列表和频道本身不出现在搜索结果、时下流行、最受欢迎和推荐中。 输入的频道名称必须完全匹配,并且区分大小写。
Hide Subscriptions Live: 此设置被应用级的 "{appWideSetting}" 设置所覆盖,"{appWideSetting}"
位于 "{subsection}" 部分,该部分在 "{settingsSection}" 中
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: 使用来自 DeArrow 的用户提交的标题替换原始视频标题。 UseDeArrowTitles: 使用来自 DeArrow 的用户提交的标题替换原始视频标题。
More: 更多 More: 更多

View File

@ -27,6 +27,13 @@ Close: '關閉'
Back: '返回' Back: '返回'
Forward: '前進' Forward: '前進'
# Global
# Anything shared among components / views should be put here
Global:
Videos: '影片'
Shorts: 短片
Live: 直播
# Search Bar # Search Bar
Search / Go to URL: '搜尋/ 前往網址' Search / Go to URL: '搜尋/ 前往網址'
# In Filter Button # In Filter Button
@ -79,6 +86,8 @@ Subscriptions:
Error Channels: 有錯誤的頻道 Error Channels: 有錯誤的頻道
Disabled Automatic Fetching: 您已停用自動訂閱擷取。重新整理訂閱以在此處檢視。 Disabled Automatic Fetching: 您已停用自動訂閱擷取。重新整理訂閱以在此處檢視。
Empty Channels: 您訂閱的頻道目前沒有任何影片。 Empty Channels: 您訂閱的頻道目前沒有任何影片。
Subscriptions Tabs: 訂閱分頁
All Subscription Tabs Hidden: 所有訂閱分頁均已隱藏。要檢視此處的內容,請取消隱藏「{settingsSection}」中「{subsection}」區塊中的一些分頁。
Trending: Trending:
Trending: '發燒影片' Trending: '發燒影片'
Trending Tabs: 熱門分頁 Trending Tabs: 熱門分頁
@ -375,6 +384,12 @@ Settings:
Watch Page: 觀看頁面 Watch Page: 觀看頁面
General: 一般 General: 一般
Channel Page: 頻道頁面 Channel Page: 頻道頁面
Subscriptions Page: 訂閱頁面
Hide Channel Podcasts: 隱藏頻道 Podcast
Hide Channel Releases: 隱藏頻道發布
Hide Subscriptions Shorts: 隱藏訂閱短片
Hide Subscriptions Videos: 隱藏訂閱影片
Hide Subscriptions Live: 隱藏訂閱直播
The app needs to restart for changes to take effect. Restart and apply change?: 此變更需要重啟讓修改生效。重啟並且套用變更? The app needs to restart for changes to take effect. Restart and apply change?: 此變更需要重啟讓修改生效。重啟並且套用變更?
Proxy Settings: Proxy Settings:
Error getting network information. Is your proxy configured properly?: 取得網路資訊時發生錯誤。您的代理伺服器設定正確嗎? Error getting network information. Is your proxy configured properly?: 取得網路資訊時發生錯誤。您的代理伺服器設定正確嗎?
@ -544,8 +559,13 @@ Channel:
Live: 直播 Live: 直播
This channel does not currently have any live streams: 此頻道目前沒有任何直播 This channel does not currently have any live streams: 此頻道目前沒有任何直播
Shorts: Shorts:
Shorts: 短片
This channel does not currently have any shorts: 此頻道目前沒有任何短片 This channel does not currently have any shorts: 此頻道目前沒有任何短片
Podcasts:
This channel does not currently have any podcasts: 此頻道目前沒有 podcast
Podcasts: Podcast
Releases:
Releases: 發布
This channel does not currently have any releases: 此頻道目前沒有任何發布
Video: Video:
Open in YouTube: '在YouTube中開啟' Open in YouTube: '在YouTube中開啟'
Copy YouTube Link: '複製YouTube連結' Copy YouTube Link: '複製YouTube連結'
@ -850,7 +870,8 @@ Tooltips:
Experimental Settings: Experimental Settings:
Replace HTTP Cache: 停用 Electron 以磁碟為基礎的 HTTP 快取並啟用自訂的記憶體圖片快取。會導致記憶體使用量增加。 Replace HTTP Cache: 停用 Electron 以磁碟為基礎的 HTTP 快取並啟用自訂的記憶體圖片快取。會導致記憶體使用量增加。
Distraction Free Settings: Distraction Free Settings:
Hide Channels: 輸入頻道名稱或頻道 ID 以隱藏所有影片、播放清單與頻道本身,使其完全不出現在搜尋或趨勢中。輸入的頻道名稱必須完全符合,且區分大小寫。 Hide Channels: 輸入頻道名稱或頻道 ID 以隱藏所有影片、播放清單與頻道本身,使其完全不出現在搜尋、趨勢、熱門與建議中。輸入的頻道名稱必須完全符合,且區分大小寫。
Hide Subscriptions Live: 此設定會被「{settingsSection}」的「{subsection}」部分中應用程式範圍的「{appWideSetting}」設定覆寫
SponsorBlock Settings: SponsorBlock Settings:
UseDeArrowTitles: 將影片標題取代為 DeArrow 使用者遞交的標題。 UseDeArrowTitles: 將影片標題取代為 DeArrow 使用者遞交的標題。
Playing Next Video Interval: 馬上播放下一個影片。點擊取消。| 播放下一個影片的時間為{nextVideoInterval}秒。點擊取消。| Playing Next Video Interval: 馬上播放下一個影片。點擊取消。| 播放下一個影片的時間為{nextVideoInterval}秒。點擊取消。|

939
yarn.lock

File diff suppressed because it is too large Load Diff