mirror of https://github.com/FreeTubeApp/FreeTube
Merge branch 'development' into piped-support
This commit is contained in:
commit
6fc7cb0b44
|
@ -1 +1,17 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/FreeTubeApp/FreeTube/discussions/categories/general
|
||||
about: View discussions or start one yourself
|
||||
- name: Questions
|
||||
url: https://github.com/FreeTubeApp/FreeTube/discussions/categories/q-a
|
||||
about: Ask and answer questions
|
||||
- name: Matrix Community
|
||||
url: https://matrix.to/#/+freetube:matrix.org
|
||||
about: Join our Matrix chatroom - "Note: Bugs and Feature requests should be made on GitHub and not in the Matrix room"
|
||||
- name: Translate FreeTube
|
||||
url: https://hosted.weblate.org/engage/free-tube/
|
||||
about: Help translate FreeTube on Weblate
|
||||
- name: FreeTube Documentation
|
||||
url: https://docs.freetubeapp.io/
|
||||
about: View the Documentation to find all relevant information about FreeTube
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
process.env.NODE_ENV = 'development'
|
||||
|
||||
const open = require('open')
|
||||
const electron = require('electron')
|
||||
const webpack = require('webpack')
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
|
@ -161,7 +160,8 @@ function startWeb (callback) {
|
|||
if (!web) {
|
||||
startRenderer(startMain)
|
||||
} else {
|
||||
startWeb(({ port }) => {
|
||||
startWeb(async ({ port }) => {
|
||||
const open = (await import('open')).default
|
||||
open(`http://localhost:${port}`)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
|
|||
|
||||
const isDevMode = process.env.NODE_ENV === 'development'
|
||||
|
||||
const { version: swiperVersion } = JSON.parse(readFileSync(path.join(__dirname, '../node_modules/swiper/package.json')))
|
||||
|
||||
const processLocalesPlugin = new ProcessLocalesPlugin({
|
||||
compress: !isDevMode,
|
||||
inputDir: path.join(__dirname, '../static/locales'),
|
||||
|
@ -118,7 +120,8 @@ const config = {
|
|||
'process.env.IS_ELECTRON': true,
|
||||
'process.env.IS_ELECTRON_MAIN': false,
|
||||
'process.env.LOCALE_NAMES': JSON.stringify(processLocalesPlugin.localeNames),
|
||||
'process.env.GEOLOCATION_NAMES': JSON.stringify(readdirSync(path.join(__dirname, '..', 'static', 'geolocations')).map(filename => filename.replace('.json', '')))
|
||||
'process.env.GEOLOCATION_NAMES': JSON.stringify(readdirSync(path.join(__dirname, '..', 'static', 'geolocations')).map(filename => filename.replace('.json', ''))),
|
||||
'process.env.SWIPER_VERSION': `'${swiperVersion}'`
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
excludeChunks: ['processTaskWorker'],
|
||||
|
@ -137,7 +140,7 @@ const config = {
|
|||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../node_modules/swiper/modules/{a11y,navigation,pagination}-element.css').replaceAll('\\', '/'),
|
||||
to: 'swiper.css',
|
||||
to: `swiper-${swiperVersion}.css`,
|
||||
context: path.join(__dirname, '../node_modules/swiper/modules'),
|
||||
transformAll: (assets) => {
|
||||
return Buffer.concat(assets.map(asset => asset.data))
|
||||
|
|
|
@ -11,6 +11,8 @@ const ProcessLocalesPlugin = require('./ProcessLocalesPlugin')
|
|||
|
||||
const isDevMode = process.env.NODE_ENV === 'development'
|
||||
|
||||
const { version: swiperVersion } = JSON.parse(fs.readFileSync(path.join(__dirname, '../node_modules/swiper/package.json')))
|
||||
|
||||
const config = {
|
||||
name: 'web',
|
||||
mode: process.env.NODE_ENV,
|
||||
|
@ -114,6 +116,7 @@ const config = {
|
|||
new webpack.DefinePlugin({
|
||||
'process.env.IS_ELECTRON': false,
|
||||
'process.env.IS_ELECTRON_MAIN': false,
|
||||
'process.env.SWIPER_VERSION': `'${swiperVersion}'`,
|
||||
|
||||
// video.js' vhs-utils supports both atob() in web browsers and Buffer in node
|
||||
// As the FreeTube web build only runs in web browsers, we can override their check for atob() here: https://github.com/videojs/vhs-utils/blob/main/src/decode-b64-to-uint8-array.js#L3
|
||||
|
@ -145,7 +148,7 @@ const config = {
|
|||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../node_modules/swiper/modules/{a11y,navigation,pagination}-element.css').replaceAll('\\', '/'),
|
||||
to: 'swiper.css',
|
||||
to: `swiper-${swiperVersion}.css`,
|
||||
context: path.join(__dirname, '../node_modules/swiper/modules'),
|
||||
transformAll: (assets) => {
|
||||
return Buffer.concat(assets.map(asset => asset.data))
|
||||
|
|
30
package.json
30
package.json
|
@ -65,7 +65,7 @@
|
|||
"marked": "^12.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"process": "^0.11.10",
|
||||
"swiper": "^11.0.6",
|
||||
"swiper": "^11.0.7",
|
||||
"video.js": "7.21.5",
|
||||
"videojs-contrib-quality-levels": "^3.0.0",
|
||||
"videojs-http-source-selector": "^1.1.6",
|
||||
|
@ -77,21 +77,21 @@
|
|||
"vue-observe-visibility": "^1.0.0",
|
||||
"vue-router": "^3.6.5",
|
||||
"vuex": "^3.6.2",
|
||||
"youtubei.js": "^9.0.2"
|
||||
"youtubei.js": "^9.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/eslint-parser": "^7.23.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@double-great/stylelint-a11y": "^3.0.1",
|
||||
"@babel/preset-env": "^7.24.0",
|
||||
"@double-great/stylelint-a11y": "^3.0.2",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^6.10.0",
|
||||
"css-minimizer-webpack-plugin": "^6.0.0",
|
||||
"electron": "^28.2.2",
|
||||
"electron-builder": "^24.9.1",
|
||||
"eslint": "^8.56.0",
|
||||
"electron": "^29.1.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
|
@ -100,21 +100,21 @@
|
|||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-unicorn": "^51.0.1",
|
||||
"eslint-plugin-vue": "^9.21.1",
|
||||
"eslint-plugin-vue": "^9.22.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.2.1",
|
||||
"eslint-plugin-yml": "^1.12.2",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-minimizer-webpack-plugin": "^5.0.0",
|
||||
"lefthook": "^1.6.1",
|
||||
"mini-css-extract-plugin": "^2.8.0",
|
||||
"lefthook": "^1.6.4",
|
||||
"mini-css-extract-plugin": "^2.8.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
"sass": "^1.70.0",
|
||||
"sass-loader": "^14.1.0",
|
||||
"sass": "^1.71.1",
|
||||
"sass-loader": "^14.1.1",
|
||||
"stylelint": "^16.2.1",
|
||||
"stylelint-config-sass-guidelines": "^11.0.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
|
@ -124,9 +124,9 @@
|
|||
"vue-devtools": "^5.1.4",
|
||||
"vue-eslint-parser": "^9.4.2",
|
||||
"vue-loader": "^15.10.0",
|
||||
"webpack": "^5.90.1",
|
||||
"webpack": "^5.90.3",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-dev-server": "^5.0.2",
|
||||
"webpack-watch-external-files-plugin": "^3.0.0",
|
||||
"yaml-eslint-parser": "^1.2.2"
|
||||
}
|
||||
|
|
|
@ -64,7 +64,9 @@ function runApp() {
|
|||
const path = urlParts[1]
|
||||
|
||||
if (path) {
|
||||
visible = ['/playlist', '/channel', '/watch'].some(p => path.startsWith(p))
|
||||
visible = ['/channel', '/watch'].some(p => path.startsWith(p)) ||
|
||||
// Only show copy link entry for non user playlists
|
||||
(path.startsWith('/playlist') && !/playlistType=user/.test(path))
|
||||
}
|
||||
} else {
|
||||
visible = true
|
||||
|
@ -103,17 +105,17 @@ function runApp() {
|
|||
let url
|
||||
|
||||
if (toYouTube) {
|
||||
url = `https://youtu.be/${id}`
|
||||
url = new URL(`https://youtu.be/${id}`)
|
||||
} else {
|
||||
url = `https://redirect.invidious.io/watch?v=${id}`
|
||||
url = new URL(`https://redirect.invidious.io/watch?v=${id}`)
|
||||
}
|
||||
|
||||
if (query) {
|
||||
const params = new URLSearchParams(query)
|
||||
const newParams = new URLSearchParams()
|
||||
const newParams = new URLSearchParams(url.search)
|
||||
let hasParams = false
|
||||
|
||||
if (params.has('playlistId')) {
|
||||
if (params.has('playlistId') && params.get('playlistType') !== 'user') {
|
||||
newParams.set('list', params.get('playlistId'))
|
||||
hasParams = true
|
||||
}
|
||||
|
@ -124,11 +126,11 @@ function runApp() {
|
|||
}
|
||||
|
||||
if (hasParams) {
|
||||
url += '?' + newParams.toString()
|
||||
url.search = newParams.toString()
|
||||
}
|
||||
}
|
||||
|
||||
return url
|
||||
return url.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -886,7 +886,23 @@ export default defineComponent({
|
|||
showToast(`${message}: ${err}`)
|
||||
return
|
||||
}
|
||||
const playlists = JSON.parse(data)
|
||||
let playlists = null
|
||||
|
||||
// for the sake of backwards compatibility,
|
||||
// check if this is the old JSON array export (used until version 0.19.1),
|
||||
// that didn't match the actual database format
|
||||
const trimmedData = data.trim()
|
||||
|
||||
if (trimmedData[0] === '[' && trimmedData[trimmedData.length - 1] === ']') {
|
||||
playlists = JSON.parse(trimmedData)
|
||||
} else {
|
||||
// otherwise assume this is the correct database format,
|
||||
// which is also what we export now (used in 0.20.0 and later versions)
|
||||
data = data.split('\n')
|
||||
data.pop()
|
||||
|
||||
playlists = data.map(playlistJson => JSON.parse(playlistJson))
|
||||
}
|
||||
|
||||
const requiredKeys = [
|
||||
'playlistName',
|
||||
|
@ -1019,7 +1035,11 @@ export default defineComponent({
|
|||
]
|
||||
}
|
||||
|
||||
await this.promptAndWriteToFile(options, JSON.stringify(this.allPlaylists), 'All playlists has been successfully exported')
|
||||
const playlistsDb = this.allPlaylists.map(playlist => {
|
||||
return JSON.stringify(playlist)
|
||||
}).join('\n') + '\n'// a trailing line is expected
|
||||
|
||||
await this.promptAndWriteToFile(options, playlistsDb, 'All playlists has been successfully exported')
|
||||
},
|
||||
|
||||
exportPlaylistsForOlderVersionsSometimes: function () {
|
||||
|
|
|
@ -40,7 +40,6 @@ export default defineComponent({
|
|||
voteCount: '',
|
||||
postContent: '',
|
||||
commentCount: '',
|
||||
isLoading: true,
|
||||
author: '',
|
||||
authorId: '',
|
||||
}
|
||||
|
@ -73,7 +72,7 @@ export default defineComponent({
|
|||
injectStylesUrls: [
|
||||
// This file is created with the copy webpack plugin in the web and renderer webpack configs.
|
||||
// If you add more modules, please remember to add their CSS files to the list in webpack config files.
|
||||
createWebURL('/swiper.css')
|
||||
createWebURL(`/swiper-${process.env.SWIPER_VERSION}.css`)
|
||||
],
|
||||
|
||||
a11y: true,
|
||||
|
@ -132,7 +131,6 @@ export default defineComponent({
|
|||
this.type = (this.data.postContent !== null && this.data.postContent !== undefined) ? this.data.postContent.type : 'text'
|
||||
this.author = this.data.author
|
||||
this.authorId = this.data.authorId
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
getBestQualityImage(imageArray) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="!isLoading"
|
||||
class="ft-list-post ft-list-item outside"
|
||||
:appearance="appearance"
|
||||
:class="{ list: listType === 'list', grid: listType === 'grid' }"
|
||||
|
|
|
@ -128,6 +128,11 @@ export default defineComponent({
|
|||
|
||||
handleResize: function () {
|
||||
this.useModal = window.innerWidth <= 900
|
||||
}
|
||||
},
|
||||
|
||||
focus() {
|
||||
// To be called by parent components
|
||||
this.$refs.iconButton.focus()
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div class="ftIconButton">
|
||||
<font-awesome-icon
|
||||
ref="iconButton"
|
||||
class="iconButton"
|
||||
:title="title"
|
||||
:icon="icon"
|
||||
|
|
|
@ -3,3 +3,8 @@
|
|||
.thumbnailLink:hover {
|
||||
outline: 3px solid var(--side-nav-hover-color);
|
||||
}
|
||||
|
||||
.thumbnailImage {
|
||||
// Makes img element sized correctly before image loading starts
|
||||
aspect-ratio: 16/9 auto;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
>
|
||||
<ft-playlist-selector
|
||||
tabindex="0"
|
||||
:data="playlist"
|
||||
:playlist="playlist"
|
||||
:index="index"
|
||||
:selected="selectedPlaylistIdList.includes(playlist._id)"
|
||||
@selected="countSelected(playlist._id)"
|
||||
|
|
|
@ -8,7 +8,7 @@ export default defineComponent({
|
|||
'ft-icon-button': FtIconButton
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
playlist: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
@ -30,6 +30,8 @@ export default defineComponent({
|
|||
title: '',
|
||||
thumbnail: require('../../assets/img/thumbnail_placeholder.svg'),
|
||||
videoCount: 0,
|
||||
|
||||
videoPresenceCountInPlaylistTextShouldBeVisible: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -39,6 +41,9 @@ export default defineComponent({
|
|||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
toBeAddedToPlaylistVideoList: function () {
|
||||
return this.$store.getters.getToBeAddedToPlaylistVideoList
|
||||
},
|
||||
|
||||
titleForDisplay: function () {
|
||||
if (typeof this.title !== 'string') { return '' }
|
||||
|
@ -46,28 +51,63 @@ export default defineComponent({
|
|||
|
||||
return `${this.title.substring(0, 255)}...`
|
||||
},
|
||||
|
||||
loneToBeAddedToPlaylistVideo: function () {
|
||||
if (this.toBeAddedToPlaylistVideoList.length !== 1) { return null }
|
||||
|
||||
return this.toBeAddedToPlaylistVideoList[0]
|
||||
},
|
||||
loneVideoPresenceCountInPlaylist() {
|
||||
if (this.loneToBeAddedToPlaylistVideo == null) { return null }
|
||||
|
||||
const v = this.playlist.videos.reduce((accumulator, video) => {
|
||||
return video.videoId === this.loneToBeAddedToPlaylistVideo.videoId
|
||||
? accumulator + 1
|
||||
: accumulator
|
||||
}, 0)
|
||||
// Don't display zero value
|
||||
return v === 0 ? null : v
|
||||
},
|
||||
loneVideoPresenceCountInPlaylistText() {
|
||||
if (this.loneVideoPresenceCountInPlaylist == null) { return null }
|
||||
|
||||
return this.$tc('User Playlists.AddVideoPrompt.Added {count} Times', this.loneVideoPresenceCountInPlaylist, {
|
||||
count: this.loneVideoPresenceCountInPlaylist,
|
||||
})
|
||||
},
|
||||
videoPresenceCountInPlaylistTextVisible() {
|
||||
if (!this.videoPresenceCountInPlaylistTextShouldBeVisible) { return false }
|
||||
|
||||
return this.loneVideoPresenceCountInPlaylistText != null
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
this.parseUserData()
|
||||
},
|
||||
methods: {
|
||||
parseUserData: function () {
|
||||
this.title = this.data.playlistName
|
||||
if (this.data.videos.length > 0) {
|
||||
const thumbnailURL = `https://i.ytimg.com/vi/${this.data.videos[0].videoId}/mqdefault.jpg`
|
||||
this.title = this.playlist.playlistName
|
||||
if (this.playlist.videos.length > 0) {
|
||||
const thumbnailURL = `https://i.ytimg.com/vi/${this.playlist.videos[0].videoId}/mqdefault.jpg`
|
||||
if (this.backendPreference === 'invidious') {
|
||||
this.thumbnail = thumbnailURL.replace('https://i.ytimg.com', this.currentInvidiousInstance)
|
||||
} else {
|
||||
this.thumbnail = thumbnailURL
|
||||
}
|
||||
}
|
||||
this.videoCount = this.data.videos.length
|
||||
this.videoCount = this.playlist.videos.length
|
||||
},
|
||||
|
||||
toggleSelection: function () {
|
||||
this.$emit('selected', this.index)
|
||||
},
|
||||
|
||||
onVisibilityChanged(visible) {
|
||||
if (!visible) { return }
|
||||
|
||||
this.videoPresenceCountInPlaylistTextShouldBeVisible = true
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'openInExternalPlayer'
|
||||
])
|
||||
|
|
|
@ -54,6 +54,10 @@
|
|||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.videoPresenceCount {
|
||||
margin-block-start: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.grid {
|
||||
|
|
|
@ -29,12 +29,24 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span
|
||||
<div
|
||||
v-observe-visibility="{
|
||||
callback: onVisibilityChanged,
|
||||
once: true,
|
||||
}"
|
||||
class="info"
|
||||
>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
{{ titleForDisplay }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="videoPresenceCountInPlaylistTextVisible"
|
||||
class="videoPresenceCount"
|
||||
>
|
||||
{{ loneVideoPresenceCountInPlaylistText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,18 +9,29 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
catchTimestampClick: function(event) {
|
||||
const match = event.detail.match(/(\d+):(\d+):?(\d+)?/)
|
||||
if (match[3] !== undefined) { // HH:MM:SS
|
||||
const seconds = 3600 * Number(match[1]) + 60 * Number(match[2]) + Number(match[3])
|
||||
this.$emit('timestamp-event', seconds)
|
||||
} else { // MM:SS
|
||||
const seconds = 60 * Number(match[1]) + Number(match[2])
|
||||
this.$emit('timestamp-event', seconds)
|
||||
}
|
||||
catchTimestampClick: function (event) {
|
||||
this.$emit('timestamp-event', event.detail)
|
||||
},
|
||||
detectTimestamps: function (input) {
|
||||
return input.replaceAll(/(\d+(:\d+)+)/g, '<a href="#" onclick="this.dispatchEvent(new CustomEvent(\'timestamp-clicked\',{bubbles:true, detail:\'$1\'}))">$1</a>')
|
||||
const videoId = this.$route.params.id
|
||||
|
||||
return input.replaceAll(/(?:(\d+):)?(\d+):(\d+)/g, (timestamp, hours, minutes, seconds) => {
|
||||
let time = 60 * Number(minutes) + Number(seconds)
|
||||
|
||||
if (hours) {
|
||||
time += 3600 * Number(hours)
|
||||
}
|
||||
|
||||
const url = this.$router.resolve({
|
||||
path: `/watch/${videoId}`,
|
||||
query: {
|
||||
timestamp: time
|
||||
}
|
||||
}).href
|
||||
|
||||
// Adding the URL lets the user open the video in a new window at this timestamp
|
||||
return `<a href="${url}" onclick="event.preventDefault();this.dispatchEvent(new CustomEvent('timestamp-clicked',{bubbles:true,detail:${time}}));window.scrollTo(0,0)">${timestamp}</a>`
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import FtToastEvents from './ft-toast-events.js'
|
||||
|
||||
let id = 0
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtToast',
|
||||
data: function () {
|
||||
|
@ -15,7 +17,9 @@ export default defineComponent({
|
|||
FtToastEvents.removeEventListener('toast-open', this.open)
|
||||
},
|
||||
methods: {
|
||||
performAction: function (index) {
|
||||
performAction: function (id) {
|
||||
const index = this.toasts.findIndex(toast => id === toast.id)
|
||||
|
||||
this.toasts[index].action()
|
||||
this.remove(index)
|
||||
},
|
||||
|
@ -26,7 +30,13 @@ export default defineComponent({
|
|||
toast.isOpen = false
|
||||
},
|
||||
open: function ({ detail: { message, time, action } }) {
|
||||
const toast = { message: message, action: action || (() => { }), isOpen: false, timeout: null }
|
||||
const toast = {
|
||||
message: message,
|
||||
action: action || (() => { }),
|
||||
isOpen: false,
|
||||
timeout: null,
|
||||
id: id++
|
||||
}
|
||||
toast.timeout = setTimeout(this.close, time || 3000, toast)
|
||||
setTimeout(() => { toast.isOpen = true })
|
||||
if (this.toasts.length > 4) {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<div class="toast-holder">
|
||||
<div
|
||||
v-for="(toast, index) in toasts"
|
||||
:key="'toast-' + index"
|
||||
v-for="toast in toasts"
|
||||
:key="toast.id"
|
||||
class="toast"
|
||||
:class="{ closed: !toast.isOpen, open: toast.isOpen }"
|
||||
tabindex="0"
|
||||
role="status"
|
||||
@click="performAction(index)"
|
||||
@keydown.enter.prevent="performAction(index)"
|
||||
@keydown.space.prevent="performAction(index)"
|
||||
@click="performAction(toast.id)"
|
||||
@keydown.enter.prevent="performAction(toast.id)"
|
||||
@keydown.space.prevent="performAction(toast.id)"
|
||||
>
|
||||
<p class="message">
|
||||
{{ toast.message }}
|
||||
|
|
|
@ -6,10 +6,12 @@ import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
|||
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
|
||||
import FtInput from '../ft-input/ft-input.vue'
|
||||
import FtPrompt from '../ft-prompt/ft-prompt.vue'
|
||||
import FtButton from '../ft-button/ft-button.vue'
|
||||
import {
|
||||
formatNumber,
|
||||
showToast,
|
||||
} from '../../helpers/utils'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PlaylistInfo',
|
||||
|
@ -19,6 +21,7 @@ export default defineComponent({
|
|||
'ft-icon-button': FtIconButton,
|
||||
'ft-input': FtInput,
|
||||
'ft-prompt': FtPrompt,
|
||||
'ft-button': FtButton,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
|
@ -84,6 +87,9 @@ export default defineComponent({
|
|||
},
|
||||
data: function () {
|
||||
return {
|
||||
searchVideoMode: false,
|
||||
query: '',
|
||||
updateQueryDebounce: function() {},
|
||||
editMode: false,
|
||||
showDeletePlaylistPrompt: false,
|
||||
showRemoveVideosOnWatchPrompt: false,
|
||||
|
@ -257,6 +263,8 @@ export default defineComponent({
|
|||
created: function () {
|
||||
this.newTitle = this.title
|
||||
this.newDescription = this.description
|
||||
|
||||
this.updateQueryDebounce = debounce(this.updateQuery, 500)
|
||||
},
|
||||
methods: {
|
||||
toggleCopyVideosPrompt: function (force = false) {
|
||||
|
@ -398,6 +406,30 @@ export default defineComponent({
|
|||
showToast(this.$t('User Playlists.SinglePlaylistView.Toast.Quick bookmark disabled'))
|
||||
},
|
||||
|
||||
updateQuery(query) {
|
||||
this.query = query
|
||||
this.$emit('search-video-query-change', query)
|
||||
},
|
||||
enableVideoSearchMode() {
|
||||
this.searchVideoMode = true
|
||||
this.$emit('search-video-mode-on')
|
||||
|
||||
nextTick(() => {
|
||||
// Some elements only present after rendering update
|
||||
this.$refs.searchInput.focus()
|
||||
})
|
||||
},
|
||||
disableVideoSearchMode() {
|
||||
this.searchVideoMode = false
|
||||
this.updateQuery('')
|
||||
this.$emit('search-video-mode-off')
|
||||
|
||||
nextTick(() => {
|
||||
// Some elements only present after rendering update
|
||||
this.$refs.enableSearchModeButton?.focus()
|
||||
})
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'showAddToPlaylistPromptForManyVideos',
|
||||
'updatePlaylist',
|
||||
|
|
|
@ -73,6 +73,16 @@
|
|||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.searchInputsRow {
|
||||
margin-block-start: 8px;
|
||||
|
||||
display: grid;
|
||||
|
||||
/* 2 columns */
|
||||
grid-template-columns: 1fr auto;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1250px) {
|
||||
:deep(.sharePlaylistIcon .iconDropdown) {
|
||||
inset-inline-start: auto;
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
<hr>
|
||||
|
||||
<div
|
||||
v-if="!searchVideoMode"
|
||||
class="channelShareWrapper"
|
||||
>
|
||||
<router-link
|
||||
|
@ -115,6 +116,14 @@
|
|||
</div>
|
||||
|
||||
<div class="playlistOptions">
|
||||
<ft-icon-button
|
||||
v-if="isUserPlaylist && videoCount > 0 && !editMode"
|
||||
ref="enableSearchModeButton"
|
||||
:title="$t('User Playlists.SinglePlaylistView.Search for Videos')"
|
||||
:icon="['fas', 'search']"
|
||||
theme="secondary"
|
||||
@click="enableVideoSearchMode"
|
||||
/>
|
||||
<ft-icon-button
|
||||
v-if="editMode"
|
||||
:title="$t('User Playlists.Save Changes')"
|
||||
|
@ -196,6 +205,28 @@
|
|||
@click="handleRemoveVideosOnWatchPromptAnswer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isUserPlaylist && searchVideoMode"
|
||||
class="searchInputsRow"
|
||||
>
|
||||
<ft-input
|
||||
ref="searchInput"
|
||||
class="searchInput"
|
||||
:placeholder="$t('User Playlists.SinglePlaylistView.Search for Videos')"
|
||||
:show-clear-text-button="true"
|
||||
:show-action-button="false"
|
||||
@input="(input) => updateQueryDebounce(input)"
|
||||
@clear="updateQueryDebounce('')"
|
||||
/>
|
||||
<ft-icon-button
|
||||
v-if="isUserPlaylist && searchVideoMode"
|
||||
:title="$t('User Playlists.Cancel')"
|
||||
:icon="['fas', 'times']"
|
||||
theme="secondary"
|
||||
@click="disableVideoSearchMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -121,6 +121,8 @@ export default defineComponent({
|
|||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const subscriptionUpdates = []
|
||||
|
||||
const postListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let posts = []
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
|
@ -137,6 +139,32 @@ export default defineComponent({
|
|||
channelId: channel.id,
|
||||
posts: posts,
|
||||
})
|
||||
|
||||
if (posts.length > 0) {
|
||||
const post = posts.find(post => post.authorId === channel.id)
|
||||
|
||||
if (post) {
|
||||
const name = post.author
|
||||
let thumbnailUrl = post.authorThumbnails?.[0]?.url
|
||||
|
||||
if (name || thumbnailUrl) {
|
||||
if (thumbnailUrl) {
|
||||
if (thumbnailUrl.startsWith('//')) {
|
||||
thumbnailUrl = 'https:' + thumbnailUrl
|
||||
} else if (thumbnailUrl.startsWith(`${this.currentInvidiousInstance}/ggpht`)) {
|
||||
thumbnailUrl = thumbnailUrl.replace(`${this.currentInvidiousInstance}/ggpht`, 'https://yt3.googleusercontent.com')
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionUpdates.push({
|
||||
channelId: channel.id,
|
||||
channelName: name,
|
||||
channelThumbnailUrl: thumbnailUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return posts
|
||||
}))).flatMap((o) => o)
|
||||
postList.push(...postListFromRemote)
|
||||
|
@ -147,6 +175,8 @@ export default defineComponent({
|
|||
this.postList = postList
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
|
||||
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
|
||||
},
|
||||
|
||||
maybeLoadPostsForSubscriptionsFromRemote: async function () {
|
||||
|
@ -211,6 +241,7 @@ export default defineComponent({
|
|||
|
||||
...mapActions([
|
||||
'updateShowProgressBar',
|
||||
'batchUpdateSubscriptionDetails',
|
||||
'updateSubscriptionPostsCacheByChannel',
|
||||
]),
|
||||
|
||||
|
|
|
@ -133,19 +133,23 @@ export default defineComponent({
|
|||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const subscriptionUpdates = []
|
||||
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
let name, thumbnailUrl
|
||||
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelLiveInvidiousRSS(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelLiveInvidiousRSS(channel))
|
||||
} else {
|
||||
videos = await this.getChannelLiveInvidious(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelLiveInvidious(channel))
|
||||
}
|
||||
} else {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelLiveLocalRSS(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelLiveLocalRSS(channel))
|
||||
} else {
|
||||
videos = await this.getChannelLiveLocal(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelLiveLocal(channel))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +160,15 @@ export default defineComponent({
|
|||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
|
||||
if (name || thumbnailUrl) {
|
||||
subscriptionUpdates.push({
|
||||
channelId: channel.id,
|
||||
channelName: name,
|
||||
channelThumbnailUrl: thumbnailUrl
|
||||
})
|
||||
}
|
||||
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
@ -163,6 +176,8 @@ export default defineComponent({
|
|||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
|
||||
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
|
@ -178,16 +193,18 @@ export default defineComponent({
|
|||
|
||||
getChannelLiveLocal: async function (channel, failedAttempts = 0) {
|
||||
try {
|
||||
const entries = await getLocalChannelLiveStreams(channel.id)
|
||||
const result = await getLocalChannelLiveStreams(channel.id)
|
||||
|
||||
if (entries === null) {
|
||||
if (result === null) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
addPublishedDatesLocal(entries)
|
||||
addPublishedDatesLocal(result.videos)
|
||||
|
||||
return entries
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
|
@ -202,12 +219,16 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return await this.getChannelLiveInvidious(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return await this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -231,7 +252,9 @@ export default defineComponent({
|
|||
this.errorChannels.push(channel)
|
||||
}
|
||||
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
|
@ -249,12 +272,16 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelLiveLocal(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -273,7 +300,16 @@ export default defineComponent({
|
|||
|
||||
addPublishedDatesInvidious(videos)
|
||||
|
||||
resolve(videos)
|
||||
let name
|
||||
|
||||
if (videos.length > 0) {
|
||||
name = videos.find(video => video.author).author
|
||||
}
|
||||
|
||||
resolve({
|
||||
name,
|
||||
videos
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
|
@ -289,14 +325,18 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Local API'))
|
||||
resolve(this.getChannelLiveLocal(channel, failedAttempts + 1))
|
||||
} else {
|
||||
resolve([])
|
||||
resolve({
|
||||
videos: []
|
||||
})
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
resolve(this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
default:
|
||||
resolve([])
|
||||
resolve({
|
||||
videos: []
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -310,7 +350,9 @@ export default defineComponent({
|
|||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 500 || response.status === 404) {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
|
@ -328,17 +370,22 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Local API'))
|
||||
return this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelLiveInvidious(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'batchUpdateSubscriptionDetails',
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionLiveCacheByChannel',
|
||||
]),
|
||||
|
|
|
@ -118,12 +118,16 @@ export default defineComponent({
|
|||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const subscriptionUpdates = []
|
||||
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
let name
|
||||
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
videos = await this.getChannelShortsInvidious(channel)
|
||||
({ videos, name } = await this.getChannelShortsInvidious(channel))
|
||||
} else {
|
||||
videos = await this.getChannelShortsLocal(channel)
|
||||
({ videos, name } = await this.getChannelShortsLocal(channel))
|
||||
}
|
||||
|
||||
channelCount++
|
||||
|
@ -133,6 +137,14 @@ export default defineComponent({
|
|||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
|
||||
if (name) {
|
||||
subscriptionUpdates.push({
|
||||
channelId: channel.id,
|
||||
channelName: name
|
||||
})
|
||||
}
|
||||
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
@ -140,6 +152,8 @@ export default defineComponent({
|
|||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
|
||||
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
|
@ -172,7 +186,9 @@ export default defineComponent({
|
|||
this.errorChannels.push(channel)
|
||||
}
|
||||
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
|
@ -188,10 +204,14 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelShortsInvidious(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -204,7 +224,9 @@ export default defineComponent({
|
|||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 500 || response.status === 404) {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
|
@ -220,15 +242,20 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Local API'))
|
||||
return this.getChannelShortsLocal(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'batchUpdateSubscriptionDetails',
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionShortsCacheByChannel',
|
||||
]),
|
||||
|
|
|
@ -133,19 +133,23 @@ export default defineComponent({
|
|||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const subscriptionUpdates = []
|
||||
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
let name, thumbnailUrl
|
||||
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelVideosInvidiousRSS(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelVideosInvidiousRSS(channel))
|
||||
} else {
|
||||
videos = await this.getChannelVideosInvidiousScraper(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelVideosInvidiousScraper(channel))
|
||||
}
|
||||
} else {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelVideosLocalRSS(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelVideosLocalRSS(channel))
|
||||
} else {
|
||||
videos = await this.getChannelVideosLocalScraper(channel)
|
||||
({ videos, name, thumbnailUrl } = await this.getChannelVideosLocalScraper(channel))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +160,15 @@ export default defineComponent({
|
|||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
|
||||
if (name || thumbnailUrl) {
|
||||
subscriptionUpdates.push({
|
||||
channelId: channel.id,
|
||||
channelName: name,
|
||||
channelThumbnailUrl: thumbnailUrl
|
||||
})
|
||||
}
|
||||
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
@ -163,6 +176,8 @@ export default defineComponent({
|
|||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
|
||||
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
|
@ -178,16 +193,18 @@ export default defineComponent({
|
|||
|
||||
getChannelVideosLocalScraper: async function (channel, failedAttempts = 0) {
|
||||
try {
|
||||
const videos = await getLocalChannelVideos(channel.id)
|
||||
const result = await getLocalChannelVideos(channel.id)
|
||||
|
||||
if (videos === null) {
|
||||
if (result === null) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
addPublishedDatesLocal(videos)
|
||||
addPublishedDatesLocal(result.videos)
|
||||
|
||||
return videos
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
|
@ -202,12 +219,16 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return await this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -231,7 +252,9 @@ export default defineComponent({
|
|||
this.errorChannels.push(channel)
|
||||
}
|
||||
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
|
@ -249,12 +272,16 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -270,7 +297,16 @@ export default defineComponent({
|
|||
invidiousAPICall(subscriptionsPayload).then((result) => {
|
||||
addPublishedDatesInvidious(result.videos)
|
||||
|
||||
resolve(result.videos)
|
||||
let name
|
||||
|
||||
if (result.videos.length > 0) {
|
||||
name = result.videos.find(video => video.type === 'video' && video.author).author
|
||||
}
|
||||
|
||||
resolve({
|
||||
name,
|
||||
videos: result.videos
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
|
@ -286,14 +322,18 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Local API'))
|
||||
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
|
||||
} else {
|
||||
resolve([])
|
||||
resolve({
|
||||
videos: []
|
||||
})
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
default:
|
||||
resolve([])
|
||||
resolve({
|
||||
videos: []
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -308,7 +348,9 @@ export default defineComponent({
|
|||
|
||||
if (response.status === 500 || response.status === 404) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
|
@ -326,17 +368,22 @@ export default defineComponent({
|
|||
showToast(this.$t('Falling back to Local API'))
|
||||
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'batchUpdateSubscriptionDetails',
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionVideosCacheByChannel',
|
||||
]),
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
.chapterThumbnail {
|
||||
grid-area: thumbnail;
|
||||
inline-size: 130px;
|
||||
block-size: auto;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ export default defineComponent({
|
|||
changeChapter: function(index) {
|
||||
this.currentIndex = index
|
||||
this.$emit('timestamp-event', this.chapters[index].startSeconds)
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
|
||||
navigateChapters(direction) {
|
||||
|
|
|
@ -46,13 +46,15 @@
|
|||
@keydown.space.stop.prevent="changeChapter(index)"
|
||||
@keydown.enter.stop.prevent="changeChapter(index)"
|
||||
>
|
||||
<!-- Setting the aspect ratio avoids layout shifts when the images load -->
|
||||
<img
|
||||
v-if="!compact"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="chapterThumbnail"
|
||||
loading="lazy"
|
||||
:src="chapter.thumbnail"
|
||||
:src="chapter.thumbnail.url"
|
||||
:style="{ aspectRatio: chapter.thumbnail.width / chapter.thumbnail.height }"
|
||||
>
|
||||
<div class="chapterTimestamp">
|
||||
{{ chapter.timestamp }}
|
||||
|
|
|
@ -64,6 +64,19 @@
|
|||
inset-inline-end: calc(50% - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 460px) {
|
||||
flex-wrap: nowrap;
|
||||
:deep(.iconDropdown) {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-block-start: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,29 +85,43 @@
|
|||
color: var(--tertiary-text-color);
|
||||
|
||||
.likeSection {
|
||||
color: var(--tertiary-text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-inline-start: auto;
|
||||
margin-block-start: 4px;
|
||||
max-inline-size: 210px;
|
||||
text-align: end;
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
margin-inline-start: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.likeBar {
|
||||
border-radius: 4px;
|
||||
block-size: 8px;
|
||||
margin-block-end: 4px;
|
||||
}
|
||||
|
||||
.likeCount {
|
||||
margin-inline-end: 0;
|
||||
color: var(--tertiary-text-color);
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
flex-direction: column;
|
||||
margin-inline-start: auto;
|
||||
margin-block-start: 4px;
|
||||
max-inline-size: 210px;
|
||||
text-align: end;
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
margin-inline-start: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.likeBar {
|
||||
border-radius: 4px;
|
||||
block-size: 8px;
|
||||
margin-block-end: 4px;
|
||||
}
|
||||
|
||||
.likeCount {
|
||||
margin-inline-end: 0;
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
}
|
||||
}
|
||||
.datePublishedAndViewCount {
|
||||
@media only screen and (max-width: 460px) {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
flex-direction: column;
|
||||
|
||||
.seperator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.videoViews {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,12 @@
|
|||
</div>
|
||||
<div class="videoMetrics">
|
||||
<div class="datePublishedAndViewCount">
|
||||
{{ publishedString }} {{ dateString }} • {{ parsedViewCount }}
|
||||
{{ publishedString }} {{ dateString }}
|
||||
<template
|
||||
v-if="!hideVideoViews"
|
||||
>
|
||||
<span class="seperator">• </span><span class="videoViews">{{ parsedViewCount }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideVideoLikesAndDislikes"
|
||||
|
|
|
@ -35,23 +35,23 @@ const TRACKING_PARAM_NAMES = [
|
|||
* @param {boolean} options.generateSessionLocally generate the session locally or let YouTube generate it (local is faster, remote is more accurate)
|
||||
* @returns the Innertube instance
|
||||
*/
|
||||
async function createInnertube(options = { withPlayer: false, location: undefined, safetyMode: false, clientType: undefined, generateSessionLocally: true }) {
|
||||
async function createInnertube({ withPlayer = false, location = undefined, safetyMode = false, clientType = undefined, generateSessionLocally = true } = {}) {
|
||||
let cache
|
||||
if (options.withPlayer) {
|
||||
if (withPlayer) {
|
||||
const userData = await getUserDataPath()
|
||||
cache = new PlayerCache(join(userData, 'player_cache'))
|
||||
}
|
||||
|
||||
return await Innertube.create({
|
||||
retrieve_player: !!options.withPlayer,
|
||||
location: options.location,
|
||||
enable_safety_mode: !!options.safetyMode,
|
||||
client_type: options.clientType,
|
||||
retrieve_player: !!withPlayer,
|
||||
location: location,
|
||||
enable_safety_mode: !!safetyMode,
|
||||
client_type: clientType,
|
||||
|
||||
// use browser fetch
|
||||
fetch: (input, init) => fetch(input, init),
|
||||
cache,
|
||||
generate_session_locally: !!options.generateSessionLocally
|
||||
generate_session_locally: !!generateSessionLocally
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -274,6 +274,9 @@ export async function getLocalChannel(id) {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
*/
|
||||
export async function getLocalChannelVideos(id) {
|
||||
const innertube = await createInnertube()
|
||||
|
||||
|
@ -286,15 +289,22 @@ export async function getLocalChannelVideos(id) {
|
|||
}))
|
||||
|
||||
const videosTab = new YT.Channel(null, response)
|
||||
const { id: channelId = id, name, thumbnailUrl } = parseLocalChannelHeader(videosTab)
|
||||
|
||||
let videos
|
||||
|
||||
// if the channel doesn't have a videos tab, YouTube returns the home tab instead
|
||||
// so we need to check that we got the right tab
|
||||
if (videosTab.current_tab?.endpoint.metadata.url?.endsWith('/videos')) {
|
||||
const { id: channelId = id, name } = parseLocalChannelHeader(videosTab)
|
||||
|
||||
return parseLocalChannelVideos(videosTab.videos, channelId, name)
|
||||
videos = parseLocalChannelVideos(videosTab.videos, channelId, name)
|
||||
} else {
|
||||
return []
|
||||
videos = []
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
thumbnailUrl,
|
||||
videos
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -306,6 +316,9 @@ export async function getLocalChannelVideos(id) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
*/
|
||||
export async function getLocalChannelLiveStreams(id) {
|
||||
const innertube = await createInnertube()
|
||||
|
||||
|
@ -318,15 +331,22 @@ export async function getLocalChannelLiveStreams(id) {
|
|||
}))
|
||||
|
||||
const liveStreamsTab = new YT.Channel(null, response)
|
||||
const { id: channelId = id, name, thumbnailUrl } = parseLocalChannelHeader(liveStreamsTab)
|
||||
|
||||
let videos
|
||||
|
||||
// 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')) {
|
||||
const { id: channelId = id, name } = parseLocalChannelHeader(liveStreamsTab)
|
||||
|
||||
return parseLocalChannelVideos(liveStreamsTab.videos, channelId, name)
|
||||
videos = parseLocalChannelVideos(liveStreamsTab.videos, channelId, name)
|
||||
} else {
|
||||
return []
|
||||
videos = []
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
thumbnailUrl,
|
||||
videos
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -539,7 +559,7 @@ export function parseLocalChannelShorts(shorts, channelId, channelName) {
|
|||
title: short.title.text,
|
||||
author: channelName,
|
||||
authorId: channelId,
|
||||
viewCount: parseLocalSubscriberCount(short.views.text),
|
||||
viewCount: short.views.isEmpty() ? null : parseLocalSubscriberCount(short.views.text),
|
||||
lengthSeconds: ''
|
||||
}
|
||||
})
|
||||
|
|
|
@ -80,9 +80,14 @@ export async function parseYouTubeRSSFeed(rssString, channelId) {
|
|||
promises.push(parseRSSEntry(entry, channelId, channelName))
|
||||
}
|
||||
|
||||
return await Promise.all(promises)
|
||||
return {
|
||||
name: channelName,
|
||||
videos: await Promise.all(promises)
|
||||
}
|
||||
} catch (e) {
|
||||
return []
|
||||
return {
|
||||
videos: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,43 @@ const actions = {
|
|||
commit('setProfileList', profiles)
|
||||
},
|
||||
|
||||
async batchUpdateSubscriptionDetails({ getters, dispatch }, channels) {
|
||||
if (channels.length === 0) { return }
|
||||
|
||||
const profileList = getters.getProfileList
|
||||
|
||||
for (const profile of profileList) {
|
||||
const currentProfileCopy = deepCopy(profile)
|
||||
let profileUpdated = false
|
||||
|
||||
for (const { channelThumbnailUrl, channelName, channelId } of channels) {
|
||||
const channel = currentProfileCopy.subscriptions.find((channel) => {
|
||||
return channel.id === channelId
|
||||
}) ?? null
|
||||
|
||||
if (channel === null) { continue }
|
||||
|
||||
if (channel.name !== channelName && channelName != null) {
|
||||
channel.name = channelName
|
||||
profileUpdated = true
|
||||
}
|
||||
|
||||
if (channelThumbnailUrl) {
|
||||
const thumbnail = channelThumbnailUrl.replace(/=s\d*/, '=s176') // change thumbnail size if different
|
||||
|
||||
if (channel.thumbnail !== thumbnail) {
|
||||
channel.thumbnail = thumbnail
|
||||
profileUpdated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (profileUpdated) {
|
||||
await dispatch('updateProfile', currentProfileCopy)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async updateSubscriptionDetails({ getters, dispatch }, { channelThumbnailUrl, channelName, channelId }) {
|
||||
const thumbnail = channelThumbnailUrl?.replace(/=s\d*/, '=s176') ?? null // change thumbnail size if different
|
||||
const profileList = getters.getProfileList
|
||||
|
|
|
@ -54,6 +54,10 @@ const actions = {
|
|||
commit('updateShortsCacheByChannel', payload)
|
||||
},
|
||||
|
||||
updateSubscriptionShortsCacheWithChannelPageShorts: ({ commit }, payload) => {
|
||||
commit('updateShortsCacheWithChannelPageShorts', payload)
|
||||
},
|
||||
|
||||
updateSubscriptionLiveCacheByChannel: ({ commit }, payload) => {
|
||||
commit('updateLiveCacheByChannel', payload)
|
||||
},
|
||||
|
@ -86,6 +90,31 @@ const mutations = {
|
|||
if (videos != null) { newObject.videos = videos }
|
||||
state.shortsCache[channelId] = newObject
|
||||
},
|
||||
updateShortsCacheWithChannelPageShorts(state, { channelId, videos }) {
|
||||
const cachedObject = state.shortsCache[channelId]
|
||||
|
||||
if (cachedObject && cachedObject.videos.length > 0) {
|
||||
cachedObject.videos.forEach(cachedVideo => {
|
||||
const channelVideo = videos.find(short => cachedVideo.videoId === short.videoId)
|
||||
|
||||
if (channelVideo) {
|
||||
// authorId probably never changes, so we don't need to update that
|
||||
|
||||
cachedVideo.title = channelVideo.title
|
||||
cachedVideo.author = channelVideo.author
|
||||
|
||||
// as the channel shorts page only has compact view counts for numbers above 1000 e.g. 12k
|
||||
// and the RSS feeds include an exact value, we only want to overwrite it when the number is larger than the cached value
|
||||
// 12345 vs 12000 => 12345
|
||||
// 12345 vs 15000 => 15000
|
||||
|
||||
if (channelVideo.viewCount > cachedVideo.viewCount) {
|
||||
cachedVideo.viewCount = channelVideo.viewCount
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
clearShortsCache(state) {
|
||||
state.shortsCache = {}
|
||||
},
|
||||
|
|
|
@ -33,6 +33,10 @@ import {
|
|||
parseLocalListVideo,
|
||||
parseLocalSubscriberCount
|
||||
} from '../../helpers/api/local'
|
||||
import {
|
||||
addPublishedDatesInvidious,
|
||||
addPublishedDatesLocal
|
||||
} from '../../helpers/subscriptions'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Channel',
|
||||
|
@ -175,6 +179,13 @@ export default defineComponent({
|
|||
return this.subscriptionInfo !== null
|
||||
},
|
||||
|
||||
isSubscribedInAnyProfile: function () {
|
||||
const profileList = this.$store.getters.getProfileList
|
||||
|
||||
// check the all channels profile
|
||||
return profileList[0].subscriptions.some((channel) => channel.id === this.id)
|
||||
},
|
||||
|
||||
videoLiveSelectNames: function () {
|
||||
return [
|
||||
this.$t('Channel.Videos.Sort Types.Newest'),
|
||||
|
@ -771,6 +782,17 @@ export default defineComponent({
|
|||
this.latestVideos = parseLocalChannelVideos(videosTab.videos, this.id, this.channelName)
|
||||
this.videoContinuationData = videosTab.has_continuation ? videosTab : null
|
||||
this.isElementListLoading = false
|
||||
|
||||
if (this.isSubscribedInAnyProfile && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
|
||||
addPublishedDatesLocal(this.latestVideos)
|
||||
this.updateSubscriptionVideosCacheByChannel({
|
||||
channelId: this.id,
|
||||
// create a copy so that we only cache the first page
|
||||
// if we use the same array, the store will get angry at us for modifying it outside of the store,
|
||||
// when the user clicks load more
|
||||
videos: [...this.latestVideos]
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
|
@ -829,6 +851,16 @@ export default defineComponent({
|
|||
this.latestShorts = parseLocalChannelShorts(shortsTab.videos, this.id, this.channelName)
|
||||
this.shortContinuationData = shortsTab.has_continuation ? shortsTab : null
|
||||
this.isElementListLoading = false
|
||||
|
||||
if (this.isSubscribedInAnyProfile && this.latestShorts.length > 0 && this.shortSortBy === 'newest') {
|
||||
// As the shorts tab API response doesn't include the published dates,
|
||||
// we can't just write the results to the subscriptions cache like we do with videos and live (can't sort chronologically without the date).
|
||||
// However we can still update the metadata in the cache such as the view count and title that might have changed since it was cached
|
||||
this.updateSubscriptionShortsCacheWithChannelPageShorts({
|
||||
channelId: this.id,
|
||||
videos: this.latestShorts
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
|
@ -887,6 +919,17 @@ export default defineComponent({
|
|||
this.latestLive = parseLocalChannelVideos(liveTab.videos, this.id, this.channelName)
|
||||
this.liveContinuationData = liveTab.has_continuation ? liveTab : null
|
||||
this.isElementListLoading = false
|
||||
|
||||
if (this.isSubscribedInAnyProfile && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
|
||||
addPublishedDatesLocal(this.latestLive)
|
||||
this.updateSubscriptionLiveCacheByChannel({
|
||||
channelId: this.id,
|
||||
// create a copy so that we only cache the first page
|
||||
// if we use the same array, the store will get angry at us for modifying it outside of the store,
|
||||
// when the user clicks load more
|
||||
videos: [...this.latestLive]
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
|
@ -1056,6 +1099,16 @@ export default defineComponent({
|
|||
}
|
||||
this.videoContinuationData = response.continuation || null
|
||||
this.isElementListLoading = false
|
||||
|
||||
if (this.isSubscribedInAnyProfile && !more && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
|
||||
addPublishedDatesInvidious(this.latestVideos)
|
||||
this.updateSubscriptionVideosCacheByChannel({
|
||||
channelId: this.id,
|
||||
// create a copy so that we only cache the first page
|
||||
// if we use the same array, it will also contain all the next pages
|
||||
videos: [...this.latestVideos]
|
||||
})
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
|
@ -1105,6 +1158,17 @@ export default defineComponent({
|
|||
}
|
||||
this.shortContinuationData = response.continuation || null
|
||||
this.isElementListLoading = false
|
||||
|
||||
if (this.isSubscribedInAnyProfile && !more && this.latestShorts.length > 0 && this.shortSortBy === 'newest') {
|
||||
// As the shorts tab API response doesn't include the published dates,
|
||||
// we can't just write the results to the subscriptions cache like we do with videos and live (can't sort chronologically without the date).
|
||||
// However we can still update the metadata in the cache e.g. adding the duration, as that isn't included in the RSS feeds
|
||||
// and updating the view count and title that might have changed since it was cached
|
||||
this.updateSubscriptionShortsCacheWithChannelPageShorts({
|
||||
channelId: this.id,
|
||||
videos: this.latestShorts
|
||||
})
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
|
@ -1146,6 +1210,17 @@ export default defineComponent({
|
|||
}
|
||||
this.liveContinuationData = response.continuation || null
|
||||
this.isElementListLoading = false
|
||||
|
||||
if (this.isSubscribedInAnyProfile && !more && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
|
||||
addPublishedDatesInvidious(this.latestLive)
|
||||
this.updateSubscriptionLiveCacheByChannel({
|
||||
channelId: this.id,
|
||||
// create a copy so that we only cache the first page
|
||||
// if we use the same array, the store will get angry at us for modifying it outside of the store,
|
||||
// when the user clicks load more
|
||||
videos: [...this.latestLive]
|
||||
})
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
|
@ -1448,7 +1523,7 @@ export default defineComponent({
|
|||
})
|
||||
if (this.backendPreference === 'local' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
this.getChannelPodcastsInvidious()
|
||||
this.channelInvidiousPodcasts()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
|
@ -1563,6 +1638,19 @@ export default defineComponent({
|
|||
|
||||
this.latestCommunityPosts = parseLocalCommunityPosts(posts)
|
||||
this.communityContinuationData = communityTab.has_continuation ? communityTab : null
|
||||
|
||||
if (this.latestCommunityPosts.length > 0) {
|
||||
this.latestCommunityPosts.forEach(post => {
|
||||
post.authorId = this.id
|
||||
})
|
||||
this.updateSubscriptionPostsCacheByChannel({
|
||||
channelId: this.id,
|
||||
// create a copy so that we only cache the first page
|
||||
// if we use the same array, the store will get angry at us for modifying it outside of the store,
|
||||
// when the user clicks load more
|
||||
posts: [...this.latestCommunityPosts]
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
|
@ -1614,6 +1702,19 @@ export default defineComponent({
|
|||
this.latestCommunityPosts = posts
|
||||
}
|
||||
this.communityContinuationData = continuation
|
||||
|
||||
if (this.isSubscribedInAnyProfile && !more && this.latestCommunityPosts.length > 0) {
|
||||
this.latestCommunityPosts.forEach(post => {
|
||||
post.authorId = this.id
|
||||
})
|
||||
this.updateSubscriptionPostsCacheByChannel({
|
||||
channelId: this.id,
|
||||
// create a copy so that we only cache the first page
|
||||
// if we use the same array, the store will get angry at us for modifying it outside of the store,
|
||||
// when the user clicks load more
|
||||
posts: [...this.latestCommunityPosts]
|
||||
})
|
||||
}
|
||||
}).catch(async (err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
|
@ -1853,7 +1954,11 @@ export default defineComponent({
|
|||
|
||||
...mapActions([
|
||||
'showOutlines',
|
||||
'updateSubscriptionDetails'
|
||||
'updateSubscriptionDetails',
|
||||
'updateSubscriptionVideosCacheByChannel',
|
||||
'updateSubscriptionLiveCacheByChannel',
|
||||
'updateSubscriptionShortsCacheWithChannelPageShorts',
|
||||
'updateSubscriptionPostsCacheByChannel'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -60,6 +60,9 @@ export default defineComponent({
|
|||
getPlaylistInfoDebounce: function() {},
|
||||
playlistInEditMode: false,
|
||||
|
||||
playlistInVideoSearchMode: false,
|
||||
videoSearchQuery: '',
|
||||
|
||||
promptOpen: false,
|
||||
}
|
||||
},
|
||||
|
@ -107,7 +110,7 @@ export default defineComponent({
|
|||
|
||||
moreVideoDataAvailable() {
|
||||
if (this.isUserPlaylistRequested) {
|
||||
return this.userPlaylistVisibleLimit < this.videoCount
|
||||
return this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length
|
||||
} else {
|
||||
return this.continuationData !== null
|
||||
}
|
||||
|
@ -126,17 +129,29 @@ export default defineComponent({
|
|||
return this.selectedUserPlaylist?._id !== this.quickBookmarkPlaylistId
|
||||
},
|
||||
|
||||
sometimesFilteredUserPlaylistItems() {
|
||||
if (!this.isUserPlaylistRequested) { return this.playlistItems }
|
||||
if (this.processedVideoSearchQuery === '') { return this.playlistItems }
|
||||
|
||||
return this.playlistItems.filter((v) => {
|
||||
return v.title.toLowerCase().includes(this.processedVideoSearchQuery)
|
||||
})
|
||||
},
|
||||
visiblePlaylistItems: function () {
|
||||
if (!this.isUserPlaylistRequested) {
|
||||
// No filtering for non user playlists yet
|
||||
return this.playlistItems
|
||||
}
|
||||
|
||||
if (this.userPlaylistVisibleLimit < this.videoCount) {
|
||||
return this.playlistItems.slice(0, this.userPlaylistVisibleLimit)
|
||||
if (this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length) {
|
||||
return this.sometimesFilteredUserPlaylistItems.slice(0, this.userPlaylistVisibleLimit)
|
||||
} else {
|
||||
return this.playlistItems
|
||||
return this.sometimesFilteredUserPlaylistItems
|
||||
}
|
||||
}
|
||||
},
|
||||
processedVideoSearchQuery() {
|
||||
return this.videoSearchQuery.trim().toLowerCase()
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route () {
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
}"
|
||||
@enter-edit-mode="playlistInEditMode = true"
|
||||
@exit-edit-mode="playlistInEditMode = false"
|
||||
@search-video-mode-on="playlistInVideoSearchMode = true"
|
||||
@search-video-mode-off="playlistInVideoSearchMode = false"
|
||||
@search-video-query-change="(v) => videoSearchQuery = v"
|
||||
@prompt-open="promptOpen = true"
|
||||
@prompt-close="promptOpen = false"
|
||||
/>
|
||||
|
@ -39,48 +42,59 @@
|
|||
<template
|
||||
v-if="playlistItems.length > 0"
|
||||
>
|
||||
<transition-group
|
||||
name="playlistItem"
|
||||
tag="span"
|
||||
<template
|
||||
v-if="visiblePlaylistItems.length > 0"
|
||||
>
|
||||
<ft-list-video-numbered
|
||||
v-for="(item, index) in visiblePlaylistItems"
|
||||
:key="`${item.videoId}-${item.playlistItemId || index}`"
|
||||
class="playlistItem"
|
||||
:data="item"
|
||||
:playlist-id="playlistId"
|
||||
:playlist-type="infoSource"
|
||||
:playlist-index="index"
|
||||
:playlist-item-id="item.playlistItemId"
|
||||
appearance="result"
|
||||
:always-show-add-to-playlist-button="true"
|
||||
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
|
||||
:can-move-video-up="index > 0"
|
||||
:can-move-video-down="index < visiblePlaylistItems.length - 1"
|
||||
:can-remove-from-playlist="true"
|
||||
:video-index="index"
|
||||
:initial-visible-state="index < 10"
|
||||
@move-video-up="moveVideoUp(item.videoId, item.playlistItemId)"
|
||||
@move-video-down="moveVideoDown(item.videoId, item.playlistItemId)"
|
||||
@remove-from-playlist="removeVideoFromPlaylist(item.videoId, item.playlistItemId)"
|
||||
/>
|
||||
</transition-group>
|
||||
<transition-group
|
||||
name="playlistItem"
|
||||
tag="span"
|
||||
>
|
||||
<ft-list-video-numbered
|
||||
v-for="(item, index) in visiblePlaylistItems"
|
||||
:key="`${item.videoId}-${item.playlistItemId || index}`"
|
||||
class="playlistItem"
|
||||
:data="item"
|
||||
:playlist-id="playlistId"
|
||||
:playlist-type="infoSource"
|
||||
:playlist-index="playlistInVideoSearchMode ? playlistItems.findIndex(i => i === item) : index"
|
||||
:playlist-item-id="item.playlistItemId"
|
||||
appearance="result"
|
||||
:always-show-add-to-playlist-button="true"
|
||||
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
|
||||
:can-move-video-up="index > 0 && !playlistInVideoSearchMode"
|
||||
:can-move-video-down="index < playlistItems.length - 1 && !playlistInVideoSearchMode"
|
||||
:can-remove-from-playlist="true"
|
||||
:video-index="playlistInVideoSearchMode ? playlistItems.findIndex(i => i === item) : index"
|
||||
:initial-visible-state="index < 10"
|
||||
@move-video-up="moveVideoUp(item.videoId, item.playlistItemId)"
|
||||
@move-video-down="moveVideoDown(item.videoId, item.playlistItemId)"
|
||||
@remove-from-playlist="removeVideoFromPlaylist(item.videoId, item.playlistItemId)"
|
||||
/>
|
||||
</transition-group>
|
||||
<ft-flex-box
|
||||
v-if="moreVideoDataAvailable && !isLoadingMore"
|
||||
>
|
||||
<ft-button
|
||||
:label="$t('Subscriptions.Load More Videos')"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="getNextPage"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<div
|
||||
v-if="isLoadingMore"
|
||||
class="loadNextPageWrapper"
|
||||
>
|
||||
<ft-loader />
|
||||
</div>
|
||||
</template>
|
||||
<ft-flex-box
|
||||
v-if="moreVideoDataAvailable && !isLoadingMore"
|
||||
v-else
|
||||
>
|
||||
<ft-button
|
||||
:label="$t('Subscriptions.Load More Videos')"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="getNextPage"
|
||||
/>
|
||||
<p class="message">
|
||||
{{ $t("User Playlists['Empty Search Message']") }}
|
||||
</p>
|
||||
</ft-flex-box>
|
||||
<div
|
||||
v-if="isLoadingMore"
|
||||
class="loadNextPageWrapper"
|
||||
>
|
||||
<ft-loader />
|
||||
</div>
|
||||
</template>
|
||||
<ft-flex-box
|
||||
v-else
|
||||
|
|
|
@ -454,7 +454,7 @@ export default defineComponent({
|
|||
timestamp: formatDurationAsTimestamp(start),
|
||||
startSeconds: start,
|
||||
endSeconds: 0,
|
||||
thumbnail: chapter.thumbnail[0].url
|
||||
thumbnail: chapter.thumbnail[0]
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Put the name of your locale in the same language
|
||||
Locale Name: 'الإنجليزية (الولايات المتحدة)'
|
||||
Locale Name: 'العربية'
|
||||
FreeTube: 'فري تيوب'
|
||||
# Currently on Subscriptions, Playlists, and History
|
||||
'This part of the app is not ready yet. Come back later when progress has been made.': >-
|
||||
|
@ -173,6 +173,7 @@ User Playlists:
|
|||
Reverted to use {oldPlaylistName} for quick bookmark: تمت العودة لاستخدام {oldPlaylistName}
|
||||
للإشارة المرجعية السريعة
|
||||
Quick bookmark disabled: تم تعطيل الإشارة المرجعية السريعة
|
||||
Search for Videos: البحث عن مقاطع الفيديو
|
||||
AddVideoPrompt:
|
||||
Select a playlist to add your N videos to: حدد قائمة تشغيل لإضافة الفيديو الخاص
|
||||
بك إلى | حدد قائمة تشغيل لإضافة مقاطع الفيديو {videoCount} إليها
|
||||
|
@ -186,6 +187,7 @@ User Playlists:
|
|||
Save: حفظ
|
||||
Search in Playlists: البحث في قوائم التشغيل
|
||||
N playlists selected: تم تحديد {playlistCount}
|
||||
Added {count} Times: تمت إضافة {count} الوقت | تمت إضافة {count} مرة
|
||||
CreatePlaylistPrompt:
|
||||
Toast:
|
||||
There is already a playlist with this name. Please pick a different name.: توجد
|
||||
|
|
|
@ -146,6 +146,7 @@ User Playlists:
|
|||
Select a playlist to add your N videos to: Vyberte playlist, do kterého přidat
|
||||
vaše video | Vyberte playlist, do kterého přidat vašich {videoCount} videí
|
||||
N playlists selected: Vybráno {playlistCount}
|
||||
Added {count} Times: Přidáno {count}krát | Přidáno {count}krát
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Nejsou zde žádná videa k odstranění.
|
||||
|
@ -175,6 +176,7 @@ User Playlists:
|
|||
This playlist is now used for quick bookmark: Tento playlist bude nyní použit
|
||||
pro rychlé uložení
|
||||
Quick bookmark disabled: Rychlé uložení vypnuto
|
||||
Search for Videos: Hledat videa
|
||||
Are you sure you want to delete this playlist? This cannot be undone: Opravdu chcete
|
||||
odstranit tento playlist? Tato akce je nevratná.
|
||||
Sort By:
|
||||
|
@ -234,7 +236,7 @@ Settings:
|
|||
General Settings:
|
||||
General Settings: 'Obecné nastavení'
|
||||
Check for Updates: 'Kontrolovat aktualizace'
|
||||
Check for Latest Blog Posts: 'Kontrolovat nejnovější příspěvky blogů'
|
||||
Check for Latest Blog Posts: 'Kontrolovat nejnovější příspěvky na blogu'
|
||||
Fallback to Non-Preferred Backend on Failure: 'Při chybě přepnout na nepreferovaný
|
||||
backend'
|
||||
Enable Search Suggestions: 'Zapnout návrhy vyhledávání'
|
||||
|
|
|
@ -187,6 +187,8 @@ User Playlists:
|
|||
EarliestPlayedFirst: 'Earliest Played'
|
||||
|
||||
SinglePlaylistView:
|
||||
Search for Videos: Search for Videos
|
||||
|
||||
Toast:
|
||||
This video cannot be moved up.: This video cannot be moved up.
|
||||
This video cannot be moved down.: This video cannot be moved down.
|
||||
|
@ -214,6 +216,8 @@ User Playlists:
|
|||
Search in Playlists: Search in Playlists
|
||||
Save: Save
|
||||
|
||||
Added {count} Times: 'Added {count} Time | Added {count} Times'
|
||||
|
||||
Toast:
|
||||
You haven't selected any playlist yet.: You haven't selected any playlist yet.
|
||||
"{videoCount} video(s) added to 1 playlist": "1 video added to 1 playlist | {videoCount} videos added to 1 playlist"
|
||||
|
|
|
@ -121,14 +121,14 @@ Playlists: 'Playlists'
|
|||
User Playlists:
|
||||
Your Playlists: 'Your playlists'
|
||||
Playlist Message: This page is not reflective of fully working playlists. It only
|
||||
lists videos that you have saved or favourited. When the work has finished, all
|
||||
videos currently here will be migrated to a ‘Favourites’ playlist.
|
||||
lists videos that you have saved or made a Favourite. When the work has finished,
|
||||
all videos currently here will be migrated to a ‘Favourites’ playlist.
|
||||
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
|
||||
Search bar placeholder: Search in playlist
|
||||
Empty Search Message: There are no videos in this playlist that match your search
|
||||
Create New Playlist: Create new playlist
|
||||
Create New Playlist: Create new Playlist
|
||||
Add to Playlist: Add to playlist
|
||||
This playlist currently has no videos.: This playlist currently has no videos.
|
||||
Move Video Down: Move video down
|
||||
|
@ -185,11 +185,21 @@ User Playlists:
|
|||
"{videoCount} video(s) added to 1 playlist": 1 video added to 1 playlist | {videoCount}
|
||||
videos added to 1 playlist
|
||||
You haven't selected any playlist yet.: You haven't selected any playlist yet.
|
||||
"{videoCount} video(s) added to {playlistCount} playlists": 1 video added to
|
||||
{playlistCount} playlists | {videoCount} videos added to {playlistCount}
|
||||
playlists
|
||||
Select a playlist to add your N videos to: Select a playlist to add your video
|
||||
to | Select a playlist to add your {videoCount} videos to
|
||||
CreatePlaylistPrompt:
|
||||
New Playlist Name: New playlist name
|
||||
New Playlist Name: New Playlist name
|
||||
Create: Create
|
||||
Toast:
|
||||
Playlist {playlistName} has been successfully created.: Playlist {playlistName}
|
||||
has been successfully created.
|
||||
There was an issue with creating the playlist.: There was an problem when creating
|
||||
the playlist.
|
||||
There is already a playlist with this name. Please pick a different name.: There
|
||||
is already a Playlist with this name. Please pick a different name.
|
||||
You have no playlists. Click on the create new playlist button to create a new one.: You
|
||||
have no playlists. Click on the create new playlist button to create a new one.
|
||||
Move Video Up: Move video up
|
||||
|
@ -252,7 +262,7 @@ Settings:
|
|||
Clear Default Instance: Clear default instance
|
||||
Set Current Instance as Default: Set current instance as default
|
||||
Current instance will be randomized on startup: Current instance will be randomised
|
||||
on startup
|
||||
on Startup
|
||||
No default instance has been set: No default instance has been set
|
||||
The currently set default instance is {instance}: The currently set default instance
|
||||
is {instance}
|
||||
|
@ -1082,8 +1092,8 @@ New Window: New window
|
|||
Channels:
|
||||
Empty: Your channel list is currently empty.
|
||||
Unsubscribe: Unsubscribe
|
||||
Unsubscribed: '{channelName} has been removed from your subscriptions'
|
||||
Unsubscribe Prompt: Are you sure you want to unsubscribe from ‘{channelName}’?
|
||||
Unsubscribed: '{channelName} has been removed from your Subscriptions'
|
||||
Unsubscribe Prompt: Are you sure you want to Unsubscribe from ‘{channelName}’?
|
||||
Title: Channel list
|
||||
Search bar placeholder: Search channels
|
||||
Channels: Channels
|
||||
|
@ -1113,3 +1123,4 @@ Playlist will not pause when current video is finished: Playlist will not pause
|
|||
current video is finished
|
||||
Go to page: Go to {page}
|
||||
Tag already exists: ‘{tagName}’ tag already exists
|
||||
Close Banner: Close Banner
|
||||
|
|
|
@ -142,6 +142,7 @@ User Playlists:
|
|||
a la que añadir su vídeo | Seleccione una lista de reproducción a la que añadir
|
||||
sus {videoCount} vídeos
|
||||
N playlists selected: '{playlistCount} seleccionada'
|
||||
Added {count} Times: Añadido {count} vez | Añadido {count} veces
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: No había vídeos que eliminar.
|
||||
|
|
|
@ -161,6 +161,7 @@ User Playlists:
|
|||
Select a playlist to add your N videos to: Vali esitusloend, kuhu soovid oma video
|
||||
lisada | Vali esitusloend, kuhu soovid oma {videoCount} videot lisada
|
||||
N playlists selected: '{playlistCount} valitud'
|
||||
Added {count} Times: Lisatud {count} kord | Lisatud {count} korda
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Ei leidnud ühtegi videot, mida saaks eemaldada.
|
||||
|
@ -884,6 +885,7 @@ Video:
|
|||
Pause on Current Video: Peata hetkel esitatav video
|
||||
Unhide Channel: Näita kanalit
|
||||
Hide Channel: Peida kanal
|
||||
More Options: Veel valikuid
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -1120,3 +1122,7 @@ Channel Unhidden: '{channel} on eemaldatud kanalite filtrist'
|
|||
Tag already exists: Silt „{tagName}“ on juba olemas
|
||||
Trimmed input must be at least N characters long: Kärbitud sisend peab olema vähemalt
|
||||
1 tähemärgi pikkune | Kärbitud sisend peab olema vähemalt {length} tähemärgi pikkune
|
||||
Age Restricted:
|
||||
This channel is age restricted: Kanali vaatamisel on vanusepiirang
|
||||
This video is age restricted: Video vaatamisel on vanusepiirang
|
||||
Close Banner: Sulge rekaampilt
|
||||
|
|
|
@ -34,6 +34,17 @@ Forward: 'Aurrera'
|
|||
Global:
|
||||
Videos: 'Bideoak'
|
||||
|
||||
Counts:
|
||||
Subscriber Count: 1.harpideduna| {count} harpidedun
|
||||
Watching Count: 1. ikuslea | {count} ikusle
|
||||
Channel Count: 1. kanala ! {count} kanal
|
||||
Video Count: 1. bideoa | {count} bideo
|
||||
View Count: 1. ikustaldia | {count} ikustaldi
|
||||
Live: Zuzenekoa
|
||||
Shorts: Laburrak
|
||||
Input Tags:
|
||||
Length Requirement: Etiketak {zenbaki} karaktere izan behar ditu gutxienez
|
||||
Community: Komunitatea
|
||||
Version {versionNumber} is now available! Click for more details: '{versionNumber}
|
||||
bertsioa erabilgarri! Klikatu azalpen gehiagorako'
|
||||
Download From Site: 'Webgunetik jaitsi'
|
||||
|
@ -90,6 +101,14 @@ Subscriptions:
|
|||
Refresh Subscriptions: 'Harpidetzak freskatu'
|
||||
Load More Videos: 'Bideo gehiago kargatu'
|
||||
Error Channels: Akatsak dituzten kateak
|
||||
Disabled Automatic Fetching: Harpidetza-bilaketa automatikoa desgaitu duzu. Freskatu
|
||||
harpidetzak hemen ikusteko.
|
||||
Empty Channels: Harpidetutako kanalek ez dute bideorik.
|
||||
Empty Posts: Harpidetutako kanalek ez dute argitalpenik.
|
||||
Load More Posts: Kargatu mezu gehiago
|
||||
Subscriptions Tabs: Harpidetzen fitxak
|
||||
All Subscription Tabs Hidden: Harpidetza fitxa guztiak ezkutatuta daude. Hemen edukia
|
||||
ikusteko, erakutsi fitxa batzuk "{azpisection}" ataleko "{settingsSection}"-ean.
|
||||
More: 'Gehiago'
|
||||
Trending:
|
||||
Trending: 'Joerak'
|
||||
|
@ -111,6 +130,100 @@ User Playlists:
|
|||
Search bar placeholder: Bilatu Erreprodukzio-zerrendan
|
||||
Empty Search Message: Erreprodukzio-zerrenda honetan ez dago zure bilaketarekin
|
||||
bat datorren bideorik
|
||||
Remove Watched Videos: Kendu ikusitako bideoak
|
||||
You have no playlists. Click on the create new playlist button to create a new one.: Ez
|
||||
duzu erreprodukzio-zerrendarik. Egin klik sortu erreprodukzio-zerrenda berria
|
||||
botoian berri bat sortzeko.
|
||||
This playlist currently has no videos.: Erreprodukzio-zerrenda honek ez du bideorik.
|
||||
Create New Playlist: Sortu erreprodukzio zerrenda berria
|
||||
Add to Playlist: Gehitu erreprodukzio zerrendara
|
||||
Add to Favorites: Gehitu {playlistName}-ra
|
||||
Remove from Favorites: Kendu {playlistName}tik
|
||||
Move Video Up: Mugitu bideoa gora
|
||||
Move Video Down: Mugitu bideoa behera
|
||||
Remove from Playlist: Kendu erreprodukzio zerrendatik
|
||||
Playlist Name: Erreprodukzio zerrendaren izena
|
||||
Playlist Description: Erreprodukzio-zerrendaren deskribapena
|
||||
Save Changes: Gorde aldaketak
|
||||
Cancel: Utzi
|
||||
Edit Playlist Info: Editatu erreprodukzio zerrendaren informazioa
|
||||
Copy Playlist: Kopiatu Erreprodukzio zerrenda
|
||||
CreatePlaylistPrompt:
|
||||
New Playlist Name: Erreprodukzio-zerrendaren izen berria
|
||||
Toast:
|
||||
Playlist {playlistName} has been successfully created.: '{playlistName} erreprodukzio-zerrenda
|
||||
behar bezala sortu da.'
|
||||
There was an issue with creating the playlist.: Arazo bat izan da erreprodukzio-zerrenda
|
||||
sortzearekin.
|
||||
There is already a playlist with this name. Please pick a different name.: Badago
|
||||
dagoeneko izen honekin erreprodukzio-zerrenda bat. Mesedez, aukeratu beste
|
||||
izen bat.
|
||||
Create: Sortu
|
||||
Enable Quick Bookmark With This Playlist: Gaitu laster-marka azkarra erreprodukzio-zerrenda
|
||||
honekin
|
||||
Disable Quick Bookmark: Desgaitu laster-marka azkarra
|
||||
Sort By:
|
||||
NameDescending: Z-A
|
||||
LatestCreatedFirst: Sortu berria
|
||||
EarliestCreatedFirst: Lehenen sortuak
|
||||
LatestPlayedFirst: Duela gutxi erreproduzitua
|
||||
EarliestPlayedFirst: Lehenen erreproduzitua
|
||||
Sort By: Ordenatu honen arabera
|
||||
NameAscending: A-Z
|
||||
EarliestUpdatedFirst: Lehenen eguneratuak
|
||||
LatestUpdatedFirst: Eguneratu berriak
|
||||
Delete Playlist: Ezabatu erreprodukzio zerrenda
|
||||
Are you sure you want to delete this playlist? This cannot be undone: Ziur erreprodukzio-zerrenda
|
||||
ezabatu nahi duzula? Hau ezin da desegin.
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
Playlist has been updated.: Erreprodukzio-zerrenda eguneratu da.
|
||||
"{videoCount} video(s) have been removed": bideo 1 kendu da | {videoCount} bideo
|
||||
kendu dira
|
||||
This video cannot be moved up.: Bideo hau ezin da gora eraman.
|
||||
This video cannot be moved down.: Bideoa ezin da behera eraman.
|
||||
Video has been removed: Bideoa kendu da
|
||||
There was a problem with removing this video: Arazo bat izan da bideoa kentzean
|
||||
This playlist is now used for quick bookmark: Erreprodukzio-zerrenda hau laster-markak
|
||||
egiteko erabiltzen da orain
|
||||
Quick bookmark disabled: Laster-marka azkarra desgaituta dago
|
||||
This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo: Erreprodukzio-zerrenda
|
||||
hau laster-marketarako erabiltzen da orain {oldPlaylistName}-ren ordez. Egin
|
||||
klik hemen desegiteko
|
||||
Reverted to use {oldPlaylistName} for quick bookmark: '{oldPlaylistName} erabili
|
||||
da berriro azkar markatzeko'
|
||||
Some videos in the playlist are not loaded yet. Click here to copy anyway.: Erreprodukzio-zerrendako
|
||||
bideo batzuk oraindik ez dira kargatu. Egin klik hemen kopiatzeko hala ere.
|
||||
Playlist name cannot be empty. Please input a name.: Erreprodukzio zerrendaren
|
||||
izena ezin da hutsik egon. Mesedez, idatzi izena.
|
||||
There was an issue with updating this playlist.: Arazo bat izan da erreprodukzio-zerrenda
|
||||
eguneratzean.
|
||||
There were no videos to remove.: Ez zegoen kentzeko bideorik.
|
||||
This playlist is protected and cannot be removed.: Erreprodukzio-zerrenda hau
|
||||
babestuta dago eta ezin da kendu.
|
||||
Playlist {playlistName} has been deleted.: '{playlistName} erreprodukzio-zerrenda
|
||||
ezabatu da.'
|
||||
This playlist does not exist: Erreprodukzio-zerrenda hau ez da existitzen
|
||||
Search for Videos: Bilatu bideoak
|
||||
AddVideoPrompt:
|
||||
Added {count} Times: Gehitu da {count} Time | {count} aldiz gehitu da
|
||||
Toast:
|
||||
"{videoCount} video(s) added to 1 playlist": Bideo 1 gehitu da erreprodukzio-zerrenda
|
||||
batera | {videoCount} bideo gehitu dira erreprodukzio-zerrenda batera
|
||||
You haven't selected any playlist yet.: Oraindik ez duzu erreprodukzio zerrendarik
|
||||
hautatu.
|
||||
"{videoCount} video(s) added to {playlistCount} playlists": Bideo 1 gehitu da
|
||||
{playlistCount} erreprodukzio zerrendetan | {videoCount} bideo gehitu dira
|
||||
{playlistCount} erreprodukzio-zerrendetan
|
||||
Select a playlist to add your N videos to: Hautatu erreprodukzio-zerrenda zure
|
||||
bideoa -ra gehitzeko | Hautatu erreprodukzio-zerrenda zure {videoCount} bideoak
|
||||
gehitzeko
|
||||
N playlists selected: '{playlistCount} hautatuta'
|
||||
Search in Playlists: Bilatu erreprodukzio-zerrendetan
|
||||
Save: Gorde
|
||||
Are you sure you want to remove all watched videos from this playlist? This cannot be undone: Ziur
|
||||
ikusitako bideo guztiak erreprodukzio-zerrenda honetatik kendu nahi dituzula?
|
||||
Hau ezin da desegin.
|
||||
History:
|
||||
# On History Page
|
||||
History: 'Historikoa'
|
||||
|
@ -147,6 +260,8 @@ Settings:
|
|||
Beginning: 'Hasiera'
|
||||
Middle: 'Erdian'
|
||||
End: 'Bukaera'
|
||||
Hidden: Ezkutatuta
|
||||
Blur: Lausotzea
|
||||
View all Invidious instance information: 'Invidious instantzia guztien informazioa
|
||||
ikusi'
|
||||
Region for Trending: 'Joeren eskualdea'
|
||||
|
@ -179,6 +294,9 @@ Settings:
|
|||
Dracula: Drakula
|
||||
System Default: Sistemak lehenetsia
|
||||
Catppuccin Mocha: Catppuccin Motxa
|
||||
Nordic: nordikoa
|
||||
Pastel Pink: Pastel arrosa
|
||||
Hot Pink: Arrosa beroa
|
||||
Main Color Theme:
|
||||
Main Color Theme: 'Oinarrizko koloreen gaia'
|
||||
Red: 'Gorria'
|
||||
|
@ -221,6 +339,7 @@ Settings:
|
|||
Secondary Color Theme: 'Gaiaren bigarren mailako kolorea'
|
||||
#* Main Color Theme
|
||||
Hide Side Bar Labels: Ezkutatu alboko barraren etiketak
|
||||
Hide FreeTube Header Logo: Ezkutatu FreeTube goiburuko logotipoa
|
||||
Player Settings:
|
||||
Player Settings: 'Erreprodukzioaren ezarpenak'
|
||||
Force Local Backend for Legacy Formats: 'Behartu backend lokala Legacy formatuentzat'
|
||||
|
@ -272,6 +391,11 @@ Settings:
|
|||
Display Play Button In Video Player: Bistaratu Erreproduzitzeko botoia bideo erreproduzitzailean
|
||||
Max Video Playback Rate: Gehienezko bideoen erreprodukzio-tasa
|
||||
Video Playback Rate Interval: Bideo Erreprodukzio-tasa tartea
|
||||
Comment Auto Load:
|
||||
Comment Auto Load: Iruzkin karga automatikoa
|
||||
Skip by Scrolling Over Video Player: Saltatu bideo-erreproduzitzailean korrituz
|
||||
Enter Fullscreen on Display Rotate: Sartu pantaila osoko pantaila biratu pantailan
|
||||
Allow DASH AV1 formats: Baimendu DASH AV1 formatuak
|
||||
Privacy Settings:
|
||||
Privacy Settings: 'Pribatutasunari buruzko ezarpenak'
|
||||
Remember History: 'Historikoa oroitu'
|
||||
|
@ -289,11 +413,20 @@ Settings:
|
|||
Are you sure you want to remove all subscriptions and profiles? This cannot be undone.: 'Ziur
|
||||
al zaude zure profil eta harpidetza guztiak ezabatu nahi dituzula? Ezingo duzu
|
||||
atzera egin.'
|
||||
All playlists have been removed: Erreprodukzio-zerrenda guztiak kendu dira
|
||||
Save Watched Videos With Last Viewed Playlist: Gorde ikusitako bideoak ikusitako
|
||||
azken erreprodukzio-zerrendarekin
|
||||
Remove All Playlists: Kendu erreprodukzio-zerrenda guztiak
|
||||
Are you sure you want to remove all your playlists?: Ziur erreprodukzio-zerrenda
|
||||
guztiak kendu nahi dituzula?
|
||||
Subscription Settings:
|
||||
Subscription Settings: 'Harpidetzen ezarpenak'
|
||||
Hide Videos on Watch: 'Ikusten ari zaren bideoa ezkutatu'
|
||||
Fetch Feeds from RSS: 'RSS jarioak eskuratu'
|
||||
Manage Subscriptions: 'Harpidetzak kudeatu'
|
||||
Fetch Automatically: Eskuratu jarioa automatikoki
|
||||
Only Show Latest Video for Each Channel: Erakutsi soilik kanal bakoitzeko azken
|
||||
bideoa
|
||||
Distraction Free Settings:
|
||||
Distraction Free Settings: 'Oharkabetasunak ekiditeko ezarpenak'
|
||||
Hide Video Views: 'Bideoen ikustaldi kopurua ezkutatu'
|
||||
|
@ -310,6 +443,38 @@ Settings:
|
|||
Hide Video Description: Bideoaren deskribapena ezkutatu
|
||||
Hide Comments: Iruzkinak ezkutatu
|
||||
Hide Live Streams: Zuzeneko emanaldiak ezkutatu
|
||||
Sections:
|
||||
Side Bar: Alboko Barra
|
||||
Subscriptions Page: Harpidetzak Orria
|
||||
Channel Page: Kanalaren Orria
|
||||
General: Orokorra
|
||||
Watch Page: Ikusi Orria
|
||||
Hide Profile Pictures in Comments: Ezkutatu profileko argazkiak iruzkinetan
|
||||
Display Titles Without Excessive Capitalisation: Bistaratu izenburuak gehiegizko
|
||||
letra larriz eta puntuaziorik gabe
|
||||
Hide Channels Placeholder: Kanalaren IDa
|
||||
Hide Channel Playlists: Ezkutatu kanaleko erreprodukzio-zerrendak
|
||||
Hide Channel Community: Ezkutatu kanalaren komunitatea
|
||||
Hide Channel Podcasts: Ezkutatu kanaleko podcastak
|
||||
Hide Videos and Playlists Containing Text: Ezkutatu testua duten bideoak eta erreprodukzio-zerrendak
|
||||
Hide Channels: Ezkutatu bideoak kanaletatik
|
||||
Hide Upcoming Premieres: Ezkutatu datozen estreinaldiak
|
||||
Hide Subscriptions Videos: Ezkutatu harpidetza-bideoak
|
||||
Hide Subscriptions Shorts: Ezkutatu bideo laburren harpidetzak
|
||||
Hide Channel Releases: Ezkutatu kanalen kaleratzeak
|
||||
Hide Chapters: Ezkutatu kapituluak
|
||||
Hide Channels Invalid: Emandako kanalaren IDa baliogabea da
|
||||
Hide Channels API Error: Errore bat gertatu da emandako IDa duen erabiltzailea
|
||||
berreskuratzean. Mesedez, egiaztatu berriro IDa zuzena den.
|
||||
Hide Channels Already Exists: Kanalaren IDa badago jada
|
||||
Hide Channel Shorts: Ezkutatu kanalaren bideo laburrak
|
||||
Hide Videos and Playlists Containing Text Placeholder: Hitza, Hitzaren zatia edo
|
||||
esaldia
|
||||
Hide Subscriptions Live: Ezkutatu zuzenekoen harpidetzak
|
||||
Hide Subscriptions Community: Ezkutatu harpidetzen komunitateak
|
||||
Hide Featured Channels: Ezkutatu nabarmendutako kanalak
|
||||
Hide Channels Disabled Message: Kanal batzuk IDa erabiliz blokeatu dira eta ez
|
||||
dira prozesatu. Eginbidea blokeatuta dago ID horiek eguneratzen ari diren bitartean
|
||||
Data Settings:
|
||||
Data Settings: 'Datuen ezarpenak'
|
||||
Select Import Type: 'Hautatu Inportazio mota'
|
||||
|
@ -357,6 +522,18 @@ Settings:
|
|||
esportatu dira
|
||||
Playlist insufficient data: Ez da datu nahikorik "{playlist}" erreprodukzio zerrendarentzat,
|
||||
elementutik ateratzen
|
||||
History File: Historikoaren fitxategia
|
||||
Playlist File: Erreprodukzio-zerrendaren fitxategia
|
||||
Export Playlists For Older FreeTube Versions:
|
||||
Label: Esportatu erreprodukzio zerrendak FreeTube bertsio zaharretarako
|
||||
Tooltip: "Aukera honek erreprodukzio-zerrenda guztietako bideoak \"Gogokoak\"\
|
||||
\ izeneko erreprodukzio-zerrenda batera esportatzen ditu.\nNola esportatu
|
||||
eta inportatu bideoak erreprodukzio-zerrendetan FreeTube-ren bertsio zaharrago
|
||||
baterako:\n 1. Esportatu zure erreprodukzio zerrendak aukera hau gaituta.\n
|
||||
2. Ezabatu lehendik dituzun erreprodukzio-zerrenda guztiak Pribatutasun-ezarpenetan
|
||||
dagoen Kendu zerrenda guztiak aukera erabiliz.\n 3. Abiarazi FreeTube-ren
|
||||
bertsio zaharra eta inportatu esportatutako erreprodukzio-zerrendak.\""
|
||||
Subscription File: Harpidetza Fitxategia
|
||||
Proxy Settings:
|
||||
Proxy Settings: 'Proxy-aren ezarpenak'
|
||||
Enable Tor / Proxy: 'Tor / Proxy ahalbidetu'
|
||||
|
@ -381,6 +558,10 @@ Settings:
|
|||
External Player: Kanpoko erreproduzitzailea
|
||||
Custom External Player Executable: Lehenetsitako kanpo erreproduzitzailea exekutagarria
|
||||
Custom External Player Arguments: Lehenetsitako kanpo erreproduzitzailea argudioak
|
||||
Ignore Default Arguments: Ez ikusi lehenetsitako argudioak
|
||||
Players:
|
||||
None:
|
||||
Name: Bat ere ez
|
||||
Download Settings:
|
||||
Download Settings: Deskargen ezarpenak
|
||||
Ask Download Path: Deskargaren ibilbidea galdetu
|
||||
|
@ -402,11 +583,31 @@ Settings:
|
|||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': Babesleak blokeatzeko
|
||||
API Url (lehenetsia https://sponsor.ajay.app da)
|
||||
SponsorBlock Settings: Babesleak blokeatzeko ezarpenak
|
||||
UseDeArrowTitles: Erabili DeArrow bideo-izenburuak
|
||||
UseDeArrowThumbnails: Erabili DeArrow irudi txikietarako
|
||||
'DeArrow Thumbnail Generator API Url (Default is https://dearrow-thumb.ajay.app)': DeArrow
|
||||
Thumbnail Generator API URLa (lehenetsia https://dearrow-thumb.ajay.app da)
|
||||
Parental Control Settings:
|
||||
Show Family Friendly Only: Erakutsi familientzat aproposa dena bakarrik
|
||||
Hide Unsubscribe Button: Harpidetza kendu botoia ezkutatu
|
||||
Parental Control Settings: Gurasoen kontrolaren ezarpenak
|
||||
Hide Search Bar: Bilaketa barra ezkutatu
|
||||
Password Dialog:
|
||||
Password: Pasahitza
|
||||
Password Incorrect: Pasahitza okerra
|
||||
Unlock: Desblokeatu
|
||||
Enter Password To Unlock: Sartu pasahitza ezarpenak desblokeatzeko
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Ordeztu HTTP cachea
|
||||
Experimental Settings: Ezarpen esperimentalak
|
||||
Warning: Ezarpen hauek esperimentalak dira, aktibatuta dauden bitartean hutsegiteak
|
||||
eragin ditzakete. Oso gomendagarria da babeskopiak egitea. Erabili zure ardurapean!
|
||||
Password Settings:
|
||||
Password Settings: Pasahitz ezarpenak
|
||||
Set Password To Prevent Access: Ezarri pasahitz bat ezarpenetara sarbidea galarazteko
|
||||
Remove Password: Pasahitza ezabatu
|
||||
Set Password: Ezarri pasahitza
|
||||
Expand All Settings Sections: Zabaldu ezarpen guztien atalak
|
||||
About:
|
||||
#On About page
|
||||
About: 'Honi buruz'
|
||||
|
@ -436,6 +637,7 @@ About:
|
|||
these people and projects: 'Hurrengo pertsonak eta proiektuak'
|
||||
Donate: 'Donazioa egin'
|
||||
|
||||
Discussions: Eztabaidak
|
||||
Profile:
|
||||
Profile Select: 'Hautatu profila'
|
||||
Profile Filter: 'Profilaren iragazkiak'
|
||||
|
@ -480,6 +682,12 @@ Profile:
|
|||
batetik ezabatuko.'
|
||||
#On Channel Page
|
||||
Profile Settings: Profilaren ezarpenak
|
||||
Close Profile Dropdown: Itxi goitibeherako profila
|
||||
Open Profile Dropdown: Ireki goitibeherako profila
|
||||
Toggle Profile List: Aldatu profilen zerrenda
|
||||
Edit Profile Name: Editatu profilaren izena
|
||||
Create Profile Name: Sortu profilaren izena
|
||||
Profile Name: Profilaren izena
|
||||
Channel:
|
||||
Subscribe: 'Harpidetu'
|
||||
Unsubscribe: 'Harpidetza kendu'
|
||||
|
@ -510,6 +718,39 @@ Channel:
|
|||
About: 'Honi buruz'
|
||||
Channel Description: 'Kanalaren deskribapena'
|
||||
Featured Channels: 'Nabarmendutako kanalak'
|
||||
Joined: Bat eginda
|
||||
Location: Kokapena
|
||||
Tags:
|
||||
Tags: Etiketak
|
||||
Search for: Bilatu "{tag}"
|
||||
Details: Xehetasunak
|
||||
This channel does not exist: Kanal hau ez da existitzen
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: Kanal
|
||||
hau adin mugatuta dago eta une honetan ezin da ikusi FreeTube-n.
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Une honetan kanal honek ez du
|
||||
bideo laburrik
|
||||
Releases:
|
||||
This channel does not currently have any releases: Une honetan kanal honek ez
|
||||
du argitalpenik
|
||||
Releases: Argitalpenak
|
||||
Community:
|
||||
Hide Answers: Erantzunak ezkutatu
|
||||
votes: '{votes} bozka'
|
||||
This channel currently does not have any posts: Une honetan kanal honek ez du
|
||||
argitalpenik
|
||||
Reveal Answers: Erantzunak agerian utzi
|
||||
Video hidden by FreeTube: FreeTube-k ezkutatutako bideoa
|
||||
Channel Tabs: Kanalaren fitxak
|
||||
This channel does not allow searching: Kanal honek ez du bilaketarik onartzen
|
||||
Live:
|
||||
Live: Zuzenekoak
|
||||
This channel does not currently have any live streams: Kanal honek ez du zuzeneko
|
||||
erreprodukziorik
|
||||
Podcasts:
|
||||
Podcasts: Podcastak
|
||||
This channel does not currently have any podcasts: Une honetan kanal honek ez
|
||||
du podcastik
|
||||
Video:
|
||||
Mark As Watched: 'Ikusitako gisa jarri'
|
||||
Remove From History: 'Historikotik ezabatu'
|
||||
|
@ -592,6 +833,7 @@ Video:
|
|||
Years: 'Urteak'
|
||||
Ago: 'Duela'
|
||||
Upcoming: 'Estreinaldiak'
|
||||
In less than a minute: Minutu bat baino gutxiagoan
|
||||
Published on: 'Noiz argitaratua'
|
||||
Streamed on: 'Noiz zuzenean emana'
|
||||
Started streaming on: 'Noiz hasi zen zuzenekoa'
|
||||
|
@ -636,6 +878,16 @@ Video:
|
|||
intro: Sarrera
|
||||
Skipped segment: Saltatu egin da segmentua
|
||||
Premieres on: Estreinaldiak
|
||||
Hide Channel: Kanala ezkutatu
|
||||
Unhide Channel: Kanala erakutsi
|
||||
Premieres: Estreinaldiak
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Zuzeneko
|
||||
txata ez dago erabilgarri zuzeneko honentzat. Baliteke igo duenak desgaitu izana.
|
||||
Show Super Chat Comment: Erakutsi Super txat iruzkina
|
||||
Pause on Current Video: Gelditu uneko bideoa
|
||||
More Options: Aukera gehiago
|
||||
Upcoming: Datozenak
|
||||
Scroll to Bottom: Joan Beherantz
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -685,6 +937,7 @@ Share:
|
|||
YouTube Channel URL copied to clipboard: 'Youtube-ko kanalaren URL-a arbelean itsatsi
|
||||
da'
|
||||
|
||||
Share Channel: Kanala partekatu
|
||||
Mini Player: 'Erreproduzitzaile txikia'
|
||||
Comments:
|
||||
Comments: 'Iruzkinak'
|
||||
|
@ -710,6 +963,10 @@ Comments:
|
|||
Show More Replies: Erantzun gehiago erakutsi
|
||||
And others: eta bestelakoak
|
||||
Pinned by: Honengatik ainguratuta
|
||||
Hearted: Bihotzez
|
||||
From {channelName}: '{channelName}-tik'
|
||||
View {replyCount} replies: Ikusi {replyCount} erantzunak
|
||||
Subscribed: Harpidetuta
|
||||
Up Next: 'Hurrengoa'
|
||||
|
||||
#Tooltips
|
||||
|
@ -749,11 +1006,19 @@ Tooltips:
|
|||
eta mantendu (Komando tekla MAC-etan) eta ondoren saguaren ezkerreko botoia
|
||||
sakatu, lehenetsitako erreprodukzio tasara itzultzeko (1x baldin eta ezarpenetan
|
||||
aldaketarik egin ez bada).
|
||||
Skip by Scrolling Over Video Player: Erabili korritze-gurpila bideoa saltatzeko,
|
||||
MPV estiloa.
|
||||
Allow DASH AV1 formats: DASH AV1 formatuak DASH H.264 formatuak baino itxura hobea
|
||||
izan dezake. DASH AV1 formatuek potentzia gehiago behar dute erreproduzitzeko!
|
||||
Ez daude bideo guztietan eskuragarri, kasu horietan erreproduzitzaileak DASH
|
||||
H.264 formatuak erabiliko ditu ordez.
|
||||
Subscription Settings:
|
||||
Fetch Feeds from RSS: 'Posible denean, Freetube-k bere lehenetsitako metodoa erabili
|
||||
beharrean RSS-ak baliatuko ditu zure harpidetzen jariora konektatzeko. RSS arinagoa
|
||||
izateaz gain, IP-en blokeoak saihesten ditu. Aldiz, zuzenekoaren egoeraren edo
|
||||
bideoaren iraupenaren informaziorik ez du ematen, besteak beste'
|
||||
Fetch Automatically: Gaituta dagoenean, FreeTubek automatikoki eskuratuko du zure
|
||||
harpidetza-jarioa leiho berri bat irekitzen denean eta profila aldatzean.
|
||||
Privacy Settings:
|
||||
Remove Video Meta Files: 'Gaituta dagoenean, FreeTube-k automatikoki ezabatzen
|
||||
ditu bideoen erreprodukzioan sortutako metafitxategiak, bistaratze orria ixten
|
||||
|
@ -772,6 +1037,29 @@ Tooltips:
|
|||
Individious-en ezarpenek ez dituzte kanpo erreproduzitzaileak trabatzen.
|
||||
Custom External Player Arguments: Komando-lerroko argumentu pertsonalizatuak,
|
||||
puntu eta komaz bereizita (';'), kanpoko erreproduzitzailera pasatzea nahi duzu.
|
||||
Ignore Default Arguments: Ez bidali argumentu lehenetsirik kanpoko erreproduzitzaileari
|
||||
bideoaren URLaz gain (adibidez, erreprodukzio-tasa, erreprodukzio-zerrendaren
|
||||
URLa, etab.). Argumentu pertsonalizatuak transmitituko dira oraindik.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Sartu kanalaren ID bat bideo, erreprodukzio-zerrenda eta kanala
|
||||
bera bilaketetan, joeran, ezagunenetan eta gomendagarrienetan ager ez dadin
|
||||
ezkutatzeko. Sartutako kanalaren IDak guztiz bat etorri behar du eta maiuskulak
|
||||
eta minuskulak bereizten ditu.
|
||||
Hide Videos and Playlists Containing Text: Idatzi hitz bat, hitz-zati bat edo
|
||||
esaldi bat (maiuskulak eta minuskulak bereizten ez diren) jatorrizko izenburuak
|
||||
FreeTube osoan duten bideo eta erreprodukzio-zerrenda guztiak ezkutatzeko, historia,
|
||||
zure erreprodukzio-zerrendak eta erreprodukzio-zerrenden barneko bideoak soilik
|
||||
kenduta.
|
||||
Hide Subscriptions Live: Ezarpen hau aplikazio osorako "{appWideSetting}" ezarpenak
|
||||
ordezkatzen du, "{settingsSection}" ataleko "{azpisekzioa}" atalean
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Electron-en diskoan oinarritutako HTTP cachea desgaitzen du
|
||||
eta memoriako irudien cache pertsonalizatua gaitu. RAM erabilera handitzea ekarriko
|
||||
du.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowThumbnails: Ordeztu bideoaren miniaturak DeArrow-en miniaturaz.
|
||||
UseDeArrowTitles: Ordeztu bideoen izenburuak DeArrow-en erabiltzaileek bidalitako
|
||||
tituluekin.
|
||||
Local API Error (Click to copy): 'Tokiko API-ak huts egin du (klikatu kopiatzeko)'
|
||||
Invidious API Error (Click to copy): 'Individious-eko APIak huts egin du (klikatu
|
||||
kopiatzeko)'
|
||||
|
@ -828,3 +1116,33 @@ Channels:
|
|||
Count: '{number} kanal aurkitu dira.'
|
||||
Empty: Zure kanalen zerrenda hutsik da.
|
||||
Preferences: Hobespenak
|
||||
Go to page: Joan {page}-ra
|
||||
Close Banner: Itxi iragarkia
|
||||
Age Restricted:
|
||||
This channel is age restricted: Kanal hau adin mugatuta dago
|
||||
This video is age restricted: Bideo hau adin mugatuta dago
|
||||
Playlist will not pause when current video is finished: Erreprodukzio-zerrenda ez
|
||||
da etengo uneko bideoa amaitzen denean
|
||||
Playlist will pause when current video is finished: Erreprodukzio-zerrenda pausatu
|
||||
egingo da uneko bideoa amaitzen denean
|
||||
Channel Unhidden: '{channel} kanalaren iragazkitik kendu da'
|
||||
Clipboard:
|
||||
Cannot access clipboard without a secure connection: Ezin da arbelera sartu konexio
|
||||
segururik gabe
|
||||
Copy failed: Ezin izan da kopiatu arbelean
|
||||
Tag already exists: '"{tagName}" etiketa badago jada'
|
||||
Trimmed input must be at least N characters long: Moztutako sarrerak karaktere bat
|
||||
izan behar du gutxienez | Moztutako sarrerak gutxienez {length} karaktere izan behar
|
||||
ditu
|
||||
Chapters:
|
||||
'Chapters list hidden, current chapter: {chapterName}': 'Kapituluen zerrenda ezkutatuta
|
||||
dago, uneko kapitulua: {chapterName}'
|
||||
Chapters: Kapituluak
|
||||
'Chapters list visible, current chapter: {chapterName}': 'Kapituluen zerrenda ikusgai,
|
||||
uneko kapitulua: {chapterName}'
|
||||
Hashtag:
|
||||
Hashtag: Traola
|
||||
This hashtag does not currently have any videos: Traola honek ez du bideorik une
|
||||
honetan
|
||||
Ok: Ados
|
||||
Channel Hidden: '{channel} gehitu da kanalaren iragazkian'
|
||||
|
|
|
@ -148,6 +148,7 @@ User Playlists:
|
|||
à {playlistCount} listes de lecture | {videoCount} vidéos ajoutées à {playlistCount}
|
||||
listes de lecture
|
||||
N playlists selected: '{playlistCount} Sélectionnée(s)'
|
||||
Added {count} Times: Ajouté {count} Fois | Ajouté {count} Fois
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Il n'y avait aucune vidéo à supprimer.
|
||||
|
|
|
@ -195,6 +195,7 @@ User Playlists:
|
|||
videa dodana u 1 zbirku
|
||||
Select a playlist to add your N videos to: Odaberi zbirku za dodavanje tvog videa
|
||||
| Odaberi zbirku za dodavanje tvojih {videoCount} videa
|
||||
Added {count} Times: Dodano {count} puta | Dodano {count} puta
|
||||
CreatePlaylistPrompt:
|
||||
Create: Stvori
|
||||
Toast:
|
||||
|
@ -272,7 +273,7 @@ Settings:
|
|||
Black: 'Crna'
|
||||
Dark: 'Tamna'
|
||||
Light: 'Svijetla'
|
||||
Dracula: 'Drakula'
|
||||
Dracula: 'Dracula'
|
||||
System Default: Standard sustava
|
||||
Catppuccin Mocha: Catppuccin Mocha
|
||||
Pastel Pink: Pastelno ružičasta
|
||||
|
@ -928,7 +929,7 @@ Video:
|
|||
Resolution: Razlučivost
|
||||
Player Dimensions: Dimenzije playera
|
||||
Bitrate: Brzina prijenosa
|
||||
Volume: Glasnoća
|
||||
Volume: Direktorij
|
||||
Bandwidth: Propusnost
|
||||
Buffered: Učitano u memoriju
|
||||
Mimetype: Mimetype
|
||||
|
|
|
@ -147,12 +147,13 @@ User Playlists:
|
|||
lejátszási listákhoz
|
||||
"{videoCount} video(s) added to 1 playlist": 1 videó hozzáadva 1 lejátszási
|
||||
listához | {videoCount} videó hozzáadása 1 lejátszási listához
|
||||
You haven't selected any playlist yet.: Még nem választott ki lejátszási listát
|
||||
sem.
|
||||
You haven't selected any playlist yet.: Még nem választott ki egyetlen lejátszási
|
||||
listát sem.
|
||||
Select a playlist to add your N videos to: Válasszon ki egy lejátszási listát
|
||||
a videó hozzáadásához | Válasszon ki egy lejátszási listát a {videoCount} videó
|
||||
hozzáadásához
|
||||
N playlists selected: '{playlistCount} Kiválasztott'
|
||||
Added {count} Times: Hozzáadva {count} Alkalommal | Hozzáadva {count} Alkalommal
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Nem voltak eltávolítható videók.
|
||||
|
@ -183,6 +184,7 @@ User Playlists:
|
|||
Kattints ide a visszavonáshoz
|
||||
Reverted to use {oldPlaylistName} for quick bookmark: Visszaállítva a(z) {oldPlaylistName}
|
||||
használatára a gyors könyvjelzőhöz
|
||||
Search for Videos: Videók keresése
|
||||
Are you sure you want to delete this playlist? This cannot be undone: Biztos, hogy
|
||||
törölni szeretné ezt a lejátszási listát? Ezt nem lehet visszacsinálni.
|
||||
Sort By:
|
||||
|
|
|
@ -44,6 +44,8 @@ Global:
|
|||
Subscriber Count: 1 áskrifandi | {count} áskrifendur
|
||||
View Count: 1 áhorf | {count} áhorf
|
||||
Watching Count: 1 að horfa | {count} að horfa
|
||||
Input Tags:
|
||||
Length Requirement: Merki þarf að vera a.m.k. {number} stafa langt
|
||||
Version {versionNumber} is now available! Click for more details: 'Útgáfa {versionNumber}
|
||||
er tiltæk! Smelltu til að skoða nánar'
|
||||
Download From Site: 'Sækja af vefsvæði'
|
||||
|
@ -174,6 +176,14 @@ User Playlists:
|
|||
Some videos in the playlist are not loaded yet. Click here to copy anyway.: Sum
|
||||
myndskeið í spilunarlistanum hafa ekki enn hlaðist inn. Smelltu hér til að
|
||||
afrita samt.
|
||||
This playlist is now used for quick bookmark: Þessi spilunarlisti er núna notaður
|
||||
undir flýtibókamerki
|
||||
Quick bookmark disabled: Flýtibókamerki óvirk
|
||||
This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo: Þessi
|
||||
spilunarlisti er núna notaður undir flýtibókamerki í stað {oldPlaylistName}.
|
||||
Smelltu hér til að afturkalla
|
||||
Reverted to use {oldPlaylistName} for quick bookmark: Snéri aftur í að nota
|
||||
{oldPlaylistName} undir flýtibókamerki
|
||||
AddVideoPrompt:
|
||||
N playlists selected: '{playlistCount} valin'
|
||||
Search in Playlists: Leita í spilunarlistum
|
||||
|
@ -187,6 +197,7 @@ User Playlists:
|
|||
| {videoCount} myndskeiðum bætt við 1 spilunarlista
|
||||
Select a playlist to add your N videos to: Veldu spilunarlista til að bæta myndskeiðinu
|
||||
þínu á | Veldu spilunarlista til að bæta {videoCount}̣ myndskeiðunum þínum á
|
||||
Added {count} Times: Bætt við {count} sinni | Bætt við {count} sinnum
|
||||
CreatePlaylistPrompt:
|
||||
Create: Búa til
|
||||
New Playlist Name: Heiti á nýjum spilunarlista
|
||||
|
@ -209,6 +220,10 @@ User Playlists:
|
|||
Delete Playlist: Eyða spilunarlista
|
||||
Are you sure you want to delete this playlist? This cannot be undone: Ertu viss
|
||||
um að þú viljir eyða þessum spilunarlista? Aðgerðin er ekki afturkallanleg.
|
||||
Add to Favorites: Bæta á {playlistName}
|
||||
Remove from Favorites: Fjarlægja af {playlistName}
|
||||
Enable Quick Bookmark With This Playlist: Virkja flýtibókamerki með þessum spilunarlista
|
||||
Disable Quick Bookmark: Eyða flýtibókamerki
|
||||
History:
|
||||
# On History Page
|
||||
History: 'Áhorf'
|
||||
|
@ -283,6 +298,7 @@ Settings:
|
|||
Catppuccin Mocha: Catppuccin Mocha
|
||||
Pastel Pink: Pastelbleikt
|
||||
Hot Pink: Dimmbleikt
|
||||
Nordic: Norrænt
|
||||
Main Color Theme:
|
||||
Main Color Theme: 'Aðallitur þema'
|
||||
Red: 'Rautt'
|
||||
|
@ -436,6 +452,7 @@ Settings:
|
|||
Hide Channels: Fela myndskeið úr rásum
|
||||
Hide Channels Placeholder: Auðkenni rásar
|
||||
Display Titles Without Excessive Capitalisation: Birta titla án umfram-hástafa
|
||||
og greinarmerkja
|
||||
Sections:
|
||||
Side Bar: Hliðarspjald
|
||||
Channel Page: Rásasíða
|
||||
|
@ -461,6 +478,9 @@ Settings:
|
|||
Hide Channels Already Exists: Auðkenni rásar er þegar til
|
||||
Hide Channels API Error: Villa við að ná í notanda með uppgefið auðkenni. Athugaðu
|
||||
aftur hvort auðkennið ré rétt.
|
||||
Hide Videos and Playlists Containing Text: Fela myndskeið og spilunarlista sem
|
||||
innihalda texta
|
||||
Hide Videos and Playlists Containing Text Placeholder: Orð, orðhluti eða setning
|
||||
Data Settings:
|
||||
Data Settings: 'Stillingar gagna'
|
||||
Select Import Type: 'Veldu tegund innflutnings'
|
||||
|
@ -721,6 +741,7 @@ Channel:
|
|||
Reveal Answers: Birta svör
|
||||
Hide Answers: Fela svör
|
||||
votes: '{votes} atkvæði'
|
||||
Video hidden by FreeTube: Myndskeið falið af FreeTube
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Þessi rás er í augnablikinu ekki
|
||||
með neinar stuttmyndir
|
||||
|
@ -878,6 +899,7 @@ Video:
|
|||
Pause on Current Video: Setja núverandi myndskeið í bið
|
||||
Unhide Channel: Birta rás
|
||||
Hide Channel: Fela rás
|
||||
More Options: Fleiri valkostir
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -1117,3 +1139,8 @@ Playlist will not pause when current video is finished: Spilunarlisti mun ekki f
|
|||
Go to page: Fara á {page}
|
||||
Channel Hidden: '{channel} bætt við rásasíu'
|
||||
Channel Unhidden: '{channel} fjarlægt úr rásasíu'
|
||||
Close Banner: Loka borða
|
||||
Age Restricted:
|
||||
This video is age restricted: Þetta myndskeið er með aldurstakmörkunum
|
||||
This channel is age restricted: Þessi rás er með aldurstakmörkunum
|
||||
Tag already exists: '"{tagName}" merkið er þegar til staðar'
|
||||
|
|
|
@ -143,6 +143,7 @@ User Playlists:
|
|||
Select a playlist to add your N videos to: Seleziona una playlist a cui aggiungere
|
||||
il tuo video | Seleziona una playlist a cui aggiungere i tuoi {videoCount} video
|
||||
N playlists selected: '{playlistCount} selezionate'
|
||||
Added {count} Times: Aggiunto {count} volta | Aggiunti {count} volte
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Non c'erano video da rimuovere.
|
||||
|
@ -175,6 +176,7 @@ User Playlists:
|
|||
This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo: Questa
|
||||
playlist è ora usata per i segnalibri rapidi al posto di {oldPlaylistName}.
|
||||
Fai clic qui per annullare
|
||||
Search for Videos: Cerca video
|
||||
Are you sure you want to delete this playlist? This cannot be undone: Sei sicuro
|
||||
di voler eliminare questa playlist? Questa operazione non può essere annullata.
|
||||
Sort By:
|
||||
|
|
|
@ -114,6 +114,10 @@ User Playlists:
|
|||
このページは、完全に動作する動画リストではありません。保存またはお気に入りと設定した動画のみが表示されます。操作が完了すると、現在ここにあるすべての動画は「お気に入り」の動画リストに移動します。
|
||||
Search bar placeholder: 動画リスト内の検索
|
||||
Empty Search Message: この再生リストに、検索に一致する動画はありません
|
||||
This playlist currently has no videos.: 存在、この再生リストには動画があっていません。
|
||||
Create New Playlist: 新規再生リストを作られる
|
||||
Sort By:
|
||||
NameAscending: A-Z
|
||||
History:
|
||||
# On History Page
|
||||
History: '履歴'
|
||||
|
@ -276,7 +280,7 @@ Settings:
|
|||
Folder Button: フォルダーの選択
|
||||
Enter Fullscreen on Display Rotate: 横画面時にフルスクリーンにする
|
||||
Skip by Scrolling Over Video Player: 動画プレーヤーでスクロールしてスキップ可能にする
|
||||
Allow DASH AV1 formats: DASH AV1形式を許可する
|
||||
Allow DASH AV1 formats: DASH AV1形式を許可
|
||||
Comment Auto Load:
|
||||
Comment Auto Load: コメント自動読み込み
|
||||
Subscription Settings:
|
||||
|
@ -850,7 +854,7 @@ The playlist has been reversed: 再生リストを逆順にしました
|
|||
A new blog is now available, {blogTitle}. Click to view more: '新着ブログ公開、{blogTitle}。クリックしてブログを読む'
|
||||
Download From Site: サイトからダウンロード
|
||||
Version {versionNumber} is now available! Click for more details: 最新バージョン {versionNumber}
|
||||
配信中! 詳細はクリックして確認してください
|
||||
配信中!詳細はクリックして確認してください
|
||||
This video is unavailable because of missing formats. This can happen due to country unavailability.: この動画は、動画形式の情報が利用できないため再生できません。再生が許可されていない国で発生します。
|
||||
Tooltips:
|
||||
Subscription Settings:
|
||||
|
@ -866,8 +870,8 @@ Tooltips:
|
|||
Scroll Playback Rate Over Video Player: カーソルが動画上にあるとき、Ctrl キー(Mac では Command キー)を押したまま、マウスホイールを前後にスクロールして再生速度を調整します。Control
|
||||
キー(Mac では Command キー)を押したままマウスを左クリックすると、すぐにデフォルトの再生速度(設定を変更していない場合は 1 x)に戻ります。
|
||||
Skip by Scrolling Over Video Player: スクロール ホイールを使用して、ビデオ、MPV スタイルをスキップします。
|
||||
Allow DASH AV1 formats: DASH H.264形式よりDASH AV1形式の方がきれいに見える可能性があるけど、再生には必要な電力がより多い。全ての動画でDASH
|
||||
AV1を利用できないため、プレイヤーはDASH H.264形式に自動変更する場合があります。
|
||||
Allow DASH AV1 formats: DASH H.264形式よりもDASH AV1形式の方がきれいに見える可能性がありますが、再生にはより多くの処理能力が必要となります。DASH
|
||||
AV1形式を使用できない場合、プレイヤーはDASH H.264形式を自動で使用します。
|
||||
General Settings:
|
||||
Invidious Instance: FreeTube が使用する Invidious API の接続先サーバーです。
|
||||
Preferred API Backend: FreeTube が youtube からデータを取得する方法を選択します。「内部 API」とはアプリから取得する方法です。「Invidious
|
||||
|
@ -941,3 +945,5 @@ Hashtag:
|
|||
This hashtag does not currently have any videos: このハッシュタグには現在動画がありません
|
||||
Playlist will pause when current video is finished: 現在のビデオが終了すると、プレイリストは停止します
|
||||
Playlist will not pause when current video is finished: 現在のビデオが終了しても、プレイリストは停止しません
|
||||
Close Banner: バナーを閉じる
|
||||
Go to page: '{page}に行く'
|
||||
|
|
|
@ -41,6 +41,9 @@ Global:
|
|||
Counts:
|
||||
Subscriber Count: 1 prenumeruoti | {count} prenumeratorių
|
||||
Channel Count: 1 kanalas | {count} kanalai
|
||||
Video Count: 1 vaizdo įrašas | {count} vaizdo įrašai
|
||||
View Count: 1 peržiūrėti | {count} peržiūrų
|
||||
Watching Count: 1 žiūri | {count} žiūri
|
||||
Version {versionNumber} is now available! Click for more details: 'Versija {versionNumber}
|
||||
jau prieinama! Spustelėkite, jei norite gauti daugiau informacijos'
|
||||
Download From Site: 'Atsisiųsti iš svetainės'
|
||||
|
@ -904,3 +907,4 @@ Clipboard:
|
|||
be saugaus ryšio
|
||||
Preferences: Nuostatos
|
||||
Go to page: Eiti į {page}
|
||||
Close Banner: Uždaryti baneris
|
||||
|
|
|
@ -166,6 +166,7 @@ User Playlists:
|
|||
Select a playlist to add your N videos to: Selecteer een afspeellijst om uw video
|
||||
aan toe te voegen | Selecteer een afspeellijst om uw {videoCount} video's aan
|
||||
toe te voegen
|
||||
Added {count} Times: '{count} keer toegevoegd | {count} keer toegevoegd'
|
||||
Save Changes: Wijzigingen opslaan
|
||||
Copy Playlist: Afspeellijst kopiëren
|
||||
Create New Playlist: Nieuwe afspeellijst aanmaken
|
||||
|
|
|
@ -143,6 +143,7 @@ User Playlists:
|
|||
dodać swój film | Wybierz playlistę, do której chcesz dodać swoje {videoCount}
|
||||
film(y/ów)
|
||||
N playlists selected: Zaznaczono {playlistCount}
|
||||
Added {count} Times: Dodano {count} raz | Dodano {count} razy
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Nie było żadnych filmów do usunięcia.
|
||||
|
@ -1221,3 +1222,4 @@ Trimmed input must be at least N characters long: Przycięte wyrażenie musi mie
|
|||
Age Restricted:
|
||||
This video is age restricted: Ten film ma ograniczenie wiekowe
|
||||
This channel is age restricted: Ten kanał ma ograniczenie wiekowe
|
||||
Close Banner: Zamknij Baner
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,6 +42,8 @@ Global:
|
|||
View Count: 1 vizionare | {count} vizionări
|
||||
Channel Count: 1 canal | {count} canale
|
||||
Watching Count: 1 se uită | {count} se uită
|
||||
Input Tags:
|
||||
Length Requirement: Tag-ul trebuie să aibă cel puțin {number} caractere
|
||||
Version {versionNumber} is now available! Click for more details: 'Versiunea {versionNumber}
|
||||
este acum disponibilă! Click pentru mai multe detalii'
|
||||
Download From Site: 'Descărcați de pe site'
|
||||
|
@ -108,6 +110,8 @@ Subscriptions:
|
|||
All Subscription Tabs Hidden: Toate filele de abonament sunt ascunse. Pentru a vedea
|
||||
conținutul aici, vă rugăm să afișați unele file din secțiunea "{subsection}” din
|
||||
"{settingsSection}”.
|
||||
Empty Posts: Canalele tale abonate nu au momentan nicio postare.
|
||||
Load More Posts: Încarcă mai multe postări
|
||||
Trending:
|
||||
Trending: 'Tendințe'
|
||||
Trending Tabs: File în tendințe
|
||||
|
@ -1003,3 +1007,5 @@ Playlist will pause when current video is finished: Lista de redare se va între
|
|||
când videoclipul curent este terminat
|
||||
Playlist will not pause when current video is finished: Lista de redare nu se va întrerupe
|
||||
când videoclipul curent este terminat
|
||||
Go to page: Mergeți la {page}
|
||||
Close Banner: Închideți bannerul
|
||||
|
|
|
@ -149,6 +149,7 @@ User Playlists:
|
|||
да додате видео снимак | Изаберите плејлисту на коју желите да додате {videoCount}
|
||||
видео снимака
|
||||
N playlists selected: 'Изабрано: {playlistCount}'
|
||||
Added {count} Times: Додато {count} пут | Додато {count} пута
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: Није било видео снимака за уклањање.
|
||||
|
|
|
@ -178,6 +178,7 @@ User Playlists:
|
|||
kullanımına geri dönüldü
|
||||
This playlist is now used for quick bookmark: Bu oynatma listesi artık hızlı
|
||||
yer imi için kullanılıyor
|
||||
Search for Videos: Video Ara
|
||||
AddVideoPrompt:
|
||||
Select a playlist to add your N videos to: Videonuzu eklemek için bir oynatma
|
||||
listesi seçin | {videoCount} videonuzu eklemek için bir oynatma listesi seçin
|
||||
|
@ -191,6 +192,7 @@ User Playlists:
|
|||
| {videoCount} video 1 oynatma listesine eklendi
|
||||
N playlists selected: '{playlistCount} Seçildi'
|
||||
Search in Playlists: Oynatma Listelerinde Ara
|
||||
Added {count} Times: '{count} Defa Eklendi | {count} Defa Eklendi'
|
||||
CreatePlaylistPrompt:
|
||||
New Playlist Name: Yeni Oynatma Listesi Adı
|
||||
Create: Oluştur
|
||||
|
|
|
@ -43,6 +43,8 @@ Global:
|
|||
Subscriber Count: 1 підписник | {count} підписників
|
||||
View Count: 1 перегляд | {count} переглядів
|
||||
Watching Count: 1 глядач | {count} глядачів
|
||||
Input Tags:
|
||||
Length Requirement: Тег повинен мати довжину не менше {number} символів
|
||||
Version {versionNumber} is now available! Click for more details: 'Доступна нова
|
||||
версія {versionNumber}! Натисніть, щоб переглянути подробиці'
|
||||
Download From Site: 'Завантажити з сайту'
|
||||
|
@ -116,17 +118,36 @@ Trending:
|
|||
Music: Музика
|
||||
Default: Типово
|
||||
Most Popular: 'Найпопулярніші'
|
||||
Playlists: 'Добірки'
|
||||
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: Збережені
|
||||
відео порожні. Клацніть на кнопку збереження у куті відео, щоб воно було перелічено
|
||||
тут
|
||||
Playlist Message: Ця сторінка не показує повністю робочих добірок. На ній перелічено
|
||||
лише відео, які ви зберегли або вибрали. Коли робота завершиться, усі відео, які
|
||||
зараз знаходяться тут, буде переміщено до добірки "Вибране".
|
||||
Playlist Message: Ця сторінка не показує повністю робочих списків відтворення. На
|
||||
ній перелічено лише відео, які ви зберегли або вибрали. Коли робота завершиться,
|
||||
усі відео, які зараз знаходяться тут, буде переміщено до списку відтворення "Вибране".
|
||||
Search bar placeholder: Шукати у добірці
|
||||
Empty Search Message: Немає відео в цій добірці, які відповідають вашому запиту
|
||||
Create New Playlist: Створити новий список відтворення
|
||||
Add to Playlist: Додати список відтворення
|
||||
Remove from Favorites: Вилучити з {playlistName}
|
||||
Move Video Up: Посунути відео вгору
|
||||
Move Video Down: Посунути відео вниз
|
||||
Remove from Playlist: Вилучити зі списку відтворення
|
||||
Playlist Description: Опис списку відтворення
|
||||
Save Changes: Зберегти зміни
|
||||
Cancel: Скасувати
|
||||
Copy Playlist: Скопіювати список відтворення
|
||||
You have no playlists. Click on the create new playlist button to create a new one.: У
|
||||
вас немає списків відтворення. Натисніть на кнопку створити новий список відтворення,
|
||||
щоб створити його.
|
||||
This playlist currently has no videos.: Наразі у цьому списку відтворення немає
|
||||
відео.
|
||||
Add to Favorites: Додати до {playlistName}
|
||||
Playlist Name: Назва списку відтворення
|
||||
Edit Playlist Info: Змінити інформацію списку відтворення
|
||||
Remove Watched Videos: Вилучити з переглянутих відео
|
||||
History:
|
||||
# On History Page
|
||||
History: 'Історія'
|
||||
|
@ -321,8 +342,8 @@ Settings:
|
|||
Are you sure you want to remove all subscriptions and profiles? This cannot be undone.: 'Справді
|
||||
хочете вилучити всі підписки та профілі? Цю дію не можна скасувати.'
|
||||
Automatically Remove Video Meta Files: Автоматично вилучати метафайли відео
|
||||
Save Watched Videos With Last Viewed Playlist: Зберегти переглянуті відео в добірку,
|
||||
яку ви переглядали останнім часом
|
||||
Save Watched Videos With Last Viewed Playlist: Зберегти переглянуті відео у список
|
||||
відтворення, який ви переглядали останнім часом
|
||||
Subscription Settings:
|
||||
Subscription Settings: 'Налаштування підписки'
|
||||
Hide Videos on Watch: 'Ховати відео при перегляді'
|
||||
|
@ -342,7 +363,7 @@ Settings:
|
|||
Hide Popular Videos: 'Не показувати популярні відео'
|
||||
Hide Live Chat: 'Не показувати живий чат'
|
||||
Hide Active Subscriptions: Сховати активні підписки
|
||||
Hide Playlists: Сховати добірки
|
||||
Hide Playlists: Сховати списки відтворення
|
||||
Hide Video Description: Сховати опис відео
|
||||
Hide Comments: Сховати коментарі
|
||||
Hide Sharing Actions: Сховати дії поширення
|
||||
|
@ -354,7 +375,7 @@ Settings:
|
|||
Display Titles Without Excessive Capitalisation: Показувати заголовки без надмірно
|
||||
великих літер
|
||||
Hide Featured Channels: Сховати пропоновані канали
|
||||
Hide Channel Playlists: Сховати добірки з каналів
|
||||
Hide Channel Playlists: Сховати списки відтворення каналу
|
||||
Hide Channel Community: Сховати спільноту каналу
|
||||
Hide Channel Shorts: Сховати Shorts каналу
|
||||
Sections:
|
||||
|
@ -414,13 +435,13 @@ Settings:
|
|||
Unknown data key: 'Невідомий ключ даних'
|
||||
How do I import my subscriptions?: 'Як імпортувати свої підписки?'
|
||||
Manage Subscriptions: Керування підписками
|
||||
Playlist insufficient data: Недостатньо даних для добірки "{playlist}", пропуск
|
||||
елемента
|
||||
All playlists has been successfully exported: Усі добірки успішно експортовано
|
||||
Playlist insufficient data: Недостатньо даних для списку відтворення "{playlist}",
|
||||
пропуск елемента
|
||||
All playlists has been successfully exported: Усі списки відтворення успішно експортовано
|
||||
Import Playlists: Імпорт добірок
|
||||
Export Playlists: Експорт добірок
|
||||
All playlists has been successfully imported: Усі добірки успішно імпортовано
|
||||
Playlist File: Файл добірки
|
||||
All playlists has been successfully imported: Усі списки відтворення успішно імпортовано
|
||||
Playlist File: Файл списку відтворення
|
||||
Subscription File: Файл підписки
|
||||
History File: Файл історії
|
||||
Advanced Settings: {}
|
||||
|
@ -590,7 +611,7 @@ Channel:
|
|||
Oldest: 'Найдавніші'
|
||||
Most Popular: 'Найпопулярніші'
|
||||
Playlists:
|
||||
Playlists: 'Добірки'
|
||||
Playlists: 'Списки відтворення'
|
||||
This channel does not currently have any playlists: 'Цей канал наразі не має добірок'
|
||||
Sort Types:
|
||||
Last Video Added: 'Останнє додане відео'
|
||||
|
@ -646,9 +667,9 @@ Video:
|
|||
Open Channel in Invidious: 'Відкрити канал у Invidious'
|
||||
Copy Invidious Channel Link: 'Копіювати посилання на канал Invidious'
|
||||
Views: 'Перегляди'
|
||||
Loop Playlist: 'Зациклити добірку'
|
||||
Shuffle Playlist: 'Перемішати добірку'
|
||||
Reverse Playlist: 'Змінити напрямок добірки'
|
||||
Loop Playlist: 'Повторювати список відтворення'
|
||||
Shuffle Playlist: 'Перемішати список відтворення'
|
||||
Reverse Playlist: 'Зворотний напрямок списку відтворення'
|
||||
Play Next Video: 'Відтворити наступне відео'
|
||||
Play Previous Video: 'Відтворити попереднє відео'
|
||||
Watched: 'Переглянуто'
|
||||
|
@ -739,7 +760,7 @@ Video:
|
|||
starting video at offset: запуск відео зі зміщенням
|
||||
UnsupportedActionTemplate: '{externalPlayer} не підтримує: {action}'
|
||||
OpeningTemplate: Відкриття {videoOrPlaylist} у {externalPlayer}...
|
||||
playlist: добірка
|
||||
playlist: список відтворення
|
||||
video: відео
|
||||
OpenInTemplate: Відкрити у {externalPlayer}
|
||||
Premieres on: Прем'єри
|
||||
|
@ -782,7 +803,7 @@ Videos:
|
|||
#& Playlists
|
||||
Playlist:
|
||||
#& About
|
||||
View Full Playlist: 'Переглянути всю добірку'
|
||||
View Full Playlist: 'Переглянути весь список відтворення'
|
||||
Videos: 'Відео'
|
||||
View: 'Перегляд'
|
||||
Views: 'Переглядів'
|
||||
|
@ -791,7 +812,7 @@ Playlist:
|
|||
# On Video Watch Page
|
||||
#* Published
|
||||
#& Views
|
||||
Playlist: Добірка
|
||||
Playlist: Список відтворення
|
||||
Toggle Theatre Mode: 'Перемкнути режим театру'
|
||||
Change Format:
|
||||
Change Media Formats: 'Зміна форматів відео'
|
||||
|
@ -804,7 +825,7 @@ Change Format:
|
|||
відео'
|
||||
Share:
|
||||
Share Video: 'Поділитися відео'
|
||||
Share Playlist: 'Поділитися добіркою'
|
||||
Share Playlist: 'Поділитися списком відтворення'
|
||||
Include Timestamp: 'Включити позначку часу'
|
||||
Copy Link: 'Копіювати посилання'
|
||||
Open Link: 'Відкрити посилання'
|
||||
|
@ -915,8 +936,8 @@ Tooltips:
|
|||
програвач можна знайти за допомогою змінної середовища PATH. Якщо потрібно,
|
||||
тут можна призначити нетиповий шлях.
|
||||
External Player: Якщо обрано зовнішній програвач, з'явиться піктограма для відкриття
|
||||
відео (добірка, якщо підтримується) у зовнішньому програвачі, на мініатюрі.
|
||||
Увага, налаштування Invidious не застосовуються до сторонніх програвачів.
|
||||
відео (список відтворення, якщо підтримується) у зовнішньому програвачі, на
|
||||
мініатюрі. Увага, налаштування Invidious не застосовуються до сторонніх програвачів.
|
||||
DefaultCustomArgumentsTemplate: "(Типово: '{defaultCustomArguments}')"
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Вимикає дисковий HTTP-кеш Electron і вмикає власний кеш зображень
|
||||
|
@ -941,12 +962,12 @@ Loop is now disabled: 'Цикл вимкнено'
|
|||
Loop is now enabled: 'Цикл увімкнено'
|
||||
Shuffle is now disabled: 'Випадковий порядок вимкнено'
|
||||
Shuffle is now enabled: 'Випадковий порядок увімкнено'
|
||||
The playlist has been reversed: 'Добірку обернено'
|
||||
The playlist has been reversed: 'Список відтворення обернено'
|
||||
Playing Next Video: 'Відтворення наступного відео'
|
||||
Playing Previous Video: 'Відтворення попереднього відео'
|
||||
Canceled next video autoplay: 'Скасовано автовідтворення наступного відео'
|
||||
'The playlist has ended. Enable loop to continue playing': 'Добірка завершилася. Увімкніть
|
||||
цикл, щоб продовжити відтворення'
|
||||
'The playlist has ended. Enable loop to continue playing': 'Список відтворення завершився.
|
||||
Увімкніть повторення, щоб продовжити відтворення'
|
||||
|
||||
Yes: 'Так'
|
||||
No: 'Ні'
|
||||
|
@ -1001,10 +1022,11 @@ Ok: Гаразд
|
|||
Hashtag:
|
||||
Hashtag: Хештег
|
||||
This hashtag does not currently have any videos: За цим хештегом наразі немає відео
|
||||
Playlist will pause when current video is finished: Добірка призупиняється, коли поточне
|
||||
відео завершено
|
||||
Playlist will not pause when current video is finished: Добірка не призупиняється,
|
||||
Playlist will pause when current video is finished: Список відтворення призупиняється,
|
||||
коли поточне відео завершено
|
||||
Playlist will not pause when current video is finished: Список відтворення не призупиняється,
|
||||
коли поточне відео завершено
|
||||
Channel Hidden: '{channel} додано до фільтра каналу'
|
||||
Go to page: Перейти до {page}
|
||||
Channel Unhidden: '{channel} вилучено з фільтра каналу'
|
||||
Close Banner: Закрити банер
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Locale Name: Tiếng Anh
|
||||
Locale Name: Tiếng Việt
|
||||
FreeTube: 'FreeTube'
|
||||
# Currently on Subscriptions, Playlists, and History
|
||||
'This part of the app is not ready yet. Come back later when progress has been made.': >-
|
||||
|
@ -18,7 +18,7 @@ Select all: 'Chọn tất cả'
|
|||
Reload: 'Tải lại'
|
||||
Force Reload: 'Buộc tải lại'
|
||||
Toggle Developer Tools: 'Chuyển đổi công cụ phát triển'
|
||||
Actual size: 'Kích thước thực sự'
|
||||
Actual size: 'Kích thước thực'
|
||||
Zoom in: 'Phóng to'
|
||||
Zoom out: 'Thu nhỏ'
|
||||
Toggle fullscreen: 'Chuyển đổi toàn màn hình'
|
||||
|
@ -36,6 +36,15 @@ Global:
|
|||
# Search Bar
|
||||
Live: Trực tiếp
|
||||
Shorts: Shorts
|
||||
Community: Cộng đồng
|
||||
Counts:
|
||||
Subscriber Count: 1 người đăng ký | {count} người đăng ký
|
||||
View Count: 1 lượt xem | {count} lượt xem
|
||||
Channel Count: 1 kênh | {count} kênh
|
||||
Video Count: 1 video | {count} video
|
||||
Watching Count: 1 lượt xem | {count} lượt xem
|
||||
Input Tags:
|
||||
Length Requirement: 1 đang xem | {count} đang xem
|
||||
Search / Go to URL: 'Tìm kiếm / Đi đến URL'
|
||||
# In Filter Button
|
||||
Search Filters:
|
||||
|
@ -45,7 +54,7 @@ Search Filters:
|
|||
Most Relevant: 'Liên quan nhất'
|
||||
Rating: 'Đánh giá'
|
||||
Upload Date: 'Ngày tải lên'
|
||||
View Count: 'Lượng xem'
|
||||
View Count: 'Lượt xem'
|
||||
Time:
|
||||
Time: 'Thời gian'
|
||||
Any Time: 'Mọi lúc'
|
||||
|
@ -62,11 +71,12 @@ Search Filters:
|
|||
#& Playlists
|
||||
Movies: Phim ảnh
|
||||
Duration:
|
||||
Duration: 'Thời hạn'
|
||||
All Durations: 'Tất cả thời hạn'
|
||||
Duration: 'Thời lượng'
|
||||
All Durations: 'Tất cả thời lượng'
|
||||
Short (< 4 minutes): 'Ngắn (<4 phút)'
|
||||
Long (> 20 minutes): 'Dài (> 20 phút)'
|
||||
# On Search Page
|
||||
Medium (4 - 20 minutes): Vừa (4 - 20 phút)
|
||||
Search Results: 'Kết quả tìm kiếm'
|
||||
Fetching results. Please wait: 'Đang lấy kết quả. Xin hãy chờ'
|
||||
Fetch more results: 'Lấy thêm kết quả'
|
||||
|
@ -80,18 +90,26 @@ Subscriptions:
|
|||
sách Đăng ký đang trống. Bắt đầu thêm đăng ký để xem chúng tại đây.'
|
||||
'Getting Subscriptions. Please wait.': 'Đang lấy Đăng ký. Vui lòng đợi.'
|
||||
'Getting Subscriptions. Please wait.': Đang lấy Đăng ký. Vui lòng chờ.
|
||||
Load More Videos: Load thêm video
|
||||
Refresh Subscriptions: Refresh đăng ký
|
||||
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: Kênh
|
||||
này có nhiều người đăng ký. Buộc RSS để tránh bị giới hạn
|
||||
Error Channels: Các kênh lỗi
|
||||
Load More Videos: Tải thêm video
|
||||
Refresh Subscriptions: Tải lại đăng ký
|
||||
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: Hồ
|
||||
sơ này có nhiều đăng ký. Đang buộc RSS để tránh bị giới hạn
|
||||
Error Channels: Các kênh có lỗi
|
||||
Subscriptions Tabs: Trang đăng ký
|
||||
Disabled Automatic Fetching: Bạn đã vô hiệu hóa tự động tải đăng ký. Hãy tải lại
|
||||
danh sách đăng ký để xem chúng tại đây.
|
||||
All Subscription Tabs Hidden: Tất cả các trang đăng ký đăng bị ẩn. Để xem nội dung
|
||||
ở đây, vui lòng bỏ ẩn một số trang ở trong mục "{subsection}" của "{settingsSection}".
|
||||
Load More Posts: Tải thêm bài đăng
|
||||
Empty Channels: Các kênh bạn đăng ký hiện chưa đăng video nào.
|
||||
Empty Posts: Các kênh bạn đăng ký hiện chưa đăng bài đăng nào.
|
||||
Trending:
|
||||
Trending: 'Xu hướng'
|
||||
Movies: Phim
|
||||
Music: Âm nhạc
|
||||
Default: Mặc định
|
||||
Gaming: Trò chơi
|
||||
Trending Tabs: Tab Xu hướng
|
||||
Trending Tabs: Trang Xu hướng
|
||||
Most Popular: 'Phổ biến nhất'
|
||||
Playlists: 'Danh sách phát'
|
||||
User Playlists:
|
||||
|
@ -104,12 +122,103 @@ User Playlists:
|
|||
Playlist Message: Trang này không liệt kê tất cả danh sách video bạn đã theo giỏi.
|
||||
Nó chỉ hiển thị các video mà bạn đã lưu hoặc thêm vào mục yêu thích. Khi xong
|
||||
việc, tất cả các video trên trang này sẽ được chuyển vào danh sách 'yêu thích'.
|
||||
Remove from Playlist: Xóa khỏi danh sách phát
|
||||
Playlist Name: Tên danh sách phát
|
||||
Save Changes: Lưu thay đổi
|
||||
Remove Watched Videos: Xóa video đã xem
|
||||
Sort By:
|
||||
LatestUpdatedFirst: Được cập nhật gần đây
|
||||
LatestPlayedFirst: Được phát gần đây
|
||||
Sort By: Sắp xếp theo
|
||||
EarliestUpdatedFirst: Được cập nhật sớm nhất
|
||||
NameAscending: A-Z
|
||||
NameDescending: Z-A
|
||||
LatestCreatedFirst: Được tạo gần đây
|
||||
EarliestCreatedFirst: Được tạo sớm nhất
|
||||
EarliestPlayedFirst: Được phát sớm nhất
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
Video has been removed: Video đã được xóa
|
||||
There was an issue with updating this playlist.: Đã có vấn đề xảy ra trong khi
|
||||
cập nhập danh sách phát.
|
||||
Reverted to use {oldPlaylistName} for quick bookmark: Đã quay lại dùng {oldPlaylistName}
|
||||
cho dấu trang nhanh
|
||||
Quick bookmark disabled: Đã tắt dấu trang nhanh
|
||||
Some videos in the playlist are not loaded yet. Click here to copy anyway.: Một
|
||||
số video trong danh sách phát này chưa được tải, bạn vẫn có thể nhấn vào đây
|
||||
để sao chép.
|
||||
This playlist is protected and cannot be removed.: Danh sách phát này được bảo
|
||||
vệ và không thể bị xóa.
|
||||
Playlist {playlistName} has been deleted.: Danh sách phát {playlistName} đã
|
||||
được xóa.
|
||||
This playlist does not exist: Danh sách phát này không tồn tại
|
||||
This video cannot be moved up.: Video này không thể được chuyển lên.
|
||||
This video cannot be moved down.: Video này không thể được chuyển xuống.
|
||||
There was a problem with removing this video: Đã có vấn để xảy ra trong khi
|
||||
xóa video này
|
||||
This playlist is now used for quick bookmark: Danh sách này giờ đang được dùng
|
||||
để tạo dấu trang nhanh
|
||||
This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo: Danh
|
||||
sách này giờ đang được dùng để tạo dấu trang nhanh thay vì {oldPlaylistName}.
|
||||
Nhấn đây để hoàn tác
|
||||
Playlist name cannot be empty. Please input a name.: Tên danh sách phát không
|
||||
thể để trống. Vui lòng nhập một tên.
|
||||
Playlist has been updated.: Danh sách phát đã được cập nhật.
|
||||
"{videoCount} video(s) have been removed": 1 video đã được xóa | {videoCount}
|
||||
video đã được xóa
|
||||
There were no videos to remove.: Không có video nào để xóa.
|
||||
You have no playlists. Click on the create new playlist button to create a new one.: Bạn
|
||||
đang không có danh sách phát nào. Nhấn vào nút tạo danh sách phát mới để tạo một
|
||||
cái mới.
|
||||
This playlist currently has no videos.: Danh sách phát này hiện không có video nào.
|
||||
Edit Playlist Info: Chỉnh sửa thông tin danh sách phát
|
||||
Are you sure you want to delete this playlist? This cannot be undone: Bạn có chắc
|
||||
muốn xóa danh sách phát này?Việc này không thể hoàn tác.
|
||||
CreatePlaylistPrompt:
|
||||
Create: Tạo mới
|
||||
Toast:
|
||||
Playlist {playlistName} has been successfully created.: Danh sách phát {playlistName}
|
||||
đã được tạo thành công.
|
||||
There was an issue with creating the playlist.: Đã có vấn đề xảy ra trong khi
|
||||
tạo danh sách phát này.
|
||||
There is already a playlist with this name. Please pick a different name.: Hiện
|
||||
đã có danh sách phát với tên này rồi. Vui lòng chọn một cái tên khác.
|
||||
New Playlist Name: Tên danh sách phát mới
|
||||
AddVideoPrompt:
|
||||
Select a playlist to add your N videos to: Chọn một danh sách phát để lưu video
|
||||
này vào | Chọn một danh sách phát để lưu {videoCount} video này vào
|
||||
N playlists selected: Đã chọn {playlistCount}
|
||||
Search in Playlists: Tìm trong danh sách phát
|
||||
Toast:
|
||||
"{videoCount} video(s) added to {playlistCount} playlists": Đã thêm 1 video
|
||||
vào {playlistCount} danh sách phát | Đã thêm {videoCount} video vào {playlistCount}
|
||||
danh sách phát
|
||||
You haven't selected any playlist yet.: Bạn đang chưa chọn danh sách phát nào.
|
||||
"{videoCount} video(s) added to 1 playlist": Đã thêm 1 video vào 1 danh sách
|
||||
phát | Đã thêm {videoCount} vào 1 danh sách phát
|
||||
Save: Lưu
|
||||
Create New Playlist: Tạo danh sách phat mới
|
||||
Playlist Description: Mô tả danh sách phát
|
||||
Copy Playlist: Sao chép danh sách phát
|
||||
Enable Quick Bookmark With This Playlist: Bật dấu trang nhanh với danh sách phát
|
||||
này
|
||||
Add to Playlist: Thêm vào danh sách phát
|
||||
Cancel: Hủy bỏ
|
||||
Add to Favorites: Thêm vào {playlistName}
|
||||
Remove from Favorites: Xóa khỏi {playlistName}
|
||||
Move Video Down: Chuyển video xuống
|
||||
Move Video Up: Chuyển video lên
|
||||
Disable Quick Bookmark: Tắt dấu trang nhanh
|
||||
Delete Playlist: Xóa danh sách phát
|
||||
Are you sure you want to remove all watched videos from this playlist? This cannot be undone: Bạn
|
||||
có chắc muốn xóa tất cả các video đã xem khỏi danh sách phát này? Việc này không
|
||||
thể được hoàn tác.
|
||||
History:
|
||||
# On History Page
|
||||
History: 'Lịch sử'
|
||||
Watch History: 'Lịch sử xem'
|
||||
Your history list is currently empty.: Lịch sử của bạn hiện đang trống.
|
||||
Search bar placeholder: Tìm kiếm trong Lịch sử
|
||||
Search bar placeholder: Tìm trong lịch sử
|
||||
Empty Search Message: Không có video nào trong lịch sử của bạn trùng với những gì
|
||||
bạn đang tìm kiếm
|
||||
Settings:
|
||||
|
@ -123,8 +232,8 @@ Settings:
|
|||
Default Landing Page: 'Trang mặc định'
|
||||
Locale Preference: 'Ngôn ngữ'
|
||||
Preferred API Backend:
|
||||
Preferred API Backend: 'Backend API'
|
||||
Local API: 'Local API'
|
||||
Preferred API Backend: 'API Backend'
|
||||
Local API: 'API địa phương'
|
||||
Invidious API: 'API Invidious'
|
||||
Video View Type:
|
||||
Video View Type: 'Kiểu xem video'
|
||||
|
@ -133,16 +242,18 @@ Settings:
|
|||
Thumbnail Preference:
|
||||
Thumbnail Preference: 'Thumbnail'
|
||||
Default: 'Mặc định'
|
||||
Beginning: 'Lúc đầu'
|
||||
Middle: 'Chính giữa'
|
||||
End: 'Đầu cuối'
|
||||
Beginning: 'Đầu'
|
||||
Middle: 'Giữa'
|
||||
End: 'Cuối'
|
||||
Blur: Làm mờ
|
||||
Hidden: Ẩn
|
||||
'Invidious Instance (Default is https://invidious.snopyta.org)': 'Phiên bản Invidious
|
||||
(Mặc định là https://invidious.snopyta.org)'
|
||||
Region for Trending: 'Phổ biến theo quốc gia'
|
||||
#! List countries
|
||||
Check for Latest Blog Posts: Check Blog post mới nhất
|
||||
Check for Latest Blog Posts: Kiểm tra bài đăng mới nhất
|
||||
Check for Updates: Kiểm tra cập nhật
|
||||
Current Invidious Instance: Phiên bản invidious hiện tại
|
||||
Current Invidious Instance: Phiên bản Invidious hiện tại
|
||||
The currently set default instance is {instance}: Phiên bản Invidious mặc định
|
||||
hiện được đặt thành {instance}
|
||||
No default instance has been set: Không có phiên bản mặc định nào được đặt
|
||||
|
@ -156,7 +267,7 @@ Settings:
|
|||
No Action: Không hành động
|
||||
External Link Handling: Xử lý liên kết bên ngoài
|
||||
View all Invidious instance information: Xem tất cả thông tin phiên bản Invidious
|
||||
System Default: Mặc định Hệ thống
|
||||
System Default: Mặc định hệ thống
|
||||
Theme Settings:
|
||||
Theme Settings: 'Cài đặt chủ đề'
|
||||
Match Top Bar with Main Color: 'Khớp thanh trên cùng với màu chính'
|
||||
|
@ -165,34 +276,37 @@ Settings:
|
|||
Black: 'Đen'
|
||||
Dark: 'Tối'
|
||||
Light: 'Sáng'
|
||||
Dracula: 'Ma cà rồng'
|
||||
Dracula: 'Dracula'
|
||||
System Default: Mặc định hệ thống
|
||||
Catppuccin Mocha: Catppuccin Mocha - Màu cà phê
|
||||
Catppuccin Mocha: Catppuccin Mocha
|
||||
Nordic: Nordic
|
||||
Pastel Pink: Hồng phấn tiên
|
||||
Hot Pink: Hồng nóng bỏng
|
||||
Main Color Theme:
|
||||
Main Color Theme: 'Màu chủ đề chính'
|
||||
Red: 'Đỏ'
|
||||
Pink: 'Hồng'
|
||||
Purple: 'Tím'
|
||||
Deep Purple: 'Tím Đậm'
|
||||
Indigo: 'Xanh Đậm'
|
||||
Blue: 'Xanh'
|
||||
Light Blue: 'Xanh Nhạt'
|
||||
Cyan: 'Lục Lam'
|
||||
Deep Purple: 'Tím đậm'
|
||||
Indigo: 'Chàm'
|
||||
Blue: 'Xanh lam'
|
||||
Light Blue: 'Xanh nước biển'
|
||||
Cyan: 'Xanh lơ'
|
||||
Teal: 'Xanh mòng két'
|
||||
Green: 'Xanh Lá'
|
||||
Light Green: 'Xanh Lợt'
|
||||
Lime: 'Vôi'
|
||||
Green: 'Xanh lục'
|
||||
Light Green: 'Xanh lợt'
|
||||
Lime: 'Vàng chanh'
|
||||
Yellow: 'Vàng'
|
||||
Amber: 'Hổ Phách'
|
||||
Amber: 'Hổ phách'
|
||||
Orange: 'Cam'
|
||||
Deep Orange: 'Cam Đậm'
|
||||
Dracula Cyan: 'Ma cà rồng Lục Lam'
|
||||
Dracula Green: 'Ma cà rồng Xanh Lá'
|
||||
Dracula Orange: 'Ma cà rồng Cam'
|
||||
Dracula Pink: 'Ma cà rồng Hồng'
|
||||
Dracula Purple: 'Ma cà rồng Tím'
|
||||
Dracula Red: 'Ma cà rồng Đỏ'
|
||||
Dracula Yellow: 'Ma cà rồng Vàng'
|
||||
Deep Orange: 'Cam đậm'
|
||||
Dracula Cyan: 'Xanh lơ Dracula'
|
||||
Dracula Green: 'Xanh lục Dracula'
|
||||
Dracula Orange: 'Cam Dracula'
|
||||
Dracula Pink: 'Hồng Dracula'
|
||||
Dracula Purple: 'Tím Dracula'
|
||||
Dracula Red: 'Đỏ Dracula'
|
||||
Dracula Yellow: 'Vàng Dracula'
|
||||
Catppuccin Mocha Rosewater: Catppuccin Mocha Rosewater Màu hoa hồng
|
||||
Catppuccin Mocha Flamingo: Catppuccin Mocha Flamingo Màu hồng hạc
|
||||
Catppuccin Mocha Pink: Catppuccin Mocha Pink Màu hồng hạc
|
||||
|
@ -207,13 +321,13 @@ Settings:
|
|||
Catppuccin Mocha Sapphire: Catppuccin Mocha Sapphire màu xanh
|
||||
Catppuccin Mocha Lavender: Catppuccin Mocha Lavender Màu tím nhạt
|
||||
Catppuccin Mocha Blue: Catppuccin Mocha Blue Màu xanh
|
||||
Secondary Color Theme: 'Màu chủ đề thứ hai'
|
||||
Secondary Color Theme: 'Màu chủ đề phụ'
|
||||
#* Main Color Theme
|
||||
UI Scale: Tỉ lệ UI
|
||||
Disable Smooth Scrolling: Tắt cuộn mượt
|
||||
Expand Side Bar by Default: Mở rộng thanh bên theo mặc định
|
||||
Hide Side Bar Labels: Ẩn Nhãn Thanh Bên
|
||||
Hide FreeTube Header Logo: Ẩn Logo FreeTube trên thanh trên
|
||||
Hide Side Bar Labels: Ẩn nhãn thanh bên
|
||||
Hide FreeTube Header Logo: Ẩn logo FreeTube trên thanh trên
|
||||
Player Settings:
|
||||
Player Settings: 'Cài đặt trình phát'
|
||||
Force Local Backend for Legacy Formats: 'Bắt buộc Local Backend cho định dạng
|
||||
|
@ -221,14 +335,14 @@ Settings:
|
|||
Remember History: 'Nhớ lịch sử'
|
||||
Play Next Video: 'Phát video tiếp theo'
|
||||
Turn on Subtitles by Default: 'Bật phụ đề theo mặc định'
|
||||
Autoplay Videos: 'Tự phát videos'
|
||||
Autoplay Videos: 'Tự phát video'
|
||||
Proxy Videos Through Invidious: 'Proxy video qua Invidious'
|
||||
Autoplay Playlists: 'Danh sách tự động phát'
|
||||
Enable Theatre Mode by Default: 'Bật chế độ rạp hát theo mặc định'
|
||||
Default Volume: 'Âm lượng mặc định'
|
||||
Default Playback Rate: 'Tốc độ phát mặc định'
|
||||
Default Video Format:
|
||||
Default Video Format: 'Định dạng video theo mặc định'
|
||||
Default Video Format: 'Định dạng video mặc định'
|
||||
Dash Formats: 'Định dạng DASH'
|
||||
Legacy Formats: 'Định dạng Legacy'
|
||||
Audio Formats: 'Định dạng âm thanh'
|
||||
|
@ -244,21 +358,21 @@ Settings:
|
|||
1440p: '1440p'
|
||||
4k: '4k'
|
||||
8k: '8k'
|
||||
Scroll Playback Rate Over Video Player: Tốc độ Phát lại Cuộn qua Trình phát Video
|
||||
Scroll Volume Over Video Player: Cuộn Âm lượng qua Trình phát Video
|
||||
Scroll Playback Rate Over Video Player: Con lăn chuột điểu chỉnh tốc độ phát lại
|
||||
Scroll Volume Over Video Player: Con lăn chuột điểu chỉnh âm lượng
|
||||
Display Play Button In Video Player: Hiển thị nút phát trong trình phát video
|
||||
Next Video Interval: Khoảng thời gian Video Tiếp theo
|
||||
Fast-Forward / Rewind Interval: Khoảng thời gian tua đi / tua lại
|
||||
Screenshot:
|
||||
Enable: Bật chức năng Chụp màn hình
|
||||
Enable: Bật chức năng chụp màn hình
|
||||
Format Label: Định dạng chụp màn hình
|
||||
Quality Label: Chất lượng chụp màn hình
|
||||
File Name Label: Kiểu tên tệp
|
||||
Folder Label: Chụp màn hình thư mục
|
||||
File Name Label: Mẫu tên tệp
|
||||
Folder Label: Thư mục ảnh chụp màn hình
|
||||
Ask Path: Yêu cầu thứ mục lưu
|
||||
Folder Button: Chọn thư mục
|
||||
Error:
|
||||
Empty File Name: Tên tệp. trống
|
||||
Empty File Name: Tên tệp trống
|
||||
Forbidden Characters: Các ký từ bị cấm
|
||||
File Name Tooltip: Bạn có thể dùng các biến số dưới đây. %Y Năm 4 chữ số. %M
|
||||
Tháng 2 chữ số. %D Ngày 2 chữ số. %H Giờ 2 chữ số. %N Phút 2 chữ số. %S Giây
|
||||
|
@ -266,8 +380,12 @@ Settings:
|
|||
3 chữ số. %i Video ID. Bạn cũng có thể dùng dấu "\" hoặc "/" để tạo các thư
|
||||
mục con.
|
||||
Max Video Playback Rate: Tốc độ phát lại tối đa
|
||||
Video Playback Rate Interval: khoảng cách phát lại video
|
||||
Video Playback Rate Interval: Khoảng cách tốc độ phát
|
||||
Enter Fullscreen on Display Rotate: Bật toàn màn hình khi xoay
|
||||
Comment Auto Load:
|
||||
Comment Auto Load: Tự động tải bình luận
|
||||
Skip by Scrolling Over Video Player: Tua video bằng con lăn chuột
|
||||
Allow DASH AV1 formats: Cho phép định dạng DASH AV1
|
||||
Subscription Settings:
|
||||
Subscription Settings: 'Cài đặt đăng ký'
|
||||
Hide Videos on Watch: 'Ẩn video khi đã xem'
|
||||
|
@ -280,8 +398,9 @@ Settings:
|
|||
Import Subscriptions: 'Nhập đăng ký'
|
||||
Export Subscriptions: 'Xuất đăng ký'
|
||||
How do I import my subscriptions?: 'Làm sao để nhập đăng ký của tôi?'
|
||||
Fetch Feeds from RSS: Lấy feeds từ RSS
|
||||
Fetch Feeds from RSS: Cập nhật bảng tin qua RSS
|
||||
Fetch Automatically: Tự động làm mới bảng tin
|
||||
Only Show Latest Video for Each Channel: Chỉ hiện video mới nhất cho mỗi kênh
|
||||
Advanced Settings:
|
||||
Advanced Settings: 'Cài đặt nâng cao'
|
||||
Enable Debug Mode (Prints data to the console): 'Bật chế độ Debug (Ghi data ra
|
||||
|
@ -310,27 +429,27 @@ Settings:
|
|||
|
||||
Data Settings:
|
||||
How do I import my subscriptions?: Làm sao để tôi nhập đăng ký?
|
||||
Unknown data key: Key data không xác định
|
||||
Unable to write file: Không thể viết file
|
||||
Unable to read file: Không thể đọc file
|
||||
Unknown data key: Key dữ liệu không xác định
|
||||
Unable to write file: Không thể viết tệp
|
||||
Unable to read file: Không thể đọc tệp
|
||||
All watched history has been successfully exported: Tất cả lịch sử xem đã được
|
||||
xuất ra thành công
|
||||
All watched history has been successfully imported: Tất cả lịch sử xem đã được
|
||||
nhập vào thành công
|
||||
History object has insufficient data, skipping item: Lịch sử object không đủ dữ
|
||||
liệu, bỏ qua
|
||||
History object has insufficient data, skipping item: Lịch sử không đủ dữ liệu,
|
||||
đang bỏ qua mục này
|
||||
Subscriptions have been successfully exported: Đăng ký đã được xuất thành công
|
||||
Invalid history file: File lịch sử không hợp lệ
|
||||
Invalid history file: Tệp lịch sử không hợp lệ
|
||||
This might take a while, please wait: Điều này có thể tốn thời gian, xin hãy chờ
|
||||
Invalid subscriptions file: File đăng ký không hợp lệ
|
||||
One or more subscriptions were unable to be imported: Một hay hơn đăng ký không
|
||||
Invalid subscriptions file: Tệp đăng ký không hợp lệ
|
||||
One or more subscriptions were unable to be imported: Một hay nhiều đăng ký không
|
||||
thể nhập
|
||||
All subscriptions have been successfully imported: Tất cả đăng ký đã được nhập
|
||||
vào thành công
|
||||
All subscriptions and profiles have been successfully imported: Tất cả đăng ký
|
||||
và profiles đã được nhập vào thành công
|
||||
Profile object has insufficient data, skipping item: Profile object không đủ dữ
|
||||
liệu, bỏ qua
|
||||
All subscriptions and profiles have been successfully imported: Tất cả các đăng
|
||||
ký và hồ sơ đã được nhập thành công
|
||||
Profile object has insufficient data, skipping item: Hồ sơ không đủ dữ liệu, đang
|
||||
bỏ qua mục này
|
||||
Export History: Xuất lịch sử
|
||||
Import History: Nhập lịch sử
|
||||
Export NewPipe: Xuất NewPipe
|
||||
|
@ -344,9 +463,9 @@ Settings:
|
|||
Import Subscriptions: Nhập đăng ký
|
||||
Select Export Type: Chọn kiểu xuất ra
|
||||
Select Import Type: Chọn kiểu nhập vào
|
||||
Data Settings: Dữ liệu
|
||||
Data Settings: Cài đặt dữ liệu
|
||||
Manage Subscriptions: Quản lý đăng ký
|
||||
Import Playlists: Thêm danh sách phát
|
||||
Import Playlists: Nhập danh sách phát
|
||||
All playlists has been successfully imported: Tất cả các danh sách phát đã được
|
||||
thêm vào thành công
|
||||
All playlists has been successfully exported: Tất cả các danh sách phát đã được
|
||||
|
@ -354,36 +473,64 @@ Settings:
|
|||
Playlist insufficient data: Dữ liệu bị thiếu cho danh sách phát "{playlist}",
|
||||
bỏ qua mục này
|
||||
Export Playlists: Xuất danh sách phát
|
||||
History File: Tệp Lịch sử
|
||||
Playlist File: Tệp Danh sách phát
|
||||
History File: Tệp lịch sử
|
||||
Playlist File: Tệp danh sách phát
|
||||
Subscription File: Tệp đăng ký
|
||||
Export Playlists For Older FreeTube Versions:
|
||||
Label: Xuất danh sách phát cho các phiên bản FreeTube cũ hơn
|
||||
Tooltip: "Tùy chọn này sẽ gộp tất cả các danh sách phát vào một danh sách phát
|
||||
mang tên 'Ưa thích'.\nCách để nhập & xuất video trong danh sách phát cho một
|
||||
phiên bản cũ hơn của FreeTube:\n1. Xuất danh sách phát với tùy chọn này được
|
||||
bật.\n2. Xóa tất cả các danh sách phát qua tùy chọn Xóa tất cả danh sách phát
|
||||
trong mục Cài đặt quyền riêng tư.\n3. Mở phiên bản cũ hơn và nhập danh sách
|
||||
phát đã được xuất ra."
|
||||
Distraction Free Settings:
|
||||
Hide Live Chat: Giấu live chat
|
||||
Hide Popular Videos: Giấu video phổ biến
|
||||
Hide Trending Videos: Giấu video xu hướng
|
||||
Hide Recommended Videos: Giấu video nên xem
|
||||
Hide Comment Likes: Giấu bình luận like
|
||||
Hide Channel Subscribers: Giấu số người đăng ký
|
||||
Hide Video Likes And Dislikes: Giấu thích và không thích
|
||||
Hide Video Views: Giấu lượt xem
|
||||
Distraction Free Settings: Chế độ không phân tâm
|
||||
Hide Active Subscriptions: Ẩn Đăng ký Hiện hoạt
|
||||
Hide Live Chat: Ẩn live chat
|
||||
Hide Popular Videos: Ẩn video phổ biến
|
||||
Hide Trending Videos: Ẩn video xu hướng
|
||||
Hide Recommended Videos: Ẩn video tiếp theo
|
||||
Hide Comment Likes: Ẩn lượt thích bình luận
|
||||
Hide Channel Subscribers: Ẩn số người đăng ký
|
||||
Hide Video Likes And Dislikes: Ẩn lượt thích và không thích
|
||||
Hide Video Views: Ẩn lượt xem
|
||||
Distraction Free Settings: Cài đặt không phân tâm
|
||||
Hide Active Subscriptions: Ẩn đăng ký hiện hoạt
|
||||
Hide Playlists: Ẩn danh sách phát
|
||||
Hide Comments: Ẩn bình luận
|
||||
Hide Live Streams: Ẩn phát trực tiếp
|
||||
Hide Video Description: Ẩn mổ tả video
|
||||
Hide Sharing Actions: Ẩn hoạt động chia sẻ
|
||||
Hide Sharing Actions: Ẩn nút chia sẻ
|
||||
Sections:
|
||||
General: Chung
|
||||
Side Bar: Thanh bên
|
||||
Channel Page: Trang kênh
|
||||
Subscriptions Page: Trang đăng ký
|
||||
Watch Page: Trang trình chiếu video
|
||||
Hide Channel Playlists: Ẩn danh sách phát của kênh
|
||||
Hide Featured Channels: Ẩn các kênh nổi bật
|
||||
Hide Channels Placeholder: Tên kênh hoặc ID
|
||||
Hide Profile Pictures in Comments: Ẩn ảnh đại diện trong Bình luận
|
||||
Hide Channels Placeholder: ID kênh
|
||||
Hide Profile Pictures in Comments: Ẩn ảnh đại diện trong bình luận
|
||||
Hide Chapters: Ẩn các chương
|
||||
Hide Channels: Ẩn các videos khỏi kênh
|
||||
Hide Channels: Ẩn các video khỏi kênh
|
||||
Hide Channel Releases: Ẩn nhạc của kênh
|
||||
Hide Videos and Playlists Containing Text: Ẩn các video và danh sách phát có chứa
|
||||
Hide Channels Invalid: ID kênh không hợp lệ
|
||||
Hide Channel Community: Ẩn cộng đồng của kênh
|
||||
Hide Channel Shorts: Ẩn short của kênh
|
||||
Hide Subscriptions Shorts: Ẩn short từ các đăng ký
|
||||
Hide Subscriptions Live: Ẩn video phát trực tiếp từ các đăng ký
|
||||
Hide Upcoming Premieres: Ẩn video sắp ra mắt
|
||||
Hide Channel Podcasts: Ẩn podcast của kênh
|
||||
Hide Subscriptions Community: Ẩn bài đăng cộng đồng từ các đăng ký
|
||||
Hide Channels Already Exists: ID kênh đã tồn tại
|
||||
Hide Videos and Playlists Containing Text Placeholder: Từ, tiếng, hoặc cụm từ
|
||||
Hide Subscriptions Videos: Ẩn video từ các đăng ký
|
||||
Display Titles Without Excessive Capitalisation: Hiển thị tiêu đề không viết hoa
|
||||
quá mức
|
||||
Privacy Settings:
|
||||
Are you sure you want to remove all subscriptions and profiles? This cannot be undone.: Bạn
|
||||
có muốn xóa toàn bộ đăng ký và profiles không? Điều này không thể phục hồi.
|
||||
Remove All Subscriptions / Profiles: Xóa bỏ tất cả đăng ký / Profiles
|
||||
có muốn xóa toàn bộ đăng ký và hồ sơ không? Điều này không thể phục hồi.
|
||||
Remove All Subscriptions / Profiles: Xóa tất cả đăng ký / hồ sơ
|
||||
Watch history has been cleared: Lịch sử xem đã được xóa
|
||||
Are you sure you want to remove your entire watch history?: Bạn có thực sự muốn
|
||||
xóa toàn bộ lịch sử xem?
|
||||
|
@ -391,58 +538,70 @@ Settings:
|
|||
Search cache has been cleared: Bộ đệm của tìm kiếm đã xóa
|
||||
Are you sure you want to clear out your search cache?: Bạn có chắc là muốn xóa
|
||||
bộ đệm của tìm kiếm?
|
||||
Clear Search Cache: Xóa Tìm kiếm cache
|
||||
Clear Search Cache: Xóa cache tìm kiếm
|
||||
Save Watched Progress: Lưu quá trình xem
|
||||
Remember History: Nhớ lịch sử
|
||||
Privacy Settings: Thiết lập quyền riêng tư
|
||||
Automatically Remove Video Meta Files: Tự động xúa các tệp meta video
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: App
|
||||
cần khởi động lại để chỉnh sửa có hiệu nghiệm. Khởi động lại và áp đặt?
|
||||
Privacy Settings: Cài đặt quyền riêng tư
|
||||
Automatically Remove Video Meta Files: Tự động xóa các tệp meta video
|
||||
Remove All Playlists: Xóa tất cả danh sách phát
|
||||
Are you sure you want to remove all your playlists?: Bạn có chắc muốn xóa tất
|
||||
cả các danh sách phát không?
|
||||
All playlists have been removed: Đã xóa tất cả các danh sách phát
|
||||
Save Watched Videos With Last Viewed Playlist: Lưu video đã xem với danh sách
|
||||
phát được xem lần cuối
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Ứng
|
||||
dụng cần được khởi động lại để chỉnh sửa có hiệu nghiệm. Khởi động lại và áp dụng
|
||||
cài đặt?
|
||||
Proxy Settings:
|
||||
Proxy Host: Máy chủ Proxy
|
||||
Proxy Host: Máy chủ proxy
|
||||
Region: Vùng
|
||||
Country: Quốc gia
|
||||
Proxy Settings: Cài đặt proxy
|
||||
Enable Tor / Proxy: Bật Tor / Proxy
|
||||
Proxy Protocol: Giao thức proxy
|
||||
Proxy Port Number: Số Cổng Proxy
|
||||
Proxy Port Number: Số cổng proxy
|
||||
City: Thành phố
|
||||
Ip: Ip
|
||||
Ip: IP
|
||||
Your Info: Thông tin của bạn
|
||||
Error getting network information. Is your proxy configured properly?: Lỗi nhận
|
||||
thông tin mạng. Proxy của bạn đã được cài đặc đúng cách chưa?
|
||||
Clicking on Test Proxy will send a request to: Nhấn vào Proxy thử nghiệm sẽ gửi
|
||||
yêu cầu đến
|
||||
Test Proxy: Proxy thử nghiệm
|
||||
Clicking on Test Proxy will send a request to: Nhấn vào Thử proxy sẽ gửi yêu cầu
|
||||
đến
|
||||
Test Proxy: Thử proxy
|
||||
SponsorBlock Settings:
|
||||
Enable SponsorBlock: Bật SponsorBlock
|
||||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock API
|
||||
Url (Mặc định là https://sponsor.ajay.app)
|
||||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': URL API SponsorBlock
|
||||
(Mặc định là https://sponsor.ajay.app)
|
||||
Skip Options:
|
||||
Skip Option: Tuỳ chọn lượt bỏ
|
||||
Show In Seek Bar: Hiển thị trong thanh tìm kiếm
|
||||
Auto Skip: Tự động lượt bỏ
|
||||
Prompt To Skip: Nhắc nhở lượt bỏ
|
||||
Do Nothing: Không làm gì hết
|
||||
Skip Option: Tuỳ chọn bỏ qua
|
||||
Show In Seek Bar: Hiển thị trong thanh tiến trình
|
||||
Auto Skip: Tự động bỏ qua
|
||||
Prompt To Skip: Nhắc nhở bỏ qua
|
||||
Do Nothing: Không làm gì
|
||||
Notify when sponsor segment is skipped: Thông báo khi đoạn quảng cáo bị bỏ qua
|
||||
Category Color: Bản màu
|
||||
SponsorBlock Settings: Cài đặt SponsorBlock
|
||||
'DeArrow Thumbnail Generator API Url (Default is https://dearrow-thumb.ajay.app)': URL
|
||||
API ảnh xem trước DeArrow (Mặc định là https://dearrow-thumb.ajay.app)
|
||||
UseDeArrowTitles: Dùng tiêu đề video từ DeArrow
|
||||
UseDeArrowThumbnails: Dùng ảnh xem trước từ DeArrow
|
||||
External Player Settings:
|
||||
External Player Settings: Cài đặt trình phát video bên ngoài
|
||||
External Player: Trình phát video bên ngoài
|
||||
Custom External Player Arguments: Chứng minh trình phát bên ngoài tùy chỉnh
|
||||
Custom External Player Arguments: Tham số trình phát bên ngoài tùy chỉnh
|
||||
Ignore Unsupported Action Warnings: Bỏ qua các cảnh bảo tác vụ không được hổ trợ
|
||||
Custom External Player Executable: Chạy trình phát bên ngoài tuỳ chỉnh
|
||||
Players:
|
||||
None:
|
||||
Name: Trống
|
||||
Ignore Default Arguments: Bỏ qua tham số mặc định
|
||||
Parental Control Settings:
|
||||
Parental Control Settings: Cài đặt trình kiểm soát của phụ huynh
|
||||
Hide Unsubscribe Button: Ẩn Nút Huỷ Đăng Ký
|
||||
Show Family Friendly Only: Chỉ hiển thị những nội dung gia đình và thân thiện
|
||||
Parental Control Settings: Cài đặt kiểm soát của phụ huynh
|
||||
Hide Unsubscribe Button: Ẩn nút huỷ đăng ký
|
||||
Show Family Friendly Only: Chỉ hiển thị nội dung thân thiện với gia đình
|
||||
Hide Search Bar: Ẩn thanh tìm kiếm
|
||||
Download Settings:
|
||||
Download Behavior: Thói quen tải xuống
|
||||
Download Behavior: Hành vi tải xuống
|
||||
Download in app: Tải xuống trong ứng dụng
|
||||
Open in web browser: Mở trên trình duyệt
|
||||
Ask Download Path: Yêu cầu đường dẫn tải xuống
|
||||
|
@ -451,11 +610,11 @@ Settings:
|
|||
Password Dialog:
|
||||
Password: Mật khẩu
|
||||
Password Incorrect: Mật khẩu sai
|
||||
Enter Password To Unlock: Nhập mật khẩu để mở Cài đặt
|
||||
Enter Password To Unlock: Nhập mật khẩu để mở cài đặt
|
||||
Unlock: Mở khoá
|
||||
Password Settings:
|
||||
Set Password To Prevent Access: Đặt mặt khẩu để ngăn truy cập cài đặt
|
||||
Password Settings: Cài đặt Mật khẩu
|
||||
Password Settings: Cài đặt mật khẩu
|
||||
Remove Password: Xoá mật khẩu
|
||||
Set Password: Đặt mật khẩu
|
||||
Experimental Settings:
|
||||
|
@ -463,6 +622,8 @@ Settings:
|
|||
cài đặt này có thể gây ra hoạt động bất ổn định. Hãy tạo phương án dự phòng
|
||||
trước khi bật!
|
||||
Experimental Settings: Cài đặt thử nghiệm
|
||||
Replace HTTP Cache: Thay thế bộ đệm HTTP
|
||||
Expand All Settings Sections: Mở rộng tất cả các mục tùy chọn
|
||||
About:
|
||||
#On About page
|
||||
About: 'Giới thiệu'
|
||||
|
@ -496,29 +657,29 @@ About:
|
|||
|
||||
#On Channel Page
|
||||
Mastodon: Mastodon
|
||||
Email: Thư điện tử
|
||||
Email: Email
|
||||
Source code: Mã nguồn
|
||||
FAQ: Câu hỏi thường gặp
|
||||
Report a problem: Báo cáo sự cố
|
||||
Licensed under the AGPLv3: Được cấp phép theo AGPLv3
|
||||
View License: Xem Giấy phép
|
||||
View License: Xem giấy phép
|
||||
Help: Trợ giúp
|
||||
Translate: Phiên dịch
|
||||
Website: Trang web
|
||||
Blog: Blog
|
||||
Credits: Tín dụng
|
||||
Credits: Ghi công
|
||||
Donate: Quyên tặng
|
||||
GitHub issues: Sự cố GitHub
|
||||
FreeTube Wiki: FreeTube Wiki
|
||||
Beta: Thử nghiệm
|
||||
Downloads / Changelog: Tải xuống / bản ghi changelog
|
||||
GitHub releases: Phiên bản GitHub
|
||||
Please check for duplicates before posting: Vui lòng kiểm tra các bản sao trước
|
||||
khi đăng
|
||||
Chat on Matrix: Trò chuyện trên Ma trận
|
||||
Downloads / Changelog: Tải xuống / nhật ký thay đổi
|
||||
GitHub releases: Tải xuống trên GitHub
|
||||
Please check for duplicates before posting: Vui lòng kiểm tra trùng lặp trước khi
|
||||
đăng
|
||||
Chat on Matrix: Trò chuyện trên Matrix
|
||||
room rules: quy định phòng chat
|
||||
FreeTube is made possible by: FreeTube được tạo ra bởi
|
||||
these people and projects: những người và dự án
|
||||
these people and projects: những người và dự án này
|
||||
Please read the: Hãy đọc
|
||||
Discussions: Thảo luận
|
||||
Channel:
|
||||
|
@ -551,6 +712,7 @@ Channel:
|
|||
Featured Channels: 'Kênh đặc sắc'
|
||||
Tags:
|
||||
Search for: Tìm kiếm cho "{tag}"
|
||||
Tags: Thẻ
|
||||
Joined: Đã tham gia
|
||||
Location: Vị trí
|
||||
Details: Chi tiết
|
||||
|
@ -572,10 +734,21 @@ Channel:
|
|||
votes: '{votes} bình chọn'
|
||||
Reveal Answers: Hiện Câu trả lời
|
||||
Hide Answers: Ẩn Câu trả lời
|
||||
Video hidden by FreeTube: FreeTube đã ẩn video này
|
||||
Releases:
|
||||
Releases: Xuất bản
|
||||
This channel does not currently have any releases: Kênh này hiện không có bất
|
||||
kỳ bản phát hành nào
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: Kênh
|
||||
này là kênh giới hạn độ tuổi và hiện tại không thể xem được trên FreeTube.
|
||||
Channel Tabs: Trang kênh
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Kênh này hiện không có video
|
||||
shorts
|
||||
Podcasts:
|
||||
Podcasts: Podcast
|
||||
This channel does not currently have any podcasts: Kênh này hiện không có podcast
|
||||
nào
|
||||
Video:
|
||||
Open in YouTube: 'Mở trong Youtube'
|
||||
Copy YouTube Link: 'Sao chép liên kết Youtube'
|
||||
|
@ -706,6 +879,13 @@ Video:
|
|||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Trò
|
||||
chuyện trực tiếp hiện không khả dụng trên luồng phát này. Chủ sở hữu có thể đã
|
||||
tắt chức năng này.
|
||||
Hide Channel: Ẩn kênh này
|
||||
More Options: Tùy chọn khác
|
||||
Pause on Current Video: Tạm dừng trên video hiện tại
|
||||
Show Super Chat Comment: Hiển thị bình luận Super Chat
|
||||
Upcoming: Sắp ra mắt
|
||||
Premieres: Công chiếu
|
||||
Unhide Channel: Hiển thị kênh này
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -750,6 +930,7 @@ Share:
|
|||
Include Timestamp: Có kèm dấu thời gian
|
||||
YouTube Channel URL copied to clipboard: Đã sao chép liên kết kênh Youtube
|
||||
Invidious Channel URL copied to clipboard: URL của kênh ưu tiên đã được sao chép
|
||||
Share Channel: Chia sẻ kênh
|
||||
Mini Player: 'Trình phát Mini'
|
||||
Comments:
|
||||
Comments: 'Bình luận'
|
||||
|
@ -776,6 +957,8 @@ Comments:
|
|||
Pinned by: Được ghim bởi
|
||||
Show More Replies: Hiện thêm câu trả lời
|
||||
View {replyCount} replies: Hiển thị {replyCount} câu trả lời
|
||||
Hearted: Thả Tim
|
||||
Subscribed: Đã đăng ký
|
||||
Up Next: 'Tiếp theo'
|
||||
|
||||
# Toast Messages
|
||||
|
@ -824,24 +1007,30 @@ Profile:
|
|||
chỉnh về {profile}'
|
||||
Profile has been updated: Profile đã được cập nhật
|
||||
Profile has been created: Profile đã được tạo
|
||||
Your profile name cannot be empty: Tên Profile của bạn không được để trống
|
||||
Profile could not be found: Probile không thể tìm thấy được
|
||||
Your profile name cannot be empty: Tên hồ sơ của bạn không được để trống
|
||||
Profile could not be found: Hồ sơ không thể tìm thấy được
|
||||
All subscriptions will also be deleted.: Tất cả đăng ký đều sẽ bị xóa.
|
||||
Are you sure you want to delete this profile?: Bạn có chắc là muốn xóa Profile này?
|
||||
Delete Profile: Xóa Profile
|
||||
Make Default Profile: Chọn làm Profile mặc định
|
||||
Update Profile: Cập nhật Profile
|
||||
Create Profile: Tạo Profile
|
||||
Profile Preview: Xem trước Profile
|
||||
Custom Color: Màu custom
|
||||
Are you sure you want to delete this profile?: Bạn có chắc muốn xóa hồ sơ này?
|
||||
Delete Profile: Xóa hồ sơ
|
||||
Make Default Profile: Chọn làm hồ sơ mặc định
|
||||
Update Profile: Cập nhật hồ sơ
|
||||
Create Profile: Tạo hồ sơ
|
||||
Profile Preview: Xem trước hồ sơ
|
||||
Custom Color: Màu tùy chỉnh
|
||||
Color Picker: Chọn màu
|
||||
Edit Profile: Chỉnh sửa Profile
|
||||
Create New Profile: Tạo Profile mới
|
||||
Profile Manager: Quản lý Profile
|
||||
Edit Profile: Chỉnh sửa hồ sơ
|
||||
Create New Profile: Tạo hồ sơ mới
|
||||
Profile Manager: Quản lý hồ sơ
|
||||
All Channels: Tất cả kênh
|
||||
Profile Select: Chọn Profile
|
||||
Profile Select: Chọn hồ sơ
|
||||
Profile Filter: Bộ lọc hồ sơ
|
||||
Profile Settings: Cài đặt hồ sơ cá nhân
|
||||
Profile Settings: Cài đặt hồ sơ
|
||||
Toggle Profile List: Mở/đóng danh sách hồ sơ
|
||||
Edit Profile Name: Chỉnh sửa tên hồ sơ
|
||||
Profile Name: Tên hồ sơ
|
||||
Open Profile Dropdown: Mở danh sách hồ sơ
|
||||
Create Profile Name: Tạo tên hồ sơ
|
||||
Close Profile Dropdown: Đóng danh sách hồ sơ
|
||||
A new blog is now available, {blogTitle}. Click to view more: Một blog mới đã có,
|
||||
{blogTitle}. Nhấn để xem chi tiết
|
||||
Download From Site: Tải từ website
|
||||
|
@ -853,15 +1042,15 @@ Search Bar:
|
|||
More: Thêm
|
||||
Are you sure you want to open this link?: Bạn có chắc là bạn muốn mở liên kết này
|
||||
không?
|
||||
New Window: Cửa Sổ Mới
|
||||
New Window: Cửa sổ mới
|
||||
Channels:
|
||||
Channels: Kênh
|
||||
Title: Danh sách kênh
|
||||
Search bar placeholder: Tìm Kênh
|
||||
Search bar placeholder: Tìm kênh
|
||||
Empty: Danh sách kênh của bạn hiện đang trống.
|
||||
Unsubscribed: '{channelName} đã bị xoá khỏi danh sách kênh đã đăng ký của bạn'
|
||||
Unsubscribe Prompt: Bạn có chắc răng bạn muốn huỷ đăng ký kênh "{channelName}"?
|
||||
Unsubscribe: Huỷ đăng ký kênh
|
||||
Unsubscribe: Huỷ đăng ký
|
||||
Count: '{number} kênh đã tìm được.'
|
||||
Tooltips:
|
||||
General Settings:
|
||||
|
@ -896,6 +1085,12 @@ Tooltips:
|
|||
lui để kiểm soát tốc độ phát. Nhấn và giữ phím Control (Phím Command trên Mac)
|
||||
và nhấp chuột trái để lập tức trở về tốc độ phát mặc định (1x trừ khi nó đã
|
||||
được thay đổi trong cài đặt).
|
||||
Skip by Scrolling Over Video Player: Sử dụng con lăn chuột để bỏ qua video, kiểu
|
||||
MPV.
|
||||
Allow DASH AV1 formats: Định dạng DASH AV1 có thể trông đẹp hơn định dạng DASH
|
||||
H.264. Định dạng DASH AV1 yêu cầu nhiều năng lượng hơn để phát lại! Chúng không
|
||||
có sẵn trên tất cả các video, trong những trường hợp đó trình phát sẽ sử dụng
|
||||
định dạng DASH H.264 thay thế.
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Bất kỳ tham số dòng lệnh tùy chỉnh nào, được
|
||||
phân tách bằng dấu chấm phẩy (';'), bạn muốn được chuyển đến trình phát bên
|
||||
|
@ -915,9 +1110,23 @@ Tooltips:
|
|||
thức mặc định để lấy nguồn cấp dữ liệu đăng ký của bạn. RSS nhanh hơn và tránh
|
||||
việc bị chặn IP, nhưng không cung cấp thông tin nhất định như thời lượng video
|
||||
hoặc trạng thái phát trực tiếp
|
||||
Fetch Automatically: Khi được bật, FreeTube sẽ tự động tìm nạp nguồn cấp dữ liệu
|
||||
đăng ký của bạn khi cửa sổ mới được mở và khi chuyển đổi hồ sơ.
|
||||
Privacy Settings:
|
||||
Remove Video Meta Files: Khi được bật lên, FreeTube sẽ tự động xóa các tệp meta
|
||||
được tạo trong quá trình phát lại video, khi trang xem bị đóng.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Nhập tên kênh hoặc ID kênh để ẩn tất cả video, danh sách phát và
|
||||
chính kênh đó khỏi xuất hiện trong tìm kiếm, xu hướng, phổ biến nhất và được
|
||||
đề xuất. Tên kênh đã nhập phải khớp hoàn toàn và có phân biệt chữ hoa chữ thường.
|
||||
Hide Subscriptions Live: Cài đặt này bị ghi đè bởi cài đặt "{appWideSetting}"
|
||||
trên toàn ứng dụng, trong phần "{subsection}" của "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowThumbnails: Thay thế thumbnail video bằng thumbnail gửi từ DeArrow.
|
||||
UseDeArrowTitles: Thay thế tiêu đề video bằng tiêu đề do người dùng gửi từ DeArrow.
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Tắt bộ nhớ đệm HTTP dựa trên đĩa của Electron và bật bộ nhớ
|
||||
đệm hình ảnh trong bộ nhớ tùy chỉnh. Sẽ dẫn đến việc sử dụng RAM tăng lên.
|
||||
Hashtags have not yet been implemented, try again later: Thẻ hashtag chưa thể dùng
|
||||
được, hãy thử lại sau
|
||||
Playing Next Video Interval: Phát video tiếp theo ngay lập tức. Nhấn vào để hủy. |
|
||||
|
@ -938,3 +1147,30 @@ Starting download: Bắt đầu tải xuống "{videoTitle}"
|
|||
Ok: Ok
|
||||
Clipboard:
|
||||
Copy failed: Sao chép vào bộ nhớ tạm thất bại
|
||||
Cannot access clipboard without a secure connection: Không thể truy cập clipboard
|
||||
nếu không có kết nối an toàn
|
||||
Go to page: Đi đến {page}
|
||||
Close Banner: Đóng thanh trên
|
||||
Chapters:
|
||||
'Chapters list visible, current chapter: {chapterName}': 'Danh sách các chương hiển
|
||||
thị, chương hiện tại: {chapterName}'
|
||||
Chapters: Chương
|
||||
'Chapters list hidden, current chapter: {chapterName}': 'Danh sách các chương bị
|
||||
ẩn, chương hiện tại: {chapterName}'
|
||||
Channel Unhidden: '{channel} đã bị xóa khỏi bộ lọc kênh'
|
||||
Tag already exists: Thẻ "{tagName}" đã tồn tại
|
||||
Hashtag:
|
||||
This hashtag does not currently have any videos: Hashtag này hiện không có bất kỳ
|
||||
video nào
|
||||
Hashtag: Hashtag
|
||||
Age Restricted:
|
||||
This channel is age restricted: Kênh này bị giới hạn độ tuổi
|
||||
This video is age restricted: Video này bị giới hạn độ tuổi
|
||||
Preferences: Tuỳ chỉnh
|
||||
Playlist will not pause when current video is finished: Danh sách phát sẽ không tạm
|
||||
dừng khi video hiện tại kết thúc
|
||||
Channel Hidden: '{channel} đã thêm vào bộ lọc kênh'
|
||||
Playlist will pause when current video is finished: Danh sách phát sẽ tạm dừng khi
|
||||
video hiện tại kết thúc
|
||||
Trimmed input must be at least N characters long: Dữ liệu nhập bị cắt bớt phải dài
|
||||
ít nhất 1 ký tự | Dữ liệu nhập bị cắt bớt phải dài ít nhất {length} ký tự
|
||||
|
|
|
@ -128,6 +128,7 @@ User Playlists:
|
|||
Select a playlist to add your N videos to: 选择一个播放列表来添加你的视频 | 选择一个播放列表来添加你的 {videoCount}
|
||||
个视频
|
||||
N playlists selected: 选中了 {playlistCount} 个播放列表
|
||||
Added {count} Times: 添加了 {count} 次
|
||||
SinglePlaylistView:
|
||||
Toast:
|
||||
There were no videos to remove.: 没有可删除的视频。
|
||||
|
|
|
@ -158,6 +158,7 @@ User Playlists:
|
|||
{oldPlaylistName}。點擊此處撤銷
|
||||
Reverted to use {oldPlaylistName} for quick bookmark: 恢復為使用 {oldPlaylistName}
|
||||
進行快速書籤
|
||||
Search for Videos: 搜尋影片
|
||||
AddVideoPrompt:
|
||||
Select a playlist to add your N videos to: 選擇要新增影片的播放清單 | 選擇播放清單以將您的 {videoCount}
|
||||
部影片新增至其中
|
||||
|
@ -170,6 +171,7 @@ User Playlists:
|
|||
部影片新增至 1 個播放清單
|
||||
"{videoCount} video(s) added to {playlistCount} playlists": 1 部影片新增至 {playlistCount}
|
||||
個播放清單 | {videoCount} 部影片新增至 {playlistCount} 個播放清單
|
||||
Added {count} Times: 新增了 {count} 次 | 新增了 {count} 次
|
||||
CreatePlaylistPrompt:
|
||||
New Playlist Name: 新播放清單名稱
|
||||
Toast:
|
||||
|
@ -245,7 +247,7 @@ Settings:
|
|||
Black: '黑色'
|
||||
Dark: '深色'
|
||||
Light: '淺色'
|
||||
Dracula: '德古拉'
|
||||
Dracula: 'Dracula'
|
||||
System Default: 系統預設值
|
||||
Catppuccin Mocha: 卡布奇諾摩卡
|
||||
Pastel Pink: 淡粉紅色
|
||||
|
@ -815,6 +817,7 @@ Video:
|
|||
Pause on Current Video: 暫停目前影片
|
||||
Unhide Channel: 顯示頻道
|
||||
Hide Channel: 隱藏頻道
|
||||
More Options: 更多選項
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -1046,3 +1049,7 @@ Channel Unhidden: '{channel} 已從頻道過濾條件移除'
|
|||
Trimmed input must be at least N characters long: 修剪後的輸入必須至少有 1 個字元長 | 修剪後的輸入必須至少有
|
||||
{length} 個字元長
|
||||
Tag already exists: 「{tagName}」標籤已存在
|
||||
Close Banner: 關閉橫幅
|
||||
Age Restricted:
|
||||
This channel is age restricted: 此頻道有年齡限制
|
||||
This video is age restricted: 此影片有年齡限制
|
||||
|
|
Loading…
Reference in New Issue