Merge branch 'Master' into 'efy', Update efy submodule

This commit is contained in:
dragos-efy 2023-09-06 00:13:33 +03:00
commit fbe23772cb
111 changed files with 11620 additions and 6205 deletions

7
.eslintrc.cjs Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/vue3-recommended", "eslint:recommended", "@unocss", "plugin:prettier/recommended"],
};

View file

@ -22,7 +22,7 @@ body:
label: Official Instance
description: Can the bug be reproduced on the official instance?
options:
- label: The bug is reproducable on the [official hosted instance](http://piped.video/) or is API related.
- label: The bug is reproducible on the [official hosted instance](http://piped.video/), or is API-related.
- type: textarea
attributes:
@ -58,7 +58,7 @@ body:
description: |
If applicable, add logs from the JavaScript console and/or backend server.
validations:
required: true
required: false
- type: textarea
attributes:
@ -67,6 +67,8 @@ body:
If applicable, add browser, and OS with version.
placeholder: >-
Brave 1.x.x on Arch Linux.
validations:
required: true
- type: textarea
attributes:

View file

@ -9,12 +9,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
<<<<<<< HEAD
with:
submodules: true
=======
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
>>>>>>> master
- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: "yarn"
- run: yarn install --prefer-offline
- run: yarn build
- run: yarn lint --no-fix
cache: "pnpm"
- run: pnpm install
- run: pnpm build
- run: pnpm lint --no-fix

65
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,65 @@
name: "CodeQL"
on:
push:
branches: [ 'master' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'master' ]
schedule:
- cron: '42 11 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View file

@ -12,12 +12,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: "yarn"
- run: yarn install --prefer-offline
- run: yarn build && ./localizefonts.sh && mv dist/ dist-ci/
cache: "pnpm"
- run: pnpm install
- run: pnpm build && ./localizefonts.sh && mv dist/ dist-ci/
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
@ -33,7 +37,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile.ci

View file

@ -1,31 +1,35 @@
name: Build and Deploy
on:
push:
paths-ignore:
- "**.md"
branches:
- master
push:
paths-ignore:
- "**.md"
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: "yarn"
- run: yarn install --prefer-offline
- run: yarn build && ./localizefonts.sh && cp dist/index.html dist/ipfs-404.html
- uses: aquiladev/ipfs-action@v0.3.1-alpha.2
id: ipfs-add
with:
path: ./dist
service: infura
infuraProjectId: ${{ secrets.INFURA_PROJECT_ID }}
infuraProjectSecret: ${{ secrets.INFURA_PROJECT_SECRET }}
- name: Update DNSLink
run: npx dnslink-cloudflare -d kavin.rocks -l /ipfs/${{ steps.ipfs-add.outputs.hash }} -r _dnslink.piped-ipfs
env:
CF_API_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: "pnpm"
- run: pnpm install
- run: pnpm build && ./localizefonts.sh && cp dist/index.html dist/ipfs-404.html
- uses: aquiladev/ipfs-action@v0.3.1-alpha.2
id: ipfs-add
with:
path: ./dist
service: infura
infuraProjectId: ${{ secrets.INFURA_PROJECT_ID }}
infuraProjectSecret: ${{ secrets.INFURA_PROJECT_SECRET }}
- name: Update DNSLink
run: npx dnslink-cloudflare -d kavin.rocks -l /ipfs/${{ steps.ipfs-add.outputs.hash }} -r _dnslink.piped-ipfs
env:
CF_API_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}

25
.github/workflows/reviewdog.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: reviewdog
on: [pull_request]
jobs:
eslint:
name: runner / eslint
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: "pnpm"
- run: pnpm install
- uses: reviewdog/action-eslint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
eslint_flags: "--ignore-path .gitignore --ext .js,.vue ."

View file

@ -8,6 +8,12 @@ jobs:
merge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check if en.json has been updated
run: |
if -n git diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} src/locales/en.json; then
exit 1
fi
- name: AutoMerge Weblate translations
if: github.event.pull_request.user.login == 'weblate'
run: gh pr merge --auto --delete-branch --merge "$PR_URL"

106
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,106 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior include but are not limited to:
- The usage of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, threats, and personal or political attacks
- Harassment of any form
- Publishing others' private information, such as a physical or electronic address, without explicit permission from the individual
- Derailling conversations unnecessarily in a way that is not constructive, such as repeatedly posting off-topic comments whilest not in an off-topic channel
- Other conduct which could reasonably be considered inappropriate in a professional setting
- Tagging maintainers or project members without being one yourself
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at the contact section of the project README, or alternatively any admin of an official medium community. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private or public, written warning from community leaders, providing
clarity when necessary around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A written warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Kick / Temporary Ban
**Community Impact**: A serious or repeated violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A kick or temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View file

@ -8,10 +8,12 @@ RUN --mount=type=cache,target=/var/cache/apk \
COPY . .
RUN --mount=type=cache,target=/root/.cache/yarn \
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN --mount=type=cache,target=/root/.local/share/pnpm \
--mount=type=cache,target=/app/node_modules \
yarn install --prefer-offline && \
yarn build && ./localizefonts.sh
pnpm install --prefer-offline && \
pnpm build && ./localizefonts.sh
FROM nginx:alpine

View file

@ -2,6 +2,7 @@
[![AGPL v3](https://shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0.en.html)
[![Matrix](https://img.shields.io/matrix/piped:matrix.org)](https://matrix.to/#/#piped:matrix.org)
[![Lemmy](https://img.shields.io/lemmy/piped%40feddit.rocks)](https://feddit.rocks/c/piped)
[![Registered Users](https://pipedapi.kavin.rocks/registered/badge)](https://piped.video/register)
[![IPFS Build](https://github.com/TeamPiped/Piped/actions/workflows/ipfs-build.yml/badge.svg)](https://piped-ipfs.kavin.rocks/)
[![GitHub Repo stars](https://img.shields.io/github/stars/TeamPiped/Piped-Frontend?style=social)](https://github.com/TeamPiped/Piped/stargazers)
@ -61,6 +62,10 @@ By using Piped, you can freely watch and listen to content without the fear of p
- You can join us via Matrix at [#piped](https://matrix.to/#/#piped:matrix.org).
- You can also join us at the libera.chat IRC network which is bridged to the Matrix room at [#piped](https://web.libera.chat/#piped).
## Public Communities
- You can join us on Lemmy on the [!piped@feddit.rocks](https://feddit.rocks/c/piped) community.
## Self-Hosting
See https://docs.piped.video/docs/self-hosting/ for more details.
@ -103,13 +108,13 @@ You can help by translating the project to a language you speak at https://hoste
### Development Setup
```
yarn install
pnpm install
```
### Compiles and hot-reloads for development
```
yarn serve
pnpm serve
```
You can now make changes and view then in realtime!
@ -140,7 +145,14 @@ Contributions in any other form are also welcomed.
- [Yattee](https://github.com/yattee/yattee) - an alternative frontend for YouTube, for IOS.
- [LibreTube](https://github.com/Libre-tube/LibreTube) - an alternative frontend for YouTube, for Android.
- [Racoon](https://github.com/shailendramaurya/racoon) - A web based minimal YouTube downloader.
- [Hyperpipe](https://codeberg.org/Hyperpipe/Hyperpipe) - an alternative privacy respecting frontend for YouTube Music.
- [Musicale](https://github.com/Bellisario/musicale) - an alternative to YouTube Music, with style.
- [ytify](https://github.com/n-ce/ytify) - a complementary minimal audio streaming frontend for YouTube.
- [PsTube](https://github.com/prateekmedia/pstube) - Watch and download videos without ads on Android, Linux, Windows, iOS, and Mac OSX.
- [Piped-Material](https://github.com/mmjee/Piped-Material) - A fork of Piped, focusing on better performance and a more usable design.
- [ReacTube](https://github.com/NeeRaj-2401/ReacTube) - Privacy friendly & distraction free Youtube front-end using Piped API.
- [YTDLnis](https://github.com/deniscerri/ytdlnis) - Video and audio downloader for Android that uses Piped to update formats.
## YourKit

View file

@ -2,6 +2,7 @@ server {
listen 80;
listen [::]:80;
server_name localhost;
error_log off;
location / {
root /usr/share/nginx/html;

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html style="background: #0f0f0f" lang="en">
<html style="background: #0f0f0f" lang="en" >
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
@ -7,6 +7,7 @@
<link rel="icon" href="/favicon.ico" />
<link title="Piped" type="application/opensearchdescription+xml" rel="search" href="/opensearch.xml" />
<title>Piped</title>
<meta name="theme-color" content="#0f0f0f">
<meta property="og:title" content="Piped" />
<meta property="og:type" content="website" />
<meta property="og:image" content="/img/icons/favicon-32x32.png" />

View file

@ -3,7 +3,7 @@
base='https://fonts\.(gstatic\.com|kavin\.rocks)'
fonts=$(cat dist/assets/* | grep -Po "$base[^)]*" | sort | uniq)
for font in $fonts; do
file="dist/fonts$(echo $font | sed -E "s#$base##")"
file="dist/fonts$(echo "$font" | sed -E "s#$base##")"
mkdir -p "$(dirname "$file")"
curl -L "$font" -o "$file"
done

View file

@ -6,56 +6,54 @@
"serve": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier -w --ignore-path .gitignore **/**.{js,vue,json}",
"lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-brands-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/vue-fontawesome": "3.0.2",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-brands-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3",
"buffer": "6.0.3",
"dompurify": "2.4.2",
"hotkeys-js": "3.10.1",
"dompurify": "3.0.5",
"hotkeys-js": "3.12.0",
"javascript-time-ago": "2.5.9",
"mux.js": "6.2.0",
"shaka-player": "4.3.2",
"linkify-html": "4.1.1",
"linkifyjs": "4.1.1",
"mux.js": "6.3.0",
"qrcode": "^1.5.3",
"shaka-player": "4.3.8",
"stream-browserify": "3.0.0",
"vue": "3.2.45",
"vue": "3.3.4",
"vue-i18n": "9.2.2",
"vue-router": "4.1.6",
"vue-router": "4.2.4",
"xml-js": "1.6.11"
},
"devDependencies": {
"@iconify/json": "2.2.3",
"@intlify/vite-plugin-vue-i18n": "6.0.3",
"@unocss/preset-icons": "0.48.2",
"@unocss/preset-web-fonts": "0.48.2",
"@unocss/transformer-directives": "0.48.2",
"@unocss/transformer-variant-group": "0.48.2",
"@vitejs/plugin-legacy": "3.0.1",
"@vitejs/plugin-vue": "4.0.0",
"@vue/compiler-sfc": "3.2.45",
"eslint": "8.31.0",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-vue": "9.8.0",
"prettier": "2.8.1",
"unocss": "0.48.2",
"vite": "3.2.5",
"@iconify-json/fa6-brands": "1.1.13",
"@iconify-json/fa6-solid": "1.1.15",
"@intlify/unplugin-vue-i18n": "0.12.2",
"@unocss/eslint-config": "0.55.0",
"@unocss/preset-icons": "0.55.0",
"@unocss/preset-uno": "0.55.0",
"@unocss/preset-web-fonts": "0.55.0",
"@unocss/reset": "0.55.0",
"@unocss/transformer-directives": "0.55.0",
"@unocss/transformer-variant-group": "0.55.0",
"@vitejs/plugin-legacy": "4.1.1",
"@vitejs/plugin-vue": "4.2.3",
"@vue/compiler-sfc": "3.3.4",
"eslint": "8.47.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-vue": "9.17.0",
"lightningcss": "1.21.5",
"prettier": "3.0.1",
"unocss": "0.55.0",
"vite": "4.4.9",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-pwa": "0.14.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"plugin:prettier/recommended",
"eslint:recommended"
],
"rules": {}
"vite-plugin-pwa": "0.16.4",
"workbox-window": "7.0.0"
},
"browserslist": [
"last 1 chrome version",

5406
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

@ -1 +1 @@
Subproject commit 79329007824820dedf4234200f105b9d4caac577
Subproject commit 76a23d4128778b25de0495dd09b82bdfe0b2eb27

View file

@ -1,21 +1,17 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"group:recommended"
],
"ignorePresets": [
":prHourlyLimit2"
],
"packageRules": [
{
"matchPackagePrefixes": [
"@unocss/"
],
"matchPackageNames": [
"unocss"
],
"groupName": "unocss"
}
]
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "group:recommended"],
"ignorePresets": [":prHourlyLimit2"],
"packageRules": [
{
"matchPackagePrefixes": ["@unocss/"],
"matchPackageNames": ["unocss"],
"groupName": "unocss"
}
],
"lockFileMaintenance": {
"enabled": true,
"automerge": true
},
"platformAutomerge": true
}

View file

@ -34,7 +34,7 @@ video {
/*Radius 0*/
.video-grid img {
border-radius: var(--efy_radius0);
border-radius: var(--efy_radius) var(--efy_radius) 0 0;
}
/*Radius 2*/
@ -68,7 +68,7 @@ video {
display: flex;
flex-wrap: wrap;
gap: var(--efy_gap0);
margin: 5rem 0 0;
margin: 5rem 15rem 15rem 15rem;
}
.pp-video-card-buttons :is(a, button) {
padding: 4rem 8rem;
@ -141,38 +141,46 @@ export default {
theme: "dark",
};
},
methods: {
setTheme() {
let themePref = this.getPreferenceString("theme", "dark");
if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
else this.theme = themePref;
},
},
mounted() {
this.setTheme();
darkModePreference.addEventListener("change", () => {
this.setTheme();
});
if (this.getPreferenceBoolean("watchHistory", false))
if ("indexedDB" in window) {
const request = indexedDB.open("piped-db", 2);
request.onupgradeneeded = ev => {
const db = request.result;
console.log("Upgrading object store.");
if (!db.objectStoreNames.contains("watch_history")) {
const store = db.createObjectStore("watch_history", { keyPath: "videoId" });
store.createIndex("video_id_idx", "videoId", { unique: true });
store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
}
if (ev.oldVersion < 2) {
const store = request.transaction.objectStore("watch_history");
store.createIndex("watchedAt", "watchedAt", { unique: false });
}
};
request.onsuccess = e => {
window.db = e.target.result;
};
} else console.log("This browser doesn't support IndexedDB");
if ("indexedDB" in window) {
const request = indexedDB.open("piped-db", 5);
request.onupgradeneeded = ev => {
const db = request.result;
console.log("Upgrading object store.");
if (!db.objectStoreNames.contains("watch_history")) {
const store = db.createObjectStore("watch_history", { keyPath: "videoId" });
store.createIndex("video_id_idx", "videoId", { unique: true });
store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
}
if (ev.oldVersion < 2) {
const store = request.transaction.objectStore("watch_history");
store.createIndex("watchedAt", "watchedAt", { unique: false });
}
if (!db.objectStoreNames.contains("playlist_bookmarks")) {
const store = db.createObjectStore("playlist_bookmarks", { keyPath: "playlistId" });
store.createIndex("playlist_id_idx", "playlistId", { unique: true });
store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
}
if (!db.objectStoreNames.contains("channel_groups")) {
const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
store.createIndex("groupName", "groupName", { unique: true });
}
if (!db.objectStoreNames.contains("playlists")) {
const playlistStore = db.createObjectStore("playlists", { keyPath: "playlistId" });
playlistStore.createIndex("playlistId", "playlistId", { unique: true });
const playlistVideosStore = db.createObjectStore("playlist_videos", { keyPath: "videoId" });
playlistVideosStore.createIndex("videoId", "videoId", { unique: true });
}
};
request.onsuccess = e => {
window.db = e.target.result;
};
} else console.log("This browser doesn't support IndexedDB");
const App = this;
@ -197,5 +205,24 @@ export default {
}
})();
},
methods: {
setTheme() {
let themePref = this.getPreferenceString("theme", "dark");
if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
else this.theme = themePref;
// Change title bar color based on user's theme
const themeColor = document.querySelector("meta[name='theme-color']");
if (this.theme === "light") {
themeColor.setAttribute("content", "#FFF");
} else {
themeColor.setAttribute("content", "#0F0F0F");
}
// Used for the scrollbar
const root = document.querySelector(":root");
this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
},
},
};
</script>

View file

@ -1,19 +1,19 @@
<template>
<div>
<div class="flex flex-col flex-justify-between">
<router-link :to="props.item.url">
<div class="relative">
<img class="w-full" :src="props.item.thumbnail" loading="lazy" />
<div class="my-4 flex justify-center">
<img class="aspect-square w-[50%] rounded-full" :src="props.item.thumbnail" loading="lazy" />
</div>
<p>
<span v-text="props.item.name" />
<font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
</p>
</router-link>
<p v-if="props.item.description" v-text="props.item.description" />
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
<p>
<span v-text="props.item.uploader" />
<font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
<font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
</p>
</router-link>
@ -29,6 +29,9 @@
<script setup>
const props = defineProps({
item: Object,
item: {
type: Object,
required: true,
},
});
</script>

View file

@ -1,88 +1,93 @@
<template>
<ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
<div v-if="channel" v-show="!channel.error">
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
<div class="pp-channel-page-author flex place-items-center">
<img height="48" width="48" class="m-1" :src="channel.avatarUrl" />
<h5 v-text="channel.name" />
<font-awesome-icon class="ml-1.5" v-if="channel.verified" icon="check" />
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="whitespace-pre-wrap mt-2">
<span v-html="purifyHTML(urlify(channel.description))" />
</p>
<LoadingIndicatorPage :show-content="channel != null && !channel.error">
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
<div class="pp-channel-page-author flex">
<img height="48" width="48" class="m-1" :src="channel.avatarUrl" />
<h5 v-text="channel.name" />
<font-awesome-icon v-if="channel.verified" class="ml-1.5" icon="check" />
</div>
<p v-text="channel.description" style="margin: 10rem 0 0 0" />
<div class="flex mt-4 mb-2 pp-channel-tabs">
<button
class="btn pp-subscribe"
@click="subscribeHandler"
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(channel.subscriberCount) },
}"
></button>
<div class="pp-channel-tabs">
<button
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(channel.subscriberCount) },
}"
class="pp-subscribe"
@click="subscribeHandler"
></button>
<!-- RSS Feed button -->
<a
aria-label="RSS feed"
title="RSS feed"
role="button"
v-if="channel.id"
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
target="_blank"
class="btn"
style="display: inline; float: unset; margin-left: var(--efy_gap0)"
>
<font-awesome-icon icon="rss" />
</a>
<WatchOnYouTubeButton :link="`https://youtube.com/channel/${this.channel.id}`" />
<p>|</p>
<button
v-for="(tab, index) in tabs"
:key="tab.name"
style="margin-right: var(--efy_gap0)"
@click="loadTab(index)"
:class="{ active: selectedTab == index }"
>
<span v-text="tab.translatedName"></span>
</button>
</div>
<!-- RSS Feed button -->
<a
v-if="channel.id"
aria-label="RSS feed"
title="RSS feed"
role="button"
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
target="_blank"
class="pp-square"
style="display: inline; float: unset"
>
<font-awesome-icon icon="rss" />
</a>
<WatchOnButton :link="`https://youtube.com/channel/${channel.id}`" />
<p style="place-self: center">|</p>
<button
v-for="(tab, index) in tabs"
:key="tab.name"
:class="{ active: selectedTab == index }"
@click="loadTab(index)"
>
<span v-text="tab.translatedName"></span>
</button>
</div>
<hr />
<hr />
<div class="video-grid">
<ContentItem
v-for="item in contentItems"
:key="item.url"
:item="item"
height="94"
width="168"
hide-channel
class="efy_trans_filter"
/>
</div>
<div class="video-grid">
<ContentItem
v-for="item in contentItems"
:key="item.url"
:item="item"
height="94"
width="168"
hide-channel
class="efy_trans_filter"
/>
</div>
</LoadingIndicatorPage>
</div>
</template>
<style>
.pp-channel-tabs > p {
place-self: center;
padding: 0 10rem;
-webkit-text-fill-color: var(--efy_text_trans2);
.pp-channel-tabs {
display: flex;
flex-wrap: wrap;
margin: 15rem 0;
gap: var(--efy_gap0);
}
.pp-channel-tabs :is(button, [role="button"]) {
margin: 0;
}
</style>
<script>
import ErrorHandler from "./ErrorHandler.vue";
import ContentItem from "./ContentItem.vue";
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
import WatchOnButton from "./WatchOnButton.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
// import CollapsableText from "./CollapsableText.vue";
export default {
components: {
ErrorHandler,
ContentItem,
WatchOnYouTubeButton,
WatchOnButton,
LoadingIndicatorPage,
// CollapsableText,
},
data() {
return {
@ -130,7 +135,9 @@ export default {
});
},
async fetchChannel() {
const url = this.apiUrl() + "/" + this.$route.params.path + "/" + this.$route.params.channelId;
const url = this.$route.path.includes("@")
? this.apiUrl() + "/@/" + this.$route.params.channelId
: this.apiUrl() + "/" + this.$route.params.path + "/" + this.$route.params.channelId;
return await this.fetchJson(url);
},
async getChannelData() {
@ -142,13 +149,16 @@ export default {
this.contentItems = this.channel.relatedStreams;
this.fetchSubscribedStatus();
this.updateWatched(this.channel.relatedStreams);
this.fetchDeArrowContent(this.channel.relatedStreams);
this.tabs.push({
translatedName: this.$t("video.videos"),
});
const tabQuery = this.$route.query.tab;
for (let i = 0; i < this.channel.tabs.length; i++) {
let tab = this.channel.tabs[i];
tab.translatedName = this.getTranslatedTabName(tab.name);
this.tabs.push(tab);
if (tab.name === tabQuery) this.loadTab(i + 1);
}
}
});
@ -178,6 +188,7 @@ export default {
this.loading = false;
this.updateWatched(json.relatedStreams);
json.relatedStreams.map(stream => this.contentItems.push(stream));
this.fetchDeArrowContent(this.contentItems);
});
},
fetchChannelTabNextPage() {
@ -188,6 +199,7 @@ export default {
this.tabs[this.selectedTab].tabNextPage = json.nextpage;
this.loading = false;
json.content.map(item => this.contentItems.push(item));
this.fetchDeArrowContent(this.contentItems);
this.tabs[this.selectedTab].content = this.contentItems;
});
},
@ -231,10 +243,17 @@ export default {
},
loadTab(index) {
this.selectedTab = index;
// update the tab query in the url path
const url = new URL(window.location);
url.searchParams.set("tab", this.tabs[index].name ?? "videos");
window.history.replaceState(window.history.state, "", url);
if (index == 0) {
this.contentItems = this.channel.relatedStreams;
return;
}
if (this.tabs[index].content) {
this.contentItems = this.tabs[index].content;
return;
@ -243,6 +262,7 @@ export default {
data: this.tabs[index].data,
}).then(tab => {
this.contentItems = this.tabs[index].content = tab.content;
this.fetchDeArrowContent(this.contentItems);
this.tabs[this.selectedTab].tabNextPage = tab.nextpage;
});
},

View file

@ -5,11 +5,11 @@
{{ $t("video.chapters") }} - {{ chapters.length }}
</h6>
<div
:key="chapter.start"
v-for="(chapter, index) in chapters"
@click="$emit('seek', chapter.start)"
:key="chapter.start"
class="chapter efy_anim_pulse efy_trans_filter"
:class="{ 'pp-chapter-active': isCurrentChapter(index) }"
@click="$emit('seek', chapter.start)"
>
<div class="flex">
<img :src="chapter.image" :alt="chapter.title" />
@ -23,11 +23,11 @@
<!-- mobile view -->
<div v-else class="pp-chapters pp-mobile flex overflow-x-auto">
<div
:key="chapter.start"
v-for="(chapter, index) in chapters"
@click="$emit('seek', chapter.start)"
:key="chapter.start"
class="chapter efy_anim_pulse efy_trans_filter"
:class="{ 'pp-chapter-active': isCurrentChapter(index) }"
@click="$emit('seek', chapter.start)"
>
<img :src="chapter.image" :alt="chapter.title" />
<div class="m-1 flex">
@ -38,24 +38,12 @@
</div>
</template>
<style>
.chapter {
@apply cursor-pointer self-center p-2.5;
}
.pp-mobile .chapter img {
@apply w-full h-full;
}
.chapter img {
@apply w-3/10 h-3/10;
}
.text-truncate {
@apply truncate overflow-hidden inline-block w-10em;
}
</style>
<script setup>
const props = defineProps({
chapters: Object,
chapters: {
type: Object,
default: () => null,
},
mobileLayout: {
type: Boolean,
default: () => true,
@ -75,3 +63,24 @@ const isCurrentChapter = index => {
defineEmits(["seek"]);
</script>
<style>
.chapter {
@apply cursor-pointer self-center p-2.5;
}
.pp-mobile .chapter img {
@apply w-full h-full;
}
.chapter img {
@apply w-3/10 h-3/10;
}
.text-truncate {
@apply truncate overflow-hidden inline-block w-10em;
}
.pp-chapter-active,
.pp-chapters .chapter:hover {
background: var(--efy_color);
background-clip: padding-box;
color: var(--efy_text2);
}
</style>

View file

@ -0,0 +1,42 @@
<template v-if="text">
<div class="mx-1 whitespace-pre-wrap py-2">
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-if="showFullText" v-html="fullText()" />
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-else v-html="colapsedText()" />
<span v-if="text.length > 100 && !showFullText">...</span>
<button
v-if="text.length > 100"
class="block whitespace-normal font-semibold text-neutral-500 hover:underline"
@click="showFullText = !showFullText"
>
[{{ showFullText ? $t("actions.show_less") : $t("actions.show_more") }}]
</button>
</div>
</template>
<script>
import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
export default {
props: {
text: {
type: String,
default: null,
},
},
data() {
return {
showFullText: false,
};
},
methods: {
fullText() {
return purifyHTML(rewriteDescription(this.text));
},
colapsedText() {
return purifyHTML(rewriteDescription(this.text.slice(0, 100)));
},
},
};
</script>

View file

@ -1,38 +1,43 @@
<template>
<div class="comment flex mt-1.5">
<img :src="comment.thumbnail" class="comment-avatar" height="48" width="48" loading="lazy" alt="Avatar" />
<router-link style="height: fit-content" :to="comment.commentorUrl">
<img :src="comment.thumbnail" class="comment-avatar" height="48" width="48" loading="lazy" alt="Avatar" />
</router-link>
<div class="comment-content pl-2">
<div class="comment-header">
<div v-if="comment.pinned" class="comment-pinned">
<font-awesome-icon icon="thumbtack" />
<span
class="ml-1.5"
v-t="{
path: 'comment.pinned_by',
args: { author: uploader },
}"
class="ml-1.5"
/>
</div>
<div class="comment-author mt-1 flex">
<router-link class="font-bold link" :to="comment.commentorUrl">{{ comment.author }}</router-link>
<font-awesome-icon class="ml-1.5" v-if="comment.verified" icon="check" />
<div class="comment-author flex align-center">
<router-link class="link font-bold" :to="comment.commentorUrl">{{ comment.author }}</router-link>
<font-awesome-icon v-if="comment.verified" class="ml-1.5" icon="check" />
<div class="comment-meta mb-1.5" v-text="' • ' + comment.commentedTime + ' •'" />
<div class="i-fa-solid:thumbs-up" />
<span class="ml-1" v-text="numberFormat(comment.likeCount)" />
<font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
<div class="comment-footer mt-1 flex items-center">
<div class="i-fa6-solid:thumbs-up" />
<span class="ml-1" v-text="numberFormat(comment.likeCount)" />
<font-awesome-icon v-if="comment.hearted" class="ml-1" icon="heart" />
</div>
</div>
</div>
<div class="whitespace-pre-wrap" v-html="purifyHTML(comment.commentText)" />
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="whitespace-pre-wrap" v-html="purifiedText" />
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
<div @click="loadReplies" class="cursor-pointer">
<div class="cursor-pointer" @click="loadReplies">
<a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
</div>
</template>
<template v-if="showingReplies">
<div @click="hideReplies" class="cursor-pointer">
<div class="cursor-pointer" @click="hideReplies">
<a v-t="'actions.hide_replies'" />
<font-awesome-icon class="ml-1.5" icon="level-up-alt" />
</div>
@ -41,7 +46,7 @@
<div v-for="reply in replies" :key="reply.commentId" class="w-full">
<CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
</div>
<div v-if="nextpage" @click="loadReplies" class="cursor-pointer">
<div v-if="nextpage" class="cursor-pointer" @click="loadReplies">
<a v-t="'actions.load_more_replies'" />
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
</div>
@ -51,6 +56,8 @@
</template>
<script>
import { purifyHTML } from "@/utils/HtmlUtils";
export default {
props: {
comment: {
@ -70,6 +77,11 @@ export default {
nextpage: null,
};
},
computed: {
purifiedText() {
return purifyHTML(this.comment.commentText);
},
},
methods: {
async loadReplies() {
if (!this.showingReplies && this.loadingReplies) {
@ -97,4 +109,7 @@ export default {
.comment-content {
overflow-wrap: anywhere;
}
.comment-avatar {
max-width: unset;
}
</style>

View file

@ -0,0 +1,28 @@
<template>
<ModalComponent @close="$emit('close')">
<div>
<h3 class="text-xl" v-text="message" />
<div class="ml-auto mt-8 w-min flex gap-2">
<button v-t="'actions.cancel'" class="btn" @click="$emit('close')" />
<button v-t="'actions.okay'" class="btn" @click="$emit('confirm')" />
</div>
</div>
</ModalComponent>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: {
ModalComponent,
},
props: {
message: {
type: String,
required: true,
},
},
emits: ["close", "confirm"],
};
</script>

View file

@ -6,7 +6,10 @@
import { defineAsyncComponent } from "vue";
const props = defineProps({
item: Object,
item: {
type: Object,
required: true,
},
});
const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));

View file

@ -1,6 +1,6 @@
<template>
<p v-text="message" />
<button @click="toggleTrace" class="btn" v-t="'actions.show_more'" />
<button v-t="'actions.show_more'" class="btn" @click="toggleTrace" />
<p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
</template>

View file

@ -1,40 +1,97 @@
<template>
<button class="btn mr-2" @click="exportHandler">
<router-link to="/subscriptions">Subscriptions</router-link>
</button>
<span>
<a :href="getRssUrl" class="btn">
<font-awesome-icon icon="rss" />
</a>
</span>
<span class="md:float-right flex pp-sortby-feed">
<SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
</span>
<hr />
<div class="flex flex-wrap align-center" style="place-content: space-between">
<span class="buttons flex" style="gap: var(--efy_gap0)">
<router-link role="button" to="/subscriptions">Subscriptions</router-link>
<a :href="getRssUrl" role="button" class="pp-square">
<font-awesome-icon icon="rss" />
</a>
</span>
<div class="video-grid">
<VideoItem :is-feed="true" v-for="video in videos" :key="video.url" :item="video" />
<div class="filters flex align-center">
<span class="flex">
<label for="filters" v-text="`${$t('actions.filter')}:`" />
<select
id="filters"
v-model="selectedFilter"
default="all"
class="select flex-grow"
@change="onFilterChange()"
>
<option v-for="filter in availableFilters" :key="filter" v-t="`video.${filter}`" :value="filter" />
</select>
</span>
<span class="flex">
<label for="group-selector" v-text="`${$t('titles.channel_groups')}:`" />
<select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
<option v-t="`video.all`" value="" />
<option
v-for="group in channelGroups"
:key="group.groupName"
:value="group.groupName"
v-text="group.groupName"
/>
</select>
</span>
<span class="pp-sortby-feed flex">
<SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
</span>
</div>
</div>
<hr />
<LoadingIndicatorPage :show-content="videosStore != null" class="video-grid">
<template v-for="video in filteredVideos" :key="video.url">
<VideoItem v-if="shouldShowVideo(video)" :is-feed="true" :item="video" />
</template>
</LoadingIndicatorPage>
</template>
<style>
.filters {
flex-wrap: wrap;
}
.filters,
.filters span {
gap: var(--efy_gap0);
}
.filters :is(select, label),
.buttons a[role="button"] {
margin: 0 !important;
white-space: nowrap;
align-items: center;
place-content: center;
}
.filters span {
align-items: center;
}
.buttons a[role="button"] {
height: var(--efy_ratio_width);
}
</style>
<script>
import VideoItem from "./VideoItem.vue";
import SortingSelector from "./SortingSelector.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
export default {
components: {
VideoItem,
SortingSelector,
LoadingIndicatorPage,
},
data() {
return {
currentVideoCount: 0,
videoStep: 100,
videosStore: [],
videosStore: null,
videos: [],
availableFilters: ["all", "shorts", "videos"],
selectedFilter: "all",
selectedGroupName: "",
channelGroups: [],
};
},
computed: {
@ -42,6 +99,12 @@ export default {
if (_this.authenticated) return _this.authApiUrl() + "/feed/rss?authToken=" + _this.getAuthToken();
else return _this.authApiUrl() + "/feed/unauthenticated/rss?channels=" + _this.getUnauthenticatedChannels();
},
filteredVideos(_this) {
const selectedGroup = _this.channelGroups.filter(group => group.groupName == _this.selectedGroupName);
return _this.selectedGroupName == ""
? _this.videos
: _this.videos.filter(video => selectedGroup[0].channels.includes(video.uploaderUrl.substr(-11)));
},
},
mounted() {
this.fetchFeed().then(videos => {
@ -49,6 +112,23 @@ export default {
this.loadMoreVideos();
this.updateWatched(this.videos);
});
this.selectedFilter = this.getPreferenceString("feedFilter") ?? "all";
if (!window.db) return;
const cursor = this.getChannelGroupsCursor();
cursor.onsuccess = e => {
const cursor = e.target.result;
if (cursor) {
const group = cursor.value;
this.channelGroups.push({
groupName: group.groupName,
channels: JSON.parse(group.channels),
});
cursor.continue();
}
};
},
activated() {
document.title = this.$t("titles.feed") + " - Piped";
@ -74,15 +154,31 @@ export default {
}
},
loadMoreVideos() {
if (!this.videosStore) return;
this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
if (this.videos.length != this.videosStore.length)
if (this.videos.length != this.videosStore.length) {
this.videos = this.videosStore.slice(0, this.currentVideoCount);
this.fetchDeArrowContent(this.videos);
}
},
handleScroll() {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - window.innerHeight) {
this.loadMoreVideos();
}
},
shouldShowVideo(video) {
switch (this.selectedFilter.toLowerCase()) {
case "shorts":
return video.isShort;
case "videos":
return !video.isShort;
default:
return true;
}
},
onFilterChange() {
this.setPreference("feedFilter", this.selectedFilter);
},
},
};
</script>

View file

@ -1,13 +1,30 @@
<template>
<h1 class="font-bold text-center" v-t="'titles.history'" />
<div class="flex place-items-center">
<div class="mr-2">
<button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
<div class="flex flex-col gap-2 md:flex-row md:items-center">
<button v-t="'actions.clear_history'" class="btn" @click="clearHistory" />
<button v-t="'actions.export_to_json'" class="btn" @click="exportHistory" />
<div class="ml-auto flex items-center gap-1">
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
</div>
</div>
<div class="mr-2">
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
<div class="ml-4 flex items-center">
<input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
<label v-t="'actions.delete_automatically'" class="ml-2" for="autoDelete" />
<select v-model="autoDeleteDelayHours" class="select ml-3 pl-3" @change="onChange">
<option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
<option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
<option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
<option v-t="{ path: 'info.hours', args: { amount: '12' } }" value="12" />
<option v-t="{ path: 'info.days', args: { amount: '1' } }" value="24" />
<option v-t="{ path: 'info.days', args: { amount: '3' } }" value="72" />
<option v-t="{ path: 'info.weeks', args: { amount: '1' } }" value="168" />
<option v-t="{ path: 'info.weeks', args: { amount: '3' } }" value="336" />
<option v-t="{ path: 'info.months', args: { amount: '1' } }" value="672" />
<option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
</select>
</div>
</div>
@ -32,28 +49,39 @@ export default {
data() {
return {
videos: [],
autoDeleteHistory: false,
autoDeleteDelayHours: "24",
};
},
mounted() {
this.autoDeleteHistory = this.getPreferenceBoolean("autoDeleteWatchHistory", false);
this.autoDeleteDelayHours = this.getPreferenceString("autoDeleteWatchHistoryDelayHours", "24");
(async () => {
if (window.db) {
var tx = window.db.transaction("watch_history", "readonly");
if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
var tx = window.db.transaction("watch_history", "readwrite");
var store = tx.objectStore("watch_history");
const cursorRequest = store.index("watchedAt").openCursor(null, "prev");
cursorRequest.onsuccess = e => {
const cursor = e.target.result;
if (cursor) {
const video = cursor.value;
this.videos.push({
url: "/watch?v=" + video.videoId,
title: video.title,
uploaderName: video.uploaderName,
uploaderUrl: video.uploaderUrl,
duration: video.duration,
thumbnail: video.thumbnail,
watchedAt: video.watchedAt,
});
if (this.videos.length < 1000) cursor.continue();
if (!this.shouldRemoveVideo(video)) {
this.videos.push({
url: "/watch?v=" + video.videoId,
title: video.title,
uploaderName: video.uploaderName,
uploaderUrl: video.uploaderUrl,
duration: video.duration,
thumbnail: video.thumbnail,
watchedAt: video.watchedAt,
watched: true,
currentTime: video.currentTime,
});
} else {
store.delete(video.videoId);
}
}
};
}
@ -71,6 +99,32 @@ export default {
}
this.videos = [];
},
exportHistory() {
const dateStr = new Date().toISOString().split(".")[0];
let json = {
format: "Piped",
version: 1,
playlists: [
{
name: `Piped History ${dateStr}`,
type: "history",
visibility: "private",
videos: this.videos.map(video => "https://youtube.com" + video.url),
},
],
};
this.download(JSON.stringify(json), `piped_history_${dateStr}.json`, "application/json");
},
onChange() {
this.setPreference("autoDeleteWatchHistory", this.autoDeleteHistory);
this.setPreference("autoDeleteWatchHistoryDelayHours", this.autoDeleteDelayHours);
},
shouldRemoveVideo(video) {
if (!this.autoDeleteHistory) return false;
// convert from hours to milliseconds
let maximumTimeDiff = Number(this.autoDeleteDelayHours) * 60 * 60 * 1000;
return Date.now() - video.watchedAt > maximumTimeDiff;
},
},
};
</script>

View file

@ -106,10 +106,14 @@ export default {
}
// FreeTube DB
else if (text.indexOf("allChannels") != -1) {
const json = JSON.parse(text);
json.subscriptions.forEach(item => {
this.subscriptions.push(item.id);
});
const lines = text.split("\n");
for (let line of lines) {
if (line === "") continue;
const json = JSON.parse(line);
json.subscriptions.forEach(item => {
this.subscriptions.push(item.id);
});
}
}
// Google Takeout JSON
else if (text.indexOf("contentDetails") != -1) {

View file

@ -0,0 +1,47 @@
<template>
<div v-if="!showContent" class="min-h-[75vh] w-full flex items-center justify-center">
<span id="spinner" />
</div>
<div v-else>
<slot />
</div>
</template>
<script>
export default {
props: {
showContent: {
type: Boolean,
required: true,
},
},
};
</script>
<style>
#spinner {
display: inline-block;
width: 70rem;
height: 70rem;
}
#spinner:after {
content: " ";
display: block;
width: 54rem;
height: 54rem;
margin: 8rem;
border-radius: 50%;
border: 4rem solid var(--efy_text);
border-color: var(--efy_text) transparent var(--efy_text) transparent;
animation: spinner 1.2s linear infinite;
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View file

@ -0,0 +1,70 @@
<template>
<h1 v-t="'titles.login'" class="my-4 text-center font-bold" />
<hr />
<div class="text-center">
<form class="children:pb-3">
<div>
<input
v-model="username"
class="input"
type="text"
autocomplete="username"
:placeholder="$t('login.username')"
:aria-label="$t('login.username')"
@keyup.enter="login"
/>
</div>
<div>
<input
v-model="password"
class="input"
type="password"
autocomplete="password"
:placeholder="$t('login.password')"
:aria-label="$t('login.password')"
@keyup.enter="login"
/>
</div>
<div>
<a v-t="'titles.login'" class="btn w-auto" @click="login" />
</div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
username: null,
password: null,
};
},
mounted() {
//TODO: Add Server Side check
if (this.getAuthToken()) {
this.$router.push("/");
}
},
activated() {
document.title = this.$t("titles.login") + " - Piped";
},
methods: {
login() {
if (!this.username || !this.password) return;
this.fetchJson(this.authApiUrl() + "/login", null, {
method: "POST",
body: JSON.stringify({
username: this.username,
password: this.password,
}),
}).then(resp => {
if (resp.token) {
this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token);
window.location = "/"; // done to bypass cache
} else alert(resp.error);
});
},
},
};
</script>

View file

@ -11,6 +11,7 @@
<script>
export default {
emits: ["close"],
mounted() {
window.addEventListener("keydown", this.handleKeyDown);
},
@ -34,7 +35,10 @@ export default {
<style>
.modal {
@apply fixed z-50 top-0 left-0 w-full h-full bg-dark-900 bg-opacity-80 transition-opacity table;
@apply fixed z-50 top-0 left-0 w-full h-full bg-gray bg-opacity-80 transition-opacity table;
}
.dark .modal {
@apply bg-dark-900 bg-opacity-80;
}
.modal > div {
@ -42,7 +46,7 @@ export default {
}
.modal-container {
@apply w-300rem m-auto max-w-[100vw] relative;
@apply w-100% m-auto max-w-[410rem] relative;
}
.modal-container > button {

View file

@ -56,10 +56,10 @@
</div>
<div class="lt-md:hidden flex flex-1 justify-start" style="position: relative">
<input
ref="videoSearch"
v-model="searchText"
type="text"
role="search"
ref="videoSearch"
:title="$t('actions.search')"
:placeholder="$t('actions.search')"
@keyup="onKeyUp"
@ -78,8 +78,8 @@
<router-link v-t="'titles.preferences'" to="/preferences" />
<p
v-if="shouldShowLogin"
class="cursor-pointer font-bold"
v-t="'titles.account'"
class="cursor-pointer font-bold"
@click="showLoginModal = !showLoginModal"
/>
<router-link v-if="shouldShowHistory" v-t="'titles.history'" to="/history" />
@ -87,15 +87,8 @@
<router-link v-if="!shouldShowTrending" v-t="'titles.feed'" to="/feed" />
<button
efy_sidebar_btn="relative, pp-desktop"
style="
background: transparent;
-webkit-text-fill-color: var(--efy_text);
padding: 0;
margin: -5rem 0 0 0;
border: 0;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
"
style="background: transparent; padding: 0; margin: -5rem 0 0 0; border: 0"
class="efy_trans_filter_off efy_shadow_button_off"
>
<i efy_icon="menu" style="margin: 0" />
</button>
@ -115,7 +108,7 @@
@focus="onInputFocus"
@blur="onInputBlur"
/>
<span v-if="searchText" class="delete-search" @click="searchText = ''">x</span>
<span v-if="searchText" class="delete-search" @click="searchText = ''"></span>
</div>
<SearchSuggestions
v-show="(searchText || showSearchHistory) && suggestionsVisible"
@ -204,17 +197,17 @@ export default {
suggestionsVisible: false,
showLoginModal: false,
showTopNav: false,
homePagePath: "/",
registrationDisabled: false,
};
},
mounted() {
const query = new URLSearchParams(window.location.search).get("search_query");
if (query) this.onSearchTextChange(query);
this.focusOnSearchBar();
},
computed: {
shouldShowLogin(_this) {
return _this.getAuthToken() == null;
},
shouldShowRegister(_this) {
return _this.registrationDisabled == false ? _this.shouldShowLogin : false;
},
shouldShowHistory(_this) {
return _this.getPreferenceBoolean("watchHistory", false);
},
@ -225,6 +218,13 @@ export default {
return _this.getPreferenceBoolean("searchHistory", false) && localStorage.getItem("search_history");
},
},
mounted() {
this.fetchAuthConfig();
const query = new URLSearchParams(window.location.search).get("search_query");
if (query) this.onSearchTextChange(query);
this.focusOnSearchBar();
this.homePagePath = this.getHomePage(this);
},
methods: {
// focus on search bar when Ctrl+k is pressed
focusOnSearchBar() {
@ -241,12 +241,7 @@ export default {
},
onKeyPress(e) {
if (e.key === "Enter") {
e.target.blur();
this.$router.push({
name: "SearchResults",
query: { search_query: this.searchText },
});
return;
this.submitSearch(e);
}
},
onInputFocus() {
@ -259,6 +254,22 @@ export default {
onSearchTextChange(searchText) {
this.searchText = searchText;
},
async fetchAuthConfig() {
this.fetchJson(this.authApiUrl() + "/config").then(config => {
this.registrationDisabled = config?.registrationDisabled === true;
});
},
onSearchClick(e) {
this.submitSearch(e);
},
submitSearch(e) {
e.target.blur();
this.$router.push({
name: "SearchResults",
query: { search_query: this.searchText },
});
return;
},
},
};
</script>

View file

@ -1,7 +1,7 @@
<template>
<div class="flex flex-col justify-center items-center min-h-[88vh]">
<h1 class="font-bold !text-[170rem] mb-[-6vh]">404</h1>
<h2 class="!text-[40rem]" v-t="'info.page_not_found'" />
<a class="btn mt-128" href="/" v-t="'actions.back_to_home'" />
<h2 v-t="'info.page_not_found'" class="!text-[40rem]" />
<a v-t="'actions.back_to_home'" role="button" href="/" />
</div>
</template>

View file

@ -5,11 +5,12 @@
<option v-for="playlist in playlists" :value="playlist.id" :key="playlist.id" v-text="playlist.name" />
</select>
<div class="flex justify-end">
<button ref="addButton" v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
<button
class="btn"
@click="handleClick(selectedPlaylist)"
ref="addButton"
v-t="'actions.add_to_playlist'"
class="btn"
@click="handleClick(selectedPlaylist)"
/>
</div>
</ModalComponent>
@ -23,11 +24,16 @@ export default {
ModalComponent,
},
props: {
videoInfo: {
type: Object,
required: true,
},
videoId: {
type: String,
required: true,
},
},
emits: ["close"],
data() {
return {
playlists: [],
@ -62,31 +68,25 @@ export default {
this.$refs.addButton.disabled = true;
this.processing = true;
this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
method: "POST",
body: JSON.stringify({
playlistId: playlistId,
videoId: this.videoId,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
}).then(json => {
this.addVideosToPlaylist(playlistId, [this.videoId], [this.videoInfo]).then(json => {
this.setPreference("selectedPlaylist" + this.hashCode(this.authApiUrl()), playlistId);
this.$emit("close");
if (json.error) alert(json.error);
});
},
async fetchPlaylists() {
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
headers: {
Authorization: this.getAuthToken(),
},
}).then(json => {
this.getPlaylists().then(json => {
this.playlists = json;
});
},
onCreatePlaylist() {
const name = prompt(this.$t("actions.create_playlist"));
if (!name) return;
this.createPlaylist(name).then(json => {
if (json.error) alert(json.error);
else this.fetchPlaylists();
});
},
},
};
</script>

View file

@ -1,23 +1,24 @@
<template>
<div>
<div class="flex flex-col flex-justify-between">
<router-link :to="props.item.url">
<div class="relative">
<img class="w-full" :src="props.item.thumbnail" loading="lazy" />
</div>
<p>
<span v-text="props.item.name" />
<font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
</p>
</router-link>
<p v-if="props.item.description" v-text="props.item.description" />
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
<p>
<span v-text="props.item.uploader" />
<font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
<span v-text="props.item.uploaderName" />
<font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
</p>
</router-link>
<a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
<a v-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
<template v-if="props.item.videos >= 0">
<br v-if="props.item.uploaderName" />
<strong v-text="`${props.item.videos} ${$t('video.videos')}`" />
@ -29,6 +30,9 @@
<script setup>
const props = defineProps({
item: Object,
item: {
type: Object,
required: true,
},
});
</script>

View file

@ -1,10 +1,12 @@
<template>
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
<div v-if="playlist" v-show="!playlist.error">
<h1 class="text-center my-4" v-text="playlist.name" />
<LoadingIndicatorPage v-show="!playlist?.error" :show-content="playlist">
<h5 class="mb-1 ml-1" v-text="playlist.name" />
<div class="flex justify-between items-center">
<CollapsableText v-if="playlist?.description" :text="playlist.description" />
<div class="mt-1 flex items-center justify-between">
<div>
<router-link class="link" :to="playlist.uploaderUrl || '/'">
<img :src="playlist.uploaderAvatar" loading="lazy" />
@ -14,7 +16,11 @@
<div>
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
<br />
<button class="btn mr-1 ml-2" v-if="authenticated && !isPipedPlaylist" @click="clonePlaylist">
<button v-if="!isPipedPlaylist" class="btn mx-1" @click="bookmarkPlaylist">
{{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
}}<font-awesome-icon class="ml-3" icon="bookmark" />
</button>
<button v-if="authenticated && !isPipedPlaylist" class="btn mr-1 ml-2" @click="clonePlaylist">
{{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
</button>
<button class="btn mr-1" @click="downloadPlaylistAsTxt">
@ -23,7 +29,7 @@
<a class="btn" :href="getRssUrl">
<font-awesome-icon icon="rss" />
</a>
<WatchOnYouTubeButton :link="`https://www.youtube.com/playlist?list=${this.$route.query.list}`" />
<WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" />
</div>
</div>
@ -37,29 +43,34 @@
:index="index"
:playlist-id="$route.query.list"
:admin="admin"
@remove="removeVideo(index)"
height="94"
width="168"
@remove="removeVideo(index)"
/>
</div>
</div>
</LoadingIndicatorPage>
</template>
<script>
import ErrorHandler from "./ErrorHandler.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
import CollapsableText from "./CollapsableText.vue";
import VideoItem from "./VideoItem.vue";
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
import WatchOnButton from "./WatchOnButton.vue";
export default {
components: {
ErrorHandler,
VideoItem,
WatchOnYouTubeButton,
WatchOnButton,
LoadingIndicatorPage,
CollapsableText,
},
data() {
return {
playlist: null,
admin: false,
isBookmarked: false,
};
},
computed: {
@ -74,19 +85,17 @@ export default {
},
},
mounted() {
this.getPlaylistData();
const playlistId = this.$route.query.list;
if (this.authenticated && playlistId?.length == 36)
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
headers: {
Authorization: this.getAuthToken(),
},
}).then(json => {
this.getPlaylists().then(json => {
if (json.error) alert(json.error);
else if (json.filter(playlist => playlist.id === playlistId).length > 0) this.admin = true;
else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
});
else if (playlistId.startsWith("local")) this.admin = true;
this.isPlaylistBookmarked();
},
activated() {
this.getPlaylistData();
window.addEventListener("scroll", this.handleScroll);
if (this.playlist) this.updateTitle();
},
@ -95,12 +104,21 @@ export default {
},
methods: {
async fetchPlaylist() {
const playlistId = this.$route.query.list;
if (playlistId.startsWith("local")) {
return this.getPlaylist(playlistId);
}
return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
},
async getPlaylistData() {
this.fetchPlaylist()
.then(data => (this.playlist = data))
.then(() => this.updateTitle());
.then(() => {
this.updateTitle();
this.updateWatched(this.playlist.relatedStreams);
this.fetchDeArrowContent(this.playlist.relatedStreams);
});
},
async updateTitle() {
document.title = this.playlist.name + " - Piped";
@ -116,6 +134,7 @@ export default {
this.playlist.nextpage = json.nextpage;
this.loading = false;
json.relatedStreams.map(stream => this.playlist.relatedStreams.push(stream));
this.fetchDeArrowContent(this.playlist.relatedStreams);
});
}
},
@ -144,6 +163,48 @@ export default {
});
this.download(data, this.playlist.name + ".txt", "text/plain");
},
async bookmarkPlaylist() {
if (!this.playlist) return;
if (this.isBookmarked) {
this.removePlaylistBookmark();
return;
}
if (window.db) {
const playlistId = this.$route.query.list;
var tx = window.db.transaction("playlist_bookmarks", "readwrite");
var store = tx.objectStore("playlist_bookmarks");
store.put({
playlistId: playlistId,
name: this.playlist.name,
uploader: this.playlist.uploader,
uploaderUrl: this.playlist.uploaderUrl,
thumbnail: this.playlist.thumbnailUrl,
uploaderAvatar: this.playlist.uploaderAvatar,
videos: this.playlist.videos,
});
this.isBookmarked = true;
}
},
async removePlaylistBookmark() {
var tx = window.db.transaction("playlist_bookmarks", "readwrite");
var store = tx.objectStore("playlist_bookmarks");
store.delete(this.$route.query.list);
this.isBookmarked = false;
},
async isPlaylistBookmarked() {
// needed in order to change the is bookmarked var later
const App = this;
const playlistId = this.$route.query.list;
var tx = window.db.transaction("playlist_bookmarks", "readwrite");
var store = tx.objectStore("playlist_bookmarks");
var req = store.openCursor(playlistId);
req.onsuccess = function (e) {
var cursor = e.target.result;
App.isBookmarked = cursor ? true : false;
};
},
},
};
</script>

View file

@ -29,18 +29,7 @@ export default {
},
selectedIndex: {
type: Number,
},
},
mounted() {
this.updateScroll();
},
methods: {
updateScroll() {
const elems = Array.from(this.$refs.scrollable.children).filter(elm => elm.matches("div"));
const index = this.selectedIndex - 1;
if (index < elems.length)
this.$refs.scrollable.scrollTop =
elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
required: true,
},
},
watch: {
@ -54,5 +43,18 @@ export default {
deep: true,
},
},
mounted() {
this.updateScroll();
this.updateWatched(this.playlist.relatedStreams);
},
methods: {
updateScroll() {
const elems = Array.from(this.$refs.scrollable.children).filter(elm => elm.matches("div"));
const index = this.selectedIndex - 1;
if (index < elems.length)
this.$refs.scrollable.scrollTop =
elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
},
},
};
</script>

View file

@ -1,125 +1,153 @@
<template>
<h1 class="font-bold text-center my-4" v-t="'titles.playlists'" />
<hr />
<div class="flex justify-between">
<button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
<div class="flex">
<button v-if="playlists.length > 0" v-t="'actions.export_to_json'" @click="exportPlaylists" />
<input id="fileSelector" ref="fileSelector" type="file" class="display-none" @change="importPlaylists" />
<label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" role="button" />
</div>
</div>
<div class="video-grid">
<div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
<router-link :to="`/playlist?list=${playlist.id}`">
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 15rem"
class="flex link"
:title="playlist.name"
v-text="playlist.name"
/>
</router-link>
<div class="pp-video-card-buttons flex gap-15rem children:m-0" style="flex-wrap: wrap">
<button v-text="`${playlist.videos} ${$t('video.videos')}`" class="thumbnail-overlay thumbnail-right" />
<button
v-t="'actions.edit_playlist'"
class="pp-color h-auto"
@click="showPlaylistEditModal(playlist)"
/>
<button
v-t="'actions.delete_playlist'"
class="pp-color h-auto"
@click="playlistToDelete = playlist.id"
/>
</div>
<ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
<div class="flex flex-col gap-2">
<h2 v-t="'actions.edit_playlist'" />
<input
v-model="newPlaylistName"
class="input"
type="text"
:placeholder="$t('actions.playlist_name')"
/>
<input
v-model="newPlaylistDescription"
class="input"
type="text"
:placeholder="$t('actions.playlist_description')"
/>
<button v-t="'actions.okay'" class="btn ml-auto" @click="editPlaylist(playlist)" />
</div>
</ModalComponent>
<ConfirmModal
v-if="playlistToDelete == playlist.id"
:message="$t('actions.delete_playlist_confirm')"
@close="playlistToDelete = null"
@confirm="onDeletePlaylist(playlist.id)"
/>
</div>
</div>
<hr />
<div>
<div class="flex justify-between">
<button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
<div class="flex">
<button
v-if="this.playlists.length > 0"
v-t="'actions.export_to_json'"
class="btn"
@click="exportPlaylists"
/>
<input
id="fileSelector"
ref="fileSelector"
type="file"
class="display-none"
@change="importPlaylists"
/>
<label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" role="button" />
</div>
</div>
<h2 v-t="'titles.bookmarks'" class="my-4 font-bold" />
<div class="video-grid">
<div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
<router-link :to="`/playlist?list=${playlist.id}`">
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
class="flex link"
:title="playlist.name"
v-text="playlist.name"
/>
</router-link>
<div class="pp-video-card-buttons flex gap-15rem children:m-0" style="flex-wrap: wrap">
<button
class="thumbnail-overlay thumbnail-right"
v-text="`${playlist.videos} ${$t('video.videos')}`"
/>
<button
class="pp-color h-auto"
@click="renamePlaylist(playlist.id)"
v-t="'actions.rename_playlist'"
/>
<button
class="pp-color h-auto"
@click="deletePlaylist(playlist.id)"
v-t="'actions.delete_playlist'"
/>
<div v-if="bookmarks" class="video-grid">
<router-link
v-for="(playlist, index) in bookmarks"
:key="playlist.playlistId"
:to="`/playlist?list=${playlist.playlistId}`"
>
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
<div class="relative text-sm">
<span class="thumbnail-overlay thumbnail-right" v-text="`${playlist.videos} ${$t('video.videos')}`" />
<div class="absolute bottom-100px right-5px z-100 px-5px" @click.prevent="removeBookmark(index)">
<font-awesome-icon class="ml-3" icon="bookmark" />
</div>
</div>
</div>
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 0 0 10rem"
class="link my-2 flex overflow-hidden"
:title="playlist.name"
v-text="playlist.name"
/>
<a :href="playlist.uploaderUrl" class="flex items-center">
<img class="h-32px w-32px rounded-full" :src="playlist.uploaderAvatar" />
<span class="ml-3 hover:underline" v-text="playlist.uploader" />
</a>
</router-link>
</div>
</template>
<script>
import ConfirmModal from "./ConfirmModal.vue";
import ModalComponent from "./ModalComponent.vue";
export default {
components: { ConfirmModal, ModalComponent },
data() {
return {
playlists: [],
bookmarks: [],
playlistToDelete: null,
playlistToEdit: null,
newPlaylistName: "",
newPlaylistDescription: "",
};
},
mounted() {
if (this.authenticated) this.fetchPlaylists();
else this.$router.push("/login");
this.fetchPlaylists();
this.loadPlaylistBookmarks();
},
activated() {
document.title = this.$t("titles.playlists") + " - Piped";
},
methods: {
fetchPlaylists() {
this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
headers: {
Authorization: this.getAuthToken(),
},
}).then(json => {
this.getPlaylists().then(json => {
this.playlists = json;
});
},
renamePlaylist(id) {
const newName = prompt(this.$t("actions.new_playlist_name"));
if (!newName) return;
this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
method: "POST",
body: JSON.stringify({
playlistId: id,
newName: newName,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
}).then(json => {
if (json.error) alert(json.error);
else {
this.playlists.forEach((playlist, index) => {
if (playlist.id == id) {
this.playlists[index].name = newName;
return;
}
});
}
});
showPlaylistEditModal(playlist) {
this.newPlaylistName = playlist.name;
this.newPlaylistDescription = playlist.description;
this.playlistToEdit = playlist.id;
},
deletePlaylist(id) {
if (confirm(this.$t("actions.delete_playlist_confirm")))
this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
method: "POST",
body: JSON.stringify({
playlistId: id,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
}).then(json => {
editPlaylist(selectedPlaylist) {
// save the new name and description since they could be overwritten during the http request
const newName = this.newPlaylistName;
const newDescription = this.newPlaylistDescription;
if (newName != selectedPlaylist.name) {
this.renamePlaylist(selectedPlaylist.id, newName).then(json => {
if (json.error) alert(json.error);
else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
else selectedPlaylist.name = newName;
});
}
if (newDescription != selectedPlaylist.description) {
this.changePlaylistDescription(selectedPlaylist.id, newDescription).then(json => {
if (json.error) alert(json.error);
else selectedPlaylist.description = newDescription;
});
}
this.playlistToEdit = null;
},
onDeletePlaylist(id) {
this.deletePlaylist(id).then(json => {
if (json.error) alert(json.error);
else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
});
this.playlistToDelete = null;
},
onCreatePlaylist() {
const name = prompt(this.$t("actions.create_playlist"));
@ -129,19 +157,6 @@ export default {
else this.fetchPlaylists();
});
},
async createPlaylist(name) {
let json = await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
method: "POST",
body: JSON.stringify({
name: name,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
});
return json;
},
async exportPlaylists() {
if (!this.playlists) return;
let json = {
@ -154,8 +169,8 @@ export default {
this.download(JSON.stringify(json), "playlists.json", "application/json");
},
async fetchPlaylistJson(playlistId) {
let playlist = await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
let playlistJson = {
let playlist = await this.getPlaylist(playlistId);
return {
name: playlist.name,
// possible other types: history, watch later, ...
type: "playlist",
@ -164,57 +179,69 @@ export default {
// list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
};
return playlistJson;
},
async importPlaylists() {
const file = this.$refs.fileSelector.files[0];
const files = this.$refs.fileSelector.files;
for (let file of files) {
await this.importPlaylistFile(file);
}
window.location.reload();
},
async importPlaylistFile(file) {
let text = await file.text();
let tasks = [];
// list of playlists exported from Piped
if (text.includes("playlists")) {
if (file.name.slice(-4).toLowerCase() == ".csv") {
const lines = text.split("\n");
const playlistName = lines[1].split(",")[4];
const playlist = {
name: playlistName != "" ? playlistName : new Date().toJSON(),
videos: lines
.slice(4, lines.length)
.filter(line => line != "")
.slice(1)
.map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
};
tasks.push(this.createPlaylistWithVideos(playlist));
} else if (text.includes('"Piped"')) {
// CSV from Google Takeout
let playlists = JSON.parse(text).playlists;
if (!playlists.length) {
alert(this.$t("actions.no_valid_playlists"));
return;
}
for (var i = 0; i < playlists.length; i++) {
tasks.push(this.createPlaylistWithVideos(playlists[i]));
for (let playlist of playlists) {
tasks.push(this.createPlaylistWithVideos(playlist));
}
// CSV from Google Takeout
} else if (file.name.slice(-4).toLowerCase() == ".csv") {
const lines = text.split("\n");
const playlist = {
name: lines[1].split(",")[4],
videos: lines
.slice(4, lines.length)
.filter(line => line != "")
.map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
};
tasks.push(this.createPlaylistWithVideos(playlist));
} else {
alert(this.$t("actions.no_valid_playlists"));
return;
}
await Promise.all(tasks);
window.location.reload();
},
async createPlaylistWithVideos(playlist) {
let newPlaylist = await this.createPlaylist(playlist.name);
let videoIds = playlist.videos.map(url => url.substr(-11));
await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
},
async addVideosToPlaylist(playlistId, videoIds) {
await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
method: "POST",
body: JSON.stringify({
playlistId: playlistId,
videoIds: videoIds,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
});
async loadPlaylistBookmarks() {
if (!window.db) return;
var tx = window.db.transaction("playlist_bookmarks", "readonly");
var store = tx.objectStore("playlist_bookmarks");
const cursorRequest = store.openCursor();
cursorRequest.onsuccess = e => {
const cursor = e.target.result;
if (cursor) {
this.bookmarks.push(cursor.value);
cursor.continue();
}
};
},
async removeBookmark(index) {
var tx = window.db.transaction("playlist_bookmarks", "readwrite");
var store = tx.objectStore("playlist_bookmarks");
store.delete(this.bookmarks[index].playlistId);
this.bookmarks.splice(index, 1);
},
},
};

View file

@ -1,4 +1,5 @@
<template>
<!--efy-->
<div class="pp-pref-cards">
<div efy_card="grid">
<h2>Quick</h2>
@ -96,6 +97,342 @@
v-t="'actions.restore_preferences'"
@click="restorePreferences()"
/>
</div>
</div>
<!--master-->
<div class="flex">
<button @click="$router.go(-1) || $router.push('/')">
<font-awesome-icon icon="chevron-left" /><span v-t="'actions.back'" class="ml-1.5" />
</button>
</div>
<h1 v-t="'titles.preferences'" class="text-center font-bold" />
<hr />
<label for="ddlTheme" class="pref">
<strong v-t="'actions.theme'" />
<select id="ddlTheme" v-model="selectedTheme" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.auto'" value="auto" />
<option v-t="'actions.dark'" value="dark" />
<option v-t="'actions.light'" value="light" />
</select>
</label>
<label class="pref" for="ddlLanguageSelection">
<strong v-t="'actions.language_selection'" />
<select id="ddlLanguageSelection" v-model="selectedLanguage" class="select w-auto" @change="onChange($event)">
<option v-for="language in languages" :key="language.code" :value="language.code" v-text="language.name" />
</select>
</label>
<label class="pref" for="ddlCountrySelection">
<strong v-t="'actions.country_selection'" />
<select id="ddlCountrySelection" v-model="countrySelected" class="select w-50" @change="onChange($event)">
<option v-for="country in countryMap" :key="country.code" :value="country.code" v-text="country.name" />
</select>
</label>
<label class="pref" for="ddlDefaultHomepage">
<strong v-t="'actions.default_homepage'" />
<select id="ddlDefaultHomepage" v-model="defaultHomepage" class="select w-auto" @change="onChange($event)">
<option v-t="'titles.trending'" value="trending" />
<option v-t="'titles.feed'" value="feed" />
</select>
</label>
<h2 v-t="'titles.player'" class="text-center" />
<label class="pref" for="chkAutoPlayVideo">
<strong v-t="'actions.autoplay_video'" />
<input
id="chkAutoPlayVideo"
v-model="autoPlayVideo"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAutoDisplayCaptions">
<strong v-t="'actions.auto_display_captions'" />
<input
id="chkAutoDisplayCaptions"
v-model="autoDisplayCaptions"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAutoPlayNextCountdown">
<strong v-t="'actions.autoplay_next_countdown'" />
<input
id="chkAutoPlayNextCountdown"
v-model="autoPlayNextCountdown"
class="input w-24"
type="number"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAudioOnly">
<strong v-t="'actions.audio_only'" />
<input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="ddlDefaultQuality">
<strong v-t="'actions.default_quality'" />
<select id="ddlDefaultQuality" v-model="defaultQuality" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.auto'" value="0" />
<option v-for="resolution in resolutions" :key="resolution" :value="resolution" v-text="`${resolution}p`" />
</select>
</label>
<label class="pref" for="txtBufferingGoal">
<strong v-t="'actions.buffering_goal'" />
<input
id="txtBufferingGoal"
v-model="bufferingGoal"
class="input w-24"
type="text"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeComments">
<strong v-t="'actions.minimize_comments_default'" />
<input
id="chkMinimizeComments"
v-model="minimizeComments"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeDescription">
<strong v-t="'actions.minimize_description_default'" />
<input
id="chkMinimizeDescription"
v-model="minimizeDescription"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeRecommendations">
<strong v-t="'actions.minimize_recommendations_default'" />
<input
id="chkMinimizeRecommendations"
v-model="minimizeRecommendations"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeChapters">
<strong v-t="'actions.minimize_chapters_default'" />
<input
id="chkMinimizeChapters"
v-model="minimizeChapters"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<!-- chapters layout on mobile -->
<label class="pref lg:invisible" for="chkMinimizeChapters">
<strong v-t="'actions.chapters_layout_mobile'" />
<select id="ddlDefaultHomepage" v-model="mobileChapterLayout" class="select w-auto" @change="onChange($event)">
<option v-t="'video.chapters_horizontal'" value="Horizontal" />
<option v-t="'video.chapters_vertical'" value="Vertical" />
</select>
</label>
<label class="pref" for="chkShowWatchOnYouTube">
<strong v-t="'actions.show_watch_on_youtube'" />
<input
id="chkShowWatchOnYouTube"
v-model="showWatchOnYouTube"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkShowSearchSuggestions">
<strong v-t="'actions.show_search_suggestions'" />
<input
id="chkShowSearchSuggestions"
v-model="searchSuggestions"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkStoreSearchHistory">
<strong v-t="'actions.store_search_history'" />
<input
id="chkStoreSearchHistory"
v-model="searchHistory"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkStoreWatchHistory">
<strong v-t="'actions.store_watch_history'" />
<input
id="chkStoreWatchHistory"
v-model="watchHistory"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label v-if="watchHistory" class="pref" for="chkHideWatched">
<strong v-t="'actions.hide_watched'" />
<input id="chkHideWatched" v-model="hideWatched" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="ddlEnabledCodecs">
<strong v-t="'actions.enabled_codecs'" />
<select
id="ddlEnabledCodecs"
v-model="enabledCodecs"
class="select h-auto w-auto"
multiple
@change="onChange($event)"
>
<option value="av1">AV1</option>
<option value="vp9">VP9</option>
<option value="avc">AVC (h.264)</option>
</select>
</label>
<label class="pref" for="chkDisableLBRY">
<strong v-t="'actions.disable_lbry'" />
<input id="chkDisableLBRY" v-model="disableLBRY" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="chkEnableLBRYProxy">
<strong v-t="'actions.enable_lbry_proxy'" />
<input
id="chkEnableLBRYProxy"
v-model="proxyLBRY"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<h2 class="text-center">SponsorBlock</h2>
<p class="text-center">
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
</p>
<label class="pref" for="chkEnableSponsorblock">
<strong v-t="'actions.enable_sponsorblock'" />
<input
id="chkEnableSponsorblock"
v-model="sponsorBlock"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<div v-if="sponsorBlock">
<label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
<strong v-t="item.label" />
<select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.no'" value="no" />
<option v-t="'actions.skip_button_only'" value="button" />
<option v-t="'actions.skip_automatically'" value="auto" />
</select>
</label>
<label class="pref" for="chkShowMarkers">
<strong v-t="'actions.show_markers'" />
<input
id="chkShowMarkers"
v-model="showMarkers"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="txtMinSegmentLength">
<strong v-t="'actions.min_segment_length'" />
<input
id="txtMinSegmentLength"
v-model="minSegmentLength"
class="input w-24"
type="text"
@change="onChange($event)"
/>
</label>
</div>
<h2 v-t="'titles.dearrow'" class="text-center" />
<p class="text-center">
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
</p>
<label class="pref" for="chkDeArrow">
<strong v-t="'actions.enable_dearrow'" />
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<h2 v-t="'titles.instance'" class="text-center" />
<label class="pref" for="ddlInstanceSelection">
<strong v-text="`${$t('actions.instance_selection')}:`" />
<select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
<option
v-for="instance in instances"
:key="instance.name"
:value="instance.api_url"
v-text="instance.name"
/>
</select>
</label>
<label class="pref" for="chkAuthInstance">
<strong v-text="`${$t('actions.different_auth_instance')}:`" />
<input
id="chkAuthInstance"
v-model="authInstance"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<template v-if="authInstance">
<label class="pref" for="ddlAuthInstanceSelection">
<strong v-text="`${$t('actions.instance_auth_selection')}:`" />
<select
id="ddlAuthInstanceSelection"
v-model="selectedAuthInstance"
class="select w-auto"
@change="onChange($event)"
>
<option
v-for="instance in instances"
:key="instance.name"
:value="instance.api_url"
v-text="instance.name"
/>
</select>
</label>
</template>
<br />
<!-- options that are visible only when logged in -->
<div v-if="authenticated">
<h2 v-t="'titles.account'" class="text-center"></h2>
<label class="pref" for="txtDeleteAccountPassword">
<strong v-t="'actions.delete_account'" />
<div class="flex items-center">
<input
id="txtDeleteAccountPassword"
ref="txtDeleteAccountPassword"
v-model="password"
:placeholder="$t('login.password')"
:aria-label="$t('login.password')"
class="input mr-2 w-auto"
type="password"
@keyup.enter="deleteAccount"
/>
<a v-t="'actions.delete_account'" class="btn w-auto" @click="deleteAccount" />
</div>
</label>
<div class="pref">
<a v-t="'actions.logout'" class="btn w-auto" @click="logout" />
<a
v-t="'actions.invalidate_session'"
class="btn w-auto"
style="margin-left: 0.5em"
@click="invalidateSession"
/>
<!--masterend-->
<input class="hidden" id="fileSelector" ref="fileSelector" type="file" @change="restorePreferences()" />
<!-- options that are visible only when logged in -->
@ -406,7 +743,7 @@
<th v-t="'preferences.instance_locations'" />
<th v-t="'preferences.has_cdn'" />
<th v-t="'preferences.registered_users'" />
<th class="lt-md:hidden" v-t="'preferences.version'" />
<th v-t="'preferences.version'" class="lt-md:hidden" />
<th v-t="'preferences.up_to_date'" />
<th v-t="'preferences.ssl_score'" />
</tr>
@ -420,35 +757,61 @@
<td class="lt-md:hidden" v-text="instance.version" />
<td v-text="`${instance.up_to_date ? '&#9989;' : '&#10060;'}`" />
<td>
<a :href="sslScore(instance.api_url)" target="_blank" v-t="'actions.view_ssl_score'" />
<a v-t="'actions.view_ssl_score'" :href="sslScore(instance.api_url)" target="_blank" />
</td>
</tr>
</tbody>
</table>
<!--master-->
<br />
<p v-t="'info.preferences_note'" />
<br />
<button v-t="'actions.reset_preferences'" class="btn" @click="showConfirmResetPrefsDialog = true" />
<button v-t="'actions.backup_preferences'" class="btn mx-4" @click="backupPreferences()" />
<label v-t="'actions.restore_preferences'" for="fileSelector" class="btn" @click="restorePreferences()" />
<input id="fileSelector" ref="fileSelector" class="hidden" type="file" @change="restorePreferences()" />
<ConfirmModal
v-if="showConfirmResetPrefsDialog"
:message="$t('actions.confirm_reset_preferences')"
@close="showConfirmResetPrefsDialog = false"
@confirm="resetPreferences()"
/>
<!--masterend-->
</template>
<script>
import CountryMap from "@/utils/CountryMaps/en.json";
import ConfirmModal from "./ConfirmModal.vue";
export default {
components: {
ConfirmModal,
},
data() {
return {
mobileChapterLayout: "Vertical",
selectedInstance: null,
authInstance: false,
selectedAuthInstance: null,
instances: [],
sponsorBlock: true,
skipSponsor: true,
skipIntro: false,
skipOutro: false,
skipPreview: false,
skipInteraction: true,
skipSelfPromo: true,
skipMusicOffTopic: true,
skipHighlight: false,
skipFiller: false,
skipOptions: new Map([
["sponsor", { value: "auto", label: "actions.skip_sponsors" }],
["intro", { value: "no", label: "actions.skip_intro" }],
["outro", { value: "no", label: "actions.skip_outro" }],
["preview", { value: "no", label: "actions.skip_preview" }],
["interaction", { value: "auto", label: "actions.skip_interaction" }],
["selfpromo", { value: "auto", label: "actions.skip_self_promo" }],
["music_offtopic", { value: "auto", label: "actions.skip_non_music" }],
["poi_highlight", { value: "no", label: "actions.skip_highlight" }],
["filler", { value: "no", label: "actions.skip_filler_tangent" }],
]),
showMarkers: true,
minSegmentLength: 0,
dearrow: false,
selectedTheme: "dark",
autoPlayVideo: true,
autoDisplayCaptions: false,
autoPlayNextCountdown: 5,
listen: false,
resolutions: [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320],
defaultQuality: 0,
@ -461,6 +824,7 @@ export default {
minimizeRecommendations: false,
minimizeChapters: false,
showWatchOnYouTube: false,
searchSuggestions: true,
watchHistory: false,
searchHistory: false,
hideWatched: false,
@ -468,6 +832,7 @@ export default {
languages: [
{ code: "ar", name: "Arabic" },
{ code: "az", name: "Azərbaycan" },
{ code: "bg", name: "Български" },
{ code: "bn", name: "বাংলা" },
{ code: "bs", name: "Bosanski" },
{ code: "ca", name: "Català" },
@ -495,6 +860,7 @@ export default {
{ code: "ml", name: "മലയാളം" },
{ code: "nb_NO", name: "Norwegian Bokmål" },
{ code: "nl", name: "Nederlands" },
{ code: "oc", name: "Occitan" },
{ code: "or", name: "ଓଡ଼ିଆ" },
{ code: "pl", name: "Polski" },
{ code: "pt", name: "Português" },
@ -502,6 +868,7 @@ export default {
{ code: "pt_BR", name: "Português (Brasil)" },
{ code: "ro", name: "Română" },
{ code: "ru", name: "Русский" },
{ code: "si", name: "සිංහල" },
{ code: "sr", name: "Српски" },
{ code: "sv", name: "Svenska" },
{ code: "ta", name: "தமிழ்" },
@ -516,6 +883,7 @@ export default {
disableLBRY: false,
proxyLBRY: false,
password: null,
showConfirmResetPrefsDialog: false,
};
},
activated() {
@ -526,7 +894,7 @@ export default {
this.fetchJson("https://piped-instances.kavin.rocks/").then(resp => {
this.instances = resp;
if (this.instances.filter(instance => instance.api_url == this.apiUrl()).length == 0)
if (!this.instances.some(instance => instance.api_url == this.apiUrl()))
this.instances.push({
name: "Custom Instance",
api_url: this.apiUrl(),
@ -541,57 +909,30 @@ export default {
this.selectedAuthInstance = this.getPreferenceString("auth_instance_url", this.selectedInstance);
this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true);
if (localStorage.getItem("selectedSkip") !== null) {
var skipList = localStorage.getItem("selectedSkip").split(",");
this.skipSponsor =
this.skipIntro =
this.skipOutro =
this.skipPreview =
this.skipInteraction =
this.skipSelfPromo =
this.skipMusicOffTopic =
this.skipHighlight =
this.skipFiller =
false;
var skipOptions, skipList;
if ((skipOptions = this.getPreferenceJSON("skipOptions")) !== undefined) {
Object.entries(skipOptions).forEach(([key, value]) => {
var opt = this.skipOptions.get(key);
if (opt !== undefined) opt.value = value;
else console.log("Unknown sponsor type: " + key);
});
} else if ((skipList = this.getPreferenceString("selectedSkip")) !== undefined) {
skipList = skipList.split(",");
this.skipOptions.forEach(opt => (opt.value = "no"));
skipList.forEach(skip => {
switch (skip) {
case "sponsor":
this.skipSponsor = true;
break;
case "intro":
this.skipIntro = true;
break;
case "outro":
this.skipOutro = true;
break;
case "preview":
this.skipPreview = true;
break;
case "interaction":
this.skipInteraction = true;
break;
case "selfpromo":
this.skipSelfPromo = true;
break;
case "music_offtopic":
this.skipMusicOffTopic = true;
break;
case "poi_highlight":
this.skipHighlight = true;
break;
case "filler":
this.skipFiller = true;
break;
default:
console.log("Unknown sponsor type: " + skip);
break;
}
var opt = this.skipOptions.get(skip);
if (opt !== undefined) opt.value = "auto";
else console.log("Unknown sponsor type: " + skip);
});
}
this.showMarkers = this.getPreferenceBoolean("showMarkers", true);
this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
this.dearrow = this.getPreferenceBoolean("dearrow", false);
this.selectedTheme = this.getPreferenceString("theme", "dark");
this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true);
this.autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
this.autoPlayNextCountdown = this.getPreferenceNumber("autoPlayNextCountdown", 5);
this.listen = this.getPreferenceBoolean("listen", false);
this.defaultQuality = Number(localStorage.getItem("quality"));
this.bufferingGoal = Math.max(Number(localStorage.getItem("bufferGoal")), 10);
@ -602,6 +943,7 @@ export default {
this.minimizeRecommendations = this.getPreferenceBoolean("minimizeRecommendations", false);
this.minimizeChapters = this.getPreferenceBoolean("minimizeChapters", false);
this.showWatchOnYouTube = this.getPreferenceBoolean("showWatchOnYouTube", false);
this.searchSuggestions = this.getPreferenceBoolean("searchSuggestions", true);
this.watchHistory = this.getPreferenceBoolean("watchHistory", false);
this.searchHistory = this.getPreferenceBoolean("searchHistory", false);
this.selectedLanguage = this.getPreferenceString("hl", await this.defaultLanguage);
@ -609,6 +951,7 @@ export default {
this.disableLBRY = this.getPreferenceBoolean("disableLBRY", false);
this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
this.hideWatched = this.getPreferenceBoolean("hideWatched", false);
this.mobileChapterLayout = this.getPreferenceString("mobileChapterLayout", "Vertical");
if (this.selectedLanguage != "en") {
try {
this.CountryMap = await import(`../utils/CountryMaps/${this.selectedLanguage}.json`).then(
@ -638,21 +981,19 @@ export default {
localStorage.setItem("auth_instance_url", this.selectedAuthInstance);
localStorage.setItem("sponsorblock", this.sponsorBlock);
var sponsorSelected = [];
if (this.skipSponsor) sponsorSelected.push("sponsor");
if (this.skipIntro) sponsorSelected.push("intro");
if (this.skipOutro) sponsorSelected.push("outro");
if (this.skipPreview) sponsorSelected.push("preview");
if (this.skipInteraction) sponsorSelected.push("interaction");
if (this.skipSelfPromo) sponsorSelected.push("selfpromo");
if (this.skipMusicOffTopic) sponsorSelected.push("music_offtopic");
if (this.skipHighlight) sponsorSelected.push("poi_highlight");
if (this.skipFiller) sponsorSelected.push("filler");
localStorage.setItem("selectedSkip", sponsorSelected);
var skipOptions = {};
this.skipOptions.forEach((v, k) => (skipOptions[k] = v.value));
localStorage.setItem("skipOptions", JSON.stringify(skipOptions));
localStorage.setItem("showMarkers", this.showMarkers);
localStorage.setItem("minSegmentLength", this.minSegmentLength);
localStorage.setItem("dearrow", this.dearrow);
localStorage.setItem("theme", this.selectedTheme);
localStorage.setItem("playerAutoPlay", this.autoPlayVideo);
localStorage.setItem("autoDisplayCaptions", this.autoDisplayCaptions);
localStorage.setItem("autoPlayNextCountdown", this.autoPlayNextCountdown);
localStorage.setItem("listen", this.listen);
localStorage.setItem("quality", this.defaultQuality);
localStorage.setItem("bufferGoal", this.bufferingGoal);
@ -663,6 +1004,7 @@ export default {
localStorage.setItem("minimizeRecommendations", this.minimizeRecommendations);
localStorage.setItem("minimizeChapters", this.minimizeChapters);
localStorage.setItem("showWatchOnYouTube", this.showWatchOnYouTube);
localStorage.setItem("searchSuggestions", this.searchSuggestions);
localStorage.setItem("watchHistory", this.watchHistory);
localStorage.setItem("searchHistory", this.searchHistory);
if (!this.searchHistory) localStorage.removeItem("search_history");
@ -671,6 +1013,7 @@ export default {
localStorage.setItem("disableLBRY", this.disableLBRY);
localStorage.setItem("proxyLBRY", this.proxyLBRY);
localStorage.setItem("hideWatched", this.hideWatched);
localStorage.setItem("mobileChapterLayout", this.mobileChapterLayout);
if (shouldReload) window.location.reload();
}
@ -700,7 +1043,7 @@ export default {
window.location = "/";
},
resetPreferences() {
if (!confirm(this.$t("actions.confirm_reset_preferences"))) return;
this.showConfirmResetPrefsDialog = false;
// clear the local storage
localStorage.clear();
// redirect to the home page
@ -740,4 +1083,10 @@ export default {
.pref {
@apply flex justify-between items-center;
}
.pref:nth-child(odd) {
@apply bg-gray-200;
}
.dark .pref:nth-child(odd) {
@apply bg-dark-800;
}
</style>

30
src/components/QrCode.vue Normal file
View file

@ -0,0 +1,30 @@
<template>
<canvas ref="qrCodeCanvas" style="border-radius: var(--efy_radius)" />
</template>
<script>
import QRCode from "qrcode";
export default {
props: {
text: {
type: String,
required: true,
},
},
watch: {
text() {
this.generateQrCode();
},
},
mounted() {
this.generateQrCode();
},
methods: {
generateQrCode() {
QRCode.toCanvas(this.$refs.qrCodeCanvas, this.text, error => {
if (error) console.error(error);
});
},
},
};
</script>

View file

@ -0,0 +1,114 @@
<template>
<h1 v-t="'titles.register'" class="my-4 text-center font-bold" />
<hr />
<div class="flex justify-center text-center">
<form class="items-center px-3 children:pb-3">
<div>
<input
v-model="username"
class="input w-full"
type="text"
autocomplete="username"
:placeholder="$t('login.username')"
:aria-label="$t('login.username')"
@keyup.enter="register"
/>
</div>
<div class="flex justify-center">
<input
v-model="password"
class="input w-full"
:type="showPassword ? 'text' : 'password'"
autocomplete="password"
:placeholder="$t('login.password')"
:aria-label="$t('login.password')"
@keyup.enter="register"
/>
<button type="button" class="btn ml-2" @click="showPassword = !showPassword">
<div class="i-fa6-solid:eye" />
</button>
</div>
<div class="flex justify-center">
<input
v-model="passwordConfirm"
class="input w-full"
:type="showConfirmPassword ? 'text' : 'password'"
autocomplete="password"
:placeholder="$t('login.password_confirm')"
:aria-label="$t('login.password_confirm')"
@keyup.enter="register"
/>
<button type="button" class="btn ml-2" @click="showConfirmPassword = !showConfirmPassword">
<div class="i-fa6-solid:eye" />
</button>
</div>
<div>
<a v-t="'titles.register'" class="btn w-auto" @click="register" />
</div>
</form>
</div>
<ConfirmModal
v-if="showUnsecureRegisterDialog"
:message="$t('info.register_no_email_note')"
@close="showUnsecureRegisterDialog = false"
@confirm="
forceUnsecureRegister = true;
showUnsecureRegisterDialog = false;
register();
"
/>
</template>
<script>
import { isEmail } from "../utils/Misc.js";
import ConfirmModal from "./ConfirmModal.vue";
export default {
components: { ConfirmModal },
data() {
return {
username: null,
password: null,
passwordConfirm: null,
showPassword: false,
showConfirmPassword: false,
showUnsecureRegisterDialog: false,
forceUnsecureRegister: false,
};
},
mounted() {
//TODO: Add Server Side check
if (this.getAuthToken()) {
this.$router.push("/");
}
},
activated() {
document.title = "Register - Piped";
},
methods: {
register() {
if (!this.username || !this.password) return;
if (this.password != this.passwordConfirm) {
alert(this.$t("login.passwords_incorrect"));
return;
}
if (isEmail(this.username) && !this.forceUnsecureRegister) {
this.showUnsecureRegisterDialog = true;
return;
}
this.fetchJson(this.authApiUrl() + "/register", null, {
method: "POST",
body: JSON.stringify({
username: this.username,
password: this.password,
}),
}).then(resp => {
if (resp.token) {
this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token);
window.location = "/"; // done to bypass cache
} else alert(resp.error);
});
},
},
};
</script>

View file

@ -1,11 +1,11 @@
<template>
<h1 class="text-center my-2" v-text="$route.query.search_query" />
<h1 class="my-2 text-center" v-text="$route.query.search_query" />
<label for="ddlSearchFilters" class="mr-2">
<strong v-text="`${$t('actions.filter')}:`" />
</label>
<select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
<option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`search.${filter}`" />
<option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
</select>
<hr />
@ -18,19 +18,21 @@
</i18n-t>
</div>
<div v-if="results" class="video-grid">
<LoadingIndicatorPage :show-content="results != null && results.items?.length" class="video-grid">
<template v-for="result in results.items" :key="result.url">
<ContentItem :item="result" height="94" width="168" />
</template>
</div>
</LoadingIndicatorPage>
</template>
<script>
import ContentItem from "./ContentItem.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
export default {
components: {
ContentItem,
LoadingIndicatorPage,
},
data() {
return {
@ -44,6 +46,7 @@ export default {
"music_videos",
"music_albums",
"music_playlists",
"music_artists",
],
selectedFilter: this.$route.query.filter ?? "all",
};
@ -72,7 +75,10 @@ export default {
},
async updateResults() {
document.title = this.$route.query.search_query + " - Piped";
this.results = this.fetchResults().then(json => (this.results = json));
this.results = this.fetchResults().then(json => {
this.results = json;
this.updateWatched(this.results.items);
});
},
updateFilter() {
this.$router.replace({

View file

@ -1,5 +1,5 @@
<template>
<div class="absolute suggestions-container">
<div class="suggestions-container absolute">
<ul>
<li
v-for="(suggestion, i) in searchSuggestions"
@ -50,12 +50,16 @@ export default {
if (!this.searchText) {
if (this.getPreferenceBoolean("searchHistory", false))
this.searchSuggestions = JSON.parse(localStorage.getItem("search_history")) ?? [];
} else if (this.getPreferenceBoolean("searchSuggestions", true)) {
this.searchSuggestions =
(
await this.fetchJson(this.apiUrl() + "/opensearch/suggestions", {
query: this.searchText,
})
)?.[1] ?? [];
} else {
this.searchSuggestions = (
await this.fetchJson(this.apiUrl() + "/opensearch/suggestions", {
query: this.searchText,
})
)?.[1];
this.searchSuggestions = [];
return;
}
this.searchSuggestions.unshift(this.searchText);
this.setSelected(0);

View file

@ -3,34 +3,45 @@
<h4 v-t="'actions.share'" />
<div class="flex justify-between mt-2 mb-2">
<label v-t="'actions.piped_link'" />
<input type="checkbox" v-model="pipedLink" @change="onChange" />
<input v-model="pipedLink" type="checkbox" @change="onChange" />
</div>
<div v-if="this.hasPlaylist" class="flex justify-between">
<div v-if="hasPlaylist" class="flex justify-between">
<label v-t="'actions.with_playlist'" />
<input type="checkbox" v-model="withPlaylist" @change="onChange" />
<input v-model="withPlaylist" type="checkbox" @change="onChange" />
</div>
<div class="flex justify-between">
<label v-t="'actions.with_timecode'" for="withTimeCode" />
<input id="withTimeCode" type="checkbox" v-model="withTimeCode" @change="onChange" />
<input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" />
</div>
<div v-if="this.withTimeCode" class="flex justify-between mt-2" style="align-items: center">
<div v-if="withTimeCode" class="flex justify-between mt-2" style="align-items: center">
<label v-t="'actions.time_code'" />
<input class="input w-300 mb-0rem" type="text" v-model="timeStamp" />
<input v-model="timeStamp" class="input w-12" type="text" @change="onChange" />
</div>
<a :href="generatedLink" target="_blank">
<h6 class="mb-2 mt-2" v-text="generatedLink" />
</a>
<div class="flex justify-end mt-4">
<button class="btn" style="margin-right: 15rem" v-t="'actions.follow_link'" @click="followLink()" />
<button class="btn" v-t="'actions.copy_link'" @click="copyLink()" />
<QrCode v-if="showQrCode" :text="generatedLink" />
<div class="mt-4 flex justify-end">
<button v-t="'actions.generate_qrcode'" class="btn" @click="showQrCode = !showQrCode" />
<button v-t="'actions.follow_link'" class="btn ml-3" @click="followLink()" />
<button v-t="'actions.copy_link'" class="btn ml-3" @click="copyLink()" />
</div>
</ModalComponent>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const QrCode = defineAsyncComponent(() => import("./QrCode.vue"));
</script>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: {
ModalComponent,
},
props: {
videoId: {
type: String,
@ -42,14 +53,13 @@ export default {
},
playlistId: {
type: String,
default: undefined,
},
playlistIndex: {
type: Number,
default: undefined,
},
},
components: {
ModalComponent,
},
data() {
return {
withTimeCode: true,
@ -57,8 +67,23 @@ export default {
withPlaylist: true,
timeStamp: null,
hasPlaylist: false,
showQrCode: false,
};
},
computed: {
generatedLink() {
var baseUrl = this.pipedLink
? window.location.origin + "/watch?v=" + this.videoId
: "https://youtu.be/" + this.videoId;
var url = new URL(baseUrl);
if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
if (this.hasPlaylist && this.withPlaylist) {
url.searchParams.append("list", this.playlistId);
url.searchParams.append("index", this.playlistIndex);
}
return url.href;
},
},
mounted() {
this.timeStamp = parseInt(this.currentTime);
this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
@ -87,19 +112,5 @@ export default {
this.setPreference("shareWithPlaylist", this.withPlaylist, true);
},
},
computed: {
generatedLink() {
var baseUrl = this.pipedLink
? window.location.origin + "/watch?v=" + this.videoId
: "https://youtu.be/" + this.videoId;
var url = new URL(baseUrl);
if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
if (this.hasPlaylist && this.withPlaylist) {
url.searchParams.append("list", this.playlistId);
url.searchParams.append("index", this.playlistIndex);
}
return url.href;
},
},
};
</script>

View file

@ -1,7 +1,7 @@
<template>
<label for="ddlSortBy" v-t="'actions.sort_by'" class="mr-2" />
<label v-t="'actions.sort_by'" for="ddlSortBy" class="mr-2" />
<select id="ddlSortBy" v-model="selectedSort" class="select w-auto m-0">
<option v-for="(value, key) in options" v-t="`actions.${key}`" :key="key" :value="value" />
<option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
</select>
</template>
@ -18,7 +18,10 @@ const options = {
const selectedSort = ref("descending");
const props = defineProps({
byKey: String,
byKey: {
type: String,
required: true,
},
});
const emit = defineEmits(["apply"]);

View file

@ -4,26 +4,77 @@
<button class="btn mr-2">
<router-link to="/import" v-t="'actions.import_from_json'" />
</button>
<button class="btn" @click="exportHandler" v-t="'actions.export_to_json'" />
<button v-t="'actions.export_to_json'" class="btn" @click="exportHandler" />
</div>
<i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
</div>
<hr />
<div class="w-full flex flex-wrap">
<button
v-for="group in channelGroups"
:key="group.groupName"
class="btn mx-1 w-max"
:class="{ selected: selectedGroup === group }"
@click="selectGroup(group)"
>
<span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
<div v-if="group.groupName != '' && selectedGroup == group">
<font-awesome-icon class="mx-2" icon="edit" @click="showEditGroupModal = true" />
<font-awesome-icon class="mx-2" icon="circle-minus" @click="deleteGroup(group)" />
</div>
</button>
<button class="btn mx-1">
<font-awesome-icon icon="circle-plus" @click="showCreateGroupModal = true" />
</button>
</div>
<br />
<hr />
<!-- Subscriptions card list -->
<div class="pp-subs-cards">
<!-- channel info card -->
<div efy_card class="pp-subs-card" v-for="subscription in subscriptions" :key="subscription.url">
<div v-for="subscription in filteredSubscriptions" :key="subscription.url" efy_card class="pp-subs-card">
<router-link :to="subscription.url" class="pp-import-channel flex font-bold">
<img :src="subscription.avatar" width="48" height="48" />
<span class="mx-2" v-text="subscription.name" />
</router-link>
<!-- (un)subscribe btn -->
<button
@click="handleButton(subscription)"
v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
class="btn mt-2 w-full"
@click="handleButton(subscription)"
/>
</div>
</div>
<br />
<ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
<h2 v-t="'actions.create_group'" />
<div class="flex flex-col">
<input v-model="newGroupName" class="input my-4" type="text" :placeholder="$t('actions.group_name')" />
<button v-t="'actions.create_group'" class="btn ml-auto w-max" @click="createGroup()" />
</div>
</ModalComponent>
<ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false">
<div class="mb-5 mt-3 flex justify-between">
<input v-model="editedGroupName" type="text" class="input" />
<button v-t="'actions.okay'" class="btn" :placeholder="$t('actions.group_name')" @click="editGroupName()" />
</div>
<div class="mb-2 mt-3 flex flex-col overflow-y-scroll" style="height: 300rem">
<div v-for="subscription in subscriptions" :key="subscription.name">
<div class="mr-3 flex justify-between">
<span>{{ subscription.name }}</span>
<input
type="checkbox"
class="checkbox"
:checked="selectedGroup.channels.includes(subscription.url.substr(-11))"
@change="checkedChange(subscription)"
/>
</div>
<hr />
</div>
</div>
</ModalComponent>
</template>
<style>
@ -39,20 +90,58 @@
margin-bottom: 0;
width: 100%;
}
.selected {
border: 0.1rem outset red;
}
</style>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: { ModalComponent },
data() {
return {
subscriptions: [],
selectedGroup: {
groupName: "",
channels: [],
},
channelGroups: [],
showCreateGroupModal: false,
showEditGroupModal: false,
newGroupName: "",
editedGroupName: "",
};
},
computed: {
filteredSubscriptions(_this) {
return _this.selectedGroup.groupName == ""
? _this.subscriptions
: _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
},
},
mounted() {
this.fetchSubscriptions().then(json => {
this.subscriptions = json;
this.subscriptions.forEach(subscription => (subscription.subscribed = true));
});
this.channelGroups.push(this.selectedGroup);
if (!window.db) return;
const cursor = this.getChannelGroupsCursor();
cursor.onsuccess = e => {
const cursor = e.target.result;
if (cursor) {
const group = cursor.value;
this.channelGroups.push({
groupName: group.groupName,
channels: JSON.parse(group.channels),
});
cursor.continue();
}
};
},
activated() {
document.title = "Subscriptions - Piped";
@ -91,7 +180,6 @@ export default {
},
exportHandler() {
const subscriptions = [];
this.subscriptions.forEach(subscription => {
subscriptions.push({
url: "https://www.youtube.com" + subscription.url,
@ -99,15 +187,58 @@ export default {
service_id: 0,
});
});
const json = JSON.stringify({
app_version: "",
app_version_int: 0,
subscriptions: subscriptions,
});
this.download(json, "subscriptions.json", "application/json");
},
selectGroup(group) {
this.selectedGroup = group;
this.editedGroupName = group.groupName;
},
createGroup() {
if (!this.newGroupName || this.channelGroups.some(group => group.groupName == this.newGroupName)) return;
const newGroup = {
groupName: this.newGroupName,
channels: [],
};
this.channelGroups.push(newGroup);
this.createOrUpdateChannelGroup(newGroup);
this.newGroupName = "";
this.showCreateGroupModal = false;
},
editGroupName() {
const oldGroupName = this.selectedGroup.groupName;
const newGroupName = this.editedGroupName;
// the group mustn't yet exist and the name can't be empty
if (!newGroupName || newGroupName == oldGroupName) return;
if (this.channelGroups.some(group => group.groupName == newGroupName)) return;
// create a new group with the same info and delete the old one
this.selectedGroup.groupName = newGroupName;
this.createOrUpdateChannelGroup(this.selectedGroup);
this.deleteChannelGroup(oldGroupName);
this.showEditGroupModal = false;
},
deleteGroup(group) {
this.deleteChannelGroup(group.groupName);
this.channelGroups = this.channelGroups.filter(g => g != group);
this.selectedGroup = this.channelGroups[0];
},
checkedChange(subscription) {
const channelId = subscription.url.substr(-11);
this.selectedGroup.channels = this.selectedGroup.channels.includes(channelId)
? this.selectedGroup.channels.filter(channel => channel != channelId)
: this.selectedGroup.channels.concat(channelId);
this.createOrUpdateChannelGroup(this.selectedGroup);
},
},
};
</script>

View file

@ -0,0 +1,29 @@
<template>
<div class="toast">
<slot />
<button v-t="'actions.dismiss'" @click="dismiss" />
</div>
</template>
<script>
export default {
emits: ["dismissed"],
methods: {
dismiss() {
this.$emit("dismissed");
},
},
};
</script>
<style>
.toast {
@apply bg-white/80 text-black flex flex-col justify-center fixed top-12 right-12 p-4 min-w-max shadow rounded duration-200 z-9999;
}
.dark .toast {
@apply bg-dark-900/80 text-white;
}
.toast button {
@apply underline;
}
</style>

View file

@ -1,15 +1,17 @@
<template>
<div class="video-grid">
<LoadingIndicatorPage :show-content="videos.length != 0" class="video-grid">
<VideoItem v-for="video in videos" :key="video.url" :item="video" height="118" width="210" />
</div>
</LoadingIndicatorPage>
</template>
<script>
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
import VideoItem from "./VideoItem.vue";
export default {
components: {
VideoItem,
LoadingIndicatorPage,
},
data() {
return {
@ -23,21 +25,15 @@ export default {
this.fetchTrending(region).then(videos => {
this.videos = videos;
this.updateWatched(this.videos);
this.fetchDeArrowContent(this.videos);
});
},
activated() {
document.title = this.$t("titles.trending") + " - Piped";
if (this.videos.length > 0) this.updateWatched(this.videos);
if (this.$route.path == "/") {
switch (this.getPreferenceString("homepage", "trending")) {
case "trending":
break;
case "feed":
this.$router.push("/feed");
return;
default:
break;
}
let homepage = this.getHomePage(this);
if (homepage !== undefined) this.$router.push(homepage);
}
},
methods: {

View file

@ -1,6 +1,7 @@
<template>
<div v-if="showVideo" class="efy_trans_filter">
<div v-if="showVideo" class="efy_trans_filter efy_shadow_trans">
<router-link
class="video_item_link inline-block w-full focus:underline hover:underline"
:to="{
path: '/watch',
query: {
@ -9,18 +10,8 @@
...(index >= 0 && { index: index + 1 }),
},
}"
class="video_item_link"
>
<img
class="w-full"
:src="item.thumbnail"
:alt="item.title"
:class="{ 'shorts-img': item.isShort }"
loading="lazy"
/>
<p class="pp-video-card-title my-2 overflow-hidden flex link" :title="item.title" v-text="item.title" />
</router-link>
<!-- EFY
<div class="pp-video-card-buttons">
<button v-if="item.duration > 0" v-text="timeFormat(item.duration)" tabindex="-1" />
<button v-if="item.views >= 0" tabindex="-1">
@ -77,9 +68,129 @@
<div class="pp-text" title="item.uploaderName">
<span v-text="item.uploaderName" />
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />-->
<div class="w-full">
<img
class="w-full"
:src="thumbnail"
:alt="title"
:class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
loading="lazy"
/>
<!-- progress bar -->
<div class="relative h-1 w-full">
<div
v-if="item.watched && item.duration > 0"
class="absolute bottom-0 left-0 h-1 bg-red-600"
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
/>
</div>
</div>
<div class="relative text-sm">
<span
v-if="item.duration > 0"
class="thumbnail-overlay thumbnail-right"
v-text="timeFormat(item.duration)"
/>
<!-- shorts thumbnail -->
<span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
<span
v-else-if="item.duration >= 0"
class="thumbnail-overlay thumbnail-right"
v-text="timeFormat(item.duration)"
/>
<i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
<font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
</i18n-t>
<span v-if="item.watched" v-t="'video.watched'" class="thumbnail-overlay bottom-5px left-5px" />
</div>
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
class="pp-video-card-title my-2 overflow-hidden flex link"
:title="title"
v-text="title"
/>
</router-link>
<div class="flex">
<router-link :to="item.uploaderUrl">
<img
v-if="item.uploaderAvatar"
:src="item.uploaderAvatar"
loading="lazy"
class="mr-0.5 mt-0.5 h-32px w-32px rounded-full"
width="68"
height="68"
/>
</router-link>
<div class="flex-1 px-2">
<router-link
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
class="link-secondary block overflow-hidden text-sm"
:to="item.uploaderUrl"
:title="item.uploaderName"
>
<span v-text="item.uploaderName" />
<font-awesome-icon v-if="item.uploaderVerified" class="ml-1.5" icon="check" />
</router-link>
<div v-if="item.views >= 0 || item.uploadedDate" class="mt-1 text-xs font-normal text-gray-300">
<span v-if="item.views >= 0">
<font-awesome-icon icon="eye" />
<span class="pl-1" v-text="`${numberFormat(item.views)} •`" />
</span>
<span v-if="item.uploaded > 0" class="pl-0.5" v-text="timeAgo(item.uploaded)" />
<span v-else-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" />
</div>
</div>
<div class="flex items-center gap-2.5">
<router-link
:to="{
path: '/watch',
query: {
v: item.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
listen: '1',
},
}"
:aria-label="'Listen to ' + title"
:title="'Listen to ' + title"
>
<font-awesome-icon icon="headphones" />
</router-link>
<button :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
<font-awesome-icon icon="circle-plus" />
</button>
<button
v-if="admin"
ref="removeButton"
:title="$t('actions.remove_from_playlist')"
@click="showConfirmRemove = true"
>
<font-awesome-icon icon="circle-minus" />
</button>
<ConfirmModal
v-if="showConfirmRemove"
:message="$t('actions.delete_playlist_video_confirm')"
@close="showConfirmRemove = false"
@confirm="removeVideo(item.url.substr(-11))"
/>
<PlaylistAddModal
v-if="showModal"
:video-id="item.url.substr(-11)"
:video-info="item"
@close="showModal = !showModal"
/>
<!--Master-->
</div>
<!-- </router-link> -->
</div>
</div>
<PlaylistAddModal v-if="showModal" :video-id="video.url.substr(-11)" @close="showModal = !showModal" />
</template>
@ -95,8 +206,10 @@
<script>
import PlaylistAddModal from "./PlaylistAddModal.vue";
import ConfirmModal from "./ConfirmModal.vue";
export default {
components: { PlaylistAddModal, ConfirmModal },
props: {
item: {
type: Object,
@ -115,34 +228,32 @@ export default {
playlistId: { type: String, default: null },
admin: { type: Boolean, default: false },
},
emits: ["remove"],
data() {
return {
showModal: false,
showVideo: true,
showConfirmRemove: false,
};
},
computed: {
title() {
return this.item.dearrow?.titles[0]?.title ?? this.item.title;
},
thumbnail() {
return this.item.dearrow?.thumbnails[0]?.thumbnail ?? this.item.thumbnail;
},
},
mounted() {
this.shouldShowVideo();
},
methods: {
removeVideo() {
if (confirm(this.$t("actions.delete_playlist_video_confirm"))) {
this.$refs.removeButton.disabled = true;
this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
method: "POST",
body: JSON.stringify({
playlistId: this.playlistId,
index: this.index,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
}).then(json => {
if (json.error) alert(json.error);
else this.$emit("remove");
});
}
this.$refs.removeButton.disabled = true;
this.removeVideoFromPlaylist(this.playlistId, this.index).then(json => {
if (json.error) alert(json.error);
else this.$emit("remove");
});
},
shouldShowVideo() {
if (!this.isFeed || !this.getPreferenceBoolean("hideWatched", false)) return;
@ -158,6 +269,5 @@ export default {
};
},
},
components: { PlaylistAddModal },
};
</script>

View file

@ -2,15 +2,35 @@
<div
ref="container"
data-shaka-player-container
class="w-full max-h-screen flex justify-center efy_trans_filter_off"
class="relative max-h-screen w-full flex justify-center efy_trans_filter_off"
:class="{ 'player-container': !isEmbed }"
>
<video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
<span
id="preview-container"
ref="previewContainer"
class="absolute bottom-0 z-[2000] mb-[3.5%] hidden flex-col items-center"
>
<canvas id="preview" ref="preview" class="rounded-sm" />
<span class="mt-2 w-min rounded-xl bg-dark-700 px-2 pb-1 pt-1.5 text-sm" v-text="timeFormat(currentTime)" />
</span>
<button
v-if="inSegment"
class="skip-segment-button"
type="button"
:aria-label="$t('actions.skip_segment')"
aria-pressed="false"
@click="onClickSkipSegment"
>
<span v-t="'actions.skip_segment'" />
<i class="material-icons-round">skip_next</i>
</button>
</div>
</template>
<script>
import("shaka-player/dist/controls.css");
import "shaka-player/dist/controls.css";
import { parseTimeParam } from "@/utils/Misc";
const shaka = import("shaka-player/dist/shaka-player.ui.js");
if (!window.muxjs) {
import("mux.js").then(muxjs => {
@ -27,14 +47,6 @@ export default {
return {};
},
},
playlist: {
type: Object,
default: null,
},
index: {
type: Number,
default: -1,
},
sponsors: {
type: Object,
default: () => {
@ -45,12 +57,16 @@ export default {
selectedAutoLoop: Boolean,
isEmbed: Boolean,
},
emits: ["timeupdate"],
emits: ["timeupdate", "ended", "navigateNext"],
data() {
return {
lastUpdate: new Date().getTime(),
initialSeekComplete: false,
destroying: false,
inSegment: false,
isHoveringTimebar: false,
currentTime: 0,
seekbarPadding: 2,
};
},
computed: {
@ -88,7 +104,7 @@ export default {
this.hotkeysPromise.then(() => {
var self = this;
this.$hotkeys(
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.",
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.,alt+p,return,.,,",
function (e, handler) {
const videoEl = self.$refs.videoEl;
switch (handler.key) {
@ -175,7 +191,7 @@ export default {
e.preventDefault();
break;
case "shift+n":
self.navigateNext();
self.$emit("navigateNext");
e.preventDefault();
break;
case "shift+,":
@ -184,6 +200,22 @@ export default {
case "shift+.":
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
break;
case "alt+p":
document.pictureInPictureElement
? document.exitPictureInPicture()
: videoEl.requestPictureInPicture();
break;
case "return":
self.skipSegment(videoEl);
break;
case ".":
videoEl.currentTime += 0.04;
e.preventDefault();
break;
case ",":
videoEl.currentTime -= 0.04;
e.preventDefault();
break;
}
},
);
@ -199,6 +231,8 @@ export default {
},
methods: {
async loadVideo() {
this.updateSponsors();
const component = this;
const videoEl = this.$refs.videoEl;
@ -207,26 +241,9 @@ export default {
const time = this.$route.query.t ?? this.$route.query.start;
if (time) {
let start = 0;
if (/^[\d]*$/g.test(time)) {
start = time;
} else {
const hours = /([\d]*)h/gi.exec(time)?.[1];
const minutes = /([\d]*)m/gi.exec(time)?.[1];
const seconds = /([\d]*)s/gi.exec(time)?.[1];
if (hours) {
start += parseInt(hours) * 60 * 60;
}
if (minutes) {
start += parseInt(minutes) * 60;
}
if (seconds) {
start += parseInt(seconds);
}
}
videoEl.currentTime = start;
videoEl.currentTime = parseTimeParam(time);
this.initialSeekComplete = true;
} else if (window.db) {
} else if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
var tx = window.db.transaction("watch_history", "readonly");
var store = tx.objectStore("watch_history");
var request = store.get(this.video.id);
@ -256,9 +273,7 @@ export default {
const MseSupport = window.MediaSource !== undefined;
const lbry = this.getPreferenceBoolean("disableLBRY", false)
? null
: this.video.videoStreams.filter(stream => stream.quality === "LBRY")[0];
const lbry = null;
var uri;
var mime;
@ -268,9 +283,10 @@ export default {
mime = "application/x-mpegURL";
} else if (this.video.audioStreams.length > 0 && !lbry && MseSupport) {
if (!this.video.dash) {
const dash = (
await import("@/utils/DashUtils.js").then(mod => mod.default)
).generate_dash_file_from_formats(streams, this.video.duration);
const dash = (await import("../utils/DashUtils.js")).generate_dash_file_from_formats(
streams,
this.video.duration,
);
uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
} else {
@ -306,7 +322,7 @@ export default {
uri = this.video.hls;
mime = "application/x-mpegURL";
} else {
uri = this.video.videoStreams.filter(stream => stream.codec == null).slice(-1)[0].url;
uri = this.video.videoStreams.findLast(stream => stream.codec == null).url;
mime = "video/mp4";
}
@ -356,22 +372,19 @@ export default {
else this.setPlayerAttrs(this.$player, videoEl, uri, mime, this.$shaka);
if (noPrevPlayer) {
videoEl.addEventListener("loadeddata", () => {
if (document.pictureInPictureElement) videoEl.requestPictureInPicture();
});
videoEl.addEventListener("timeupdate", () => {
const time = videoEl.currentTime;
this.$emit("timeupdate", time);
this.updateProgressDatabase(time);
if (this.sponsors && this.sponsors.segments) {
this.sponsors.segments.map(segment => {
if (!segment.skipped || this.selectedAutoLoop) {
const end = segment.segment[1];
if (time >= segment.segment[0] && time < end) {
console.log("Skipped segment at " + time);
videoEl.currentTime = end;
segment.skipped = true;
return;
}
}
});
const segment = this.findCurrentSegment(time);
this.inSegment = !!segment;
if (segment?.autoskip && (!segment.skipped || this.selectedAutoLoop)) {
this.skipSegment(videoEl, segment);
}
}
});
@ -386,18 +399,27 @@ export default {
});
videoEl.addEventListener("ended", () => {
if (
!this.selectedAutoLoop &&
this.selectedAutoPlay &&
(this.playlist?.relatedStreams?.length > 0 || this.video.relatedStreams.length > 0)
) {
this.navigateNext();
}
this.$emit("ended");
});
}
//TODO: Add sponsors on seekbar: https://github.com/ajayyy/SponsorBlock/blob/e39de9fd852adb9196e0358ed827ad38d9933e29/src/js-components/previewBar.ts#L12
},
findCurrentSegment(time) {
return this.sponsors?.segments?.find(s => time >= s.segment[0] && time < s.segment[1]);
},
onClickSkipSegment() {
const videoEl = this.$refs.videoEl;
this.skipSegment(videoEl);
},
skipSegment(videoEl, segment) {
const time = videoEl.currentTime;
if (!segment) segment = this.findCurrentSegment(time);
if (!segment) return;
console.log("Skipped segment at " + time);
videoEl.currentTime = segment.segment[1];
segment.skipped = true;
},
setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) {
const url = "/watch?v=" + this.video.id;
@ -471,8 +493,13 @@ export default {
this.updateMarkers();
const event = new Event("playerInit");
window.dispatchEvent(event);
const player = this.$ui.getControls().getPlayer();
this.setupSeekbarPreview();
this.$player = player;
const disableVideo = this.getPreferenceBoolean("listen", false) && !this.video.livestream;
@ -543,7 +570,7 @@ export default {
player.addTextTrackAsync(
subtitle.url,
subtitle.code,
"SUBTITLE",
"subtitles",
subtitle.mimeType,
null,
subtitle.name,
@ -553,7 +580,14 @@ export default {
const rate = this.getPreferenceNumber("rate", 1);
videoEl.playbackRate = rate;
videoEl.defaultPlaybackRate = rate;
const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
this.$player.setTextTrackVisibility(autoDisplayCaptions);
});
// expand the player to fullscreen when the fullscreen query equals true
if (this.$route.query.fullscreen === "true" && !this.$ui.getControls().isFullScreenEnabled())
this.$ui.getControls().toggleFullScreen();
},
async updateProgressDatabase(time) {
// debounce
@ -578,29 +612,7 @@ export default {
this.$refs.videoEl.currentTime = time;
}
},
navigateNext() {
const params = this.$route.query;
let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
const searchParams = new URLSearchParams();
for (var param in params)
switch (param) {
case "v":
case "t":
break;
case "index":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("index", this.index + 1);
break;
case "list":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
break;
default:
searchParams.set(param, params[param]);
break;
}
const paramStr = searchParams.toString();
if (paramStr.length > 0) url += "&" + paramStr;
this.$router.push(url);
},
updateMarkers() {
const markers = this.$refs.container.querySelector(".shaka-ad-markers");
const array = ["to right"];
@ -608,38 +620,19 @@ export default {
const start = (segment.segment[0] / this.video.duration) * 100;
const end = (segment.segment[1] / this.video.duration) * 100;
var color;
switch (segment.category) {
case "sponsor":
color = "#00d400";
break;
case "selfpromo":
color = "#ffff00";
break;
case "interaction":
color = "#cc00ff";
break;
case "poi_highlight":
color = "#ff1684";
break;
case "intro":
color = "#00ffff";
break;
case "outro":
color = "#0202ed";
break;
case "preview":
color = "#008fd6";
break;
case "filler":
color = "#7300FF";
break;
case "music_offtopic":
color = "#ff9900";
break;
default:
color = "white";
}
var color = [
"sponsor",
"selfpromo",
"interaction",
"poi_highlight",
"intro",
"outro",
"preview",
"filler",
"music_offtopic",
].includes(segment.category)
? `var(--spon-seg-${segment.category})`
: "var(--spon-seg-default)";
array.push(`transparent ${start}%`);
array.push(`${color} ${start}%`);
@ -653,33 +646,133 @@ export default {
if (markers) markers.style.background = `linear-gradient(${array.join(",")})`;
},
destroy(hotkeys) {
if (this.$ui) {
this.$ui.destroy();
this.$ui = undefined;
this.$player = undefined;
}
if (this.$player) {
this.$player.destroy();
this.$player = undefined;
}
if (hotkeys) this.$hotkeys?.unbind();
this.$refs.container?.querySelectorAll("div").forEach(node => node.remove());
},
},
watch: {
sponsors() {
updateSponsors() {
if (this.getPreferenceBoolean("showMarkers", true)) {
this.shakaPromise.then(() => {
this.updateMarkers();
});
}
},
setupSeekbarPreview() {
if (!this.video.previewFrames) return;
let seekBar = document.querySelector(".shaka-seek-bar");
// load the thumbnail preview when the user moves over the seekbar
seekBar.addEventListener("mousemove", e => {
this.isHoveringTimebar = true;
const position = (e.offsetX / e.target.offsetWidth) * this.video.duration;
this.showSeekbarPreview(position * 1000);
});
// hide the preview when the user stops hovering the seekbar
seekBar.addEventListener("mouseout", () => {
this.isHoveringTimebar = false;
this.$refs.previewContainer.style.display = "none";
});
},
async showSeekbarPreview(position) {
const frame = this.getFrame(position);
const originalImage = await this.loadImage(frame.url);
if (!this.isHoveringTimebar) return;
const seekBar = document.querySelector(".shaka-seek-bar");
const container = this.$refs.previewContainer;
const canvas = this.$refs.preview;
const ctx = canvas.getContext("2d");
const offsetX = frame.positionX * frame.frameWidth;
const offsetY = frame.positionY * frame.frameHeight;
canvas.width = frame.frameWidth > 100 ? frame.frameWidth : frame.frameWidth * 2;
canvas.height = frame.frameWidth > 100 ? frame.frameHeight : frame.frameHeight * 2;
// draw the thumbnail preview into the canvas by cropping only the relevant part
ctx.drawImage(
originalImage,
offsetX,
offsetY,
frame.frameWidth,
frame.frameHeight,
0,
0,
canvas.width,
canvas.height,
);
// calculate the thumbnail preview offset and display it
const centerOffset = position / this.video.duration / 10;
const left = centerOffset - ((0.5 * canvas.width) / seekBar.clientWidth) * 100;
const maxLeft =
((seekBar.clientWidth - canvas.clientWidth) / seekBar.clientWidth) * 100 - this.seekbarPadding;
this.currentTime = position / 1000;
container.style.left = `max(${this.seekbarPadding}%, min(${left}%, ${maxLeft}%))`;
container.style.display = "flex";
},
// ineffective algorithm to find the thumbnail corresponding to the currently hovered position in the video
getFrame(position) {
let startPosition = 0;
const framePage = this.video.previewFrames.at(-1);
for (let i = 0; i < framePage.urls.length; i++) {
for (let positionY = 0; positionY < framePage.framesPerPageY; positionY++) {
for (let positionX = 0; positionX < framePage.framesPerPageX; positionX++) {
const endPosition = startPosition + framePage.durationPerFrame;
if (position >= startPosition && position <= endPosition) {
return {
url: framePage.urls[i],
positionX: positionX,
positionY: positionY,
frameWidth: framePage.frameWidth,
frameHeight: framePage.frameHeight,
};
}
startPosition = endPosition;
}
}
}
return null;
},
// creates a new image from an URL
loadImage(url) {
return new Promise(r => {
const i = new Image();
i.onload = () => r(i);
i.src = url;
});
},
destroy(hotkeys) {
if (this.$ui && !document.pictureInPictureElement) {
this.$ui.destroy();
this.$ui = undefined;
this.$player = undefined;
}
if (this.$player) {
this.$player.destroy();
if (!document.pictureInPictureElement) this.$player = undefined;
}
if (hotkeys) this.$hotkeys?.unbind();
this.$refs.container?.querySelectorAll("div").forEach(node => node.remove());
},
},
};
</script>
<style>
:root {
--player-base: rgba(255, 255, 255, 0.3);
--player-buffered: rgba(255, 255, 255, 0.54);
--player-played: rgba(255, 0, 0);
--spon-seg-sponsor: #00d400;
--spon-seg-selfpromo: #ffff00;
--spon-seg-interaction: #cc00ff;
--spon-seg-poi_highlight: #ff1684;
--spon-seg-intro: #00ffff;
--spon-seg-outro: #0202ed;
--spon-seg-preview: #008fd6;
--spon-seg-filler: #7300ff;
--spon-seg-music_offtopic: #ff9900;
--spon-seg-default: white;
}
.player-container {
@apply max-h-75vh min-h-64;
background: #000;
@ -732,11 +825,15 @@ html .shaka-range-element:focus {
.material-icons-round {
font-family: "Material Icons Round" !important;
}
.material-icons-round,
.shaka-bottom-controls .material-icons-round,
.shaka-current-time {
-webkit-text-fill-color: #fff !important;
filter: none !important;
opacity: 1 !important;
box-shadow: none !important;
}
.shaka-bottom-controls .material-icons-round {
box-shadow: none !important;
}
.shaka-overflow-menu,
.shaka-settings-menu {
@ -767,4 +864,17 @@ html .shaka-range-element:focus {
.shaka-controls-container {
border-radius: var(--efy_radius) !important;
}
.skip-segment-button {
z-index: 1000;
position: absolute;
transform: translate(0, -50%);
top: 50%;
right: 0;
border-right: 0;
border-radius: var(--efy_radius) 0 0 var(--efy_radius);
display: flex;
align-items: center;
justify-content: center;
}
</style>

View file

@ -0,0 +1,29 @@
<script>
export default {
props: {
link: {
type: String,
required: true,
},
platform: {
type: String,
required: false,
default: "YouTube",
},
},
};
</script>
<template>
<template v-if="getPreferenceBoolean('showWatchOnYouTube', false)">
<a
:href="link"
role="button"
class="pp-square flex items-center justify-center"
:aria-label="'Watch on Odysee'"
:title="`${$t('player.watch_on')}${platform}`"
>
<font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
</a>
</template>
</template>

View file

@ -1,24 +0,0 @@
<script>
export default {
props: {
link: String,
},
};
</script>
<template>
<template v-if="this.getPreferenceBoolean('showWatchOnYouTube', false)">
<a :href="link" class="pp-watch-onyt-btn btn" role="button" :title="$t('player.watch_on') + 'YouTube'">
<font-awesome-icon class="mx-1.5" :icon="['fab', 'youtube']" />
</a>
</template>
</template>
<style>
.pp-watch-onyt-btn {
display: flex;
margin-left: var(--efy_gap0);
align-items: center;
gap: 5rem;
}
</style>

View file

@ -1,43 +1,48 @@
<template>
<div v-if="video && isEmbed" class="absolute top-0 left-0 h-full w-full bg-black z-50">
<div v-if="video && isEmbed" class="absolute left-0 top-0 z-50 h-full w-full bg-black">
<VideoPlayer
ref="videoPlayer"
:video="video"
:sponsors="sponsors"
:playlist="playlist"
:index="index"
:selected-auto-play="false"
:selected-auto-loop="selectedAutoLoop"
:is-embed="isEmbed"
/>
</div>
<div v-if="video && !isEmbed" class="w-full">
<LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full">
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
<Transition>
<ToastComponent v-if="shouldShowToast" @dismissed="dismiss">
<i18n-t keypath="info.next_video_countdown">{{ counter }}</i18n-t>
</ToastComponent>
</Transition>
<div v-show="!video.error">
<div :class="isMobile ? 'flex-col' : 'flex'">
<VideoPlayer
ref="videoPlayer"
:video="video"
:sponsors="sponsors"
:playlist="playlist"
:index="index"
:selected-auto-play="selectedAutoPlay"
:selected-auto-loop="selectedAutoLoop"
@timeupdate="onTimeUpdate"
/>
<keep-alive>
<VideoPlayer
ref="videoPlayer"
:video="video"
:sponsors="sponsors"
:selected-auto-play="selectedAutoPlay"
:selected-auto-loop="selectedAutoLoop"
@timeupdate="onTimeUpdate"
@ended="onVideoEnded"
@navigate-next="navigateNext"
/>
</keep-alive>
<ChaptersBar
:mobileLayout="isMobile"
v-if="video?.chapters?.length > 0 && showChapters"
:mobile-layout="isMobile"
:chapters="video.chapters"
:player-position="currentTime"
@seek="navigate"
/>
</div>
<!-- video title -->
<div class="pp-video-title font-bold mt-2 text-2xl break-words" v-text="video.title" />
<div class="pp-bellow-video flex flex-wrap mt-3 mb-3">
<div class="pp-video-title mt-2 break-words text-2xl font-bold" v-text="video.title" />
<div class="pp-bellow-video mb-3 mt-3 flex flex-wrap">
<!-- views / date -->
<div class="flex flex-auto">
<span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
@ -48,11 +53,11 @@
<div class="pp-likes flex children:mr-2">
<template v-if="video.likes >= 0">
<div class="flex items-center">
<div class="i-fa-solid:thumbs-up" />
<div class="i-fa6-solid:thumbs-up" />
<strong class="ml-1" v-text="addCommas(video.likes)" />
</div>
<div class="flex items-center">
<div class="i-fa-solid:thumbs-down" />
<div class="i-fa6-solid:thumbs-down" />
<strong class="ml-1" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
</div>
</template>
@ -72,54 +77,24 @@
video.uploader
}}</router-link>
<!-- Verified Badge -->
<font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
<font-awesome-icon v-if="video.uploaderVerified" class="ml-1" icon="check" />
</div>
<div class="pp-watch-buttons">
<!-- Subscribe button -->
<button
class="btn"
@click="subscribeHandler"
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(video.uploaderSubscriberCount) },
}"
class="btn"
@click="subscribeHandler"
/>
<!-- RSS Feed button -->
<a
aria-label="RSS feed"
title="RSS feed"
role="button"
v-if="video.uploaderUrl"
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
target="_blank"
class="btn flex-col"
>
<font-awesome-icon icon="rss" />
</a>
<WatchOnYouTubeButton :link="`https://youtu.be/${getVideoId()}`" />
<!-- Share Dialog -->
<button class="btn" @click="showShareModal = !showShareModal">
<i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
<font-awesome-icon class="mx-1.5 ml-1" icon="fa-share" />
</button>
<!-- LBRY -->
<a v-if="video.lbryId" :href="'https://odysee.com/' + video.lbryId" class="btn">
<i18n-t keypath="player.watch_on" tag="strong">LBRY</i18n-t>
</a>
<!-- listen / watch toggle -->
<router-link
:to="toggleListenUrl"
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
class="btn flex-col"
>
<font-awesome-icon :icon="isListening ? 'tv' : 'headphones'" />
</router-link>
<!-- Playlist Add button -->
<button class="btn" v-if="authenticated" @click="showModal = !showModal">
<button v-if="authenticated" class="btn" @click="showModal = !showModal">
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
</button>
<PlaylistAddModal v-if="showModal" :video-id="getVideoId()" @close="showModal = !showModal" />
<!-- Share Dialog -->
<ShareModal
v-if="showShareModal"
:video-id="getVideoId()"
@ -128,59 +103,116 @@
:playlist-index="index"
@close="showShareModal = !showShareModal"
/>
<button class="btn flex items-center" @click="showShareModal = !showShareModal">
<font-awesome-icon class="mx-1.5 mr-1" icon="fa-share" />
<i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
</button>
<!-- YouTube -->
<WatchOnButton :link="`https://youtu.be/${getVideoId()}`" />
<!-- Odysee -->
<WatchOnButton v-if="video.lbryId" platform="Odysee" :link="`https://odysee.com/${video.lbryId}`" />
<!-- listen / watch toggle -->
<router-link
:to="toggleListenUrl"
role="button"
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
class="pp-square btn flex items-center"
>
<font-awesome-icon class="mx-1.5" :icon="isListening ? 'tv' : 'headphones'" />
</router-link>
<!-- RSS Feed button -->
<a
v-if="video.uploaderUrl"
aria-label="RSS feed"
title="RSS feed"
role="button"
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
target="_blank"
class="pp-square btn flex items-center"
>
<font-awesome-icon class="mx-1.5" icon="rss" />
</a>
</div>
</div>
<hr />
<hr class="mb-2" />
<div
v-for="metaInfo in video?.metaInfo ?? []"
:key="metaInfo.title"
class="btn my-3 flex flex-wrap cursor-default gap-2 px-4 py-2"
>
<span>{{ metaInfo.description ?? metaInfo.title }}</span>
<a v-for="(link, linkIndex) in metaInfo.urls" :key="linkIndex" :href="link" class="underline">{{
metaInfo.urlTexts[linkIndex]
}}</a>
<br />
</div>
<div efy_select>
<input id="showDesc" type="checkbox" v-model="showDesc" />
<label for="showDesc" v-t="'actions.show_description'" />
<input id="showComments" type="checkbox" v-model="showComments" @click="toggleComments" />
<label for="showComments" v-t="'actions.show_comments'" />
<input id="showRecs" type="checkbox" v-model="showRecs" />
<label for="showRecs" v-t="'actions.show_recommendations'" />
<input id="showDesc" v-model="showDesc" type="checkbox" />
<label v-t="'actions.show_description'" for="showDesc" />
<input id="showComments" v-model="showComments" type="checkbox" @click="toggleComments" />
<label
v-text="`${$t('actions.show_comments')} - ${numberFormat(comments?.commentCount)}`"
for="showComments"
/>
<input id="showRecs" v-model="showRecs" type="checkbox" />
<label v-t="'actions.show_recommendations'" for="showRecs" />
<span v-show="video?.chapters?.length > 0">
<input id="showChapters" v-model="showChapters" type="checkbox" />
<label v-t="'actions.show_chapters'" class="ml-2" for="showChapters" />
</span>
<input id="chkAutoLoop" v-model="selectedAutoLoop" type="checkbox" @change="onChange($event)" />
<label for="chkAutoLoop" v-text="`${$t('actions.loop_this_video')}`" />
<input id="chkAutoPlay" v-model="selectedAutoPlay" type="checkbox" @change="onChange($event)" />
<label for="chkAutoPlay" v-text="`${$t('actions.auto_play_next_video')}`" />
<span v-show="video?.chapters?.length > 0">
<input id="showChapters" type="checkbox" v-model="showChapters" />
<label class="ml-2" for="showChapters" v-t="'actions.show_chapters'" />
</span>
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div
v-show="showDesc"
class="break-words mb-2"
v-html="purifyHTML(video.description)"
style="border-top: var(--efy_border); margin: 15rem 0; padding: 15rem 0"
/>
<div
v-if="showDesc && sponsors && sponsors.segments"
v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
/>
<template v-if="showDesc">
<hr />
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="description break-words" v-html="purifiedDescription" />
<hr />
<div
v-if="sponsors && sponsors.segments"
v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
/>
<div v-if="video.category" v-text="`${$t('video.category')}: ${video.category}`" />
<div v-text="`${$t('video.license')}: ${video.license}`" />
<div class="capitalize" v-text="`${$t('video.visibility')}: ${video.visibility}`" />
<hr />
<div v-if="video.tags" class="video-tags">
<router-link
v-for="tag in video.tags"
:key="tag"
class="line-clamp-1 efy_trans_filter efy_shadow_trans"
:to="`/results?search_query=${encodeURIComponent(tag)}`"
>{{ tag }}</router-link
>
</div>
</template>
</div>
<hr />
<div class="grid pp-rec-vids">
<div class="pp-rec-vids grid">
<div v-if="!showComments" class="w-full"></div>
<div v-if="!comments" class="">
<p class="text-center mt-8" v-t="'comment.loading'"></p>
<div v-else-if="!comments">
<p v-t="'comment.loading'" class="mt-8 text-center"></p>
</div>
<div v-else-if="comments.disabled" class="">
<p class="text-center mt-8" v-t="'comment.disabled'"></p>
<div v-else-if="comments.disabled">
<p v-t="'comment.disabled'" class="mt-8 text-center"></p>
</div>
<div v-else ref="comments" v-show="showComments" class="pp-comments">
<div v-else ref="comments" class="pp-comments">
<CommentItem
v-for="comment in comments.comments"
:key="comment.commentId"
:comment="comment"
:uploader="video.uploader"
:video-id="getVideoId()"
class="efy_trans_filter"
class="efy_trans_filter efy_shadow_trans"
/>
</div>
@ -203,7 +235,7 @@
</div>
</div>
</div>
</div>
</LoadingIndicatorPage>
</template>
<script>
@ -215,7 +247,11 @@ import ChaptersBar from "./ChaptersBar.vue";
import PlaylistAddModal from "./PlaylistAddModal.vue";
import ShareModal from "./ShareModal.vue";
import PlaylistVideos from "./PlaylistVideos.vue";
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
import WatchOnButton from "./WatchOnButton.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
import ToastComponent from "./ToastComponent.vue";
import { parseTimeParam } from "@/utils/Misc";
import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
export default {
name: "App",
@ -228,14 +264,14 @@ export default {
PlaylistAddModal,
ShareModal,
PlaylistVideos,
WatchOnYouTubeButton,
WatchOnButton,
LoadingIndicatorPage,
ToastComponent,
},
data() {
const smallViewQuery = window.matchMedia("(max-width: 640px)");
return {
video: {
title: "Loading...",
},
video: null,
playlistId: null,
playlist: null,
index: null,
@ -256,6 +292,9 @@ export default {
showShareModal: false,
isMobile: true,
currentTime: 0,
shouldShowToast: false,
timeoutCounter: null,
counter: 0,
};
},
computed: {
@ -277,6 +316,12 @@ export default {
year: "numeric",
});
},
defaultCounter(_this) {
return _this.getPreferenceNumber("autoPlayNextCountdown", 5);
},
purifiedDescription() {
return purifyHTML(this.video.description);
},
},
mounted() {
// check screen size
@ -295,7 +340,7 @@ export default {
(async () => {
const videoId = this.getVideoId();
const instance = this;
if (window.db && !this.video.error) {
if (window.db && this.getPreferenceBoolean("watchHistory", false) && !this.video.error) {
var tx = window.db.transaction("watch_history", "readwrite");
var store = tx.objectStore("watch_history");
var request = store.get(videoId);
@ -325,6 +370,8 @@ export default {
this.getPlaylistData();
this.getSponsors();
if (!this.isEmbed && this.showComments) this.getComments();
if (this.isEmbed) document.querySelector("html").style.overflow = "hidden";
window.addEventListener("click", this.handleClick);
window.addEventListener("resize", () => {
this.smallView = this.smallViewQuery.matches;
});
@ -336,7 +383,7 @@ export default {
this.showDesc = !this.getPreferenceBoolean("minimizeDescription", false);
this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false);
this.showChapters = !this.getPreferenceBoolean("minimizeChapters", false);
if (this.video.duration) {
if (this.video?.duration) {
document.title = this.video.title + " - Piped";
this.$refs.videoPlayer.loadVideo();
}
@ -345,24 +392,45 @@ export default {
deactivated() {
this.active = false;
window.removeEventListener("scroll", this.handleScroll);
this.dismiss();
},
unmounted() {
window.removeEventListener("scroll", this.handleScroll);
window.removeEventListener("click", this.handleClick);
this.dismiss();
},
methods: {
fetchVideo() {
return this.fetchJson(this.apiUrl() + "/streams/" + this.getVideoId());
},
async fetchSponsors() {
return await this.fetchJson(this.apiUrl() + "/sponsors/" + this.getVideoId(), {
category:
'["' +
this.getPreferenceString("selectedSkip", "sponsor,interaction,selfpromo,music_offtopic").replaceAll(
",",
'","',
) +
'"]',
var selectedSkip = this.getPreferenceString(
"selectedSkip",
"sponsor,interaction,selfpromo,music_offtopic",
).split(",");
const skipOptions = this.getPreferenceJSON("skipOptions");
if (skipOptions !== undefined) {
selectedSkip = Object.keys(skipOptions).filter(
k => skipOptions[k] !== undefined && skipOptions[k] !== "no",
);
}
const sponsors = await this.fetchJson(this.apiUrl() + "/sponsors/" + this.getVideoId(), {
category: JSON.stringify(selectedSkip),
});
sponsors?.segments?.forEach(segment => {
const option = skipOptions[segment.category];
segment.autoskip = option === undefined || option === "auto";
});
const minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
sponsors.segments = sponsors.segments?.filter(segment => {
const length = segment.segment[1] - segment.segment[0];
return length >= minSegmentLength;
});
return sponsors;
},
toggleComments() {
this.showComments = !this.showComments;
@ -395,13 +463,10 @@ export default {
elem.outerHTML = elem.getAttribute("href");
});
xmlDoc.querySelectorAll("br").forEach(elem => (elem.outerHTML = "\n"));
this.video.description = this.urlify(xmlDoc.querySelector("body").innerHTML)
.replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
.replaceAll(
/(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm,
"/watch?v=$1",
)
.replaceAll("\n", "<br>");
this.video.description = rewriteDescription(xmlDoc.querySelector("body").innerHTML);
this.updateWatched(this.video.relatedStreams);
this.fetchDeArrowContent(this.video.relatedStreams);
}
});
},
@ -422,6 +487,9 @@ export default {
}
}
});
await this.fetchPlaylistPages().then(() => {
this.fetchDeArrowContent(this.playlist.relatedStreams);
});
}
},
async fetchPlaylistPages() {
@ -500,6 +568,35 @@ export default {
}
this.subscribed = !this.subscribed;
},
handleClick(event) {
if (!event || !event.target) return;
if (!event.target.matches("a[href]")) return;
const target = event.target;
if (!target.getAttribute("href")) return;
if (this.handleTimestampLinks(target)) {
event.preventDefault();
}
},
handleTimestampLinks(target) {
try {
const url = new URL(target.getAttribute("href"), document.baseURI);
if (
url.searchParams.size > 2 ||
url.searchParams.get("v") !== this.getVideoId() ||
!url.searchParams.has("t")
) {
return false;
}
const time = parseTimeParam(url.searchParams.get("t"));
if (time) {
this.navigate(time);
}
return true;
} catch (e) {
console.error(e);
}
return false;
},
handleScroll() {
if (this.loading || !this.comments || !this.comments.nextpage) return;
if (window.innerHeight + window.scrollY >= this.$refs.comments?.offsetHeight - window.innerHeight) {
@ -523,6 +620,73 @@ export default {
onTimeUpdate(time) {
this.currentTime = time;
},
onVideoEnded() {
if (
!this.selectedAutoLoop &&
this.selectedAutoPlay &&
(this.playlist?.relatedStreams?.length > 0 || this.video.relatedStreams.length > 0)
) {
this.showToast();
}
},
showToast() {
this.counter = this.defaultCounter;
if (this.counter < 1) {
this.navigateNext();
return;
}
if (this.timeoutCounter) clearInterval(this.timeoutCounter);
this.timeoutCounter = setInterval(() => {
this.counter--;
if (this.counter === 0) {
this.dismiss();
this.navigateNext();
}
}, 1000);
this.shouldShowToast = true;
},
dismiss() {
clearInterval(this.timeoutCounter);
this.shouldShowToast = false;
},
navigateNext() {
const params = this.$route.query;
let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
const searchParams = new URLSearchParams();
for (var param in params)
switch (param) {
case "v":
case "t":
break;
case "index":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("index", this.index + 1);
break;
case "list":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
break;
default:
searchParams.set(param, params[param]);
break;
}
// save the fullscreen state
searchParams.set("fullscreen", this.$refs.videoPlayer.$ui.getControls().isFullScreenEnabled());
const paramStr = searchParams.toString();
if (paramStr.length > 0) url += "&" + paramStr;
this.$router.push(url);
},
},
};
</script>
<style>
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: translateX(100%) scale(0.5);
}
.description a {
text-decoration: underline;
filter: brightness(0.75);
}
</style>

1
src/locales/ang.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -4,15 +4,18 @@
"login": "تسجيل الدخول",
"register": "إنشاء حساب",
"preferences": "الإعدادات",
"history": "تاريخ التصفح",
"history": "سجل المشاهدة",
"subscriptions": "الاشتراكات",
"playlists": "قوائم التشغيل",
"feed": "التغذية",
"feed": "محتوى الاشتراكات",
"account": "الحساب",
"instance": "الخادم",
"player": "المشغل",
"livestreams": "البث المباشر",
"channels": "القنوات"
"channels": "القنوات",
"bookmarks": "الاشارات المرجعيه",
"channel_groups": "مجموعات القنوات",
"dearrow": "دي ارو"
},
"player": {
"watch_on": "شاهد عبر"
@ -41,7 +44,7 @@
"enable_sponsorblock": "تفعيل مانع الإعلانات",
"auto": "تلقائي",
"dark": "داكن",
"search": "بحث",
"search": "بحث (Ctrl+K)",
"autoplay_video": "تشغيل تلقائي",
"audio_only": "صوت فقط",
"default_quality": "الجودة الأساسية",
@ -50,7 +53,7 @@
"skip_interaction": "تخطي تذكير التفاعل (اشتراك)",
"skip_non_music": "تخطي الموسيقى: قسم غير الموسيقى",
"theme": "السمة",
"instance_selection": "تحديد المثيل",
"instance_selection": "قائمة الخوادم",
"export_to_json": "تصدير إلى JSON",
"show_more": "اظهار المزيد",
"skip_outro": "تخطي بطاقات النهاية / الاعتمادات",
@ -60,12 +63,12 @@
"skip_filler_tangent": "تخطي المحتوى الغير مهم",
"show_markers": "إظهار العلامات على المشغل",
"buffering_goal": "هدف التخزين المؤقت (بالثواني)",
"country_selection": "اختيار البلد",
"country_selection": "البلد",
"default_homepage": "الصفحة الرئيسية الافتراضية",
"show_comments": "إظهار التعليقات",
"minimize_description_default": "تصغير الوصف بشكل افتراضي",
"store_watch_history": "تخزين سجل المشاهدة",
"language_selection": "اختيار اللغة",
"language_selection": "اللغة",
"instances_list": "قائمة المثيلات",
"enabled_codecs": "برامج الترميز الممكنة (متعددة)",
"import_from_json": "استيراد من JSON/CSV",
@ -90,7 +93,7 @@
"minimize_recommendations_default": "تقليل التوصيات بشكل افتراضي",
"invalidate_session": "تسجيل الخروج من جميع الأجهزة",
"different_auth_instance": "استخدام مثيل مختلف للمصادقة",
"instance_auth_selection": "تحديد مثيل Autentication",
"instance_auth_selection": "خادم المصادقة",
"clone_playlist": "استنساخ قائمة التشغيل",
"clone_playlist_success": "تم استنساخها بنجاح!",
"download_as_txt": "تنزيل بتنسيق .txt",
@ -105,10 +108,8 @@
"follow_link": "اتبع الرابط",
"copy_link": "نسخ الرابط",
"time_code": "رمز الوقت (بالثواني)",
"rename_playlist": "إعادة تسمية قائمة التشغيل",
"new_playlist_name": "اسم قائمة تشغيل جديد",
"show_chapters": "الفصول",
"store_search_history": "حفظ سجل البحث",
"store_search_history": "تخزين سجل البحث",
"documentation": "التوثيق",
"status_page": "الحالة",
"source_code": "شفرة المصدر",
@ -120,7 +121,29 @@
"show_watch_on_youtube": "عرض زر مشاهدة على يوتيوب",
"minimize_chapters_default": "تصغير الفصول بشكل افتراضي",
"no_valid_playlists": "لا يحتوي الملف على قوائم تشغيل صالحة!",
"with_playlist": "المشاركة مع قائمة التشغيل"
"with_playlist": "المشاركة مع قائمة التشغيل",
"bookmark_playlist": "الاشاره المرجعيه",
"playlist_bookmarked": "تم وضعها في الاشارات المرجعية",
"skip_button_only": "إظهار زر التخطي",
"skip_automatically": "تلقائيا",
"min_segment_length": "الحد الأدنى لطول الفصل (بالثواني)",
"skip_segment": "تخطي الجزء",
"show_less": "عرض أقل",
"autoplay_next_countdown": "العد التنازلي الافتراضي حتى الفيديو التالي ( ثانية )",
"dismiss": "تجاهل",
"group_name": "أسم المجموعة",
"create_group": "إنشاء مجموعة",
"auto_display_captions": "عرض التسميات التوضيحية تلقائيا",
"cancel": "إلغاء",
"okay": "حسنًا",
"playlist_description": "وصف قائمة التشغيل",
"playlist_name": "اسم قائمة التشغيل",
"edit_playlist": "تعديل قائمة التشغيل",
"show_search_suggestions": "إظهار اقتراحات البحث",
"chapters_layout_mobile": "تخطيط الفصول على الهاتف",
"delete_automatically": "الحذف تلقائيا بعد",
"enable_dearrow": "تمكين دي ارو",
"generate_qrcode": "إنشاء رمز الاستجابة السريعة"
},
"video": {
"sponsor_segments": "المقاطع الإعلانية",
@ -130,7 +153,13 @@
"views": "{views} عدد المشاهدات",
"shorts": "فديوهات قصيرة",
"videos": "الفيديوات",
"live": "{0} مباشر"
"live": "{0} مباشر",
"all": "الكل",
"category": "الفئة",
"chapters_vertical": "رَأسِيّ",
"chapters_horizontal": "أفقي",
"visibility": "الظهور",
"license": "الترخيص"
},
"search": {
"channels": "يوتيوب: القنوات",
@ -141,7 +170,8 @@
"music_videos": "YT Music: مقاطع فيديو",
"did_you_mean": "هل تقصد: {0}؟",
"music_playlists": "YT Music: قوائم التشغيل",
"music_albums": "YT Music: ألبومات"
"music_albums": "YT Music: ألبومات",
"music_artists": "YT الموسيقى: الفنانين"
},
"preferences": {
"version": "الإصدار",
@ -160,7 +190,9 @@
},
"login": {
"username": "اسم المستخدم",
"password": "كلمة السر"
"password": "كلمة السر",
"passwords_incorrect": "كلمة المرور لم تتطابق!",
"password_confirm": "تأكيد كلمة المرور"
},
"subscriptions": {
"subscribed_channels_count": "مشترك في: {0}"
@ -173,6 +205,12 @@
"page_not_found": "لم يتم العثور على الصفحة",
"copied": "نسخ!",
"cannot_copy": "لا يمكن نسخه!",
"local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟"
"local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟",
"register_no_email_note": "لا ينصح باستخدام البريد الإلكتروني كاسم مستخدم. المضي قدما على أي حال؟",
"next_video_countdown": "تشغيل الفيديو التالي بعد { 0 } ق",
"weeks": "{amount} أسبوع (أسابيع)",
"hours": "{amount} ساعة (ساعات)",
"months": "{amount} شهر (أشهر)",
"days": "{amount} يوم (أيام)"
}
}

View file

@ -4,15 +4,17 @@
"login": "Daxil ol",
"register": "Qeydiyyatdan keç",
"feed": "Axın",
"preferences": "Seçimlər",
"preferences": "Üstünlüklər",
"history": "Tarixçə",
"subscriptions": "Abunəliklər",
"playlists": "Pleylistlər",
"playlists": "Oynatma Siyahıları",
"account": "Hesab",
"instance": "Nümunə",
"player": "Oynadıcı",
"livestreams": "Canlı yayımlar",
"channels": "Kanallar"
"livestreams": "Canlı Yayımlar",
"channels": "Kanallar",
"bookmarks": "Əlfəcinlər",
"channel_groups": "Kanal qrupları"
},
"player": {
"watch_on": "{0} saytında bax"
@ -30,12 +32,12 @@
"uses_api_from": "API-dən istifadə edir ",
"enable_sponsorblock": "SponsorBlok'u Aktivləşdir",
"skip_sponsors": "Sponsorları Ötür",
"skip_intro": "Fasilə/Giriş Animasiyasını Ötür",
"skip_outro": "Bitiş Kartları/Kreditləri Ötür",
"skip_intro": "Fasilə/Giriş Animasiyasın Ötür",
"skip_outro": "Son Kartları/Kreditləri Ötür",
"skip_preview": "Önbaxışı/Anonsu Ötür",
"skip_interaction": "İnteraksiya Xatırladıcısını Ötür(Abunə Ol)",
"skip_self_promo": "Ödənişsiz/Özünü Reklamı Ötür",
"skip_non_music": "Musiqini Ötür: Musiqi Olmayan Bölmə",
"skip_interaction": "Əlaqələndirmə Xatırladıcısın Ötür(Abunə Ol)",
"skip_self_promo": "Ödənilməmiş/Özünü Reklamı Ötür",
"skip_non_music": "Musiqini Ötür: Musiqisiz Bölmə",
"skip_highlight": "Anonsu Ötür",
"skip_filler_tangent": "Doldurucu Səhnələri Ötür",
"theme": "Tema",
@ -44,7 +46,7 @@
"light": "İşıqlı",
"autoplay_video": "Videonu Avto-oynat",
"audio_only": "Yalnız Səs",
"default_quality": "Defolt Keyfiyyət",
"default_quality": "Standart Keyfiyyət",
"buffering_goal": "Tamponlama hədəfi (saniyələrlə)",
"export_to_json": "JSON-a İxrac Et",
"import_from_json": "JSON/CSV-dan İdxal Et",
@ -53,61 +55,59 @@
"donations": "İnkişaf ianələri",
"minimize_description": "Açıqlamanı Kiçilt",
"show_description": "Açıqlamanı Göstər",
"minimize_recommendations": "Tövsiyələri kiçilt",
"minimize_recommendations": "Tövsiyələri Kiçilt",
"show_recommendations": "Tövsiyələri Göstər",
"disable_lbry": "Yayım üçün LBRY-ni deaktiv et",
"enable_lbry_proxy": "LBRY üçün Proksi-ni Aktivləşdir",
"view_ssl_score": "SSL Nəticəsinə Bax",
"search": "Axtarış",
"search": "Axtarış (Ctrl+K)",
"filter": "Filtr",
"loading": "Yüklənir...",
"clear_history": "Tarixçəni Təmizlə",
"hide_replies": "Cavabları Gizlət",
"load_more_replies": "Daha Çox Cavab Yüklə",
"add_to_playlist": "Pleylistə Əlavə Et",
"remove_from_playlist": "Pleylistdən Sil",
"delete_playlist_video_confirm": "Video pleylistdən silinsin?",
"create_playlist": "Pleylist Yarat",
"delete_playlist": "Pleylisti Sil",
"select_playlist": "Pleylist Seç",
"delete_playlist_confirm": "Bu pleylist silinsin?",
"please_select_playlist": "Lütfən, pleylist seç",
"add_to_playlist": "Oynatma siyahısına əlavə et",
"remove_from_playlist": "Oynatma siyahısından təmizlə",
"delete_playlist_video_confirm": "Video oynatma siyahısından silinsin?",
"create_playlist": "Oynatma Siyahısı Yarat",
"delete_playlist": "Oynatma Siyahısın Sil",
"select_playlist": "Oynatma Siyahısı Seç",
"delete_playlist_confirm": "Bu oynatma siyahısı silinsin?",
"please_select_playlist": "Xahiş edilir, oynatma siyahısı seç",
"country_selection": "Ölkə Seçimi",
"default_homepage": "Defolt Əsas Səhifə",
"default_homepage": "Standart Əsas Səhifə",
"show_comments": "Şərhləri Göstər",
"instance_selection": "Nümunə Seçimi",
"minimize_description_default": "Açıqlamanı Defolt Olaraq Kiçilt",
"minimize_description_default": "Açıqlamanı Standart Olaraq Kiçilt",
"language_selection": "Dil Seçimi",
"instances_list": "Nümunələr Siyahısı",
"show_more": "Daha Çox Göstər",
"no": "Xeyr",
"store_watch_history": "Baxış Tarixçəsini Saxla",
"enabled_codecs": "Aktiv Kodeklər (Birdən çox)",
"store_watch_history": "Baxış Tarixçəsin Saxla",
"enabled_codecs": "Aktiv Kodlayıcılar (Çoxlu)",
"yes": "Bəli",
"show_markers": "Oynadıcıda Markerləri Göstər",
"delete_account": "Hesabı Sil",
"logout": "Bu cihazdan çıx",
"minimize_recommendations_default": "Defolt olaraq Tövsiyələri kiçilt",
"minimize_recommendations_default": "Standart olaraq Tövsiyələri kiçilt",
"download_as_txt": ".txt kimi endir",
"reset_preferences": "Seçimləri sıfırla",
"reset_preferences": "Üstünlükləri sıfırla",
"confirm_reset_preferences": "Seçimlərinizi sıfırlamaq istədiyinizə əminsiniz?",
"backup_preferences": "Yedəkləmə seçimləri",
"backup_preferences": "Nüsxələmə seçimləri",
"restore_preferences": "Seçimləri bərpa et",
"invalidate_session": "Bütün cihazlardan çıxın",
"different_auth_instance": "Təsdiqləmə üçün fərqli nümunə istifadə et",
"instance_auth_selection": "Təsdiqləmə Nümunəsi Seçimi",
"clone_playlist": "Pleylisti Klonla",
"clone_playlist": "Oynatma Siyahısın Klonla",
"clone_playlist_success": "Uğurla klonlandı!",
"rename_playlist": "Pleylist adını dəyiş",
"time_code": "Vaxt kodu (saniyələrlə)",
"store_search_history": "Axtarış tarixçəsini saxla",
"documentation": "Sertifikatlaşdırma",
"documentation": "Sənədləşdirmə",
"status_page": "Vəziyyət",
"source_code": "Mənbə kodu",
"instance_donations": "Nümunə ianələri",
"hide_watched": "Axında baxılan videoları gizlət",
"show_chapters": "Bölmələr",
"new_playlist_name": "Yeni pleylist adı",
"share": "Paylaş",
"with_timecode": "Vaxt kodu ilə paylaş",
"follow_link": "Bağlantını izlə",
@ -117,21 +117,40 @@
"reply_count": "{count} cavab",
"minimize_comments_default": "Şərhləri standart olaraq kiçilt",
"minimize_comments": "Şərhləri Kiçilt",
"minimize_chapters_default": "Defolt olaraq bölmələri kiçilt",
"show_watch_on_youtube": "YouTube-da Baxış düyməsini göstər",
"no_valid_playlists": "Faylda etibarlı pleylistlər yoxdur!",
"with_playlist": "Pleylistlə paylaş"
"minimize_chapters_default": "Standart olaraq bölmələri kiçilt",
"show_watch_on_youtube": "YouTube-da Baxış düyməsin göstər",
"no_valid_playlists": "Faylın etibarlı oynatma siyahıları yoxdur!",
"with_playlist": "Oynatma siyahısıyla paylaş",
"bookmark_playlist": "Əlfəcin",
"playlist_bookmarked": "Əlfəcinləndi",
"skip_button_only": "Ötürmə düyməsin göstər",
"skip_automatically": "Avtomatik olaraq",
"min_segment_length": "Minimum Seqment Uzunluğu (saniyələrlə)",
"skip_segment": "Seqmenti ötür",
"show_less": "Daha az göstər",
"autoplay_next_countdown": "Növbəti videoya qədər standart geri sayım (saniyə)",
"dismiss": "Rədd et",
"create_group": "Qrup yarat",
"group_name": "Qrup adı",
"cancel": "Ləğv et",
"edit_playlist": "Oynatma siyahısın redaktə et",
"playlist_description": "Oynatma siyahısı təsviri",
"okay": "Oldu!",
"chapters_layout_mobile": "Mobildə Bölmələrin Tərtibatı",
"playlist_name": "Oynatma siyahısı adı",
"show_search_suggestions": "Axtarış təkliflərin göstər",
"auto_display_captions": "Titrləri Avtomatik Göstər"
},
"comment": {
"pinned_by": "Tərəfindən Sabitləndi {author}",
"disabled": "Şərhlər yükləyici tərəfindən deaktiv edilib.",
"loading": "Şərhlər yüklənir...",
"user_disabled": "Şərhlər tənzimləmələrdə deaktiv edilib."
"pinned_by": "{author} tərəfindən sabitlənib",
"disabled": "Şərhlər yükləyici tərəfindən bağlanıb.",
"loading": "Şərhlər yüklənilir...",
"user_disabled": "Şərhlər tənzimləmələrdə qeyri-aktiv edilib."
},
"preferences": {
"instance_name": "Nümunə Adı",
"instance_locations": "Nümunə Məkanları",
"has_cdn": "CDN varmı?",
"has_cdn": "CDN Varmı?",
"registered_users": "Qeydiyyatdan Keçmiş İstifadəçilər",
"version": "Versiya",
"up_to_date": "Güncəllənib?",
@ -146,21 +165,25 @@
"views": "{views} baxış",
"watched": "Baxılıb",
"sponsor_segments": "Sponsorlar Seqmentləri",
"ratings_disabled": "Reytinqlər Deaktivdir",
"ratings_disabled": "Reytinqlər Qeyri-aktivdir",
"chapters": "Bölmələr",
"live": "{0} Canlı",
"shorts": "Qısa"
"shorts": "Qısa",
"all": "Hamısı",
"category": "Kateqoriya",
"chapters_horizontal": "Üfüqi",
"chapters_vertical": "Şaquli"
},
"search": {
"did_you_mean": "Bunu nəzərdə tutursunuz: {0}?",
"all": "YouTube: Hamısı",
"videos": "YouTube: Videolar",
"channels": "YouTube: Kanallar",
"playlists": "YouTube: Pleylistlər",
"playlists": "YouTube: Oynatma siyahıları",
"music_songs": "YT Music: Mahnılar",
"music_videos": "YT Music: Videolar",
"music_albums": "YT Music: Albomlar",
"music_playlists": "YT Music: Pleylistlər"
"music_playlists": "YT Music: Oynatma Siyahıları"
},
"subscriptions": {
"subscribed_channels_count": "Abunə oldu: {0}"
@ -169,10 +192,12 @@
"preferences_note": "Qeyd: seçimlər brauzerinizin yerli yaddaşında saxlanılır. Brauzer məlumatlarınızın silinməsi onları sıfırlayacaq."
},
"info": {
"preferences_note": "Qeyd: Seçimlər brauzerinizin yerli yaddaşında saxlanılır. Brauzer məlumatlarınızın silinməsi onları sıfırlayacaq.",
"preferences_note": "Qeyd: Seçimlər brauzerinizin öz yaddaşında saxlanılır. Brauzer məlumatınızın silinməsi onları sıfırlayacaq.",
"page_not_found": "Səhifə tapılmadı",
"copied": "Kopyalandı!",
"cannot_copy": "Kopyalanmır!",
"local_storage": "Bu fəaliyyət localStorage tələb edir, məlumat bazası aktivdir?"
"copied": "Nüsxələndi!",
"cannot_copy": "Nüsxələnmir!",
"local_storage": "Bu fəaliyyət yerli yaddaş tələb edir, məlumat bazası aktivdir?",
"register_no_email_note": "E-poçt-u istifadəçi adı kimi istifadə etmək tövsiyə edilmir. Baxmayaraq ki, davam edilsin?",
"next_video_countdown": "Növbəti video {0} saniyəyə oynadılır"
}
}

169
src/locales/bg.json Normal file
View file

@ -0,0 +1,169 @@
{
"titles": {
"channels": "Канали",
"login": "Вход",
"register": "Регистрация",
"feed": "Емисия",
"history": "История",
"playlists": "Плейлисти",
"instance": "Инстанция",
"player": "Плейър",
"livestreams": "Излъчвания на живо",
"bookmarks": "Отметки",
"trending": "Набиращи популярност",
"account": "Профил",
"preferences": "Настройки",
"subscriptions": "Абонаменти",
"dearrow": "DeArrow",
"channel_groups": "Канални групи"
},
"actions": {
"most_recent": "Най-скорошен",
"unsubscribe": "Отписване - {count}",
"uses_api_from": "Използва API от ",
"skip_sponsors": "Пропускане на спонсори",
"skip_preview": "Пропускане на преглед/обобщение",
"skip_self_promo": "Пропускане на самореклама/неплатена реклама",
"min_segment_length": "Минимална дължина на сегмента (в секунди)",
"default_quality": "Качество по подразбиране",
"minimize_comments_default": "Минимизиране на коментарите по подразбиране",
"subscribe": "Абониране - {count}",
"view_subscriptions": "Преглед на абонаменти",
"sort_by": "Сортиране по:",
"least_recent": "Най-малко скорошен",
"channel_name_asc": "Име на канал (А-Я)",
"channel_name_desc": "Име на канал (Я-А)",
"back": "Назад",
"enable_sponsorblock": "Активиране на SponsorBlock",
"skip_button_only": "Показване на бутона за пропускане",
"skip_automatically": "Автоматично",
"skip_intro": "Пропускане на прекъсване/въвеждаща анимация",
"skip_outro": "Пропускане на крайни карти/надписи",
"skip_interaction": "Пропускане на напомняне за абониране",
"skip_non_music": "Попускане Немузикален раздел в музика",
"skip_highlight": "Пропускане на видео акцент",
"show_markers": "Показване на маркери в плейъра",
"skip_segment": "Пропускане на сегмент",
"theme": "Тема",
"auto": "Автоматично",
"dark": "Тъмна",
"light": "Светла",
"autoplay_video": "Автоматично пускане на видео",
"audio_only": "Само аудио",
"buffering_goal": "Буфериране (в секунди)",
"country_selection": "Избор на държава",
"default_homepage": "Начална страница по подразбиране",
"minimize_description_default": "Минимизиране на описанието по подразбиране",
"store_watch_history": "Запазване на историята на гледане",
"language_selection": "Избор на език",
"instances_list": "Списък на инстанциите",
"enabled_codecs": "Разрешени кодеци (множество)",
"instance_selection": "Избор на инстанция",
"show_more": "Покажи повече",
"yes": "Да",
"no": "Не",
"export_to_json": "Експорт в JSON",
"import_from_json": "Импорт от JSON/CSV",
"loop_this_video": "Повтаряне на това видео",
"auto_play_next_video": "Автоматично пускане на следващото видео",
"donations": "Дарения за разработка",
"minimize_comments": "Минимизиране на коментарите",
"show_comments": "Показване на коментарите",
"show_description": "Показване на описание",
"search": "Търси",
"minimize_description": "Минимизиране на описание",
"filter": "Филтър",
"clear_history": "Изчистване на историята",
"minimize_recommendations": "Минимизиране на препоръчани",
"show_recommendations": "Показване на препоръчани",
"view_ssl_score": "Преглед на SSL резултат",
"loading": "Зареждане...",
"hide_replies": "Скрий отговорите",
"load_more_replies": "Зареди още отговори",
"remove_from_playlist": "Премахване от плейлист",
"create_playlist": "Създаване на плейлист",
"reset_preferences": "Нулиране на настройките",
"with_timecode": "Сподели с текущото време",
"piped_link": "Piped връзка",
"documentation": "Документация",
"delete_account": "Изтрий акаунта",
"download_as_txt": "Изтегляне като .txt",
"share": "Сподели",
"follow_link": "Последвай връзката",
"add_to_playlist": "Добави към плейлист",
"delete_playlist_video_confirm": "Да се премахне ли видеото от плейлиста?",
"show_watch_on_youtube": "Показване на бутона \"Гледай в YouTube\"",
"source_code": "Изходен код",
"minimize_chapters_default": "Минимизиране на разделите по подразбиране",
"minimize_recommendations_default": "Минимизиране на препоръчани по подразбиране",
"show_chapters": "Раздели",
"logout": "Отписване от това устройство",
"clone_playlist": "Клониране на плейлист",
"clone_playlist_success": "Успешно клониране!",
"backup_preferences": "Архивиране на настройките",
"back_to_home": "Обратно към начална страница",
"status_page": "Статус",
"copy_link": "Копирай връзката",
"time_code": "Текущо време (в секунди)",
"reply_count": "{count} отговора",
"restore_preferences": "Възстановяване на настройките",
"invalidate_session": "Отписване от всички устройства",
"different_auth_instance": "Използване на различна инстанция за удостоверяване",
"store_search_history": "Запазване на историята на търсене",
"instance_auth_selection": "Избор на инстанция за удостоверяване",
"confirm_reset_preferences": "Сигурни ли сте, че искате да нулирате настройките?",
"hide_watched": "Скриване на гледани видеоклипове в Абонаменти",
"enable_dearrow": "Включи DeArrow"
},
"player": {
"watch_on": "Гледай в {0}"
},
"login": {
"username": "Потребителско име",
"password": "Парола"
},
"video": {
"videos": "Видеоклипове",
"views": "{views} показвания",
"chapters": "Раздели",
"all": "Всички",
"watched": "Гледани",
"category": "Категория"
},
"preferences": {
"version": "Версия",
"registered_users": "Регистрирани потребители",
"instance_locations": "Местоположения на инстанция",
"instance_name": "Име на инстанция",
"has_cdn": "Има ли CDN?",
"up_to_date": "Актуален?",
"ssl_score": "SSL резултат"
},
"comment": {
"disabled": "Коментарите са деактивирани.",
"pinned_by": "Фиксиран от {author}",
"loading": "Коментарите се зареждат...",
"user_disabled": "Коментарите са деактивирани в настройките."
},
"search": {
"did_you_mean": "Имахте предвид: {0}?",
"all": "YouTube: Всички",
"videos": "YouTube: Видеоклипове",
"channels": "YouTube: Канали",
"playlists": "YouTube: Плейлисти",
"music_songs": "YT Music: Песни",
"music_videos": "YT Music: Видеоклипове",
"music_albums": "YT Music: Албуми",
"music_playlists": "YT Music: Плейлисти"
},
"subscriptions": {
"subscribed_channels_count": "Абониран за: {0}"
},
"info": {
"page_not_found": "Страницата не е намерена",
"copied": "Копирано!",
"cannot_copy": "Не може да се копира!",
"local_storage": "Това действие изисква localStorage, разрешени ли са бисквитките?",
"register_no_email_note": "Използването на имейл като потребителско име не се препоръчва. Продължете все пак?"
}
}

View file

@ -25,7 +25,7 @@
"audio_only": "Samo zvuk",
"default_homepage": "Zadana početna stranica",
"loop_this_video": "Stavite ovaj video na ponavljanje",
"search": "Pretraga",
"search": "Pretraga (Ctrl+K)",
"skip_preview": "Preskočite pregled",
"skip_non_music": "Preskočite muziku: sekcija bez muzike",
"skip_self_promo": "Preskočite neplaćenu/samo-promociju",
@ -77,8 +77,6 @@
"minimize_chapters_default": "Smanjite poglavlja po zadanom",
"show_watch_on_youtube": "Prikaži „Gledaj na YouTube-u” dugme",
"different_auth_instance": "Koristite drugu instancu za autentifikaciju",
"rename_playlist": "Preimenuj listu izvođenja",
"new_playlist_name": "Novi naziv liste izvođenja",
"with_timecode": "Podijelite s vremenskim kodom",
"piped_link": "Piped poveznica",
"follow_link": "Prati poveznicu",
@ -102,7 +100,16 @@
"minimize_comments": "Minimizirajte komentare",
"delete_account": "Izbriši račun",
"minimize_recommendations_default": "Smanjite preporuke po zadanom",
"reset_preferences": "Vrati postavke na zadano"
"reset_preferences": "Vrati postavke na zadano",
"bookmark_playlist": "Bilježak",
"playlist_bookmarked": "Obilježeno",
"show_less": "Prikaži manje",
"skip_button_only": "Prikaži dugme za preskakanje",
"skip_automatically": "Automatski",
"min_segment_length": "Najmanja dužina segmenta (u sekundama)",
"skip_segment": "Preskoči segment",
"autoplay_next_countdown": "Zadano odbrojavanje do sljedećeg videa (u sekundama)",
"dismiss": "Odbaci"
},
"titles": {
"register": "Registrirajte se",
@ -117,7 +124,8 @@
"account": "Račun",
"player": "Pokretnik",
"channels": "Kanali",
"livestreams": "Prijenosi uživo"
"livestreams": "Prijenosi uživo",
"bookmarks": "Bilješci"
},
"search": {
"music_songs": "YT Music: Pjesme",
@ -154,7 +162,9 @@
"watched": "Pogledano",
"videos": "Video zapisi",
"live": "{0} Uživo",
"shorts": "Kratki videi"
"shorts": "Kratki videi",
"category": "Kategorija",
"all": "Sve"
},
"comment": {
"pinned_by": "Prikačeno od {author}",
@ -170,6 +180,8 @@
"cannot_copy": "Nije moguće kopirati!",
"page_not_found": "Stranica nije pronađena",
"copied": "Kopirano!",
"local_storage": "Ova radnja zahtijeva lokalno pohranjivanje, jesu li kolačići omogućeni?"
"local_storage": "Ova radnja zahtijeva lokalno pohranjivanje, jesu li kolačići omogućeni?",
"register_no_email_note": "Korištenje e-maila kao korisničko ime se ne preporučuje. Svejedno nastaviti?",
"next_video_countdown": "Reproduciranje sljedećeg videa u {0}"
}
}

View file

@ -12,7 +12,8 @@
"instance": "Instància",
"player": "Reproductor",
"livestreams": "Retransmissió en directe",
"channels": "Canals"
"channels": "Canals",
"bookmarks": "Marcadors"
},
"actions": {
"channel_name_desc": "Nom del Canal (Z-A)",
@ -42,7 +43,7 @@
"enabled_codecs": "Còdecs Habilitats (Múltiple)",
"instances_list": "Llista d'Instàncies",
"instance_selection": "Selecció d'Instàncies",
"show_more": "Mostrar Més",
"show_more": "Mostrar més",
"yes": "Sí",
"no": "No",
"export_to_json": "Exportar a JSON",
@ -102,8 +103,6 @@
"time_code": "Moment (en segons)",
"copy_link": "Copiar l'enllaç",
"follow_link": "Vés a l'enllaç",
"rename_playlist": "Canviar el nom de la llista de reproducció",
"new_playlist_name": "Nom nou de la llista de reproducció",
"store_search_history": "Emmagatzema l'historial de cerca",
"instance_donations": "Donacions a instàncies",
"hide_watched": "Amaga els vídeos vistos de Continguts",
@ -114,7 +113,17 @@
"show_watch_on_youtube": "Mostra el botó \"Veure a Youtube\"",
"reply_count": "{count} respostes",
"minimize_comments_default": "Minimitzar els comentaris per defecte",
"minimize_comments": "Minimitza els comentaris"
"minimize_comments": "Minimitza els comentaris",
"no_valid_playlists": "L'arxiu no conté llistes de reproducció vàlides!",
"bookmark_playlist": "Marcador",
"playlist_bookmarked": "Afegit a marcadors",
"minimize_chapters_default": "Minimitzar capítols per defecte",
"skip_button_only": "Mostra el botó de saltar",
"skip_automatically": "Automàticament",
"min_segment_length": "Longitud de segment mínima (en segons)",
"skip_segment": "Saltar segment",
"with_playlist": "Comparteix amb llista de reproducció",
"show_less": "Mostrar menys"
},
"comment": {
"pinned_by": "Fixat per {author}",
@ -139,7 +148,9 @@
"live": "{0} En Directe",
"videos": "Vídeos",
"views": "{views} visualitzacions",
"shorts": "Curts"
"shorts": "Curts",
"all": "Tot",
"category": "Categoria"
},
"search": {
"did_you_mean": "Volies dir: {0}?",
@ -169,6 +180,8 @@
"preferences_note": "Nota: les preferències es desen a l'emmagatzematge local del navegador. Si elimineu les dades del navegador, es restabliran.",
"page_not_found": "No s'ha torbat la pàgina",
"copied": "Copiat!",
"cannot_copy": "No es pot copiar!"
"cannot_copy": "No es pot copiar!",
"local_storage": "Aquesta acció requereix emmagatzematge local, estan les cookies habilitades?",
"register_no_email_note": "Utilitzar un correu elextrònic com a usuari no és recomanable. Continuar de totes maneres?"
}
}

View file

@ -12,7 +12,10 @@
"instance": "Instance",
"player": "Přehrávač",
"livestreams": "Živé přenosy",
"channels": "Kanály"
"channels": "Kanály",
"bookmarks": "Záložky",
"channel_groups": "Skupiny kanálů",
"dearrow": "DeArrow"
},
"actions": {
"loop_this_video": "Přehrávat video ve smyčce",
@ -35,15 +38,15 @@
"audio_only": "Pouze zvuk",
"default_quality": "Výchozí kvalita",
"buffering_goal": "Ukládání do vyrovnávací paměti (v sekundách)",
"country_selection": "Výběr země",
"country_selection": "Země",
"default_homepage": "Výchozí domovská stránka",
"show_comments": "Zobrazit komentáře",
"minimize_description_default": "Automaticky minimalizovat popis",
"store_watch_history": "Ukládat historii sledování",
"language_selection": "Výběr jazyka",
"language_selection": "Jazyk",
"instances_list": "Seznam instancí",
"enabled_codecs": "Povolené kodeky (několik)",
"instance_selection": "Výběr instance",
"instance_selection": "Instance",
"show_more": "Zobrazit více",
"yes": "Ano",
"no": "Ne",
@ -59,7 +62,7 @@
"disable_lbry": "Zakázat LBRY pro streamování",
"enable_lbry_proxy": "Povolit proxy pro LBRY",
"view_ssl_score": "Zobrazit stav SSL",
"search": "Vyhledat",
"search": "Vyhledávání (Ctrl+K)",
"filter": "Filtr",
"loading": "Načítání...",
"clear_history": "Smazat historii",
@ -87,7 +90,7 @@
"minimize_recommendations_default": "Ve výchozím nastavení minimalizovat doporučení",
"invalidate_session": "Odhlásit se ze všech zařízení",
"different_auth_instance": "Použít jinou instanci pro autentizaci",
"instance_auth_selection": "Výběr autentizační instance",
"instance_auth_selection": "Autentizační instance",
"clone_playlist": "Duplikovat playlist",
"clone_playlist_success": "Úspěšně duplikováno!",
"download_as_txt": "Stáhnout jako .txt",
@ -102,8 +105,6 @@
"follow_link": "Otevřít odkaz",
"copy_link": "Kopírovat odkaz",
"time_code": "Časový kód (v sekundách)",
"rename_playlist": "Přejmenovat playlist",
"new_playlist_name": "Nový název playlistu",
"show_chapters": "Kapitoly",
"store_search_history": "Ukládat historii vyhledávání",
"hide_watched": "Skrýt sledovaná videa v kanálu",
@ -117,7 +118,29 @@
"show_watch_on_youtube": "Zobrazit tlačítko Sledovat na YouTube",
"minimize_chapters_default": "Ve výchozím nastavení skrýt kapitoly",
"no_valid_playlists": "Soubor neobsahuje platné playlisty!",
"with_playlist": "Sdílet s playlistem"
"with_playlist": "Sdílet s playlistem",
"bookmark_playlist": "Záložka",
"playlist_bookmarked": "Uloženo",
"skip_automatically": "Automaticky",
"skip_segment": "Přeskočit segment",
"skip_button_only": "Zobrazit tlačítko přeskočení",
"min_segment_length": "Minimální délka segmentu (v sekundách)",
"show_less": "Zobrazit méně",
"autoplay_next_countdown": "Výchozí odpočet do dalšího videa (v sekundách)",
"dismiss": "Zavřít",
"group_name": "Název skupiny",
"create_group": "Vytvořit skupinu",
"auto_display_captions": "Automatické zobrazení titulků",
"playlist_name": "Název playlistu",
"cancel": "Zrušit",
"edit_playlist": "Upravit playlist",
"playlist_description": "Popis playlistu",
"okay": "Okay",
"show_search_suggestions": "Zobrazit našeptávání ve vyhledávání",
"chapters_layout_mobile": "Rozložení kapitol na mobilu",
"enable_dearrow": "Povolit DeArrow",
"delete_automatically": "Automaticky odstranit po",
"generate_qrcode": "Vygenerovat QR kód"
},
"player": {
"watch_on": "Sledovat na {0}"
@ -139,7 +162,9 @@
},
"login": {
"username": "Uživatelské jméno",
"password": "Heslo"
"password": "Heslo",
"password_confirm": "Potvrzení hesla",
"passwords_incorrect": "Hesla se neshodují!"
},
"video": {
"videos": "Videa",
@ -149,7 +174,13 @@
"ratings_disabled": "Hodnocení zakázáno",
"chapters": "Kapitoly",
"live": "{0} Živě",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Vše",
"category": "Kategorie",
"chapters_horizontal": "Horizontální",
"chapters_vertical": "Vertikální",
"license": "Licence",
"visibility": "Viditelnost"
},
"search": {
"did_you_mean": "Mysleli jste: {0}?",
@ -160,7 +191,8 @@
"playlists": "YouTube: Playlisty",
"music_videos": "YT Music: Videa",
"music_albums": "YT Music: Alba",
"music_playlists": "YT Music: Playlisty"
"music_playlists": "YT Music: Playlisty",
"music_artists": "YT Music: Umělci"
},
"subscriptions": {
"subscribed_channels_count": "Přihlášeno k odběru: {0}"
@ -173,6 +205,12 @@
"page_not_found": "Stránka nenalezena",
"copied": "Zkopírováno!",
"cannot_copy": "Nelze zkopírovat!",
"local_storage": "Tato akce vyžaduje localStorage, jsou povoleny cookies?"
"local_storage": "Tato akce vyžaduje localStorage, jsou povoleny cookies?",
"register_no_email_note": "Použití e-mailu jako uživatelského jména se nedoporučuje. Chcete přesto pokračovat?",
"next_video_countdown": "Přehrávání dalšího videa za {0}s",
"hours": "{amount} hodin",
"days": "{amount} dnů",
"weeks": "{amount} týdnů",
"months": "{amount} měsíců"
}
}

View file

@ -1,17 +1,17 @@
{
"actions": {
"skip_outro": "Abspann überspringen",
"skip_non_music": "Musik überspringen: Nicht-Musik-Bereich",
"skip_outro": "Endkarten und Abspann überspringen",
"skip_non_music": "Musik: Nicht-Musik-Abschnitte überspringen",
"skip_self_promo": "Unbezahlte Werbung/Eigenwerbung überspringen",
"skip_interaction": "Interaktionserinnerung überspringen (Abonnieren)",
"skip_preview": "Vorschau/Rückschau überspringen",
"skip_interaction": "Interaktionserinnerungen überspringen (Daumen hoch, abonnieren, ...)",
"skip_preview": "Vorschau und Rückblick überspringen",
"instances_list": "Liste der Instanzen",
"language_selection": "Sprachauswahl",
"language_selection": "Sprache",
"store_watch_history": "Wiedergabeverlauf speichern",
"minimize_description_default": "Beschreibung standardmäßig minimieren",
"show_comments": "Kommentare anzeigen",
"default_homepage": "Standard-Startseite",
"country_selection": "Länderauswahl",
"default_homepage": "Startseite",
"country_selection": "Land",
"buffering_goal": "Pufferungsziel (in Sekunden)",
"default_quality": "Standardqualität",
"audio_only": "Nur Audio",
@ -20,9 +20,9 @@
"dark": "Dunkel",
"auto": "Automatisch",
"theme": "Farbschema",
"skip_intro": "Pausen-/Intro-Animation überspringen",
"skip_sponsors": "Sponsoren überspringen",
"enable_sponsorblock": "Sponsorblock einschalten",
"skip_intro": "Unterbrechungen und Intro-Animation überspringen",
"skip_sponsors": "Gesponsorte Videoabschnitte überspringen",
"enable_sponsorblock": "SponsorBlock verwenden",
"uses_api_from": "Verwendet die API von ",
"back": "Zurück",
"channel_name_desc": "Kanalname (Z-A)",
@ -30,18 +30,18 @@
"least_recent": "Am wenigsten neu",
"most_recent": "Am Neuesten",
"sort_by": "Sortieren nach:",
"view_subscriptions": "Abonnements anzeigen",
"view_subscriptions": "Abos anzeigen",
"unsubscribe": "Deabonnieren - {count}",
"subscribe": "Abonnieren - {count}",
"enabled_codecs": "Aktivierte Codecs (mehrere)",
"enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)",
"enable_lbry_proxy": "Proxy für LBRY einschalten",
"disable_lbry": "LBRY für Streaming deaktivieren",
"instance_selection": "Instanzauswahl",
"instance_selection": "Instanz",
"show_description": "Beschreibung anzeigen",
"minimize_description": "Beschreibung minimieren",
"show_recommendations": "Empfehlungen anzeigen",
"minimize_recommendations": "Empfehlungen minimieren",
"donations": "Spenden für die Entwickler",
"donations": "Spenden",
"auto_play_next_video": "Nächstes Video automatisch abspielen",
"loop_this_video": "Dieses Video wiederholen",
"import_from_json": "Aus JSON/CSV importieren",
@ -51,32 +51,30 @@
"yes": "Ja",
"loading": "Wird geladen…",
"filter": "Filtern",
"search": "Suchen",
"search": "Suchen (Strg+K)",
"view_ssl_score": "SSL-Bewertung anzeigen",
"clear_history": "Verlauf löschen",
"hide_replies": "Antworten ausblenden",
"load_more_replies": "Mehr Antworten laden",
"skip_highlight": "Höhepunkt überspringen",
"skip_filler_tangent": "Lückenfüller überspringen",
"delete_playlist_confirm": "Diese Wiedergabeliste löschen?",
"remove_from_playlist": "Aus Wiedergabeliste entfernen",
"add_to_playlist": "Zur Wiedergabeliste hinzufügen",
"create_playlist": "Wiedergabeliste erstellen",
"delete_playlist_video_confirm": "Video aus Wiedergabeliste entfernen?",
"delete_playlist": "Wiedergabeliste löschen",
"please_select_playlist": "Bitte wählen Sie eine Wiedergabeliste",
"select_playlist": "Wählen Sie eine Wiedergabeliste",
"delete_playlist_confirm": "Diese Playlist löschen?",
"remove_from_playlist": "Aus Playlist entfernen",
"add_to_playlist": "Zur Playlist hinzufügen",
"create_playlist": "Playlist erstellen",
"delete_playlist_video_confirm": "Video aus Playlist entfernen?",
"delete_playlist": "Playlist löschen",
"please_select_playlist": "Bitte wähle eine Playlist",
"select_playlist": "Wähle eine Playlist",
"show_markers": "Markierungen auf dem Player anzeigen",
"delete_account": "Konto löschen",
"logout": "Von diesem Gerät abmelden",
"minimize_recommendations_default": "Empfehlungen standardmäßig minimieren",
"invalidate_session": "Von allen Geräte abmelden",
"invalidate_session": "Von allen Geräten abmelden",
"different_auth_instance": "Eine andere Instanz für die Authentifizierung verwenden",
"instance_auth_selection": "Auswahl der Autentifizierungsinstanz",
"clone_playlist": "Wiedergabeliste klonen",
"clone_playlist_success": "Erfolgreich geklont!",
"rename_playlist": "Wiedergabeliste umbenennen",
"new_playlist_name": "Neuer Name der Wiedergabeliste",
"instance_auth_selection": "Authentifizierungsinstanz",
"clone_playlist": "Playlist duplizieren",
"clone_playlist_success": "Erfolgreich dupliziert!",
"piped_link": "Piped-Link",
"download_as_txt": "Als .txt herunterladen",
"back_to_home": "Zurück zur Startseite",
@ -84,7 +82,7 @@
"with_timecode": "Mit Zeitstempel teilen",
"follow_link": "Link öffnen",
"copy_link": "Link kopieren",
"time_code": "Zeitstempel (in sekunden)",
"time_code": "Zeitstempel (in Sekunden)",
"reset_preferences": "Einstellungen zurücksetzen",
"confirm_reset_preferences": "Bist du sicher, dass du deine Einstellungen zurücksetzen möchtest?",
"backup_preferences": "Einstellungen sichern",
@ -92,17 +90,39 @@
"show_chapters": "Kapitel",
"source_code": "Quellcode",
"store_search_history": "Suchverlauf speichern",
"hide_watched": "Gesehene Videos im Feed ausblenden",
"hide_watched": "Gesehene Videos im Abo-Feed ausblenden",
"reply_count": "{count} Antworten",
"instance_donations": "Instanz-Spenden",
"documentation": "Dokumentation",
"status_page": "Status",
"minimize_chapters_default": "Kapitel standardmäßig minimieren",
"minimize_comments_default": "Kommentare automatisch minimieren",
"minimize_comments_default": "Kommentare standardmäßig minimieren",
"minimize_comments": "Kommentare minimieren",
"no_valid_playlists": "Die Datei enthält keine gültigen Wiedergabelisten!",
"no_valid_playlists": "Die Datei enthält keine gültigen Playlists!",
"show_watch_on_youtube": "Schaltfläche „Auf YouTube ansehen“ anzeigen",
"with_playlist": "Mit Wiedergabeliste teilen"
"with_playlist": "Mit Playlist teilen",
"playlist_bookmarked": "Markiert",
"bookmark_playlist": "Lesezeichen",
"skip_segment": "Abschnitt überspringen",
"skip_automatically": "Automatisch",
"min_segment_length": "Minimale Abschnittlänge (in Sekunden)",
"skip_button_only": "Überspringen-Schaltfläche anzeigen",
"show_less": "Weniger anzeigen",
"autoplay_next_countdown": "Anzahl der Sekunden bis das nächste Video automatisch startet",
"dismiss": "Ablehnen",
"group_name": "Gruppenname",
"create_group": "Gruppe erstellen",
"auto_display_captions": "Untertitel automatisch anzeigen",
"cancel": "Abbrechen",
"okay": "Okay",
"edit_playlist": "Playlist bearbeiten",
"playlist_name": "Name der Playlist",
"playlist_description": "Beschreibung der Playlist",
"show_search_suggestions": "Suchvorschläge anzeigen",
"chapters_layout_mobile": "Kapitel-Layout auf Mobilgeräten",
"delete_automatically": "Automatisch löschen nach",
"enable_dearrow": "DeArrow verwenden",
"generate_qrcode": "QR-Code generieren"
},
"player": {
"watch_on": "Auf {0} ansehen"
@ -110,27 +130,36 @@
"titles": {
"history": "Verlauf",
"preferences": "Einstellungen",
"feed": "Abonnements",
"feed": "Abos",
"register": "Registrieren",
"login": "Anmelden",
"trending": "Trends",
"subscriptions": "Abonnements",
"playlists": "Wiedergabelisten",
"subscriptions": "Abos",
"playlists": "Playlists",
"account": "Konto",
"player": "Player",
"instance": "Instanz",
"livestreams": "Livestreams",
"channels": "Kanäle"
"channels": "Kanäle",
"bookmarks": "Lesezeichen",
"channel_groups": "Kanalgruppen",
"dearrow": "DeArrow"
},
"video": {
"sponsor_segments": "Sponsoren-Segmente",
"sponsor_segments": "Sponsoren-Abschnitte",
"watched": "Angesehen",
"views": "{views} Aufrufe",
"videos": "Videos",
"ratings_disabled": "Bewertungen deaktiviert",
"live": "{0} Live",
"chapters": "Kapitel",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Alle",
"category": "Kategorie",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertikal",
"license": "Lizenz",
"visibility": "Sichtbarkeit"
},
"preferences": {
"ssl_score": "SSL-Bewertung",
@ -145,31 +174,40 @@
"pinned_by": "Angeheftet von {author}",
"user_disabled": "Kommentare wurden in den Einstellungen deaktiviert.",
"disabled": "Kommentare wurden vom Autor deaktiviert.",
"loading": "Kommentare werden geladen …"
"loading": "Kommentare werden geladen…"
},
"login": {
"password": "Passwort",
"username": "Anmeldename"
"username": "Benutzername",
"password_confirm": "Passwort bestätigen",
"passwords_incorrect": "Passwörter stimmen nicht überein!"
},
"search": {
"did_you_mean": "Hast du gemeint: {0}?",
"all": "YouTube: Alle",
"videos": "YouTube: Videos",
"channels": "YouTube: Kanäle",
"playlists": "YouTube: Wiedergabelisten",
"playlists": "YouTube: Playlists",
"music_songs": "YT Music: Lieder",
"music_videos": "YT Music: Videos",
"music_albums": "YT Music: Alben",
"music_playlists": "YT Music: Wiedergabelisten"
"music_playlists": "YT Music: Playlists",
"music_artists": "YT Music: Künstler:innen"
},
"subscriptions": {
"subscribed_channels_count": "Aboniert bei: {0}"
"subscribed_channels_count": "Anzahl Abos: {0}"
},
"info": {
"preferences_note": "Achtung: Einstellung werden lokal in deinem Browser gespeichert. Wenn du deine Browserdaten löschst werden sie auch gelöscht.",
"preferences_note": "Achtung: Die Einstellungen werden lokal in deinem Browser gespeichert. Wenn du deine Browserdaten löschst, werden auch deine Einstellungen zurückgesetzt.",
"page_not_found": "Seite nicht gefunden",
"copied": "Kopiert!",
"cannot_copy": "Kopieren nicht möglich!",
"local_storage": "Diese Aktion erfordert „localStorage“, sind Cookies aktiviert?"
"local_storage": "Diese Aktion erfordert „localStorage“, sind Cookies aktiviert?",
"register_no_email_note": "Es wird nicht empfohlen, eine E-Mail als Benutzernamen zu verwenden. Trotzdem fortfahren?",
"next_video_countdown": "Nächstes Video startet in {0}s",
"weeks": "{amount} Woche(n)",
"months": "{amount} Monat(en)",
"hours": "{amount} Stunde(n)",
"days": "{amount} Tag(e)"
}
}

View file

@ -12,7 +12,10 @@
"instance": "Instance",
"player": "Player",
"livestreams": "Livestreams",
"channels": "Channels"
"channels": "Channels",
"bookmarks": "Bookmarks",
"channel_groups": "Channel groups",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Watch on {0}"
@ -29,6 +32,8 @@
"back": "Back",
"uses_api_from": "Uses the API from ",
"enable_sponsorblock": "Enable Sponsorblock",
"skip_button_only": "Show skip button",
"skip_automatically": "Automatically",
"skip_sponsors": "Skip Sponsors",
"skip_intro": "Skip Intermission/Intro Animation",
"skip_outro": "Skip Endcards/Credits",
@ -39,23 +44,27 @@
"skip_highlight": "Skip Highlight",
"skip_filler_tangent": "Skip Filler Tangent",
"show_markers": "Show Markers on Player",
"min_segment_length": "Minimum Segment Length (in seconds)",
"skip_segment": "Skip Segment",
"enable_dearrow": "Enable DeArrow",
"theme": "Theme",
"auto": "Auto",
"dark": "Dark",
"light": "Light",
"autoplay_video": "Autoplay Video",
"autoplay_next_countdown": "Default Countdown until next video (in seconds)",
"audio_only": "Audio Only",
"default_quality": "Default Quality",
"buffering_goal": "Buffering Goal (in seconds)",
"country_selection": "Country Selection",
"country_selection": "Country",
"default_homepage": "Default Homepage",
"minimize_comments_default": "Minimize Comments by default",
"minimize_description_default": "Minimize Description by default",
"store_watch_history": "Store Watch History",
"language_selection": "Language Selection",
"language_selection": "Language",
"instances_list": "Instances List",
"enabled_codecs": "Enabled Codecs (Multiple)",
"instance_selection": "Instance Selection",
"instance_selection": "Instance",
"show_more": "Show More",
"yes": "Yes",
"no": "No",
@ -63,6 +72,7 @@
"import_from_json": "Import from JSON/CSV",
"loop_this_video": "Loop this Video",
"auto_play_next_video": "Auto Play next Video",
"auto_display_captions": "Auto Display Captions",
"donations": "Development donations",
"minimize_comments": "Minimize Comments",
"show_comments": "Comments",
@ -73,7 +83,7 @@
"disable_lbry": "Disable LBRY for Streaming",
"enable_lbry_proxy": "Enable Proxy for LBRY",
"view_ssl_score": "View SSL Score",
"search": "Search",
"search": "Search (Ctrl+K)",
"filter": "Filter",
"loading": "Loading...",
"clear_history": "Clear History",
@ -91,10 +101,11 @@
"logout": "Logout from this device",
"minimize_recommendations_default": "Minimize Recommendations by default",
"minimize_chapters_default": "Minimize Chapters by default",
"chapters_layout_mobile": "Chapters Layout On Mobile",
"show_watch_on_youtube": "Show Watch on YouTube button",
"invalidate_session": "Logout all devices",
"different_auth_instance": "Use a different instance for authentication",
"instance_auth_selection": "Autentication Instance Selection",
"instance_auth_selection": "Authentication Instance",
"clone_playlist": "Clone Playlist",
"clone_playlist_success": "Successfully cloned!",
"download_as_txt": "Download as .txt",
@ -103,8 +114,9 @@
"backup_preferences": "Backup preferences",
"restore_preferences": "Restore preferences",
"back_to_home": "Back to home",
"rename_playlist": "Rename",
"new_playlist_name": "New playlist name",
"edit_playlist": "Edit",
"playlist_name": "Playlist name",
"playlist_description": "Playlist description",
"share": "Share",
"with_timecode": "Share with time code",
"piped_link": "Piped link",
@ -112,7 +124,7 @@
"copy_link": "Copy link",
"time_code": "Time code (in seconds)",
"show_chapters": "Chapters",
"store_search_history": "Store Search history",
"store_search_history": "Store Search History",
"hide_watched": "Hide watched videos in the feed",
"documentation": "Documentation",
"status_page": "Status",
@ -120,7 +132,18 @@
"instance_donations": "Instance donations",
"reply_count": "{count} replies",
"no_valid_playlists": "The file doesn't contain valid playlists!",
"with_playlist": "Share with playlist"
"with_playlist": "Share with playlist",
"bookmark_playlist": "Bookmark",
"playlist_bookmarked": "Bookmarked",
"dismiss": "Dismiss",
"show_less": "Show less",
"create_group": "Create group",
"group_name": "Group name",
"cancel": "Cancel",
"okay": "Okay",
"show_search_suggestions": "Show search suggestions",
"delete_automatically": "Delete automatically after",
"generate_qrcode": "Generate QR Code"
},
"comment": {
"pinned_by": "Pinned by {author}",
@ -139,7 +162,9 @@
},
"login": {
"username": "Username",
"password": "Password"
"password": "Password",
"password_confirm": "Confirm password",
"passwords_incorrect": "Passwords don't match!"
},
"video": {
"videos": "Videos",
@ -149,7 +174,13 @@
"ratings_disabled": "Ratings Disabled",
"chapters": "Chapters",
"live": "{0} Live",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "All",
"category": "Category",
"license": "License",
"visibility": "Visibility",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical"
},
"search": {
"did_you_mean": "Did you mean: {0}?",
@ -160,7 +191,8 @@
"music_songs": "YT Music: Songs",
"music_videos": "YT Music: Videos",
"music_albums": "YT Music: Albums",
"music_playlists": "YT Music: Playlists"
"music_playlists": "YT Music: Playlists",
"music_artists": "YT Music: Artists"
},
"subscriptions": {
"subscribed_channels_count": "Subscribed to: {0}"
@ -170,6 +202,12 @@
"page_not_found": "Page not found",
"copied": "Copied!",
"cannot_copy": "Can't copy!",
"local_storage": "This action requires localStorage, are cookies enabled?"
"local_storage": "This action requires localStorage, are cookies enabled?",
"register_no_email_note": "Using an e-mail as username is not recommended. Proceed anyways?",
"next_video_countdown": "Playing next video in {0}s",
"hours": "{amount} hour(s)",
"days": "{amount} day(s)",
"weeks": "{amount} week(s)",
"months": "{amount} month(s)"
}
}

View file

@ -12,7 +12,10 @@
"player": "Ludilo",
"instance": "Nodo",
"channels": "Kanaloj",
"livestreams": "Tujelsendoj"
"livestreams": "Tujelsendoj",
"bookmarks": "Legosignoj",
"channel_groups": "Kanalaroj",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Vidi en {0}"
@ -38,22 +41,21 @@
"light": "Hela",
"autoplay_video": "Aŭtomate Ludi Videon",
"audio_only": "Nur Sono",
"default_quality": "Defaŭlta Kvalito",
"country_selection": "Landa Elekto",
"default_homepage": "Defaŭlta Ĉefpaĝo",
"default_quality": "Implicita Kvalito",
"country_selection": "Lando",
"default_homepage": "Implicita Ĉefpaĝo",
"show_comments": "Montri Komentojn",
"language_selection": "Lingva Elekto",
"language_selection": "Lingvo",
"donations": "Donacoj por programado",
"show_more": "Montri Pli",
"show_more": "Montri pli",
"yes": "Jes",
"no": "Ne",
"show_chapters": "Sekcioj",
"filter": "Filtri",
"search": "Serĉi",
"search": "Serĉi (Ctrl+K)",
"hide_replies": "Kaŝi Respondojn",
"add_to_playlist": "Aldoni al ludlisto",
"delete_playlist": "Forigi Ludliston",
"rename_playlist": "Renomi ludliston",
"download_as_txt": "Elŝuti kiel .txt",
"piped_link": "Piped-ligilo",
"copy_link": "Kopii ligilon",
@ -66,7 +68,6 @@
"remove_from_playlist": "Forigi el ludlisto",
"create_playlist": "Krei Ludliston",
"delete_account": "Forigi Konton",
"new_playlist_name": "Nomo de nova ludlisto",
"reply_count": "{count} respondoj",
"load_more_replies": "Ŝargi pli da Respondoj",
"share": "Konigi",
@ -83,7 +84,7 @@
"auto_play_next_video": "Aŭtomate Ludi sekvan Videon",
"show_recommendations": "Montri Rekomendojn",
"reset_preferences": "Restarigi agordojn",
"instance_selection": "Noda Elekto",
"instance_selection": "Nodo",
"view_ssl_score": "Vidu SSL-Poentaron",
"backup_preferences": "Savkopii agordojn",
"disable_lbry": "Malebligi LBRY-n por Elsendfluo",
@ -91,7 +92,7 @@
"store_search_history": "Konservi Ŝerĉhistorion",
"hide_watched": "Kaŝi viditajn videojn en la fluo",
"minimize_recommendations": "Plejetigi Rekomendojn",
"instance_auth_selection": "Elekto de Aŭtentokontrola Nodo",
"instance_auth_selection": "Aŭtentokontrola nodo",
"restore_preferences": "Restarigi agordojn",
"status_page": "Stato",
"please_select_playlist": "Bonvolu elekti ludliston",
@ -113,14 +114,36 @@
"skip_interaction": "Preterpasi Interagan Memorigon (Aboni)",
"store_watch_history": "Konservi Vidhistorion",
"logout": "Elsaluti el ĉi tiu aparato",
"minimize_description_default": "Defaŭlte Plejetigi Priskribon",
"minimize_recommendations_default": "Defaŭlte Plejetigi Rekomendojn",
"minimize_comments_default": "Defaŭlte Plejetigi Komentojn",
"minimize_description_default": "Implicite Plejetigi Priskribon",
"minimize_recommendations_default": "Implicite Plejetigi Rekomendojn",
"minimize_comments_default": "Implicite Plejetigi Komentojn",
"minimize_comments": "Plejetigi Komentojn",
"show_watch_on_youtube": "Montri «Vidi en Youtube»-butonon",
"minimize_chapters_default": "Defaŭlte plejetigi ĉapitrojn",
"minimize_chapters_default": "Implicite plejetigi ĉapitrojn",
"no_valid_playlists": "La dosiero ne enhavas validajn ludlistojn!",
"with_playlist": "Konigi kun ludlisto"
"with_playlist": "Konigi kun ludlisto",
"playlist_bookmarked": "Legosignita",
"bookmark_playlist": "Legosigno",
"skip_automatically": "Aŭtomate",
"skip_button_only": "Montri preterpasi-butonon",
"min_segment_length": "Minimuma Segmenta Daŭro (en sekundoj)",
"skip_segment": "Preterpasi Segmenton",
"show_less": "Montri malpli",
"dismiss": "Nuligi",
"autoplay_next_countdown": "Implicita retronombrado ĝis sekva video (en sekundoj)",
"group_name": "Nomo de la aro",
"create_group": "Krei aron",
"auto_display_captions": "Aŭtomate montri subtekstojn",
"playlist_name": "Nomo de la ludlisto",
"edit_playlist": "Redakti ludliston",
"okay": "Bone",
"playlist_description": "Priskribo de la ludlisto",
"cancel": "Nuligi",
"show_search_suggestions": "Montri serĉ-sugestojn",
"chapters_layout_mobile": "Aranĝo de ĉapitroj en poŝtelefono",
"delete_automatically": "Aŭtomate forigi post",
"enable_dearrow": "Ebligi DeArrow",
"generate_qrcode": "Generi QR-kodon"
},
"video": {
"chapters": "Sekcioj",
@ -128,9 +151,15 @@
"live": "{0} Realtempe",
"views": "{views} spektoj",
"sponsor_segments": "Sponsoraj Segmentoj",
"watched": "Viditaj",
"watched": "Spektita",
"ratings_disabled": "Taksadoj Malebligitaj",
"shorts": "Mallongaj"
"shorts": "Mallongaj",
"all": "Ĉiuj",
"category": "Kategorio",
"chapters_horizontal": "Horizontala",
"chapters_vertical": "Vertikala",
"license": "Permesilo",
"visibility": "Videbleco"
},
"search": {
"music_albums": "YT Music: Albumoj",
@ -141,18 +170,27 @@
"music_videos": "YT Music: Videoj",
"music_songs": "YT Music: Muzikaĵoj",
"all": "YouTube: Ĉio",
"did_you_mean": "Ĉu vi volis diri «{0}»?"
"did_you_mean": "Ĉu vi volis diri «{0}»?",
"music_artists": "YT Music: Artistoj"
},
"info": {
"copied": "Kopiita!",
"cannot_copy": "Ne povas kopii!",
"preferences_note": "Noto: la agordoj estas konservitaj en la loka memoro de via retumilo. Forigi la datumojn de via retumilo restarigos ilin.",
"page_not_found": "Paĝo ne trovita",
"local_storage": "Ĉi tiu ago postulas localStorage, ĉu kuketoj estas ebligitaj?"
"local_storage": "Ĉi tiu ago postulas localStorage, ĉu kuketoj estas ebligitaj?",
"register_no_email_note": "Uzi retadreson kiel uzantnomon ne estas rekomendita. Ĉu daŭrigi ĉiuokaze?",
"next_video_countdown": "Oni ludos la sekvan videon post {0}s",
"hours": "{amount} horo(j)",
"days": "{amount} tago(j)",
"weeks": "{amount} semajno(j)",
"months": "{amount} monato(j)"
},
"login": {
"username": "Uzantnomo",
"password": "Pasvorto"
"password": "Pasvorto",
"password_confirm": "Konfirmu la pasvorton",
"passwords_incorrect": "Pasvortoj ne kongruas!"
},
"preferences": {
"version": "Versio",

View file

@ -7,7 +7,13 @@
"ratings_disabled": "Valoraciones Desactivadas",
"chapters": "Capítulos",
"live": "{0} Directo",
"shorts": "Cortos"
"shorts": "Cortos",
"all": "Todos",
"category": "Categoría",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical",
"license": "Licencia",
"visibility": "Visibilidad"
},
"preferences": {
"ssl_score": "Puntuación SSL",
@ -37,15 +43,15 @@
"no": "No",
"yes": "Sí",
"show_more": "Mostrar más",
"instance_selection": "Selección de instancias",
"instance_selection": "Instancia",
"enabled_codecs": "Códecs habilitados (múltiples)",
"instances_list": "Lista de instancias",
"language_selection": "Selección de lenguajes",
"language_selection": "Idioma",
"store_watch_history": "Recordar historial de visualización",
"minimize_description_default": "Minimizar la descripción por defecto",
"show_comments": "Mostrar comentarios",
"default_homepage": "Página de inicio predeterminada",
"country_selection": "Selección de países",
"country_selection": "País",
"buffering_goal": "Objetivo de amortiguación (en segundos)",
"default_quality": "Calidad predeterminada",
"audio_only": "Sólo audio",
@ -74,7 +80,7 @@
"subscribe": "Suscribirme - {count}",
"loading": "Cargando…",
"filter": "Filtrar",
"search": "Buscar",
"search": "Buscar (Ctrl+K)",
"view_ssl_score": "Ver la puntuación SSL",
"minimize_recommendations": "Minimizar recomendaciones",
"show_recommendations": "Mostrar recomendaciones",
@ -99,10 +105,8 @@
"logout": "Cerrar sesión en este dispositivo",
"minimize_recommendations_default": "Minimizar Recomendaciones por defecto",
"invalidate_session": "Cerrar sesión en todos los dispositivos",
"instance_auth_selection": "Selección de la Instancia de Autentificación",
"instance_auth_selection": "Instancia de autenticación",
"download_as_txt": "Descargar como .txt",
"rename_playlist": "Cambiar el nombre de la lista de reproducción",
"new_playlist_name": "Nuevo nombre de la lista de reproducción",
"share": "Compartir",
"with_timecode": "Compartir con código de tiempo",
"piped_link": "Enlace de Piped",
@ -115,7 +119,7 @@
"restore_preferences": "Restablecer las preferencias",
"back_to_home": "Volver a la página de inicio",
"show_chapters": "Capítulos",
"store_search_history": "Guardar historial de búsqueda",
"store_search_history": "Guardar el historial de las búsquedas",
"source_code": "Código fuente",
"documentation": "Documentación",
"instance_donations": "Donaciones para instancia",
@ -127,10 +131,32 @@
"show_watch_on_youtube": "Mostrar botón Ver en YouTube",
"minimize_chapters_default": "Minimiza capítulos por defecto",
"no_valid_playlists": "¡El archivo no contiene listas de reproducción válidas!",
"with_playlist": "Compartir con lista de reproducción"
"with_playlist": "Compartir con lista de reproducción",
"playlist_bookmarked": "Marcado",
"bookmark_playlist": "Marcador",
"skip_button_only": "Muestra botón de saltar",
"skip_automatically": "Automáticamente",
"min_segment_length": "Mínima Duración de Segmento (en segundos)",
"skip_segment": "Saltar Segmento",
"show_less": "Mostrar menos",
"autoplay_next_countdown": "Cuenta atrás predeterminada antes del siguiente vídeo (en segundos)",
"dismiss": "Cancelar",
"group_name": "Nombre del grupo",
"create_group": "Crear grupo",
"auto_display_captions": "Mostrar automáticamente subtítulos",
"edit_playlist": "Editar lista de reproducción",
"okay": "Vale",
"playlist_name": "Nombre de la lista de reproducción",
"playlist_description": "Descripción de la lista de reproducción",
"cancel": "Cancelar",
"show_search_suggestions": "Mostrar sugerencias de búsqueda",
"chapters_layout_mobile": "Disposición de capítulos en móvil",
"delete_automatically": "Borrar automáticamente después de",
"enable_dearrow": "Activar DeArrow",
"generate_qrcode": "Generar código QR"
},
"titles": {
"feed": "Fuente web",
"feed": "Contenido",
"subscriptions": "Suscripciones",
"history": "Historial",
"trending": "En tendencias",
@ -142,14 +168,19 @@
"instance": "Instancia",
"player": "Reproductor",
"livestreams": "Directos",
"channels": "Canales"
"channels": "Canales",
"bookmarks": "Marcadores",
"channel_groups": "Grupos de canales",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Ver en {0}"
},
"login": {
"password": "Contraseña",
"username": "Nombre de usuario"
"username": "Nombre de usuario",
"passwords_incorrect": "¡Las contraseñas no coinciden!",
"password_confirm": "Confirma la contraseña"
},
"search": {
"did_you_mean": "¿Quisiste decir {0}?",
@ -160,7 +191,8 @@
"videos": "YouTube: Vídeos",
"channels": "YouTube: Canales",
"playlists": "YouTube: Listas de reproducción",
"music_albums": "YT Music: Álbumes"
"music_albums": "YT Music: Álbumes",
"music_artists": "YT Music: Artistas"
},
"subscriptions": {
"subscribed_channels_count": "Suscrito a: {0}"
@ -170,6 +202,12 @@
"page_not_found": "Página no encontrada",
"copied": "¡Copiado!",
"cannot_copy": "¡No se puede copiar!",
"local_storage": "Esta acción requiere «localStorage», ¿están activadas las «cookies»?"
"local_storage": "Esta acción requiere «localStorage», ¿están activadas las «cookies»?",
"register_no_email_note": "No se recomienda usar un correo electrónico como nombre de usuario. ¿Continuar de todos modos?",
"next_video_countdown": "El próximo vídeo se reproducirá en {0}s",
"hours": "{amount} hora(s)",
"days": "{amount} día(s)",
"weeks": "{amount} semana(s)",
"months": "{amount} mes(es)"
}
}

View file

@ -45,7 +45,7 @@
"import_from_json": "وارد کردن از JSON/CSV",
"export_to_json": "دادن خروجی با فرمت JSON",
"show_description": "نمایش توضیحات ویدئو",
"donations": "کمک های مالی",
"donations": "کمک های مالی برای توسعه",
"clear_history": "تخلیه تاریخچه",
"loop_this_video": "تکرار شدن این ویدئو",
"auto_play_next_video": "پخش خودکار ویدئو بعدی",
@ -57,16 +57,43 @@
"hide_replies": "پنهان کردن پاسخ ها",
"load_more_replies": "بارگذاری پاسخ های بیشتر",
"disable_lbry": "غیرفعال کردن LBRY برای استریم ها",
"enable_lbry_proxy": "فعال کردن پراکسی برای LBRY"
"enable_lbry_proxy": "فعال کردن پراکسی برای LBRY",
"delete_playlist": "حذف لیست پخش",
"show_markers": "نشانگر را روی پخش کننده نشان دهد",
"select_playlist": "انتخاب لیست پخش",
"add_to_playlist": "افزودن به لیست پخش",
"create_playlist": "ایجاد لیست پخش",
"delete_playlist_confirm": "این لیست پخش حذف شود؟",
"delete_account": "حذف اکانت",
"logout": "خروج از این دستگاه",
"delete_playlist_video_confirm": "ویدیو از لیست پخش حذف شود؟",
"autoplay_next_countdown": "پیش‌فرض شمارش‌معکوس پخش ویدیوی بعدی ( به ثانیه )",
"please_select_playlist": "انتخاب لیست پخش",
"minimize_comments_default": "کوچک کردن کامنت‌ها به صورت پیش فرض",
"minimize_comments": "کوچک کردن کامنت (نظرات)",
"remove_from_playlist": "حذف از لیست پخش",
"skip_button_only": "دکمه‌ی گذر را نشان بده",
"skip_automatically": "خودکار",
"skip_segment": "گذر از بخش",
"minimize_recommendations_default": "به طور پیش فرض پیشنهادها را حداقل کنید.",
"min_segment_length": "حداقل طول بخش (به ثانیه)"
},
"titles": {
"history": "سابقه",
"history": "تاریخچه",
"preferences": "تنظیمات",
"feed": "خوراک",
"register": "ثبت نام",
"login": "ورود",
"trending": "محبوب",
"subscriptions": "اشتراک‌ها"
"subscriptions": "اشتراک‌ها",
"account": "حساب کاربری",
"instance": "نمونه",
"playlists": "لیست پخش",
"player": "پخش کننده",
"bookmarks": "نشان‌دارها",
"livestreams": "پخش زنده",
"channels": "کانال‌ها",
"channel_groups": "گروه های کانال"
},
"player": {
"watch_on": "تماشا روی {0}"
@ -86,7 +113,9 @@
"instance_locations": "محل های سرویس",
"has_cdn": "CDN دارد؟",
"ssl_score": "امتیاز SSL",
"instance_name": "نام سرویس"
"instance_name": "نام سرویس",
"version": "نسخه",
"registered_users": "کاربران تایید شده"
},
"comment": {
"pinned_by": "سنجاق شده توسط {author}"

View file

@ -101,7 +101,6 @@
"show_watch_on_youtube": "Näytä Katso YouTubessa -painike",
"different_auth_instance": "Käytä eri instanssia todennukseen",
"download_as_txt": "Lataa .txt-tiedostona",
"rename_playlist": "Nimeä soittolista uudelleen",
"show_chapters": "Luvut",
"minimize_comments": "Minimoi kommentit",
"minimize_comments_default": "Minimoi kommentit oletusarvoisesti",
@ -118,7 +117,6 @@
"hide_watched": "Piilota katsotut videot syötteessä",
"time_code": "Aikakoodi (sekunteina)",
"follow_link": "Avaa linkki",
"new_playlist_name": "Soittolistan uusi nimi",
"invalidate_session": "Kirjaudu ulos kaikista laitteista",
"logout": "Kirjaudu ulos tästä laitteesta",
"backup_preferences": "Varmuuskopiointiasetukset",

View file

@ -12,7 +12,9 @@
"instance": "Instance",
"player": "Lecteur",
"livestreams": "Diffusions en direct",
"channels": "Chaînes"
"channels": "Chaînes",
"bookmarks": "Marque-pages",
"channel_groups": "Groupes de chaînes"
},
"actions": {
"subscribe": "S'abonner - {count}",
@ -66,7 +68,7 @@
"yes": "Oui",
"loading": "Chargement…",
"filter": "Filtrer",
"search": "Rechercher",
"search": "Rechercher (Ctrl+K)",
"view_ssl_score": "Afficher le score SSL",
"clear_history": "Effacer l'historique",
"load_more_replies": "Charger plus de réponses",
@ -102,8 +104,6 @@
"piped_link": "Lien vers Piped",
"follow_link": "Ouvrir le lien",
"time_code": "Horodatage (en secondes)",
"rename_playlist": "Renommer la liste de lecture",
"new_playlist_name": "Nouveau nom de la liste de lecture",
"show_chapters": "Chapitres",
"store_search_history": "Mémoriser l'historique de recherche",
"documentation": "Documentation",
@ -116,7 +116,27 @@
"minimize_comments": "Minimiser les commentaires",
"show_watch_on_youtube": "Afficher le bouton Regarder sur YouTube",
"minimize_chapters_default": "Minimiser les chapitres par défaut",
"no_valid_playlists": "Le fichier ne contient pas de listes de lecture valides !"
"no_valid_playlists": "Le fichier ne contient pas de listes de lecture valides !",
"bookmark_playlist": "Marque-page",
"playlist_bookmarked": "Dans les marque-pages",
"with_playlist": "Partager avec la liste de lecture",
"skip_button_only": "Afficher le bouton de saut",
"skip_automatically": "Automatiquement",
"min_segment_length": "Longueur minimale du segment (en secondes)",
"skip_segment": "Sauter le segment",
"show_less": "Afficher moins",
"okay": "OK",
"edit_playlist": "Éditer la liste de lecture",
"playlist_name": "Nom de la liste de lecture",
"auto_display_captions": "Afficher sous-titres automatiquement",
"dismiss": "Rejeter",
"cancel": "Annuler",
"playlist_description": "Description de la liste de lecture",
"create_group": "Créer un groupe",
"group_name": "Nom du groupe",
"autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)",
"chapters_layout_mobile": "Format des chapitres sur mobile",
"show_search_suggestions": "Afficher les suggestions de recherche"
},
"player": {
"watch_on": "Regarder sur {0}"
@ -129,7 +149,11 @@
"ratings_disabled": "Évaluations désactivées",
"chapters": "Chapitres",
"live": "{0} en direct",
"shorts": "Courtes"
"shorts": "Courtes",
"all": "Tout",
"category": "Catégorie",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical"
},
"preferences": {
"ssl_score": "Score SSL",
@ -172,6 +196,8 @@
"page_not_found": "Page non trouvée",
"copied": "Copié !",
"cannot_copy": "Impossible de copier !",
"local_storage": "Cette action nécessite localStorage, les cookies sont-ils activés ?"
"local_storage": "Cette action nécessite localStorage, les cookies sont-ils activés ?",
"register_no_email_note": "Il n'est pas recommandé d'utiliser une adresse courriel omme nom d'utilisateur. Continuer quand même ?",
"next_video_countdown": "Lecture de la prochaine vidéo dans {0}s"
}
}

View file

@ -12,7 +12,10 @@
"playlists": "רשימות נגינה",
"instance": "עותק",
"livestreams": "שידורים חיים",
"channels": "ערוצים"
"channels": "ערוצים",
"bookmarks": "סימניות",
"channel_groups": "קבוצות ערוץ",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "לצפות ב־{0}"
@ -43,16 +46,16 @@
"audio_only": "שמע בלבד",
"default_quality": "איכות ברירת מחדל",
"buffering_goal": "יעד שמירה למטמון (בשניות)",
"country_selection": "בחירת מדינה",
"country_selection": "מדינה",
"default_homepage": "עמוד הבית כברירת מחדל",
"show_comments": "הצגת תגובות",
"minimize_description_default": "מזעור התגובות כברירת מחדל",
"store_watch_history": "שחזור היסטוריית הצפייה",
"language_selection": "בחירת שפה",
"language_selection": "שפה",
"instances_list": "רשימת עותקים",
"enabled_codecs": "מפענחים פעילים (מגוון)",
"instance_selection": "בחירת עותק",
"show_more": "להציג עוד",
"instance_selection": "עותק",
"show_more": "להציג יותר",
"yes": "כן",
"no": "לא",
"export_to_json": "ייצוא ל־JSON",
@ -77,7 +80,7 @@
"logout": "יציאה מהחשבון במכשיר הזה",
"minimize_recommendations_default": "מזעור המלצות כברירת מחדל",
"invalidate_session": "להוציא את כל המכשירים מהחשבון",
"instance_auth_selection": "בחירת עותק אימות",
"instance_auth_selection": "עותק אימות",
"clone_playlist": "שכפול רשימת נגינה",
"clone_playlist_success": "שוכפל בהצלחה!",
"download_as_txt": "הורדה כ־‎.txt",
@ -99,11 +102,9 @@
"disable_lbry": "השבתת הזרמה עם LBRY",
"enable_lbry_proxy": "הפעלת מתווך ל־LBRY",
"view_ssl_score": "הצגת דירוג SSL",
"search": "חיפוש",
"search": "חיפוש (Ctrl+K)",
"loop_this_video": "ניגון הסרטון בלולאה",
"minimize_recommendations": "מזעור המלצות",
"rename_playlist": "שינוי שם רשימת נגינה",
"new_playlist_name": "שם לרשימת נגינה חדשה",
"show_chapters": "פרקים",
"skip_intro": "דילוג על הפוגה/הנפשת הקדמה",
"skip_outro": "דילוג על כרטיסי סיום/קרדיטים",
@ -120,7 +121,29 @@
"minimize_chapters_default": "מזעור הפרקים כברירת מחדל",
"show_watch_on_youtube": "הצגת כפתור לצפייה ב־YouTube",
"no_valid_playlists": "הקובץ לא מכיל רשימות נגינה תקפות!",
"with_playlist": "שיתוף עם רשימת נגינה"
"with_playlist": "שיתוף עם רשימת נגינה",
"playlist_bookmarked": "נוסף לסימניות",
"bookmark_playlist": "סימנייה",
"skip_button_only": "הצגת כפתור דילוג",
"min_segment_length": "אורך מקטע מזערי (בשניות)",
"skip_segment": "דילוג על מקטע",
"skip_automatically": "אוטומטית",
"show_less": "להציג פחות",
"autoplay_next_countdown": "ספירה לאחור כברירת מחדל עד לסרטון הבא (בשניות)",
"dismiss": "התעלמות",
"create_group": "יצירת קבוצה",
"group_name": "שם הקבוצה",
"auto_display_captions": "הצגת כתוביות אוטומטית",
"cancel": "ביטול",
"edit_playlist": "עריכת רשימת נגינה",
"playlist_name": "שם רשימת הנגינה",
"playlist_description": "תיאור רשימת הנגינה",
"okay": "אישור",
"show_search_suggestions": "הצגת הצעות חיפוש",
"chapters_layout_mobile": "פריסת פרקים בנייד",
"delete_automatically": "למחוק אוטומטית לאחר",
"enable_dearrow": "הפעלת DeArrow",
"generate_qrcode": "יצירת קוד QR"
},
"comment": {
"pinned_by": "ננעץ על ידי {author}",
@ -149,7 +172,13 @@
"ratings_disabled": "הדירוגים מושבתים",
"chapters": "פרקים",
"live": "{0} בשידור חי",
"shorts": "קצרצרים"
"shorts": "קצרצרים",
"all": "הכול",
"category": "קטגוריה",
"chapters_horizontal": "אופקית",
"chapters_vertical": "אנכית",
"visibility": "חשיפה",
"license": "רישיון"
},
"search": {
"did_you_mean": "האם התכוונת לביטוי {0}?",
@ -160,14 +189,21 @@
"music_songs": "YT Music: שירים",
"music_videos": "YT Music: סרטונים",
"music_albums": "YT Music: אלבומים",
"music_playlists": "YT Music: רשימות נגינה"
"music_playlists": "YT Music: רשימות נגינה",
"music_artists": "YT Music: אומנים"
},
"info": {
"preferences_note": "לתשומת לבך: ההעדפות נשמרות באחסון המקומי של הדפדפן שלך. מחיקת נתוני הדפדפן שלך תאפס אותם.",
"page_not_found": "העמוד לא נמצא",
"copied": "הועתק!",
"cannot_copy": "לא ניתן להעתיק!",
"local_storage": "פעולה זו דורשת אחסון מקומי (localStorage), האם עוגיות פעילות?"
"local_storage": "פעולה זו דורשת אחסון מקומי (localStorage), האם עוגיות פעילות?",
"register_no_email_note": "לא מומלץ להשתמש בכתובת דוא״ל כשם משתמש. להמשיך בכל זאת?",
"next_video_countdown": "הסרטון הבא יתנגן בעוד {0} שניות",
"days": "{amount} ימים",
"weeks": "{amount} שבועות",
"months": "{amount} חודשים",
"hours": "{amount} שעות"
},
"subscriptions": {
"subscribed_channels_count": "נרשמת אל: {0}"

View file

@ -9,7 +9,10 @@
"feed": "फ़ीड",
"playlists": "प्लेलिस्ट",
"livestreams": "लाइव स्ट्रीम",
"channels": "चैनल"
"channels": "चैनल",
"player": "चालक",
"account": "खाता",
"instance": "इंस्टैंस"
},
"actions": {
"subscribe": "सदस्यता लें - {count}",
@ -17,7 +20,7 @@
"unsubscribe": "सदस्यता ले ली है - {count}",
"no": "नहीं",
"hide_replies": "जवाब छिपाएं",
"search": "खोजें",
"search": "खोजें (Ctrl+K)",
"loop_this_video": "इस वीडियो को लूप करें",
"loading": "लोड हो रहा है...",
"show_description": "विवरण दिखाएं",
@ -38,17 +41,17 @@
"autoplay_video": "ऑटोप्ले वीडियो",
"audio_only": "सिर्फ़ ध्वनि",
"default_quality": "डिफ़ॉल्ट गुणवत्ता",
"country_selection": "देश चयन",
"country_selection": "देश",
"show_comments": "टिप्पणियाँ दिखाएँ",
"store_watch_history": "स्टोर देखने का इतिहास",
"language_selection": "भाषा चयन",
"language_selection": "भाषा",
"instances_list": "इंस्टेंस सूची",
"instance_selection": "इंस्टेंस चयन",
"instance_selection": "इंस्टेंस",
"show_more": "और दिखाओ",
"export_to_json": "JSON में निर्यात करें",
"import_from_json": "JSON/CSV से आयात करें",
"auto_play_next_video": "अगला वीडियो ऑटोप्ले करें",
"donations": "दान",
"donations": "विकास दान",
"minimize_recommendations": "सिफारिशों को कम करें",
"show_recommendations": "सिफारिशें दिखाएं",
"disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें",
@ -59,19 +62,24 @@
"load_more_replies": "और जवाब लोड करें",
"enabled_codecs": "सक्षम कोडेक्स (एकाधिक)",
"buffering_goal": "बफरिंग गोल (सेकंड में)",
"delete_playlist_confirm": "क्या आप वाकई इस प्लेलिस्ट को हटाना चाहते हैं?",
"delete_playlist_confirm": "प्लेलिस्ट को मिटाना है?",
"add_to_playlist": "प्लेलिस्ट में जोड़ें",
"remove_from_playlist": "प्लेलिस्ट से निकाले",
"delete_playlist_video_confirm": "क्या आप वाकई इस प्लेलिस्ट से इस वीडियो को निकालना चाहेंगे?",
"delete_playlist_video_confirm": "वीडियो को प्लेलिस्ट से निकालना है?",
"create_playlist": "प्लेलिस्ट बनायें",
"select_playlist": "एक प्लेलिस्ट चुनें",
"please_select_playlist": "कृपया एक प्लेलिस्ट चुनें",
"delete_playlist": "प्लेलिस्ट हटाएं"
"delete_playlist": "प्लेलिस्ट हटाएं",
"enable_sponsorblock": "विज्ञापन प्रतिबंध करें",
"default_homepage": "स्वतः निर्धारित मुख्यपृष्ठ",
"sort_by": "वर्गीकरण:",
"skip_automatically": "स्वतः",
"delete_account": "खाता मिटाएँ"
},
"video": {
"views": "{views} बार देखा गया",
"videos": "वीडियो",
"watched": "पहले ही देखा",
"watched": "पहले ही देखा हुआ",
"ratings_disabled": "रेटिंग अक्षम",
"chapters": "चैप्टर",
"live": "{0} लाइव"

View file

@ -4,10 +4,16 @@
"watched": "Gledano",
"views": "{views} gledanja",
"videos": "Videa",
"ratings_disabled": "Ocjenjivanje isključeno",
"ratings_disabled": "Ocjene su isključene",
"chapters": "Poglavlja",
"live": "{0} uživo",
"shorts": "Kratka videa"
"shorts": "Kratka videa",
"all": "Sva",
"category": "Kategorija",
"chapters_horizontal": "Vodoravno",
"chapters_vertical": "Okomito",
"license": "Licenca",
"visibility": "Vidljivost"
},
"preferences": {
"ssl_score": "SSL ocjena",
@ -20,13 +26,13 @@
},
"comment": {
"pinned_by": "Prikvačio korisnik {author}",
"disabled": "Prijenosnik onemogućuje komentare.",
"disabled": "Prijenosnik je isključio komentare.",
"loading": "Učitavanje komentara...",
"user_disabled": "Komentari su isključeni u postavkama."
},
"actions": {
"enable_lbry_proxy": "Uključi proxy za LBRY",
"disable_lbry": "Onemogući LBRY za prijenos",
"disable_lbry": "Isključi LBRY za prijenos",
"minimize_description_default": "Standardno sakrij opis",
"minimize_description": "Sakrij opis",
"show_description": "Prikaži opis",
@ -40,14 +46,14 @@
"no": "Ne",
"yes": "Da",
"show_more": "Prikaži više",
"instance_selection": "Izbor instance",
"enabled_codecs": "Uključeni kodeki (višestruki)",
"instance_selection": "Instanca",
"enabled_codecs": "Uključeni kodeki (moguće je odabrati nekoliko kodeka)",
"instances_list": "Popis instanci",
"language_selection": "Izbor jezika",
"language_selection": "Jezik",
"store_watch_history": "Spremi povijest gledanja",
"show_comments": "Prikaži komentare",
"default_homepage": "Standardna početna stranica",
"country_selection": "Izbor zemlje",
"country_selection": "Zemlja",
"buffering_goal": "Cilj međuspremnika (u sekundama)",
"default_quality": "Standardna kvaliteta",
"audio_only": "Samo zvuk",
@ -69,36 +75,36 @@
"view_subscriptions": "Pogledaj pretplate",
"unsubscribe": "Otkaži pretplatu {count}",
"subscribe": "Pretplati se {count}",
"skip_interaction": "Preskoči podsjetnik za interakciju (zahtijeva pretplatu)",
"skip_interaction": "Preskoči podsjetnik za interakciju (pretplata)",
"skip_outro": "Preskoči odjavnu špicu",
"skip_intro": "Preskoči pauzu i uvodnu animaciju",
"skip_sponsors": "Preskoči sponzore",
"enable_sponsorblock": "Uključi blok sponsora",
"loading": "Učitavanje…",
"filter": "Filtar",
"search": "Pretraga",
"search": "Pretraga (Ctrl+K)",
"view_ssl_score": "Pogledaj SSL ocjenu",
"hide_replies": "Sakrij odgovore",
"load_more_replies": "Prikaži više odgovora",
"clear_history": "Obriši povijest",
"skip_highlight": "Preskoči isticanje",
"skip_filler_tangent": "Preskoči nebitne međudijelove",
"delete_playlist_confirm": "Izbrisati ovaj popis snimaka?",
"remove_from_playlist": "Ukloni iz popisa snimaka",
"create_playlist": "Stvori popis snimaka",
"delete_playlist": "Izbriši popis snimaka",
"add_to_playlist": "Dodaj u popis snimaka",
"select_playlist": "Odaberi popis snimaka",
"please_select_playlist": "Odaberi popis snimaka",
"delete_playlist_video_confirm": "Ukloniti video iz popisa snimaka?",
"show_markers": "Prikaži oznake na Pokretaču",
"skip_filler_tangent": "Preskoči prazne umetke",
"delete_playlist_confirm": "Izbrisati ovu playlistu?",
"remove_from_playlist": "Ukloni iz playliste",
"create_playlist": "Stvori playlistu",
"delete_playlist": "Izbriši playlistu",
"add_to_playlist": "Dodaj u playlistu",
"select_playlist": "Odaberi playlistu",
"please_select_playlist": "Odaberi playlistu",
"delete_playlist_video_confirm": "Ukloniti video iz playliste?",
"show_markers": "Prikaži oznake na playeru",
"delete_account": "Izbriši račun",
"logout": "Odjavi se s ovog uređaja",
"minimize_recommendations_default": "Standardno sakrij preporuke",
"invalidate_session": "Odjavi sve uređaje",
"different_auth_instance": "Koristi drugu instancu za autentifikaciju",
"instance_auth_selection": "Odabir instance autentifikacije",
"clone_playlist": "Dupliciraj popis snimaka",
"instance_auth_selection": "Instanca autentifikacije",
"clone_playlist": "Dupliciraj playlistu",
"clone_playlist_success": "Dupliciranje uspjelo!",
"download_as_txt": "Preuzmi kao .txt",
"reset_preferences": "Resetiraj postavke",
@ -111,14 +117,12 @@
"confirm_reset_preferences": "Stvarno želiš resetirati tvoje postavke?",
"backup_preferences": "Spremi sigurnosnu kopiju postavki",
"with_timecode": "Dijeli s vremenskim kodom",
"rename_playlist": "Preimenuj popis snimaka",
"new_playlist_name": "Ime novog popisa snimaka",
"share": "Dijeli",
"show_chapters": "Poglavlja",
"documentation": "Dokumentacija",
"source_code": "Izvorni kod",
"instance_donations": "Donacije instance",
"store_search_history": "Spremi povijest pretrage",
"store_search_history": "Povijest pretrage trgovine",
"hide_watched": "Sakrij gledana videa u novostima",
"status_page": "Stanje",
"reply_count": "{count} odgovora",
@ -126,8 +130,30 @@
"minimize_comments": "Sakrij komentare",
"show_watch_on_youtube": "Prikaži gumb „Gledaj na YouTubeu”",
"minimize_chapters_default": "Standardno sakrij poglavlja",
"no_valid_playlists": "Datoteka ne sadrži ispravne popise snimaka!",
"with_playlist": "Dijeli s popisom snimaka"
"no_valid_playlists": "Datoteka ne sadrži ispravne playliste!",
"with_playlist": "Dijeli s playlistom",
"playlist_bookmarked": "Zabilježeno",
"bookmark_playlist": "Zabilježi",
"skip_button_only": "Prikaži gumb za preskakanje",
"skip_automatically": "Automatski",
"skip_segment": "Preskoči segment",
"min_segment_length": "Najmanja duljina segmenta (u sekundama)",
"show_less": "Prikaži manje",
"autoplay_next_countdown": "Standardno odbrojavanje do sljedećeg videa (u sekundama)",
"dismiss": "Odbaci",
"create_group": "Stvori grupu",
"group_name": "Ime grupe",
"auto_display_captions": "Automatski prikaži titlove",
"cancel": "Odustani",
"okay": "U redu",
"edit_playlist": "Uredi playlistu",
"playlist_name": "Ime playliste",
"playlist_description": "Opis playliste",
"chapters_layout_mobile": "Raspored poglavlja na mobilnim uređajima",
"show_search_suggestions": "Prikaži prijedloge pretrage",
"delete_automatically": "Automatski izbriši nakon",
"enable_dearrow": "Aktiviraj DeArrow",
"generate_qrcode": "Generiraj QR kod"
},
"player": {
"watch_on": "Gledaj na {0}"
@ -140,12 +166,15 @@
"register": "Registracija",
"login": "Prijava",
"trending": "U trendu",
"playlists": "Popisi snimaka",
"playlists": "Playliste",
"account": "Račun",
"instance": "Instanca",
"player": "Pokretač",
"player": "Player",
"channels": "Kanali",
"livestreams": "Prijenosi uživo"
"livestreams": "Prijenosi uživo",
"bookmarks": "Zabilješke",
"channel_groups": "Grupe kanala",
"dearrow": "DeArrow"
},
"login": {
"password": "Lozinka",
@ -156,14 +185,15 @@
"all": "YouTube: Sve",
"videos": "YouTube: Videa",
"channels": "YouTube: Kanali",
"playlists": "YouTube: Popisi snimaka",
"playlists": "YouTube: Playliste",
"music_songs": "YT Music: Pjesme",
"music_videos": "YT Music: Videa",
"music_albums": "YT Music: Albumi",
"music_playlists": "YT Music: Popisi snimaka"
"music_playlists": "YT Music: Playliste",
"music_artists": "YT Music: Izvođači"
},
"subscriptions": {
"subscribed_channels_count": "Pretplata na: {0}"
"subscribed_channels_count": "Broj pretplata: {0}"
},
"information": {
"preferences_note": "Napomena: postavke se spremaju u lokalno spremište preglednika. Brisanje podataka preglednika resetira postavke."
@ -173,6 +203,12 @@
"page_not_found": "Stranica nije pronađena",
"copied": "Kopirano!",
"cannot_copy": "Nije moguće kopirati!",
"local_storage": "Ova radnja zahtijeva lokalno spremište. Jesu li kolačići uključeni?"
"local_storage": "Ova radnja zahtijeva lokalno spremište. Jesu li kolačići uključeni?",
"register_no_email_note": "Korištenje e-mail adrese kao korisničkog imena se ne preporučuje. Svejedno nastaviti?",
"next_video_countdown": "Reprodukcija sljedećeg videa za {0} s",
"hours": "{amount} h",
"days": "{amount} dan(a)",
"weeks": "{amount} tj",
"months": "{amount} mj"
}
}

View file

@ -8,7 +8,11 @@
"subscriptions": "Feliratkozások",
"playlists": "Lejátszási listák",
"trending": "Felkapott",
"account": "Fiók"
"account": "Fiók",
"player": "Lejátszó",
"instance": "Szerver",
"livestreams": "Élő adások",
"channels": "Csatornák"
},
"actions": {
"subscribe": "Feliratkozás - {count}",
@ -51,7 +55,7 @@
"instance_selection": "Példány kiválasztása",
"skip_filler_tangent": "Témától eltérő töltelék/viccek",
"loop_this_video": "Videó ismétlése",
"donations": "Támogatások",
"donations": "Fejlesztési támogatások",
"minimize_description": "Leírás minimalizálása",
"show_recommendations": "Javaslatok megjelenítése",
"enable_lbry_proxy": "Proxy engedélyezése a LBRY számára",
@ -85,7 +89,33 @@
"different_auth_instance": "Másik példány használata a hitelesítéshez",
"instance_auth_selection": "Autentikációs példány kiválasztása",
"clone_playlist": "Lejátszási lista klónozása",
"clone_playlist_success": "Sikeresen klónozva!"
"clone_playlist_success": "Sikeresen klónozva!",
"reset_preferences": "Alaphelyzetbe állítás",
"restore_preferences": "Beállítások betöltése fájlból",
"instance_donations": "Szerver adományozások",
"piped_link": "Piped link",
"time_code": "Idő kód (másodpercekben)",
"show_chapters": "Fejezetek",
"download_as_txt": "Letöltés szövegdokumentumként",
"source_code": "A szoftver kódja",
"reply_count": "{count} hozzászólások",
"documentation": "Dokumentáció",
"minimize_chapters_default": "Mindig tüntesd el a fejezeteket",
"hide_watched": "Ne mutassa a látott videókat a felíratkozásoknál",
"show_watch_on_youtube": "Mutasd a \"Lejátszás Youtube-on\" gombot",
"confirm_reset_preferences": "Biztos alaphelyzetbe állítod?",
"backup_preferences": "Beállítások mentése",
"share": "Megosztás",
"with_timecode": "Megosztás & videó kezdés ettől a ponttól",
"store_search_history": "Mentse a keresési előzményeket",
"follow_link": "Követések link",
"copy_link": "Link másolása",
"status_page": "Státusz",
"no_valid_playlists": "Nincs a fájlban egy valós lejátszási lista se!",
"with_playlist": "Megosztás lejátszási listával",
"minimize_comments_default": "Mindig tüntesd el a kommenteket",
"minimize_comments": "Kommentek eltüntetése",
"back_to_home": "Vissza a főoldalra"
},
"video": {
"ratings_disabled": "Értékelések Letiltva",
@ -129,5 +159,15 @@
"disabled": "A hozzászólásokat a feltöltő letiltotta.",
"user_disabled": "A beállításoknál a megjegyzések le vannak tiltva.",
"loading": "Kommentek betöltése..."
},
"subscriptions": {
"subscribed_channels_count": "Feliratkozva: {0} csatornára"
},
"info": {
"preferences_note": "Figyelem: A beállításaid a böngésződ tárhelyére vannak mentve. Ha törlöd őket el fognak tűnni a beállításaid.",
"copied": "Másolva!",
"local_storage": "Ennek a beállításnak szüksége van a \"lokális tárhely\" funkcióra, be vannak a sütik kapcsolva?",
"cannot_copy": "Nem lehet másolni!",
"page_not_found": "Oldnal nem található"
}
}

190
src/locales/hy.json Normal file
View file

@ -0,0 +1,190 @@
{
"actions": {
"skip_automatically": "Ավտոմատվաց",
"subscribe": "Բաժանորդ - {count}",
"uses_api_from": "Օգտագործում է API -ից ",
"enable_sponsorblock": "Միացնել հովանավորների արգելափակումը",
"skip_intro": "Բաց թողնել ընդմիջում/ներածական անիմացիա",
"skip_outro": "Բաց թողնել վերջնական քարտերը/վարկերը",
"skip_preview": "Բաց թողնել նախադիտումը/վերանայումը",
"skip_interaction": "Բաց թողնել փոխազդեցության հիշեցումը (բաժանորդագրվել)",
"skip_self_promo": "Բաց թողնել չվճարված/ինքնագովազդ",
"skip_highlight": "Բաց թողնել ընդգծումը",
"skip_filler_tangent": "Բաց թողնել ավելորդ շոշափող",
"show_markers": "Ցույց տալ մարկերները նվագարկչի վրա",
"skip_segment": "Բաց թողնել հատվածը",
"theme": "Թեմա",
"auto": "Ավտոմատ",
"dark": "Մութ",
"audio_only": "Միայն աուդիո",
"buffering_goal": "Բուֆերային նպատակ (վայրկյաններով)",
"country_selection": "Երկրի ընտրություն",
"default_homepage": "Կանխադրված գլխավոր էջ",
"minimize_comments_default": "Նվազացնել մեկնաբանությունները",
"channel_name_asc": "Ալիքի անունը (A-Z)",
"unsubscribe": "Չեղարկել",
"view_subscriptions": "Դիտել բաժանորդագրությունները",
"least_recent": "Նվազագույնը վերջին",
"sort_by": "Դասավորել ըստ:",
"most_recent": "Ամենավերջին",
"back": "Ետ",
"channel_name_desc": "Ալիքի անունը (Z-A)",
"autoplay_video": "Տեսանյութի ավտոմատ նվագարկում",
"autoplay_next_countdown": "Կանխադրված հետհաշվարկ մինչև հաջորդ տեսանյութը (վայրկյանների ընթացքում)",
"skip_button_only": "Ցույց տալ բաց թողնել կոճակը",
"skip_sponsors": "Բաց թողնել հովանավորներին",
"min_segment_length": "Սեգմենտի նվազագույն երկարությունը (վայրկյաններով)",
"skip_non_music": "Բաց թողնել երաժշտությունը. ոչ երաժշտական բաժին",
"default_quality": "Կանխադրված որակ",
"light": "Լույս",
"store_watch_history": "Խանութի դիտումների պատմությունը",
"language_selection": "Լեզվի ընտրություն",
"instances_list": "Դեպքերի ցուցակ",
"minimize_description_default": "Նվազեցնել նկարագրությունը",
"enabled_codecs": "Միացված կոդեկներ (բազմաթիվ)",
"show_more": "Ցույց տալ ավելին",
"yes": "Այո՛",
"no": "Ոչ",
"loop_this_video": "Կրկնել այս տեսանյութը",
"auto_play_next_video": "Ավտոմատ նվագարկել հաջորդ տեսանյութը",
"donations": "Զարգացման նվիրատվություններ",
"show_comments": "Ցույց տալ մեկնաբանությունները",
"minimize_description": "Նվազեգնել նկարագրությունը",
"show_description": "Ցույց տալ նկարագրությունը",
"show_recommendations": "Նվազեցնել առաջարկությունները",
"disable_lbry": "Անջատել LBRY-ն եթերի համար",
"enable_lbry_proxy": "Միացնել պռոքսի LBRY-ի համար",
"view_ssl_score": "Դիտեք SSL միավորը",
"search": "Որոնում (Ctrl+K)",
"hide_replies": "Թաքցնել պատասխանները",
"add_to_playlist": "Ավելացնել տեսացանկին",
"remove_from_playlist": "Հեռացնել տեսացանկից",
"delete_playlist_video_confirm": "Հեռացնե՞լ տեսանյութը տեսացանկից:",
"create_playlist": "Ստեղծել տեսացանկ",
"delete_playlist": "Ջնջել տեսացանկը",
"delete_playlist_confirm": "Ջնջե՞լ այս տեսացանկը:",
"please_select_playlist": "Խնդրում ենք ընտրել տեսացանկ",
"delete_account": "Հաշիվը ջնջել",
"logout": "Դուրս գալ այս սարքից",
"minimize_chapters_default": "Նվազեցնել գլուխները",
"show_watch_on_youtube": "Ցույց տալ <<դիտել YouTube-ում >> կոճակը",
"invalidate_session": "Դուրս գալ բոլոր սարքերից",
"instance_auth_selection": "Նույնականացման օրինակի ընտրություն",
"clone_playlist_success": "Հաջողությամբ կլոնավորվեց:",
"download_as_txt": "Ներբեռնեք որպես .txt",
"reset_preferences": "Վերակայել նախապատվությունները",
"confirm_reset_preferences": "Իսկապե՞ս ուզում եք վերակայել ձեր նախապատվությունները:",
"backup_preferences": "Պահուստային նախապատվություններ",
"restore_preferences": "Վերականգնել նախապատվությունները",
"follow_link": "Հետևել հղմանը",
"instance_donations": "Օրինակների նվիրատվություններ",
"reply_count": "{count} պատասխան",
"no_valid_playlists": "Ֆայլը վավեր տեսացանկկեր չի պարունակում:",
"with_playlist": "Կիսվեք տեսացանկով",
"bookmark_playlist": "Էջանիշ",
"playlist_bookmarked": "Էջանշված",
"show_less": "Ցույց տալ ավելի քիչ",
"create_group": "Ստեղծել խումբ",
"minimize_recommendations": "Նվազեցնել առաջարկությունները",
"filter": "Զտել",
"instance_selection": "Օրինակի ընտրություն",
"import_from_json": "Ներմուծում JSON/CSV-ից",
"export_to_json": "Արտահանել JSON",
"minimize_comments": "Նվազագույնի հասցնել մեկնաբանությունները",
"clear_history": "Մաքրել պատմություն",
"loading": "Բեռնվում է...",
"with_timecode": "Կիսվեք ժամանակի կոդով",
"load_more_replies": "Բեռնել ավելի շատ պատասխաններ",
"select_playlist": "Ընտրեք տեսացանկ",
"minimize_recommendations_default": "Նվազեցնել առաջարկությունները",
"clone_playlist": "Կլոնավորել տեսացանկը",
"back_to_home": "Վերադարձ դեպի հիմնական էջ",
"different_auth_instance": "Նույնականացման համար օգտագործեք այլ օրինակ",
"share": "Կիսվել",
"copy_link": "Պատճենել հղումը",
"dismiss": "Հեռացնել",
"piped_link": "Piped հղում",
"time_code": "Ժամանակի կոդը (վայրկյաններով)",
"store_search_history": "Խանութի որոնման պատմություն",
"show_chapters": "Գլուխներ",
"hide_watched": "Թաքցնել դիտված տեսանյութերը լրահոսում",
"status_page": "Կարգավիճակ",
"documentation": "Փաստաթղթեր",
"source_code": "Աղբյուրի կոդը",
"group_name": "Խմբի անվանումը"
},
"titles": {
"trending": "Թրենդային",
"login": "Մտնել",
"register": "Գրանցվել",
"feed": "Սկիզբ",
"preferences": "Նախապատվություններ",
"history": "Պատմություն",
"subscriptions": "Հետևում եմ",
"playlists": "Տեսացանկեր",
"account": "Հաշիվ",
"instance": "Օրինակ",
"player": "նվագարկիչ",
"livestreams": "Ուղիղ Եթերներ",
"channels": "Ալիքներ",
"bookmarks": "Էջանիշեր",
"channel_groups": "Ալիքի խմբեր"
},
"player": {
"watch_on": "Դիտեք {0}-ով"
},
"preferences": {
"registered_users": "Գրանցված օգտվողներ",
"version": "Տարբերակ",
"up_to_date": "Մինչ օրս",
"ssl_score": "SSL միավոր",
"instance_locations": "Օրինակների տեղադրություններ",
"instance_name": "Օրինակի անվանումը",
"has_cdn": "Ունի CDN:"
},
"login": {
"username": "Օգտագործողի անունը",
"password": "Գաղտնաբառ"
},
"video": {
"videos": "Տեսանյութեր",
"views": "{views} դիտում",
"watched": "Դիտվաց",
"sponsor_segments": "Հովանավորների հատվածներ",
"ratings_disabled": "Վարկանիշներն անջատված են",
"chapters": "Գլուխներ",
"live": "{0} Եթեր",
"shorts": "Shorts",
"all": "Բոլորը",
"category": "Կարգ"
},
"search": {
"did_you_mean": "Դուք նկատի ունեիք՝ {0}:",
"all": "YouTube: Բոլորը",
"videos": "YouTube: Տեսանյութեր",
"channels": "YouTube: Ալիքներ",
"playlists": "YouTube՝ տեսացանկ",
"music_songs": "YT Երաժշտություն. երգեր",
"music_videos": "YT Երաժշտություն. Տեսանյութեր",
"music_albums": "YT Երաժշտություն. Ալբոմներ",
"music_playlists": "YT Երաժշտություն. տեսացանկ"
},
"subscriptions": {
"subscribed_channels_count": "Բաժանորդագրված է՝ {0}"
},
"comment": {
"loading": "Մեկնաբանությունների բեռնում...",
"pinned_by": "Ամրացված է {author}-ի կողմից",
"disabled": "Մեկնաբանություններն անջատված են վերբեռնողի կողմից:",
"user_disabled": "Մեկնաբանություններն անջատված են կարգավորումներում:"
},
"info": {
"preferences_note": "Նշում. նախապատվությունները պահվում են ձեր բրաուզերի տեղական պահեստում: Ձեր բրաուզերի տվյալները ջնջելով դրանք կվերակայվեն:",
"page_not_found": "Էջը չի գտնվել",
"copied": "Պատճենվել է",
"cannot_copy": "Հնարավոր չէ պատճենել:",
"local_storage": "Այս գործողության համար պահանջվում է տեղական պահեստ, քուքի ֆայլերը միացվա՞ծ են:",
"register_no_email_note": "Էլեկտրոնային փոստի օգտագործումը որպես օգտվողի անուն խորհուրդ չի տրվում: Շարունակե՞լ, այնուամենայնիվ:",
"next_video_countdown": "Հաջորդ տեսանյութը նվագարկվում է {0} վրկ-ում"
}
}

View file

@ -12,7 +12,10 @@
"account": "Akun",
"player": "Pemain",
"livestreams": "Siaran Langsung",
"channels": "Saluran"
"channels": "Saluran",
"bookmarks": "Markah",
"channel_groups": "Grup saluran",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Tonton di {0}"
@ -41,14 +44,14 @@
"audio_only": "Audio Saja",
"default_quality": "Kualitas Bawaan",
"buffering_goal": "Tujuan Buffering (dalam detik)",
"country_selection": "Pemilihan Negara",
"country_selection": "Negara",
"default_homepage": "Halaman Beranda Bawaan",
"show_comments": "Tampilkan Komentar",
"minimize_description_default": "Kecilkan Deskripsi secara default",
"language_selection": "Pemilihan Bahasa",
"language_selection": "Bahasa",
"instances_list": "Daftar Instansi",
"enabled_codecs": "Kodek yang Diaktifkan (Beberapa)",
"instance_selection": "Pemilihan Instansi",
"instance_selection": "Instansi",
"show_more": "Tampilkan Lebih Banyak",
"yes": "Iya",
"no": "Tidak",
@ -63,7 +66,7 @@
"disable_lbry": "Nonaktifkan LBRY untuk Streaming",
"enable_lbry_proxy": "Aktifkan Proksi untuk LBRY",
"view_ssl_score": "Tampilkan Skor SSL",
"search": "Telusuri",
"search": "Telusuri (Ctrl+K)",
"filter": "Saring",
"loading": "Memuat...",
"clear_history": "Hapus Riwayat",
@ -89,7 +92,7 @@
"logout": "Keluar dari perangkat ini",
"minimize_recommendations_default": "Kecilkan Rekomendasi secara bawaan",
"invalidate_session": "Keluarkan semua perangkat",
"instance_auth_selection": "Pemilihan Instansi Otentikasi",
"instance_auth_selection": "Instance Otentikasi",
"different_auth_instance": "Gunakan instansi lain untuk otentikasi",
"clone_playlist_success": "Berhasil disalin!",
"clone_playlist": "Salin Daftar Putar",
@ -98,8 +101,6 @@
"restore_preferences": "Pulihkan preferensi",
"confirm_reset_preferences": "Apakah Anda yakin ingin mengatur ulang preferensi Anda?",
"backup_preferences": "Cadangkan preferensi",
"rename_playlist": "Ubah nama daftar putar",
"new_playlist_name": "Nama daftar putar baru",
"share": "Bagikan",
"with_timecode": "Bagikan dengan kode waktu",
"piped_link": "Tautan Piped",
@ -120,7 +121,29 @@
"show_watch_on_youtube": "Tampilkan tombol Tonton di YouTube",
"minimize_chapters_default": "Kecilkan Bab secara bawaan",
"no_valid_playlists": "Berkas ini tidak berisi daftar putar yang valid!",
"with_playlist": "Bagikan dengan daftar putar"
"with_playlist": "Bagikan dengan daftar putar",
"playlist_bookmarked": "Dimarkahi",
"bookmark_playlist": "Markahi",
"skip_button_only": "Tampilkan tombol lewati",
"skip_automatically": "Secara otomatis",
"min_segment_length": "Panjang Segmen Minimum (dalam detik)",
"skip_segment": "Lewati Segmen",
"show_less": "Tampilkan lebih sedikit",
"autoplay_next_countdown": "Hitungan mundur bawaan sebelum video berikutnya (dalam detik)",
"dismiss": "Abaikan",
"create_group": "Buat grup",
"group_name": "Nama grup",
"auto_display_captions": "Tampilkan Takarir Secara Otomatis",
"cancel": "Batal",
"playlist_description": "Deskripsi daftar putar",
"edit_playlist": "Sunting daftar putar",
"playlist_name": "Nama daftar putar",
"okay": "Oke",
"show_search_suggestions": "Tampilkan saran pencarian",
"chapters_layout_mobile": "Tata Letak Bab di Ponsel",
"delete_automatically": "Hapus secara otomatis setelah",
"enable_dearrow": "Aktifkan DeArrow",
"generate_qrcode": "Buat Kode QR"
},
"comment": {
"pinned_by": "Dipasangi pin oleh {author}",
@ -139,7 +162,9 @@
},
"login": {
"username": "Nama Pengguna",
"password": "Kata Sandi"
"password": "Kata Sandi",
"password_confirm": "Konfirmasi kata sandi",
"passwords_incorrect": "Kata sandi tidak cocok!"
},
"video": {
"videos": "Video",
@ -149,7 +174,13 @@
"ratings_disabled": "Penilaian Dinonaktifkan",
"chapters": "Bagian",
"live": "{0} Langsung",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Semua",
"category": "Kategori",
"chapters_horizontal": "Horisontal",
"chapters_vertical": "Vertikal",
"license": "Lisensi",
"visibility": "Visibilitas"
},
"search": {
"did_you_mean": "Apakah Anda bermaksud: {0}?",
@ -160,7 +191,8 @@
"music_videos": "YT Music: Video",
"music_albums": "YT Music: Album",
"music_playlists": "YT Music: Daftar Putar",
"all": "YouTube: Semua"
"all": "YouTube: Semua",
"music_artists": "YT Music: Artis"
},
"subscriptions": {
"subscribed_channels_count": "Berlangganan ke: {0}"
@ -173,6 +205,12 @@
"preferences_note": "Catatan: preferensi disimpan dalam penyimpanan lokal peramban Anda. Menghapus data peramban Anda akan mengatur ulang.",
"copied": "Disalin!",
"cannot_copy": "Tidak dapat menyalin!",
"local_storage": "Tindakan ini membutuhkan localStorage, apakah kuki diaktifkan?"
"local_storage": "Tindakan ini membutuhkan localStorage, apakah kuki diaktifkan?",
"register_no_email_note": "Menggunakan surel sebagai nama pengguna tidak disarankan. Lanjut?",
"next_video_countdown": "Memutar video berikutnya dalam {0} detik",
"weeks": "{amount} minggu",
"hours": "{amount} jam",
"days": "{amount} hari",
"months": "{amount} bulan"
}
}

View file

@ -12,7 +12,9 @@
"account": "Reikningur",
"instance": "Tilvik",
"livestreams": "Útsendingar í beinni",
"channels": "Rásir"
"channels": "Rásir",
"bookmarks": "Bókamerki",
"channel_groups": "Rásarhópar"
},
"actions": {
"sort_by": "Raða eftir:",
@ -94,8 +96,6 @@
"instance_donations": "Framlög til netþjóns",
"status_page": "Staða",
"source_code": "Frumkóði",
"rename_playlist": "Endurnefna spilunarlista",
"new_playlist_name": "Nýtt heiti spilunarlista",
"share": "Deila",
"with_timecode": "Deilа með tímakóða",
"piped_link": "Hlekkur Piped",

View file

@ -1,12 +1,12 @@
{
"actions": {
"instances_list": "Elenco delle istanze",
"language_selection": "Selezione della lingua",
"language_selection": "Lingua",
"store_watch_history": "Conserva la cronologia di visualizzazione",
"minimize_description_default": "Minimizza la descrizione per impostazione predefinita",
"show_comments": "Mostra i commenti",
"default_homepage": "Pagina iniziale predefinita",
"country_selection": "Selezione del paese",
"country_selection": "Paese",
"buffering_goal": "Obiettivo di buffering (in secondi)",
"default_quality": "Qualità predefinita",
"audio_only": "Solo audio",
@ -37,7 +37,7 @@
"enable_lbry_proxy": "Abilita il proxy per LBRY",
"disable_lbry": "Disabilita LBRY per lo streaming",
"show_description": "Mostra la descrizione",
"minimize_description": "Minimizza la descrizione per impostazione predefinita",
"minimize_description": "Minimizza descrizione",
"minimize_recommendations": "Minimizza consigli",
"show_recommendations": "Mostra consigli",
"donations": "Donazioni per lo sviluppo",
@ -48,10 +48,10 @@
"no": "No",
"yes": "Sì",
"show_more": "Mostra di più",
"instance_selection": "Selezione dell'istanza",
"instance_selection": "Istanza",
"loading": "Caricamento…",
"filter": "Filtra",
"search": "Cerca",
"search": "Cerca (Ctrl+K)",
"view_ssl_score": "Visualizza il punteggio SSL",
"clear_history": "Cancella la cronologia",
"load_more_replies": "Carica più risposte",
@ -72,7 +72,7 @@
"minimize_recommendations_default": "Riduci al minimo le raccomandazioni per impostazione predefinita",
"different_auth_instance": "Usa un'istanza diversa per l'autenticazione",
"invalidate_session": "Disconnetti su tutti i dispositivi",
"instance_auth_selection": "Selezione dell'istanza di autenticazione",
"instance_auth_selection": "Istanza di autenticazione",
"clone_playlist_success": "Clonato con successo!",
"clone_playlist": "Clona la playlist",
"download_as_txt": "Scarica come .txt",
@ -87,8 +87,6 @@
"time_code": "Tempo (in secondi)",
"follow_link": "Apri il collegamento",
"with_timecode": "Condividi con marca temporale",
"new_playlist_name": "Nuovo nome dalla playlist",
"rename_playlist": "Rinomina la playlist",
"show_chapters": "Capitoli",
"store_search_history": "Memorizza la cronologia delle ricerche",
"status_page": "Stato",
@ -101,7 +99,25 @@
"minimize_comments_default": "Minimizza i commenti per impostazione predefinita",
"minimize_comments": "Minimizza i commenti",
"minimize_chapters_default": "Minimizza i capitoli per impostazione predefinita",
"no_valid_playlists": "Il file non contiene playlist valide!"
"no_valid_playlists": "Il file non contiene playlist valide!",
"bookmark_playlist": "Segnalibro",
"with_playlist": "Condividi con la playlist",
"playlist_bookmarked": "Nei segnalibri",
"min_segment_length": "Lunghezza minima del segmento (in secondi)",
"skip_automatically": "Automaticamente",
"skip_button_only": "Mostra pulsante di salto",
"skip_segment": "Salta segmento",
"show_less": "Mostra meno",
"autoplay_next_countdown": "Conto alla rovescia predefinito prima del video successivo (in secondi)",
"cancel": "Annulla",
"okay": "Va bene",
"edit_playlist": "Modifica playlist",
"playlist_name": "Nome playilist",
"playlist_description": "Descrizione playlist",
"group_name": "Nome gruppo",
"create_group": "Crea gruppo",
"show_search_suggestions": "Mostra suggerimenti di ricerca",
"dismiss": "Chiudi"
},
"player": {
"watch_on": "Guarda su {0}"
@ -119,7 +135,8 @@
"instance": "Istanza",
"player": "Riproduttore",
"livestreams": "Streaming live",
"channels": "Canali"
"channels": "Canali",
"bookmarks": "Segnalibri"
},
"video": {
"sponsor_segments": "Segmenti sponsor",
@ -129,7 +146,11 @@
"ratings_disabled": "Valutazioni disabilitate",
"live": "{0} Diretta",
"chapters": "Capitoli",
"shorts": "Short"
"shorts": "Short",
"all": "Tutti",
"category": "Categoria",
"chapters_horizontal": "Orizzontale",
"chapters_vertical": "Verticale"
},
"preferences": {
"ssl_score": "Valutazione SSL",
@ -159,7 +180,8 @@
"channels": "YouTube: Canali",
"music_songs": "YT Music: Canzoni",
"all": "YouTube: Tutto",
"videos": "YouTube: Video"
"videos": "YouTube: Video",
"music_artists": "YT Music: Artisti"
},
"subscriptions": {
"subscribed_channels_count": "Abbonato a: {0}"
@ -172,6 +194,8 @@
"preferences_note": "Nota: le preferenze sono salvate nella memoria locale del tuo browser. L'eliminazione dei dati del tuo browser le ripristinerà.",
"copied": "Copiato!",
"cannot_copy": "Impossibile copiare!",
"local_storage": "Questa azione richiede localStorage, i cookie sono abilitati?"
"local_storage": "Questa azione richiede localStorage, i cookie sono abilitati?",
"register_no_email_note": "L'utilizzo di un indirizzo e-mail come nome utente è sconsigliato. Continuare comunque?",
"next_video_countdown": "Riproduzione prossimo video tra {0}s"
}
}

View file

@ -12,10 +12,13 @@
"player": "プレイヤー",
"instance": "インスタンス",
"channels": "チャンネル",
"livestreams": "ライブ配信"
"livestreams": "ライブ配信",
"bookmarks": "ブックマーク",
"channel_groups": "グループ",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "{0}で見る"
"watch_on": "{0}で視聴"
},
"actions": {
"subscribe": "チャンネル登録 - {count}",
@ -27,32 +30,32 @@
"channel_name_asc": "チャンネル名 (AからZ)",
"channel_name_desc": "チャンネル名 (ZからA)",
"back": "戻る",
"uses_api_from": "API使用元 ",
"enable_sponsorblock": "SponsorBlockを有効化",
"uses_api_from": "API 提供元 ",
"enable_sponsorblock": "SponsorBlock を使用",
"skip_sponsors": "広告をスキップ",
"skip_intro": "インターミッション/イントロアニメーションをスキップする",
"skip_outro": "エンドカード/クレジットをスキップする",
"skip_preview": "プレビュー/要約をスキップ",
"skip_interaction": "インタラクションリマインダーをスキップする (チャンネル登録)",
"skip_self_promo": "無償/自己プロモーションをスキップ",
"skip_intro": "合間/導入アニメをスキップ",
"skip_outro": "終了シーン/クレジットをスキップ",
"skip_preview": "予告/あらすじをスキップ",
"skip_interaction": "登録など操作を頼む自己宣伝をスキップ",
"skip_self_promo": "無償または自己の宣伝をスキップ",
"skip_non_music": "音楽: 非音楽部分をスキップ",
"theme": "テーマ",
"auto": "自動",
"dark": "ダーク",
"light": "ライト",
"autoplay_video": "動画を自動再生",
"audio_only": "音声のみ",
"default_quality": "デフォルトの画質",
"audio_only": "音声のみのモード",
"default_quality": "標準の画質",
"buffering_goal": "バッファリング目標値 (秒)",
"country_selection": "国の選択",
"default_homepage": "デフォルトのホームページ",
"country_selection": "国",
"default_homepage": "ホームに表示するページ",
"show_comments": "コメントを表示",
"minimize_description_default": "デフォルトで詳細を最小化する",
"store_watch_history": "再生履歴を保存する",
"language_selection": "言語の選択",
"minimize_description_default": "最初から説明を最小化",
"store_watch_history": "再生履歴を保存",
"language_selection": "言語",
"instances_list": "インスタンス一覧",
"enabled_codecs": "コーデックの有効化 (複数選択)",
"instance_selection": "インスタンスを選択",
"instance_selection": "インスタンス",
"show_more": "もっと見る",
"yes": "はい",
"no": "いいえ",
@ -66,60 +69,81 @@
"minimize_recommendations": "おすすめを最小化",
"show_recommendations": "おすすめを見る",
"disable_lbry": "ストリーミングのLBRYを無効化",
"enable_lbry_proxy": "LBRYプロキシをオン",
"view_ssl_score": "SSLスコアを見る",
"search": "検索",
"enable_lbry_proxy": "LBRYにプロキシを使用",
"view_ssl_score": "SSLの評価を表示",
"search": "検索 (Ctrl+K)",
"filter": "フィルター",
"loading": "読み込み中…",
"clear_history": "再生履歴を削除",
"hide_replies": "返信を非表示",
"load_more_replies": "リプライをもっと見る",
"skip_filler_tangent": "無関係なコンテンツをスキップ",
"load_more_replies": "返信をもっと見る",
"skip_filler_tangent": "無関係な談話をスキップ",
"skip_highlight": "ハイライトをスキップ",
"add_to_playlist": "再生リストに追加する",
"add_to_playlist": "再生リストに追加",
"create_playlist": "再生リストを作成",
"remove_from_playlist": "再生リストから削除",
"delete_playlist_video_confirm": "再生リストからこの動画を削除しますか?",
"delete_playlist": "再生リストを削除",
"please_select_playlist": "再生リストを選択してください",
"show_markers": "プレイヤーにマーカーを表示",
"show_markers": "プレイヤーに目印の区切りを表示",
"select_playlist": "再生リストを選択",
"delete_playlist_confirm": "再生リストを削除しますか?",
"delete_account": "アカウントを削除する",
"store_search_history": "検索履歴を保存する",
"delete_account": "アカウントを削除",
"store_search_history": "検索履歴を保存",
"show_chapters": "チャプター",
"status_page": "状態",
"source_code": "ソースコード",
"instance_donations": "インスタンスに寄付",
"minimize_comments": "コメントを最小化する",
"minimize_comments": "コメントを最小化",
"share": "共有",
"with_timecode": "タイムコード付きで共有",
"different_auth_instance": "認証に別のインスタンスを使",
"with_timecode": "時間指定",
"different_auth_instance": "認証に別のインスタンスを使",
"download_as_txt": ".txtでダウンロード",
"logout": "このデバイスでログアウト",
"minimize_recommendations_default": "デフォルトでおすすめを最小化する",
"logout": "この端末からログアウト",
"minimize_recommendations_default": "最初からおすすめを最小化",
"hide_watched": "再生済みの動画をフィードに表示しない",
"minimize_chapters_default": "デフォルトでチャプターを最小化する",
"show_watch_on_youtube": "\"YouTubeで見る\"ボタンを表示する",
"invalidate_session": "すべてのデバイスでログアウトする",
"instance_auth_selection": "認証インスタンスの選択",
"minimize_chapters_default": "最初からチャプターを最小化",
"show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
"invalidate_session": "すべての端末からログアウト",
"instance_auth_selection": "認証インスタンス",
"clone_playlist_success": "複製に成功しました!",
"backup_preferences": "設定をバックアップする",
"restore_preferences": "設定を復元する",
"backup_preferences": "設定をバックアップ",
"restore_preferences": "設定を復元",
"back_to_home": "ホームに戻る",
"copy_link": "リンクコピーする",
"copy_link": "リンクコピー",
"time_code": "タイムコード (秒)",
"documentation": "ドキュメント",
"reset_preferences": "設定をリセット",
"reset_preferences": "設定を初期化",
"confirm_reset_preferences": "設定をリセットしますか?",
"rename_playlist": "プレイリスト名を変更する",
"piped_link": "Pipedリンク",
"new_playlist_name": "新しいプレイリスト名",
"follow_link": "リンクに従う",
"follow_link": "リンクを開く",
"reply_count": "{count} 件の返信",
"clone_playlist": "プレイリストを複製",
"minimize_comments_default": "デフォルトでコメントを最小化する",
"no_valid_playlists": "ファイルに有効なプレイリストが含まれていません。"
"clone_playlist": "再生リストを複製",
"minimize_comments_default": "最初からコメントを最小化",
"no_valid_playlists": "このファイルは有効な再生リストではありません!",
"playlist_bookmarked": "ブックマーク完了",
"bookmark_playlist": "ブックマーク",
"with_playlist": "再生リストで共有",
"skip_automatically": "自動",
"skip_button_only": "スキップボタン表示",
"skip_segment": "ここをスキップ",
"min_segment_length": "最小の区切りの長さ (秒)",
"show_less": "少なく見る",
"autoplay_next_countdown": "次の動画の再生までの時間 (秒)",
"dismiss": "却下",
"create_group": "グループ作成",
"group_name": "グループ名",
"auto_display_captions": "自動で字幕を表示",
"okay": "OK",
"cancel": "キャンセル",
"edit_playlist": "再生リストを編集",
"playlist_name": "再生リスト名",
"playlist_description": "再生リストの説明",
"chapters_layout_mobile": "モバイル端末でのチャプターの配置",
"show_search_suggestions": "検索語句の候補を表示",
"delete_automatically": "指定時間後に自動削除",
"enable_dearrow": "DeArrow を使用",
"generate_qrcode": "QRコードの生成"
},
"comment": {
"pinned_by": "{author} によって固定",
@ -130,9 +154,9 @@
"preferences": {
"instance_name": "インスタンス名",
"instance_locations": "インスタンスの場所",
"has_cdn": "CDNは存在する?",
"ssl_score": "SSLスコア",
"registered_users": "登録済みユーザー",
"has_cdn": "CDNの有無",
"ssl_score": "SSLの評価",
"registered_users": "登録利用者数",
"version": "バージョン",
"up_to_date": "最新?"
},
@ -144,11 +168,17 @@
"videos": "動画",
"views": "{views} 回再生",
"watched": "再生済み",
"sponsor_segments": "スポンサーによる広告",
"sponsor_segments": "広告シーン数",
"ratings_disabled": "評価は無効化されています",
"chapters": "チャプター",
"live": "{0} ライブ配信",
"shorts": "ショート"
"shorts": "ショート",
"all": "すべて",
"category": "分類",
"chapters_horizontal": "横方向",
"chapters_vertical": "縦方向",
"visibility": "公開状態",
"license": "ライセンス"
},
"search": {
"did_you_mean": "もしかして: {0}",
@ -159,14 +189,21 @@
"music_songs": "YT Music: 音楽",
"music_videos": "YT Music: 動画",
"music_albums": "YT Music: アルバム",
"music_playlists": "YT Music: 再生リスト"
"music_playlists": "YT Music: 再生リスト",
"music_artists": "YT Music: アーティスト"
},
"info": {
"page_not_found": "ページが見つかりません",
"copied": "コピーしました!",
"cannot_copy": "コピーできません!",
"preferences_note": "注意: 設定はブラウザのローカルストレージに保存されます。ブラウザのデータを削除するとリセットされます。",
"local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか"
"preferences_note": "注意: 設定は、お使いのブラウザの保存領域に保存されます。ブラウザのデータを削除すると初期化されます。",
"local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか",
"register_no_email_note": "ユーザー名としてのメールアドレスの使用は推奨しません。それでも続けますか?",
"next_video_countdown": "{0} 秒後に次の動画",
"days": "{amount}日",
"weeks": "{amount}週",
"hours": "{amount}時間",
"months": "{amount}月"
},
"subscriptions": {
"subscribed_channels_count": "チャンネル登録: {0}"

View file

@ -6,7 +6,14 @@
"history": "Amazray",
"subscriptions": "Ijerriden",
"account": "Amiḍan",
"channels": "Ibuda"
"channels": "Ibuda",
"playlists": "Tabdert n tɣuri",
"instance": "Tummant",
"player": "Ameɣri",
"livestreams": "Azuzer usrid",
"bookmarks": "Ticraḍ",
"feed": "Amultaɣ",
"trending": "Tiddin"
},
"actions": {
"dark": "Ubrik",
@ -19,16 +26,148 @@
"delete_playlist_confirm": "Kkes tabdart-a n tɣuri?",
"share": "Bḍu",
"documentation": "Tasemlit",
"status_page": "État"
"status_page": "État",
"least_recent": "N melmi kna",
"channel_name_desc": "Isem n ubadu (A-Z)",
"channel_name_asc": "Isem n ubadu (A-z)",
"back": "Tuɣalin",
"uses_api_from": "Isseqdac API n: ",
"enable_sponsorblock": "Rmed amsewḥal n udellel",
"skip_sponsors": "Zgel adellel",
"add_to_playlist": "Rnu ɣer tebdart n tɣuri",
"hide_replies": "Ffer tiririyin",
"load_more_replies": "Sali-d ugar n tririyin",
"remove_from_playlist": "Kkes seg tebdart n tɣuri",
"delete_playlist_video_confirm": "Kkes tavidyut seg tebdart n tɣuri?",
"create_playlist": "Rnu tabdart n tɣuri",
"subscribe": "Qqen - {count}",
"unsubscribe": "Sefsex tuqqna n - {count}",
"view_subscriptions": "Wali imuktaɣen",
"sort_by": "Semyizwer s:",
"most_recent": "Amaynut akk",
"theme": "Asentel",
"auto": "Awurman",
"delete_playlist": "Kkes tabdart n tɣuri",
"select_playlist": "Fren tabdart n tɣuri",
"please_select_playlist": "Ttxil-k·m fren tabdart n tɣuri",
"delete_account": "Kkes amiḍan",
"logout": "Ffeɣ seg yibenk-a",
"light": "D ameceɛlal",
"autoplay_video": "Taɣuri tawurmant n tvidyut",
"audio_only": "Ameslaw kan",
"default_quality": "Taɣara tuzwirt",
"auto_play_next_video": "Taɣuri tawurmant n tvidyut tuḍfirt",
"donations": "Tawsa i usnefli",
"disable_lbry": "Sens LBRY i usuddem",
"enable_lbry_proxy": "Rmed apṛuksi i LBRY",
"view_ssl_score": "Sken agmuḍ SSL",
"show_recommendations": "Sken iwellihen",
"clear_history": "Sfeḍ azray",
"copy_link": "Nɣel aseɣwen",
"time_code": "Tangalt n wakud (s tsinin)",
"show_more": "Sken ugar",
"export_to_json": "Sifeḍ ɣer JSON",
"minimize_comments": "Semẓi iwenniten",
"show_comments": "Sken iwenniten",
"minimize_description": "Semẓi aglam",
"show_description": "Sken aglam",
"minimize_recommendations": "Semẓi iwellihen",
"reply_count": "{count} tririyin",
"bookmark_playlist": "Tacreḍt",
"playlist_bookmarked": "Yettwacreḍ",
"instances_list": "Tabdart n tummanin",
"country_selection": "Afran n tmurt",
"default_homepage": "Asebter agejdan amezwer",
"instance_selection": "Afran n tummant",
"import_from_json": "Kter seg JSON/CSV",
"store_search_history": "Ḥrez azray n unadi",
"instance_donations": "Tawsa n tummant",
"source_code": "Tangalt taɣbalut",
"minimize_description_default": "Semẓi aglam s wudem amezwer",
"skip_highlight": "Zgel asebruraq",
"minimize_comments_default": "Semẓi iwenniten s wudem amezwer",
"store_watch_history": "Ḥrez azray n tmeẓriwt",
"loop_this_video": "Err tavidyut-a d taẓayert",
"minimize_recommendations_default": "Semẓi iwellihen s wudem amezwer",
"no_valid_playlists": "Afaylu ulac deg-s tibdarin n tɣuri timeɣta!",
"with_playlist": "Bḍu s tebdart n tɣuri",
"skip_preview": "Zgel Taskant/Agzul",
"skip_outro": "Zgel ismawen n taggara",
"minimize_chapters_default": "Semẓi ixfawen s wudem uzwir",
"confirm_reset_preferences": "D tidet tebɣiḍ ad twennzeḍ ismenyifen-ik•im?",
"backup_preferences": "Ḥrez ismenyifen",
"restore_preferences": "Err-d ismenyifen",
"back_to_home": "Uɣal ɣer ugejdan",
"follow_link": "Ḍfer aseɣwen",
"show_chapters": "Ixfawen",
"show_watch_on_youtube": "Sken taqeffalt Wali ɣef YouTube",
"reset_preferences": "Wennez ismenyifen",
"with_timecode": "Bḍu s tengalt n wakud",
"hide_watched": "Ffer tividyutin yemmeẓren deg usuddel",
"skip_interaction": "Zgel ismektiyen n umyigew (Multeɣ)",
"enabled_codecs": "Isettengal ttwaremden (aṭas)",
"buffering_goal": "Iswi n uḥraz akudan (s tsinin)",
"skip_non_music": "Zgel aẓawan: tafrant ur nelli d aẓawan",
"show_markers": "Sken ticraḍ ɣef umeɣri",
"instance_auth_selection": "Tafrant n tummant n usesteb",
"invalidate_session": "Ffeɣ seg meṛṛa ibenkan",
"different_auth_instance": "Seqdec tummant tayeḍ i usesteb",
"download_as_txt": "Sader am.txt",
"piped_link": "Aseɣwen n Piped",
"clone_playlist": "Tabdart n tɣuri yemtawan"
},
"preferences": {
"version": "Lqem"
"version": "Lqem",
"has_cdn": "Ɣur-s CDN?",
"registered_users": "Iseqdacen yettwajerrden",
"up_to_date": "D amaynut?",
"ssl_score": "Agmuḍ SSL",
"instance_name": "Isem n tummant",
"instance_locations": "Idgan n tummant"
},
"login": {
"username": "Nom d'utilisateur",
"password": "Awal n uɛeddi"
},
"video": {
"videos": "Tividyutin"
"videos": "Tividyutin",
"views": "{views} tmeẓriwin",
"watched": "Yemẓer",
"shorts": "Tiwezlanin",
"live": "{0} srid",
"sponsor_segments": "Inegzumen n udellel",
"chapters": "Ixfawen",
"ratings_disabled": "Iktazalen ttwasensen"
},
"player": {
"watch_on": "Wali deg {0}"
},
"comment": {
"pinned_by": "Isenteḍ-itt {author}",
"loading": "Asali n yiwenniten...",
"disabled": "Ameskar issens iwenniten.",
"user_disabled": "Nsan yiwenniten deg Yiɣewwaren."
},
"search": {
"did_you_mean": "Tebɣiḍ ad d-tiniḍ: {0}?",
"music_playlists": "YT Music: Tibdarin n tɣuri",
"all": "YouTube: Akk",
"videos": "YouTube: Tividyutin",
"channels": "YouTube: Ibuda",
"music_videos": "YT Music: Tividyutin",
"playlists": "YouTube: Tibdarin n tɣuri",
"music_songs": "YT Music: Tizlatin",
"music_albums": "YT Music: Albumen"
},
"info": {
"copied": "Yettwanɣel!",
"page_not_found": "Ur yettwaf ara usebter",
"cannot_copy": "D awezɣi ad d-yettwanɣel!",
"register_no_email_note": "Aseqdec n yimayl am yisem n useqdac, ur yettusireg ara. Kemmel ɣas akken?",
"local_storage": "Tigawt-a aḥraz adigan, inagan n tuqqna remden?",
"preferences_note": "Tamawt: ismenyifen ttwaskelsen deg uklas adigan n yiminig-ik·im. Tukksa n yisefka yiminig-ik·im ad ten-iwennez."
},
"subscriptions": {
"subscribed_channels_count": "Imulteɣ ɣer: {0}"
}
}

View file

@ -8,7 +8,7 @@
"show_comments": "댓글 보이기",
"unsubscribe": "구독 취소 - {count}",
"view_subscriptions": "구독 보기",
"least_recent": "가장 오래된",
"least_recent": "오래된",
"theme": "테마",
"auto": "자동",
"channel_name_desc": "채널 이름 (Z-A)",
@ -26,16 +26,16 @@
"no": "아니요",
"loop_this_video": "이 동영상 반복",
"auto_play_next_video": "다음 동영상 자동 재생",
"minimize_description": "설명 최소화",
"minimize_description": "설명 가리기",
"show_recommendations": "추천 동영상 표시",
"sort_by": "정렬:",
"most_recent": "가장 최신",
"most_recent": "최신",
"channel_name_asc": "채널 이름 (A-Z)",
"subscribe": "구독 - {count}",
"audio_only": "오디오만",
"skip_sponsors": "스폰서 스킵",
"dark": "다크",
"minimize_description_default": "기본적으로 설명 최소화",
"minimize_description_default": "설명 가리기를 기본 설정값으로",
"enabled_codecs": "활성화된 코덱 (여러 개)",
"instance_selection": "인스턴스 선택",
"import_from_json": "JSON/CSV에서 가져오기",
@ -50,11 +50,11 @@
"show_description": "설명 표시",
"disable_lbry": "LBRY 스트리밍 비활성화",
"enable_lbry_proxy": "LBRY 프록시 활성화",
"minimize_recommendations": "추천 동영상 최소화",
"minimize_recommendations": "추천 동영상 가리기",
"loading": "로딩...",
"view_ssl_score": "SSL 점수 보기",
"skip_intro": "중간 휴식/인트로 애니메이션 스킵",
"search": "검색",
"search": "검색 (Ctrl+K)",
"clear_history": "기록 지우기",
"skip_highlight": "하이라이트 스킵",
"select_playlist": "재생목록 선택",
@ -69,8 +69,6 @@
"logout": "이 기기에서 로그아웃",
"show_chapters": "챕터",
"download_as_txt": ".txt로 다운로드",
"new_playlist_name": "새 재생목록 이름",
"rename_playlist": "재생목록 이름 변경",
"share": "공유",
"copy_link": "링크 복사",
"time_code": "시작 시간 (초)",
@ -84,7 +82,7 @@
"restore_preferences": "설정 복원",
"backup_preferences": "설정 백업",
"back_to_home": "홈으로 가기",
"minimize_recommendations_default": "기본적으로 추천 동영상 최소화",
"minimize_recommendations_default": "추천 동영상 가리기를 기본 설정값으로",
"invalidate_session": "모든 기기에서 로그아웃",
"instance_auth_selection": "인증 인스턴스 선택",
"different_auth_instance": "인증에 다른 인스턴스 사용",
@ -95,7 +93,17 @@
"documentation": "문서",
"source_code": "소스 코드",
"instance_donations": "인스턴스 기부",
"status_page": "상태"
"status_page": "상태",
"bookmark_playlist": "북마크",
"dismiss": "무시",
"minimize_comments_default": "댓글 가리기를 기본 설정값으로",
"minimize_comments": "댓글 가리기",
"show_watch_on_youtube": "YouTube에서 보기’ 버튼 표시",
"minimize_chapters_default": "챕터 가리기를 기본 설정값으로",
"autoplay_next_countdown": "다음 영상 재생까지 대기 시간(초)",
"skip_button_only": "스킵 버튼 표시",
"skip_automatically": "자동",
"show_less": "덜 보기"
},
"titles": {
"feed": "피드",
@ -108,7 +116,10 @@
"playlists": "재생목록",
"account": "계정",
"instance": "인스턴스",
"player": "플레이어"
"player": "플레이어",
"bookmarks": "북마크",
"livestreams": "실시간",
"channels": "채널"
},
"player": {
"watch_on": "에서 보기 {0}"
@ -140,7 +151,8 @@
"ratings_disabled": "등급 비활성화됨",
"live": "{0} 라이브",
"shorts": "Shorts",
"chapters": "챕터"
"chapters": "챕터",
"category": "카테고리"
},
"search": {
"did_you_mean": "이것을 찾으셨나요: {0}?",

View file

@ -80,7 +80,6 @@
"reply_count": "{count} atsakymai",
"show_chapters": "Skirsniai",
"piped_link": "Piped nuoroda",
"rename_playlist": "Pervardyti grojaraštį",
"follow_link": "Sekti nuorodą",
"store_search_history": "Išsaugoti paieškos istoriją",
"hide_watched": "Slėpti žiūrėtus vaizdo įrašus sklaidos kanale",
@ -88,7 +87,6 @@
"status_page": "Būsena",
"copy_link": "Kopijuoti nuorodą",
"share": "Dalintis",
"new_playlist_name": "Naujas grojaraščio pavadinimas",
"minimize_recommendations_default": "Sumažinti rekomendacijas automatiškai",
"instance_donations": "Perdavimo šaltinio parama",
"instance_auth_selection": "Autentifikavimo perdavimo šaltinio pasirinkimas",

View file

@ -7,7 +7,8 @@
"feed": "ഫീഡ്",
"login": "പ്രവേശിക്കുക",
"subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ",
"playlists": "പ്ലേലിസ്റ്റുകൾ"
"playlists": "പ്ലേലിസ്റ്റുകൾ",
"player": "കളിക്കാരൻ"
},
"actions": {
"view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
@ -72,7 +73,8 @@
"select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കൂ",
"please_select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കുക",
"delete_playlist_video_confirm": "ഈ പ്ലേലിസ്റ്റിൽ നിന്ന് ഈ വീഡിയോ ഒഴിവാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?",
"delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?"
"delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?",
"skip_automatically": "സ്വയമേവ"
},
"player": {
"watch_on": "കാണുക {0}"

View file

@ -81,8 +81,6 @@
"confirm_reset_preferences": "Tilbakestill alle innstillingene?",
"restore_preferences": "Gjenopprett innstillinger",
"show_chapters": "Kapitler",
"rename_playlist": "Gi spillelisten ny navn",
"new_playlist_name": "Nytt spillelistenavn",
"share": "Del",
"with_timecode": "Del med tidskode",
"piped_link": "Piped-lenke",

View file

@ -1,7 +1,7 @@
{
"actions": {
"skip_sponsors": "Sponsors Overslaan",
"skip_outro": "Eindkaarten/Credits overslaan",
"skip_outro": "Eindkaarten/Credits Overslaan",
"add_to_playlist": "Aan Afspeellijst Toevoegen",
"sort_by": "Sorteer op:",
"buffering_goal": "Bufferdoel (in seconden)",
@ -10,7 +10,7 @@
"disable_lbry": "LBRY voor Streamen Uitschakelen",
"enable_lbry_proxy": "Proxy voor LBRY Inschakelen",
"view_ssl_score": "SSL-score Bekijken",
"search": "Zoeken",
"search": "Zoeken (Ctrl+K)",
"filter": "Filter",
"skip_filler_tangent": "Opvultangens Overslaan",
"theme": "Thema",
@ -20,7 +20,7 @@
"skip_self_promo": "Onbetaalde/Zelf-promotie Overslaan",
"skip_highlight": "Markering Overslaan",
"skip_interaction": "Interactieherinnering Overslaan (Abonneren)",
"show_more": "Toon Meer",
"show_more": "Toon meer",
"unsubscribe": "Afmelden - {count}",
"view_subscriptions": "Abonnementen Bekijken",
"enable_sponsorblock": "Sponsorblok Inschakelen",
@ -58,17 +58,17 @@
"remove_from_playlist": "Uit Afspeellijst Verwijderen",
"select_playlist": "Selecteer een Afspeellijst",
"delete_playlist_confirm": "Deze afspeellijst verwijderen?",
"please_select_playlist": "Kies een afspeellijst a.u.b.",
"please_select_playlist": "Selecteer een afspeellijst alsjeblief",
"instance_selection": "Instantie Selectie",
"import_from_json": "Importeren uit JSON/CSV",
"clear_history": "Geschiedenis Wissen",
"load_more_replies": "Laad meer Antwoorden",
"delete_playlist_video_confirm": "Video van playlist verwijderen?",
"delete_playlist_video_confirm": "Video uit deze afspeellijst verwijderen?",
"create_playlist": "Afspeellijst Maken",
"delete_playlist": "Afspeellijst Verwijderen",
"show_markers": "Toon markeringen op Speler",
"store_search_history": "Zoekgeschiedenis opslaan",
"minimize_chapters_default": "Hoofdstukken standaard minimaliseren",
"show_markers": "Laat markeringen op speler zien",
"store_search_history": "Zoekgeschiedenis Opslaan",
"minimize_chapters_default": "Hoofdstukken Standaard Minimaliseren",
"show_watch_on_youtube": "Toon Bekijk op YouTube knop",
"restore_preferences": "Voorkeuren herstellen",
"with_timecode": "Delen met tijdcode",
@ -77,11 +77,9 @@
"copy_link": "Link kopiëren",
"hide_watched": "Verberg bekeken video's in de feed",
"minimize_comments": "Opmerkingen minimaliseren",
"instance_auth_selection": "Autenticatie Instantie Selectie",
"clone_playlist": "Afspeellijst klonen",
"instance_auth_selection": "Selectie authenticatie-instantie",
"clone_playlist": "Afspeellijst dupliceren",
"download_as_txt": "Downloaden als .txt",
"rename_playlist": "Afspeellijst hernoemen",
"new_playlist_name": "Nieuwe afspeellijstnaam",
"share": "Delen",
"documentation": "Documentatie",
"status_page": "Status",
@ -91,18 +89,27 @@
"instance_donations": "Instantie donaties",
"reply_count": "{count} antwoorden",
"no_valid_playlists": "Het bestand bevat geen geldige afspeellijsten!",
"clone_playlist_success": "Succesvol gekloond!",
"reset_preferences": "Voorkeuren opnieuw instellen",
"clone_playlist_success": "Dupliceren gelukt!",
"reset_preferences": "Voorkeuren herstellen",
"back_to_home": "Terug naar de start",
"minimize_comments_default": "Opmerkingen standaard minimaliseren",
"minimize_comments_default": "Opmerkingen Standaard Minimaliseren",
"delete_account": "Account Verwijderen",
"logout": "Uitloggen van dit apparaat",
"minimize_recommendations_default": "Aanbevelingen standaard minimaliseren",
"logout": "Uitloggen op dit apparaat",
"minimize_recommendations_default": "Aanbevelingen Standaard Minimaliseren",
"confirm_reset_preferences": "Weet u zeker dat u uw voorkeuren opnieuw wilt instellen?",
"backup_preferences": "Back-up voorkeuren",
"invalidate_session": "Alle apparaten afmelden",
"invalidate_session": "Uitloggen op alle apparaten",
"different_auth_instance": "Gebruik een andere instantie voor authenticatie",
"with_playlist": "Delen met afspeellijst"
"with_playlist": "Delen met afspeellijst",
"playlist_bookmarked": "Bladwijzer gemaakt",
"bookmark_playlist": "Bladwijzer",
"skip_automatically": "Automatisch",
"skip_button_only": "toon de overslaan knop",
"min_segment_length": "Minimale segmentlengte (in seconden)",
"skip_segment": "segment overslaan",
"show_less": "Toon minder",
"autoplay_next_countdown": "Standaard aftellen tot de volgende video (in seconden)",
"dismiss": "Afwijzen"
},
"titles": {
"register": "Registreren",
@ -111,13 +118,14 @@
"preferences": "Voorkeuren",
"history": "Geschiedenis",
"subscriptions": "Abonnementen",
"trending": "Trending",
"trending": "populair",
"playlists": "Afspeellijsten",
"account": "Account",
"account": "profiel",
"instance": "Instantie",
"player": "Speler",
"livestreams": "Livestreams",
"channels": "Kanalen"
"channels": "Kanalen",
"bookmarks": "Bladwijzers"
},
"player": {
"watch_on": "Kijk op {0}"
@ -145,7 +153,9 @@
"sponsor_segments": "Sponsorsegmenten",
"ratings_disabled": "Beoordelingen Uitgeschakeld",
"live": "{0} Live",
"shorts": "Shorts"
"shorts": "Shorts",
"category": "Categorie",
"all": "Alle"
},
"preferences": {
"has_cdn": "Heeft CDN?",
@ -158,8 +168,8 @@
},
"comment": {
"pinned_by": "Vastgemaakt door {author}",
"user_disabled": "Reacties zijn uitgeschakeld in de instellingen.",
"loading": "Reacties laden...",
"user_disabled": "Opmerkingen zijn uitgeschakeld in de instellingen.",
"loading": "Opmerkingen laden...",
"disabled": "Reacties zijn uitgeschakeld door de uploader."
},
"info": {
@ -167,7 +177,9 @@
"copied": "Gekopieerd!",
"cannot_copy": "Kan niet kopiëren!",
"page_not_found": "Pagina niet gevonden",
"local_storage": "Deze actie vereist lokale opslag, zijn cookies ingeschakeld?"
"local_storage": "Deze actie vereist lokale opslag, zijn cookies ingeschakeld?",
"register_no_email_note": "Een e-mailadres als gebruikersnaam gebruiken wordt afgeraden. Toch doorgaan?",
"next_video_countdown": "Volgende video afspelen in {0}s"
},
"subscriptions": {
"subscribed_channels_count": "Geabonneerd op: {0}"

200
src/locales/oc.json Normal file
View file

@ -0,0 +1,200 @@
{
"titles": {
"login": "Se connectar",
"feed": "Flux",
"preferences": "Preferéncias",
"history": "Istoric",
"account": "Compte",
"instance": "Instància",
"player": "Lector",
"livestreams": "Dirèctes",
"channels": "Canals",
"trending": "Tendéncias",
"register": "Sinscriure",
"subscriptions": "Abonaments",
"playlists": "Listas de lecturas",
"bookmarks": "Marcapaginas",
"channel_groups": "Grops de cadenas"
},
"player": {
"watch_on": "Agachar sus {0}"
},
"actions": {
"subscribe": "Sabonar - {count}",
"unsubscribe": "Se desabonar - {count}",
"view_subscriptions": "Veire los abonaments",
"sort_by": "Triar per:",
"most_recent": "Mai recents",
"least_recent": "Mens recents",
"channel_name_asc": "Nom del canal (A-Z)",
"channel_name_desc": "Nom del canal (Z-A)",
"back": "Tornar",
"skip_outro": "Passar los crèdits a la fin",
"skip_preview": "Passar lapercebut / resumit",
"uses_api_from": "Utiliza lAPI de ",
"skip_intro": "Passar lanimacion dentracte / Introduccion",
"enable_sponsorblock": "Activar Sponsorblock",
"skip_sponsors": "Passar las promocions",
"show_comments": "Mostrar los comentaris",
"minimize_description": "Minimizar la descripcion",
"show_description": "Mostrar la descripcion",
"show_recommendations": "Mostrar las recomandacions",
"minimize_recommendations": "Minimizar las recomandacions",
"enable_lbry_proxy": "Activar lo servidor mandatari per LBRY",
"view_ssl_score": "Mostrar lavaloracion SSL",
"search": "Recercar (Ctrl+K)",
"filter": "Filtrar",
"disable_lbry": "Desactivar LBRY per la difusion en dirècte",
"loading": "Cargament…",
"clear_history": "Netejar listoric",
"hide_replies": "Rescondre las responsas",
"load_more_replies": "Cargar mai de responsas",
"remove_from_playlist": "Levar de la lista de lectura",
"add_to_playlist": "Apondre a la listra de lectura",
"minimize_comments": "Minimizar los comentaris",
"theme": "Tèma",
"language_selection": "Seleccion de la lenga",
"loop_this_video": "Legir en bocla la vidèo",
"reset_preferences": "Restablir las preferéncias",
"auto": "Auto",
"dark": "Escur",
"light": "Clar",
"donations": "Don pel desvolopament",
"backup_preferences": "Salvagardar las preferéncias",
"share": "Partejar",
"documentation": "Documentacion",
"source_code": "Còdi font",
"restore_preferences": "Restablir las preferéncias",
"skip_interaction": "Ignorar los rapèls dinteraccion (Sabonar)",
"show_markers": "Mostrar los marcadors sul lector",
"default_quality": "Qualitat per defaut",
"buffering_goal": "Objectiu de mesa en memòria tampon (en segondas)",
"minimize_description_default": "Minimizar la descripcion per defaut",
"instances_list": "Lista dinstàncias",
"enabled_codecs": "Codecs activats (multiples)",
"yes": "Òc",
"no": "Non",
"export_to_json": "Exportar en JSON",
"import_from_json": "Importar dun JSON/CSV",
"auto_play_next_video": "Legir la vidèo seguenta automaticament",
"create_playlist": "Crear una lista de lectura",
"delete_playlist": "Levar de la lista de lectura",
"select_playlist": "Seleccionatz una lista de lectura",
"delete_playlist_confirm": "Suprimir aquesta lista de lectura ?",
"clone_playlist": "Clonar la lista de lectura",
"instance_auth_selection": "Seleccion de linstància dautentificacion",
"clone_playlist_success": "Clonatge capitat !",
"follow_link": "Dobrir lo ligam",
"skip_self_promo": "Sautar la promocion gratuita / autopromocion",
"skip_non_music": "Sautar la musica : seccion non musicala",
"skip_highlight": "Ignorar los tempses fòrts",
"skip_filler_tangent": "Sautar la tangenta demplenament",
"autoplay_video": "Legir automaticament la vidèo",
"audio_only": "Sonque àudio",
"minimize_comments_default": "Minimizar los comentaris per defaut",
"instance_selection": "Seleccion dinstàncias",
"please_select_playlist": "Seleccionatz una lista de lectura",
"show_watch_on_youtube": "Mostrar lo boton « Veire sus YouTube »",
"invalidate_session": "Se desconnectar de totes los aparelhs",
"different_auth_instance": "Utilizar una instància diferenta per lautentificacion",
"back_to_home": "Tornar a lacuèlh",
"with_timecode": "Partejar amb còdi orari",
"piped_link": "Ligam cap a Piped",
"show_chapters": "Capítols",
"country_selection": "Seleccion del país",
"default_homepage": "Pagina dacuèlh per defaut",
"minimize_recommendations_default": "Minimizar las recomandacions per defaut",
"store_watch_history": "Servar listoric de visualizacion",
"show_more": "Ne mostrar mai",
"delete_playlist_video_confirm": "Levar aquesta vidèo de la lista de lectura ?",
"delete_account": "Suprimir lo compte",
"logout": "Se desconnectar daqueste aparelh",
"minimize_chapters_default": "Minimizar los capítols per defaut",
"download_as_txt": "Telecargar coma .txt",
"copy_link": "Copiar lo ligam",
"time_code": "Moment (en segondas)",
"store_search_history": "Gardar listoric de recèrca",
"confirm_reset_preferences": "Volètz vertadièrament reïnicializar las preferéncias ?",
"hide_watched": "Rescondre las vidèos vistas al flux",
"reply_count": "{count} responsas",
"with_playlist": "Partejar amb una lista de lectura",
"playlist_bookmarked": "Marcat",
"bookmark_playlist": "Marcapagina",
"status_page": "Estat",
"no_valid_playlists": "Lo fichièr conten pas cap de lista de lectura valida !",
"instance_donations": "Dons dinstància",
"skip_button_only": "Afichar lo boton per sautar",
"skip_automatically": "Automaticament",
"min_segment_length": "Durada minimum de segment (en segondas)",
"skip_segment": "Sautar lo segment",
"show_less": "Ne mostrar mens",
"autoplay_next_countdown": "Descompte per defaut abans de passar a la vidèo seguenta (en segondas)",
"dismiss": "Ignorar",
"create_group": "Crear un grop",
"group_name": "Nom del grop",
"cancel": "Anullar",
"okay": "Dacòrd",
"edit_playlist": "Modificar la lista de lectura",
"playlist_name": "Nom de la lista de lectura",
"playlist_description": "Descripcion de la lista de lectura",
"auto_display_captions": "Afichatge auto dels sostítols",
"show_search_suggestions": "Mostrar las suggestions de recèrcas",
"chapters_layout_mobile": "Disposicion capítols sus mobil"
},
"preferences": {
"instance_locations": "Localizacion de linstància",
"registered_users": "Utilizaires inscriches",
"instance_name": "Nom de linstància",
"has_cdn": "A un CDN ?",
"version": "Version",
"up_to_date": "Actualizat ?",
"ssl_score": "Marca SSL"
},
"login": {
"username": "Nom dutilizaire",
"password": "Senhal"
},
"video": {
"views": "{views} visualizacions",
"watched": "Vista",
"ratings_disabled": "Avaloracions desactivadas",
"sponsor_segments": "Segments de sponsors",
"live": "{0} en dirècte",
"shorts": "Corts",
"chapters": "Capítols",
"videos": "Vidèos",
"all": "Totas",
"category": "Categoria",
"chapters_horizontal": "Orizontal",
"chapters_vertical": "Vertical"
},
"info": {
"preferences_note": "Nòta: las preferéncias son gardadas dins lespaci demmagazinatge del navegador. La supression de las donadas del navegador las restablirà.",
"copied": "Copiat !",
"page_not_found": "Pagina pas trobada",
"cannot_copy": "Còpia impossibla !",
"local_storage": "Aquesta accion requerís lo localStorage, son activats los cookies ?",
"register_no_email_note": "Es pas recomandat dutilizar una adreça electronica coma nom dutilizaire. Contunhar çaquelà ?",
"next_video_countdown": "La vidèo seguenta començarà daquí {0}s"
},
"comment": {
"disabled": "Lautor a desactivat los comentaris.",
"loading": "Cargament dels comentaris…",
"user_disabled": "Los comentaris son desactivats als paramètres.",
"pinned_by": "Penjat per {author}"
},
"search": {
"did_you_mean": "Voliatz dire : {0} ?",
"channels": "YouTube : Canals",
"videos": "YouTube : Vidèos",
"all": "YouTube : Tot",
"playlists": "YouTube : Listas de lectura",
"music_songs": "YT Music : Cançons",
"music_videos": "YT Music : Vidèos",
"music_albums": "YT Music : Albums",
"music_playlists": "YT Music : Listas de lectura"
},
"subscriptions": {
"subscribed_channels_count": "Abonat a : {0}"
}
}

View file

@ -12,7 +12,9 @@
"account": "ଆକାଉଣ୍ଟ",
"player": "ପ୍ଲେୟାର",
"livestreams": "ସିଧାପ୍ରସାରଣ ଗୁଡ଼ିକ",
"channels": "ସ୍ରୋତ ଗୁଡ଼ିକ"
"channels": "ସ୍ରୋତ ଗୁଡ଼ିକ",
"bookmarks": "ବୁକମାର୍କଗୁଡିକ",
"channel_groups": "ଚ୍ୟାନେଲ୍ ଗୋଷ୍ଠୀଗୁଡିକ"
},
"player": {
"watch_on": "{0} ରେ ଦେଖନ୍ତୁ"
@ -32,9 +34,7 @@
"minimize_recommendations": "ସୁପାରିଶକୁ କମ୍ କରନ୍ତୁ",
"show_recommendations": "ସୁପାରିଶଗୁଡିକ ଦେଖାନ୍ତୁ",
"disable_lbry": "ଷ୍ଟ୍ରିମିଂ ପାଇଁ LBRY ଅକ୍ଷମ କରନ୍ତୁ",
"search": "ସନ୍ଧାନ କରନ୍ତୁ",
"rename_playlist": "ପ୍ଲେ ଲିଷ୍ଟର ନାମ ପରିବର୍ତ୍ତନ କରନ୍ତୁ",
"new_playlist_name": "ନୂତନ ପ୍ଲେଲିଷ୍ଟ ନାମ",
"search": "ସନ୍ଧାନ କରନ୍ତୁ (Ctrl+K)",
"channel_name_asc": "ସ୍ରୋତ ର ନାମ (A-Z)",
"least_recent": "ସର୍ବନିମ୍ନ ସାମ୍ପ୍ରତିକ",
"channel_name_desc": "ସ୍ରୋତ ର ନାମ (Z-A)",
@ -119,7 +119,19 @@
"documentation": "ଡକ୍ୟୁମେଣ୍ଟେସନ୍",
"instance_donations": "ଇନଷ୍ଟାଣ୍ଟ ଦାନ ଗୁଡ଼ିକ",
"minimize_chapters_default": "ଡିଫଲ୍ଟ ଭାବରେ ଅଧ୍ୟାୟଗୁଡ଼ିକୁ କମ୍ କରନ୍ତୁ",
"no_valid_playlists": "ଫାଇଲ୍ ଟି ବୈଧ ପ୍ଲେଲିଷ୍ଟ ଧାରଣ କରେ ନାହିଁ!"
"no_valid_playlists": "ଫାଇଲ୍ ଟି ବୈଧ ପ୍ଲେଲିଷ୍ଟ ଧାରଣ କରେ ନାହିଁ!",
"with_playlist": "ପ୍ଲେଲିଷ୍ଟ ସହିତ ଅଂଶୀଦାର କରନ୍ତୁ",
"bookmark_playlist": "ବୁକମାର୍କ",
"playlist_bookmarked": "ବୁକମାର୍କ ହୋଇଛି",
"min_segment_length": "ସର୍ବନିମ୍ନ ସେଗମେଣ୍ଟ ଲମ୍ବ (ସେକେଣ୍ଡରେ)",
"skip_button_only": "ସ୍କିପ୍ ବଟନ୍ ଦେଖାନ୍ତୁ",
"skip_automatically": "ସ୍ୱୟଂଚାଳିତ ଭାବରେ",
"skip_segment": "ସେଗମେଣ୍ଟକୁ ଏଡ଼ାଇଦିଅ",
"show_less": "କମ୍ ଦେଖାନ୍ତୁ",
"autoplay_next_countdown": "ପରବର୍ତ୍ତୀ ଭିଡିଓ ପର୍ଯ୍ୟନ୍ତ ଡିଫଲ୍ଟ କାଉଣ୍ଟଡାଉନ୍ (ସେକେଣ୍ଡରେ)",
"dismiss": "ବରଖାସ୍ତ",
"create_group": "ଗୋଷ୍ଠୀ ସୃଷ୍ଟି କରନ୍ତୁ",
"group_name": "ଗୋଷ୍ଠୀ ନାମ"
},
"comment": {
"loading": "ମନ୍ତବ୍ୟ ଲୋଡ୍ ହେଉଛି ...",
@ -135,7 +147,9 @@
"videos": "ଭିଡିଓ ଗୁଡିକ",
"ratings_disabled": "ମୂଲ୍ୟାୟନ ଅକ୍ଷମ ହୋଇଛି",
"chapters": "ଅଧ୍ୟାୟ ଗୁଡ଼ିକ",
"live": "{0} ସିଧାପ୍ରସାରଣ"
"live": "{0} ସିଧାପ୍ରସାରଣ",
"all": "ସମସ୍ତ",
"category": "ବର୍ଗ"
},
"search": {
"did_you_mean": "ଆପଣ କହିବାକୁ ଚାହୁଁଛନ୍ତି କି: {0}?",
@ -156,7 +170,9 @@
"copied": "କପି ହୋଇଛି!",
"cannot_copy": "କପି କରିପାରିବ ନାହିଁ!",
"page_not_found": "ପୃଷ୍ଠାଟି ମିଳିଲା ନାହିଁ",
"local_storage": "ଏହି କ୍ରିୟା ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍ ଆବଶ୍ୟକ କରେ, କୁକିଜ୍ ସକ୍ଷମ ଅଛି କି?"
"local_storage": "ଏହି କ୍ରିୟା ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍ ଆବଶ୍ୟକ କରେ, କୁକିଜ୍ ସକ୍ଷମ ଅଛି କି?",
"register_no_email_note": "ଉପଯୋଗକର୍ତ୍ତା ନାମ ଭାବରେ ଏକ ଇ-ମେଲ୍ ବ୍ୟବହାର କରିବା ସୁପାରିଶ କରାଯାଏ ନାହିଁ । ଯେକୌଣସି ପ୍ରକାରେ ଅଗ୍ରଗତି କରନ୍ତୁ?",
"next_video_countdown": "{0} ସେକେଣ୍ଡ ରେ ପରବର୍ତ୍ତୀ ଭିଡିଓ ଖେଳିବାକୁ ଆରମ୍ଭ ହେବ"
},
"preferences": {
"instance_name": "ଇନଷ୍ଟାନ୍ସ ନାମ",

View file

@ -12,7 +12,10 @@
"account": "Konto",
"instance": "Instancja",
"livestreams": "Na żywo",
"channels": "Kanały"
"channels": "Kanały",
"bookmarks": "Zakładki",
"channel_groups": "Grupy kanałów",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Obejrzyj na {0}"
@ -46,15 +49,15 @@
"audio_only": "Tylko audio",
"default_quality": "Domyślna jakość",
"buffering_goal": "Cel buforowania (w sekundach)",
"country_selection": "Wybór kraju",
"country_selection": "Kraj",
"default_homepage": "Domyślna strona główna",
"show_comments": "Pokaż komentarze",
"minimize_description_default": "Ukryj opis",
"store_watch_history": "Zapamiętaj historię oglądania",
"language_selection": "Wybór języka",
"language_selection": "Język",
"instances_list": "Lista instancji",
"enabled_codecs": "Włączone kodeki (lista wielokrotnego wyboru)",
"instance_selection": "Wybór instancji",
"instance_selection": "Instancja",
"show_more": "Pokaż więcej",
"yes": "Tak",
"no": "Nie",
@ -67,10 +70,10 @@
"show_description": "Pokaż opis",
"minimize_recommendations": "Ukryj proponowane",
"show_recommendations": "Pokaż proponowane",
"disable_lbry": "Wyłącz LBRY dla streaming-u",
"disable_lbry": "Wyłącz LBRY dla przesyłania strumieniowego",
"enable_lbry_proxy": "Włącz proxy dla LBRY",
"view_ssl_score": "Pokaż ocenę SSL",
"search": "Szukaj",
"search": "Szukaj (Ctrl+K)",
"filter": "Filtruj",
"loading": "Ładowanie...",
"clear_history": "Wyczyść historię",
@ -92,7 +95,7 @@
"documentation": "Dokumentacja",
"instance_donations": "Darowizny na rzecz instancji",
"back_to_home": "Idź do strony głównej",
"instance_auth_selection": "Wybrana instancja autoryzacyjna",
"instance_auth_selection": "Instancja uwierzytelniania",
"time_code": "Kod czasowy (w sekundach)",
"show_markers": "Pokaż segmenty na odtwarzaczu",
"store_search_history": "Zapamiętaj historię wyszukiwania",
@ -100,7 +103,6 @@
"source_code": "Kod źródłowy",
"show_chapters": "Rozdziały",
"minimize_chapters_default": "Ukryj rozdziały",
"rename_playlist": "Zmień nazwę playlisty",
"follow_link": "Otwórz link",
"minimize_comments_default": "Ukryj sekcję komentarzy",
"minimize_comments": "Ukryj komentarze",
@ -113,14 +115,35 @@
"backup_preferences": "Pobierz kopię zapasową ustawień",
"download_as_txt": "Pobierz jako .txt",
"reset_preferences": "Zresetuj ustawienia",
"new_playlist_name": "Nowa nazwa playlisty",
"share": "Udostępnij",
"with_timecode": "Udostępnij z kodem czasowym",
"piped_link": "Link Piped",
"status_page": "Status",
"reply_count": "{count} odpowiedzi",
"no_valid_playlists": "Ten plik nie zawiera poprawnych playlist!",
"with_playlist": "Udostępnij z playlistą"
"with_playlist": "Udostępnij z playlistą",
"playlist_bookmarked": "Dodano do zakładek",
"bookmark_playlist": "Zakładka",
"skip_button_only": "Pokaż przycisk pomijania",
"skip_automatically": "Automatycznie",
"min_segment_length": "Minimalna długość segmentu (w sekundach)",
"skip_segment": "Pomiń segment",
"show_less": "Pokaż mniej",
"autoplay_next_countdown": "Domyślne odliczanie do następnego filmu (w sekundach)",
"dismiss": "Odrzuć",
"group_name": "Nazwa grupy",
"create_group": "Utwórz grupę",
"auto_display_captions": "Automatyczne wyświetlanie napisów",
"edit_playlist": "Edytuj playlistę",
"playlist_name": "Nazwa playlisty",
"playlist_description": "Opis playlisty",
"okay": "OK",
"cancel": "Anuluj",
"show_search_suggestions": "Pokaż sugestie wyszukiwania",
"chapters_layout_mobile": "Układ rozdziałów na urządzeniach mobilnych",
"delete_automatically": "Usuń automatycznie po",
"enable_dearrow": "Włącz DeArrow",
"generate_qrcode": "Wygeneruj kod QR"
},
"comment": {
"pinned_by": "Przypięty przez {author}",
@ -139,7 +162,9 @@
},
"login": {
"username": "Nazwa użytkownika",
"password": "Hasło"
"password": "Hasło",
"password_confirm": "Potwierdź hasło",
"passwords_incorrect": "Hasła się nie zgadzają!"
},
"video": {
"videos": "Filmy",
@ -148,8 +173,14 @@
"sponsor_segments": "Segmenty sponsorowane",
"ratings_disabled": "Ocenianie wyłączone",
"chapters": "Rozdziały",
"live": "{0} Na żywo",
"shorts": "Krótkie wideo"
"live": "{0} na żywo",
"shorts": "Krótkie wideo",
"all": "Wszystkie",
"category": "Kategoria",
"chapters_horizontal": "Poziomy",
"chapters_vertical": "Pionowy",
"license": "Licencja",
"visibility": "Widoczność"
},
"search": {
"did_you_mean": "Czy chodziło ci o: {0}?",
@ -160,14 +191,21 @@
"music_songs": "YT Music: Utwory",
"music_videos": "YT Music: Teledyski",
"music_albums": "YT Music: Albumy",
"music_playlists": "YT Music: Playlisty"
"music_playlists": "YT Music: Playlisty",
"music_artists": "YT Music: Wykonawcy"
},
"info": {
"cannot_copy": "Nie można skopiować!",
"copied": "Skopiowano!",
"page_not_found": "Strona nie znaleziona",
"preferences_note": "Uwaga: ustawienia są zapisywane w lokalnej pamięci przeglądarki. Usunięcie danych przeglądarki spowoduje ich zresetowanie.",
"local_storage": "Ta akcja wymaga dostępu do lokalnej pamięci, czy pliki cookie są włączone?"
"local_storage": "Ta akcja wymaga dostępu do lokalnej pamięci, czy pliki cookie są włączone?",
"register_no_email_note": "Użycie adresu email jako nazwy użytkownika jest niezalecane. Kontynuować mimo to?",
"next_video_countdown": "Odtwarzanie następnego filmu za {0} s",
"days": "{amount} dni",
"weeks": "{amount} tygodnie",
"hours": "{amount} godziny",
"months": "{amount} miesiące"
},
"subscriptions": {
"subscribed_channels_count": "Licznik subskrybcji: {0}"

View file

@ -1,130 +1,154 @@
{
"titles": {
"trending": "Tendências",
"preferences": "Configurações",
"preferences": "Preferências",
"subscriptions": "Subscrições",
"login": "Iniciar Sessão",
"login": "Iniciar sessão",
"register": "Registar",
"history": "Histórico",
"feed": "Conteúdo",
"playlists": "Listas de Reprodução",
"playlists": "Listas de reprodução",
"account": "Conta",
"instance": "Instância",
"player": "Reprodutor",
"livestreams": "Transmissões ao vivo",
"channels": "Canais"
"livestreams": "Emissões em direto",
"channels": "Canais",
"bookmarks": "Marcadores",
"channel_groups": "Grupos de canais",
"dearrow": "DeArrow"
},
"actions": {
"sort_by": "Ordenar por:",
"most_recent": "Mais Recente",
"least_recent": "Menos Recente",
"channel_name_asc": "Nome do Canal (A-Z)",
"back": "Voltar",
"most_recent": "Mais recentes",
"least_recent": "Mais antigos",
"channel_name_asc": "Nome do canal (A-Z)",
"back": "Recuar",
"uses_api_from": "Utiliza a \"API\" de ",
"enable_sponsorblock": "Ativar \"SponsorBlock\"",
"skip_intro": "Saltar Intermissão/Animação de Introdução",
"skip_outro": "Saltar \"Endcards\"/Créditos",
"skip_preview": "Saltar Pré-Visualização/Recapitulação",
"skip_intro": "Ignorar intermissão/animação de introdução",
"skip_outro": "Ignorar \"Endcards\"/Créditos",
"skip_preview": "Ignorar pré-visualização/recapitulando",
"auto": "Automático",
"dark": "Escuro",
"autoplay_video": "Reproduzir Vídeo Automaticamente",
"audio_only": "Apenas Áudio",
"default_quality": "Qualidade Padrão",
"country_selection": "Seleção de País",
"default_homepage": "Página Inicial Padrão",
"show_comments": "Mostrar Comentários",
"minimize_description_default": "Minimizar Descrição por defeito",
"store_watch_history": "Guardar Histórico de Visualizações",
"instances_list": "Lista de Instâncias",
"enabled_codecs": "\"Codecs\" Activados (Vários)",
"instance_selection": "Seleção de Instância",
"show_more": "Mostrar Mais",
"autoplay_video": "Reproduzir vídeos automaticamente",
"audio_only": "Apenas áudio",
"default_quality": "Qualidade padrão",
"country_selection": "País",
"default_homepage": "Página inicial padrão",
"show_comments": "Mostrar comentários",
"minimize_description_default": "Minimizar descrição por omissão",
"store_watch_history": "Guardar histórico de visualizações",
"instances_list": "Lista de instâncias",
"enabled_codecs": "Codificadores ativados (vários)",
"instance_selection": "Instância",
"show_more": "Mostrar mais",
"import_from_json": "Importar de JSON/CSV",
"export_to_json": "Exportar para JSON",
"loop_this_video": "Repetir este Vídeo",
"auto_play_next_video": "Reproduzir Automaticamente o próximo Vídeo",
"donations": "Doações de desenvolvimento",
"minimize_description": "Minimizar Descrição",
"show_description": "Mostrar Descrição",
"show_recommendations": "Mostrar Recomendações",
"disable_lbry": "Desactivar \"LBRY\" para Transmissão",
"enable_lbry_proxy": "Activar \"Proxy\" para \"LBRY\"",
"view_ssl_score": "Ver Pontuação \"SSL\"",
"search": "Procurar",
"loop_this_video": "Repetir este vídeo",
"auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
"donations": "Doações para o desenvolvimento",
"minimize_description": "Minimizar descrição",
"show_description": "Mostrar descrição",
"show_recommendations": "Mostrar recomendações",
"disable_lbry": "Desativar \"LBRY\" para emissões",
"enable_lbry_proxy": "Ativar proxy para \"LBRY\"",
"view_ssl_score": "Ver avaliação \"SSL\"",
"search": "Pesquisa (Ctrl+K)",
"filter": "Filtrar",
"loading": "A Carregar...",
"clear_history": "Limpar Histórico",
"loading": "A carregar...",
"clear_history": "Limpar histórico",
"subscribe": "Subscrever - {count}",
"unsubscribe": "Anular subscrição - {count}",
"view_subscriptions": "Ver Subscrições",
"channel_name_desc": "Nome do Canal (Z-A)",
"skip_sponsors": "Saltar Patrocínios",
"view_subscriptions": "Ver subscrições",
"channel_name_desc": "Nome do canal (Z-A)",
"skip_sponsors": "Ignorar patrocínios",
"yes": "Sim",
"skip_non_music": "Saltar Música: Secção Não-Musical",
"skip_non_music": "Ignorar música: secção não musical",
"no": "Não",
"theme": "Tema",
"language_selection": "Seleção de Idioma",
"minimize_recommendations": "Minimizar Recomendações",
"language_selection": "Idioma",
"minimize_recommendations": "Minimizar recomendações",
"light": "Claro",
"hide_replies": "Ocultar Respostas",
"load_more_replies": "Carregar mais Respostas",
"skip_highlight": "Saltar Destaque",
"skip_interaction": "Saltar Lembrete de Interação (Subscreve)",
"skip_self_promo": "Saltar Promoção Não Paga/Auto-Promoção",
"buffering_goal": "Objetivo de \"Buffering\" (em segundos)",
"skip_filler_tangent": "Saltar Tangente \"Filler\"",
"hide_replies": "Ocultar respostas",
"load_more_replies": "Carregar mais respostas",
"skip_highlight": "Ignorar destaques",
"skip_interaction": "Ignorar lembrete de interação (subscrição)",
"skip_self_promo": "Ignorar promoção gratuita/auto-promoção",
"buffering_goal": "Objetivo de 'buffer' (em segundos)",
"skip_filler_tangent": "Ignorar segmentos de preenchimento",
"add_to_playlist": "Adicionar à lista de reprodução",
"delete_playlist": "Apagar Lista de Reprodução",
"select_playlist": "Selecionar uma Lista de Reprodução",
"delete_playlist": "Apagar lista de reprodução",
"select_playlist": "Selecionar uma lista de reprodução",
"delete_playlist_confirm": "Apagar esta lista de reprodução?",
"please_select_playlist": "Selecionar uma lista de reprodução se faz favor",
"delete_playlist_video_confirm": "Remover o vídeo da lista de reprodução?",
"please_select_playlist": "Selecione uma lista de reprodução",
"delete_playlist_video_confirm": "Remover vídeo da lista de reprodução?",
"remove_from_playlist": "Remover da lista de reprodução",
"create_playlist": "Criar Lista de Reprodução",
"create_playlist": "Criar lista de reprodução",
"clone_playlist_success": "Clonada com sucesso!",
"clone_playlist": "Clonar Lista de Reprodução",
"show_markers": "Mostrar Marcadores no Leitor",
"delete_account": "Apagar Conta",
"logout": "Terminar sessão neste aparelho",
"minimize_recommendations_default": "Minimizar Recomendações por defeito",
"invalidate_session": "Terminar sessão em todos os aparelhos",
"clone_playlist": "Clonar lista de reprodução",
"show_markers": "Mostrar marcas no reprodutor",
"delete_account": "Apagar conta",
"logout": "Terminar sessão neste dispositivo",
"minimize_recommendations_default": "Minimizar recomendações por omissão",
"invalidate_session": "Terminar sessão em todos os dispositivos",
"different_auth_instance": "Usar uma instância diferente para autenticação",
"instance_auth_selection": "Selecção da Instância para Autenticação",
"confirm_reset_preferences": "Tem a certeza que quer redefinir as suas configurações?",
"instance_auth_selection": "Instância para autenticação",
"confirm_reset_preferences": "Tem a certeza de que deseja repor as preferências?",
"download_as_txt": "Descarregar como .txt",
"reset_preferences": "Redefinir preferências",
"restore_preferences": "Restaurar configurações",
"reset_preferences": "Repor preferências",
"restore_preferences": "Restaurar preferências",
"follow_link": "Seguir ligação",
"piped_link": "Ligação do Piped",
"backup_preferences": "Exportar configurações",
"store_search_history": "Armazenar Histórico de Pesquisa",
"hide_watched": "Ocultar vídeos assistidos no feed",
"backup_preferences": "Exportar preferências",
"store_search_history": "Guardar histórico de pesquisas",
"hide_watched": "Ocultar do feed os vídeos visualizados",
"documentation": "Documentação",
"status_page": "Estado",
"source_code": "Código-fonte",
"instance_donations": "Doações de instâncias",
"minimize_chapters_default": "Minimizar Capítulos por padrão",
"show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
"new_playlist_name": "Novo nome da lista de reprodução",
"minimize_comments": "Minimizar Comentários",
"minimize_chapters_default": "Minimizar capítulos por omissão",
"show_watch_on_youtube": "Mostrar botão Ver no YouTube",
"minimize_comments": "Minimizar comentários",
"back_to_home": "Voltar ao início",
"rename_playlist": "Renomear",
"copy_link": "Copiar ligação",
"time_code": "Código de tempo (em segundos)",
"minimize_comments_default": "Minimizar Comentários por defeito",
"minimize_comments_default": "Minimizar comentários por omissão",
"share": "Partilhar",
"with_timecode": "Partilhar com código de tempo",
"show_chapters": "Capítulos",
"reply_count": "{count} respostas",
"no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!"
"no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!",
"with_playlist": "Partilhar com lista de reprodução",
"playlist_bookmarked": "Marcado",
"bookmark_playlist": "Marcador",
"skip_button_only": "Mostrar botão Ignorar",
"skip_automatically": "Automaticamente",
"min_segment_length": "Tamanho mínimo do segmento (segundos)",
"skip_segment": "Ignorar segmento",
"show_less": "Mostrar menos",
"dismiss": "Ignorar",
"autoplay_next_countdown": "Contagem decrescente até ao próximo vídeo (em segundos)",
"create_group": "Criar grupo",
"group_name": "Nome do grupo",
"auto_display_captions": "Mostrar legendas",
"playlist_description": "Descrição da lista de reprodução",
"okay": "Ok",
"cancel": "Cancelar",
"edit_playlist": "Editar lista de reprodução",
"playlist_name": "Nome da lista de reprodução",
"show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Aplicações recentemente adicionadas",
"enable_dearrow": "Ativar o DeArrow",
"delete_automatically": "Eliminar automaticamente após",
"generate_qrcode": "Gerar código QR"
},
"preferences": {
"instance_name": "Nome da Instância",
"instance_locations": "Localizações da Instância",
"ssl_score": "Pontuação \"SSL\"",
"has_cdn": "Tem \"CDN\"?",
"instance_name": "Nome da instância",
"instance_locations": "Localizações da instância",
"ssl_score": "Avaliação SSL",
"has_cdn": "Tem CDN?",
"version": "Versão",
"registered_users": "Utilizadores Registados",
"registered_users": "Utilizadores registados",
"up_to_date": "Atualizada?"
},
"login": {
@ -135,40 +159,53 @@
"videos": "Vídeos",
"views": "{views} visualizações",
"watched": "Visto",
"sponsor_segments": "Segmentos Patrocinados",
"ratings_disabled": "Classificações Desactivadas",
"sponsor_segments": "Segmentos patrocinados",
"ratings_disabled": "Avaliações desativadas",
"chapters": "Capítulos",
"live": "{0} em Direto",
"shorts": "\"Shorts\""
"live": "{0} em direto",
"shorts": "Curtos",
"all": "Todos",
"category": "Categoria",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical",
"license": "Licença",
"visibility": "Visibilidade"
},
"search": {
"did_you_mean": "Será que querias dizer: {0}?",
"did_you_mean": "Será que queria dizer: {0}?",
"all": "YouTube: Tudo",
"videos": "YouTube: Vídeos",
"channels": "YouTube: Canais",
"music_songs": "YT Music: Músicas",
"music_videos": "YT Music: Vídeos",
"music_albums": "YT Music: Álbuns",
"music_playlists": "YT Music: Listas de Reprodução",
"playlists": "YouTube: Listas de Reprodução"
"music_playlists": "YT Music: Listas de reprodução",
"playlists": "YouTube: Listas de reprodução",
"music_artists": "YT Music: Artistas"
},
"player": {
"watch_on": "Ver em {0}"
},
"comment": {
"pinned_by": "Afixado por {author}",
"disabled": "Os comentários estão desactivados pelo dono do canal.",
"disabled": "O dono do canal desativou os comentários.",
"loading": "A carregar comentários...",
"user_disabled": "Os comentários estão desactivados nas definições."
"user_disabled": "Os comentários estão desativados nas definições."
},
"subscriptions": {
"subscribed_channels_count": "Subscrito a: {0}"
"subscribed_channels_count": "Subscreveu: {0}"
},
"info": {
"copied": "Copiada!",
"cannot_copy": "Não foi possível copiar!",
"page_not_found": "Página não encontrada",
"local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
"preferences_note": "Nota: as configurações são guardadas no armazenamento local to seu navegador. Eliminar os dados de navegação irá redefini-las."
"preferences_note": "Nota: as preferências são guardadas no armazenamento local do seu navegador. Se limpar os dados de navegação, também limpa as preferências.",
"register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
"next_video_countdown": "O próximo vídeo será reproduzido dentro de {0} segundos",
"hours": "{quantidade} hora(s)",
"days": "{quantidade} dia(s)",
"weeks": "{quantidade} semana(s)",
"months": "{quantidade} mês(es)"
}
}

View file

@ -10,7 +10,7 @@
"dark": "Escuro",
"light": "Claro",
"show_comments": "Exibir Comentários",
"country_selection": "Seleção de País",
"country_selection": "País",
"default_homepage": "Página Inicial Padrão",
"default_quality": "Qualidade Padrão",
"autoplay_video": "Reprodução Automática",
@ -34,7 +34,7 @@
"skip_non_music": "Pular Música: Seção não Musical",
"skip_filler_tangent": "Pular Enchimento Tangencial",
"enabled_codecs": "Codecs Ativados (Múltiplos)",
"language_selection": "Seleção de Idioma",
"language_selection": "Idioma",
"yes": "Sim",
"show_more": "Mostrar Mais",
"export_to_json": "Exportar para JSON",
@ -59,10 +59,10 @@
"loop_this_video": "Repetir este Vídeo",
"instances_list": "Lista de Instâncias",
"clear_history": "Limpar Histórico",
"search": "Pesquisar",
"search": "Pesquisar (Ctrl+K)",
"no": "Não",
"show_description": "Exibir Descrição",
"instance_selection": "Seleção de Instância",
"instance_selection": "Instância",
"auto_play_next_video": "Autorreproduzir Próximo Vídeo",
"filter": "Filtro",
"store_watch_history": "Salvar Histórico de Exibição",
@ -81,14 +81,12 @@
"status_page": "Estado",
"source_code": "Código fonte",
"instance_donations": "Doações de instâncias",
"instance_auth_selection": "Seleção de Instância de Autenticação",
"instance_auth_selection": "Instância de Autenticação",
"clone_playlist_success": "Clonada com sucesso!",
"download_as_txt": "Baixar como .txt",
"restore_preferences": "Restaurar preferências",
"back_to_home": "Voltar ao início",
"share": "Compartilhar",
"rename_playlist": "Renomear playlist",
"new_playlist_name": "Novo nome da playlist",
"with_timecode": "Compartilhar com código de tempo",
"piped_link": "Link do Piped",
"follow_link": "Seguir link",
@ -102,7 +100,29 @@
"show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
"minimize_chapters_default": "Minimizar Capítulos por padrão",
"no_valid_playlists": "O arquivo não contém playlists válidas!",
"with_playlist": "Compartilhar com playlist"
"with_playlist": "Compartilhar com playlist",
"bookmark_playlist": "Favorito",
"playlist_bookmarked": "Favoritado",
"skip_automatically": "Automaticamente",
"skip_segment": "Ignorar Segmento",
"min_segment_length": "Comprimento Mínimo do Segmento (em segundos)",
"skip_button_only": "Mostrar botão pular",
"show_less": "Mostrar menos",
"autoplay_next_countdown": "Contagem regressiva padrão até o próximo vídeo (em segundos)",
"dismiss": "Liberar",
"cancel": "Cancelar",
"edit_playlist": "Editar playlist",
"playlist_description": "Descrição da playlist",
"okay": "Ok",
"playlist_name": "Nome da playlist",
"auto_display_captions": "Exibição Automática de Legendas",
"create_group": "Criar grupo",
"group_name": "Nome do grupo",
"show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Layout dos Capítulos no Celular",
"delete_automatically": "Deletar automaticamente após",
"generate_qrcode": "Gerar código QR",
"enable_dearrow": "Ativar DeArrow"
},
"titles": {
"history": "Histórico",
@ -117,7 +137,10 @@
"player": "Player",
"account": "Conta",
"channels": "Canais",
"livestreams": "Transmissões ao vivo"
"livestreams": "Transmissões ao vivo",
"bookmarks": "Favoritos",
"channel_groups": "Grupos de Canais",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Assistir no {0}"
@ -149,7 +172,13 @@
"watched": "Assistido",
"ratings_disabled": "Avaliações Desativadas",
"sponsor_segments": "Segmentos de Patrocinadores",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Todos",
"category": "Categoria",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical",
"license": "Licença",
"visibility": "Visibilidade"
},
"search": {
"did_you_mean": "Você quis dizer: {0}?",
@ -160,14 +189,21 @@
"music_videos": "YT Music: Vídeos",
"music_albums": "YT Music: Álbuns",
"music_playlists": "YT Music: Playlists",
"all": "YouTube: Tudo"
"all": "YouTube: Tudo",
"music_artists": "YT Music: Artistas"
},
"info": {
"copied": "Copiado!",
"cannot_copy": "Não foi possível copiar!",
"preferences_note": "Nota: as preferências são salvas no armazenamento local do seu navegador. A exclusão dos dados do seu navegador irá redefini-los.",
"page_not_found": "página não encontrada",
"local_storage": "Esta ação requer localStorage, os cookies estão ativados?"
"local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
"register_no_email_note": "Usar um e-mail como nome de usuário não é recomendado. Continuar mesmo assim?",
"next_video_countdown": "Reproduzindo o próximo vídeo em {0}s",
"hours": "{amount} hora(s)",
"days": "{amount} dia(s)",
"weeks": "{amount} semana(s)",
"months": "{amount} mês/meses"
},
"subscriptions": {
"subscribed_channels_count": "Inscrito em: {0}"

View file

@ -1,135 +1,158 @@
{
"titles": {
"preferences": "Configurações",
"preferences": "Preferências",
"history": "Histórico",
"feed": "Conteúdo",
"subscriptions": "Subscrições",
"trending": "Tendências",
"login": "Iniciar Sessão",
"login": "Iniciar sessão",
"register": "Registar",
"playlists": "Listas de Reprodução",
"playlists": "Listas de reprodução",
"account": "Conta",
"instance": "Instância",
"player": "Reprodutor",
"livestreams": "Transmissões ao vivo",
"channels": "Canais"
"livestreams": "Emissões em direto",
"channels": "Canais",
"bookmarks": "Marcadores",
"channel_groups": "Grupos de canais",
"dearrow": "DeArrow"
},
"actions": {
"view_subscriptions": "Ver Subscrições",
"view_subscriptions": "Ver subscrições",
"sort_by": "Ordenar por:",
"most_recent": "Mais Recente",
"least_recent": "Menos Recente",
"channel_name_asc": "Nome do Canal (A-Z)",
"channel_name_desc": "Nome do Canal (Z-A)",
"most_recent": "Mais recentes",
"least_recent": "Mais antigos",
"channel_name_asc": "Nome do canal (A-Z)",
"channel_name_desc": "Nome do canal (Z-A)",
"uses_api_from": "Utiliza a \"API\" de ",
"enable_sponsorblock": "Ativar \"SponsorBlock\"",
"skip_sponsors": "Saltar Patrocínios",
"skip_intro": "Saltar Intermissão/Animação de Introdução",
"skip_outro": "Saltar \"Endcards\"/Créditos",
"skip_preview": "Saltar Pré-Visualização/Recapitulação",
"skip_interaction": "Saltar Lembrete de Interação (Subscreve)",
"skip_self_promo": "Saltar Promoção Não Paga/Auto-Promoção",
"skip_non_music": "Saltar Música: Secção Não-Musical",
"skip_highlight": "Saltar Destaque",
"skip_filler_tangent": "Saltar Tangente \"Filler\"",
"skip_sponsors": "Ignorar patrocínios",
"skip_intro": "Ignorar intermissão/animação de introdução",
"skip_outro": "Ignorar \"Endcards\"/Créditos",
"skip_preview": "Ignorar pré-visualização/recapitulando",
"skip_interaction": "Ignorar lembrete de interação (subscrição)",
"skip_self_promo": "Ignorar promoção gratuita/auto-promoção",
"skip_non_music": "Ignorar música: secção não musical",
"skip_highlight": "Ignorar destaques",
"skip_filler_tangent": "Ignorar segmentos de preenchimento",
"theme": "Tema",
"auto": "Automático",
"dark": "Escuro",
"light": "Claro",
"buffering_goal": "Objetivo de \"Buffering\" (em segundos)",
"country_selection": "Seleção de País",
"default_homepage": "Página Inicial Padrão",
"show_comments": "Mostrar Comentários",
"minimize_description_default": "Minimizar Descrição por defeito",
"store_watch_history": "Guardar Histórico de Visualizações",
"language_selection": "Seleção de Idioma",
"enabled_codecs": "\"Codecs\" Activados (Vários)",
"instance_selection": "Seleção de Instância",
"show_more": "Mostrar Mais",
"buffering_goal": "Objetivo de 'buffer' (em segundos)",
"country_selection": "País",
"default_homepage": "Página inicial padrão",
"show_comments": "Mostrar comentários",
"minimize_description_default": "Minimizar descrição por omissão",
"store_watch_history": "Guardar histórico de visualizações",
"language_selection": "Idioma",
"enabled_codecs": "Codificadores ativados (vários)",
"instance_selection": "Instância",
"show_more": "Mostrar mais",
"import_from_json": "Importar de JSON/CSV",
"loop_this_video": "Repetir este Vídeo",
"auto_play_next_video": "Reproduzir Automaticamente o próximo Vídeo",
"donations": "Doações de desenvolvimento",
"minimize_description": "Minimizar Descrição",
"show_description": "Mostrar Descrição",
"minimize_recommendations": "Minimizar Recomendações",
"show_recommendations": "Mostrar Recomendações",
"view_ssl_score": "Ver Pontuação \"SSL\"",
"search": "Procurar",
"hide_replies": "Ocultar Respostas",
"load_more_replies": "Carregar mais Respostas",
"loop_this_video": "Repetir este vídeo",
"auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
"donations": "Doações para o desenvolvimento",
"minimize_description": "Minimizar descrição",
"show_description": "Mostrar descrição",
"minimize_recommendations": "Minimizar recomendações",
"show_recommendations": "Mostrar recomendações",
"view_ssl_score": "Ver avaliação \"SSL\"",
"search": "Pesquisa (Ctrl+K)",
"hide_replies": "Ocultar respostas",
"load_more_replies": "Carregar mais respostas",
"unsubscribe": "Anular subscrição - {count}",
"subscribe": "Subscrever - {count}",
"back": "Voltar",
"audio_only": "Apenas Áudio",
"default_quality": "Qualidade Padrão",
"instances_list": "Lista de Instâncias",
"back": "Recuar",
"audio_only": "Apenas áudio",
"default_quality": "Qualidade padrão",
"instances_list": "Lista de instâncias",
"export_to_json": "Exportar para JSON",
"autoplay_video": "Reproduzir Vídeo Automaticamente",
"autoplay_video": "Reproduzir vídeos automaticamente",
"yes": "Sim",
"enable_lbry_proxy": "Activar \"Proxy\" para \"LBRY\"",
"enable_lbry_proxy": "Ativar proxy para \"LBRY\"",
"no": "Não",
"filter": "Filtrar",
"clear_history": "Limpar Histórico",
"disable_lbry": "Desactivar \"LBRY\" para Transmissão",
"loading": "A Carregar...",
"please_select_playlist": "Selecionar uma lista de reprodução se faz favor",
"select_playlist": "Selecionar uma Lista de Reprodução",
"clear_history": "Limpar histórico",
"disable_lbry": "Desativar \"LBRY\" para emissões",
"loading": "A carregar...",
"please_select_playlist": "Selecione uma lista de reprodução",
"select_playlist": "Selecionar uma lista de reprodução",
"add_to_playlist": "Adicionar à lista de reprodução",
"delete_playlist": "Apagar Lista de Reprodução",
"delete_playlist": "Apagar lista de reprodução",
"download_as_txt": "Descarregar como .txt",
"delete_playlist_confirm": "Apagar esta lista de reprodução?",
"show_markers": "Mostrar Marcadores no Leitor",
"show_markers": "Mostrar marcas no reprodutor",
"remove_from_playlist": "Remover da lista de reprodução",
"delete_playlist_video_confirm": "Remover o vídeo da lista de reprodução?",
"create_playlist": "Criar Lista de Reprodução",
"delete_account": "Apagar Conta",
"logout": "Terminar sessão neste aparelho",
"minimize_recommendations_default": "Minimizar Recomendações por defeito",
"delete_playlist_video_confirm": "Remover vídeo da lista de reprodução?",
"create_playlist": "Criar lista de reprodução",
"delete_account": "Apagar conta",
"logout": "Terminar sessão neste dispositivo",
"minimize_recommendations_default": "Minimizar recomendações por omissão",
"different_auth_instance": "Usar uma instância diferente para autenticação",
"instance_auth_selection": "Selecção da Instância para Autenticação",
"invalidate_session": "Terminar sessão em todos os aparelhos",
"clone_playlist": "Clonar Lista de Reprodução",
"instance_auth_selection": "Instância de autenticação",
"invalidate_session": "Terminar sessão em todos os dispositivos",
"clone_playlist": "Clonar lista de reprodução",
"clone_playlist_success": "Clonada com sucesso!",
"rename_playlist": "Renomear",
"restore_preferences": "Restaurar configurações",
"confirm_reset_preferences": "Tem a certeza que quer redefinir as suas configurações?",
"new_playlist_name": "Novo nome da lista de reprodução",
"restore_preferences": "Restaurar preferências",
"confirm_reset_preferences": "Tem a certeza de que deseja repor as preferências?",
"share": "Partilhar",
"with_timecode": "Partilhar com código de tempo",
"piped_link": "Ligação do Piped",
"follow_link": "Seguir ligação",
"copy_link": "Copiar ligação",
"time_code": "Código de tempo (em segundos)",
"reset_preferences": "Redefinir preferências",
"backup_preferences": "Exportar configurações",
"reset_preferences": "Repor preferências",
"backup_preferences": "Exportar preferências",
"back_to_home": "Voltar ao início",
"minimize_comments_default": "Minimizar Comentários por defeito",
"store_search_history": "Armazenar Histórico de Pesquisa",
"minimize_chapters_default": "Minimizar Capítulos por padrão",
"show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
"minimize_comments_default": "Minimizar comentários por omissão",
"store_search_history": "Histórico de pesquisa da loja",
"minimize_chapters_default": "Minimizar capítulos por omissão",
"show_watch_on_youtube": "Mostrar botão Ver no YouTube",
"show_chapters": "Capítulos",
"hide_watched": "Ocultar vídeos assistidos no feed",
"hide_watched": "Ocultar do feed os vídeos visualizados",
"documentation": "Documentação",
"status_page": "Estado",
"minimize_comments": "Minimizar Comentários",
"minimize_comments": "Minimizar comentários",
"reply_count": "{count} respostas",
"source_code": "Código-fonte",
"instance_donations": "Doações de instâncias",
"no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!"
"no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!",
"bookmark_playlist": "Marcador",
"playlist_bookmarked": "Marcado",
"with_playlist": "Partilhar com lista de reprodução",
"skip_button_only": "Mostrar botão Ignorar",
"skip_automatically": "Automaticamente",
"min_segment_length": "Tamanho mínimo do segmento (segundos)",
"skip_segment": "Ignorar segmento",
"show_less": "Mostrar menos",
"autoplay_next_countdown": "Contagem decrescente até ao próximo vídeo (em segundos)",
"dismiss": "Ignorar",
"create_group": "Criar grupo",
"group_name": "Nome do grupo",
"auto_display_captions": "Mostrar legendas",
"playlist_description": "Descrição da lista de reprodução",
"edit_playlist": "Editar lista de reprodução",
"playlist_name": "Nome da lista de reprodução",
"cancel": "Cancelar",
"okay": "Ok",
"show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Aplicações recentemente adicionadas",
"enable_dearrow": "Ativar o DeArrow",
"delete_automatically": "Eliminar automaticamente após"
},
"comment": {
"pinned_by": "Afixado por {author}",
"disabled": "Os comentários estão desactivados pelo dono do canal.",
"user_disabled": "Os comentários estão desactivados nas definições.",
"disabled": "O dono do canal desativou os comentários.",
"user_disabled": "Os comentários estão desativados nas definições.",
"loading": "A carregar comentários..."
},
"preferences": {
"instance_name": "Nome da Instância",
"instance_locations": "Localizações da Instância",
"has_cdn": "Tem \"CDN\"?",
"registered_users": "Utilizadores Registados",
"ssl_score": "Pontuação \"SSL\"",
"instance_name": "Nome da instância",
"instance_locations": "Localizações da instância",
"has_cdn": "Tem CDN?",
"registered_users": "Utilizadores registados",
"ssl_score": "Avaliação SSL",
"up_to_date": "Atualizada?",
"version": "Versão"
},
@ -141,34 +164,47 @@
"videos": "Vídeos",
"views": "{views} visualizações",
"watched": "Visto",
"sponsor_segments": "Segmentos Patrocinados",
"ratings_disabled": "Classificações Desactivadas",
"sponsor_segments": "Segmentos patrocinados",
"ratings_disabled": "Avaliações desativadas",
"chapters": "Capítulos",
"live": "{0} em Direto",
"shorts": "\"Shorts\""
"live": "{0} em direto",
"shorts": "Curtos",
"all": "Todos",
"category": "Categoria",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical",
"license": "Licença",
"visibility": "Visibilidade"
},
"search": {
"did_you_mean": "Será que querias dizer: {0}?",
"did_you_mean": "Será que queria dizer: {0}?",
"all": "YouTube: Tudo",
"videos": "YouTube: Vídeos",
"channels": "YouTube: Canais",
"playlists": "YouTube: Listas de Reprodução",
"playlists": "YouTube: Listas de reprodução",
"music_songs": "YT Music: Músicas",
"music_videos": "YT Music: Vídeos",
"music_albums": "YT Music: Álbuns",
"music_playlists": "YT Music: Listas de Reprodução"
"music_playlists": "YT Music: Listas de reprodução",
"music_artists": "YT Music: Artistas"
},
"player": {
"watch_on": "Ver em {0}"
},
"subscriptions": {
"subscribed_channels_count": "Subscrito a: {0}"
"subscribed_channels_count": "Subscreveu: {0}"
},
"info": {
"preferences_note": "Nota: as configurações são guardadas no armazenamento local to seu navegador. Eliminar os dados de navegação irá redefini-las.",
"preferences_note": "Nota: as preferências são guardadas no armazenamento local do seu navegador. Se limpar os dados de navegação, também limpa as preferências.",
"page_not_found": "Página não encontrada",
"copied": "Copiada!",
"cannot_copy": "Não foi possível copiar!",
"local_storage": "Esta ação requer localStorage, os cookies estão ativados?"
"local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
"register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
"next_video_countdown": "A reproduzir o vídeo seguinte em {0}s",
"hours": "{quantidade} hora(s)",
"days": "{quantidade} dia(s)",
"weeks": "{quantidade} semana(s)",
"months": "{quantidade} mês(es)"
}
}

View file

@ -1,152 +1,182 @@
{
"actions": {
"back_to_home": "Înapoi acasă",
"store_search_history": "Salveaza Istoric de Cautări",
"with_timecode": "Distribuie cu cod de timp",
"store_search_history": "Rețineți istoricul de căutări",
"with_timecode": "Distribuiți cu timpul de cod",
"piped_link": "Link Piped",
"time_code": "Cod de timp (secunde)",
"show_chapters": "Capitole",
"search": "Caută",
"logout": "Scoate contul de pe acest dispozitiv",
"add_to_playlist": "Adaugă în Playlist",
"remove_from_playlist": "Șterge din Playlist",
"create_playlist": "Creează Playlist",
"delete_playlist": "Șterge Playlist",
"delete_playlist_confirm": "Ștergi acest playlist?",
"please_select_playlist": "Te rog să alegi un playlist",
"minimize_recommendations_default": "Ascunde Recomandări ca default",
"subscribe": "Abonează-te - {count}",
"least_recent": "Mai puțin recente",
"channel_name_asc": "Nume Canal (A-Z)",
"channel_name_desc": "Nume Canal (Z-A)",
"search": "Căutare (Ctrl+K)",
"logout": "Deconectați-vă de pe acest dispozitiv",
"add_to_playlist": "Adăugare în playlist",
"remove_from_playlist": "Ștergere din playlist",
"create_playlist": "Creați playlist",
"delete_playlist": "Ștergeți playlist",
"delete_playlist_confirm": "Ștergi acest playlist?",
"please_select_playlist": "Vă rugăm să alegeți un playlist",
"minimize_recommendations_default": "Minimizați recomandările în mod implicit",
"subscribe": "Abonare - {count}",
"least_recent": "Cele mai vechi",
"channel_name_asc": "Numele canalului (A-Z)",
"channel_name_desc": "Nume canalului (Z-A)",
"back": "Înapoi",
"uses_api_from": "Folosește API de la ",
"enable_sponsorblock": "Activează Sponsorblock",
"skip_intro": "Sari animația de Intermisie / Intro",
"skip_preview": "Sari Preview / Recapitulare",
"skip_self_promo": "Sari Promoția Neplătita / Proprie",
"skip_non_music": "Sari Muzica: Secțiunea de Non-Muzică",
"skip_highlight": "Sari Highlight",
"show_markers": "Arată Marcaje in Player",
"uses_api_from": "Se folosește API-ul de la ",
"enable_sponsorblock": "Activați Sponsorblock",
"skip_intro": "Omitere pauze/animații de intro",
"skip_preview": "Omitere previzualizare/recapitulare",
"skip_self_promo": "Omitere promoție neplătită/autopromovare",
"skip_non_music": "Omitere muzică: Secțiune non-muzicală",
"skip_highlight": "Omitere evidențiere",
"show_markers": "Se afișează marcatori în player",
"dark": "Întunecat",
"auto": "Auto",
"audio_only": "Doar Audio",
"default_quality": "Calitate Default",
"country_selection": "Selecție Țară",
"default_homepage": "Pagina de Acasă",
"minimize_comments_default": "Ascunde Comentariile",
"minimize_description_default": "Ascunde Descrierea",
"language_selection": "Selecție Limbă",
"audio_only": "Doar audio",
"default_quality": "Calitate implicită",
"country_selection": "Țară",
"default_homepage": "Pagina principală implicită",
"minimize_comments_default": "Minimizați comentariile în mod implicit",
"minimize_description_default": "Minimizați descrierea în mod implicit",
"language_selection": "Limbă",
"instances_list": "Listă de Instanțe",
"enabled_codecs": "Activează Codecuri (Multiple)",
"loop_this_video": "Repornește Video-ul",
"donations": "Donații",
"show_recommendations": "Arată Recomandări",
"disable_lbry": "Oprește LBRY pentru Streaming",
"enable_lbry_proxy": "Activează Proxy pentru LBRY",
"view_ssl_score": "Vezi Scor SSL",
"enabled_codecs": "Codecuri activate (multiple)",
"loop_this_video": "Repetare video",
"donations": "Donații pentru dezvoltare",
"show_recommendations": "Afișați recomandările",
"disable_lbry": "Dezactivați LBRY pentru streaming",
"enable_lbry_proxy": "Activați proxy pentru LBRY",
"view_ssl_score": "Vedeți scorul SSL",
"filter": "Filtru",
"loading": "Se încarcă...",
"clear_history": "Șterge Istoric",
"hide_replies": "Ascunde răspunsuri",
"load_more_replies": "Mai multe Răspunsuri",
"delete_playlist_video_confirm": "Ștergi video-ul din playlist?",
"select_playlist": "Alege un Playlist",
"delete_account": "Șterge Contul",
"show_watch_on_youtube": "Arata buton de Vezi pe YouTube",
"invalidate_session": "Deconectează-te peste tot",
"instance_auth_selection": "Selecție Instanță de Autentificare",
"clone_playlist_success": "Clonat cu succes!",
"reset_preferences": "Resetează preferințele",
"confirm_reset_preferences": "Sigur vrei să îți resetezi preferințele?",
"rename_playlist": "Redenumește playlist",
"new_playlist_name": "Nume playlist nou",
"share": "Distribuie",
"follow_link": "Deschide link",
"copy_link": "Copiază link",
"hide_watched": "Ascunde video-urile vizionate",
"clear_history": "Ștergeți istoricul",
"hide_replies": "Ascundeți răspunsurile",
"load_more_replies": "Mai multe răspunsuri",
"delete_playlist_video_confirm": "Ștergeți videoclipul din playlist?",
"select_playlist": "Selectați un playlist",
"delete_account": "Ștergeți-vă contul",
"show_watch_on_youtube": "Afișați butonul „Vizionați pe YouTube”",
"invalidate_session": "Deconectați toate dispozitivele",
"instance_auth_selection": "Instanța de autentificare",
"clone_playlist_success": "Clonată cu succes!",
"reset_preferences": "Resetați preferințele",
"confirm_reset_preferences": "Sunteți sigur că doriți să vă resetați preferințele?",
"share": "Distribuiți",
"follow_link": "Urmați link-ul",
"copy_link": "Copiați link-ul",
"hide_watched": "Ascundeți videoclipurile vizionate din flux",
"documentation": "Documentație",
"status_page": "Status",
"source_code": "Cod sursă",
"instance_donations": "Donații instanță",
"reply_count": "{count} răspunsuri",
"minimize_chapters_default": "Ascunde Capitole ca default",
"skip_sponsors": "Sari Sponsori",
"different_auth_instance": "Folosește altă instanță pentru autentificare",
"clone_playlist": "Clonează Playlist",
"backup_preferences": "Backup preferințe",
"unsubscribe": "Dezabonează-te - {count}",
"view_subscriptions": "Vezi Subscripții",
"sort_by": "Sortează după:",
"download_as_txt": "Descarcă ca .txt",
"most_recent": "Cele mai Recente",
"skip_outro": "Sari Endcards / Credite",
"skip_interaction": "Sari Reminder de interacțiune (Subscribe)",
"minimize_chapters_default": "Minimizați capitolele în mod implicit",
"skip_sponsors": "Omitere sponsori",
"different_auth_instance": "Folosiți o instanță diferită pentru autentificare",
"clone_playlist": "Clonați lista de redare",
"backup_preferences": "Faceți backup la preferințe",
"unsubscribe": "Dezabonare - {count}",
"view_subscriptions": "Vedeți abonamentele",
"sort_by": "Sortare după:",
"download_as_txt": "Descărcați ca .txt",
"most_recent": "Cele mai recente",
"skip_outro": "Omitere carduri de sfârșit/mulțumiri",
"skip_interaction": "Omitere reamintiri de interacțiune (abonare)",
"light": "Luminat",
"restore_preferences": "Restore preferințe",
"skip_filler_tangent": "Sari Tangenta Filler",
"restore_preferences": "Restaurați preferințele",
"skip_filler_tangent": "Omitere tangentă de umplere",
"theme": "Temă",
"autoplay_video": "Autopornește Video",
"buffering_goal": "Buffering Goal (secunde)",
"instance_selection": "Selecție Instanță",
"store_watch_history": "Salvează Istoricul de Vizionare",
"minimize_comments": "Ascunde Comentarii",
"minimize_description": "Ascunde Descriere",
"show_more": "Mai Mult",
"autoplay_video": "Redare automată video",
"buffering_goal": "Obiectiv de tamponare (în secunde)",
"instance_selection": "Instanță",
"store_watch_history": "Rețineți istoricul de vizionări",
"minimize_comments": "Minimizați comentariile",
"minimize_description": "Minimizați descrierea",
"show_more": "Mai mult",
"no": "Nu",
"export_to_json": "Exportă ca JSON",
"import_from_json": "Importă din JSON/CSV",
"auto_play_next_video": "Autopornește următorul Video",
"minimize_recommendations": "Ascunde Recomandări",
"export_to_json": "Exportați ca JSON",
"import_from_json": "Importați din JSON/CSV",
"auto_play_next_video": "Redați automat următorul video",
"minimize_recommendations": "Minimizați recomandările",
"yes": "Da",
"show_comments": "Arată Comentarii",
"show_description": "Arată Descriere"
"show_comments": "Afișați comentariile",
"show_description": "Afișați descrierea",
"bookmark_playlist": "Marcați",
"no_valid_playlists": "Fișierul nu conține playlist-uri valide!",
"skip_automatically": "Automat",
"min_segment_length": "Lungimea minimă a segmentului (în secunde)",
"skip_segment": "Omitere segment",
"skip_button_only": "Afișați butonul de omitere",
"with_playlist": "Distribuiți cu playlist",
"playlist_bookmarked": "Marcat",
"show_less": "Mai puțin",
"autoplay_next_countdown": "Numărătoarea inversă implicită până la următorul videoclip (în secunde)",
"dismiss": "Concediază",
"group_name": "Numele grupului",
"create_group": "Creați un grup",
"auto_display_captions": "Afișare automată subtitrări",
"playlist_name": "Numele playlist-ului",
"okay": "OK",
"playlist_description": "Descrierea playlist-ului",
"edit_playlist": "Editează playlist-ul",
"cancel": "Anulare",
"chapters_layout_mobile": "Mod afișare capitole pe mobil",
"show_search_suggestions": "Afișare sugestii căutare",
"enable_dearrow": "Activați DeArrow",
"delete_automatically": "Șterge automat după"
},
"preferences": {
"ssl_score": "Scor SSL",
"version": "Versiune",
"up_to_date": "Actualizat?",
"instance_name": "Nume Instanță",
"instance_locations": "Locațiile Instanței",
"instance_name": "Nume instanță",
"instance_locations": "Locațiile instanței",
"has_cdn": "Are CDN?",
"registered_users": "Useri Înregistrați"
"registered_users": "Utilizatori înregistrați"
},
"comment": {
"user_disabled": "Comentariile sunt dezactivate în setări.",
"pinned_by": "Pomovat de {author}",
"disabled": "Comentariile sunt dezactivate de creator.",
"loading": "Se incarcă comentariile..."
"pinned_by": "Fixat de {author}",
"disabled": "Comentariile sunt dezactivate de către autor.",
"loading": "Se încarcă comentariile..."
},
"video": {
"views": "{views} vizionări",
"chapters": "Capitole",
"shorts": "Shorts",
"watched": "Văzut",
"sponsor_segments": "Segmente Sponsori",
"ratings_disabled": "Like-uri dezactivate",
"live": "{0} Live",
"videos": "Video-uri"
"watched": "Vizionat",
"sponsor_segments": "Segmente sponsori",
"ratings_disabled": "Evaluări dezactivate",
"live": "{0} în direct",
"videos": "Videoclipuri",
"category": "Categorie",
"all": "Tot",
"chapters_horizontal": "Orizontal",
"chapters_vertical": "Vertical"
},
"login": {
"username": "Nume User",
"username": "Nume de utilizator",
"password": "Parolă"
},
"search": {
"videos": "YouTube: Video-uri",
"music_playlists": "YT Music: Playlisturi",
"did_you_mean": "Voiai să scrii: {0}?",
"videos": "YouTube: Videoclipuri",
"music_playlists": "YT Music: Liste de redare",
"did_you_mean": "Vă refereați la: {0}?",
"all": "YouTube: Toate",
"channels": "YouTube: Canale",
"playlists": "YouTube: Playlisturi",
"music_songs": "YT Music: Cântece",
"music_videos": "YT Music: Video-uri",
"music_albums": "YT Music: Albume"
"playlists": "YouTube: Liste de redare",
"music_songs": "YT Music: Muzică",
"music_videos": "YT Music: Videoclipuri",
"music_albums": "YT Music: Albume",
"music_artists": "YT Music: Artiști"
},
"info": {
"cannot_copy": "Nu se poate copia!",
"preferences_note": "Sfat: preferințele sunt salvate in memoria locala a browserului tău. Ștergând datele browserului le ștergi si pe ele.",
"page_not_found": "Pagină negăsită",
"copied": "S-a copiat!"
"cannot_copy": "Nu s-a putut copia!",
"preferences_note": "Notă: preferințele sunt salvate în memoria locală a browserului dvs. Ștergerea datelor din browserul dvs. le va reseta.",
"page_not_found": "Pagina nu a fost găsită",
"copied": "Copiat!",
"register_no_email_note": "Utilizarea unui e-mail ca nume de utilizator nu este recomandată. Continuați oricum?",
"local_storage": "Această acțiune necesită localStorage, sunt activate cookie-urile?",
"next_video_countdown": "Redarea următorului videoclip în {0}s",
"days": "{amount} zi(le)"
},
"subscriptions": {
"subscribed_channels_count": "Abonat la: {0}"
@ -154,19 +184,22 @@
"titles": {
"register": "Înregistrare",
"history": "Istoric",
"subscriptions": "Abonări",
"playlists": "Playlisturi",
"subscriptions": "Abonamente",
"playlists": "Liste de redare",
"account": "Cont",
"instance": "Instanță",
"login": "Logare",
"feed": "Abonări",
"trending": "Trending",
"livestreams": "Live-uri",
"login": "Autentificare",
"feed": "Flux",
"trending": "Tendințe",
"livestreams": "Fluxuri live",
"channels": "Canale",
"preferences": "Preferințe",
"player": "Player"
"player": "Player-ul",
"bookmarks": "Marcaje",
"channel_groups": "Grupuri de canale",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Vezi pe {0}"
"watch_on": "Vizionați pe {0}"
}
}

View file

@ -5,14 +5,17 @@
"register": "Регистрация",
"feed": "Подписки",
"preferences": "Настройки",
"history": "История просмотров",
"subscriptions": "Ваши подписки",
"history": "История",
"subscriptions": "Подписки",
"playlists": "Плейлисты",
"account": "Аккаунт",
"player": "Плеер",
"instance": "Сервер",
"livestreams": "Прямые трансляции",
"channels": "Каналы"
"channels": "Каналы",
"bookmarks": "Закладки",
"channel_groups": "Группы каналов",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Смотреть на {0}"
@ -27,7 +30,7 @@
"channel_name_asc": "Имя канала (А-Я)",
"channel_name_desc": "Имя канала (Я-А)",
"back": "Назад",
"uses_api_from": "Использовать API, предоставляемое ",
"uses_api_from": "Использовать API ",
"enable_sponsorblock": "Включить Sponsorblock",
"skip_sponsors": "Пропускать спонсорскую рекламу",
"skip_intro": "Пропускать заставку/интро",
@ -53,7 +56,7 @@
"instances_list": "Список зеркал сервиса Piped",
"enabled_codecs": "Включённые кодеки (Можно выбрать несколько)",
"instance_selection": "Выбор зеркала сервиса Piped",
"show_more": "Показать больше",
"show_more": "Показать еще",
"yes": "Да",
"no": "Нет",
"export_to_json": "Экспорт в JSON",
@ -66,9 +69,9 @@
"minimize_recommendations": "Свернуть рекомендации",
"show_recommendations": "Показать рекомендации",
"disable_lbry": "Отключить LBRY для стриминга",
"enable_lbry_proxy": "Проксировать видео с LBRY",
"enable_lbry_proxy": "Проксировать видео для LBRY",
"view_ssl_score": "Посмотреть настройки SSL",
"search": "Поиск",
"search": "Поиск (Ctrl+K)",
"filter": "Фильтр",
"loading": "Загрузка...",
"clear_history": "Очистить историю",
@ -84,52 +87,72 @@
"select_playlist": "Выбрать плейлист",
"delete_playlist_confirm": "Удалить этот плейлист?",
"delete_playlist_video_confirm": "Удалить видео из плейлиста?",
"show_markers": "Показать Mаркеры Hа Проигрывателе",
"show_markers": "Показать маркеры на проигрывателе",
"delete_account": "Удалить аккаунт",
"logout": "Выйти из этого устройства",
"download_as_txt": "Скачать как .txt",
"minimize_recommendations_default": "Скрыть Рекомендации по умолчанию",
"invalidate_session": "Выйти из всех устройств",
"different_auth_instance": "Использовать другие средства аутентификации",
"instance_auth_selection": "Выбор средств аутентификации",
"different_auth_instance": "Использовать другое зеркало для аутентификации",
"instance_auth_selection": "Выбор зеркала аутентификации",
"clone_playlist": "Клонировать плейлист",
"clone_playlist_success": "Клонирование прошло успешно!",
"show_chapters": "Части",
"rename_playlist": "Переименовать плейлист",
"new_playlist_name": "Новое название плейлиста",
"clone_playlist_success": "Успешно клонировано!",
"show_chapters": "Главы",
"share": "Поделиться",
"with_timecode": "Поделиться с отметкой времени",
"with_timecode": "Поделиться с таймкодом",
"piped_link": "Ссылка Piped",
"follow_link": "Ссылка подписки",
"follow_link": "Перейти по ссылке",
"copy_link": "Скопировать ссылку",
"time_code": "Тайм-код (в секундах)",
"time_code": "Таймкод (в секундах)",
"reset_preferences": "Сбросить настройки",
"confirm_reset_preferences": "Вы уверены, что хотите сбросить настройки?",
"backup_preferences": "Бэкап настроек",
"restore_preferences": "Восстановить настройки",
"back_to_home": "Вернутся на главную",
"back_to_home": "Назад на главную",
"store_search_history": "Хранить историю поиска",
"hide_watched": "Скрыть просмотренные видео в ленте",
"status_page": "Статус",
"source_code": "Исходный код",
"documentation": "Пожертвования сервера",
"instance_donations": "Пожертвования сервера",
"documentation": "Документация",
"instance_donations": "Пожертвования зеркала",
"reply_count": "{count} ответов",
"minimize_comments_default": "Сворачивать комментарии по умолчанию",
"minimize_comments": "Свернуть комментарии",
"show_watch_on_youtube": "Показать кнопку Смотреть на YouTube",
"minimize_chapters_default": "Скрывать главы по умолчанию",
"no_valid_playlists": "Файл не содержит действительных списков воспроизведения!"
"no_valid_playlists": "Файл не содержит действующих плейлистов!",
"with_playlist": "Поделиться с плейлистом",
"bookmark_playlist": "Закладка",
"playlist_bookmarked": "В закладках",
"skip_automatically": "Автоматически",
"min_segment_length": "Минимальная длина сегмента (в секундах)",
"skip_button_only": "Показать кнопку \"Пропустить\"",
"skip_segment": "Пропустить сегмент",
"show_less": "Показать меньше",
"autoplay_next_countdown": "Отсчет по умолчанию до следующего видео (в секундах)",
"dismiss": "Отклонить",
"group_name": "Имя группы",
"create_group": "Создать группу",
"cancel": "Отмена",
"edit_playlist": "Редактировать плейлист",
"playlist_description": "Описание плейлиста",
"okay": "Хорошо",
"auto_display_captions": "Авто-отображение субтитров",
"playlist_name": "Название плейлиста",
"show_search_suggestions": "Показать поисковые предложения",
"chapters_layout_mobile": "Расположение глав в мобильном виде",
"delete_automatically": "Автоматическое удаление после",
"enable_dearrow": "Включить DeArrow"
},
"comment": {
"pinned_by": "Прикреплено пользователем {author}",
"pinned_by": "Закреплено пользователем {author}",
"loading": "Загрузка комментариев...",
"user_disabled": "Комментарии отключены в настройках.",
"disabled": "Коментарии отключены автором."
"disabled": "Комментарии отключены автором."
},
"preferences": {
"instance_name": "Название",
"instance_locations": "Местоположение",
"instance_name": "Имя зеркала",
"instance_locations": "Местоположения зеркала",
"has_cdn": "Имеется CDN?",
"ssl_score": "Оценка настроек SSL",
"registered_users": "Зарегистрировано пользователей",
@ -137,7 +160,7 @@
"up_to_date": "Версия актуальна?"
},
"login": {
"username": "Аккаунт на Piped",
"username": "Имя пользователя",
"password": "Пароль"
},
"video": {
@ -148,7 +171,13 @@
"ratings_disabled": "Оценки отключены",
"live": "{0} В эфире",
"chapters": "Содержание",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Все",
"category": "Категория",
"chapters_horizontal": "Горизонтально",
"chapters_vertical": "Вертикально",
"visibility": "Видимость",
"license": "Лицензия"
},
"search": {
"did_you_mean": "Может быть вы имели в виду: {0}?",
@ -159,16 +188,23 @@
"music_songs": "YT Music: Композиции",
"music_videos": "YT Music: Видео",
"music_albums": "YT Music: Альбомы",
"music_playlists": "YT Music: Плейлисты"
"music_playlists": "YT Music: Плейлисты",
"music_artists": "YT Music: Исполнители"
},
"subscriptions": {
"subscribed_channels_count": "Подписан на: {0}"
},
"info": {
"preferences_note": "Примечание: настройки сохранены в локальном хранилище браузера. При удалении данных браузера они будут удалены.",
"preferences_note": "Примечание: настройки сохранены в локальном хранилище браузера. Удаление данных вашего браузера сбросит их.",
"copied": "Скопировано!",
"cannot_copy": "Не получилось скопировать!",
"cannot_copy": "Не удалось скопировать!",
"page_not_found": "Страница не найдена",
"local_storage": "Это действие требует локального хранилища (localStorage), разрешены ли файлы cookie?"
"local_storage": "Это действие требует разрешения localStorage, включены ли cookie-файлы?",
"register_no_email_note": "Использование электронной почты в качестве имени пользователя не рекомендуется. Продолжить?",
"next_video_countdown": "Следующие видео через {0} с",
"days": "{amount} дней",
"hours": "{amount} час(ов)",
"weeks": "{amount} недель",
"months": "{amount} месяцев"
}
}

207
src/locales/si.json Normal file
View file

@ -0,0 +1,207 @@
{
"titles": {
"trending": "නැගී එන",
"login": "පිවිසෙන්න",
"register": "ලියාපදිංචිය",
"preferences": "අභිප්‍රේත",
"history": "ඉතිහාසය",
"subscriptions": "දායකත්‍ව",
"account": "ගිණුම",
"player": "වාදකය",
"livestreams": "සජීව ප්‍රචාර",
"channels": "නාලිකා",
"playlists": "වාදන ලැයිස්තු",
"instance": "සේවාදායකය",
"bookmarks": "පොත්යොමු",
"feed": "සංග්‍රහය",
"channel_groups": "නාලිකා සමූහ"
},
"actions": {
"subscribe": "දායකවන්න - {count}",
"unsubscribe": "දායක නොවන්න - {count}",
"most_recent": "වඩාත් මෑත",
"least_recent": "ආසන්න මෑත",
"channel_name_asc": "නාලිකාවේ නම (අ-ෆ)",
"channel_name_desc": "නාලිකාවේ නම (ෆ-අ)",
"back": "ආපසු",
"skip_sponsors": "අනුග්‍රහකයින් මඟහරින්න",
"skip_outro": "අවසන් කාඩ්පත/දායක ලැයිස්තුව මඟ හරින්න",
"skip_preview": "පෙරදසුන/සාරාංශය මඟ හරින්න",
"skip_self_promo": "නොගෙවූ/ස්වයං ප්‍රවර්ධන මඟ හරින්න",
"skip_filler_tangent": "අදාළ නොවන කොටස් මඟහරින්න",
"theme": "තේමාව",
"dark": "අඳුරු",
"light": "දීප්ත",
"autoplay_video": "දෘශ්‍යක ඉබේ වාදනය",
"auto": "ස්වයං",
"default_quality": "පෙරනිමි ගුණත්‍වය",
"default_homepage": "පෙරනිමි මුල් පිටුව",
"show_markers": "වාදකයේ මාකර් පෙන්වන්න",
"buffering_goal": "අන්තරාචය ඉලක්කය (තත්. වලින්)",
"enable_sponsorblock": "Sponsorblock සබල කරන්න",
"sort_by": "පෙළගසන්න:",
"skip_highlight": "ඉස්මතු කිරීම් මඟ හරින්න",
"language_selection": "භාෂාව",
"show_more": "තව පෙන්වන්න",
"yes": "ඔව්",
"no": "නැහැ",
"export_to_json": "JSON ලෙස නිර්යාත කරන්න",
"import_from_json": "JSON/CSV වෙතින් ආයාත කරන්න",
"loop_this_video": "මෙම දෘශ්‍යකය පුඩුලන්න",
"auto_play_next_video": "ඊළඟ දෘශ්‍යකය ඉබේ වාදනය කරන්න",
"donations": "සංවර්ධන පරිත්‍යාග",
"minimize_comments": "අදහස් හකුළන්න",
"show_comments": "අදහස් පෙන්වන්න",
"minimize_description": "විස්තරය හකුළන්න",
"show_description": "සවිස්තරය පෙන්වන්න",
"minimize_recommendations": "නිර්දේශ හකුළන්න",
"show_recommendations": "නිර්දේශ පෙන්වන්න",
"store_watch_history": "නැරඹුම් ඉතිහාසය ගබඩා කරන්න",
"enabled_codecs": "සබල කර ඇති කෝඩෙක්ස් (බහු)",
"minimize_description_default": "පෙරනිමි පරිදි සවිස්තරය සඟවන්න",
"instances_list": "සේවාදායක ලැයිස්තුව",
"instance_selection": "සේවාදායකය",
"view_ssl_score": "SSL ලකුණු බලන්න",
"search": "සොයන්න (Ctrl+K)",
"loading": "පූරණය වෙමින්...",
"hide_replies": "පිළිතුරු සඟවන්න",
"load_more_replies": "තවත් පිළිතුරු පෙන්වන්න",
"add_to_playlist": "වාදන ලැයිස්තුවට දමන්න",
"create_playlist": "වාදන ලැයිස්තුව සාදන්න",
"delete_playlist": "වාදන ලැයිස්තුව මකන්න",
"select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
"please_select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
"delete_account": "ගිණුම මකන්න",
"logout": "මෙම උපාංගයෙන් නික්මෙන්න",
"minimize_recommendations_default": "පෙරනිමි පරිදි නිර්දේශ හකුළන්න",
"invalidate_session": "සියළුම උපාංග නික්මවන්න",
"clone_playlist": "වාදන ලැයිස්තුවේ අනුපිටපතක්",
"download_as_txt": ".txt ලෙස බාගන්න",
"reset_preferences": "අභිප්‍රේත නැවත සකසන්න",
"backup_preferences": "අභිප්‍රේත උපස්ථ කරන්න",
"restore_preferences": "අභිප්‍රේත ප්‍රත්‍යර්පණය කරන්න",
"back_to_home": "ආපසු මුල් පිටුවට",
"share": "බෙදාගන්න",
"with_timecode": "කාල කේතය සමඟ බෙදා ගන්න",
"piped_link": "පයිප්ඩ් සබැඳිය",
"copy_link": "සබැඳියේ පිටපතක්",
"time_code": "කාල කේතය (තත්පර වලින්)",
"show_chapters": "පරිච්ඡේද",
"status_page": "තත්‍වය",
"source_code": "ප්‍රභව කේතය",
"documentation": "ප්‍රලේඛනය",
"reply_count": "පිළිතුරු {count}",
"with_playlist": "වාදන ලැයිස්තුව සමඟ බෙදා ගන්න",
"bookmark_playlist": "පොත්යොමුවක්",
"show_watch_on_youtube": "යූටියුබ්හි නරඹන්න බොත්තම පෙන්වන්න",
"filter": "පෙරහන",
"instance_donations": "සේවාදායක පරිත්‍යාග",
"instance_auth_selection": "සත්‍යතාව තහවුරු කිරීම සඳහා සේවාදායකයක් තේරීම",
"view_subscriptions": "දායකත්‍ව බලන්න",
"uses_api_from": "භාවිතා වන යෙ.ක්‍ර.මු. (API): ",
"skip_intro": "විරාම/හඳුන්වාදීමේ සජීවිකරණය මඟ හරින්න",
"skip_interaction": "අන්තර් ක්‍රියා මතක් කිරීම මඟ හරින්න (දායක වන්න)",
"skip_non_music": "ගීත: ගීතය නොවන කොටස මඟ හරින්න",
"remove_from_playlist": "වාදන ලැයිස්තුවෙන් ඉවතලන්න",
"audio_only": "හඬ පමණි",
"country_selection": "රට",
"minimize_comments_default": "පෙරනිමි පරිදි අදහස් සඟවන්න",
"clear_history": "ඉතිහාසය මකන්න",
"disable_lbry": "ප්‍රචාරය සඳහා LBRY අබල කරන්න",
"delete_playlist_video_confirm": "වාදන ලැයිස්තුවෙන් දෘශ්‍යකය ඉවත් කරන්නද?",
"delete_playlist_confirm": "මෙම වාදන ලැයිස්තුව මකන්නද?",
"minimize_chapters_default": "පෙරනිමි පරිදි පරිච්ඡේද හකුළන්න",
"clone_playlist_success": "අනුපිටපතක් සෑදිණි!",
"confirm_reset_preferences": "ඔබගේ අභිප්‍රේත නැවත සැකසීමට වුවමනා ද?",
"follow_link": "සබැඳියට යන්න",
"store_search_history": "සෙවුම් ඉතිහාසය ගබඩා කරන්න",
"no_valid_playlists": "ගොනුවේ වලංගු වාදන ලැයිස්තු අඩංගු නොවේ!",
"playlist_bookmarked": "පොත්යොමුවක් යෙදිණි",
"enable_lbry_proxy": "LBRY සඳහා ප්‍රතියුක්තය සබල කරන්න",
"different_auth_instance": "සත්‍යතාව තහවුරු කිරීම සඳහා වෙනත් සේවාදායකයක් භාවිතා කරන්න",
"hide_watched": "සංග්‍රහයෙන් නැරඹූ දෘශ්‍යක සඟවන්න",
"skip_button_only": "මඟහරින බොත්තම පෙන්වන්න",
"skip_automatically": "ස්වයංක්‍රීයව",
"skip_segment": "කොටස මඟ හරින්න",
"min_segment_length": "අවම කොටස් දිග (තත්පර වලින්)",
"show_less": "අඩුවෙන් පෙන්වන්න",
"dismiss": "අයින් කරන්න",
"autoplay_next_countdown": "ඊළඟ දෘශ්‍යකය තෙක් ගණන් කිරීම (තත්. වලින්)",
"group_name": "සමූහයේ නම",
"create_group": "සමූහය සාදන්න",
"cancel": "අවලංගු",
"okay": "හරි",
"edit_playlist": "වාදන ලැයිස්තුව සංස්කරණය",
"playlist_name": "වාදන ලැයිස්තුවේ නම",
"playlist_description": "වාදන ලැයිස්තුවේ සවිස්තරය",
"auto_display_captions": "උපසිරැසි ස්වයංක්‍රීයව පෙන්වන්න",
"show_search_suggestions": "සෙවුම් යෝජනා පෙන්වන්න",
"delete_automatically": "මෙයින් පසුව මකන්න",
"generate_qrcode": "QR කේතයක් උත්පාදනය"
},
"player": {
"watch_on": "{0} හි නරඹන්න"
},
"comment": {
"pinned_by": "{author} විසින් අමුණන ලදී",
"loading": "අදහස් පූරණය වෙමින්...",
"disabled": "උඩුගත කරන්නා විසින් අදහස් අබල කර ඇත.",
"user_disabled": "සැකසුම් හරහා අදහස් අබල කර ඇත."
},
"preferences": {
"has_cdn": "CDN තිබේද?",
"version": "අනුවාදය",
"up_to_date": "යාවත්කාලීනද?",
"instance_name": "සේවාදායකයේ නම",
"registered_users": "ලියාපදිංචි පරිශ්‍රීලකයින්",
"ssl_score": "SSL ලකුණු",
"instance_locations": "සේවාදායකයේ ස්ථානය"
},
"login": {
"username": "පරිශ්‍රීලක නාමය",
"password": "මුරපදය"
},
"video": {
"videos": "දෘශ්‍යක",
"views": "බැලීම් {views}",
"watched": "නැරඹුවා",
"sponsor_segments": "අනුග්‍රාහක අංශ",
"chapters": "පරිච්ඡේද",
"shorts": "කෙටි දෘශ්‍යක",
"ratings_disabled": "ශ්‍රේණිගත කිරීම් අබල කර ඇත",
"live": "{0} සජීවී",
"all": "සියල්ල",
"category": "ප්‍රවර්ගය",
"chapters_vertical": "සිරස්",
"license": "බලපත්‍රය",
"chapters_horizontal": "තිරස්"
},
"search": {
"did_you_mean": "ඔබ අදහස් කළේ: {0}?",
"videos": "යූටියුබ්: දෘශ්‍යක",
"playlists": "යූටියුබි: වාදන ලැයිස්තු",
"music_songs": "යූටියුබි ගීත: ගීත",
"music_videos": "යූටියුබි ගීත: දෘශ්‍යක",
"music_albums": "YT Music: ඇල්බම",
"music_playlists": "යූටියුබි ගීත: වාදන ලැයිස්තු",
"channels": "යූටියුබ්: නාලිකා",
"all": "යූටියුබි: සියල්ල",
"music_artists": "යූටියුබ් ගීත: කලාකරුවන්"
},
"info": {
"page_not_found": "පිටුව හමු නොවිණි",
"copied": "පිටපත් විය!",
"cannot_copy": "පිටපත් නොවේ!",
"local_storage": "මෙම ක්‍රියාමාර්ගයට ස්ථානීය-ආචයනය වුවමනාය, දත්තකඩ සබල කර තිබේද?",
"register_no_email_note": "පරිශ්‍රීලක නාමය ලෙස වි-තැපෑලක් භාවිතය නිර්දේශ නොකෙරේ. ඉදිරියට යන්නද?",
"preferences_note": "සටහන: ඔබගේ අතිරික්සුවේ ස්ථානීය ආචයනයේ අභිප්‍රේත සුරැකෙයි. අතිරික්සුවේ දත්ත මැකීමෙන් ඒවා අහිමි වනු ඇත.",
"next_video_countdown": "ඊළඟ දෘශ්‍යකය තත්. {0} කින් වාදනය වේ",
"days": "දවස් {amount}",
"weeks": "සති {amount}",
"hours": "පැය {amount}",
"months": "මාස {amount}"
},
"subscriptions": {
"subscribed_channels_count": "දායක වූයේ: {0}"
}
}

View file

@ -72,8 +72,6 @@
"view_ssl_score": "Zobraziť SSL skóre",
"filter": "Filter",
"delete_playlist_video_confirm": "Odstrániť video zo zoznamu?",
"rename_playlist": "Premenovať zoznam skladieb",
"new_playlist_name": "Nový názov zoznamu skladieb",
"share": "Zdieľať",
"follow_link": "Nasledujte odkaz",
"loading": "Načítavanie...",

View file

@ -7,7 +7,9 @@
"ratings_disabled": "Оцене су онемогућене",
"chapters": "Поглавља",
"live": "{0} Уживо",
"shorts": "Кратки видео снимци"
"shorts": "Кратки видео снимци",
"all": "Све",
"category": "Категорија"
},
"actions": {
"view_ssl_score": "Погледај SSL скор/оцену",
@ -23,7 +25,7 @@
"load_more_replies": "Учитај још одговора",
"unsubscribe": "Прекини са праћењем - {count}",
"auto": "Аутоматски",
"search": "Претрага",
"search": "Претрага (Ctrl+K)",
"skip_non_music": "Прескочи делове где нема музике у музичким видео клиповима",
"theme": "Теме",
"audio_only": "Само звук",
@ -98,21 +100,28 @@
"status_page": "Статус",
"instance_donations": "Донације инстанци",
"show_chapters": "Поглавља",
"rename_playlist": "Преименуј плејлисту",
"with_timecode": "Подели са временским кодом",
"piped_link": "Piped веза",
"back_to_home": "Врати се на почетну",
"follow_link": "Прати везу",
"copy_link": "Копирај везу",
"time_code": "Временски код (у секундама)",
"new_playlist_name": "Ново име плејлисте",
"minimize_comments_default": "Подразумевано умањи коментаре",
"minimize_comments": "Умањи коментаре",
"reply_count": "{count} одговора",
"minimize_chapters_default": "Умањи поглавља подразумевано",
"show_watch_on_youtube": "Прикажите \"Гредај на YouTube-у\" дугме",
"no_valid_playlists": "Датотека не садржи важеће пописе снимака!",
"with_playlist": "Делите са пописом снимака"
"with_playlist": "Делите са пописом снимака",
"playlist_bookmarked": "Обиљежено",
"bookmark_playlist": "Биљежак",
"show_less": "Прикажи мање",
"skip_button_only": "Прикажи дугме за прескакање",
"skip_automatically": "Аутоматски",
"min_segment_length": "Најмања дужина сегмента (у секундама)",
"skip_segment": "Прескочи сегмент",
"dismiss": "Одбаци",
"autoplay_next_countdown": "Подразумевано одбројавање до следећег видеа (у секундама)"
},
"preferences": {
"instance_locations": "Локација инстанце",
@ -151,7 +160,8 @@
"instance": "Инстанца",
"player": "Покретник",
"livestreams": "Уживо преноси",
"channels": "Канали"
"channels": "Канали",
"bookmarks": "Биљешци"
},
"comment": {
"pinned_by": "Закачено од {author}",
@ -170,6 +180,8 @@
"copied": "Копирано!",
"cannot_copy": "Није могуће копирати!",
"preferences_note": "Напомена: подешавања се чувају у локалној меморији вашег претраживача. Брисање података прегледача ће их ресетовати.",
"local_storage": "Ова радња захтева локално складиште, да ли су колачићи омогућени ?"
"local_storage": "Ова радња захтева локално складиште, да ли су колачићи омогућени ?",
"register_no_email_note": "Коришћење е-поруке као корисничког имена се не препоручује. Желите ли ипак наставити?",
"next_video_countdown": "Репродукује се следећи видео за {0}с"
}
}

View file

@ -10,7 +10,12 @@
"playlists": "Spellistor",
"account": "Konto",
"instance": "Instans",
"player": "Spelare"
"player": "Spelare",
"bookmarks": "Bokmärken",
"dearrow": "DeArrow",
"livestreams": "Livesändningar",
"channels": "Kanaler",
"channel_groups": "Kanal Grupper"
},
"actions": {
"subscribe": "Prenumerera - {count}",
@ -47,17 +52,17 @@
"instances_list": "Lista över instanser",
"enabled_codecs": "Aktivera codecs (flera)",
"import_from_json": "Importera från JSON/CSV",
"donations": "Donationer",
"donations": "Donationer till utveckling",
"filter": "Filter",
"hide_replies": "Dölj svar",
"load_more_replies": "Ladda fler svar",
"enable_sponsorblock": "Aktivera sponsorblockering",
"skip_preview": "Hoppa över förhandsgranskning/sammanfattning",
"autoplay_video": "Spela upp video automatiskt",
"country_selection": "Val av land",
"language_selection": "Val av språk",
"country_selection": "Land",
"language_selection": "Språk",
"skip_non_music": "Hoppa över musik: Icke-musikaliskt avsnitt",
"instance_selection": "Val av instans",
"instance_selection": "Instans",
"show_more": "Visa mer",
"yes": "Ja",
"no": "Nej",
@ -67,7 +72,7 @@
"show_recommendations": "Visa rekommendationer",
"disable_lbry": "Inaktivera LBRY för strömning",
"enable_lbry_proxy": "Aktivera proxy för LBRY",
"search": "Sök",
"search": "Sök (Ctrl+K)",
"clear_history": "Rensa historik",
"skip_filler_tangent": "Hoppa över påfyllningstangent",
"skip_highlight": "Hoppa över höjdpunkt",
@ -87,12 +92,55 @@
"minimize_recommendations_default": "Minimera rekommendationer som standard",
"invalidate_session": "Logga ut alla enheter",
"different_auth_instance": "Använd en annan instans för autentisering",
"instance_auth_selection": "Val av autentiseringsinstans",
"instance_auth_selection": "Autentiseringsinstans",
"download_as_txt": "Ladda ner som .txt",
"reset_preferences": "Återställ inställningar",
"confirm_reset_preferences": "Är du säker på att du vill återställa dina inställningar?",
"backup_preferences": "Inställningar för säkerhetskopiering",
"restore_preferences": "Återställa inställningar"
"restore_preferences": "Återställa inställningar",
"enable_dearrow": "Aktivera DeArrow",
"autoplay_next_countdown": "Antal sekunder tills nästa video startar automatiskt",
"minimize_comments_default": "Minimera kommentarer som standard",
"show_watch_on_youtube": "Visa knappen \"Titta på YouTube\"",
"back_to_home": "Tillbaka till startsidan",
"delete_automatically": "Ta bort automatiskt efter",
"with_timecode": "Dela med tidsstämpel",
"reply_count": "{count} svar",
"with_playlist": "Dela med spellista",
"dismiss": "Avböj",
"min_segment_length": "Minsta segmentlängd (i sekunder)",
"skip_segment": "Hoppa över segment",
"minimize_comments": "Minimera kommentarer",
"show_less": "Visa mindre",
"cancel": "Avbryt",
"store_search_history": "Spara sökhistorik",
"documentation": "Dokumentation",
"okay": "Okej",
"status_page": "Status",
"minimize_chapters_default": "Minimera kapitel som standard",
"time_code": "Tidsstämpel (i sekunder)",
"hide_watched": "Dölj tittade videor i flödet",
"share": "Dela",
"show_chapters": "Kapitel",
"source_code": "Källkod",
"edit_playlist": "Redigera spellista",
"playlist_name": "Spellistans namn",
"playlist_description": "Beskrivning av spellista",
"generate_qrcode": "Generera QR-kod",
"chapters_layout_mobile": "Layout för kapitel på mobil",
"piped_link": "Piped-länk",
"follow_link": "Följ-länk",
"copy_link": "Kopiera länk",
"group_name": "Gruppnamn",
"show_search_suggestions": "Visa sökförslag",
"auto_display_captions": "Automatisk visning av textning",
"bookmark_playlist": "Bokmärke",
"instance_donations": "Instans donationer",
"no_valid_playlists": "Filen innehåller inga giltiga spellistor!",
"playlist_bookmarked": "Bokmärkt",
"create_group": "Skapa grupp",
"skip_button_only": "Visa hoppa över-knapp",
"skip_automatically": "Automatiskt"
},
"player": {
"watch_on": "Titta på {0}"
@ -118,7 +166,13 @@
"ratings_disabled": "Betyg inaktiverade",
"chapters": "Kapitel",
"live": "{0} Live",
"shorts": "Shorts"
"shorts": "Shorts",
"license": "Licens",
"all": "Alla",
"category": "Kategori",
"chapters_horizontal": "Horisontell",
"visibility": "Synlighet",
"chapters_vertical": "Vertikal"
},
"comment": {
"pinned_by": "Fäst av {author}",
@ -135,12 +189,26 @@
"all": "YouTube: Alla",
"videos": "YouTube: Videor",
"playlists": "YouTube: Spellistor",
"music_songs": "YT Music: Låtar"
"music_songs": "YT Music: Låtar",
"music_artists": "YT Music: Artister"
},
"subscriptions": {
"subscribed_channels_count": "Prenumererar på: {0}"
},
"information": {
"preferences_note": "Observera: inställningar sparas i webbläsarens lokala lagring. Om du raderar dina webbläsardata återställs de."
},
"info": {
"register_no_email_note": "Det rekommenderas inte att använda e-post som användarnamn. Fortsätt ändå?",
"hours": "{amount} timma(r)",
"preferences_note": "Obs: Inställningarna sparas i det lokala lagringsutrymmet i din webbläsare. Om du raderar dina webbläsardata återställs de.",
"days": "{amount} dag(ar)",
"weeks": "{amount} vecka/veckor",
"months": "{amount} månad(er)",
"next_video_countdown": "Spelar nästa video om {0}s",
"cannot_copy": "Kan inte kopiera!",
"page_not_found": "Sida hittas ej",
"copied": "Kopierad!",
"local_storage": "Det här kräver localStorage, är cookies aktiverat?"
}
}

View file

@ -1,12 +1,12 @@
{
"actions": {
"instances_list": "Örnek Listesi",
"language_selection": "Dil Seçimi",
"instances_list": "Sunucu Listesi",
"language_selection": "Dil",
"store_watch_history": "İzleme Geçmişini Sakla",
"minimize_description_default": "Açıklamayı Öntanımlı Olarak Küçült",
"show_comments": "Yorumları Göster",
"default_homepage": "Öntanımlı Ana Sayfa",
"country_selection": "Ülke Seçimi",
"country_selection": "Ülke",
"buffering_goal": "Arabelleğe Alma Hedefi (Saniye Cinsinden)",
"default_quality": "Öntanımlı Kalite",
"audio_only": "Yalnızca Ses",
@ -46,10 +46,10 @@
"no": "Hayır",
"yes": "Evet",
"show_more": "Daha Fazla Göster",
"instance_selection": "Örnek Seçimi",
"instance_selection": "Sunucu",
"loading": "Yükleniyor...",
"filter": "Filtrele",
"search": "Ara",
"search": "Ara (Ctrl+K)",
"view_ssl_score": "SSL Puanını Görüntüle",
"minimize_recommendations": "Önerileri Küçült",
"show_recommendations": "Önerileri Göster",
@ -70,9 +70,9 @@
"delete_account": "Hesabı Sil",
"logout": "Bu Aygıttan Oturumu Kapat",
"minimize_recommendations_default": "Önerileri Öntanımlı Olarak Küçült",
"different_auth_instance": "Kimlik Doğrulama İçin Farklı Bir Örnek Kullan",
"different_auth_instance": "Kimlik Doğrulama İçin Farklı Bir Sunucu Kullan",
"invalidate_session": "Tüm Aygıtlardan Oturumu Kapat",
"instance_auth_selection": "Kimlik Doğrulama Örneği Seçimi",
"instance_auth_selection": "Kimlik Doğrulama Sunucusu",
"clone_playlist": "Oynatma Listesini Kopyala",
"clone_playlist_success": "Başarıyla kopyalandı!",
"download_as_txt": ".txt Olarak İndir",
@ -87,14 +87,12 @@
"with_timecode": "Zaman Koduyla Paylaş",
"piped_link": "Piped Bağlantısı",
"share": "Paylaş",
"rename_playlist": "Oynatma Listesini Yeniden Adlandır",
"new_playlist_name": "Yeni Oynatma Listesi Adı",
"show_chapters": "Bölümler",
"store_search_history": "Arama Geçmişini Sakla",
"hide_watched": "Akışta İzlenen Videoları Gizle",
"source_code": "Kaynak Kodu",
"documentation": "Belgelendirme",
"instance_donations": "Örnek Bağışları",
"instance_donations": "Sunucu Bağışları",
"status_page": "Durum",
"reply_count": "{count} Yanıt",
"minimize_comments": "Yorumları Küçült",
@ -102,7 +100,29 @@
"show_watch_on_youtube": "YouTube'da İzle Düğmesini Göster",
"minimize_chapters_default": "Bölümleri Öntanımlı Olarak Küçült",
"no_valid_playlists": "Dosya geçerli oynatma listeleri içermiyor!",
"with_playlist": "Oynatma listesiyle paylaş"
"with_playlist": "Oynatma listesiyle paylaş",
"bookmark_playlist": "Yer imlerine ekle",
"playlist_bookmarked": "Yer imlerine eklendi",
"min_segment_length": "En Küçük Bölüm Uzunluğu (saniye cinsinden)",
"skip_segment": "Bölümü Atla",
"skip_button_only": "Atla düğmesini göster",
"skip_automatically": "Otomatik olarak",
"show_less": "Daha az göster",
"dismiss": "Kapat",
"autoplay_next_countdown": "Bir sonraki videoya kadar öntanımlı geri sayım (saniye cinsinden)",
"create_group": "Grup oluştur",
"group_name": "Grup adı",
"auto_display_captions": "Alt Yazıları Otomatik Görüntüle",
"cancel": "İptal et",
"okay": "Tamam",
"edit_playlist": "Oynatma listesini düzenle",
"playlist_name": "Oynatma listesi adı",
"playlist_description": "Oynatma listesi açıklaması",
"show_search_suggestions": "Arama önerilerini göster",
"chapters_layout_mobile": "Mobilde Bölüm Düzeni",
"delete_automatically": "Şundan sonra otomatik olarak sil",
"enable_dearrow": "DeArrow'u Etkinleştir",
"generate_qrcode": "QR Kodu Oluştur"
},
"player": {
"watch_on": "{0} Üzerinde İzle"
@ -117,10 +137,13 @@
"subscriptions": "Abonelikler",
"playlists": "Oynatma Listeleri",
"account": "Hesap",
"instance": "Örnek",
"instance": "Sunucu",
"player": "Oynatıcı",
"livestreams": "Canlı Yayınlar",
"channels": "Kanallar"
"channels": "Kanallar",
"bookmarks": "Yer İmleri",
"channel_groups": "Kanal grupları",
"dearrow": "DeArrow"
},
"video": {
"sponsor_segments": "Sponsorlar Bölümleri",
@ -130,13 +153,19 @@
"ratings_disabled": "Derecelendirmeler Devre Dışı",
"chapters": "Bölümler",
"live": "{0} Canlı",
"shorts": "Kısa çekimler"
"shorts": "Kısa çekimler",
"all": "Tümü",
"category": "Kategori",
"chapters_horizontal": "Yatay",
"chapters_vertical": "Dikey",
"license": "Lisans",
"visibility": "Görünürlük"
},
"preferences": {
"ssl_score": "SSL Puanı",
"has_cdn": "CDN Var Mı?",
"instance_locations": "Örnek Konumları",
"instance_name": "Örnek Adı",
"instance_locations": "Sunucu Konumları",
"instance_name": "Sunucu Adı",
"registered_users": "Kayıtlı Kullanıcılar",
"version": "Sürüm",
"up_to_date": "Güncel Mi?"
@ -149,7 +178,9 @@
},
"login": {
"password": "Parola",
"username": "Kullanıcı Adı"
"username": "Kullanıcı Adı",
"password_confirm": "Parolayı doğrula",
"passwords_incorrect": "Parolalar eşleşmiyor!"
},
"search": {
"did_you_mean": "Bunu mu demek istediniz: {0}?",
@ -160,7 +191,8 @@
"videos": "YouTube: Videolar",
"music_songs": "YT Müzik: Şarkılar",
"music_videos": "YT Müzik: Videolar",
"music_albums": "YT Müzik: Albümler"
"music_albums": "YT Müzik: Albümler",
"music_artists": "YT Müzik: Sanatçılar"
},
"subscriptions": {
"subscribed_channels_count": "Abone Olunan: {0}"
@ -173,6 +205,12 @@
"page_not_found": "Sayfa Bulunamadı",
"copied": "Kopyalandı!",
"cannot_copy": "Kopyalanamıyor!",
"local_storage": "Bu eylem yerel depolama gerektirir, çerezler etkin mi?"
"local_storage": "Bu eylem yerel depolama gerektirir, çerezler etkin mi?",
"register_no_email_note": "Kullanıcı adı olarak e-posta kullanılması tavsiye edilmez. Yine de devam edilsin mi?",
"next_video_countdown": "Sonraki video {0}s içinde oynatılıyor",
"days": "{amount} gün",
"months": "{amount} ay",
"hours": "{amount} saat",
"weeks": "{amount} hafta"
}
}

View file

@ -3,127 +3,153 @@
"watch_on": "Дивитися на {0}"
},
"login": {
"username": "Назва аккаунта Piped",
"password": "Пароль"
"username": "Ім'я користувача",
"password": "Пароль",
"password_confirm": "Підтвердіть пароль",
"passwords_incorrect": "Паролі не збігаються!"
},
"actions": {
"unsubscribe": "Відписатись - {count}",
"unsubscribe": "Відписатися - {count}",
"back": "Назад",
"skip_intro": "Пропускати заставку/інтро",
"skip_intro": "Пропускати паузу/заставку",
"dark": "Темна",
"view_subscriptions": родивитися підписки",
"channel_name_asc": "Назва каналу (A-Z)",
"uses_api_from": "Використовувати API з ",
"view_subscriptions": ереглянути Підписки",
"channel_name_asc": "Назвою каналу (А)",
"uses_api_from": "Використовує API від ",
"enable_sponsorblock": "Увімкнути Sponsorblock",
"skip_outro": "Пропускати кінцівку/титри",
"skip_preview": "Пропускати короткий вміст поточного епізода або повтор частини минулого",
"skip_self_promo": "Пропускати саморекламу",
"autoplay_video": "Автоматичний програш відео",
"audio_only": "Лише звук",
"default_homepage": "За замовчуванням відкривати",
"show_comments": "Показувати коментарі",
"store_watch_history": "Зберігати історию переглянутих відео",
"language_selection": "Вибір мови",
"instance_selection": "Вибір копії сервіса Piped",
"skip_outro": "Пропускати кінцеву заставку/титри",
"skip_preview": "Пропускати попередній перегляд/короткий зміст",
"skip_self_promo": "Пропускати саморекламу/рекомендацію",
"autoplay_video": "Автоматичне відтворення відео",
"audio_only": "Лише аудіо",
"default_homepage": "Домашня сторінка за замовчуванням",
"show_comments": "Показати коментарі",
"store_watch_history": "Зберігати історію перегляду",
"language_selection": "Мова",
"instance_selection": "Екземпляр",
"show_more": "Показати більше",
"no": "Ні",
"export_to_json": "Експорт в JSON",
"export_to_json": "Експортувати в JSON",
"minimize_description": "Згорнути опис",
"show_recommendations": "Показати рекомендації",
"enable_lbry_proxy": "Проксувати відео з LBRY",
"search": "Пошук",
"enable_lbry_proxy": "Увімкнути проксі для LBRY",
"search": "Пошук (Ctrl+K)",
"clear_history": "Очистити історію перегляду",
"load_more_replies": "Завантажити більше відповідей",
"subscribe": "Підписатись - {count}",
"sort_by": "Відсортувати по:",
"most_recent": "Найновіші",
"channel_name_desc": "Назва каналу (Z-A)",
"least_recent": "Найстарші",
"subscribe": "Підписатися - {count}",
"sort_by": "Сортувати за:",
"most_recent": "Найновішими",
"channel_name_desc": "Назвою каналу (Я-А)",
"least_recent": "Найстарішими",
"minimize_recommendations": "Згорнути рекомендації",
"skip_sponsors": "Пропускати спонсорську рекламу",
"skip_interaction": "Пропускати прохання підписатися",
"skip_non_music": "Пропускати тишу в музикальних відео",
"skip_interaction": "Пропускати нагадування про взаємодію (підписка)",
"skip_non_music": "Пропускати сегменти без музики в музикальних відео",
"theme": "Тема",
"auto": "Авто",
"light": "Світла",
"buffering_goal": "Розмір буфера відео (в секундах)",
"instances_list": "Список копій сервіса Piped",
"enabled_codecs": "Увімкнені кодеки (Можно вибрати декілька)",
"buffering_goal": "Розмір буфера відео (у секундах)",
"instances_list": "Список екземплярів",
"enabled_codecs": "Увімкнені кодеки (можна вибрати декілька)",
"default_quality": "Якість за замовчуванням",
"country_selection": "Вибір країни (для трендів)",
"country_selection": "Країна",
"minimize_description_default": "Не розгортати опис за замовчуванням",
"yes": "Так",
"import_from_json": "Імпорт з JSON/CSV",
"loop_this_video": "Повтор поточного відео",
"auto_play_next_video": "Одразу програвати наступне рекомендоване відео",
"import_from_json": "Імпортувати з JSON/CSV",
"loop_this_video": "Зациклити це відео",
"auto_play_next_video": "Автоматичне відтворення наступного відео",
"donations": "Пожертвування на розробку",
"show_description": "Показати опис",
"disable_lbry": "Вимкнути LBRY для стримінгу",
"filter": "Фільтр",
"view_ssl_score": родивитися оцінку SSL",
"view_ssl_score": ереглянути оцінку SSL",
"loading": "Завантаження...",
"hide_replies": "Сховати відповіді",
"skip_highlight": "Пропустити Хайлайт",
"remove_from_playlist": "Видалити з плейлісту",
"add_to_playlist": "Додати до плейлісту",
"create_playlist": "Створити Плейліст",
"delete_playlist_confirm": "Видалити цей плейлист?",
"skip_filler_tangent": "Пропускати Нерелевантне",
"delete_playlist_video_confirm": "Видалити відео з плейлисту?",
"delete_playlist": "Видалити Плейліст",
"select_playlist": "Вибрати Плейліст",
"please_select_playlist": "Будь ласка виберіть плейліст",
"confirm_reset_preferences": "Ви впевнені, що бажаєте скинути налаштування?",
"show_markers": "Показувати Маркери на Програвачі",
"minimize_recommendations_default": "Ховати Рекомендації за замовчуванням",
"skip_highlight": "Пропускати основне",
"remove_from_playlist": "Видалити зі списку відтворення",
"add_to_playlist": "Додати до списку відтворення",
"create_playlist": "Створити список відтворення",
"delete_playlist_confirm": "Видалити цей список відтворення?",
"skip_filler_tangent": "Пропускати дотичне наповнення/жарти",
"delete_playlist_video_confirm": "Видалити відео зі списку відтворення?",
"delete_playlist": "Видалити список відтворення",
"select_playlist": "Вибрати список відтворення",
"please_select_playlist": "Будь ласка, виберіть список відтворення",
"confirm_reset_preferences": "Ви впевнені, що бажаєте скинути свої налаштування?",
"show_markers": "Показувати маркери на програвачі",
"minimize_recommendations_default": "Згортати рекомендації за замовчуванням",
"logout": "Вийти з цього пристрою",
"backup_preferences": "Налаштування резервних копій",
"backup_preferences": "Налаштування резервного копіювання",
"download_as_txt": "Завантажити як .txt",
"rename_playlist": "Перейменувати плейлист",
"show_chapters": "Глави",
"invalidate_session": "Вийти зі всіх пристроїв",
"clone_playlist": "Клонувати Плейлист",
"show_chapters": "Розділи",
"invalidate_session": "Вийти з усіх пристроїв",
"clone_playlist": "Клонувати список відтворення",
"reset_preferences": "Скинути налаштування",
"back_to_home": "Повернутися на головну",
"share": "Поділитися",
"with_timecode": "Поділитися з відміткою часу",
"piped_link": "Покликання Piped",
"follow_link": окликання підписки",
"instance_donations": "Пожертвування інстанції",
"copy_link": "Копіювати покликання",
"store_search_history": "Зберігати історію Пошуку",
"piped_link": "Посилання Piped",
"follow_link": ерейти за посиланням",
"instance_donations": "Пожертвування екземпляра",
"copy_link": "Копіювати посилання",
"store_search_history": "Зберігати історію пошуку",
"documentation": "Документація",
"instance_auth_selection": "Вибір Інстанції для Аутентифікації",
"minimize_chapters_default": "Ховати глави за замовчуванням",
"instance_auth_selection": "Екземпляр для автентифікації",
"minimize_chapters_default": "Згортати розділи за замовчуванням",
"show_watch_on_youtube": "Показати кнопку Дивитися на YouTube",
"restore_preferences": "Відновити налаштування",
"different_auth_instance": "Використовувати іншу інстанцію для аутентифікації",
"clone_playlist_success": "Клонування пройшло успішно!",
"different_auth_instance": "Використовувати інший екземпляр для автентифікації",
"clone_playlist_success": "Успішно клоновано!",
"hide_watched": "Сховати переглянуті відео в стрічці",
"status_page": "Статус",
"source_code": "Вихідний код",
"new_playlist_name": "Нова назва плейлиста",
"time_code": "Відмітка часу (в секундах)",
"time_code": "Відмітка часу (у секундах)",
"reply_count": "{count} відповідей",
"minimize_comments_default": "Згортати коментарі за замовчуванням",
"minimize_comments": "Згорнути коментарі",
"delete_account": "Видалити Акаунт",
"no_valid_playlists": "Файл не містить дійсних плейлистів!"
"delete_account": "Видалити обліковий запис",
"no_valid_playlists": "Файл не містить дійсних списків відтворення!",
"bookmark_playlist": "Закладка",
"playlist_bookmarked": "Додано в закладки",
"with_playlist": "Поділитися зі списком відтворення",
"skip_button_only": "Показати кнопку пропуску",
"skip_segment": "Пропустити сегмент",
"skip_automatically": "Автоматично",
"min_segment_length": "Мінімальна довжина сегмента (у секундах)",
"show_less": "Показати менше",
"dismiss": "Відхилити",
"autoplay_next_countdown": "Зворотний відлік до наступного відео (у секундах)",
"create_group": "Створити групу",
"group_name": "Назва групи",
"edit_playlist": "Редагувати список відтворення",
"playlist_name": "Назва списку відтворення",
"playlist_description": "Опис списку відтворення",
"auto_display_captions": "Автоматичне відображення субтитрів",
"cancel": "Скасувати",
"show_search_suggestions": "Показувати пошукові пропозицій",
"okay": "Добре",
"chapters_layout_mobile": "Макет розділів на телефоні",
"enable_dearrow": "Увімкнути DeArrow",
"delete_automatically": "Видаляти автоматично після",
"generate_qrcode": "Згенерувати QR-код"
},
"titles": {
"register": "Реєстрація",
"feed": "Підписки",
"preferences": "Налаштування",
"history": "Історія переглядів",
"history": "Історія перегляду",
"subscriptions": "Канали, на які ви підписані",
"trending": "Тренди",
"login": "Логін",
"playlists": "Плейлісти",
"instance": "Інстанція",
"playlists": "Списки відтворення",
"instance": "Екземпляр",
"player": "Програвач",
"account": "Акаунт",
"account": "Обліковий запис",
"livestreams": "Наживо",
"channels": "Канали"
"channels": "Канали",
"bookmarks": "Закладки",
"channel_groups": "Групи каналів",
"dearrow": "DeArrow"
},
"comment": {
"pinned_by": "Прикріплено користувачем {author}",
@ -132,13 +158,13 @@
"user_disabled": "Коментарі вимкнені в налаштуваннях."
},
"preferences": {
"instance_locations": "Місцезнаходження копії сервісу",
"instance_locations": "Місцезнаходження екземпляру",
"ssl_score": "Оцінка SSL",
"instance_name": "Назва копії сервісу",
"instance_name": "Назва екземпляру",
"has_cdn": "Використовує CDN?",
"version": "Версія",
"up_to_date": "Версія актуальна?",
"registered_users": "Зареєстровано Користувачей"
"registered_users": "Зареєстровано користувачей"
},
"video": {
"videos": "Відео",
@ -148,18 +174,25 @@
"ratings_disabled": "Оцінки вимкнені",
"chapters": "Розділи",
"live": "{0} Наживо",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Усі",
"category": "Категорія",
"chapters_horizontal": "Горизонтальний",
"chapters_vertical": "Вертикальний",
"visibility": "Видимість",
"license": "Ліцензія"
},
"search": {
"did_you_mean": "Можливо, ви мали на увазі: {0}?",
"music_playlists": "YT Music: Плейлісти",
"music_playlists": "YT Music: Списки відтворення",
"all": "YouTube: Все",
"videos": "YouTube: Відео",
"channels": "YouTube: Канали",
"music_songs": "YT Music: Пісні",
"music_videos": "TY Music: Відео",
"playlists": "YouTube: Плейлісти",
"music_albums": "YT Music: Альбоми"
"playlists": "YouTube: Списки відтворення",
"music_albums": "YT Music: Альбоми",
"music_artists": "YT Music: Артисти"
},
"subscriptions": {
"subscribed_channels_count": "Підписано на: {0}"
@ -168,7 +201,13 @@
"copied": "Скопійовано!",
"cannot_copy": "Не вийшло скопіювати!",
"page_not_found": "Сторінка не знайдена",
"preferences_note": "Зауваження: налаштування зберігаються в локальному сховищі вашого браузера. Видалення даних браузере їх скине.",
"local_storage": "Ця дія потребує localStorage, чи ввімкнуті файли cookie?"
"preferences_note": "Примітка: налаштування зберігаються в локальній пам'яті вашого браузера. Видалення даних браузера призведе до їх скидання.",
"local_storage": "Ця дія потребує localStorage, чи ввімкнуті файли cookie?",
"register_no_email_note": "Використання електронної пошти як імені користувача не рекомендується. Все одно продовжити?",
"next_video_countdown": "Наступне відео через {0} секунд",
"weeks": "{amount} тиждень(-і)",
"hours": "{amount} годин(-и)",
"months": "{amount} місяць(-і)",
"days": "{amount} день(-і)"
}
}

View file

@ -3,7 +3,7 @@
"subscribe": "Đăng ký - {count}",
"autoplay_video": "Video tự động phát",
"unsubscribe": "Hủy đăng ký - {count}",
"view_subscriptions": "Lượt đăng ký",
"view_subscriptions": "Xem những kênh đã đăng ký",
"sort_by": "Sắp xếp theo:",
"most_recent": "Gần đây nhất",
"channel_name_asc": "Tên kênh (A-Z)",
@ -14,7 +14,7 @@
"theme": "Giao diện",
"auto": "Tự động",
"buffering_goal": "Bộ nhớ đệm (tính bằng giây)",
"least_recent": "Ít nhất gần đây",
"least_recent": "Cũ nhất",
"skip_intro": "Bỏ qua gián đoạn/hoạt hình intro",
"skip_outro": "Bỏ qua màn hình kết thúc/danh đề",
"skip_interaction": "Bỏ qua lời nhắc tương tác (Đăng ký)",
@ -28,7 +28,7 @@
"show_comments": "Hiển thị bình luận",
"store_watch_history": "Lịch sử xem trên cửa hàng",
"language_selection": "Lựa chọn ngôn ngữ",
"instances_list": "Danh sách phiên bản",
"instances_list": "Danh sách instance",
"show_more": "Hiện thị nhiều hơn",
"import_from_json": "Nhập từ JSON/CSV",
"loop_this_video": "Lặp lại video này",
@ -43,7 +43,7 @@
"disable_lbry": "Tắt LBRY để phát trực tuyến",
"enable_lbry_proxy": "Bật proxy cho LBRY",
"view_ssl_score": "Hiện thị điểm số SSL",
"search": "Tìm kiếm",
"search": "Tìm kiếm (Ctrl+K)",
"filter": "Bộ lọc",
"loading": "Đang tải...",
"clear_history": "Xóa lịch sử",
@ -53,7 +53,7 @@
"light": "Sáng",
"audio_only": "Chỉ có âm thanh",
"minimize_description_default": "Thu nhỏ mô tả theo mặc định",
"instance_selection": "Lựa chọn phiên bản",
"instance_selection": "Lựa chọn instance",
"yes": "Có",
"enabled_codecs": "Các codec được bật (Nhiều)",
"export_to_json": "Xuất định dạng JSON",
@ -78,7 +78,11 @@
"minimize_comments": "Thu nhỏ bình luận",
"reply_count": "{count} phản hồi",
"status_page": "Trạng thái",
"new_playlist_name": "Tên danh sách phát mới"
"skip_automatically": "Tự động",
"show_chapters": "Chương",
"show_less": "Hiển thị ít hơn",
"cancel": "Hủy",
"okay": "OK"
},
"titles": {
"register": "Đăng ký",
@ -92,7 +96,8 @@
"account": "Tài khoản",
"channels": "Kênh",
"instance": "Instance",
"player": "Trình phát video"
"player": "Trình phát video",
"livestreams": "Phát sóng trực tiếp"
},
"player": {
"watch_on": "Xem trên {0}"
@ -104,8 +109,8 @@
"disabled": "Bình luận đã bị tắt bởi người đăng video."
},
"preferences": {
"instance_name": "Tên phiên bản",
"instance_locations": "Vị trí phiên bản",
"instance_name": "Tên instance",
"instance_locations": "Vị trí instance",
"has_cdn": "Có CDN?",
"registered_users": "Người dùng đã đăng ký",
"version": "Phiên bản",
@ -124,7 +129,8 @@
"live": "{0} Trực tiếp",
"chapters": "Chương",
"videos": "Video",
"shorts": "Shorts"
"shorts": "Shorts",
"all": "Tất cả"
},
"search": {
"did_you_mean": "Ý của bạn là: {0}?",
@ -140,7 +146,8 @@
"info": {
"copied": "Đã sao chép!",
"cannot_copy": "Không thể sao chép!",
"page_not_found": "Không tìm thấy trang"
"page_not_found": "Không tìm thấy trang",
"next_video_countdown": "Tự động phát video tiếp theo trong {0} giây"
},
"subscriptions": {
"subscribed_channels_count": "Đã đăng ký cho: {0}"

View file

@ -14,15 +14,15 @@
"no": "否",
"yes": "是",
"show_more": "显示更多",
"instance_selection": "实例选择",
"instance_selection": "实例",
"enabled_codecs": "启用的编解码器 (多个)",
"instances_list": "实例列表",
"language_selection": "语言选择",
"language_selection": "语言",
"store_watch_history": "保存观看历史",
"minimize_description_default": "默认情况下折叠描述",
"show_comments": "显示评论",
"default_homepage": "默认主页",
"country_selection": "国家/地区选择",
"country_selection": "国家/地区",
"buffering_goal": "缓冲目标 (以秒为单位)",
"default_quality": "默认质量",
"audio_only": "仅音频",
@ -44,12 +44,12 @@
"least_recent": "最早的",
"most_recent": "最新的",
"sort_by": "排序:",
"view_subscriptions": "查看订阅",
"view_subscriptions": "查看订阅列表",
"unsubscribe": "取消订阅 - {count}",
"subscribe": "订 - {count}",
"subscribe": "订 - {count}",
"loading": "正在加载...",
"filter": "筛选",
"search": "搜索",
"search": "搜索 (Ctrl+K)",
"view_ssl_score": "查看 SSL 得分",
"minimize_recommendations": "最小化建议",
"show_recommendations": "显示推荐",
@ -72,7 +72,7 @@
"minimize_recommendations_default": "默认最小化推荐",
"invalidate_session": "注销所有设备",
"different_auth_instance": "使用不同的实例进行身份验证",
"instance_auth_selection": "身份验证实例选择",
"instance_auth_selection": "身份验证实例",
"clone_playlist": "克隆播放列表",
"clone_playlist_success": "克隆成功!",
"download_as_txt": "下载为 .txt",
@ -87,8 +87,6 @@
"share": "分享",
"with_timecode": "用时间码分享",
"time_code": "时间码(单位:秒)",
"rename_playlist": "重命名播放列表",
"new_playlist_name": "新播放列表名",
"show_chapters": "章节",
"store_search_history": "保存搜索历史",
"hide_watched": "在源中隐藏看过的视频",
@ -101,7 +99,30 @@
"minimize_comments_default": "默认最小化评论",
"show_watch_on_youtube": "显示“在 YouTube 上观看”按钮",
"minimize_chapters_default": "默认最小化章节",
"no_valid_playlists": "此文件不含无效的播放列表!"
"no_valid_playlists": "此文件不包含有效的播放列表!",
"with_playlist": "分享播放列表",
"playlist_bookmarked": "已加入书签",
"bookmark_playlist": "书签",
"skip_automatically": "自动",
"min_segment_length": "最小分段长度(以秒为单位)",
"skip_segment": "跳过分段",
"skip_button_only": "显示跳过按钮",
"show_less": "显示更少",
"autoplay_next_countdown": "下一个视频开始前的默认倒计时长(以秒计)",
"dismiss": "解除",
"group_name": "组名称",
"create_group": "创建组",
"auto_display_captions": "自动显示字幕",
"playlist_name": "播放列表名称",
"playlist_description": "播放列表描述",
"cancel": "取消",
"okay": "好的",
"edit_playlist": "编辑播放列表",
"show_search_suggestions": "显示搜索建议",
"chapters_layout_mobile": "移动设备上的章节布局",
"delete_automatically": "多久后自动删除",
"enable_dearrow": "启用 DeArrow",
"generate_qrcode": "生成二维码"
},
"video": {
"sponsor_segments": "赞助商部分",
@ -111,7 +132,13 @@
"live": "{0} 直播",
"chapters": "章节",
"ratings_disabled": "已禁用评价",
"shorts": "短视频"
"shorts": "短视频",
"all": "全部",
"category": "类别",
"chapters_horizontal": "水平",
"chapters_vertical": "垂直",
"license": "许可证",
"visibility": "可见性"
},
"preferences": {
"ssl_score": "SSL 分数",
@ -132,8 +159,8 @@
"watch_on": "在 {0} 观看"
},
"titles": {
"feed": "RSS 订阅源",
"subscriptions": "订阅",
"feed": "订阅流",
"subscriptions": "订阅列表",
"history": "历史",
"preferences": "设置",
"register": "注册",
@ -144,11 +171,16 @@
"instance": "实例",
"player": "播放器",
"livestreams": "直播",
"channels": "频道"
"channels": "频道",
"bookmarks": "书签",
"channel_groups": "频道组",
"dearrow": "DeArrow"
},
"login": {
"password": "密码",
"username": "帐号"
"username": "帐号",
"password_confirm": "确认密码",
"passwords_incorrect": "密码不匹配!"
},
"search": {
"did_you_mean": "你是指 {0} 吗?",
@ -159,7 +191,8 @@
"music_songs": "YT Music歌曲",
"music_videos": "YT Music视频",
"music_albums": "YT Music专辑",
"music_playlists": "YT Music播放列表"
"music_playlists": "YT Music播放列表",
"music_artists": "YT Music艺人"
},
"subscriptions": {
"subscribed_channels_count": "已订阅:{0}"
@ -172,6 +205,12 @@
"page_not_found": "未找到页面",
"copied": "已复制!",
"cannot_copy": "无法复制!",
"local_storage": "此操作需要 localStorage启用 cookie 了吗?"
"local_storage": "此操作需要本地存储是否启用了Cookie",
"register_no_email_note": "不建议使用电子邮件作为用户名。仍要继续吗?",
"next_video_countdown": "在{0}秒后播放下一个视频",
"days": "{amount} 天",
"weeks": "{amount} 周",
"months": "{amount} 个月",
"hours": "{amount} 小时"
}
}

Some files were not shown because too many files have changed in this diff Show more