Latest EFY, Fix Bugs, New Card Layout, Seek Bar Gradient, Improved Captions

This commit is contained in:
dragos-efy 2024-05-22 23:47:19 +03:00
commit 3eefcf528f
121 changed files with 6415 additions and 3901 deletions

View File

@ -4,3 +4,4 @@ dist/
.* .*
*.md *.md
!.prettier* !.prettier*
!.eslintrc.cjs

3
.env Normal file
View File

@ -0,0 +1,3 @@
VITE_PIPED_API=https://pipedapi.kavin.rocks
VITE_PIPED_PROXY=https://pipedproxy.kavin.rocks
VITE_PIPED_INSTANCES=https://piped-instances.kavin.rocks/

View File

@ -4,4 +4,9 @@ module.exports = {
node: true, node: true,
}, },
extends: ["plugin:vue/vue3-recommended", "eslint:recommended", "@unocss", "plugin:prettier/recommended"], extends: ["plugin:vue/vue3-recommended", "eslint:recommended", "@unocss", "plugin:prettier/recommended"],
rules: {
"vue/no-undef-components": ["error", {
ignorePatterns: ["router-link", "router-view", "i18n-t", "ErrorHandler"]
}],
},
}; };

1
.github/FUNDING.yml vendored
View File

@ -1,2 +1,3 @@
github: TeamPiped github: TeamPiped
liberapay: kavin liberapay: kavin
custom: https://liberapay.com/bnyro

54
.github/ISSUE_TEMPLATE/new_instance.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: New Instance
title: "New instance: "
description: Request to add a new instance to the official list
labels: ["new-instance"]
body:
- type: input
id: api-url
attributes:
label: Api Url
description: The backend url of the instance.
placeholder: https://pipedapi.kavin.rocks
validations:
required: true
- type: input
id: location
attributes:
label: Instance location
description: The country the instance is located in.
placeholder: Germany, 🇩🇪
validations:
required: true
- type: checkboxes
id: cdn
attributes:
label: CDN (not required)
description: Whether the instances uses a [CDN](https://www.cloudflare.com/learning/cdn/what-is-a-cdn/).
options:
- label: My instance does use a CDN.
- type: checkboxes
id: approval
attributes:
label: Instance owner
description: If you're not the owner of the instance, you must leave a proof that the instance owner agreed with adding the instance to the list of public instances.
options:
- label: I am the owner of the instance.
- type: textarea
id: other
attributes:
label: Other details
- type: markdown
attributes:
value: |
Thank you for hosting a public Piped instance!
- type: markdown
attributes:
value: |
If you have any further questions, please join one of the communities that are linked in the README.

View File

@ -1,16 +0,0 @@
name: Question
description: Ask questions about configuration and usage of Piped.
labels: [question]
body:
- type: markdown
attributes:
value: |
Please make sure to read the README.md before posting a question or asking for support.
- type: textarea
id: question
attributes:
label: Describe your question
description: A clear description of what your doubt is.
validations:
required: true

View File

@ -16,12 +16,12 @@ jobs:
with: with:
version: latest version: latest
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
- run: pnpm install - run: pnpm install
- run: pnpm build - run: pnpm build
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: build name: build
path: dist path: dist

View File

@ -33,7 +33,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -47,7 +47,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # 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 # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -60,6 +60,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@ -17,7 +17,7 @@ jobs:
with: with:
version: latest version: latest
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
- run: pnpm install - run: pnpm install

View File

@ -17,7 +17,7 @@ jobs:
with: with:
version: latest version: latest
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
- run: pnpm install - run: pnpm install

View File

@ -14,7 +14,7 @@ jobs:
with: with:
version: latest version: latest
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
- run: pnpm install - run: pnpm install

View File

@ -20,7 +20,7 @@ Examples of unacceptable behavior include but are not limited to:
- Trolling, insulting/derogatory comments, threats, and personal or political attacks - Trolling, insulting/derogatory comments, threats, and personal or political attacks
- Harassment of any form - Harassment of any form
- Publishing others' private information, such as a physical or electronic address, without explicit permission from the individual - 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 - Derailling conversations unnecessarily in a way that is not constructive, such as repeatedly posting off-topic comments whilst not in an off-topic channel
- Other conduct which could reasonably be considered inappropriate in a professional setting - Other conduct which could reasonably be considered inappropriate in a professional setting
- Tagging maintainers or project members without being one yourself - Tagging maintainers or project members without being one yourself

View File

@ -18,6 +18,9 @@ RUN --mount=type=cache,target=/root/.local/share/pnpm \
FROM nginx:alpine FROM nginx:alpine
COPY --from=build /app/dist/ /usr/share/nginx/html/ COPY --from=build /app/dist/ /usr/share/nginx/html/
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80 COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]

View File

@ -2,5 +2,7 @@ FROM nginx:alpine
COPY ./dist-ci/ /usr/share/nginx/html/ COPY ./dist-ci/ /usr/share/nginx/html/
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY docker/entrypoint.sh /entrypoint.sh
EXPOSE 80 EXPOSE 80
ENTRYPOINT [ "/entrypoint.sh" ]

View File

@ -57,22 +57,21 @@ By using Piped, you can freely watch and listen to content without the fear of p
| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| ![Screenshot 1](https://cloudflare-ipfs.com/ipfs/bafybeiaxhsog7jzydr7xb3xhlemxilqksceqg5fraaiuojzclhocsqrcvq) | ![Screenshot 2](https://cloudflare-ipfs.com/ipfs/bafybeigafumvrgbfyufxjptvufobstrywrfv2kteyuuictfko6kvghjszu) | ![Screenshot 3](https://cloudflare-ipfs.com/ipfs/bafybeiehs5xjqmmq34gmewxoqm3j3b2ze3pve4sdmanz7ukrxwgrcmxnry) | | ![Screenshot 1](https://cloudflare-ipfs.com/ipfs/bafybeiaxhsog7jzydr7xb3xhlemxilqksceqg5fraaiuojzclhocsqrcvq) | ![Screenshot 2](https://cloudflare-ipfs.com/ipfs/bafybeigafumvrgbfyufxjptvufobstrywrfv2kteyuuictfko6kvghjszu) | ![Screenshot 3](https://cloudflare-ipfs.com/ipfs/bafybeiehs5xjqmmq34gmewxoqm3j3b2ze3pve4sdmanz7ukrxwgrcmxnry) |
## Public Chat Rooms ## Having trouble?
If you have any general questions regarding how Piped works or trouble setting up your own instance, please consult the following public chat rooms and documentation for help. Please use these platforms exclusively for such cases and do NOT open an issue.
### Public Chat Rooms/Communities
- You can join us via Matrix at [#piped](https://matrix.to/#/#piped:matrix.org). - 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. - You can join us on Lemmy on the [!piped@feddit.rocks](https://feddit.rocks/c/piped) community.
## Self-Hosting ### Self-Hosting
See https://docs.piped.video/docs/self-hosting/ for more details. See https://docs.piped.video/docs/self-hosting/ for more details.
The source code of the documentation website is available at https://github.com/TeamPiped/Documentation. The source code of the documentation website is available at https://github.com/TeamPiped/Documentation.
## Documentation ### Documentation
The documentation can be found at https://docs.piped.video (accessible via IPNS as well). The documentation can be found at https://docs.piped.video (accessible via IPNS as well).
@ -114,7 +113,7 @@ pnpm install
### Compiles and hot-reloads for development ### Compiles and hot-reloads for development
``` ```
pnpm serve pnpm dev
``` ```
You can now make changes and view then in realtime! You can now make changes and view then in realtime!
@ -127,33 +126,47 @@ If you would like to contact me personally, you may do so with the following mea
- Mastodon: https://mastodon.online/@kavin - Mastodon: https://mastodon.online/@kavin
- Email: kavin@kavin.rocks - Email: kavin@kavin.rocks
Please note that isn't meant for support, see [Public Chat Rooms/Communities](#public-chat-roomscommunities) for that.
## Donations ## Donations
Donations can be made at: Donations can be made at:
- bc1qhq8zjxmu405nvp37njj6zv3s980zg400pu9jfe (BTC) - bc1qhq8zjxmu405nvp37njj6zv3s980zg400pu9jfe (BTC)
- 0x1D77D4cfB1a947514241bcf19B1F04738495e2fD (ETH) - 0x1D77D4cfB1a947514241bcf19B1F04738495e2fD (ETH)
- 8A5Up8rKgagVAz6TuUduBqHp518H1U6fYc6GqCfWsaEfjGzbSccfYpgMqp5d4oe5Ws5MuFE1iKmhQTadhMhvuk3bHRT5Ebk (XMR, aka Monero) - 84wyyeGTrg4U1daJufi78bAFrBQgdRhmxJZvgYv8dAFeFVwkJaBEmw5C7fNniUM9n4jfrz3NeG32Agxtp7JNAcCUFPACfwA (XMR, aka Monero)
- nano_1ngejzydncche4rdua3iebhj7sa95pw5geq4pb8phugtjf3tku933ktjb4pq (Nano) - nano_1ngejzydncche4rdua3iebhj7sa95pw5geq4pb8phugtjf3tku933ktjb4pq (Nano)
- XpzgouDTKCUuE8a92XqjX9b43gKL8oLihw (Dash) - XpzgouDTKCUuE8a92XqjX9b43gKL8oLihw (Dash)
FIAT donations can be made at: https://liberapay.com/kavin FIAT donations can be made at:
- https://liberapay.com/kavin (Author of project, used for Project infrastructure maintenance, and official instance)
- https://liberapay.com/Bnyro (Maintainer of repo)
Contributions in any other form are also welcomed. Contributions in any other form are also welcomed.
# Made with Piped # Made with Piped
- [Yattee](https://github.com/yattee/yattee) - an alternative frontend for YouTube, for IOS. **Mobile/desktop apps**
- [LibreTube](https://github.com/Libre-tube/LibreTube) - an alternative frontend for YouTube, for Android. - [LibreTube](https://github.com/Libre-tube/LibreTube) - 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. - [YTDLnis](https://github.com/deniscerri/ytdlnis) - Video and audio downloader for Android that uses Piped to update formats.
- [DeskVideo](https://github.com/malisipi/DeskVideo) - A desktop styled, customizable alternative front-end for YouTube. - [Yattee](https://github.com/yattee/yattee) - Alternative frontend for YouTube, for MacOS / IOS.
- [PsTube](https://github.com/prateekmedia/pstube) - Watch and download videos without ads on Android, Linux, Windows, iOS, and Mac OSX.
- [Harmony Music](https://github.com/anandnet/Harmony-Music) - YouTube Music alternative for Android, built with Flutter that supports piped linking for playlists.
- [VibeYou](https://github.com/you-apps/VibeYou) - Privacy focused music player for Android supporting playback via Piped.
**Web apps**
- [Piped-Material](https://github.com/mmjee/Piped-Material) - Fork of Piped, focusing on better performance and a more usable design.
- [Hyperpipe](https://codeberg.org/Hyperpipe/Hyperpipe) - Alternative privacy respecting front-end for YouTube Music.
- [ytify](https://github.com/n-ce/ytify) - Complementary audio streaming frontend for YouTube & YouTube Music.
- [Musicale](https://github.com/Bellisario/musicale) - Alternative frontend for YouTube Music with style.
- [conduit](https://github.com/ai25/conduit) - Alternative frontend for YouTube, with a modern player and watch together capabilities.
- [DeskVideo](https://github.com/malisipi/DeskVideo) - Desktop styled, customizable alternative frontend for YouTube.
- [ReacTube](https://github.com/NeeRaj-2401/ReacTube) - Privacy friendly & distraction free YouTube frontend.
**Other**
- [vidyodl](https://github.com/MrKovar/vidyodl) - Simple API to download videos from YouTube, using Piped.
- [Piped Addon for Kodi](https://github.com/syhlx/plugin.video.piped) - Kodi plugin for Piped.
## YourKit ## YourKit

9
docker/entrypoint.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
if [ -z "$BACKEND_HOSTNAME" ]; then
echo "BACKEND_HOSTNAME not set"
exit 1
fi
sed -i s/pipedapi.kavin.rocks/"$BACKEND_HOSTNAME"/g /usr/share/nginx/html/assets/*
nginx -g "daemon off;"

View File

@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html style="background: #0f0f0f" lang="en" > <html style="background: #0f0f0f" lang="en" >
<head> <head>
<base href="%BASE_URL%"/>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />

View File

@ -1,7 +1,8 @@
#!/bin/sh #!/bin/sh
base='https://fonts\.(gstatic\.com|kavin\.rocks)' base='https://fonts\.(gstatic\.com|kavin\.rocks)'
fonts=$(cat dist/assets/* | grep -Po "$base[^)]*" | sort | uniq) fonts=$(cat dist/assets/* | grep -Eo "${base}[^)]*" | sort | uniq)
for font in $fonts; do 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")" mkdir -p "$(dirname "$file")"

View File

@ -2,61 +2,55 @@
"name": "piped", "name": "piped",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"serve": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"format": "prettier -w --ignore-path .gitignore **/**.{js,vue,json}", "format": "prettier -w --ignore-path .gitignore **/**.{js,vue,json}",
"lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ." "lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "6.4.2", "dompurify": "3.1.3",
"@fortawesome/free-brands-svg-icons": "6.4.2", "efy": "24.5.19",
"@fortawesome/free-solid-svg-icons": "6.4.2", "fast-xml-parser": "4.3.6",
"@fortawesome/vue-fontawesome": "3.0.3", "hotkeys-js": "3.13.7",
"buffer": "6.0.3", "javascript-time-ago": "2.5.10",
"dompurify": "3.0.5", "linkify-html": "4.1.3",
"efy": "24.2.25", "linkifyjs": "4.1.3",
"hotkeys-js": "3.12.0",
"javascript-time-ago": "2.5.9",
"linkify-html": "4.1.1",
"linkifyjs": "4.1.1",
"mux.js": "6.3.0",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"shaka-player": "4.4.1", "shaka-player": "4.8.2",
"stream-browserify": "3.0.0", "vue": "3.4.25",
"vite-plugin-static-copy": "0.17.1", "vue-i18n": "9.13.1",
"vue": "3.3.4", "vue-router": "4.3.2"
"vue-i18n": "9.4.0",
"vue-router": "4.2.4",
"xml-js": "1.6.11"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/fa6-brands": "1.1.13", "@iconify-json/fa6-brands": "1.1.19",
"@iconify-json/fa6-solid": "1.1.15", "@iconify-json/fa6-solid": "1.1.21",
"@intlify/unplugin-vue-i18n": "1.2.0", "@intlify/unplugin-vue-i18n": "4.0.0",
"@unocss/eslint-config": "0.55.7", "@unocss/eslint-config": "0.58.9",
"@unocss/preset-icons": "0.55.7", "@unocss/preset-icons": "0.58.9",
"@unocss/preset-uno": "0.55.7", "@unocss/preset-uno": "0.58.9",
"@unocss/preset-web-fonts": "0.55.7", "@unocss/preset-web-fonts": "0.58.9",
"@unocss/reset": "0.55.7", "@unocss/reset": "0.58.9",
"@unocss/transformer-directives": "0.55.7", "@unocss/transformer-directives": "0.58.9",
"@unocss/transformer-variant-group": "0.55.7", "@unocss/transformer-variant-group": "0.58.9",
"@vitejs/plugin-legacy": "4.1.1", "@vitejs/plugin-legacy": "5.4.0",
"@vitejs/plugin-vue": "4.3.4", "@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.3.4", "@vue/compiler-sfc": "3.4.25",
"efy": "24.2.25", "efy": "24.5.19",
"eslint": "8.49.0", "eslint": "8.57.0",
"eslint-config-prettier": "9.0.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.0.0", "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-vue": "9.17.0", "eslint-plugin-vue": "9.25.0",
"lightningcss": "1.21.8", "lightningcss": "1.24.1",
"prettier": "3.0.3", "prettier": "3.2.5",
"unocss": "0.55.7", "unocss": "0.58.9",
"vite": "4.4.9", "vite": "5.2.11",
"vite-plugin-eslint": "1.8.1", "vite-plugin-eslint": "1.8.1",
"vite-plugin-pwa": "0.16.5", "vite-plugin-pwa": "0.20.0",
"workbox-window": "7.0.0" "vite-plugin-static-copy": "^1.0.5",
"workbox-window": "7.1.0"
}, },
"browserslist": [ "browserslist": [
"last 1 chrome version", "last 1 chrome version",

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,20 @@
"matchPackagePrefixes": ["@unocss/"], "matchPackagePrefixes": ["@unocss/"],
"matchPackageNames": ["unocss"], "matchPackageNames": ["unocss"],
"groupName": "unocss" "groupName": "unocss"
},
{
"matchPackagePrefixes": ["linkify"],
"groupName": "linkify"
},
{
"matchPackagePrefixes": ["@vitejs/"],
"matchPackageNames": ["vite"],
"groupName": "vite"
},
{
"matchPackagePrefixes": ["@iconify-json/"],
"groupName": "iconify",
"automerge": true
} }
], ],
"lockFileMaintenance": { "lockFileMaintenance": {

View File

@ -10,6 +10,129 @@
<FooterComponent /> <FooterComponent />
</template> </template>
<script>
import NavBar from "./components/NavBar.vue";
import FooterComponent from "./components/FooterComponent.vue";
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
export default {
components: {
NavBar,
FooterComponent,
},
data() {
return {
theme: "dark",
};
},
mounted() {
this.setTheme();
darkModePreference.addEventListener("change", () => {
this.setTheme();
});
if ("indexedDB" in window) {
const request = indexedDB.open("piped-db", 6);
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 });
}
// migration to fix an invalid previous length of channel ids: 11 -> 24
(async () => {
if (ev.oldVersion < 6) {
const subscriptions = await this.fetchSubscriptions();
const channelGroups = await this.getChannelGroups();
for (let group of channelGroups) {
for (let i = 0; i < group.channels.length; i++) {
const tooShortChannelId = group.channels[i];
const foundChannel = subscriptions.find(
channel => channel.url.substr(-11) == tooShortChannelId,
);
if (foundChannel) group.channels[i] = foundChannel.url.substr(-24);
}
this.createOrUpdateChannelGroup(group);
}
}
})();
};
request.onsuccess = e => {
window.db = e.target.result;
};
} else console.log("This browser doesn't support IndexedDB");
const App = this;
(async function () {
const defaultLang = await App.defaultLanguage;
const locale = App.getPreferenceString("hl", defaultLang);
if (locale !== App.TimeAgoConfig.locale) {
const localeTime = await import(`../node_modules/javascript-time-ago/locale/${locale}.json`)
.catch(() => null)
.then(module => module?.default);
if (localeTime) {
App.TimeAgo.addLocale(localeTime);
App.TimeAgoConfig.locale = locale;
}
}
if (window.i18n.global.locale.value !== locale) {
if (!window.i18n.global.availableLocales.includes(locale)) {
const messages = await import(`./locales/${locale}.json`).then(module => module.default);
window.i18n.global.setLocaleMessage(locale, messages);
}
window.i18n.global.locale.value = locale;
}
})();
},
methods: {
setTheme() {
let themePref = this.getPreferenceString("theme", "dark"); // dark, light or auto
const themes = {
dark: "dark",
light: "light",
auto: darkModePreference.matches ? "dark" : "light",
};
this.theme = themes[themePref];
this.changeTitleBarColor();
// Used for the scrollbar
const root = document.querySelector(":root");
this.theme === "dark" ? root.classList.add("dark") : root.classList.remove("dark");
},
changeTitleBarColor() {
const currentColor = { dark: "#0F0F0F", light: "#FFF" };
const themeColor = document.querySelector("meta[name='theme-color']");
themeColor.setAttribute("content", currentColor[this.theme]);
},
},
};
</script>
<style> <style>
#app { #app {
min-height: calc(var(--efy_100vh) - var(--efy_gap2)); min-height: calc(var(--efy_100vh) - var(--efy_gap2));
@ -91,6 +214,12 @@ video {
} }
.pp-video-card-buttons :is(a:not(.pp-color), button:not(.pp-color)) { .pp-video-card-buttons :is(a:not(.pp-color), button:not(.pp-color)) {
background: var(--efy_bg1); background: var(--efy_bg1);
&:has([class*="headphones"], [class*="circle-plus"]) {
aspect-ratio: 1;
}
i {
margin: 0;
}
} }
.pp-video-card-buttons .pp-color { .pp-video-card-buttons .pp-color {
color: var(--efy_text2); color: var(--efy_text2);
@ -138,106 +267,103 @@ video {
:is(.pp-video-card-channel > a, .pp-video-card-channel > .pp-text):empty { :is(.pp-video-card-channel > a, .pp-video-card-channel > .pp-text):empty {
display: none; display: none;
} }
i[class*="i-fa"] {
}
.cards_horizontal {
.pp-rec-vids {
grid-template-columns: 1fr 500rem;
}
.video-grid {
grid-template-columns: repeat(auto-fill, minmax(480rem, 1fr));
}
.video-card {
display: flex;
flex-direction: row;
.video_item_link {
img {
height: 100%;
border-radius: var(--efy_radius) 0 0 var(--efy_radius);
}
.pp-video-card-title,
.flex {
display: none !important;
}
.pp-time,
.pp-watched {
position: absolute;
padding: 2rem 8rem;
border: var(--efy_border);
border-radius: var(--efy_radius0);
}
.pp-time {
background: var(--efy_bg);
bottom: var(--efy_gap00);
left: var(--efy_gap00);
}
.pp-watched {
margin: 0;
bottom: var(--efy_gap00);
right: calc(var(--efy_gap00) + 4rem);
}
}
.pp-card-info {
display: flex;
flex-direction: column;
max-width: 200rem;
.pp-video-card-2 {
margin: 5rem 0;
}
.pp-video-card-title {
font-weight: bold;
}
.pp-video-card-buttons {
width: 100%;
display: flex;
flex-direction: row;
.pp-time,
.pp-watched {
display: none;
}
}
.pp-video-card-channel {
width: 100%;
display: flex;
margin-top: 0;
> .pp-text span {
max-width: 90rem;
}
}
}
}
}
.video-card.watched {
.video_item_link img {
filter: brightness(0.3) saturate(0) !important;
}
}
[efy_mode*="light"] .video-card.watched {
.video_item_link img {
filter: contrast(0.3) saturate(0) !important;
}
}
.cards_horizontal .watched_progress {
height: 100% !important;
width: 4rem !important;
bottom: calc(100% + 7rem) !important;
left: calc(100% - 4rem) !important;
div:nth-of-type(1) {
display: none;
}
div:nth-of-type(2) {
width: 4rem;
border-radius: var(--efy_radius) var(--efy_radius) 0 0;
background: var(--efy_color);
box-shadow: -3rem 0 5rem #0005;
}
}
body:not(.cards_horizontal) .pp-video-card-2 {
display: none;
}
</style> </style>
<script>
import NavBar from "./components/NavBar.vue";
import FooterComponent from "./components/FooterComponent.vue";
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
export default {
components: {
NavBar,
FooterComponent,
},
data() {
return {
theme: "dark",
};
},
mounted() {
this.setTheme();
darkModePreference.addEventListener("change", () => {
this.setTheme();
});
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;
(async function () {
const defaultLang = await App.defaultLanguage;
const locale = App.getPreferenceString("hl", defaultLang);
if (locale !== App.TimeAgoConfig.locale) {
const localeTime = await import(`../node_modules/javascript-time-ago/locale/${locale}.json`)
.catch(() => null)
.then(module => module?.default);
if (localeTime) {
App.TimeAgo.addLocale(localeTime);
App.TimeAgoConfig.locale = locale;
}
}
if (window.i18n.global.locale.value !== locale) {
if (!window.i18n.global.availableLocales.includes(locale)) {
const messages = await import(`./locales/${locale}.json`).then(module => module.default);
window.i18n.global.setLocaleMessage(locale, messages);
}
window.i18n.global.locale.value = locale;
}
})();
},
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

@ -0,0 +1,80 @@
<template>
<ModalComponent @close="$emit('close')">
<div class="min-w-[50vw] flex flex-col">
<div class="h-[70vh] overflow-y-scroll pr-4">
<span v-t="'actions.add_to_group'" class="mb-3 inline-block w-max text-2xl" />
<div v-for="(group, index) in channelGroups" :key="group.groupName" class="px-1">
<div class="flex items-center justify-between">
<span>{{ group.groupName }}</span>
<input
type="checkbox"
:checked="group.channels.includes(channelId)"
@change="onCheckedChange(index, group)"
/>
</div>
<hr class="h-1 w-full" />
</div>
</div>
<button v-t="'actions.create_group'" class="btn ml-auto w-max" @click="showCreateGroupModal = true" />
</div>
</ModalComponent>
<CreateGroupModal
v-if="showCreateGroupModal"
:on-create-group="onCreateGroup"
@close="showCreateGroupModal = false"
/>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
import CreateGroupModal from "./CreateGroupModal.vue";
export default {
components: {
ModalComponent,
CreateGroupModal,
},
props: {
channelId: {
type: String,
required: true,
},
},
emits: ["close"],
data() {
return {
showCreateGroupModal: false,
channelGroups: [],
};
},
mounted() {
this.loadChannelGroups();
},
methods: {
async loadChannelGroups() {
const groups = await this.getChannelGroups();
this.channelGroups.push(...groups);
},
onCheckedChange(index, group) {
if (group.channels.includes(this.channelId)) {
group.channels.splice(index, 1);
} else {
group.channels.push(this.channelId);
}
this.createOrUpdateChannelGroup(group);
},
onCreateGroup(newGroupName) {
if (!newGroupName || this.channelGroups.some(group => group.groupName == newGroupName)) return;
const newGroup = {
groupName: newGroupName,
channels: [],
};
this.channelGroups.push(newGroup);
this.createOrUpdateChannelGroup(newGroup);
this.showCreateGroupModal = false;
},
},
};
</script>

View File

@ -1,35 +1,84 @@
<template> <template>
<div class="flex flex-col efy_trans_filter efy_shadow_trans"> <div class="efy_trans_filter efy_shadow_trans flex flex-col">
<router-link <router-link
:to="props.item.url" :to="item.url"
class="flex items-center p-[10rem] gap-[10rem]" class="flex items-center gap-[10rem] p-[10rem]"
style="border-bottom: var(--efy_border)" style="border-bottom: var(--efy_border)"
> >
<img <img
class="efy_shadow_trans" class="efy_shadow_trans"
style="border-radius: var(--efy_radius); width: 40rem; aspect-ratio: 1" style="border-radius: var(--efy_radius); width: 40rem; aspect-ratio: 1"
:src="props.item.thumbnail" :src="item.thumbnail"
loading="lazy" loading="lazy"
width="40" width="40"
height="40" height="40"
/> />
<div class="flex items-center overflow-hidden pp-text"> <div class="pp-text flex items-center overflow-hidden">
<p v-text="props.item.name" class="pp-video-card-title p-0!" /> <p class="pp-video-card-title p-0!" v-text="item.name" />
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" /> <i v-if="item.verified" class="i-fa6-solid:check ml-1.5" />
</div> </div>
</router-link> </router-link>
<div style="padding: 10rem"> <div style="padding: 10rem">
<p v-if="props.item.description" v-text="props.item.description" /> <p v-if="item.description" v-text="item.description" />
<div v-if="props.item.videos >= 0" v-text="`${props.item.videos} ${$t('video.videos')}`" /> <div v-if="item.videos >= 0" v-text="`${item.videos} ${$t('video.videos')}`" />
</div> </div>
<router-link v-if="item.uploaderUrl" class="link" :to="item.uploaderUrl">
<p>
<span v-text="item.uploader" />
<i v-if="item.uploaderVerified" class="i-fa6-solid:check ml-1.5" />
</p>
</router-link>
<a v-if="item.uploaderName" class="link" v-text="item.uploaderName" />
<template v-if="item.videos >= 0">
<br v-if="item.uploaderName" />
<strong v-text="`${item.videos} ${$t('video.videos')}`" />
</template>
<button
v-if="subscribed != null"
class="btn mt-2 w-max"
@click="subscribeHandler"
v-text="
$t('actions.' + (subscribed ? 'unsubscribe' : 'subscribe')) + ' - ' + numberFormat(item.subscribers)
"
/>
</div> </div>
</template> </template>
<script setup> <script>
const props = defineProps({ export default {
item: { props: {
type: Object, item: {
required: true, type: Object,
required: true,
},
}, },
}); data() {
return {
subscribed: null,
};
},
computed: {
channelId(_this) {
return _this.item.url.substr(-24);
},
},
mounted() {
this.updateSubscribedStatus();
},
methods: {
async updateSubscribedStatus() {
this.subscribed = await this.fetchSubscriptionStatus(this.channelId);
console.log(this.subscribed);
},
subscribeHandler() {
this.toggleSubscriptionState(this.channelId, this.subscribed).then(success => {
if (success) this.subscribed = !this.subscribed;
});
},
},
};
</script> </script>

View File

@ -1,23 +1,38 @@
<template> <template>
<ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" /> <ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
<div v-if="channel" v-show="!channel.error" class="mt-[15rem]"> <LoadingIndicatorPage :show-content="channel != null && !channel.error">
<LoadingIndicatorPage :show-content="channel != null && !channel.error"> <img
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full efy_shadow_trans" loading="lazy" /> v-if="channel.bannerUrl"
loading="lazy"
:src="channel.bannerUrl"
class="efy_shadow_trans mt-[15rem] w-full"
/>
<div class="flex flex-col">
<div class="pp-channel-page-author flex"> <div class="pp-channel-page-author flex">
<img height="48" width="48" class="efy_shadow_trans" :src="channel.avatarUrl" /> <img height="48" width="48" class="efy_shadow_trans" :src="channel.avatarUrl" />
<h5 v-text="channel.name" /> <div class="flex items-center gap-1">
<font-awesome-icon v-if="channel.verified" class="ml-1.5" icon="check" /> <h5 v-text="channel.name" />
<i v-if="channel.verified" class="i-fa6-solid:check ml-2" />
</div>
</div> </div>
<p v-text="channel.description" style="margin: 10rem 0 0 0" /> <p style="margin: 10rem 0 0 0" v-text="channel.description" />
<div class="pp-channel-tabs"> <div class="pp-channel-tabs">
<button <button
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(channel.subscriberCount) },
}"
class="pp-subscribe" class="pp-subscribe"
@click="subscribeHandler" @click="subscribeHandler"
v-text="
$t('actions.' + (subscribed ? 'unsubscribe' : 'subscribe')) +
' - ' +
numberFormat(channel.subscriberCount)
"
></button>
<button
v-if="subscribed"
v-t="'actions.add_to_group'"
class="btn"
@click="showGroupModal = true"
></button> ></button>
<!-- RSS Feed button --> <!-- RSS Feed button -->
@ -31,7 +46,7 @@
class="pp-square" class="pp-square"
style="display: inline; float: unset" style="display: inline; float: unset"
> >
<font-awesome-icon icon="rss" /> <i class="i-fa6-solid:rss" />
</a> </a>
<WatchOnButton :link="`https://youtube.com/channel/${channel.id}`" /> <WatchOnButton :link="`https://youtube.com/channel/${channel.id}`" />
<p style="place-self: center">|</p> <p style="place-self: center">|</p>
@ -44,43 +59,33 @@
<span v-text="tab.translatedName"></span> <span v-text="tab.translatedName"></span>
</button> </button>
</div> </div>
</div>
<hr /> <hr />
<div class="video-grid"> <div class="video-grid">
<ContentItem <ContentItem
v-for="item in contentItems" v-for="item in contentItems"
:key="item.url" :key="item.url"
:item="item" :item="item"
height="94" height="94"
width="168" width="168"
hide-channel hide-channel
class="efy_trans_filter" class="efy_trans_filter"
/> />
</div> </div>
</LoadingIndicatorPage>
</div> <AddToGroupModal v-if="showGroupModal" :channel-id="channel.id.substr(-24)" @close="showGroupModal = false" />
</LoadingIndicatorPage>
</template> </template>
<style>
.pp-channel-tabs {
display: flex;
flex-wrap: wrap;
margin: 15rem 0;
gap: var(--efy_gap0);
}
.pp-channel-tabs :is(button, [role="button"]) {
margin: 0;
border: 0;
}
</style>
<script> <script>
import ErrorHandler from "./ErrorHandler.vue"; import ErrorHandler from "./ErrorHandler.vue";
import ContentItem from "./ContentItem.vue"; import ContentItem from "./ContentItem.vue";
import WatchOnButton from "./WatchOnButton.vue"; import WatchOnButton from "./WatchOnButton.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue"; import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
// import CollapsableText from "./CollapsableText.vue"; // import CollapsableText from "./CollapsableText.vue";
import AddToGroupModal from "./AddToGroupModal.vue";
export default { export default {
components: { components: {
@ -89,6 +94,7 @@ export default {
WatchOnButton, WatchOnButton,
LoadingIndicatorPage, LoadingIndicatorPage,
// CollapsableText, // CollapsableText,
AddToGroupModal,
}, },
data() { data() {
return { return {
@ -97,6 +103,7 @@ export default {
tabs: [], tabs: [],
selectedTab: 0, selectedTab: 0,
contentItems: [], contentItems: [],
showGroupModal: false,
}; };
}, },
mounted() { mounted() {
@ -116,24 +123,8 @@ export default {
methods: { methods: {
async fetchSubscribedStatus() { async fetchSubscribedStatus() {
if (!this.channel.id) return; if (!this.channel.id) return;
if (!this.authenticated) {
this.subscribed = this.isSubscribedLocally(this.channel.id);
return;
}
this.fetchJson( this.subscribed = await this.fetchSubscriptionStatus(this.channel.id);
this.authApiUrl() + "/subscribed",
{
channelId: this.channel.id,
},
{
headers: {
Authorization: this.getAuthToken(),
},
},
).then(json => {
this.subscribed = json.subscribed;
});
}, },
async fetchChannel() { async fetchChannel() {
const url = this.$route.path.includes("@") const url = this.$route.path.includes("@")
@ -205,21 +196,9 @@ export default {
}); });
}, },
subscribeHandler() { subscribeHandler() {
if (this.authenticated) { this.toggleSubscriptionState(this.channel.id, this.subscribed).then(success => {
this.fetchJson(this.authApiUrl() + (this.subscribed ? "/unsubscribe" : "/subscribe"), null, { if (success) this.subscribed = !this.subscribed;
method: "POST", });
body: JSON.stringify({
channelId: this.channel.id,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
});
} else {
if (!this.handleLocalSubscriptions(this.channel.id)) return;
}
this.subscribed = !this.subscribed;
}, },
getTranslatedTabName(tabName) { getTranslatedTabName(tabName) {
let translatedTabName = tabName; let translatedTabName = tabName;
@ -230,8 +209,8 @@ export default {
case "playlists": case "playlists":
translatedTabName = this.$t("titles.playlists"); translatedTabName = this.$t("titles.playlists");
break; break;
case "channels": case "albums":
translatedTabName = this.$t("titles.channels"); translatedTabName = this.$t("titles.albums");
break; break;
case "shorts": case "shorts":
translatedTabName = this.$t("video.shorts"); translatedTabName = this.$t("video.shorts");
@ -270,3 +249,16 @@ export default {
}, },
}; };
</script> </script>
<style>
.pp-channel-tabs {
display: flex;
flex-wrap: wrap;
margin: 15rem 0 0 0;
gap: var(--efy_gap0);
}
.pp-channel-tabs :is(button, [role="button"]) {
margin: 0;
border: 0;
}
</style>

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="pp-chapters max-h-75vh"> <div class="pp-chapters max-h-75vh">
<h6 class="title efy_trans_filter efy_shadow_trans">{{ $t("video.chapters") }} - {{ chapters.length }}</h6> <h6 class="efy_trans_filter efy_shadow_trans title">{{ $t("video.chapters") }} - {{ chapters.length }}</h6>
<div <div
v-for="(chapter, index) in chapters" v-for="(chapter, index) in chapters"
:key="chapter.start" :key="chapter.start"
class="chapter flex efy_anim_pulse" class="chapter efy_anim_pulse flex"
:class="isCurrentChapter(index) ? 'pp-chapter-active' : 'efy_shadow_trans efy_trans_filter'" :class="isCurrentChapter(index) ? 'pp-chapter-active' : 'efy_shadow_trans efy_trans_filter'"
@click="$emit('seek', chapter.start)" @click="$emit('seek', chapter.start)"
> >
<img :src="chapter.image" :alt="chapter.title" /> <img :src="chapter.image" :alt="chapter.title" />
<span <span
:title="chapter.title" :title="chapter.title"
v-text="timeFormat(chapter.start) + ' - ' + chapter.title"
class="text font-bold" class="text font-bold"
v-text="timeFormat(chapter.start) + ' - ' + chapter.title"
/> />
</div> </div>
</div> </div>

View File

@ -1,9 +1,9 @@
<template v-if="text"> <template v-if="text">
<div class="whitespace-pre-wrap"> <div class="whitespace-pre-wrap">
<!-- eslint-disable-next-line vue/no-v-html --> <!-- eslint-disable-next-line vue/no-v-html -->
<span v-if="showFullText" v-html="fullText()" /> <span v-if="showFullText" class="contentText" v-html="fullText()" />
<!-- eslint-disable-next-line vue/no-v-html --> <!-- eslint-disable-next-line vue/no-v-html -->
<span v-else v-html="colapsedText()" /> <span v-else v-html="collapsedText()" />
<span v-if="text.length > visibleLimit && !showFullText">...</span> <span v-if="text.length > visibleLimit && !showFullText">...</span>
<button <button
v-if="text.length > visibleLimit" v-if="text.length > visibleLimit"
@ -44,9 +44,15 @@ export default {
fullText() { fullText() {
return purifyHTML(rewriteDescription(this.text)); return purifyHTML(rewriteDescription(this.text));
}, },
colapsedText() { collapsedText() {
return purifyHTML(rewriteDescription(this.text.slice(0, this.visibleLimit))); return purifyHTML(rewriteDescription(this.text.slice(0, this.visibleLimit)));
}, },
}, },
}; };
</script> </script>
<style>
.contentText {
word-wrap: anywhere;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="comment flex mt-1.5"> <div class="comment mt-1.5 flex">
<router-link style="height: fit-content" :to="comment.commentorUrl"> <router-link style="height: fit-content" :to="comment.commentorUrl">
<img :src="comment.thumbnail" class="comment-avatar" height="48" width="48" loading="lazy" alt="Avatar" /> <img :src="comment.thumbnail" class="comment-avatar" height="48" width="48" loading="lazy" alt="Avatar" />
</router-link> </router-link>
@ -7,7 +7,7 @@
<div class="comment-content pl-2"> <div class="comment-content pl-2">
<div class="comment-header"> <div class="comment-header">
<div v-if="comment.pinned" class="comment-pinned"> <div v-if="comment.pinned" class="comment-pinned">
<font-awesome-icon icon="thumbtack" /> <i class="i-fa6-solid:thumbtack" />
<span <span
v-t="{ v-t="{
path: 'comment.pinned_by', path: 'comment.pinned_by',
@ -17,14 +17,20 @@
/> />
</div> </div>
<div class="comment-author flex align-center"> <div class="comment-author align-center flex">
<router-link class="link font-bold" :to="comment.commentorUrl">{{ comment.author }}</router-link> <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" /> <i v-if="comment.verified" class="i-fa6-solid:check ml-1.5" />
<div class="comment-meta mb-1.5" v-text="' ' + comment.commentedTime + ' '" /> <div class="comment-meta mb-1.5" v-text="' · ' + comment.commentedTime + ' ·'" />
<div class="comment-footer mt-1 flex items-center"> <div class="comment-footer mt-1 flex items-center">
<div class="i-fa6-solid:thumbs-up" /> <div class="i-fa6-solid:thumbs-up" />
<span class="ml-1" v-text="numberFormat(comment.likeCount)" /> <span class="ml-1" v-text="numberFormat(comment.likeCount)" />
<font-awesome-icon v-if="comment.hearted" class="ml-1" icon="heart" /> <i v-if="comment.hearted" class="i-fa6-solid:heart ml-1" />
<img
v-if="comment.creatorReplied"
:src="uploaderAvatarUrl"
class="h-5 w-5 rounded-full"
:title="$t('actions.creator_replied')"
/>
</div> </div>
</div> </div>
</div> </div>
@ -33,22 +39,23 @@
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)"> <template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
<div class="cursor-pointer" @click="loadReplies"> <div class="cursor-pointer" @click="loadReplies">
<a v-text="`${$t('actions.reply_count', comment.replyCount)}`" /> <a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
<font-awesome-icon class="ml-1.5" icon="level-down-alt" /> <i class="i-fa6-solid:level-down-alt ml-1.5" />
</div> </div>
</template> </template>
<template v-if="showingReplies"> <template v-if="showingReplies">
<div class="cursor-pointer" @click="hideReplies"> <div class="cursor-pointer" @click="hideReplies">
<a v-t="'actions.hide_replies'" /> <a v-t="'actions.hide_replies'" />
<font-awesome-icon class="ml-1.5" icon="level-up-alt" /> <i class="i-fa6-solid:level-up-alt ml-1.5" />
</div> </div>
</template> </template>
<div v-show="showingReplies" v-if="replies" class="replies"> <div v-show="showingReplies" v-if="replies" class="replies">
<div v-for="reply in replies" :key="reply.commentId" class="w-full"> <div v-for="reply in replies" :key="reply.commentId" class="w-full">
<!-- eslint-disable-next-line vue/no-undef-components -->
<CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" /> <CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
</div> </div>
<div v-if="nextpage" class="cursor-pointer" @click="loadReplies"> <div v-if="nextpage" class="cursor-pointer" @click="loadReplies">
<a v-t="'actions.load_more_replies'" /> <a v-t="'actions.load_more_replies'" />
<font-awesome-icon class="ml-1.5" icon="level-down-alt" /> <i class="i-fa6-solid:level-down-alt ml-1.5" />
</div> </div>
</div> </div>
</div> </div>
@ -68,6 +75,7 @@ export default {
}, },
}, },
uploader: { type: String, default: null }, uploader: { type: String, default: null },
uploaderAvatarUrl: { type: String, default: null },
videoId: { type: String, default: null }, videoId: { type: String, default: null },
}, },
data() { data() {
@ -80,6 +88,7 @@ export default {
}, },
methods: { methods: {
async loadReplies() { async loadReplies() {
console.log(this.uploaderAvatarUrl);
if (!this.showingReplies && this.loadingReplies) { if (!this.showingReplies && this.loadingReplies) {
this.showingReplies = true; this.showingReplies = true;
return; return;

View File

@ -25,5 +25,19 @@ export default {
}, },
}, },
emits: ["close", "confirm"], emits: ["close", "confirm"],
mounted() {
window.addEventListener("keydown", this.handleKeyDown);
},
unmounted() {
window.removeEventListener("keydown", this.handleKeyDown);
},
methods: {
handleKeyDown(event) {
if (event.code === "Enter") {
this.$emit("confirm");
event.preventDefault();
}
},
},
}; };
</script> </script>

View File

@ -0,0 +1,35 @@
<template>
<ModalComponent @close="$emit('close')">
<h2 v-t="'actions.create_group'" />
<div class="flex flex-col">
<input v-model="groupName" 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>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: { ModalComponent },
props: {
onCreateGroup: {
required: true,
type: Function,
},
},
emits: ["close"],
data() {
return {
groupName: "",
};
},
methods: {
createGroup() {
this.onCreateGroup(this.groupName);
this.$emit("close");
},
},
};
</script>

View File

@ -0,0 +1,54 @@
<template>
<ModalComponent @close="$emit('close')">
<div class="flex flex-col">
<h2 v-t="'actions.create_playlist'" />
<input ref="input" v-model="playlistName" type="text" class="input mt-2" />
<div class="ml-auto mt-3 w-min flex">
<button v-t="'actions.cancel'" class="btn" @click="$emit('close')" />
<button v-t="'actions.okay'" class="btn ml-2" @click="onCreatePlaylist" />
</div>
</div>
</ModalComponent>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: {
ModalComponent,
},
emits: ["created", "close"],
data() {
return {
playlistName: "",
};
},
mounted() {
this.$refs.input.focus();
window.addEventListener("keydown", this.handleKeyDown);
},
unmounted() {
window.removeEventListener("keydown", this.handleKeyDown);
},
methods: {
handleKeyDown(event) {
if (event.code === "Enter") {
this.onCreatePlaylist();
event.preventDefault();
}
},
onCreatePlaylist() {
if (!this.playlistName) return;
this.createPlaylist(this.playlistName).then(response => {
if (response.error) alert(response.error);
else {
this.$emit("created", response.playlistId, this.playlistName);
this.$emit("close");
}
});
},
},
};
</script>

View File

@ -0,0 +1,83 @@
<template>
<ModalComponent @close="$emit('close')">
<h3 v-t="'titles.custom_instances'" class="my-4 font-bold" />
<hr />
<div class="text-center">
<div>
<div v-for="(customInstance, index) in customInstances" :key="customInstance.name">
<div class="flex items-center justify-between">
<span>{{ customInstance.name }} - {{ customInstance.api_url }}</span>
<button class="pp-square" style="padding: 0" @click="removeInstance(customInstance, index)">
<i class="i-fa6-solid:circle-minus m-0" />
</button>
</div>
<hr />
</div>
</div>
<form class="flex flex-col items-end gap-2">
<input
v-model="name"
type="text"
:placeholder="$t('preferences.instance_name')"
style="max-width: unset"
/>
<input
v-model="url"
type="text"
:placeholder="$t('preferences.api_url')"
style="max-width: unset; margin: var(--efy_gap) 0"
@keyup.enter="addInstance"
/>
<button v-t="'actions.add'" @click.prevent="addInstance" />
</form>
</div>
</ModalComponent>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: { ModalComponent },
emits: ["close"],
data() {
return {
customInstances: [],
name: "",
url: "",
};
},
mounted() {
this.customInstances = this.getCustomInstances();
},
methods: {
async addInstance() {
const newInstance = {
name: this.name,
api_url: this.url,
};
if (!newInstance.name || !newInstance.api_url) {
return;
}
if (!this.isValidInstanceUrl(newInstance.api_url)) {
alert(this.$t("actions.invalid_url"));
return;
}
this.addCustomInstance(newInstance);
this.name = "";
this.url = "";
},
removeInstance(instance, index) {
this.customInstances.splice(index, 1);
this.removeCustomInstance(instance);
},
isValidInstanceUrl(str) {
var a = document.createElement("a");
a.href = str;
return a.host && a.host != window.location.host;
},
},
};
</script>

View File

@ -1,14 +1,14 @@
<template> <template>
<hr /> <hr />
<div class="flex flex-wrap align-center" style="place-content: space-between; gap: var(--efy_gap0)"> <div class="align-center flex flex-wrap" style="place-content: space-between; gap: var(--efy_gap0)">
<span class="buttons flex" style="gap: var(--efy_gap0)"> <span class="buttons flex" style="gap: var(--efy_gap0)">
<router-link role="button" to="/subscriptions">Subscriptions</router-link> <router-link role="button" to="/subscriptions">Subscriptions</router-link>
<a :href="getRssUrl" role="button" class="pp-square"> <a :href="getRssUrl" role="button" class="pp-square" style="padding: 0">
<font-awesome-icon icon="rss" /> <i class="i-fa6-solid:rss m-0" />
</a> </a>
</span> </span>
<div class="filters flex align-center"> <div class="align-center filters flex">
<span class="flex"> <span class="flex">
<label for="filters" v-text="`${$t('actions.filter')}:`" /> <label for="filters" v-text="`${$t('actions.filter')}:`" />
<select <select
@ -48,29 +48,6 @@
</LoadingIndicatorPage> </LoadingIndicatorPage>
</template> </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> <script>
import VideoItem from "./VideoItem.vue"; import VideoItem from "./VideoItem.vue";
import SortingSelector from "./SortingSelector.vue"; import SortingSelector from "./SortingSelector.vue";
@ -101,14 +78,24 @@ export default {
}, },
filteredVideos(_this) { filteredVideos(_this) {
const selectedGroup = _this.channelGroups.filter(group => group.groupName == _this.selectedGroupName); const selectedGroup = _this.channelGroups.filter(group => group.groupName == _this.selectedGroupName);
const videos = this.getPreferenceBoolean("hideWatched", false)
? this.videos.filter(video => !video.watched)
: this.videos;
return _this.selectedGroupName == "" return _this.selectedGroupName == ""
? _this.videos ? videos
: _this.videos.filter(video => selectedGroup[0].channels.includes(video.uploaderUrl.substr(-11))); : videos.filter(video => selectedGroup[0].channels.includes(video.uploaderUrl.substr(-24)));
}, },
}, },
mounted() { mounted() {
this.fetchFeed().then(videos => { this.fetchFeed().then(resp => {
this.videosStore = videos; if (resp.error) {
alert(resp.error);
return;
}
this.videosStore = resp;
this.loadMoreVideos(); this.loadMoreVideos();
this.updateWatched(this.videos); this.updateWatched(this.videos);
}); });
@ -117,18 +104,7 @@ export default {
if (!window.db) return; if (!window.db) return;
const cursor = this.getChannelGroupsCursor(); this.loadChannelGroups();
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() { activated() {
document.title = this.$t("titles.feed") + " - Piped"; document.title = this.$t("titles.feed") + " - Piped";
@ -142,16 +118,9 @@ export default {
window.removeEventListener("scroll", this.handleScroll); window.removeEventListener("scroll", this.handleScroll);
}, },
methods: { methods: {
async fetchFeed() { async loadChannelGroups() {
if (this.authenticated) { const groups = await this.getChannelGroups();
return await this.fetchJson(this.authApiUrl() + "/feed", { this.channelGroups.push(...groups);
authToken: this.getAuthToken(),
});
} else {
return await this.fetchJson(this.authApiUrl() + "/feed/unauthenticated", {
channels: this.getUnauthenticatedChannels(),
});
}
}, },
loadMoreVideos() { loadMoreVideos() {
if (!this.videosStore) return; if (!this.videosStore) return;
@ -182,3 +151,26 @@ export default {
}, },
}; };
</script> </script>
<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>

View File

@ -1,28 +1,28 @@
<template> <template>
<footer class="efy_trans_filter efy_shadow_trans efy_shadow_button_off"> <footer class="efy_trans_filter efy_shadow_trans efy_shadow_button_off">
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank"> <a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
<font-awesome-icon :icon="['fab', 'github']" /> <i class="i-fa6-brands:github" />
<span v-t="'actions.source_code'" /> <span v-t="'actions.source_code'" />
</a> </a>
<a href="https://docs.piped.video/" target="_blank"> <a href="https://docs.piped.video/" target="_blank">
<font-awesome-icon :icon="['fa', 'book']" /> <i class="i-fa6-solid:book" />
<span v-t="'actions.documentation'" /> <span v-t="'actions.documentation'" />
</a> </a>
<a href="https://github.com/TeamPiped/Piped#donations" target="_blank"> <a href="https://github.com/TeamPiped/Piped#donations" target="_blank">
<font-awesome-icon :icon="['fab', 'bitcoin']" /> <i class="i-fa6-brands:bitcoin" />
<span v-t="'actions.donations'" /> <span v-t="'actions.donations'" />
</a> </a>
<a v-if="statusPageHref" :href="statusPageHref"> <a v-if="statusPageHref" :href="statusPageHref">
<font-awesome-icon :icon="['fa', 'server']" /> <i class="i-fa6-solid:server" />
<span v-t="'actions.status_page'" /> <span v-t="'actions.status_page'" />
</a> </a>
<a v-if="donationHref" :href="donationHref"> <a v-if="donationHref" :href="donationHref">
<font-awesome-icon :icon="['fa', 'donate']" /> <i class="i-fa6-solid:money-check" />
<span v-t="'actions.instance_donations'" /> <span v-t="'actions.instance_donations'" />
</a> </a>
<a v-if="privacyPolicyHref" :href="privacyPolicyHref" target="_blank"> <a v-if="privacyPolicyHref" :href="privacyPolicyHref" target="_blank">
<font-awesome-icon :icon="['fa', 'eye']" /> <i class="i-fa6-solid:eye" />
<span v-t="'actions.instance_privacy_policy'" class="ml-2" /> <span v-t="'actions.instance_privacy_policy'" />
</a> </a>
</footer> </footer>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<hr /> <hr />
<div class="flex flex-wrap items-center place-content-between" style="gap: var(--efy_gap0)"> <div class="flex flex-wrap place-content-between items-center" style="gap: var(--efy_gap0)">
<div class="flex" style="gap: var(--efy_gap0)"> <div class="flex" style="gap: var(--efy_gap0)">
<button v-t="'actions.clear_history'" class="m-0" @click="clearHistory" /> <button v-t="'actions.clear_history'" class="m-0" @click="clearHistory" />
<button v-t="'actions.export_to_json'" class="m-0" @click="exportHistory" /> <button v-t="'actions.export_to_json'" class="m-0" @click="exportHistory" />
@ -28,7 +28,7 @@
<option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" /> <option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
</select> </select>
</div> </div>
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" style="gap: 0" /> <SortingSelector by-key="watchedAt" style="gap: 0" @apply="order => videos.sort(order)" />
</div> </div>
</div> </div>

View File

@ -9,10 +9,10 @@
<strong v-text="`Selected Subscriptions: ${selectedSubscriptions}`" /> <strong v-text="`Selected Subscriptions: ${selectedSubscriptions}`" />
</div> </div>
<div efy_select> <div efy_select>
<input v-model="override" id="import-override" type="checkbox" /> <input id="import-override" v-model="override" type="checkbox" />
<label for="import-override">Override</label> <label for="import-override">Override</label>
</div> </div>
<a class="btn w-auto" @click="handleImport" role="button" style="margin: 0">Import</a> <a class="btn w-auto" role="button" style="margin: 0" @click="handleImport">Import</a>
</form> </form>
<br /> <br />
<strong>Importing Subscriptions from YouTube</strong> <strong>Importing Subscriptions from YouTube</strong>
@ -88,10 +88,11 @@ export default {
}); });
} }
// NewPipe // NewPipe
else if (text.indexOf("app_version") != -1) { else if (text.indexOf("subscriptions") != -1) {
const json = JSON.parse(text); const json = JSON.parse(text);
json.subscriptions json.subscriptions
.filter(item => item.service_id == 0) // if service_id is undefined, chances are it's a freetube export
.filter(item => item.service_id == 0 || item.service_id == undefined)
.forEach(item => { .forEach(item => {
const url = item.url; const url = item.url;
const id = url.slice(-24); const id = url.slice(-24);

View File

@ -1,6 +1,6 @@
<template> <template>
<ModalComponent> <ModalComponent>
<h5 v-t="'titles.account'" class="font-bold my-4" /> <h5 v-t="'titles.account'" class="my-4 font-bold" />
<hr /> <hr />
<div class="text-center"> <div class="text-center">
<form class="children:pb-3"> <form class="children:pb-3">
@ -12,7 +12,7 @@
autocomplete="username" autocomplete="username"
:placeholder="$t('login.username')" :placeholder="$t('login.username')"
:aria-label="$t('login.username')" :aria-label="$t('login.username')"
v-on:keyup.enter="login" @keyup.enter="login"
/> />
</div> </div>
<div> <div>
@ -23,12 +23,12 @@
autocomplete="password" autocomplete="password"
:placeholder="$t('login.password')" :placeholder="$t('login.password')"
:aria-label="$t('login.password')" :aria-label="$t('login.password')"
v-on:keyup.enter="login" @keyup.enter="login"
/> />
</div> </div>
<div class="flex justify-end p-0!" style="gap: var(--efy_gap0)"> <div class="flex justify-end p-0!" style="gap: var(--efy_gap0)">
<a role="button" class="m-0!" @click="register" v-t="'titles.register'" /> <a v-t="'titles.register'" role="button" class="m-0!" @click="register" />
<a role="button" class="m-0!" @click="login" v-t="'titles.login'" /> <a v-t="'titles.login'" role="button" class="m-0!" @click="login" />
</div> </div>
</form> </form>
</div> </div>
@ -38,6 +38,7 @@
<script> <script>
import ModalComponent from "./ModalComponent.vue"; import ModalComponent from "./ModalComponent.vue";
export default { export default {
components: { ModalComponent },
data() { data() {
return { return {
username: null, username: null,
@ -85,6 +86,5 @@ export default {
}); });
}, },
}, },
components: { ModalComponent },
}; };
</script> </script>

View File

@ -1,8 +1,11 @@
<template> <template>
<h1 v-t="'titles.login'" class="my-4 text-center font-bold" /> <div class="flex justify-center">
<h1 v-t="'titles.login'" class="my-4 text-center font-bold" />
<i class="i-fa6-solid:circle-info ml-2 mt-6 cursor-pointer" :title="$t('info.login_note')" />
</div>
<hr /> <hr />
<div class="text-center"> <div class="w-full flex items-center justify-center text-center">
<form class="children:pb-3"> <form class="w-min children:pb-3">
<div> <div>
<input <input
v-model="username" v-model="username"
@ -43,7 +46,7 @@ export default {
mounted() { mounted() {
//TODO: Add Server Side check //TODO: Add Server Side check
if (this.getAuthToken()) { if (this.getAuthToken()) {
this.$router.push("/"); this.$router.push(import.meta.env.BASE_URL);
} }
}, },
activated() { activated() {
@ -61,7 +64,7 @@ export default {
}).then(resp => { }).then(resp => {
if (resp.token) { if (resp.token) {
this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token); this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token);
window.location = "/"; // done to bypass cache window.location = import.meta.env.BASE_URL; // done to bypass cache
} else alert(resp.error); } else alert(resp.error);
}); });
}, },

View File

@ -2,8 +2,8 @@
<div class="modal"> <div class="modal">
<div @click="handleClick"> <div @click="handleClick">
<div class="modal-container"> <div class="modal-container">
<button @click="$emit('close')" class="pp-color btn m-0"> <button class="pp-square btn pp-color m-0" style="padding: 0" @click="$emit('close')">
<font-awesome-icon icon="xmark" /> <i class="i-fa6-solid:xmark m-0" />
</button> </button>
<slot></slot> <slot></slot>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<nav class="pp-nav flex flex-wrap items-center justify-center px-2 sm:px-4 py-2.5 w-full relative"> <nav class="pp-nav relative w-full flex flex-wrap items-center justify-center px-2 py-2.5 sm:px-4">
<div class="flex-1 flex justify-start pp-logo"> <div class="pp-logo flex flex-1 justify-start">
<router-link class="flex font-bold text-3xl items-center font-sans" to="/"> <router-link class="flex items-center text-3xl font-bold font-sans" to="/">
<svg <svg
id="svg-logo" id="svg-logo"
version="1.2" version="1.2"
@ -54,7 +54,7 @@
>iped</router-link >iped</router-link
> >
</div> </div>
<div class="lt-md:hidden flex flex-1 justify-start" style="position: relative"> <div class="flex flex-1 justify-start lt-md:hidden" style="position: relative">
<input <input
ref="videoSearch" ref="videoSearch"
v-model="searchText" v-model="searchText"
@ -88,7 +88,7 @@
<button <button
efy_sidebar_btn="relative, pp-desktop" efy_sidebar_btn="relative, pp-desktop"
style="background: transparent; padding: 0; margin: -5rem 0 0 0; border: 0" style="background: transparent; padding: 0; margin: -5rem 0 0 0; border: 0"
class="efy_trans_filter_off efy_shadow_button_off" class="efy_shadow_button_off efy_trans_filter_off"
> >
<i efy_icon="menu" style="margin: 0" /> <i efy_icon="menu" style="margin: 0" />
</button> </button>
@ -96,18 +96,18 @@
</nav> </nav>
<!-- search suggestions for mobile devices --> <!-- search suggestions for mobile devices -->
<div class="w-{full - 4} md:hidden" style="position: relative"> <div class="- 4} w-{full md:hidden" style="position: relative">
<input <input
v-model="searchText" v-model="searchText"
type="text" type="text"
role="search" role="search"
:title="$t('actions.search')" :title="$t('actions.search')"
:placeholder="$t('actions.search')" :placeholder="$t('actions.search')"
style="margin: 15rem 0 0 0"
@keyup="onKeyUp" @keyup="onKeyUp"
@keypress="onKeyPress" @keypress="onKeyPress"
@focus="onInputFocus" @focus="onInputFocus"
@blur="onInputBlur" @blur="onInputBlur"
style="margin: 15rem 0 0 0"
/> />
<span v-if="searchText" class="delete-search" @click="searchText = ''"></span> <span v-if="searchText" class="delete-search" @click="searchText = ''"></span>
</div> </div>
@ -120,6 +120,104 @@
<LoginModal v-if="showLoginModal" @close="showLoginModal = !showLoginModal" /> <LoginModal v-if="showLoginModal" @close="showLoginModal = !showLoginModal" />
</template> </template>
<script>
import SearchSuggestions from "./SearchSuggestions.vue";
import LoginModal from "./LoginModal.vue";
import hotkeys from "hotkeys-js";
export default {
components: {
SearchSuggestions,
LoginModal,
},
data() {
return {
searchText: "",
suggestionsVisible: false,
showLoginModal: false,
showTopNav: false,
homePagePath: import.meta.env.BASE_URL,
registrationDisabled: false,
};
},
computed: {
shouldShowLogin(_this) {
return _this.getAuthToken() == null;
},
shouldShowRegister(_this) {
return _this.registrationDisabled == false ? _this.shouldShowLogin : false;
},
shouldShowHistory(_this) {
return _this.getPreferenceBoolean("watchHistory", false);
},
shouldShowTrending(_this) {
return _this.getPreferenceString("homepage", "trending") != "trending";
},
showSearchHistory(_this) {
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() {
hotkeys("ctrl+k", event => {
event.preventDefault();
this.$refs.videoSearch.focus();
});
},
onKeyUp(e) {
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault();
}
this.$refs.searchSuggestions.onKeyUp(e);
},
onKeyPress(e) {
if (e.key === "Enter") {
this.submitSearch(e);
}
},
onInputFocus() {
if (this.showSearchHistory) this.$refs.searchSuggestions.refreshSuggestions();
this.suggestionsVisible = true;
},
onInputBlur() {
// the search suggestions will be hidden after some seconds
// otherwise anchor links won't work!
setTimeout(() => (this.suggestionsVisible = false), 200);
},
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();
if (this.searchText) {
this.$router.push({
name: "SearchResults",
query: { search_query: this.searchText },
});
} else {
this.$router.push("/");
}
return;
},
},
};
</script>
<style> <style>
.pp-nav { .pp-nav {
gap: 15rem; gap: 15rem;
@ -182,95 +280,3 @@
align-content: center; align-content: center;
} }
</style> </style>
<script>
import SearchSuggestions from "./SearchSuggestions.vue";
import LoginModal from "./LoginModal.vue";
import hotkeys from "hotkeys-js";
export default {
components: {
SearchSuggestions,
LoginModal,
},
data() {
return {
searchText: "",
suggestionsVisible: false,
showLoginModal: false,
showTopNav: false,
homePagePath: "/",
registrationDisabled: false,
};
},
computed: {
shouldShowLogin(_this) {
return _this.getAuthToken() == null;
},
shouldShowRegister(_this) {
return _this.registrationDisabled == false ? _this.shouldShowLogin : false;
},
shouldShowHistory(_this) {
return _this.getPreferenceBoolean("watchHistory", false);
},
shouldShowTrending(_this) {
return _this.getPreferenceString("homepage", "trending") != "trending";
},
showSearchHistory(_this) {
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() {
hotkeys("ctrl+k", event => {
event.preventDefault();
this.$refs.videoSearch.focus();
});
},
onKeyUp(e) {
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault();
}
this.$refs.searchSuggestions.onKeyUp(e);
},
onKeyPress(e) {
if (e.key === "Enter") {
this.submitSearch(e);
}
},
onInputFocus() {
if (this.showSearchHistory) this.$refs.searchSuggestions.refreshSuggestions();
this.suggestionsVisible = true;
},
onInputBlur() {
this.suggestionsVisible = false;
},
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,6 +1,6 @@
<template> <template>
<div class="flex flex-col justify-center items-center min-h-[88vh]"> <div class="min-h-[88vh] flex flex-col items-center justify-center">
<h1 class="font-bold !text-[170rem] mb-[-6vh]">404</h1> <h1 class="mb-[-6vh] font-bold !text-[170rem]">404</h1>
<h2 v-t="'info.page_not_found'" class="!text-[40rem]" /> <h2 v-t="'info.page_not_found'" class="!text-[40rem]" />
<a v-t="'actions.back_to_home'" role="button" href="/" /> <a v-t="'actions.back_to_home'" role="button" href="/" />
</div> </div>

View File

@ -1,11 +1,16 @@
<template> <template>
<ModalComponent @close="$emit('close')"> <ModalComponent @close="$emit('close')">
<h4 v-t="'actions.select_playlist'" class="mb-2" /> <h4 v-t="'actions.select_playlist'" class="mb-2" />
<select v-model="selectedPlaylist" class="select w-full mb-2"> <select v-model="selectedPlaylist" class="select mb-2 w-full">
<option v-for="playlist in playlists" :key="playlist.id" :value="playlist.id" v-text="playlist.name" /> <option v-for="playlist in playlists" :key="playlist.id" :value="playlist.id" v-text="playlist.name" />
</select> </select>
<div class="flex justify-end" style="gap: var(--efy_gap0)"> <div class="flex justify-end" style="gap: var(--efy_gap0)">
<button ref="addButton" v-t="'actions.create_playlist'" class="btn pp-color" @click="onCreatePlaylist" /> <button
ref="addButton"
v-t="'actions.create_playlist'"
class="btn pp-color"
@click="showCreatePlaylistModal = true"
/>
<button <button
ref="addButton" ref="addButton"
v-t="'actions.add_to_playlist'" v-t="'actions.add_to_playlist'"
@ -14,14 +19,21 @@
/> />
</div> </div>
</ModalComponent> </ModalComponent>
<CreatePlaylistModal
v-if="showCreatePlaylistModal"
@close="showCreatePlaylistModal = false"
@created="addCreatedPlaylist"
/>
</template> </template>
<script> <script>
import ModalComponent from "./ModalComponent.vue"; import ModalComponent from "./ModalComponent.vue";
import CreatePlaylistModal from "./CreatePlaylistModal.vue";
export default { export default {
components: { components: {
ModalComponent, ModalComponent,
CreatePlaylistModal,
}, },
props: { props: {
videoInfo: { videoInfo: {
@ -39,10 +51,13 @@ export default {
playlists: [], playlists: [],
selectedPlaylist: null, selectedPlaylist: null,
processing: false, processing: false,
showCreatePlaylistModal: false,
}; };
}, },
mounted() { mounted() {
this.fetchPlaylists(); this.getPlaylists().then(json => {
this.playlists = json;
});
this.selectedPlaylist = this.getPreferenceString("selectedPlaylist" + this.hashCode(this.authApiUrl())); this.selectedPlaylist = this.getPreferenceString("selectedPlaylist" + this.hashCode(this.authApiUrl()));
window.addEventListener("keydown", this.handleKeyDown); window.addEventListener("keydown", this.handleKeyDown);
window.blur(); window.blur();
@ -52,7 +67,7 @@ export default {
}, },
methods: { methods: {
handleKeyDown(event) { handleKeyDown(event) {
if (event.code === "Enter") { if (event.code === "Enter" && !this.showCreatePlaylistModal) {
this.handleClick(this.selectedPlaylist); this.handleClick(this.selectedPlaylist);
event.preventDefault(); event.preventDefault();
} }
@ -74,18 +89,9 @@ export default {
if (json.error) alert(json.error); if (json.error) alert(json.error);
}); });
}, },
async fetchPlaylists() { addCreatedPlaylist(playlistId, playlistName) {
this.getPlaylists().then(json => { this.playlists.push({ id: playlistId, name: playlistName });
this.playlists = json; this.selectedPlaylist = playlistId;
});
},
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();
});
}, },
}, },
}; };

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="video-card flex flex-col flex-justify-between efy_shadow_trans"> <div class="efy_shadow_trans video-card flex flex-col flex-justify-between">
<router-link :to="props.item.url"> <router-link :to="props.item.url">
<div class="relative"> <div class="relative">
<img class="thumbnail" :src="props.item.thumbnail" loading="lazy" /> <img class="thumbnail" :src="props.item.thumbnail" loading="lazy" />
</div> </div>
<div class="flex items-center h-[44rem] overflow-hidden"> <div class="h-[44rem] flex items-center overflow-hidden">
<p v-text="props.item.name" class="pp-video-card-title" /> <p class="pp-video-card-title" v-text="props.item.name" />
</div> </div>
</router-link> </router-link>
<p v-if="props.item.description" v-text="props.item.description" /> <p v-if="props.item.description" v-text="props.item.description" />
@ -13,8 +13,8 @@
<div class="pp-video-card-buttons"> <div class="pp-video-card-buttons">
<button <button
v-if="props.item.videos >= 0" v-if="props.item.videos >= 0"
v-text="`${props.item.videos} ${$t('video.videos')}`"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off" class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-text="`${props.item.videos} ${$t('video.videos')}`"
/> />
<router-link <router-link
v-if="props.item.uploaderUrl && item.uploaderName" v-if="props.item.uploaderUrl && item.uploaderName"
@ -24,8 +24,8 @@
style="padding: 0; flex-grow: 1; background: transparent; border: 0" style="padding: 0; flex-grow: 1; background: transparent; border: 0"
> >
<div class="pp-text efy_shadow_trans efy_shadow_button_off flex-grow-1"> <div class="pp-text efy_shadow_trans efy_shadow_button_off flex-grow-1">
<span v-text="props.item.uploaderName" style="max-width: 106rem" /> <span style="max-width: 106rem" v-text="props.item.uploaderName" />
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" /> <i v-if="item.uploaderVerified" class="i-fa6-solid:check ml-2" />
</div> </div>
</router-link> </router-link>
<a <a

View File

@ -21,31 +21,31 @@
loading="lazy" loading="lazy"
width="36" width="36"
height="36" height="36"
class="w-36rem h-36rem efy_shadow_trans" class="efy_shadow_trans h-36rem w-36rem"
/> />
<button class="pp-text efy_shadow_trans efy_shadow_button_off efy_button_text_off"> <button class="pp-text efy_shadow_trans efy_shadow_button_off efy_button_text_off">
<span v-text="playlist.uploader" /> <span v-text="playlist.uploader" />
<font-awesome-icon class="ml-1.5" v-if="playlist.uploaderVerified" icon="check" /> <i v-if="playlist.uploaderVerified" class="i-fa6-solid:check ml-1.5" />
</button> </button>
</router-link> </router-link>
<button <button
v-text="`${playlist.videos} ${$t('video.videos')}`"
class="efy_button_text_off efy_shadow_trans efy_shadow_button_off" class="efy_button_text_off efy_shadow_trans efy_shadow_button_off"
v-text="`${playlist.videos} ${$t('video.videos')}`"
/> />
</div> </div>
<div class="pp-flex-bookmarks"> <div class="pp-flex-bookmarks">
<button v-if="!isPipedPlaylist" class="btn" @click="bookmarkPlaylist"> <button v-if="!isPipedPlaylist" class="btn" @click="bookmarkPlaylist">
<font-awesome-icon class="mr-[5rem]" icon="bookmark" /> <i class="i-fa6-solid:bookmark mr-[5rem]" />
{{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`) }} {{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`) }}
</button> </button>
<button v-if="authenticated && !isPipedPlaylist" class="btn mr-1 ml-2" @click="clonePlaylist"> <button v-if="authenticated && !isPipedPlaylist" class="btn ml-2 mr-1" @click="clonePlaylist">
<font-awesome-icon class="mr-[5rem]" icon="clone" />{{ $t("actions.clone_playlist") }} <i class="i-fa6-solid:clone mr-[5rem]" />{{ $t("actions.clone_playlist") }}
</button> </button>
<button class="btn mr-1" @click="downloadPlaylistAsTxt"> <button class="btn mr-1" @click="downloadPlaylistAsTxt">
{{ $t("actions.download_as_txt") }} {{ $t("actions.download_as_txt") }}
</button> </button>
<a :href="getRssUrl" role="button" class="btn pp-square"> <a :href="getRssUrl" role="button" class="btn pp-square" style="padding: 0">
<font-awesome-icon icon="rss" /> <i class="i-fa6-solid:rss m-0" />
</a> </a>
<WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" class="pp-square" /> <WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" class="pp-square" />
</div> </div>
@ -69,17 +69,6 @@
</LoadingIndicatorPage> </LoadingIndicatorPage>
</template> </template>
<style>
.pp-flex-bookmarks {
display: flex;
flex-wrap: wrap;
gap: var(--efy_gap0);
}
.pp-flex-bookmarks > * {
margin: 0;
}
</style>
<script> <script>
import ErrorHandler from "./ErrorHandler.vue"; import ErrorHandler from "./ErrorHandler.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue"; import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
@ -132,16 +121,8 @@ export default {
window.removeEventListener("scroll", this.handleScroll); window.removeEventListener("scroll", this.handleScroll);
}, },
methods: { 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() { async getPlaylistData() {
this.fetchPlaylist() this.getPlaylist(this.$route.query.list)
.then(data => (this.playlist = data)) .then(data => (this.playlist = data))
.then(() => { .then(() => {
this.updateTitle(); this.updateTitle();
@ -237,3 +218,14 @@ export default {
}, },
}; };
</script> </script>
<style>
.pp-flex-bookmarks {
display: flex;
flex-wrap: wrap;
gap: var(--efy_gap0);
}
.pp-flex-bookmarks > * {
margin: 0;
}
</style>

View File

@ -1,12 +1,13 @@
<template> <template>
<h6 efy_card style="padding: 5rem 10rem 3rem; margin: 0 0 15rem 0">Playlist</h6> <h6 efy_card style="padding: 5rem 10rem 3rem; margin: 0 0 15rem 0">Playlist</h6>
<div class="overflow-y-scroll h-screen-sm pp-show-playlist" ref="scrollable"> <div ref="scrollable" class="pp-show-playlist h-screen-sm overflow-y-scroll">
<VideoItem <VideoItem
v-for="(related, index) in playlist.relatedStreams" v-for="(related, index) in playlist.relatedStreams"
:key="related.url" :key="related.url"
:item="related" :item="related"
:index="index" :index="index"
:playlist-id="playlistId" :playlist-id="playlistId"
:prefer-listen="preferListen"
height="94" height="94"
width="168" width="168"
/> />
@ -16,6 +17,7 @@
<script> <script>
import { nextTick } from "vue"; import { nextTick } from "vue";
import VideoItem from "./VideoItem.vue"; import VideoItem from "./VideoItem.vue";
export default { export default {
components: { VideoItem }, components: { VideoItem },
props: { props: {
@ -31,6 +33,10 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
preferListen: {
type: Boolean,
default: false,
},
}, },
watch: { watch: {
playlist: { playlist: {

View File

@ -1,27 +1,27 @@
<template> <template>
<hr /> <hr />
<div class="flex flex-wrap justify-between items-center" style="gap: var(--efy_gap0)"> <div class="flex flex-wrap items-center justify-between" style="gap: var(--efy_gap0)">
<button <button
v-t="'actions.create_playlist'" v-t="'actions.create_playlist'"
style="height: var(--efy_ratio_width); margin: 0" style="height: var(--efy_ratio_width); margin: 0"
@click="onCreatePlaylist" @click="showCreatePlaylistModal = true"
/> />
<div class="flex flex-wrap" style="gap: var(--efy_gap0)"> <div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<button <button
v-if="playlists.length > 0" v-if="playlists.length > 0"
v-t="'actions.export_to_json'" v-t="'actions.export_to_json'"
@click="exportPlaylists"
style="height: var(--efy_ratio_width); margin: 0" style="height: var(--efy_ratio_width); margin: 0"
@click="exportPlaylists"
/> />
<input <input
id="fileSelector" id="fileSelector"
ref="fileSelector" ref="fileSelector"
type="file" type="file"
class="display-none" class="hidden"
multiple="multiple" multiple="multiple"
@change="importPlaylists" @change="importPlaylists"
/> />
<label v-t="'actions.import_from_json_csv'" for="fileSelector" class="m-0! font-bold" role="button" /> <label v-t="'actions.import_from_json_csv'" for="fileSelector" class="font-bold m-0!" role="button" />
</div> </div>
</div> </div>
<hr /> <hr />
@ -32,15 +32,15 @@
<img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" /> <img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" />
<p <p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 15rem" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 15rem"
class="flex link" class="link flex"
:title="playlist.name" :title="playlist.name"
v-text="playlist.name" v-text="playlist.name"
/> />
</router-link> </router-link>
<div class="pp-video-card-buttons flex gap-15rem children:m-0" style="flex-wrap: wrap"> <div class="pp-video-card-buttons flex gap-15rem children:m-0" style="flex-wrap: wrap">
<button <button
v-text="`${playlist.videos} ${$t('video.videos')}`"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off" class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-text="`${playlist.videos} ${$t('video.videos')}`"
/> />
<button <button
v-t="'actions.edit_playlist'" v-t="'actions.edit_playlist'"
@ -86,39 +86,46 @@
<div <div
v-for="(playlist, index) in bookmarks" v-for="(playlist, index) in bookmarks"
:key="playlist.playlistId" :key="playlist.playlistId"
class="pp-bookmark video-card efy_trans_filter efy_shadow_trans" class="video-card efy_trans_filter efy_shadow_trans pp-bookmark"
> >
<router-link :to="`/playlist?list=${playlist.playlistId}`"> <router-link :to="`/playlist?list=${playlist.playlistId}`">
<img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" /> <img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" />
<div class="flex items-center h-[44rem] overflow-hidden"> <div class="h-[44rem] flex items-center overflow-hidden">
<p class="pp-video-card-title" :title="playlist.name" v-text="playlist.name" /> <p class="pp-video-card-title" :title="playlist.name" v-text="playlist.name" />
</div> </div>
</router-link> </router-link>
<div class="pp-video-card-buttons flex gap-15rem"> <div class="pp-video-card-buttons flex gap-15rem">
<button @click.prevent="removeBookmark(index)" class="btn pp-color aspect-square"> <button class="btn pp-color aspect-square" @click.prevent="removeBookmark(index)">
<font-awesome-icon icon="bookmark" /> <i class="i-fa6-solid:bookmark m-0" />
</button> </button>
<button <button
v-text="`${playlist.videos} ${$t('video.videos')}`"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off" class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-text="`${playlist.videos} ${$t('video.videos')}`"
/> />
</div> </div>
<a :href="playlist.uploaderUrl" class="pp-video-card-channel"> <a :href="playlist.uploaderUrl" class="pp-video-card-channel">
<img class="w-36rem h-36rem efy_shadow_trans" :src="playlist.uploaderAvatar" width="36" height="36" /> <img class="efy_shadow_trans h-36rem w-36rem" :src="playlist.uploaderAvatar" width="36" height="36" />
<div class="pp-text efy_shadow_trans"> <div class="pp-text efy_shadow_trans">
<span v-text="playlist.uploader" /> <span v-text="playlist.uploader" />
</div> </div>
</a> </a>
</div> </div>
</div> </div>
<br />
<CreatePlaylistModal
v-if="showCreatePlaylistModal"
@close="showCreatePlaylistModal = false"
@created="fetchPlaylists"
/>
</template> </template>
<script> <script>
import ConfirmModal from "./ConfirmModal.vue"; import ConfirmModal from "./ConfirmModal.vue";
import ModalComponent from "./ModalComponent.vue"; import ModalComponent from "./ModalComponent.vue";
import CreatePlaylistModal from "./CreatePlaylistModal.vue";
export default { export default {
components: { ConfirmModal, ModalComponent }, components: { ConfirmModal, ModalComponent, CreatePlaylistModal },
data() { data() {
return { return {
playlists: [], playlists: [],
@ -127,6 +134,7 @@ export default {
playlistToEdit: null, playlistToEdit: null,
newPlaylistName: "", newPlaylistName: "",
newPlaylistDescription: "", newPlaylistDescription: "",
showCreatePlaylistModal: false,
}; };
}, },
mounted() { mounted() {
@ -172,14 +180,6 @@ export default {
}); });
this.playlistToDelete = null; this.playlistToDelete = null;
}, },
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();
});
},
async exportPlaylists() { async exportPlaylists() {
if (!this.playlists) return; if (!this.playlists) return;
let json = { let json = {

View File

@ -85,8 +85,19 @@
</select> </select>
</label> </label>
</template> </template>
<div class="pref">
<span v-t="'titles.custom_instances'" class="w-max" />
<button v-t="'actions.customize'" class="btn" @click="showCustomInstancesModal = true" />
<CustomInstanceModal
v-if="showCustomInstancesModal"
@close="
showCustomInstancesModal = false;
fetchInstances();
"
/>
</div>
<div class="pref items-start! flex-col"> <div class="pref flex-col items-start!">
<strong>Preferences</strong> <strong>Preferences</strong>
<div class="flex flex-wrap" style="gap: var(--efy_gap0)"> <div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<button style="height: var(--efy_ratio_width)" @click="showConfirmResetPrefsDialog = true"> <button style="height: var(--efy_ratio_width)" @click="showConfirmResetPrefsDialog = true">
@ -113,7 +124,7 @@
/> />
</div> </div>
<!-- options that are visible only when logged in --> <!-- options that are visible only when logged in -->
<div v-if="authenticated" class="pref items-start! flex-col"> <div v-if="authenticated" class="pref flex-col items-start!">
<label v-t="'actions.delete_account'" for="txtDeleteAccountPassword" class="font-bold" /> <label v-t="'actions.delete_account'" for="txtDeleteAccountPassword" class="font-bold" />
<div class="flex flex-wrap" style="gap: var(--efy_gap0)"> <div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<input <input
@ -129,13 +140,14 @@
<button v-t="'actions.delete_account'" class="w-auto" @click="deleteAccount" /> <button v-t="'actions.delete_account'" class="w-auto" @click="deleteAccount" />
</div> </div>
</div> </div>
<div v-if="authenticated" class="pref items-start! flex-col" style="border-bottom: var(--efy_border)"> <div v-if="authenticated" class="pref flex-col items-start!" style="border-bottom: var(--efy_border)">
<strong>Logout</strong> <strong>Logout</strong>
<div class="flex flex-wrap" style="gap: var(--efy_gap0)"> <div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<button v-t="'actions.logout'" class="w-auto" @click="logout" /> <button v-t="'actions.logout'" class="w-auto" @click="logout" />
<button v-t="'actions.invalidate_session'" class="w-auto" @click="invalidateSession" /> <button v-t="'actions.invalidate_session'" class="w-auto" @click="invalidateSession" />
</div> </div>
</div> </div>
<hr />
</div> </div>
<div efy_card="grid"> <div efy_card="grid">
<h5 v-t="'titles.player'" /> <h5 v-t="'titles.player'" />
@ -338,6 +350,16 @@
@change="onChange($event)" @change="onChange($event)"
/> />
</label> </label>
<label class="pref" for="txtPrefetchLimit">
<strong v-t="'actions.concurrent_prefetch_limit'" />
<input
id="txtPrefetchLimit"
v-model="prefetchLimit"
class="input w-24"
type="text"
@change="onChange($event)"
/>
</label>
</div> </div>
<div efy_card="grid"> <div efy_card="grid">
@ -409,10 +431,11 @@
<th v-t="'preferences.registered_users'" /> <th v-t="'preferences.registered_users'" />
<th v-t="'preferences.version'" class="lt-md:hidden" /> <th v-t="'preferences.version'" class="lt-md:hidden" />
<th v-t="'preferences.up_to_date'" /> <th v-t="'preferences.up_to_date'" />
<th v-t="'preferences.uptime_30d'" />
<th v-t="'preferences.ssl_score'" /> <th v-t="'preferences.ssl_score'" />
</tr> </tr>
</thead> </thead>
<tbody v-for="instance in instances" :key="instance.name"> <tbody v-for="instance in publicInstances" :key="instance.name">
<tr> <tr>
<td v-text="instance.name" /> <td v-text="instance.name" />
<td v-text="instance.locations" /> <td v-text="instance.locations" />
@ -420,6 +443,7 @@
<td v-text="instance.registered" /> <td v-text="instance.registered" />
<td class="lt-md:hidden" v-text="instance.version" /> <td class="lt-md:hidden" v-text="instance.version" />
<td v-text="`${instance.up_to_date ? '&#9989;' : '&#10060;'}`" /> <td v-text="`${instance.up_to_date ? '&#9989;' : '&#10060;'}`" />
<td v-text="`${Number.parseFloat(instance.uptime_30d.toFixed(2))}%`" />
<td> <td>
<a v-t="'actions.view_ssl_score'" :href="sslScore(instance.api_url)" target="_blank" /> <a v-t="'actions.view_ssl_score'" :href="sslScore(instance.api_url)" target="_blank" />
</td> </td>
@ -431,9 +455,11 @@
<script> <script>
import CountryMap from "@/utils/CountryMaps/en.json"; import CountryMap from "@/utils/CountryMaps/en.json";
import ConfirmModal from "./ConfirmModal.vue"; import ConfirmModal from "./ConfirmModal.vue";
import CustomInstanceModal from "./CustomInstanceModal.vue";
export default { export default {
components: { components: {
ConfirmModal, ConfirmModal,
CustomInstanceModal,
}, },
data() { data() {
return { return {
@ -441,7 +467,8 @@ export default {
selectedInstance: null, selectedInstance: null,
authInstance: false, authInstance: false,
selectedAuthInstance: null, selectedAuthInstance: null,
instances: [], customInstances: [],
publicInstances: [],
sponsorBlock: true, sponsorBlock: true,
skipOptions: new Map([ skipOptions: new Map([
["sponsor", { value: "auto", label: "actions.skip_sponsors" }], ["sponsor", { value: "auto", label: "actions.skip_sponsors" }],
@ -469,7 +496,7 @@ export default {
countrySelected: "US", countrySelected: "US",
defaultHomepage: "trending", defaultHomepage: "trending",
minimizeComments: false, minimizeComments: false,
minimizeDescription: false, minimizeDescription: true,
minimizeRecommendations: false, minimizeRecommendations: false,
minimizeChapters: false, minimizeChapters: false,
showWatchOnYouTube: false, showWatchOnYouTube: false,
@ -518,6 +545,7 @@ export default {
{ code: "ro", name: "Română" }, { code: "ro", name: "Română" },
{ code: "ru", name: "Русский" }, { code: "ru", name: "Русский" },
{ code: "si", name: "සිංහල" }, { code: "si", name: "සිංහල" },
{ code: "sl", name: "Slovenian" },
{ code: "sr", name: "Српски" }, { code: "sr", name: "Српски" },
{ code: "sv", name: "Svenska" }, { code: "sv", name: "Svenska" },
{ code: "ta", name: "தமிழ்" }, { code: "ta", name: "தமிழ்" },
@ -531,29 +559,27 @@ export default {
enabledCodecs: ["vp9", "avc"], enabledCodecs: ["vp9", "avc"],
disableLBRY: false, disableLBRY: false,
proxyLBRY: false, proxyLBRY: false,
prefetchLimit: 2,
password: null, password: null,
showConfirmResetPrefsDialog: false, showConfirmResetPrefsDialog: false,
showCustomInstancesModal: false,
}; };
}, },
computed: {
instances() {
return [...this.publicInstances, ...this.customInstances];
},
},
activated() { activated() {
document.title = this.$t("titles.preferences") + " - Piped"; document.title = this.$t("titles.preferences") + " - Piped";
}, },
async mounted() { async mounted() {
if (Object.keys(this.$route.query).length > 0) this.$router.replace({ query: {} }); if (Object.keys(this.$route.query).length > 0) this.$router.replace({ query: {} });
this.fetchJson("https://piped-instances.kavin.rocks/").then(resp => { this.fetchInstances();
this.instances = resp;
if (!this.instances.some(instance => instance.api_url == this.apiUrl()))
this.instances.push({
name: "Custom Instance",
api_url: this.apiUrl(),
locations: "Unknown",
cdn: false,
});
});
if (this.testLocalStorage) { if (this.testLocalStorage) {
this.selectedInstance = this.getPreferenceString("instance", "https://pipedapi.kavin.rocks"); this.selectedInstance = this.getPreferenceString("instance", import.meta.env.VITE_PIPED_API);
this.authInstance = this.getPreferenceBoolean("authInstance", false); this.authInstance = this.getPreferenceBoolean("authInstance", false);
this.selectedAuthInstance = this.getPreferenceString("auth_instance_url", this.selectedInstance); this.selectedAuthInstance = this.getPreferenceString("auth_instance_url", this.selectedInstance);
@ -588,7 +614,7 @@ export default {
this.countrySelected = this.getPreferenceString("region", "US"); this.countrySelected = this.getPreferenceString("region", "US");
this.defaultHomepage = this.getPreferenceString("homepage", "trending"); this.defaultHomepage = this.getPreferenceString("homepage", "trending");
this.minimizeComments = this.getPreferenceBoolean("minimizeComments", false); this.minimizeComments = this.getPreferenceBoolean("minimizeComments", false);
this.minimizeDescription = this.getPreferenceBoolean("minimizeDescription", false); this.minimizeDescription = this.getPreferenceBoolean("minimizeDescription", true);
this.minimizeRecommendations = this.getPreferenceBoolean("minimizeRecommendations", false); this.minimizeRecommendations = this.getPreferenceBoolean("minimizeRecommendations", false);
this.minimizeChapters = this.getPreferenceBoolean("minimizeChapters", false); this.minimizeChapters = this.getPreferenceBoolean("minimizeChapters", false);
this.showWatchOnYouTube = this.getPreferenceBoolean("showWatchOnYouTube", false); this.showWatchOnYouTube = this.getPreferenceBoolean("showWatchOnYouTube", false);
@ -599,6 +625,7 @@ export default {
this.enabledCodecs = this.getPreferenceString("enabledCodecs", "vp9,avc").split(","); this.enabledCodecs = this.getPreferenceString("enabledCodecs", "vp9,avc").split(",");
this.disableLBRY = this.getPreferenceBoolean("disableLBRY", false); this.disableLBRY = this.getPreferenceBoolean("disableLBRY", false);
this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false); this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
this.prefetchLimit = this.getPreferenceNumber("prefetchLimit", 2);
this.hideWatched = this.getPreferenceBoolean("hideWatched", false); this.hideWatched = this.getPreferenceBoolean("hideWatched", false);
this.mobileChapterLayout = this.getPreferenceString("mobileChapterLayout", "Vertical"); this.mobileChapterLayout = this.getPreferenceString("mobileChapterLayout", "Vertical");
if (this.selectedLanguage != "en") { if (this.selectedLanguage != "en") {
@ -661,12 +688,28 @@ export default {
localStorage.setItem("enabledCodecs", this.enabledCodecs.join(",")); localStorage.setItem("enabledCodecs", this.enabledCodecs.join(","));
localStorage.setItem("disableLBRY", this.disableLBRY); localStorage.setItem("disableLBRY", this.disableLBRY);
localStorage.setItem("proxyLBRY", this.proxyLBRY); localStorage.setItem("proxyLBRY", this.proxyLBRY);
localStorage.setItem("prefetchLimit", this.prefetchLimit);
localStorage.setItem("hideWatched", this.hideWatched); localStorage.setItem("hideWatched", this.hideWatched);
localStorage.setItem("mobileChapterLayout", this.mobileChapterLayout); localStorage.setItem("mobileChapterLayout", this.mobileChapterLayout);
if (shouldReload) window.location.reload(); if (shouldReload) window.location.reload();
} }
}, },
async fetchInstances() {
this.customInstances = this.getCustomInstances();
this.fetchJson(import.meta.env.VITE_PIPED_INSTANCES).then(resp => {
this.publicInstances = resp;
if (!this.publicInstances.some(instance => instance.api_url == this.apiUrl()))
this.publicInstances.push({
name: "Selected Instance",
api_url: this.apiUrl(),
locations: "Unknown",
cdn: false,
uptime_30d: 100,
});
});
},
sslScore(url) { sslScore(url) {
return "https://www.ssllabs.com/ssltest/analyze.html?d=" + new URL(url).host + "&latest"; return "https://www.ssllabs.com/ssltest/analyze.html?d=" + new URL(url).host + "&latest";
}, },
@ -689,14 +732,14 @@ export default {
// reset the auth token // reset the auth token
localStorage.removeItem("authToken" + this.hashCode(this.authApiUrl())); localStorage.removeItem("authToken" + this.hashCode(this.authApiUrl()));
// redirect to trending page // redirect to trending page
window.location = "/"; window.location = import.meta.env.BASE_URL;
}, },
resetPreferences() { resetPreferences() {
this.showConfirmResetPrefsDialog = false; this.showConfirmResetPrefsDialog = false;
// clear the local storage // clear the local storage
localStorage.clear(); localStorage.clear();
// redirect to the home page // redirect to the home page
window.location = "/"; window.location = import.meta.env.BASE_URL;
}, },
async invalidateSession() { async invalidateSession() {
this.fetchJson(this.authApiUrl() + "/logout", null, { this.fetchJson(this.authApiUrl() + "/logout", null, {
@ -749,19 +792,23 @@ export default {
max-width: 250rem; max-width: 250rem;
} }
.pp-pref-cards { .pp-pref-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300rem, 1fr));
gap: var(--efy_gap);
margin-top: 15rem; margin-top: 15rem;
} }
[efy_card*="grid"] { [efy_card*="grid"] {
padding: 0; padding: 0;
gap: 0; gap: 0;
} scale: 1;
[efy_card*="grid"]:active {
transform: scale(1) !important;
} }
[efy_card*="grid"] h5 { [efy_card*="grid"] h5 {
padding: 5rem 10rem; padding: 5rem 10rem;
} }
tbody:nth-child(odd) { table {
background: var(--efy_bg1) !important; margin: 0;
tbody:nth-child(odd) {
background: var(--efy_bg1) !important;
}
} }
</style> </style>

View File

@ -1,8 +1,11 @@
<template> <template>
<h1 v-t="'titles.register'" class="my-4 text-center font-bold" /> <div class="flex justify-center">
<h1 v-t="'titles.register'" class="my-4 text-center font-bold" />
<i class="i-fa6-solid:circle-info ml-2 mt-6 cursor-pointer" :title="$t('info.register_note')" />
</div>
<hr /> <hr />
<div class="flex justify-center text-center"> <div class="flex flex-col items-center justify-center text-center">
<form class="items-center px-3 children:pb-3"> <form class="w-max items-center px-3 children:pb-3">
<div> <div>
<input <input
v-model="username" v-model="username"
@ -17,7 +20,7 @@
<div class="flex justify-center"> <div class="flex justify-center">
<input <input
v-model="password" v-model="password"
class="input w-full" class="input h-auto w-full"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
autocomplete="password" autocomplete="password"
:placeholder="$t('login.password')" :placeholder="$t('login.password')"
@ -31,7 +34,7 @@
<div class="flex justify-center"> <div class="flex justify-center">
<input <input
v-model="passwordConfirm" v-model="passwordConfirm"
class="input w-full" class="input h-auto w-full"
:type="showConfirmPassword ? 'text' : 'password'" :type="showConfirmPassword ? 'text' : 'password'"
autocomplete="password" autocomplete="password"
:placeholder="$t('login.password_confirm')" :placeholder="$t('login.password_confirm')"
@ -79,7 +82,7 @@ export default {
mounted() { mounted() {
//TODO: Add Server Side check //TODO: Add Server Side check
if (this.getAuthToken()) { if (this.getAuthToken()) {
this.$router.push("/"); this.$router.push(import.meta.env.BASE_URL);
} }
}, },
activated() { activated() {
@ -105,7 +108,7 @@ export default {
}).then(resp => { }).then(resp => {
if (resp.token) { if (resp.token) {
this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token); this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token);
window.location = "/"; // done to bypass cache window.location = import.meta.env.BASE_URL; // done to bypass cache
} else alert(resp.error); } else alert(resp.error);
}); });
}, },

View File

@ -3,7 +3,7 @@
<div class="flex flex-wrap place-content-between items-center"> <div class="flex flex-wrap place-content-between items-center">
<h5 class="ml-[5rem]" v-text="$route.query.search_query" /> <h5 class="ml-[5rem]" v-text="$route.query.search_query" />
<div class="flex items-center" style="gap: var(--efy_gap0)"> <div class="flex items-center" style="gap: var(--efy_gap0)">
<label v-text="`${$t('actions.filter')}:`" for="ddlSearchFilters" /> <label for="ddlSearchFilters" v-text="`${$t('actions.filter')}:`" />
<select <select
id="ddlSearchFilters" id="ddlSearchFilters"
v-model="selectedFilter" v-model="selectedFilter"

View File

@ -4,12 +4,14 @@
<li <li
v-for="(suggestion, i) in searchSuggestions" v-for="(suggestion, i) in searchSuggestions"
:key="i" :key="i"
class="suggestion"
:class="{ 'suggestion-selected': selected === i }" :class="{ 'suggestion-selected': selected === i }"
@mouseover="onMouseOver(i)" @mouseover="onMouseOver(i)"
@mousedown.stop="onClick(i)" @click="setSelected(i)"
v-text="suggestion" >
/> <router-link class="suggestion" :to="`/results?search_query=${encodeURIComponent(suggestion)}`">
{{ suggestion }}
</router-link>
</li>
</ul> </ul>
</div> </div>
</template> </template>
@ -69,13 +71,6 @@ export default {
this.selected = i; this.selected = i;
} }
}, },
onClick(i) {
this.setSelected(i);
this.$router.push({
name: "SearchResults",
query: { search_query: this.searchSuggestions[i] },
});
},
setSelected(val) { setSelected(val) {
this.selected = val; this.selected = val;
this.$emit("searchchange", this.searchSuggestions[this.selected]); this.$emit("searchchange", this.searchSuggestions[this.selected]);
@ -91,5 +86,15 @@ export default {
box-shadow: 0 0 20rem var(--efy_text_trans); box-shadow: 0 0 20rem var(--efy_text_trans);
padding: var(--efy_gap); padding: var(--efy_gap);
margin: calc(40rem + var(--efy_gap)) 0 var(--efy_gap) 0; margin: calc(40rem + var(--efy_gap)) 0 var(--efy_gap) 0;
li {
border: var(--efy_border_size) solid transparent;
border-radius: var(--efy_radius0);
}
.suggestion-selected {
border: var(--efy_border);
}
a {
-webkit-text-fill-color: var(--efy_text) !important;
}
} }
</style> </style>

View File

@ -1,10 +1,11 @@
<template> <template>
<ModalComponent> <ModalComponent>
<h5 v-t="'actions.share'" /> <h5 v-t="'actions.share'" />
<div class="flex justify-between mt-2 mb-2"> <div class="mb-2 mt-2 flex justify-between">
<label v-t="'actions.piped_link'" /> <label v-t="'actions.piped_link'" />
<input v-model="pipedLink" type="checkbox" @change="onChange" /> <input v-model="pipedLink" type="checkbox" @change="onChange" />
</div> </div>
<hr />
<div v-if="hasPlaylist" class="flex justify-between"> <div v-if="hasPlaylist" class="flex justify-between">
<label v-t="'actions.with_playlist'" /> <label v-t="'actions.with_playlist'" />
<input v-model="withPlaylist" type="checkbox" @change="onChange" /> <input v-model="withPlaylist" type="checkbox" @change="onChange" />
@ -13,10 +14,11 @@
<label v-t="'actions.with_timecode'" for="withTimeCode" /> <label v-t="'actions.with_timecode'" for="withTimeCode" />
<input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" /> <input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" />
</div> </div>
<div v-if="withTimeCode" class="flex justify-between mt-2" style="align-items: center"> <div v-if="withTimeCode" class="mt-2 flex justify-between" style="align-items: center">
<label v-t="'actions.time_code'" /> <label v-t="'actions.time_code'" />
<input v-model="timeStamp" style="max-width: 100rem" type="number" @change="onChange" /> <input v-model="timeStamp" style="max-width: 100rem; margin: 0" type="number" @change="onChange" />
</div> </div>
<hr />
<a :href="generatedLink" target="_blank"> <a :href="generatedLink" target="_blank">
<h6 class="mb-2 mt-2" v-text="generatedLink" /> <h6 class="mb-2 mt-2" v-text="generatedLink" />
</a> </a>

View File

@ -1,6 +1,6 @@
<template> <template>
<label v-t="'actions.sort_by'" for="ddlSortBy" class="m-0" /> <label v-t="'actions.sort_by'" for="ddlSortBy" class="m-0" />
<select id="ddlSortBy" v-model="selectedSort" class="w-auto m-0"> <select id="ddlSortBy" v-model="selectedSort" class="m-0 w-auto">
<option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" /> <option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
</select> </select>
</template> </template>

View File

@ -1,7 +1,7 @@
<template> <template>
<hr /> <hr />
<!-- import / export section --> <!-- import / export section -->
<div class="flex justify-between flex-wrap m0c"> <div class="m0c flex flex-wrap justify-between">
<div efy_card class="w-auto!" style="padding: var(--efy_padding)"> <div efy_card class="w-auto!" style="padding: var(--efy_padding)">
<i18n-t keypath="titles.subscriptions" efy_card />{{ ": " + subscriptions.length }} <i18n-t keypath="titles.subscriptions" efy_card />{{ ": " + subscriptions.length }}
</div> </div>
@ -12,15 +12,15 @@
id="fileSelector" id="fileSelector"
ref="fileSelector" ref="fileSelector"
type="file" type="file"
class="display-none" class="efy_hide_i"
multiple="multiple" multiple="multiple"
@change="importGroupsHandler" @change="importGroupsHandler"
/> />
<label <label
for="fileSelector" for="fileSelector"
role="button" role="button"
v-text="`${$t('actions.import_from_json')} (${$t('titles.channel_groups')})`"
class="font-bold" class="font-bold"
v-text="`${$t('actions.import_from_json')} (${$t('titles.channel_groups')})`"
/> />
<button <button
@click="exportGroupsHandler" @click="exportGroupsHandler"
@ -33,20 +33,26 @@
<button <button
v-for="group in channelGroups" v-for="group in channelGroups"
:key="group.groupName" :key="group.groupName"
class="flex gap-[10rem] items-center" class="flex items-center gap-[10rem]"
:class="{ selected: selectedGroup === group }" :class="{ selected: selectedGroup === group }"
@click="selectGroup(group)" @click="selectGroup(group)"
> >
<span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" /> <span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
<div v-if="group.groupName != '' && selectedGroup == group" class="flex flex-wrap gap-[10rem] items-center"> <div v-if="group.groupName != '' && selectedGroup == group" class="flex flex-wrap items-center gap-[10rem]">
<div>|</div> <div>|</div>
<font-awesome-icon class="mx-2" icon="edit" @click="showEditGroupModal = true" /> <i class="i-fa6-solid:pen mx-2" @click="showEditGroupModal = true" />
<div>|</div> <div>|</div>
<font-awesome-icon class="mx-2" icon="circle-minus" @click="deleteGroup(group)" /> <i class="i-fa6-solid:circle-minus mx-2" @click="groupToDelete = group.groupName" />
</div> </div>
</button> </button>
<button class="btn mx-1"> <ConfirmModal
<font-awesome-icon icon="circle-plus" @click="showCreateGroupModal = true" /> v-if="groupToDelete != null"
:message="$t('actions.delete_group_confirm')"
@close="groupToDelete = null"
@confirm="deleteGroup(groupToDelete)"
/>
<button class="btn mx-1" @click="showCreateGroupModal = true">
<i class="i-fa6-solid:circle-plus" />
</button> </button>
</div> </div>
<hr /> <hr />
@ -67,13 +73,11 @@
</div> </div>
</div> </div>
<ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal"> <CreateGroupModal
<h2 v-t="'actions.create_group'" /> v-if="showCreateGroupModal"
<div class="flex flex-col"> :on-create-group="createGroup"
<input v-model="newGroupName" class="input my-4" type="text" :placeholder="$t('actions.group_name')" /> @close="showCreateGroupModal = false"
<button v-t="'actions.create_group'" class="btn ml-auto w-max" @click="createGroup()" /> />
</div>
</ModalComponent>
<ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false"> <ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false">
<div class="mb-5 mt-3 flex justify-between"> <div class="mb-5 mt-3 flex justify-between">
@ -90,7 +94,7 @@
<input <input
type="checkbox" type="checkbox"
class="checkbox" class="checkbox"
:checked="selectedGroup.channels.includes(subscription.url.substr(-11))" :checked="selectedGroup.channels.includes(subscription.url.substr(-24))"
@change="checkedChange(subscription)" @change="checkedChange(subscription)"
/> />
</div> </div>
@ -100,37 +104,13 @@
</ModalComponent> </ModalComponent>
</template> </template>
<style>
.pp-subs-cards {
display: grid;
gap: var(--efy_gap);
grid-template-columns: repeat(auto-fill, minmax(240rem, 1fr));
}
.pp-subs-card :is(a, span) {
-webkit-text-fill-color: var(--efy_text) !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.pp-subs-card button {
margin-bottom: 0;
width: 100%;
}
.selected {
}
.m0c {
gap: var(--efy_gap0);
}
.m0c :is(button, [role="button"]) {
margin: 0;
}
</style>
<script> <script>
import ModalComponent from "./ModalComponent.vue"; import ModalComponent from "./ModalComponent.vue";
import CreateGroupModal from "./CreateGroupModal.vue";
import ConfirmModal from "./ConfirmModal.vue";
export default { export default {
components: { ModalComponent }, components: { ModalComponent, CreateGroupModal, ConfirmModal },
data() { data() {
return { return {
subscriptions: [], subscriptions: [],
@ -141,55 +121,40 @@ export default {
channelGroups: [], channelGroups: [],
showCreateGroupModal: false, showCreateGroupModal: false,
showEditGroupModal: false, showEditGroupModal: false,
newGroupName: "",
editedGroupName: "", editedGroupName: "",
groupToDelete: null,
}; };
}, },
computed: { computed: {
filteredSubscriptions(_this) { filteredSubscriptions(_this) {
return _this.selectedGroup.groupName == "" return _this.selectedGroup.groupName == ""
? _this.subscriptions ? _this.subscriptions
: _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11))); : _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-24)));
}, },
}, },
mounted() { mounted() {
this.fetchSubscriptions().then(json => { this.fetchSubscriptions().then(json => {
if (json.error) {
alert(json.error);
return;
}
this.subscriptions = json; this.subscriptions = json;
this.subscriptions.forEach(subscription => (subscription.subscribed = true)); this.subscriptions.forEach(subscription => (subscription.subscribed = true));
}); });
this.channelGroups.push(this.selectedGroup); this.channelGroups.push(this.selectedGroup);
if (!window.db) return; if (!window.db) return;
const cursor = this.getChannelGroupsCursor();
cursor.onsuccess = e => { this.loadChannelGroups();
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() { activated() {
document.title = "Subscriptions - Piped"; document.title = "Subscriptions - Piped";
}, },
methods: { methods: {
async fetchSubscriptions() { async loadChannelGroups() {
if (this.authenticated) { const groups = await this.getChannelGroups();
return await this.fetchJson(this.authApiUrl() + "/subscriptions", null, { this.channelGroups.push(...groups);
headers: {
Authorization: this.getAuthToken(),
},
});
} else {
return await this.fetchJson(this.authApiUrl() + "/subscriptions/unauthenticated", {
channels: this.getUnauthenticatedChannels(),
});
}
}, },
handleButton(subscription) { handleButton(subscription) {
const channelId = subscription.url.split("/")[2]; const channelId = subscription.url.split("/")[2];
@ -229,18 +194,16 @@ export default {
this.selectedGroup = group; this.selectedGroup = group;
this.editedGroupName = group.groupName; this.editedGroupName = group.groupName;
}, },
createGroup() { createGroup(newGroupName) {
if (!this.newGroupName || this.channelGroups.some(group => group.groupName == this.newGroupName)) return; if (!newGroupName || this.channelGroups.some(group => group.groupName == newGroupName)) return;
const newGroup = { const newGroup = {
groupName: this.newGroupName, groupName: newGroupName,
channels: [], channels: [],
}; };
this.channelGroups.push(newGroup); this.channelGroups.push(newGroup);
this.createOrUpdateChannelGroup(newGroup); this.createOrUpdateChannelGroup(newGroup);
this.newGroupName = "";
this.showCreateGroupModal = false; this.showCreateGroupModal = false;
}, },
editGroupName() { editGroupName() {
@ -259,12 +222,13 @@ export default {
this.showEditGroupModal = false; this.showEditGroupModal = false;
}, },
deleteGroup(group) { deleteGroup(group) {
this.deleteChannelGroup(group.groupName); this.deleteChannelGroup(group);
this.channelGroups = this.channelGroups.filter(g => g != group); this.channelGroups = this.channelGroups.filter(g => g.groupName != group);
this.selectedGroup = this.channelGroups[0]; this.selectedGroup = this.channelGroups[0] || {};
this.groupToDelete = null;
}, },
checkedChange(subscription) { checkedChange(subscription) {
const channelId = subscription.url.substr(-11); const channelId = subscription.url.substr(-24);
this.selectedGroup.channels = this.selectedGroup.channels.includes(channelId) this.selectedGroup.channels = this.selectedGroup.channels.includes(channelId)
? this.selectedGroup.channels.filter(channel => channel != channelId) ? this.selectedGroup.channels.filter(channel => channel != channelId)
: this.selectedGroup.channels.concat(channelId); : this.selectedGroup.channels.concat(channelId);
@ -291,3 +255,29 @@ export default {
}, },
}; };
</script> </script>
<style>
.pp-subs-cards {
display: grid;
gap: var(--efy_gap);
grid-template-columns: repeat(auto-fill, minmax(240rem, 1fr));
}
.pp-subs-card :is(a, span) {
-webkit-text-fill-color: var(--efy_text) !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.pp-subs-card button {
margin-bottom: 0;
width: 100%;
}
.selected {
}
.m0c {
gap: var(--efy_gap0);
}
.m0c :is(button, [role="button"]) {
margin: 0;
}
</style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="toast"> <div class="toast">
<slot /> <slot />
<button v-t="'actions.dismiss'" @click="dismiss" class="m-0 mt-[10rem]" /> <button v-t="'actions.dismiss'" class="m-0 mt-[10rem]" @click="dismiss" />
</div> </div>
</template> </template>

View File

@ -19,7 +19,12 @@ export default {
}; };
}, },
mounted() { mounted() {
if (this.$route.path == "/" && this.getPreferenceString("homepage", "trending") == "feed") return; if (
this.$route.path == import.meta.env.BASE_URL &&
this.getPreferenceString("homepage", "trending") == "feed"
) {
return;
}
let region = this.getPreferenceString("region", "US"); let region = this.getPreferenceString("region", "US");
this.fetchTrending(region).then(videos => { this.fetchTrending(region).then(videos => {
@ -31,7 +36,7 @@ export default {
activated() { activated() {
document.title = this.$t("titles.trending") + " - Piped"; document.title = this.$t("titles.trending") + " - Piped";
if (this.videos.length > 0) this.updateWatched(this.videos); if (this.videos.length > 0) this.updateWatched(this.videos);
if (this.$route.path == "/") { if (this.$route.path == import.meta.env.BASE_URL) {
let homepage = this.getHomePage(this); let homepage = this.getHomePage(this);
if (homepage !== undefined) this.$router.push(homepage); if (homepage !== undefined) this.$router.push(homepage);
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-if="showVideo" class="video-card efy_trans_filter efy_shadow_trans"> <div v-if="showVideo" class="video-card efy_trans_filter efy_shadow_trans" :class="{ watched: item.watched }">
<!-- EFY--> <!-- EFY-->
<router-link <router-link
class="video_item_link" class="video_item_link"
@ -9,6 +9,7 @@
v: item.url.substr(-11), v: item.url.substr(-11),
...(playlistId && { list: playlistId }), ...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }), ...(index >= 0 && { index: index + 1 }),
...(preferListen && { listen: 1 }),
}, },
}" }"
> >
@ -19,10 +20,22 @@
class="thumbnail" class="thumbnail"
loading="lazy" loading="lazy"
/> />
<div
v-if="item.duration > 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off pp-video-card-2 pp-time"
tabindex="-1"
v-text="timeFormat(item.duration)"
/>
<button
v-if="item.watched"
v-t="'video.watched'"
class="pp-video-card-2 pp-color pp-watched"
tabindex="-1"
/>
<!-- progress bar --> <!-- progress bar -->
<div <div
v-if="item.watched && item.duration > 0" v-if="item.watched && item.duration > 0"
class="relative h-1 w-full" class="watched_progress relative h-1 w-full"
style=" style="
height: 4rem; height: 4rem;
background: rgba(255, 255, 255, 0.067); background: rgba(255, 255, 255, 0.067);
@ -42,120 +55,127 @@
box-shadow: 3rem 0 5rem #0005; box-shadow: 3rem 0 5rem #0005;
" "
/> />
<div
class="absolute bottom-0 left-0"
:style="{ height: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
/>
</div> </div>
<div class="flex items-center h-[44rem] overflow-hidden"> <div class="h-[44rem] flex items-center overflow-hidden">
<p v-text="title" class="pp-video-card-title" /> <p class="pp-video-card-title" v-text="title" />
</div> </div>
</router-link> </router-link>
<div class="pp-video-card-buttons"> <div class="pp-card-info">
<button <div class="pp-video-card-2 h-[44rem] flex items-center overflow-hidden">
v-if="item.duration > 0" <p class="pp-video-card-title" v-text="title" />
v-text="timeFormat(item.duration)" </div>
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off" <div class="pp-video-card-buttons">
tabindex="-1" <button
/> v-if="item.duration > 0"
<button class="pp-time efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-if="item.views >= 0" tabindex="-1"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off" v-text="timeFormat(item.duration)"
tabindex="-1" />
> <button
<font-awesome-icon icon="eye" style="margin-right: 5rem" /> v-if="item.views >= 0"
<span v-text="`${numberFormat(item.views)}`" /> class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
</button> tabindex="-1"
>
<i class="i-fa6-solid:eye" style="margin-right: 5rem" />
<span v-text="`${numberFormat(item.views)}`" />
</button>
<router-link
class="btn efy_shadow_trans efy_shadow_button_off efy_button_text_off"
role="button"
:to="{
path: '/watch',
query: {
v: item.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
listen: '1',
},
}"
:aria-label="'Listen to ' + item.title"
:title="'Listen to ' + item.title"
>
<i :class="preferListen ? 'i-fa6-solid:tv' : 'i-fa6-solid:headphones'" />
</router-link>
<button
:title="$t('actions.add_to_playlist')"
class="btn efy_shadow_trans efy_shadow_button_off efy_button_text_off"
@click="showModal = !showModal"
>
<i class="i-fa6-solid:circle-plus" />
</button>
<button
v-if="admin"
ref="removeButton"
:title="$t('actions.remove_from_playlist')"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
@click="showConfirmRemove = true"
>
<i class="i-fa6-solid: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="getVideoId()"
:video-info="video"
@close="showModal = !showModal"
/>
<ShareModal
v-if="showShareModal"
:video-id="getVideoId()"
:current-time="0"
@close="showShareModal = false"
/>
<button
v-if="item.uploaded > 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-text="timeAgo(item.uploaded)"
/>
<button v-else-if="item.uploadedDate" tabindex="-1" v-text="item.uploadedDate" />
<button v-if="item.isShort" v-t="'video.shorts'" class="pp-color" tabindex="-1" />
<button v-else-if="item.duration < 0" v-t="'video.live'" class="pp-color" tabindex="-1" />
<button v-if="item.watched" v-t="'video.watched'" class="pp-watched pp-color" tabindex="-1" />
</div>
<router-link <router-link
class="btn efy_shadow_trans efy_shadow_button_off efy_button_text_off" v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
role="button" :to="item.uploaderUrl"
:to="{ :title="item.uploaderName"
path: '/watch', class="pp-video-card-channel"
query: {
v: item.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
listen: '1',
},
}"
:aria-label="'Listen to ' + item.title"
:title="'Listen to ' + item.title"
> >
<font-awesome-icon icon="headphones" /> <img
v-if="item.uploaderAvatar"
:src="item.uploaderAvatar"
loading="lazy"
class="efy_shadow_trans efy_shadow_button_off mt-0.5 h-36rem w-36rem"
width="36"
height="36"
/>
<div class="pp-text efy_shadow_trans efy_shadow_button_off">
<span v-text="item.uploaderName" />
<i v-if="item.uploaderVerified" class="i-fa6-solid:check ml-1.5" style="margin-right: 0" />
</div>
</router-link> </router-link>
<button
:title="$t('actions.add_to_playlist')"
@click="showModal = !showModal"
class="btn efy_shadow_trans efy_shadow_button_off efy_button_text_off"
>
<font-awesome-icon icon="circle-plus" />
</button>
<button
v-if="admin"
ref="removeButton"
:title="$t('actions.remove_from_playlist')"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
@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"
/>
<button
v-if="item.uploaded > 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-text="timeAgo(item.uploaded)"
/>
<button v-else-if="item.uploadedDate" v-text="item.uploadedDate" tabindex="-1" />
<button class="pp-color" v-if="item.isShort" v-t="'video.shorts'" tabindex="-1" />
<button v-else-if="item.duration < 0" v-t="'video.live'" class="pp-color" tabindex="-1" />
<button v-if="item.watched" v-t="'video.watched'" class="pp-color" tabindex="-1" />
</div> </div>
<router-link
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
:to="item.uploaderUrl"
:title="item.uploaderName"
class="pp-video-card-channel"
>
<img
v-if="item.uploaderAvatar"
:src="item.uploaderAvatar"
loading="lazy"
class="mt-0.5 w-36rem h-36rem efy_shadow_trans efy_shadow_button_off"
width="36"
height="36"
/>
<div class="pp-text efy_shadow_trans efy_shadow_button_off">
<span v-text="item.uploaderName" />
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
</div>
</router-link>
</div> </div>
</template> </template>
<style>
.shorts-img {
@apply w-full object-contain;
}
.video_item_link {
display: block;
}
</style>
<script> <script>
import PlaylistAddModal from "./PlaylistAddModal.vue"; import PlaylistAddModal from "./PlaylistAddModal.vue";
import ShareModal from "./ShareModal.vue";
import ConfirmModal from "./ConfirmModal.vue"; import ConfirmModal from "./ConfirmModal.vue";
export default { export default {
components: { PlaylistAddModal, ConfirmModal }, components: { PlaylistAddModal, ConfirmModal, ShareModal },
props: { props: {
item: { item: {
type: Object, type: Object,
@ -172,12 +192,14 @@ export default {
hideChannel: { type: Boolean, default: false }, hideChannel: { type: Boolean, default: false },
index: { type: Number, default: -1 }, index: { type: Number, default: -1 },
playlistId: { type: String, default: null }, playlistId: { type: String, default: null },
preferListen: { type: Boolean, default: false },
admin: { type: Boolean, default: false }, admin: { type: Boolean, default: false },
}, },
emits: ["remove"], emits: ["remove"],
data() { data() {
return { return {
showModal: false, showPlaylistModal: false,
showShareModal: false,
showVideo: true, showVideo: true,
showConfirmRemove: false, showConfirmRemove: false,
}; };
@ -217,3 +239,13 @@ export default {
}, },
}; };
</script> </script>
<style>
.shorts-img {
@apply w-full object-contain;
}
.video_item_link {
display: block;
position: relative;
}
</style>

View File

@ -2,7 +2,7 @@
<div <div
ref="container" ref="container"
data-shaka-player-container data-shaka-player-container
class="relative max-h-screen w-full flex justify-center efy_trans_filter_off" class="efy_trans_filter_off relative max-h-screen w-full flex justify-center"
:class="{ 'player-container': !isEmbed }" :class="{ 'player-container': !isEmbed }"
> >
<video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" /> <video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
@ -40,20 +40,32 @@
class="absolute top-8 rounded bg-black/80 p-2 text-lg backdrop-blur-sm" class="absolute top-8 rounded bg-black/80 p-2 text-lg backdrop-blur-sm"
/> />
</div> </div>
<ModalComponent v-if="showSpeedModal" @close="showSpeedModal = false">
<h2 v-t="'actions.playback_speed'" />
<div class="flex flex-col">
<input
v-model="playbackSpeedInput"
class="input my-3"
type="text"
:placeholder="$t('actions.playback_speed')"
@keyup.enter="setSpeedFromInput()"
/>
<button v-t="'actions.okay'" class="btn ml-auto w-min" @click="setSpeedFromInput()" />
</div>
</ModalComponent>
</template> </template>
<script> <script>
import "shaka-player/dist/controls.css"; import "shaka-player/dist/controls.css";
import { parseTimeParam } from "@/utils/Misc"; import { parseTimeParam } from "@/utils/Misc";
import ModalComponent from "./ModalComponent.vue";
const shaka = import("shaka-player/dist/shaka-player.ui.js"); const shaka = import("shaka-player/dist/shaka-player.ui.js");
if (!window.muxjs) {
import("mux.js").then(muxjs => {
window.muxjs = muxjs;
});
}
const hotkeys = import("hotkeys-js"); const hotkeys = import("hotkeys-js");
export default { export default {
components: { ModalComponent },
props: { props: {
video: { video: {
type: Object, type: Object,
@ -79,6 +91,8 @@ export default {
destroying: false, destroying: false,
inSegment: false, inSegment: false,
isHoveringTimebar: false, isHoveringTimebar: false,
showSpeedModal: false,
playbackSpeedInput: null,
currentTime: 0, currentTime: 0,
seekbarPadding: 2, seekbarPadding: 2,
error: 0, error: 0,
@ -119,7 +133,7 @@ export default {
this.hotkeysPromise.then(() => { this.hotkeysPromise.then(() => {
var self = this; var self = this;
this.$hotkeys( 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+.,alt+p,return,.,,", "f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+s,shift+,,shift+.,alt+p,return,.,,",
function (e, handler) { function (e, handler) {
const videoEl = self.$refs.videoEl; const videoEl = self.$refs.videoEl;
switch (handler.key) { switch (handler.key) {
@ -209,11 +223,14 @@ export default {
self.$emit("navigateNext"); self.$emit("navigateNext");
e.preventDefault(); e.preventDefault();
break; break;
case "shift+s":
self.showSpeedModal = true;
break;
case "shift+,": case "shift+,":
self.$player.trickPlay(Math.max(videoEl.playbackRate - 0.25, 0.25)); self.adjustPlaybackSpeed(videoEl.playbackRate - 0.25);
break; break;
case "shift+.": case "shift+.":
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2)); self.adjustPlaybackSpeed(videoEl.playbackRate + 0.25);
break; break;
case "alt+p": case "alt+p":
document.pictureInPictureElement document.pictureInPictureElement
@ -253,32 +270,6 @@ export default {
videoEl.setAttribute("poster", this.video.thumbnailUrl); videoEl.setAttribute("poster", this.video.thumbnailUrl);
const time = this.$route.query.t ?? this.$route.query.start;
if (time) {
videoEl.currentTime = parseTimeParam(time);
this.initialSeekComplete = true;
} 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);
request.onsuccess = function (event) {
var video = event.target.result;
const currentTime = video?.currentTime;
if (currentTime) {
if (currentTime < component.video.duration * 0.9) {
videoEl.currentTime = currentTime;
}
}
};
tx.oncomplete = () => {
this.initialSeekComplete = true;
};
} else {
this.initialSeekComplete = true;
}
const noPrevPlayer = !this.$player; const noPrevPlayer = !this.$player;
var streams = []; var streams = [];
@ -342,11 +333,12 @@ export default {
} }
if (noPrevPlayer) if (noPrevPlayer)
this.shakaPromise.then(() => { this.shakaPromise.then(async () => {
if (this.destroying) return; if (this.destroying) return;
this.$shaka.polyfill.installAll(); this.$shaka.polyfill.installAll();
const localPlayer = new this.$shaka.Player(videoEl); const localPlayer = new this.$shaka.Player();
await localPlayer.attach(videoEl);
const proxyURL = new URL(component.video.proxyUrl); const proxyURL = new URL(component.video.proxyUrl);
let proxyPath = proxyURL.pathname; let proxyPath = proxyURL.pathname;
if (proxyPath.lastIndexOf("/") === proxyPath.length - 1) { if (proxyPath.lastIndexOf("/") === proxyPath.length - 1) {
@ -435,7 +427,7 @@ export default {
videoEl.currentTime = segment.segment[1]; videoEl.currentTime = segment.segment[1];
segment.skipped = true; segment.skipped = true;
}, },
setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) { async setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) {
const url = "/watch?v=" + this.video.id; const url = "/watch?v=" + this.video.id;
if (!this.$ui) { if (!this.$ui) {
@ -481,14 +473,7 @@ export default {
this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl); this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
const overflowMenuButtons = [ const overflowMenuButtons = ["quality", "captions", "picture_in_picture", "playback_rate", "airplay"];
"quality",
"language",
"captions",
"picture_in_picture",
"playback_rate",
"airplay",
];
if (this.isEmbed) { if (this.isEmbed) {
overflowMenuButtons.push("open_new_tab"); overflowMenuButtons.push("open_new_tab");
@ -499,7 +484,7 @@ export default {
seekBarColors: { seekBarColors: {
base: "rgba(255, 255, 255, 0.3)", base: "rgba(255, 255, 255, 0.3)",
buffered: "rgba(255, 255, 255, 0.54)", buffered: "rgba(255, 255, 255, 0.54)",
played: `oklch(var(--efy_color1_var))`, played: "var(--efy_piped_color1)",
}, },
}; };
@ -519,6 +504,8 @@ export default {
const disableVideo = this.getPreferenceBoolean("listen", false) && !this.video.livestream; const disableVideo = this.getPreferenceBoolean("listen", false) && !this.video.livestream;
const prefetchLimit = Math.min(Math.max(this.getPreferenceNumber("prefetchLimit", 2), 0), 10);
this.$player.configure({ this.$player.configure({
preferredVideoCodecs: this.preferredVideoCodecs, preferredVideoCodecs: this.preferredVideoCodecs,
preferredAudioCodecs: ["opus", "mp4a"], preferredAudioCodecs: ["opus", "mp4a"],
@ -526,7 +513,12 @@ export default {
disableVideo: disableVideo, disableVideo: disableVideo,
}, },
streaming: { streaming: {
segmentPrefetchLimit: 10, segmentPrefetchLimit: prefetchLimit,
retryParameters: {
maxAttempts: Infinity,
baseDelay: 250,
backoffFactor: 1.5,
},
}, },
}); });
@ -535,8 +527,39 @@ export default {
quality > 0 && (this.video.audioStreams.length > 0 || this.video.livestream) && !disableVideo; quality > 0 && (this.video.audioStreams.length > 0 || this.video.livestream) && !disableVideo;
if (qualityConds) this.$player.configure("abr.enabled", false); if (qualityConds) this.$player.configure("abr.enabled", false);
const time = this.$route.query.t ?? this.$route.query.start;
var startTime = 0;
if (time) {
startTime = parseTimeParam(time);
this.initialSeekComplete = true;
} else if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
await new Promise(resolve => {
var tx = window.db.transaction("watch_history", "readonly");
var store = tx.objectStore("watch_history");
var request = store.get(this.video.id);
request.onsuccess = function (event) {
var video = event.target.result;
const currentTime = video?.currentTime;
if (currentTime) {
if (currentTime < video.duration * 0.9) {
startTime = currentTime;
}
}
resolve();
};
tx.oncomplete = () => {
this.initialSeekComplete = true;
};
});
} else {
this.initialSeekComplete = true;
}
player player
.load(uri, 0, mime) .load(uri, startTime, mime)
.then(() => { .then(() => {
const isSafari = window.navigator?.vendor?.includes("Apple"); const isSafari = window.navigator?.vendor?.includes("Apple");
@ -553,6 +576,18 @@ export default {
player.selectAudioLanguage(lang); player.selectAudioLanguage(lang);
} }
const audioLanguages = player.getAudioLanguages();
if (audioLanguages.length > 1) {
const overflowMenuButtons = this.$ui.getConfiguration().overflowMenuButtons;
// append language menu on index 1
const newOverflowMenuButtons = [
...overflowMenuButtons.slice(0, 1),
"language",
...overflowMenuButtons.slice(1),
];
this.$ui.configure("overflowMenuButtons", newOverflowMenuButtons);
}
if (qualityConds) { if (qualityConds) {
var leastDiff = Number.MAX_VALUE; var leastDiff = Number.MAX_VALUE;
var bestStream = null; var bestStream = null;
@ -603,6 +638,16 @@ export default {
const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false); const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
this.$player.setTextTrackVisibility(autoDisplayCaptions); this.$player.setTextTrackVisibility(autoDisplayCaptions);
const prefSubtitles = this.getPreferenceString("subtitles", "");
if (prefSubtitles !== "") {
const textTracks = this.$player.getTextTracks();
const subtitleIdx = textTracks.findIndex(textTrack => textTrack.language == prefSubtitles);
if (subtitleIdx != -1) {
this.$player.setTextTrackVisibility(true);
this.$player.selectTextTrack(textTracks[subtitleIdx]);
}
}
}) })
.catch(e => { .catch(e => {
console.error(e); console.error(e);
@ -636,7 +681,19 @@ export default {
this.$refs.videoEl.currentTime = time; this.$refs.videoEl.currentTime = time;
} }
}, },
adjustPlaybackSpeed(newSpeed) {
const normalizedSpeed = Math.min(4, Math.max(0.25, newSpeed));
this.$player.trickPlay(normalizedSpeed);
},
setSpeedFromInput() {
try {
const newSpeed = Number(this.playbackSpeedInput);
this.adjustPlaybackSpeed(newSpeed);
} catch (err) {
alert(this.$t("actions.invalid_input"));
}
this.showSpeedModal = false;
},
updateMarkers() { updateMarkers() {
const markers = this.$refs.container.querySelector(".shaka-ad-markers"); const markers = this.$refs.container.querySelector(".shaka-ad-markers");
const array = ["to right"]; const array = ["to right"];
@ -874,6 +931,11 @@ html .shaka-range-element:focus {
.shaka-settings-menu button { .shaka-settings-menu button {
-webkit-text-fill-color: var(--efy_text) !important; -webkit-text-fill-color: var(--efy_text) !important;
margin: 1.6rem 0 !important; margin: 1.6rem 0 !important;
place-content: start;
width: 100%;
.shaka-overflow-menu-only {
width: fit-content;
}
} }
.shaka-overflow-menu .material-icons-round, .shaka-overflow-menu .material-icons-round,
.shaka-settings-menu .material-icons-round { .shaka-settings-menu .material-icons-round {

View File

@ -16,14 +16,9 @@ export default {
<template> <template>
<template v-if="getPreferenceBoolean('showWatchOnYouTube', false)"> <template v-if="getPreferenceBoolean('showWatchOnYouTube', false)">
<a <a :href="link" role="button" class="pp-square flex items-center" style="margin: 0; padding: 0">
:href="link" <i v-if="platform == 'YouTube'" class="i-fa6-brands:youtube m-0" />
role="button" <i v-else-if="platform == 'Odysee'" class="i-fa6-brands:odysee m-0" />
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> </a>
</template> </template>
</template> </template>

View File

@ -10,7 +10,7 @@
/> />
</div> </div>
<LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full mt-[15rem]"> <LoadingIndicatorPage :show-content="video && !isEmbed" class="mt-[15rem] w-full">
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" /> <ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
<Transition> <Transition>
<ToastComponent v-if="shouldShowToast" @dismissed="dismiss"> <ToastComponent v-if="shouldShowToast" @dismissed="dismiss">
@ -46,7 +46,7 @@
<!-- views / date --> <!-- views / date -->
<div class="flex flex-auto"> <div class="flex flex-auto">
<span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" /> <span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
<span> </span> <span> · </span>
<span v-text="uploadDate" /> <span v-text="uploadDate" />
</div> </div>
<!-- Likes/dilikes --> <!-- Likes/dilikes -->
@ -77,21 +77,22 @@
video.uploader video.uploader
}}</router-link> }}</router-link>
<!-- Verified Badge --> <!-- Verified Badge -->
<font-awesome-icon v-if="video.uploaderVerified" class="ml-1" icon="check" /> <i v-if="video.uploaderVerified" class="i-fa6-solid:check ml-1" />
</div> </div>
<div class="pp-watch-buttons"> <div class="pp-watch-buttons">
<!-- Subscribe button --> <!-- Subscribe button -->
<button <button
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(video.uploaderSubscriberCount) },
}"
class="btn" class="btn"
@click="subscribeHandler" @click="subscribeHandler"
v-text="
$t('actions.' + (subscribed ? 'unsubscribe' : 'subscribe')) +
' - ' +
numberFormat(video.uploaderSubscriberCount)
"
/> />
<!-- Playlist Add button --> <!-- Playlist Add button -->
<button class="btn flex items-center" @click="showModal = !showModal"> <button class="pp-square btn flex items-center" style="padding: 0" @click="showModal = !showModal">
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" /> <i class="i-fa6-solid:circle-plus m-0" />
</button> </button>
<PlaylistAddModal <PlaylistAddModal
v-if="showModal" v-if="showModal"
@ -108,9 +109,12 @@
:playlist-index="index" :playlist-index="index"
@close="showShareModal = !showShareModal" @close="showShareModal = !showShareModal"
/> />
<button class="btn flex items-center share-btn" @click="showShareModal = !showShareModal"> <button
<font-awesome-icon class="mx-1.5 mr-1" icon="fa-share" /> class="pp-square btn share-btn flex items-center"
<i18n-t keypath="actions.share" tag="strong"></i18n-t> style="padding: 0"
@click="showShareModal = !showShareModal"
>
<i class="i-fa6-solid:share m-0" />
</button> </button>
<!-- YouTube --> <!-- YouTube -->
<WatchOnButton :link="`https://youtu.be/${getVideoId()}`" /> <WatchOnButton :link="`https://youtu.be/${getVideoId()}`" />
@ -123,8 +127,9 @@
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title" :aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title" :title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
class="pp-square btn flex items-center" class="pp-square btn flex items-center"
style="padding: 0"
> >
<font-awesome-icon class="mx-1.5" :icon="isListening ? 'tv' : 'headphones'" /> <i :class="isListening ? 'i-fa6-solid:tv' : 'i-fa6-solid:headphones'" class="m-0" />
</router-link> </router-link>
<!-- RSS Feed button --> <!-- RSS Feed button -->
<a <a
@ -135,11 +140,12 @@
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`" :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
target="_blank" target="_blank"
class="pp-square btn flex items-center" class="pp-square btn flex items-center"
style="padding: 0"
> >
<font-awesome-icon class="mx-1.5" icon="rss" /> <i class="i-fa6-solid:rss m-0" />
</a> </a>
<button class="btn flex items-center gap-1 <md:hidden" @click="downloadCurrentFrame"> <button class="pp-square btn flex items-center" style="padding: 0" @click="downloadCurrentFrame">
<i class="i-fa6-solid:download" />{{ $t("actions.download_frame") }} <i class="i-fa6-solid:download m-0" />
</button> </button>
</div> </div>
</div> </div>
@ -163,8 +169,8 @@
<label v-t="'actions.show_description'" for="showDesc" /> <label v-t="'actions.show_description'" for="showDesc" />
<input id="showComments" v-model="showComments" type="checkbox" @click="toggleComments" /> <input id="showComments" v-model="showComments" type="checkbox" @click="toggleComments" />
<label <label
v-text="`${$t('actions.show_comments')} - ${numberFormat(comments?.commentCount)}`"
for="showComments" for="showComments"
v-text="`${$t('actions.show_comments')} - ${numberFormat(comments?.commentCount)}`"
/> />
<input id="showRecs" v-model="showRecs" type="checkbox" /> <input id="showRecs" v-model="showRecs" type="checkbox" />
<label v-t="'actions.show_recommendations'" for="showRecs" /> <label v-t="'actions.show_recommendations'" for="showRecs" />
@ -195,7 +201,7 @@
<router-link <router-link
v-for="tag in video.tags" v-for="tag in video.tags"
:key="tag" :key="tag"
class="line-clamp-1 efy_trans_filter efy_shadow_trans" class="efy_trans_filter efy_shadow_trans line-clamp-1"
:to="`/results?search_query=${encodeURIComponent(tag)}`" :to="`/results?search_query=${encodeURIComponent(tag)}`"
>{{ tag }}</router-link >{{ tag }}</router-link
> >
@ -219,6 +225,7 @@
:key="comment.commentId" :key="comment.commentId"
:comment="comment" :comment="comment"
:uploader="video.uploader" :uploader="video.uploader"
:uploader-avatar-url="video.uploaderAvatar"
:video-id="getVideoId()" :video-id="getVideoId()"
class="efy_trans_filter efy_shadow_trans" class="efy_trans_filter efy_shadow_trans"
/> />
@ -230,6 +237,7 @@
:playlist-id="playlistId" :playlist-id="playlistId"
:playlist="playlist" :playlist="playlist"
:selected-index="index" :selected-index="index"
:prefer-listen="isListening"
/> />
<div v-show="showRecs" class="pp-show-recs"> <div v-show="showRecs" class="pp-show-recs">
<h6 efy_card style="padding: 5rem 10rem 3rem; margin: 0">Recommended</h6> <h6 efy_card style="padding: 5rem 10rem 3rem; margin: 0">Recommended</h6>
@ -237,6 +245,8 @@
v-for="related in video.relatedStreams" v-for="related in video.relatedStreams"
:key="related.url" :key="related.url"
:item="related" :item="related"
:prefer-listen="isListening"
class="mb-4"
height="94" height="94"
width="168" width="168"
/> />
@ -287,7 +297,7 @@ export default {
selectedAutoLoop: false, selectedAutoLoop: false,
selectedAutoPlay: null, selectedAutoPlay: null,
showComments: true, showComments: true,
showDesc: true, showDesc: false,
showRecs: true, showRecs: true,
showChapters: true, showChapters: true,
comments: null, comments: null,
@ -388,7 +398,7 @@ export default {
this.active = true; this.active = true;
this.selectedAutoPlay = this.getPreferenceBoolean("autoplay", false); this.selectedAutoPlay = this.getPreferenceBoolean("autoplay", false);
this.showComments = !this.getPreferenceBoolean("minimizeComments", false); this.showComments = !this.getPreferenceBoolean("minimizeComments", false);
this.showDesc = !this.getPreferenceBoolean("minimizeDescription", false); this.showDesc = !this.getPreferenceBoolean("minimizeDescription", true);
this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false); this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false);
this.showChapters = !this.getPreferenceBoolean("minimizeChapters", false); this.showChapters = !this.getPreferenceBoolean("minimizeChapters", false);
if (this.video?.duration) { if (this.video?.duration) {
@ -428,7 +438,7 @@ export default {
}); });
sponsors?.segments?.forEach(segment => { sponsors?.segments?.forEach(segment => {
const option = skipOptions[segment.category]; const option = skipOptions?.[segment.category];
segment.autoskip = option === undefined || option === "auto"; segment.autoskip = option === undefined || option === "auto";
}); });
@ -480,9 +490,7 @@ export default {
}, },
async getPlaylistData() { async getPlaylistData() {
if (this.playlistId) { if (this.playlistId) {
await this.fetchJson(this.apiUrl() + "/playlists/" + this.playlistId).then(data => { this.playlist = await this.getPlaylist(this.playlistId);
this.playlist = data;
});
await this.fetchPlaylistPages().then(() => { await this.fetchPlaylistPages().then(() => {
if (!(this.index >= 0)) { if (!(this.index >= 0)) {
for (let i = 0; i < this.playlist.relatedStreams.length; i++) for (let i = 0; i < this.playlist.relatedStreams.length; i++)
@ -516,65 +524,17 @@ export default {
this.fetchSponsors().then(data => (this.sponsors = data)); this.fetchSponsors().then(data => (this.sponsors = data));
}, },
async getComments() { async getComments() {
this.fetchComments().then(data => { this.comments = await this.fetchComments();
this.rewriteComments(data.comments);
this.comments = data;
});
}, },
async fetchSubscribedStatus() { async fetchSubscribedStatus() {
if (!this.channelId) return; if (!this.channelId) return;
if (!this.authenticated) {
this.subscribed = this.isSubscribedLocally(this.channelId);
return;
}
this.fetchJson( this.subscribed = await this.fetchSubscriptionStatus(this.channelId);
this.authApiUrl() + "/subscribed",
{
channelId: this.channelId,
},
{
headers: {
Authorization: this.getAuthToken(),
},
},
).then(json => {
this.subscribed = json.subscribed;
});
},
rewriteComments(data) {
data.forEach(comment => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(comment.commentText, "text/html");
xmlDoc.querySelectorAll("a").forEach(elem => {
if (!elem.innerText.match(/(?:[\d]{1,2}:)?(?:[\d]{1,2}):(?:[\d]{1,2})/))
elem.outerHTML = elem.getAttribute("href");
});
comment.commentText = 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",
);
});
}, },
subscribeHandler() { subscribeHandler() {
if (this.authenticated) { this.toggleSubscriptionState(this.channelId, this.subscribed).then(success => {
this.fetchJson(this.authApiUrl() + (this.subscribed ? "/unsubscribe" : "/subscribe"), null, { if (success) this.subscribed = !this.subscribed;
method: "POST", });
body: JSON.stringify({
channelId: this.channelId,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
});
} else {
if (!this.handleLocalSubscriptions(this.channelId)) return;
}
this.subscribed = !this.subscribed;
}, },
handleClick(event) { handleClick(event) {
if (!event || !event.target) return; if (!event || !event.target) return;
@ -614,12 +574,17 @@ export default {
}).then(json => { }).then(json => {
this.comments.nextpage = json.nextpage; this.comments.nextpage = json.nextpage;
this.loading = false; this.loading = false;
this.rewriteComments(json.comments);
this.comments.comments = this.comments.comments.concat(json.comments); this.comments.comments = this.comments.comments.concat(json.comments);
}); });
} }
}, },
getVideoId() { getVideoId() {
if (this.$route.query.video_ids) {
const videos_list = this.$route.query.video_ids.split(",");
this.index = Number(this.$route.query.index ?? 0);
return videos_list[this.index];
}
return this.$route.query.v || this.$route.params.v; return this.$route.query.v || this.$route.params.v;
}, },
navigate(time) { navigate(time) {
@ -659,7 +624,15 @@ export default {
}, },
navigateNext() { navigateNext() {
const params = this.$route.query; const params = this.$route.query;
let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url; const video_ids = this.$route.query.video_ids?.split(",") ?? [];
let url;
if (this.playlist) {
url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
} else if (video_ids.length > this.index + 1) {
url = `${this.$route.path}?index=${this.index + 1}`;
} else {
url = this.video.relatedStreams[0].url;
}
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
for (var param in params) for (var param in params)
switch (param) { switch (param) {
@ -667,7 +640,8 @@ export default {
case "t": case "t":
break; break;
case "index": case "index":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("index", this.index + 1); if (this.playlist && this.index < this.playlist.relatedStreams.length)
searchParams.set("index", this.index + 1);
break; break;
case "list": case "list":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list); if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
@ -719,6 +693,39 @@ export default {
margin: 0; margin: 0;
} }
} }
video::-webkit-media-text-track-display {
display: flex;
width: fit-content !important;
position: relative !important;
top: calc(100% - 150rem) !important;
height: fit-content !important;
padding: 2rem 8rem;
margin: auto;
margin-bottom: 8rem;
background: #0008 !important;
backdrop-filter: blur(20rem);
color: #fff;
border-radius: var(--efy_radius);
font-family: var(--efy_font_family);
font-size: 22rem !important;
}
video::cue {
background: transparent;
}
.player-container.pp-trans video::-webkit-media-text-track-display {
background: transparent !important;
backdrop-filter: none;
margin-bottom: unset;
line-height: 1.2;
text-shadow: 0 0 5rem #000;
}
.player-container.pp-solid video::-webkit-media-text-track-display {
background: var(--efy_bg) !important;
color: var(--efy_text);
backdrop-filter: none;
}
@media (width <= 768px) { @media (width <= 768px) {
.share-btn { .share-btn {
aspect-ratio: 1; aspect-ratio: 1;
@ -730,4 +737,10 @@ export default {
margin: 0; margin: 0;
} }
} }
@media (max-width: 639px) {
video::-webkit-media-text-track-display {
font-size: 16rem !important;
top: calc(100% - 120rem) !important;
}
}
</style> </style>

View File

@ -5,44 +5,46 @@
"register": "إنشاء حساب", "register": "إنشاء حساب",
"preferences": "الإعدادات", "preferences": "الإعدادات",
"history": "سجل المشاهدة", "history": "سجل المشاهدة",
"subscriptions": "الاشتراكات", "subscriptions": "الإشتراكات",
"playlists": "قوائم التشغيل", "playlists": "قوائم التشغيل",
"feed": "محتوى الاشتراكات", "feed": "محتوى الإشتراكات",
"account": "الحساب", "account": "الحساب",
"instance": "الخادم", "instance": "الخادم",
"player": "المشغل", "player": "المشغل",
"livestreams": "البث المباشر", "livestreams": "البثوث المباشرة",
"channels": "القنوات", "channels": "القنوات",
"bookmarks": "الاشارات المرجعيه", "bookmarks": "الإشارات المرجعية",
"channel_groups": "مجموعات القنوات", "channel_groups": "مجموعات القنوات",
"dearrow": "دي ارو" "dearrow": "دي ارو",
"albums": "الألبومات",
"custom_instances": "مثيلات مخصصة"
}, },
"player": { "player": {
"watch_on": "مشاهدة على {0}", "watch_on": "مشاهدة على {0}",
"failed": "فشل مع رمز الخطأ {0}، راجع السجلات لمزيد من المعلومات" "failed": "فشل مع رمز الخطأ {0}، راجع السجلات لمزيد من المعلومات"
}, },
"actions": { "actions": {
"subscribe": "اشتراك - {count}", "subscribe": "إشتراك",
"view_subscriptions": "عرض الاشتراكات", "view_subscriptions": "عرض الإشتراكات",
"most_recent": "الأحدث", "most_recent": "الأحدث",
"least_recent": "الأقدم", "least_recent": "الأقدم",
"unsubscribe": "إلغاء اشتراك - {count}", "unsubscribe": "إلغاء الإشتراك",
"channel_name_asc": "إسم القناة (أبجدي تصاعدي)", "channel_name_asc": "إسم القناة (أبجدي تصاعدي)",
"sort_by": "ترتيب النتائج:", "sort_by": "ترتيب النتائج:",
"back": "رجوع", "back": "رجوع",
"skip_intro": "تخطي الفواصل/ المقدمة", "skip_intro": "تخطي الفواصل/ المقدمة",
"light": "مضيء", "light": "فاتح",
"clear_history": "مسح تاريخ المشاهدات", "clear_history": "مسح سجل المشاهدات",
"hide_replies": "إخفاء التعليقات", "hide_replies": "إخفاء الردود",
"create_playlist": "إنشاء قائمة", "create_playlist": "إنشاء قائمة تشغيل",
"delete_playlist": "مسح القائمة", "delete_playlist": "حذف قائمة التسجيل",
"select_playlist": "اختر قائمة", "select_playlist": "اختر قائمة تسجيل",
"delete_playlist_confirm": "حذف قائمة التشغيل هذه؟", "delete_playlist_confirm": "حذف قائمة التشغيل هذه؟",
"please_select_playlist": "فضلًا اختر قائمة", "please_select_playlist": "فضلًا اختر قائمة تشغيل",
"channel_name_desc": "إسم القناة (أبجدي تنازلي)", "channel_name_desc": "إسم القناة (أبجدي تنازلي)",
"uses_api_from": "اختيار المُشغل: ", "uses_api_from": "استخدامات واجهة برمجة التطبيقات من • ",
"skip_sponsors": "تخطي الإعلان", "skip_sponsors": "تخطي الرعايات",
"enable_sponsorblock": "تفعيل مانع الإعلانات", "enable_sponsorblock": "تفعيل حظر الإعلانات",
"auto": "تلقائي", "auto": "تلقائي",
"dark": "داكن", "dark": "داكن",
"search": "‏بحث (Ctrl+K)", "search": "‏بحث (Ctrl+K)",
@ -54,7 +56,7 @@
"skip_interaction": "تخطي تذكير التفاعل (اشتراك)", "skip_interaction": "تخطي تذكير التفاعل (اشتراك)",
"skip_non_music": "تخطي الموسيقى: قسم غير الموسيقى", "skip_non_music": "تخطي الموسيقى: قسم غير الموسيقى",
"theme": "السمة", "theme": "السمة",
"instance_selection": "قائمة الخوادم", "instance_selection": "الخادم",
"export_to_json": "تصدير إلى JSON", "export_to_json": "تصدير إلى JSON",
"show_more": "اظهار المزيد", "show_more": "اظهار المزيد",
"skip_outro": "تخطي بطاقات النهاية / الاعتمادات", "skip_outro": "تخطي بطاقات النهاية / الاعتمادات",
@ -67,23 +69,23 @@
"country_selection": "البلد", "country_selection": "البلد",
"default_homepage": "الصفحة الرئيسية الافتراضية", "default_homepage": "الصفحة الرئيسية الافتراضية",
"show_comments": "إظهار التعليقات", "show_comments": "إظهار التعليقات",
"minimize_description_default": "تصغير الوصف بشكل افتراضي", "minimize_description_default": "إخفاء الوصف بشكل افتراضي",
"store_watch_history": "تخزين سجل المشاهدة", "store_watch_history": "تخزين سجل المشاهدة",
"language_selection": "اللغة", "language_selection": "اللغة",
"instances_list": "قائمة المثيلات", "instances_list": "قائمة الخوادم",
"enabled_codecs": "برامج الترميز الممكنة (متعددة)", "enabled_codecs": "برامج الترميز الممكنة (متعددة)",
"import_from_json": "استيراد من JSON", "import_from_json": "استيراد من JSON",
"loop_this_video": "تكرار هذا الفيديو", "loop_this_video": "تكرار هذا الفيديو",
"auto_play_next_video": "التشغيل التلقائي للفيديو التالي", "auto_play_next_video": "تشغيل الفيديو التالي تلقائيا",
"donations": "التبرعات للتطوير", "donations": "التبرعات للتطوير",
"minimize_description": "تصغير الوصف", "minimize_description": "إخفاء الوصف",
"show_description": "عرض الوصف", "show_description": "إظهار الوصف",
"minimize_recommendations": "تقليل التوصيات", "minimize_recommendations": "إخفاء التوصيات",
"show_recommendations": "إظهار التوصيات", "show_recommendations": "إظهار التوصيات",
"disable_lbry": "تعطيل LBRY للبث", "disable_lbry": "تعطيل LBRY للبث",
"enable_lbry_proxy": "تمكين الوكيل ل LBRY", "enable_lbry_proxy": "تمكين الوكيل لـ LBRY",
"view_ssl_score": "عرض نقاط طبقة المقابس الآمنة (SSL)", "view_ssl_score": "عرض نقاط طبقة المقابس الآمنة (SSL)",
"loading": "تحميل...", "loading": "جارٍ التحميل...",
"filter": "المرشحات", "filter": "المرشحات",
"load_more_replies": "تحميل المزيد من الردود", "load_more_replies": "تحميل المزيد من الردود",
"add_to_playlist": "إضافة إلى قائمة التشغيل", "add_to_playlist": "إضافة إلى قائمة التشغيل",
@ -91,17 +93,17 @@
"delete_playlist_video_confirm": "إزالة الفيديو من قائمة التشغيل؟", "delete_playlist_video_confirm": "إزالة الفيديو من قائمة التشغيل؟",
"delete_account": "حذف الحساب", "delete_account": "حذف الحساب",
"logout": "تسجيل الخروج من هذا الجهاز", "logout": "تسجيل الخروج من هذا الجهاز",
"minimize_recommendations_default": "تقليل التوصيات بشكل افتراضي", "minimize_recommendations_default": "إخفاء التوصيات بشكل افتراضي",
"invalidate_session": "تسجيل الخروج من جميع الأجهزة", "invalidate_session": "تسجيل الخروج من جميع الأجهزة",
"different_auth_instance": "استخدام مثيل مختلف للمصادقة", "different_auth_instance": "استخدام خادم مختلف للمصادقة",
"instance_auth_selection": "خادم المصادقة", "instance_auth_selection": "خادم المصادقة",
"clone_playlist": "استنساخ قائمة التشغيل", "clone_playlist": "استنساخ قائمة التشغيل",
"clone_playlist_success": "تم استنساخها بنجاح!", "clone_playlist_success": "تم استنساخها بنجاح!",
"download_as_txt": "تنزيل بتنسيق .txt", "download_as_txt": "تنزيل بتنسيق .txt",
"reset_preferences": "اعادة التعيين للتفضيلات", "reset_preferences": "إعادة التعيين الإعدادات",
"confirm_reset_preferences": "هل أنت متأكد من أنك تريد إعادة تعيين تفضيلاتك؟", "confirm_reset_preferences": "هل أنت متأكد من أنك تريد إعادة تعيين إعداداتك؟",
"backup_preferences": "تفضيلات النسخ الاحتياطي", "backup_preferences": "النسخ الاحتياطي للإعدادات",
"restore_preferences": "استعادة التفضيلات", "restore_preferences": "إستعادة الإعدادات",
"back_to_home": "العودة إلى الصفحة الرئيسية", "back_to_home": "العودة إلى الصفحة الرئيسية",
"share": "مشاركة", "share": "مشاركة",
"with_timecode": "شارك مع رمز الوقت", "with_timecode": "شارك مع رمز الوقت",
@ -114,17 +116,17 @@
"documentation": "التوثيق", "documentation": "التوثيق",
"status_page": "الحالة", "status_page": "الحالة",
"source_code": "شفرة المصدر", "source_code": "شفرة المصدر",
"instance_donations": "تبرعات المثيل", "instance_donations": "تبرعات للخادم",
"hide_watched": "إخفاء مقاطع الفيديو التي تمت مشاهدتها من الخلاصة", "hide_watched": "إخفاء مقاطع الفيديو التي تمت المشاهدة من محتوى الإشتراكات",
"reply_count": "{count} الردود", "reply_count": "{count} الردود",
"minimize_comments_default": "تصغير التعليقات بشكل افتراضي", "minimize_comments_default": "إخفاء التعليقات بشكل افتراضي",
"minimize_comments": "تصغير التعليقات", "minimize_comments": "إخفاء التعليقات",
"show_watch_on_youtube": "عرض زر مشاهدة على يوتيوب", "show_watch_on_youtube": "عرض زر مشاهدة على يوتيوب",
"minimize_chapters_default": "تصغير الفصول بشكل افتراضي", "minimize_chapters_default": "إخفاء الفصول بشكل افتراضي",
"no_valid_playlists": "لا يحتوي الملف على قوائم تشغيل صالحة!", "no_valid_playlists": "لا يحتوي الملف على قوائم تشغيل صالحة!",
"with_playlist": "المشاركة مع قائمة التشغيل", "with_playlist": "المشاركة مع قائمة التشغيل",
"bookmark_playlist": "الاشاره المرجعيه", "bookmark_playlist": "الإشارة المرجعية",
"playlist_bookmarked": "تم وضعها في الاشارات المرجعية", "playlist_bookmarked": "تم وضعها في الإشارة المرجعية",
"skip_button_only": "إظهار زر التخطي", "skip_button_only": "إظهار زر التخطي",
"skip_automatically": "تلقائيا", "skip_automatically": "تلقائيا",
"min_segment_length": "الحد الأدنى لطول الفصل (بالثواني)", "min_segment_length": "الحد الأدنى لطول الفصل (بالثواني)",
@ -143,20 +145,31 @@
"show_search_suggestions": "إظهار اقتراحات البحث", "show_search_suggestions": "إظهار اقتراحات البحث",
"chapters_layout_mobile": "تخطيط الفصول على الهاتف", "chapters_layout_mobile": "تخطيط الفصول على الهاتف",
"delete_automatically": "الحذف تلقائيا بعد", "delete_automatically": "الحذف تلقائيا بعد",
"enable_dearrow": "تمكين دي ارو", "enable_dearrow": "تمكين DeArrow",
"generate_qrcode": "إنشاء رمز الاستجابة السريعة", "generate_qrcode": "إنشاء رمز الاستجابة السريعة",
"import_from_json_csv": "استيراد من JSON/CSV", "import_from_json_csv": "استيراد من JSON/CSV",
"download_frame": "إطار التحميل", "download_frame": "إطار التحميل",
"instance_privacy_policy": "سياسة الخصوصية" "instance_privacy_policy": "سياسة الخصوصية",
"add_to_group": "إضافة إلى المجموعة",
"instances_not_shown": "الخوادم العامة غير المعروضة هنا ليست متاحة حاليًا.",
"concurrent_prefetch_limit": "حد الجلب المسبق للدفق المتزامن",
"customize": "تخصيص",
"invalid_url": "عنوان URL غير صالح!",
"add": "إضافة",
"delete_group_confirm": "حذف هذه المجموعة؟",
"creator_replied": "أجاب المنشء",
"creator_liked": "المنشء اعجب",
"playback_speed": "سرعة التشغيل",
"invalid_input": "مدخل غير صالح"
}, },
"video": { "video": {
"sponsor_segments": "المقاطع الإعلانية", "sponsor_segments": "المقاطع الإعلانية",
"ratings_disabled": "التقييم غير متاح", "ratings_disabled": "التقييم غير متاح",
"chapters": "فصول الفيديو", "chapters": "فصول الفيديو",
"watched": "تمت مشاهدته", "watched": "تمت المشاهدة",
"views": "{views} عدد المشاهدات", "views": "{views} عدد المشاهدات",
"shorts": "فديوهات قصيرة", "shorts": "مقاطع فديو قصيرة",
"videos": "الفيديوات", "videos": "مقاطع الفيديو",
"live": "{0} مباشر", "live": "{0} مباشر",
"all": "الكل", "all": "الكل",
"category": "الفئة", "category": "الفئة",
@ -175,20 +188,22 @@
"did_you_mean": "هل تقصد: {0}؟", "did_you_mean": "هل تقصد: {0}؟",
"music_playlists": "YT Music: قوائم التشغيل", "music_playlists": "YT Music: قوائم التشغيل",
"music_albums": "YT Music: ألبومات", "music_albums": "YT Music: ألبومات",
"music_artists": "YT الموسيقى: الفنانين" "music_artists": "YT Music: الفنانين"
}, },
"preferences": { "preferences": {
"version": "الإصدار", "version": "الإصدار",
"registered_users": "المستخدمون المسجلون", "registered_users": "المستخدمون المسجلون",
"instance_name": "اسم المثيل", "instance_name": "أسم الخادم",
"instance_locations": "مواقع المثيل", "instance_locations": "مواقع الخادم",
"has_cdn": "لديه CDN؟", "has_cdn": "لديه CDN؟",
"ssl_score": "نقاط طبقة المقابس الآمنة", "ssl_score": "نقاط طبقة المقابس الآمنة",
"up_to_date": "حديث؟" "up_to_date": "‏محدث؟",
"uptime_30d": "وقت التشغيل (30 يومًا)",
"api_url": "رابط واجهة برمجة التطبيقات"
}, },
"comment": { "comment": {
"pinned_by": "مثبت بواسطة {author}", "pinned_by": "مثبت بواسطة {author}",
"disabled": "يتم تعطيل التعليقات بواسطة القائم بالتحميل.", "disabled": "يتم تعطيل التعليقات بواسطة القائم بالرفع.",
"user_disabled": "التعليقات معطلة في الإعدادات.", "user_disabled": "التعليقات معطلة في الإعدادات.",
"loading": "تحميل التعليقات..." "loading": "تحميل التعليقات..."
}, },
@ -205,16 +220,18 @@
"preferences_note": "ملاحظة: يتم حفظ التفضيلات في وحدة التخزين المحلية في متصفحك. سيؤدي حذف بيانات المتصفح إلى إعادة تعيينها." "preferences_note": "ملاحظة: يتم حفظ التفضيلات في وحدة التخزين المحلية في متصفحك. سيؤدي حذف بيانات المتصفح إلى إعادة تعيينها."
}, },
"info": { "info": {
"preferences_note": "ملاحظة: يتم حفظ التفضيلات في وحدة التخزين المحلية في متصفحك. سيؤدي حذف بيانات المتصفح إلى إعادة تعيينها.", "preferences_note": "ملاحظة: يتم حفظ الإعدادات في وحدة التخزين المحلية في متصفحك. حذف بيانات متصفحك سيؤدي إلى إعادة تعيينها.",
"page_not_found": "لم يتم العثور على الصفحة", "page_not_found": "لم يتم العثور على الصفحة",
"copied": "نسخ!", "copied": "نسخ!",
"cannot_copy": "لا يمكن نسخه!", "cannot_copy": "لا يمكن نسخه!",
"local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟", "local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟",
"register_no_email_note": "لا ينصح باستخدام البريد الإلكتروني كاسم مستخدم. المضي قدما على أي حال؟", "register_no_email_note": "لا ينصح باستخدام البريد الإلكتروني كاسم مستخدم. المضي قدما على أي حال؟",
"next_video_countdown": "تشغيل الفيديو التالي بعد { 0 } ق", "next_video_countdown": "تشغيل الفيديو التالي بعد {0} ث",
"weeks": "{amount} أسبوع (أسابيع)", "weeks": "{amount} أسبوع (أسابيع)",
"hours": "{amount} ساعة (ساعات)", "hours": "{amount} ساعة (ساعات)",
"months": "{amount} شهر (أشهر)", "months": "{amount} شهر (أشهر)",
"days": "{amount} يوم (أيام)" "days": "{amount} يوم (أيام)",
"login_note": "سجل الدخول باستخدام حساب تم إنشاؤه في هذا الخادم.",
"register_note": "سجل حسابًا لخادم Piped هذا. سيسمح لك هذا بمزامنة اشتراكاتك وقوائم التشغيل مع حسابك، بحيث يتم تخزينها من جانب الخادم. يمكنك استخدام جميع الميزات بدون حساب، ولكن سيتم تخزين جميع البيانات في ذاكرة التخزين المؤقت المحلية لمتصفحك. يُرجى التأكد من عدم استخدام عنوان البريد الإلكتروني كاسم المستخدم الخاص بك واختيار كلمة مرور آمنة لا تستخدمها في أي مكان آخر."
} }
} }

View File

@ -21,8 +21,8 @@
"watch_on": "{0} saytında bax" "watch_on": "{0} saytında bax"
}, },
"actions": { "actions": {
"subscribe": "Abunə Ol - {count}", "subscribe": "Abunə Ol",
"unsubscribe": "Abunəlikdən Çıx - {count}", "unsubscribe": "Abunəlikdən Çıx",
"view_subscriptions": "Abunəliklərə Baxın", "view_subscriptions": "Abunəliklərə Baxın",
"sort_by": "Çeşidlə:", "sort_by": "Çeşidlə:",
"most_recent": "Ən Yeni", "most_recent": "Ən Yeni",

57
src/locales/be.json Normal file
View File

@ -0,0 +1,57 @@
{
"actions": {
"okay": "Добра",
"reply_count": "{count} адказаў",
"skip_button_only": "Паказаць кнопку прапусціць",
"channel_name_asc": "Імя канала (А-Я)",
"download_as_txt": "Спампаваць як .txt",
"subscribe": "Падпісацца",
"loading": "Загрузка...",
"filter": "Фільтар",
"unsubscribe": "Адпісацца",
"channel_name_desc": "Імя канала (Я-А)",
"language_selection": "Мова",
"show_less": "Паказаць менш",
"theme": "Тэма",
"hide_replies": "Схаваць адказы",
"least_recent": "Найменш актуальныя",
"most_recent": "Самыя актуальныя",
"no": "Не",
"documentation": "Дакументацыя",
"sort_by": "Сартаваць па:",
"back": "Назад",
"view_subscriptions": "Паказаць падпіскі",
"status_page": "Статус",
"import_from_json": "Імпартаваць з JSON",
"instance_privacy_policy": "Палітыка прыватнасці",
"bookmark_playlist": "Закладка",
"skip_outro": "Прапусціць канчатковыя цітры/тытры",
"skip_intro": "Прапусціць паўзу/застаўку",
"skip_sponsors": "Прапусціць спонсарскую рэкламу",
"skip_automatically": "Аўтаматычна",
"enable_sponsorblock": "Уключыць Sponsorblock",
"uses_api_from": "Выкарыстоўвае API ад "
},
"titles": {
"subscriptions": "Падпіскі",
"trending": "Папулярнае",
"login": "Уваход",
"preferences": "Налады",
"history": "Гісторыя",
"bookmarks": "Закладкі",
"channels": "Каналы",
"register": "Рэгістрацыя",
"livestreams": "Жывыя трансляцыі",
"feed": "Стужка",
"channel_groups": "Групы каналаў",
"account": "Ўліковы запіс",
"instance": "Інстанс",
"playlists": "Плэйлісты",
"player": "Прайгравальнік",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "Прагляд на {0}",
"failed": "Памылка з кодам памылкі {0}, для атрымання дадатковай інфармацыі глядзіце журналы"
}
}

View File

@ -19,7 +19,7 @@
}, },
"actions": { "actions": {
"most_recent": "Най-скорошен", "most_recent": "Най-скорошен",
"unsubscribe": "Отписване - {count}", "unsubscribe": "Отписване",
"uses_api_from": "Използва API от ", "uses_api_from": "Използва API от ",
"skip_sponsors": "Пропускане на спонсори", "skip_sponsors": "Пропускане на спонсори",
"skip_preview": "Пропускане на преглед/обобщение", "skip_preview": "Пропускане на преглед/обобщение",
@ -27,7 +27,7 @@
"min_segment_length": "Минимална дължина на сегмента (в секунди)", "min_segment_length": "Минимална дължина на сегмента (в секунди)",
"default_quality": "Качество по подразбиране", "default_quality": "Качество по подразбиране",
"minimize_comments_default": "Минимизиране на коментарите по подразбиране", "minimize_comments_default": "Минимизиране на коментарите по подразбиране",
"subscribe": "Абониране - {count}", "subscribe": "Абониране",
"view_subscriptions": "Преглед на абонаменти", "view_subscriptions": "Преглед на абонаменти",
"sort_by": "Сортиране по:", "sort_by": "Сортиране по:",
"least_recent": "Най-малко скорошен", "least_recent": "Най-малко скорошен",

View File

@ -12,8 +12,8 @@
"watch_on": "দেখুন {0}" "watch_on": "দেখুন {0}"
}, },
"actions": { "actions": {
"subscribe": "সদস্যতা নিন - {count}", "subscribe": "সদস্যতা নিন",
"unsubscribe": "সদস্যতা পরিত্যাগ - {count}", "unsubscribe": "সদস্যতা পরিত্যাগ",
"view_subscriptions": "সদস্যতার তালিকা", "view_subscriptions": "সদস্যতার তালিকা",
"sort_by": "ভিডিও গুলোর বিন্যাস:", "sort_by": "ভিডিও গুলোর বিন্যাস:",
"most_recent": "সবচেয়ে সাম্প্রতিক", "most_recent": "সবচেয়ে সাম্প্রতিক",

View File

@ -11,10 +11,10 @@
"skip_interaction": "Preskoči podsjetnik interakcije (uz pretplatu)", "skip_interaction": "Preskoči podsjetnik interakcije (uz pretplatu)",
"show_comments": "Prikažite komentare", "show_comments": "Prikažite komentare",
"least_recent": "Najstariji", "least_recent": "Najstariji",
"unsubscribe": "Otkažite pretplatu - {count}", "unsubscribe": "Otkažite pretplatu",
"view_subscriptions": "Prikažite Pretplate", "view_subscriptions": "Prikažite Pretplate",
"sort_by": "Poredajte po:", "sort_by": "Poredajte po:",
"subscribe": "Pretplatite se - {count}", "subscribe": "Pretplatite se",
"channel_name_asc": "Naziv kanala (A-Z)", "channel_name_asc": "Naziv kanala (A-Z)",
"back": "Nazad", "back": "Nazad",
"create_playlist": "Stvorite popis snimaka", "create_playlist": "Stvorite popis snimaka",

View File

@ -64,8 +64,8 @@
"hide_replies": "Amaga les respostes", "hide_replies": "Amaga les respostes",
"load_more_replies": "Carrega més respostes", "load_more_replies": "Carrega més respostes",
"view_subscriptions": "Mostra les subscripcions", "view_subscriptions": "Mostra les subscripcions",
"subscribe": "Subscriu-me - {count}", "subscribe": "Subscriu-me",
"unsubscribe": "Anul·la la subscripció - {count}", "unsubscribe": "Anul·la la subscripció",
"sort_by": "Ordena per:", "sort_by": "Ordena per:",
"most_recent": "Més recents", "most_recent": "Més recents",
"least_recent": "Menys recents", "least_recent": "Menys recents",
@ -95,7 +95,7 @@
"clone_playlist_success": "S'ha clonat correctament!", "clone_playlist_success": "S'ha clonat correctament!",
"download_as_txt": "Baixa com a .txt", "download_as_txt": "Baixa com a .txt",
"reset_preferences": "Restableix les preferències", "reset_preferences": "Restableix les preferències",
"restore_preferences": "Restaura les preferències", "restore_preferences": "Importa les preferències",
"backup_preferences": "Exporta les preferències", "backup_preferences": "Exporta les preferències",
"confirm_reset_preferences": "Segur que voleu restablir les vostres preferències?", "confirm_reset_preferences": "Segur que voleu restablir les vostres preferències?",
"back_to_home": "Torna a l'inici", "back_to_home": "Torna a l'inici",
@ -142,7 +142,11 @@
"auto_display_captions": "Mostra els subtítols automàticament", "auto_display_captions": "Mostra els subtítols automàticament",
"autoplay_next_countdown": "Compte enrere predefinit fins al pròxim vídeo (en segons)", "autoplay_next_countdown": "Compte enrere predefinit fins al pròxim vídeo (en segons)",
"generate_qrcode": "Genera un codi QR", "generate_qrcode": "Genera un codi QR",
"playlist_name": "Nom de la llista de reproducció" "playlist_name": "Nom de la llista de reproducció",
"add_to_group": "Afegeix al grup",
"instance_privacy_policy": "Política de privadesa",
"concurrent_prefetch_limit": "Límit de precàrrega de flux concurrent",
"instances_not_shown": "Les instàncies públiques que no es mostren aquí no es troben disponibles."
}, },
"comment": { "comment": {
"pinned_by": "Fixat per {author}", "pinned_by": "Fixat per {author}",
@ -157,7 +161,8 @@
"ssl_score": "Puntuació SSL", "ssl_score": "Puntuació SSL",
"version": "Versió", "version": "Versió",
"up_to_date": "Actualitzada?", "up_to_date": "Actualitzada?",
"registered_users": "Usuaris registrats" "registered_users": "Usuaris registrats",
"uptime_30d": "Temps d'activitat (30 d)"
}, },
"video": { "video": {
"watched": "Vist", "watched": "Vist",
@ -214,6 +219,8 @@
"next_video_countdown": "Pròxim vídeo en {0}s", "next_video_countdown": "Pròxim vídeo en {0}s",
"months": "{amount} mesos", "months": "{amount} mesos",
"weeks": "{amount} setmanes", "weeks": "{amount} setmanes",
"days": "{amount} dies" "days": "{amount} dies",
"login_note": "Inicieu la sessió amb un compte creat en aquesta instància.",
"register_note": "Registreu un compte en aquesta instància de Piped. Això us permetrà sincronitzar les vostres subscripcions i llistes de reproducció amb el vostre compte, perquè s'emmagatzemin al servidor. Podeu emprar totes les funcions sense un compte, però totes les dades es desaran a la memòria cau local del vostre navegador. Comproveu que no utilitzeu una adreça electrònica com a nom d'usuari, i trieu una contrasenya segura que no feu servir en cap altre lloc."
} }
} }

View File

@ -15,11 +15,13 @@
"channels": "Kanály", "channels": "Kanály",
"bookmarks": "Záložky", "bookmarks": "Záložky",
"channel_groups": "Skupiny kanálů", "channel_groups": "Skupiny kanálů",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Alba",
"custom_instances": "Vlastní instance"
}, },
"actions": { "actions": {
"loop_this_video": "Přehrávat video ve smyčce", "loop_this_video": "Přehrávat video ve smyčce",
"subscribe": "Odebírat - {count}", "subscribe": "Odebírat",
"view_subscriptions": "Zobrazit odběry", "view_subscriptions": "Zobrazit odběry",
"sort_by": "Seřadit podle:", "sort_by": "Seřadit podle:",
"most_recent": "Nejnovější", "most_recent": "Nejnovější",
@ -68,7 +70,7 @@
"clear_history": "Smazat historii", "clear_history": "Smazat historii",
"hide_replies": "Skrýt odpovědi", "hide_replies": "Skrýt odpovědi",
"load_more_replies": "Načíst další odpovědi", "load_more_replies": "Načíst další odpovědi",
"unsubscribe": "Zrušit odběr - {count}", "unsubscribe": "Zrušit odběr",
"skip_sponsors": "Přeskočit sponzory", "skip_sponsors": "Přeskočit sponzory",
"minimize_description": "Skrýt popis", "minimize_description": "Skrýt popis",
"skip_non_music": "Přeskočit hudbu: nehudební sekce", "skip_non_music": "Přeskočit hudbu: nehudební sekce",
@ -135,7 +137,7 @@
"cancel": "Zrušit", "cancel": "Zrušit",
"edit_playlist": "Upravit playlist", "edit_playlist": "Upravit playlist",
"playlist_description": "Popis playlistu", "playlist_description": "Popis playlistu",
"okay": "Okay", "okay": "Dobře",
"show_search_suggestions": "Zobrazit našeptávání ve vyhledávání", "show_search_suggestions": "Zobrazit našeptávání ve vyhledávání",
"chapters_layout_mobile": "Rozložení kapitol na mobilu", "chapters_layout_mobile": "Rozložení kapitol na mobilu",
"enable_dearrow": "Povolit DeArrow", "enable_dearrow": "Povolit DeArrow",
@ -143,7 +145,18 @@
"generate_qrcode": "Vygenerovat QR kód", "generate_qrcode": "Vygenerovat QR kód",
"import_from_json_csv": "Importovat z JSON/CSV", "import_from_json_csv": "Importovat z JSON/CSV",
"download_frame": "Stáhnout snímek", "download_frame": "Stáhnout snímek",
"instance_privacy_policy": "Ochrana údajů" "instance_privacy_policy": "Ochrana údajů",
"add_to_group": "Přidat do skupiny",
"instances_not_shown": "Veřejné instance, které zde nejsou zobrazeny, momentálně nejsou dostupné.",
"concurrent_prefetch_limit": "Limit souběžných přednačtení streamů",
"customize": "Přizpůsobit",
"invalid_url": "Neplatná adresa URL!",
"add": "Přidat",
"delete_group_confirm": "Odstranit tuto skupinu?",
"creator_replied": "Odpověď autora",
"creator_liked": "Autorovi se líbilo",
"playback_speed": "Rychlost přehrávání",
"invalid_input": "Neplatné údaje"
}, },
"player": { "player": {
"watch_on": "Zobrazit na {0}", "watch_on": "Zobrazit na {0}",
@ -162,7 +175,9 @@
"ssl_score": "Stav SSL", "ssl_score": "Stav SSL",
"registered_users": "Registrovaní uživatelé", "registered_users": "Registrovaní uživatelé",
"up_to_date": "Aktuální?", "up_to_date": "Aktuální?",
"version": "Verze" "version": "Verze",
"uptime_30d": "Doba online (30d)",
"api_url": "Api URL"
}, },
"login": { "login": {
"username": "Uživatelské jméno", "username": "Uživatelské jméno",
@ -215,6 +230,8 @@
"hours": "{amount} hodin", "hours": "{amount} hodin",
"days": "{amount} dnů", "days": "{amount} dnů",
"weeks": "{amount} týdnů", "weeks": "{amount} týdnů",
"months": "{amount} měsíců" "months": "{amount} měsíců",
"login_note": "Přihlaste se s účtem vytvořeným na této instanci.",
"register_note": "Zaregistrujte si účet na této instanci služby Piped. To vám umožní synchronizovat vaše odběry a seznamy skladeb s vaším účtem, takže budou uloženy na straně serveru. Všechny funkce můžete používat i bez účtu, ale všechna data budou uložena v místní mezipaměti prohlížeče. Ujistěte se, že jako uživatelské jméno NEPOUŽÍVÁTE e-mailovou adresu, a zvolte si bezpečné heslo, které nepoužíváte jinde."
} }
} }

View File

@ -31,8 +31,8 @@
"most_recent": "Am Neuesten", "most_recent": "Am Neuesten",
"sort_by": "Sortieren nach:", "sort_by": "Sortieren nach:",
"view_subscriptions": "Abos anzeigen", "view_subscriptions": "Abos anzeigen",
"unsubscribe": "Deabonnieren - {count}", "unsubscribe": "Deabonnieren",
"subscribe": "Abonnieren - {count}", "subscribe": "Abonnieren",
"enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)", "enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)",
"enable_lbry_proxy": "Proxy für LBRY einschalten", "enable_lbry_proxy": "Proxy für LBRY einschalten",
"disable_lbry": "LBRY für Streaming deaktivieren", "disable_lbry": "LBRY für Streaming deaktivieren",
@ -125,7 +125,12 @@
"generate_qrcode": "QR-Code generieren", "generate_qrcode": "QR-Code generieren",
"import_from_json_csv": "Aus JSON/CSV importieren", "import_from_json_csv": "Aus JSON/CSV importieren",
"download_frame": "Einzelbild (Frame) downloaden", "download_frame": "Einzelbild (Frame) downloaden",
"instance_privacy_policy": "Datenschutzerklärung" "instance_privacy_policy": "Datenschutzerklärung",
"add_to_group": "Zu Gruppe hinzufügen",
"instances_not_shown": "Öffentliche Instanzen, die hier nicht angezeigt werden, sind derzeit nicht verfügbar.",
"customize": "Anpassen",
"invalid_url": "Ungültige URL!",
"add": "Hinzufügen"
}, },
"player": { "player": {
"watch_on": "Auf {0} ansehen", "watch_on": "Auf {0} ansehen",
@ -147,7 +152,9 @@
"channels": "Kanäle", "channels": "Kanäle",
"bookmarks": "Lesezeichen", "bookmarks": "Lesezeichen",
"channel_groups": "Kanalgruppen", "channel_groups": "Kanalgruppen",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Alben",
"custom_instances": "Benutzerdefinierte Instanzen"
}, },
"video": { "video": {
"sponsor_segments": "Sponsoren-Abschnitte", "sponsor_segments": "Sponsoren-Abschnitte",
@ -172,7 +179,9 @@
"instance_name": "Instanzname", "instance_name": "Instanzname",
"registered_users": "Registrierte Benutzer", "registered_users": "Registrierte Benutzer",
"version": "Version", "version": "Version",
"up_to_date": "Auf dem neuesten Stand?" "up_to_date": "Auf dem neuesten Stand?",
"uptime_30d": "Uptime (30 Tage)",
"api_url": "API-URL"
}, },
"comment": { "comment": {
"pinned_by": "Angeheftet von {author}", "pinned_by": "Angeheftet von {author}",
@ -212,6 +221,8 @@
"weeks": "{amount} Woche(n)", "weeks": "{amount} Woche(n)",
"months": "{amount} Monat(en)", "months": "{amount} Monat(en)",
"hours": "{amount} Stunde(n)", "hours": "{amount} Stunde(n)",
"days": "{amount} Tag(e)" "days": "{amount} Tag(e)",
"login_note": "Melde dich mit einem Konto an, das du auf dieser Instanz erstellt hast.",
"register_note": "Erstelle ein Konto für diese Piped-Instanz. Dadurch kannst du deine Abos und Playlists mit deinem Konto synchronisieren, sie werden also serverseitig gespeichert. Du kannst all diese Funktionen auch ohne Konto nutzen, allerdings werden dann alle Daten nur im lokalen Speicher deines Browsers gespeichert. Bitte stelle sicher, dass du KEINE E-Mail-Adresse als Benutzernamen verwendest und ein sicheres Passwort wählst, welches du nicht bereits woanders nutzt."
} }
} }

View File

@ -4,7 +4,7 @@
"store_watch_history": "Αποθήκευση ιστορικού παρακολούθησης", "store_watch_history": "Αποθήκευση ιστορικού παρακολούθησης",
"default_homepage": "Προεπιλεγμένη αρχική σελίδα", "default_homepage": "Προεπιλεγμένη αρχική σελίδα",
"country_selection": "Επιλογή χώρας", "country_selection": "Επιλογή χώρας",
"audio_only": "Ήχος μόνο", "audio_only": "Μόνο ήχος",
"skip_non_music": "Παράλειψη μουσικής: Μη-μουσικό τμήμα", "skip_non_music": "Παράλειψη μουσικής: Μη-μουσικό τμήμα",
"skip_self_promo": "Παράλειψη μη-χορηγούμενης/προσωπικής προώθησης", "skip_self_promo": "Παράλειψη μη-χορηγούμενης/προσωπικής προώθησης",
"skip_preview": "Παράλειψη προεπισκόπησης/ανακεφαλαίωσης", "skip_preview": "Παράλειψη προεπισκόπησης/ανακεφαλαίωσης",
@ -31,8 +31,8 @@
"enable_sponsorblock": "Ενεργοποίηση Sponsorblock", "enable_sponsorblock": "Ενεργοποίηση Sponsorblock",
"back": "Επιστροφή", "back": "Επιστροφή",
"sort_by": "Ταξινόμηση κατά:", "sort_by": "Ταξινόμηση κατά:",
"unsubscribe": "Απεγγραφή - {count}", "unsubscribe": "Απεγγραφή",
"subscribe": "Εγγραφή - {count}", "subscribe": "Εγγραφή",
"loop_this_video": "Επανάληψη αυτού του βίντεο", "loop_this_video": "Επανάληψη αυτού του βίντεο",
"instance_selection": "Επιλογή διακομιστή", "instance_selection": "Επιλογή διακομιστή",
"enabled_codecs": "Ενεργοποιημένοι κωδικοποητές (Πολλαπλοί)", "enabled_codecs": "Ενεργοποιημένοι κωδικοποητές (Πολλαπλοί)",
@ -44,7 +44,7 @@
"show_recommendations": "Εμφάνιση συστάσεις", "show_recommendations": "Εμφάνιση συστάσεις",
"donations": "Δωρεές", "donations": "Δωρεές",
"auto_play_next_video": "Αυτόματη αναπαραγωγή επόμενου βίντεο", "auto_play_next_video": "Αυτόματη αναπαραγωγή επόμενου βίντεο",
"import_from_json": "Εισαγωγή από JSON/CSV", "import_from_json": "Εισαγωγή από JSON",
"export_to_json": "Εξαγωγή σε JSON", "export_to_json": "Εξαγωγή σε JSON",
"no": "Όχι", "no": "Όχι",
"yes": "Ναι", "yes": "Ναι",
@ -65,20 +65,89 @@
"add_to_playlist": "Προσθήκη στη λίστα αναπαραγωγής", "add_to_playlist": "Προσθήκη στη λίστα αναπαραγωγής",
"clear_history": "Καθαρισμός ιστορικού", "clear_history": "Καθαρισμός ιστορικού",
"delete_playlist_video_confirm": "Είστε σίγουρος οτι θέλετε να αφαιρέσετε αυτό το βίντεο απο την λίστα αναπαραγωγής;", "delete_playlist_video_confirm": "Είστε σίγουρος οτι θέλετε να αφαιρέσετε αυτό το βίντεο απο την λίστα αναπαραγωγής;",
"delete_playlist_confirm": "Είστε σίγουρος οτι θέλετε να διαγράψετε αυτή την λίστα αναπαραγωγής;" "delete_playlist_confirm": "Διαγραφή λίστας αναπαραγωγής;",
"okay": "Εντάξει",
"edit_playlist": "Επεξεργασία λίστας αναπαραγωγής",
"group_name": "Όνομα γκρουπ",
"invalidate_session": "Αποσύνδεση από όλες τις συσκευές",
"playlist_bookmarked": "Σώθηκε ως σελιδοδείκτης",
"add_to_group": "Προσθήκη στο γκρουπ",
"confirm_reset_preferences": "Είσαι σίγουρος ότι θες να επαναφέρεις τις προτιμήσεις σου;",
"reply_count": "{count} απαντήσεις",
"restore_preferences": "Εποκατάσταση προτιμήσεων",
"piped_link": "Σύνδεσμος Piped",
"skip_button_only": "Εμφάνισε κουμπί παράλειψης",
"minimize_comments_default": "Ελαχιστοποίηση σχολίων από προεπιλογή",
"import_from_json_csv": "Εισαγωγή από JSON/CSV",
"cancel": "Ακύρωση",
"playlist_description": "Περιγραφή λίστας αναπαραγωγής",
"download_as_txt": "Κατέβασμα σε .txt",
"source_code": "Πηγαίος κώδικας",
"copy_link": "Αντιγραφή συνδέσμου",
"dismiss": "Παράλειψη",
"hide_watched": "Απόκρυψη βίντεο που έχουν προβληθεί από το feed",
"delete_account": "Διαγραφή λογαριασμού",
"backup_preferences": "Αντίγραφο προτιμήσεων",
"instance_donations": "Δωρεά σε διακομιστή",
"with_playlist": "Κοινοποίηση με λίστα αναπαραγωγής",
"store_search_history": "Αποθήκευση ιστορικού αναζήτησης",
"share": "Κοινοποίηση",
"delete_automatically": "Αυτόματη διαγραφή μετά από",
"show_less": "Εμφάνιση λιγότερων",
"logout": "Αποσύνδεση από τη συγκεκριμένη συσκευή",
"reset_preferences": "Επαναφορά προτιμήσεων",
"download_frame": "Λήψη καρέ",
"create_group": "Δημιουργία γκρουπ",
"show_chapters": "Κεφάλαια",
"back_to_home": "Πίσω στην αρχική",
"skip_segment": "Παράλειψη τμήματος αναπαραγωγής",
"show_search_suggestions": "Εμφάνιση προτάσεων αναζήτησης",
"clone_playlist_success": "Αντιγράφηκε επιτυχώς!",
"instance_auth_selection": "Διακομιστής επιβεβαίωσης στοιχείων",
"documentation": "Έγγραφα/Οδηγοί",
"different_auth_instance": "Χρήση διαφορετικού διακομιστή για επιβεβαίωση στοιχείων",
"minimize_comments": "Ελαχιστοποίηση σχολίων",
"skip_automatically": "Αυτόματα",
"status_page": "Κατάσταση",
"show_markers": "Εμφάνισε τα στιγμιότυπα αναπαραγωγής",
"show_watch_on_youtube": "Εμφάνισε κουμπί \"Αναπαραγωγή στο YouTube\"",
"autoplay_next_countdown": "Προεπιλογή χρόνου αναμονής μέχρι το επόμενο βίντεο (σε δευτερόλεπτα)",
"generate_qrcode": "Δημιουργία QR Code",
"playlist_name": "Όνομα λίστας αναπαραγωγής",
"minimize_chapters_default": "Ελαχιστοποίηση τμημάτων αναπαραγωγής από προεπιλογή",
"instance_privacy_policy": "Πολιτική απορρήτου",
"minimize_recommendations_default": "Ελαχιστοποίηση προτάσεων από προεπιλογή",
"instances_not_shown": "Οι δημόσιοι πάροχοι σύνδεσης που δεν εμφανίζονται εδώ, ελιναι προσωρινά μη διαθέσιμοι.",
"no_valid_playlists": "Το αρχείο δεν περιέχει έγκυρες λίστες αναπαραγωγής!",
"min_segment_length": "Ελάχιστη διάρκεια τμήματος αναπαραγωγής (σε δευτερόλεπτα)",
"bookmark_playlist": "Σελιδοδείκτης",
"clone_playlist": "Αντιγραφή λίστας αναπαραγωγής",
"enable_dearrow": "Ενεργοποίηση De Arrow",
"auto_display_captions": "Αυτόματη εμφάνιση λεζαντών",
"with_timecode": "Κοινοποίηση με συγκεκριμένο σημείο αναπαραγωγής",
"time_code": "Χρονικό σημείο (σε δευτερόλεπτα)"
}, },
"titles": { "titles": {
"feed": "Τροφοδοσία", "feed": "Νεότερα",
"history": "Ιστορικό", "history": "Ιστορικό",
"preferences": "Ρυθμίσεις", "preferences": "Ρυθμίσεις",
"register": "Εγγραφή", "register": "Εγγραφή",
"login": "Σύνδεση", "login": "Σύνδεση",
"trending": "Τάσεις", "trending": "Τάσεις",
"subscriptions": "Συνδρομές", "subscriptions": "Συνδρομές",
"playlists": "Λίστες αναπαραγωγής" "playlists": "Λίστες αναπαραγωγής",
"livestreams": "Ζωντανές Ροές",
"channel_groups": "Γκρουπ Καναλιών",
"account": "Λογαριασμός",
"instance": "Διακομιστής",
"bookmarks": "Σελιδοδείκτες",
"channels": "Κανάλια",
"player": "Πρόγραμμα αναπαραγωγής",
"dearrow": "De Arrow"
}, },
"player": { "player": {
"watch_on": "Παρακολούθηση στο {0}" "watch_on": "Παρακολούθηση στο {0}",
"failed": "Κωδικός σφάλματος {0}, δες τα logs για περισσότερες πληροφορίες"
}, },
"video": { "video": {
"watched": "Προβλήθηκε", "watched": "Προβλήθηκε",
@ -87,7 +156,14 @@
"videos": "Βίντεο", "videos": "Βίντεο",
"live": "{0} Ζωντανή μετάδοση", "live": "{0} Ζωντανή μετάδοση",
"ratings_disabled": "Οι αξιολογήσεις είναι απενεργοποιημένες", "ratings_disabled": "Οι αξιολογήσεις είναι απενεργοποιημένες",
"chapters": "Κεφάλαια" "chapters": "Κεφάλαια",
"all": "Όλα",
"shorts": "Σύντομα βίντεο",
"visibility": "Εμφάνιση",
"license": "Άδεια",
"category": "Κατηγορία",
"chapters_vertical": "Κάθετα",
"chapters_horizontal": "Οριζόντια"
}, },
"preferences": { "preferences": {
"ssl_score": "SSL σκόρ", "ssl_score": "SSL σκόρ",
@ -99,11 +175,16 @@
"up_to_date": "Ενημερωμένο;" "up_to_date": "Ενημερωμένο;"
}, },
"comment": { "comment": {
"pinned_by": "Καρφιτσώθηκε από τον {author}" "pinned_by": "Καρφιτσώθηκε από τον {author}",
"loading": "Φόρτωση σχολίων...",
"user_disabled": "Τα σχόλια έχουν απενεργοποιηθεί στις ρυθμίσεις.",
"disabled": "Τα σχόλια έχουν απενεργοποιηθεί από το διαχειριστή του καναλιού."
}, },
"login": { "login": {
"password": "Κωδικός πρόσβασης", "password": "Κωδικός πρόσβασης",
"username": "Όνομα χρήστη" "username": "Όνομα χρήστη",
"password_confirm": "Επιβεβαίωση κωδικού",
"passwords_incorrect": "Οι κωδικοί δεν ταιριάζουν!"
}, },
"search": { "search": {
"did_you_mean": "Μήπως εννοείτε: {0};", "did_you_mean": "Μήπως εννοείτε: {0};",
@ -114,6 +195,25 @@
"music_songs": "YT Music: Τραγούδια", "music_songs": "YT Music: Τραγούδια",
"music_videos": "YT Music: Βίντεο", "music_videos": "YT Music: Βίντεο",
"channels": "YouTube: Κανάλια", "channels": "YouTube: Κανάλια",
"music_playlists": "YT Music: Λίστες αναπαραγωγής" "music_playlists": "YT Music: Λίστες αναπαραγωγής",
"music_artists": "YT Music: Καλλιτέχνες"
},
"info": {
"hours": "{amount} ώρα(ες)",
"next_video_countdown": "Αναπαραγωγή επόμενου βίντεο σε {0} δευτερόλεπτα",
"preferences_note": "Σημείωση: Οι προτιμήσεις σας έχουν αποθηκευτεί τοπικά στη μνήμη του browser. Διαγράφοντας τα δεδομένα του browser, σβήνετε και τις προτιμήσεις αυτές.",
"local_storage": "Η ενέργεια αυτή χρειάζεται τοπικό χώρο αποθήκευσης, έχετε ενεργοποιημένα τα cookies;",
"cannot_copy": "Αδυναμία αντιγραφής!",
"login_note": "Συνδεθείτε με λογαριασμό που έχει δημιουργθεί σε αυτό το διακομιστή.",
"months": "{amount} μήνα(ες)",
"page_not_found": "Η σελίδα δε βρέθηκε",
"weeks": "{amount} εβδομάδα(ες)",
"register_note": "Καταχωρήστε ένα λογαριασμό για το συγκεκριμένο Piped διακομιστή. Αυτό θα επιτρέψει να συγχρονίσετε τις συνδρομές και τις λίστες αναπαραγωγής σας με το λογαριασμό, ώστε να αποθηκευτούν στο server. Μπορείτε να χρησιμοποιήσετε όλα τα features χωρίς λογαριασμό, όμως τα δεδομένα θα αποθηκευτούν στην προσωρινή μνήμη του browser σας. Παρακαλώ βεβαιωθείτε ότι ΔΕΝ χρησιμοποιείτε email ως όνομα χρήστη και επιλέξτε έναν ασφαλή κωδικό τον οποίο δε χρησιμοποιείτε αλλού.",
"register_no_email_note": "Η χρήση e-mail για όνομα χρήστη δε συνίσταται. Θέλετε να προχωρήσετε;",
"days": "{amount} μέρα(ες)",
"copied": "Αντιγράφηκε!"
},
"subscriptions": {
"subscribed_channels_count": "Συνδρομή σε: {0}"
} }
} }

View File

@ -13,17 +13,19 @@
"player": "Player", "player": "Player",
"livestreams": "Livestreams", "livestreams": "Livestreams",
"channels": "Channels", "channels": "Channels",
"albums": "Albums",
"bookmarks": "Bookmarks", "bookmarks": "Bookmarks",
"channel_groups": "Channel groups", "channel_groups": "Channel groups",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"custom_instances": "Custom instances"
}, },
"player": { "player": {
"watch_on": "View on {0}", "watch_on": "View on {0}",
"failed": "Failed with error code {0}, see logs for more info" "failed": "Failed with error code {0}, see logs for more info"
}, },
"actions": { "actions": {
"subscribe": "Subscribe - {count}", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe - {count}", "unsubscribe": "Unsubscribe",
"view_subscriptions": "View Subscriptions", "view_subscriptions": "View Subscriptions",
"sort_by": "Sort by:", "sort_by": "Sort by:",
"most_recent": "Most Recent", "most_recent": "Most Recent",
@ -32,6 +34,7 @@
"channel_name_desc": "Channel Name (Z-A)", "channel_name_desc": "Channel Name (Z-A)",
"back": "Back", "back": "Back",
"uses_api_from": "Uses the API from ", "uses_api_from": "Uses the API from ",
"instances_not_shown": "Public instances that are not shown here are currently unavailable.",
"enable_sponsorblock": "Enable Sponsorblock", "enable_sponsorblock": "Enable Sponsorblock",
"skip_button_only": "Show skip button", "skip_button_only": "Show skip button",
"skip_automatically": "Automatically", "skip_automatically": "Automatically",
@ -98,6 +101,7 @@
"delete_playlist": "Delete", "delete_playlist": "Delete",
"select_playlist": "Select a Playlist", "select_playlist": "Select a Playlist",
"delete_playlist_confirm": "Delete this playlist?", "delete_playlist_confirm": "Delete this playlist?",
"delete_group_confirm": "Delete this group?",
"please_select_playlist": "Please select a playlist", "please_select_playlist": "Please select a playlist",
"delete_account": "Delete Account", "delete_account": "Delete Account",
"logout": "Logout from this device", "logout": "Logout from this device",
@ -147,7 +151,16 @@
"show_search_suggestions": "Show search suggestions", "show_search_suggestions": "Show search suggestions",
"delete_automatically": "Delete automatically after", "delete_automatically": "Delete automatically after",
"generate_qrcode": "Generate QR Code", "generate_qrcode": "Generate QR Code",
"download_frame": "Download frame" "download_frame": "Download frame",
"add_to_group": "Add to group",
"concurrent_prefetch_limit": "Concurrent Stream Prefetch Limit",
"customize": "Customize",
"invalid_url": "Invalid URL!",
"add": "Add",
"creator_replied": "Creator replied",
"creator_liked": "Creator liked",
"playback_speed": "Playback speed",
"invalid_input": "Invalid input"
}, },
"comment": { "comment": {
"pinned_by": "Pinned by {author}", "pinned_by": "Pinned by {author}",
@ -162,7 +175,9 @@
"registered_users": "Registered Users", "registered_users": "Registered Users",
"version": "Version", "version": "Version",
"up_to_date": "Up to date?", "up_to_date": "Up to date?",
"ssl_score": "SSL Score" "ssl_score": "SSL Score",
"uptime_30d": "Uptime (30d)",
"api_url": "Api URL"
}, },
"login": { "login": {
"username": "Username", "username": "Username",
@ -212,6 +227,8 @@
"hours": "{amount} hour(s)", "hours": "{amount} hour(s)",
"days": "{amount} day(s)", "days": "{amount} day(s)",
"weeks": "{amount} week(s)", "weeks": "{amount} week(s)",
"months": "{amount} month(s)" "months": "{amount} month(s)",
"register_note": "Register an account for this Piped instance. This will allow you to sync your subscriptions and playlists with your account, so they're stored on the server side. You can use all features without an account, but all data will be stored in your browser's local cache. Please make sure you do NOT use an email address as your username and choose a secure password that you do not use elsewhere.",
"login_note": "Log in with an account created on this instance."
} }
} }

View File

@ -10,7 +10,7 @@
"playlists": "Ludlistoj", "playlists": "Ludlistoj",
"account": "Konto", "account": "Konto",
"player": "Ludilo", "player": "Ludilo",
"instance": "Nodo", "instance": "Retejo",
"channels": "Kanaloj", "channels": "Kanaloj",
"livestreams": "Tujelsendoj", "livestreams": "Tujelsendoj",
"bookmarks": "Legosignoj", "bookmarks": "Legosignoj",
@ -22,8 +22,8 @@
"failed": "Fiaskis kun erarkodo {0}, vidu protokolojn por pli da informo" "failed": "Fiaskis kun erarkodo {0}, vidu protokolojn por pli da informo"
}, },
"actions": { "actions": {
"subscribe": "Aboni - {count}", "subscribe": "Aboni",
"unsubscribe": "Malaboni - {count}", "unsubscribe": "Malaboni",
"view_subscriptions": "Vidi Abonojn", "view_subscriptions": "Vidi Abonojn",
"sort_by": "Ordigi laŭ:", "sort_by": "Ordigi laŭ:",
"most_recent": "Plej Freŝaj", "most_recent": "Plej Freŝaj",
@ -81,11 +81,11 @@
"enable_lbry_proxy": "Ebligi Prokurilon por LBRY", "enable_lbry_proxy": "Ebligi Prokurilon por LBRY",
"import_from_json": "Importi el JSON", "import_from_json": "Importi el JSON",
"show_description": "Montri Priskribon", "show_description": "Montri Priskribon",
"instances_list": "Listo de Nodoj", "instances_list": "Listo de Piped-retejoj",
"auto_play_next_video": "Aŭtomate Ludi sekvan Videon", "auto_play_next_video": "Aŭtomate Ludi sekvan Videon",
"show_recommendations": "Montri Rekomendojn", "show_recommendations": "Montri Rekomendojn",
"reset_preferences": "Restarigi agordojn", "reset_preferences": "Restarigi agordojn",
"instance_selection": "Nodo", "instance_selection": "Retejo",
"view_ssl_score": "Vidu SSL-Poentaron", "view_ssl_score": "Vidu SSL-Poentaron",
"backup_preferences": "Savkopii agordojn", "backup_preferences": "Savkopii agordojn",
"disable_lbry": "Malebligi LBRY-n por Elsendfluo", "disable_lbry": "Malebligi LBRY-n por Elsendfluo",
@ -93,11 +93,11 @@
"store_search_history": "Konservi Ŝerĉhistorion", "store_search_history": "Konservi Ŝerĉhistorion",
"hide_watched": "Kaŝi viditajn videojn en la fluo", "hide_watched": "Kaŝi viditajn videojn en la fluo",
"minimize_recommendations": "Plejetigi Rekomendojn", "minimize_recommendations": "Plejetigi Rekomendojn",
"instance_auth_selection": "Aŭtentokontrola nodo", "instance_auth_selection": "Aŭtentokontrola Piped-retejo",
"restore_preferences": "Restarigi agordojn", "restore_preferences": "Restarigi agordojn",
"status_page": "Stato", "status_page": "Stato",
"please_select_playlist": "Bonvolu elekti ludliston", "please_select_playlist": "Bonvolu elekti ludliston",
"different_auth_instance": "Uzi alian nodon por aŭtentokontrolo", "different_auth_instance": "Uzi alian Piped-retejon por aŭtentokontrolo",
"back_to_home": "Ree hejmen", "back_to_home": "Ree hejmen",
"time_code": "Tempkodo (en sekundoj)", "time_code": "Tempkodo (en sekundoj)",
"skip_non_music": "Preterpasi Muzikon: Nemuzika Sekcio", "skip_non_music": "Preterpasi Muzikon: Nemuzika Sekcio",
@ -147,7 +147,8 @@
"generate_qrcode": "Generi QR-kodon", "generate_qrcode": "Generi QR-kodon",
"import_from_json_csv": "Importi el JSON/CSV", "import_from_json_csv": "Importi el JSON/CSV",
"download_frame": "Elŝuti bildon", "download_frame": "Elŝuti bildon",
"instance_privacy_policy": "Privateca politiko" "instance_privacy_policy": "Privateca politiko",
"add_to_group": "Aldoni al grupo"
}, },
"video": { "video": {
"chapters": "Sekcioj", "chapters": "Sekcioj",
@ -188,7 +189,9 @@
"hours": "{amount} horo(j)", "hours": "{amount} horo(j)",
"days": "{amount} tago(j)", "days": "{amount} tago(j)",
"weeks": "{amount} semajno(j)", "weeks": "{amount} semajno(j)",
"months": "{amount} monato(j)" "months": "{amount} monato(j)",
"login_note": "Ensaluti per konto kreita en ĉi tiu retejo.",
"register_note": "Registri konton por ĉi tiu Piped-retejo. Tio ebligos al vi sinkronigi viajn abonojn kaj ludlistojn kun via konto, do ili estos konservitaj en la servilo. Vi ankaŭ povas uzi ĉiujn funkciojn sen konto, sed ĉiuj datenoj estos konservitaj en la loka kaŝmemoro de via retumilo. Bonvolu elekti sekuran pasvorton, kiun vi ne uzas aliloke, kaj NE uzi retadreson kiel uzantnomon."
}, },
"login": { "login": {
"username": "Uzantnomo", "username": "Uzantnomo",

View File

@ -22,7 +22,9 @@
"instance_name": "Nombre de la instancia", "instance_name": "Nombre de la instancia",
"version": "Versión", "version": "Versión",
"up_to_date": "¿Actualizado?", "up_to_date": "¿Actualizado?",
"registered_users": "Usuarios Registrados" "registered_users": "Usuarios Registrados",
"uptime_30d": "Tiempo de actividad (30d)",
"api_url": "URL de la API"
}, },
"comment": { "comment": {
"pinned_by": "Fijado por {author}", "pinned_by": "Fijado por {author}",
@ -76,8 +78,8 @@
"most_recent": "Lo más reciente", "most_recent": "Lo más reciente",
"sort_by": "Ordenar por:", "sort_by": "Ordenar por:",
"view_subscriptions": "Ver suscripciones", "view_subscriptions": "Ver suscripciones",
"unsubscribe": "Anular suscripción - {count}", "unsubscribe": "Anular suscripción",
"subscribe": "Suscribirme - {count}", "subscribe": "Suscribirme",
"loading": "Cargando…", "loading": "Cargando…",
"filter": "Filtrar", "filter": "Filtrar",
"search": "Buscar (Ctrl+K)", "search": "Buscar (Ctrl+K)",
@ -156,7 +158,18 @@
"generate_qrcode": "Generar código QR", "generate_qrcode": "Generar código QR",
"import_from_json_csv": "Importar desde JSON/CSV", "import_from_json_csv": "Importar desde JSON/CSV",
"download_frame": "Descargar fotograma", "download_frame": "Descargar fotograma",
"instance_privacy_policy": "Política de privacidad" "instance_privacy_policy": "Política de privacidad",
"add_to_group": "Añadir a grupo",
"instances_not_shown": "Las instancias públicas que no se muestran aquí no están disponibles actualmente.",
"concurrent_prefetch_limit": "Límite de captación previa de transmisiones simultáneas",
"customize": "Personalizar",
"invalid_url": "¡URL no válida!",
"add": "Añadir",
"delete_group_confirm": "¿Eliminar este grupo?",
"creator_liked": "Al autor le gustó",
"creator_replied": "El autor respondió",
"playback_speed": "Velocidad de reproducción",
"invalid_input": "Entrada no válida"
}, },
"titles": { "titles": {
"feed": "Contenido", "feed": "Contenido",
@ -174,7 +187,9 @@
"channels": "Canales", "channels": "Canales",
"bookmarks": "Marcadores", "bookmarks": "Marcadores",
"channel_groups": "Grupos de canales", "channel_groups": "Grupos de canales",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Álbumes",
"custom_instances": "Instancias personalizadas"
}, },
"player": { "player": {
"watch_on": "Ver en {0}", "watch_on": "Ver en {0}",
@ -212,6 +227,8 @@
"hours": "{amount} hora(s)", "hours": "{amount} hora(s)",
"days": "{amount} día(s)", "days": "{amount} día(s)",
"weeks": "{amount} semana(s)", "weeks": "{amount} semana(s)",
"months": "{amount} mes(es)" "months": "{amount} mes(es)",
"login_note": "Inicia sesión con una cuenta creada en esta instancia.",
"register_note": "Registra una cuenta para esta instancia de Piped. Esto te permitirá sincronizar tus suscripciones y las listas de reproducción con tu cuenta, para que se almacenen en el servidor. Puedes utilizar todas las funciones sin una cuenta, pero todos los datos se almacenarán en la caché local de tu navegador. Asegúrate de NO utilizar una dirección de correo electrónico como nombre de usuario y elige una contraseña segura que no utilices en ningún otro sitio."
} }
} }

View File

@ -7,87 +7,169 @@
"register": "Registreeri", "register": "Registreeri",
"subscriptions": "Tellimused", "subscriptions": "Tellimused",
"history": "Ajalugu", "history": "Ajalugu",
"playlists": "Esitusloendid" "playlists": "Esitusloendid",
"account": "Kasutajakonto",
"player": "Meediamängija",
"instance": "Vaheserver",
"bookmarks": "Järjehoidjad",
"dearrow": "DeArrow",
"livestreams": "Otseetrid",
"channels": "Kanalid",
"channel_groups": "Kanalite grupid",
"albums": "Albumid",
"custom_instances": "Kohandatud vaheserverid"
}, },
"player": { "player": {
"watch_on": "Vaata saidil {0}" "watch_on": "Vaata {0} saidis",
"failed": "Tegevus ei õnnestunud, veakood {0}; lisateavet leiad logidest"
}, },
"actions": { "actions": {
"subscribe": "Telli - {count}", "subscribe": "Telli",
"unsubscribe": "Loobuda tellitud - {count}", "unsubscribe": "Lõpeta tellimus",
"view_subscriptions": "Vaata tellimusi", "view_subscriptions": "Vaata tellimusi",
"sort_by": "Sorteerimine:", "sort_by": "Sorteerimine:",
"most_recent": "Viimane", "most_recent": "Viimane",
"least_recent": "Kõige vähem hiljutine", "least_recent": "Kõige vanem",
"channel_name_asc": "Kanali nimi (A-Ü)", "channel_name_asc": "Kanali nimi (A-Ü)",
"channel_name_desc": "Kanali nimi (Ü-A)", "channel_name_desc": "Kanali nimi (Ü-A)",
"back": "Tagasi", "back": "Tagasi",
"uses_api_from": "Kasutab API-d ", "uses_api_from": "Kasutusel on API: ",
"enable_sponsorblock": "Sponsorblock lubamine", "enable_sponsorblock": "Luba Sponsorblock",
"skip_sponsors": "Sponsorite vahelejätmine", "skip_sponsors": "Jäta sponsorid vahele",
"skip_intro": "Vaheaja vahelejätmine/Intro animatsioon", "skip_intro": "Jäta vahe- ja intriklipid vahele",
"skip_outro": "Jäta lõppkaardid/krediidid vahele", "skip_outro": "Jäta lõppkaardid ja tunustused vahele",
"skip_preview": "Jäta eelvaade/kokkuvõte vahele", "skip_preview": "Jäta eelvaade või kokkuvõte vahele",
"skip_non_music": "Muusika vahelejätmine: Mitte-muusika sektsioon", "skip_non_music": "Muusika vahelejätmine: Mitte-muusika sektsioon",
"theme": "Teema", "theme": "Teema",
"default_quality": "Standardkvaliteet", "default_quality": "Standardkvaliteet",
"country_selection": "Riigi valimine", "country_selection": "Riik",
"default_homepage": "Vaikimisi koduleht", "default_homepage": "Vaikimisi koduleht",
"language_selection": "Kelle valik", "language_selection": "Keel",
"instances_list": "Intsidentide nimekiri", "instances_list": "Vaheserverite loend",
"show_more": "Näita rohkem", "show_more": "Näita rohkem",
"yes": "Jah", "yes": "Jah",
"no": "Ei", "no": "Ei",
"export_to_json": "Eksportida JSON-vormingusse", "export_to_json": "Ekspordi JSON-vormingusse",
"import_from_json": "Importimine JSON/CSVist", "import_from_json": "Impordi JSON'ist",
"loop_this_video": "Selle video pidev taasesitus", "loop_this_video": "Esita seda videot lõputult",
"donations": "Annetused", "donations": "Toeta arendust",
"minimize_description": "Minimeeri kirjeldus", "minimize_description": "Minimeeri kirjeldus",
"minimize_recommendations": "Soovituste minimeerimine", "minimize_recommendations": "Minimeeri soovitused",
"disable_lbry": "LBRY väljalülitamine voogedastuse jaoks", "disable_lbry": "Lülita LBRY voogedastuse puhul välja",
"search": "Otsige", "search": "Otsi (Ctrl+K)",
"filter": "Filtreeri järgi", "filter": "Filtreeri",
"loading": "Laadimine...", "loading": "Laadime...",
"clear_history": "Tühjendage ajalugu", "clear_history": "Tühjenda ajalugu",
"hide_replies": "Peida vastused", "hide_replies": "Peida vastused",
"load_more_replies": "Lae alla rohkem vastuseid", "load_more_replies": "Laadi alla rohkem vastuseid",
"add_to_playlist": "Lisa esitusloendisse", "add_to_playlist": "Lisa esitusloendisse",
"create_playlist": "Loo esitusloend", "create_playlist": "Loo esitusloend",
"delete_playlist": "Kustuta esitusloend", "delete_playlist": "Kustuta esitusloend",
"skip_highlight": "Jäta tipphetk vahele", "skip_highlight": "Jäta tipphetk vahele",
"skip_interaction": "Jäta interaktsiooni meeldetuletus vahele (Telli)", "skip_interaction": "Jäta interaktsiooni meeldetuletus vahele (Tellimine)",
"light": "Valgus", "light": "Hele",
"auto": "Automaatne", "auto": "Automaatne",
"skip_self_promo": "Jäta tasustamata/enesereklaami vahele", "skip_self_promo": "Jäta tasustamata või enesereklaam vahele",
"skip_filler_tangent": "Jäta filler'i vahelejätmine", "skip_filler_tangent": "Jäta filler'i vahele",
"dark": "Tume", "dark": "Tume",
"autoplay_video": "Mängi videot automaatselt", "autoplay_video": "Mängi videot automaatselt",
"buffering_goal": "Puhverdamise eesmärk (sekundites)", "buffering_goal": "Puhverdamise kestus (sekundites)",
"show_comments": "Näita kommentaare", "show_comments": "Näita kommentaare",
"audio_only": "Ainult heli", "audio_only": "Ainult heli",
"store_watch_history": "Salvesta oma vaatamise ajalugu", "store_watch_history": "Salvesta oma vaatamise ajalugu",
"instance_selection": "Instantsi valik", "instance_selection": "Vaheserver",
"show_recommendations": "Näita soovitusi", "show_recommendations": "Näita soovitusi",
"minimize_description_default": "Vähenda kirjeldust vaikimisi", "minimize_description_default": "Vaikimisi minimeeri kirjeldus",
"enabled_codecs": "Kasutatavad koodekid (mitu)", "enabled_codecs": "Kasutatavad koodekid (mitu)",
"auto_play_next_video": "Järgmise video automaatne esitamine", "auto_play_next_video": "Esita järgmist videot automaatselt",
"view_ssl_score": "Näita SSL skoore", "view_ssl_score": "Vaata SSL-punkte",
"enable_lbry_proxy": "LBRY proxy-serveri aktiveerimine", "enable_lbry_proxy": "Kasuta LBRY puhul puhverserverit",
"show_description": "Näita kirjeldust", "show_description": "Näita kirjeldust",
"delete_playlist_confirm": "Kas olete kindel, et soovite selle esitusloendi kustutada?", "delete_playlist_confirm": "Kas soovid selle esitusloendi kustutada?",
"remove_from_playlist": "Eemalda esitusloendist", "remove_from_playlist": "Eemalda esitusloendist",
"delete_playlist_video_confirm": "Kas olete kindel, et soovite selle video sellest esitusloendist eemaldada?", "delete_playlist_video_confirm": "Kas soovid selle video sellest esitusloendist eemaldada?",
"select_playlist": "Valige esitusloend", "select_playlist": "Vali esitusloend",
"please_select_playlist": "Valige esitusloend" "please_select_playlist": "Palun vali esitusloend",
"playlist_name": "Esitusloendi nimi",
"with_timecode": "Jaga koos ajahetkega",
"show_chapters": "Peatükid",
"status_page": "Olek",
"instance_donations": "Toeta vaheserverite haldajaid",
"with_playlist": "Jaga koos esitusloendiga",
"playlist_bookmarked": "Lisatud järjehoidjaks",
"dismiss": "Katkesta",
"create_group": "Loo grupp",
"okay": "Sobib",
"show_search_suggestions": "Näita otsingusoovitusi",
"instance_auth_selection": "Vaheserver autentimiseks",
"store_search_history": "Salvesta otsinguajalugu",
"hide_watched": "Peida vaadatud videod meediavoost",
"logout": "Logi sellest seadmest välja",
"show_less": "Näita vähem",
"delete_automatically": "Kustuta automaatselt peale",
"minimize_chapters_default": "Vaikimisi kuva peatükid minimeerituna",
"reset_preferences": "Lähtesta eelistused",
"concurrent_prefetch_limit": "Samaaegse meediavoo eellaadimise piir",
"cancel": "Katkesta",
"generate_qrcode": "Loo QR-kood",
"import_from_json_csv": "Impordi JSON- või CSV-failist",
"confirm_reset_preferences": "Kas sa oled kindel, et soovid eelistused lähtestada?",
"backup_preferences": "Varunduse eelistused",
"back_to_home": "Tagasi avalehele",
"playlist_description": "Esitusloendi kirjeldus",
"share": "Jaga",
"source_code": "Lähtekood",
"no_valid_playlists": "Selles failis ei leidu korrektseid esitusloendeid!",
"group_name": "Grupi nimi",
"minimize_comments": "Minimeeri kommentaarid",
"auto_display_captions": "Kuva tiitrid automaatselt",
"delete_account": "Kustuta konto",
"invalidate_session": "Logi kõikidest seadmetest välja",
"clone_playlist_success": "Koopia tegemine õnnestus!",
"instances_not_shown": "Kui avalik vaheserver pole siin kuvatud, siis pole ta hetkel kasutatav.",
"skip_button_only": "Näita nuppu „Jäta vahele“",
"skip_automatically": "Automatselt",
"show_markers": "Näita meediamängijas markereid",
"min_segment_length": "Väikseim segmendi pikkus (sekundites)",
"skip_segment": "Jäta segment vahele",
"enable_dearrow": "Pisipiltide kuvamiseks kasuta DeArrow teenust",
"autoplay_next_countdown": "Vaikimisi viivitus järgmise video esitamiseni (sekundites)",
"minimize_comments_default": "Vaikimisi minimeeri kommentaarid",
"minimize_recommendations_default": "Vaikimisi kuva soovitused minimeerituna",
"chapters_layout_mobile": "Peatükkide paigutus telefonivaates",
"show_watch_on_youtube": "Näita „Vaata YouTube's“ nuppu",
"different_auth_instance": "Kasuta autentimiseks teist vaheserverit",
"clone_playlist": "Tee esitusloendist koopia",
"download_as_txt": "Laadi alla txt-failina",
"restore_preferences": "Taasta eelistused",
"edit_playlist": "Muuda esitusloendit",
"follow_link": "Ava link",
"piped_link": "Link Piped'i saiti",
"copy_link": "Kopeeri link",
"time_code": "Ajahetk esituses (sekundites)",
"documentation": "Dokumentatsioon",
"reply_count": "{count} vastust",
"bookmark_playlist": "Järjehoidja",
"download_frame": "Laadi alla kaader",
"add_to_group": "Lisa gruppi",
"instance_privacy_policy": "Privaatsuspoliitika",
"customize": "Kohanda",
"invalid_url": "Vigane URL!",
"add": "Lisa",
"delete_group_confirm": "Kas kustutame selle grupi?",
"creator_replied": "Autor vastas",
"creator_liked": "Autorile meeldis see"
}, },
"preferences": { "preferences": {
"has_cdn": "Kas on CDN?", "has_cdn": "CDN'i olek?",
"ssl_score": "SSL-punktid", "ssl_score": "SSL-punktid",
"registered_users": "Registreeritud kasutajad", "registered_users": "Registreeritud kasutajad",
"up_to_date": "Ajakohastatud?", "up_to_date": "Kõik on ajakohane?",
"instance_name": "Instantsi nimi", "instance_name": "Vaheserveri nimi",
"instance_locations": "Instantsi asukoht", "instance_locations": "Vaheserveri asukohad",
"version": "Versioon" "version": "Versioon",
"uptime_30d": "Tõrgeteta tööaeg (30p)",
"api_url": "API URL"
}, },
"video": { "video": {
"videos": "Videod", "videos": "Videod",
@ -96,7 +178,14 @@
"sponsor_segments": "Sponsorite segmendid", "sponsor_segments": "Sponsorite segmendid",
"chapters": "Peatükid", "chapters": "Peatükid",
"live": "{0} Otseülekanne", "live": "{0} Otseülekanne",
"ratings_disabled": "Hinnangud välja lülitatud" "ratings_disabled": "Hinnangud välja lülitatud",
"chapters_vertical": "Püstloodis",
"chapters_horizontal": "Rõhtloodis",
"shorts": "Lühivideod",
"all": "Kõik",
"category": "Kategooria",
"license": "Litsents",
"visibility": "Nähtavus"
}, },
"search": { "search": {
"did_you_mean": "Kas sa mõtlesid: {0}?", "did_you_mean": "Kas sa mõtlesid: {0}?",
@ -107,13 +196,37 @@
"music_songs": "YT Music: Laulud", "music_songs": "YT Music: Laulud",
"music_videos": "YT Music: Videod", "music_videos": "YT Music: Videod",
"music_albums": "YT Music: Albumid", "music_albums": "YT Music: Albumid",
"music_playlists": "YT Music: Esitusloendid" "music_playlists": "YT Music: Esitusloendid",
"music_artists": "YT Music: Esitajad"
}, },
"login": { "login": {
"username": "Kasutajanimi", "username": "Kasutajanimi",
"password": "Parool" "password": "Parool",
"password_confirm": "Kinnita salasõna õigsust",
"passwords_incorrect": "Salasõnad ei klapi omavahel!"
}, },
"comment": { "comment": {
"pinned_by": "Kinnitanud {author}" "pinned_by": "Esile tõstnud: {author}",
"loading": "Laadime kommentaare...",
"user_disabled": "Kommentaarid on seadistusest välja lülitatud.",
"disabled": "Video üleslaadija ei luba kommentaare."
},
"subscriptions": {
"subscribed_channels_count": "Tellitud kanaleid: {0}"
},
"info": {
"page_not_found": "Veebilehte ei leidu",
"copied": "Kopeeritud!",
"cannot_copy": "Kopeerimine ei õnnestu!",
"local_storage": "Selle tegevuse jaoks on oluline brauseri localStorage kasutamine. Kas küpsiste salvestamine on lubatud?",
"next_video_countdown": "Järgmise video esitamiseni {0}s",
"hours": "{amount} tund(i)",
"days": "{amount} päev(a)",
"weeks": "{amount} nädal(at)",
"months": "{amount} kuu(d)",
"login_note": "Logi sisse kontoga, mis on loodud selles vaheserveris.",
"register_note": "Registreeri konto selles Piped'i vaheserveris. Nii tehes on sul võimalik sünkroniseerida oma tellimused ja esitusloendid oma kontole ja sellega neid serveris salvestada. Vastasel juhul saad sa kasutada selle veebisaidi terviklikku funktsionaalsust, kuid andmeid hoitakse vaid selle veebibrauseri andmehoidlas. Konto loomisel palun ära kasuta oma e-postiaadressi kasutajanimena ning loo turvaline salasõna, mida sa mujal ei kasuta.",
"preferences_note": "Märkus: eelistused on salvestatud sinu veebibrauseri kohalikus andmekogus. Baruseris salvestatud andmete kustutamisel kõik seadistused lähtestuvad.",
"register_no_email_note": "Meie ei soovita kasutajanimeks e-posti aadressi. Kas ikkagi jätkame?"
} }
} }

View File

@ -60,8 +60,8 @@
"most_recent": "Berriena", "most_recent": "Berriena",
"sort_by": "Ordenatu honen arabera:", "sort_by": "Ordenatu honen arabera:",
"view_subscriptions": "Harpidetzak ikusi", "view_subscriptions": "Harpidetzak ikusi",
"unsubscribe": "Kendu harpidetza - {count}", "unsubscribe": "Kendu harpidetza",
"subscribe": "Harpidetu - {count}", "subscribe": "Harpidetu",
"loading": "Kargatzen...", "loading": "Kargatzen...",
"filter": "Iragazi", "filter": "Iragazi",
"search": "Bilatu", "search": "Bilatu",

View File

@ -24,12 +24,12 @@
"most_recent": "تازه‌ترین‌ها", "most_recent": "تازه‌ترین‌ها",
"sort_by": "مرتب سازی بر اساس:", "sort_by": "مرتب سازی بر اساس:",
"view_subscriptions": "مشاهده سابسکرایب ها", "view_subscriptions": "مشاهده سابسکرایب ها",
"unsubscribe": "لغو سابسکرایب - {count}", "unsubscribe": "لغو سابسکرایب",
"subscribe": "سابسکرایب - {count}", "subscribe": "سابسکرایب",
"minimize_recommendations": "بستن توصیه ها", "minimize_recommendations": "بستن توصیه ها",
"show_recommendations": "نمایش توصیه ها", "show_recommendations": "نمایش توصیه ها",
"uses_api_from": "با استفاده از APIای از ", "uses_api_from": "با استفاده از APIای از ",
"instance_selection": "انتخاب نمونه", "instance_selection": "انتخاب سرویس دهنده",
"show_more": "نمایش بیشتر", "show_more": "نمایش بیشتر",
"yes": "بله", "yes": "بله",
"no": "خیر", "no": "خیر",
@ -42,7 +42,7 @@
"skip_interaction": "رد کردن یاداوری سابسکرایب", "skip_interaction": "رد کردن یاداوری سابسکرایب",
"skip_highlight": "رد کردن نکات برجسته", "skip_highlight": "رد کردن نکات برجسته",
"skip_filler_tangent": "رد کردن چرندیات نامربوط", "skip_filler_tangent": "رد کردن چرندیات نامربوط",
"import_from_json": "وارد کردن از JSON/CSV", "import_from_json": "وارد کردن از JSON",
"export_to_json": "دادن خروجی با فرمت JSON", "export_to_json": "دادن خروجی با فرمت JSON",
"show_description": "نمایش توضیحات ویدئو", "show_description": "نمایش توضیحات ویدئو",
"donations": "کمک های مالی برای توسعه", "donations": "کمک های مالی برای توسعه",
@ -68,15 +68,67 @@
"logout": "خروج از این دستگاه", "logout": "خروج از این دستگاه",
"delete_playlist_video_confirm": "ویدیو از لیست پخش حذف شود؟", "delete_playlist_video_confirm": "ویدیو از لیست پخش حذف شود؟",
"autoplay_next_countdown": "پیش‌فرض شمارش‌معکوس پخش ویدیوی بعدی ( به ثانیه )", "autoplay_next_countdown": "پیش‌فرض شمارش‌معکوس پخش ویدیوی بعدی ( به ثانیه )",
"please_select_playlist": "انتخاب لیست پخش", "please_select_playlist": "لطفا فهرست پخش را انتخاب کنید",
"minimize_comments_default": "کوچک کردن کامنت‌ها به صورت پیش فرض", "minimize_comments_default": "کوچک کردن کامنت‌ها به صورت پیش فرض",
"minimize_comments": "کوچک کردن کامنت (نظرات)", "minimize_comments": "کوچک کردن کامنت (نظرات)",
"remove_from_playlist": "حذف از لیست پخش", "remove_from_playlist": "حذف از لیست پخش",
"skip_button_only": "دکمه‌ی گذر را نشان بده", "skip_button_only": "دکمه‌ی گذر را نشان بده",
"skip_automatically": "خودکار", "skip_automatically": "خودکار",
"skip_segment": "گذر از بخش", "skip_segment": "گذر از بخش",
"minimize_recommendations_default": "به طور پیش فرض پیشنهادها را حداقل کنید.", "minimize_recommendations_default": "به طور پیش فرض پیشنهادها را حداقل کنید",
"min_segment_length": "حداقل طول بخش (به ثانیه)" "min_segment_length": "حداقل طول بخش (به ثانیه)",
"okay": "تایید",
"edit_playlist": "ویرایش فهرست‌پخش",
"group_name": "نام گروه",
"invalidate_session": "خروج از همهٔ دستگاه‌ها",
"add_to_group": "افزودن به گروه",
"confirm_reset_preferences": "از بازنشانی تنظیمات اطمینان دارید؟",
"reply_count": "{count} پاسخ",
"restore_preferences": "بازگرداندن تنظیمات به حالت پیش",
"piped_link": "پیوند Piped",
"import_from_json_csv": "وارد کردن از JSON/CSV",
"cancel": "لغو",
"enable_dearrow": "فعال کردن DeArrow",
"playlist_description": "شرح فهرست‌پخش",
"download_as_txt": "دریافت به صورت .txt",
"with_timecode": "اشتراک‌گذاری با مهر زمانی",
"source_code": "کد منبع",
"copy_link": "نسخه‌برداری از پیوند",
"dismiss": "رد کردن",
"hide_watched": "ویدیوهای تماشا شده را مخفی کنی",
"time_code": "مهر زمانی (به ثانیه)",
"chapters_layout_mobile": "جیدمان فصل‌های ویدیو روی موبایل",
"backup_preferences": "پشتیبان گیری از تنظیمات",
"instance_donations": "کمک مالی به سرویس دهنده",
"with_playlist": "اشتراک‌گذاری با فهرست‌پخش",
"store_search_history": "ذخیره کردن سابقهٔ جست و جو",
"share": "اشتراک‌گذاری",
"delete_automatically": "حذف خودکار پس از",
"show_less": "کمتر نشان بده",
"follow_link": "دنبال کردن پیوند",
"reset_preferences": "بازنشانی تنظیمات",
"download_frame": "دریافت قاب",
"create_group": "ساخت گروه",
"show_chapters": "فصل‌های ویدیو",
"back_to_home": "بازگشت به صفحهٔ اصلی",
"show_search_suggestions": "نمایش پیشنهادهای جست و جو",
"clone_playlist_success": "با موفقیت تکثیر شد!",
"instance_auth_selection": "سرویس‌دهندهٔ احراز هویت",
"documentation": "اسناد",
"different_auth_instance": "از سرویس‌دهندهٔ دیگری برای احراز هویت استفاده کنید",
"auto_display_captions": "نمایش خودکار زیرنویس",
"status_page": "وضعیت",
"show_watch_on_youtube": "نمایش دکمهٔ تماشا روی YouTube",
"generate_qrcode": "ساخت کد QR",
"playlist_name": "نام فهرست‌پخش",
"minimize_chapters_default": "کوچک کردن فصل‌های ویدیو به صورت پیش فرض",
"instance_privacy_policy": "سیاست حفظ حریم خصوصی",
"no_valid_playlists": "داخل فایل فهرست‌پخش معتبری وجود ندارد!",
"clone_playlist": "تکثیر فهرست‌پخش",
"playlist_bookmarked": "نشان‌دار شد",
"instances_not_shown": "سرویس‌دهنده‌های عمومی که این جا نشان داده نشده‌اند در حال حاضر در دسترس نیستند.",
"bookmark_playlist": "نشان‌دار",
"concurrent_prefetch_limit": "محدودیت پیش‌دریافت همزمان استریم"
}, },
"titles": { "titles": {
"history": "تاریخچه", "history": "تاریخچه",
@ -93,13 +145,24 @@
"bookmarks": "نشان‌دارها", "bookmarks": "نشان‌دارها",
"livestreams": "پخش زنده", "livestreams": "پخش زنده",
"channels": "کانال‌ها", "channels": "کانال‌ها",
"channel_groups": "گروه های کانال" "channel_groups": "گروه های کانال",
"dearrow": "DeArrow"
}, },
"player": { "player": {
"watch_on": "تماشا روی {0}" "watch_on": "تماشا روی {0}",
"failed": "عدم موفقیت با خطای {0}، برای اطلاعات بیشتر logها رو بررسی کنید"
}, },
"search": { "search": {
"did_you_mean": "منظورتان {0} بود؟" "did_you_mean": "منظورتان {0} بود؟",
"all": "YouTube: همه",
"music_artists": "YT Music: هنرمندان",
"channels": "YouTube: کانال‌ها",
"music_albums": "YT Music: آلبوم‌ها",
"music_videos": "YT Music: ویدیوها",
"music_playlists": "YT Music: فهرست‌های پخش",
"playlists": "YouTube: فهرست‌های پخش",
"music_songs": "YT Music: آوازها",
"videos": "YouTube: ویدیوها"
}, },
"video": { "video": {
"views": "{views} بازدید ها", "views": "{views} بازدید ها",
@ -107,7 +170,15 @@
"watched": "دیده شده", "watched": "دیده شده",
"sponsor_segments": "قسمت های اسپانسری", "sponsor_segments": "قسمت های اسپانسری",
"ratings_disabled": "رتبه بندی غیرفعال", "ratings_disabled": "رتبه بندی غیرفعال",
"chapters": "فصل ها" "chapters": "فصل ها",
"all": "همه",
"live": "{0} زنده",
"shorts": "Shortها",
"license": "مجوز",
"category": "دسته‌بندی",
"chapters_vertical": "عمودی",
"chapters_horizontal": "افقی",
"visibility": "قابل مشاهده"
}, },
"preferences": { "preferences": {
"instance_locations": "محل های سرویس", "instance_locations": "محل های سرویس",
@ -115,13 +186,38 @@
"ssl_score": "امتیاز SSL", "ssl_score": "امتیاز SSL",
"instance_name": "نام سرویس", "instance_name": "نام سرویس",
"version": "نسخه", "version": "نسخه",
"registered_users": "کاربران تایید شده" "registered_users": "کاربران تایید شده",
"up_to_date": "به روز؟",
"uptime_30d": "Uptime (30d)"
}, },
"comment": { "comment": {
"pinned_by": "سنجاق شده توسط {author}" "pinned_by": "سنجاق شده توسط {author}",
"loading": "دریافت نظرها…",
"user_disabled": "نظرها در تنظیمات غیرفعال شده‌اند.",
"disabled": "ارسال کننده، نظرها را غیرفعال کرده است."
}, },
"login": { "login": {
"username": "نام کاربری", "username": "نام کاربری",
"password": "رمز" "password": "رمز",
"password_confirm": "تایید رمز عبور",
"passwords_incorrect": "رمزهای عبور یکسان نیستند!"
},
"info": {
"hours": "{amount} ساعت",
"next_video_countdown": "پخش ویدیوی بعدی در {0} ثانیه",
"preferences_note": "توجه: تنظیمات در حافظهٔ داخلی مرورگر شما ذخیره می‌شوند. حذف اطلاعات مرورگر، تنظیمات را بازنشانی خواهد کرد.",
"local_storage": "این گزینه نیاز به localStorage دارد، آیا cookieها فعالند؟",
"cannot_copy": "نمی‌توان نسخه‌برداری کرد!",
"login_note": "با حساب کاربری ساخته شده در این سرویس دهنده وارد شوید.",
"months": "{amount} ماه",
"page_not_found": "صفحه پیدا نشد",
"weeks": "{amount} هفته",
"register_note": "در این سرویس دهندهٔ Piped حساب کاربری ایجاد کنید. به شما اجازه خواهد داد که اشتراک‌ها و فهرست‌های پخش را با حساب کاربری در سرویس‌دهنده همسان‌سازی کنید. می‌توانید از همهٔ امکانات بدون حساب کاربری استفاده کنید ولی تمام اطلاعات در حافظهٔ داخلی مرورگرتان ذخیره خواهد شد. لطفا مطمئن شوید که از ایمیل به جای نام کاربری استفاده نمی‌کنید و رمز عبور امنی انتخاب کنید که جای دیگری استفاده نشده است.",
"register_no_email_note": "استفاده از email به جای نام کاربری توصیه نمی‌شود. ادامه؟",
"days": "{amount} روز",
"copied": "نسخه‌برداری شد!"
},
"subscriptions": {
"subscribed_channels_count": "مشترک شده: {0}"
} }
} }

View File

@ -59,8 +59,8 @@
"most_recent": "Viimeisin", "most_recent": "Viimeisin",
"sort_by": "Järjestä:", "sort_by": "Järjestä:",
"view_subscriptions": "Näytä tilaukset", "view_subscriptions": "Näytä tilaukset",
"unsubscribe": "Poistu tilauksesta - {count}", "unsubscribe": "Poistu tilauksesta",
"subscribe": "Tilaa - {count}", "subscribe": "Tilaa",
"minimize_recommendations": "Minimoi suositukset", "minimize_recommendations": "Minimoi suositukset",
"show_recommendations": "Näytä suositukset", "show_recommendations": "Näytä suositukset",
"hide_replies": "Piilota vastaukset", "hide_replies": "Piilota vastaukset",

View File

@ -14,11 +14,13 @@
"livestreams": "Diffusions en direct", "livestreams": "Diffusions en direct",
"channels": "Chaînes", "channels": "Chaînes",
"bookmarks": "Marque-pages", "bookmarks": "Marque-pages",
"channel_groups": "Groupes de chaînes" "channel_groups": "Groupes de chaînes",
"dearrow": "DeArrow",
"albums": "Albums"
}, },
"actions": { "actions": {
"subscribe": "S'abonner - {count}", "subscribe": "S'abonner",
"unsubscribe": "Se désabonner - {count}", "unsubscribe": "Se désabonner",
"buffering_goal": "Objectif de mise en mémoire tampon (en secondes)", "buffering_goal": "Objectif de mise en mémoire tampon (en secondes)",
"skip_non_music": "Ignorer la musique : section non musicale", "skip_non_music": "Ignorer la musique : section non musicale",
"skip_self_promo": "Ignorer la promotion non rémunérée / l'autopromotion", "skip_self_promo": "Ignorer la promotion non rémunérée / l'autopromotion",
@ -28,12 +30,12 @@
"skip_intro": "Ignorer l'animation de l'entracte/Intro", "skip_intro": "Ignorer l'animation de l'entracte/Intro",
"skip_sponsors": "Ignorer les sponsors", "skip_sponsors": "Ignorer les sponsors",
"instances_list": "Liste des instances", "instances_list": "Liste des instances",
"language_selection": "Sélection de la langue", "language_selection": "Langue",
"store_watch_history": "Conserver l'historique de visionnage", "store_watch_history": "Conserver l'historique de visionnage",
"minimize_description_default": "Minimiser la description par défaut", "minimize_description_default": "Minimiser la description par défaut",
"show_comments": "Afficher les commentaires", "show_comments": "Afficher les commentaires",
"default_homepage": "Page d'accueil par défaut", "default_homepage": "Page d'accueil par défaut",
"country_selection": "Sélection du pays", "country_selection": "Pays",
"autoplay_video": "Lire automatiquement la vidéo", "autoplay_video": "Lire automatiquement la vidéo",
"light": "Clair", "light": "Clair",
"dark": "Sombre", "dark": "Sombre",
@ -46,7 +48,7 @@
"view_subscriptions": "Voir les abonnements", "view_subscriptions": "Voir les abonnements",
"back": "Retour", "back": "Retour",
"uses_api_from": "Utilise l'API de ", "uses_api_from": "Utilise l'API de ",
"sort_by": "Trier par :", "sort_by": "Trier par :",
"channel_name_asc": "Nom de la chaîne (A-Z)", "channel_name_asc": "Nom de la chaîne (A-Z)",
"least_recent": "Le moins récent", "least_recent": "Le moins récent",
"most_recent": "Le plus récent", "most_recent": "Le plus récent",
@ -57,10 +59,10 @@
"donations": "Dons pour le développement", "donations": "Dons pour le développement",
"auto_play_next_video": "Lire la vidéo suivante automatiquement", "auto_play_next_video": "Lire la vidéo suivante automatiquement",
"loop_this_video": "Mettre cette vidéo en boucle", "loop_this_video": "Mettre cette vidéo en boucle",
"import_from_json": "Importer depuis le format JSON/CSV", "import_from_json": "Importer depuis le format JSON",
"export_to_json": "Exporter en JSON", "export_to_json": "Exporter en JSON",
"show_more": "Afficher plus", "show_more": "Afficher plus",
"instance_selection": "Sélection de l'instance", "instance_selection": "Instance",
"minimize_description": "Minimiser la description", "minimize_description": "Minimiser la description",
"minimize_recommendations": "Minimiser les recommandations", "minimize_recommendations": "Minimiser les recommandations",
"show_recommendations": "Afficher les recommandations", "show_recommendations": "Afficher les recommandations",
@ -89,7 +91,7 @@
"minimize_recommendations_default": "Minimiser les recommandations par défaut", "minimize_recommendations_default": "Minimiser les recommandations par défaut",
"invalidate_session": "Se déconnecter de tous les appareils", "invalidate_session": "Se déconnecter de tous les appareils",
"different_auth_instance": "Utiliser une instance différente pour l'authentification", "different_auth_instance": "Utiliser une instance différente pour l'authentification",
"instance_auth_selection": "Sélection de l'instance d'authentification", "instance_auth_selection": "Instance d'authentification",
"clone_playlist": "Cloner la liste de lecture", "clone_playlist": "Cloner la liste de lecture",
"clone_playlist_success": "Clonage réussi !", "clone_playlist_success": "Clonage réussi !",
"download_as_txt": "Télécharger en tant que .txt", "download_as_txt": "Télécharger en tant que .txt",
@ -125,7 +127,7 @@
"min_segment_length": "Longueur minimale du segment (en secondes)", "min_segment_length": "Longueur minimale du segment (en secondes)",
"skip_segment": "Sauter le segment", "skip_segment": "Sauter le segment",
"show_less": "Afficher moins", "show_less": "Afficher moins",
"okay": "OK", "okay": "Valider",
"edit_playlist": "Éditer la liste de lecture", "edit_playlist": "Éditer la liste de lecture",
"playlist_name": "Nom de la liste de lecture", "playlist_name": "Nom de la liste de lecture",
"auto_display_captions": "Afficher sous-titres automatiquement", "auto_display_captions": "Afficher sous-titres automatiquement",
@ -136,10 +138,20 @@
"group_name": "Nom du groupe", "group_name": "Nom du groupe",
"autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)", "autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)",
"chapters_layout_mobile": "Format des chapitres sur mobile", "chapters_layout_mobile": "Format des chapitres sur mobile",
"show_search_suggestions": "Afficher les suggestions de recherche" "show_search_suggestions": "Afficher les suggestions de recherche",
"add_to_group": "Ajouter au groupe",
"import_from_json_csv": "Importer depuis le format JSON/CSV",
"enable_dearrow": "Activer DeArrow",
"delete_automatically": "Supprimer automatiquement après",
"download_frame": "Télécharger la miniature",
"generate_qrcode": "Générer un QR code",
"instance_privacy_policy": "Politique de confidentialité",
"concurrent_prefetch_limit": "Limite de préchargements simultanés de flux",
"instances_not_shown": "Les instances publiques qui ne sont pas affichées ici sont actuellement indisponibles."
}, },
"player": { "player": {
"watch_on": "Regarder sur {0}" "watch_on": "Voir sur {0}",
"failed": "Échec avec le code erreur {0}, voir les logs pour plus d'informations"
}, },
"video": { "video": {
"sponsor_segments": "Segments de sponsors", "sponsor_segments": "Segments de sponsors",
@ -153,16 +165,19 @@
"all": "Tout", "all": "Tout",
"category": "Catégorie", "category": "Catégorie",
"chapters_horizontal": "Horizontal", "chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical" "chapters_vertical": "Vertical",
"visibility": "Visibilité",
"license": "Licence"
}, },
"preferences": { "preferences": {
"ssl_score": "Score SSL", "ssl_score": "Score SSL",
"has_cdn": "A un RDC ?", "has_cdn": "A un CDN ?",
"instance_locations": "Localisations de l'instance", "instance_locations": "Localisations de l'instance",
"instance_name": "Nom de l'instance", "instance_name": "Nom de l'instance",
"up_to_date": "À jour ?", "up_to_date": "À jour ?",
"registered_users": "Utilisateurs enregistrés", "registered_users": "Utilisateurs enregistrés",
"version": "Version" "version": "Version",
"uptime_30d": "Uptime (30j)"
}, },
"comment": { "comment": {
"pinned_by": "Épinglé par {author}", "pinned_by": "Épinglé par {author}",
@ -172,7 +187,9 @@
}, },
"login": { "login": {
"password": "Mot de passe", "password": "Mot de passe",
"username": "Nom d'utilisateur" "username": "Nom d'utilisateur",
"password_confirm": "Confirmez le mot de passe",
"passwords_incorrect": "Les mots de passe ne correspondent pas !"
}, },
"search": { "search": {
"did_you_mean": "Vouliez-vous dire : {0} ?", "did_you_mean": "Vouliez-vous dire : {0} ?",
@ -183,7 +200,8 @@
"music_songs": "YT Music : Chansons", "music_songs": "YT Music : Chansons",
"music_videos": "YT Music : Vidéos", "music_videos": "YT Music : Vidéos",
"music_playlists": "YT Music : Listes de lecture", "music_playlists": "YT Music : Listes de lecture",
"music_albums": "YT Music : Albums" "music_albums": "YT Music : Albums",
"music_artists": "YT Music : Artistes"
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "Abonné à : {0}" "subscribed_channels_count": "Abonné à : {0}"
@ -198,6 +216,12 @@
"cannot_copy": "Impossible de copier !", "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 ?", "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" "next_video_countdown": "Lecture de la prochaine vidéo dans {0}s",
"hours": "{amount} heure(s)",
"login_note": "Se connecter avec un compte créé sur cette instance.",
"months": "{amount} mois",
"weeks": "{amount} semaine(s)",
"register_note": "Inscrivez-vous pour ce compte sur cette instance de Piped. Cela vous permettra de synchroniser vos abonnements et listes de lecture avec votre compte, afin qu'ils soient stockés côté serveur. Vous pouvez utiliser toutes les fonctionnalités sans avoir de compte, mais toutes les données seront stockées dans le cache local de votre navigateur. Assurez-vous de NE PAS utiliser une adresse e-mail comme nom d'utilisateur et choisissez un mot de passe sécurisé que vous n'utilisez pas ailleurs.",
"days": "{amount} jour(s)"
} }
} }

View File

@ -21,12 +21,12 @@
"failed": "Fallou con código do erro {0}, mira o rexistro para máis info" "failed": "Fallou con código do erro {0}, mira o rexistro para máis info"
}, },
"actions": { "actions": {
"subscribe": "Subscribirse - {count}", "subscribe": "Subscribirse",
"sort_by": "Orde por:", "sort_by": "Orde por:",
"least_recent": "Máis antigo", "least_recent": "Máis antigo",
"most_recent": "Máis recente", "most_recent": "Máis recente",
"channel_name_asc": "Nome da canle (A-Z)", "channel_name_asc": "Nome da canle (A-Z)",
"unsubscribe": "Retirar subscrición - {count}", "unsubscribe": "Retirar subscrición",
"view_subscriptions": "Ver Subscricións", "view_subscriptions": "Ver Subscricións",
"back": "Volver", "back": "Volver",
"uses_api_from": "Usa a API desde ", "uses_api_from": "Usa a API desde ",

View File

@ -22,8 +22,8 @@
"failed": "חל כשל עם קוד שגיאה {0}, מידע נוסף ביומנים" "failed": "חל כשל עם קוד שגיאה {0}, מידע נוסף ביומנים"
}, },
"actions": { "actions": {
"subscribe": "מינוי - {count}", "subscribe": "מינוי",
"unsubscribe": "ביטול מינוי - {count}", "unsubscribe": "ביטול מינוי",
"view_subscriptions": "הצגת מינויים", "view_subscriptions": "הצגת מינויים",
"sort_by": "מיון לפי:", "sort_by": "מיון לפי:",
"most_recent": "האחרונים", "most_recent": "האחרונים",
@ -147,7 +147,10 @@
"generate_qrcode": "יצירת קוד QR", "generate_qrcode": "יצירת קוד QR",
"import_from_json_csv": "ייבוא מ־JSON/CSV", "import_from_json_csv": "ייבוא מ־JSON/CSV",
"download_frame": "הורדת תמונית", "download_frame": "הורדת תמונית",
"instance_privacy_policy": "מדיניות פרטיות" "instance_privacy_policy": "מדיניות פרטיות",
"add_to_group": "הוספה לקבוצה",
"concurrent_prefetch_limit": "מגבלת משיכה טרומית של תזרימים במקביל",
"instances_not_shown": "מופעים ציבוריים שאינם מופיעים כאן אינם זמינים כרגע."
}, },
"comment": { "comment": {
"pinned_by": "ננעץ על ידי {author}", "pinned_by": "ננעץ על ידי {author}",
@ -162,7 +165,8 @@
"registered_users": "משתמשים רשומים", "registered_users": "משתמשים רשומים",
"version": "גרסה", "version": "גרסה",
"up_to_date": "עדכני?", "up_to_date": "עדכני?",
"ssl_score": "דירוג SSL" "ssl_score": "דירוג SSL",
"uptime_30d": "זמן פעילות (30 ימים)"
}, },
"login": { "login": {
"username": "שם משתמש", "username": "שם משתמש",
@ -209,7 +213,9 @@
"days": "{amount} ימים", "days": "{amount} ימים",
"weeks": "{amount} שבועות", "weeks": "{amount} שבועות",
"months": "{amount} חודשים", "months": "{amount} חודשים",
"hours": "{amount} שעות" "hours": "{amount} שעות",
"login_note": "כניסה עם חשבון שנוצר בעותק הזה.",
"register_note": "הרשמה ל־Piped הזה. תאפשר לך לסנכרן את המינויים ואת רשימות הנגינה שלך עם החשבון שלך כך שיאוחסנו בצד השרת. אפשר להשתמש בכל התכונות בלי חשבון אך כל הנתונים יאוחסנו במטמון המקומי של הדפדפן שלך. נא לוודא שלא בחרת בכתובת דוא״ל כשם המשתמש שלך ורצוי לבחור בסיסמה מאובטחת שלא משמשת אותך בשום מקום אחר."
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "נרשמת אל: {0}" "subscribed_channels_count": "נרשמת אל: {0}"

View File

@ -1,105 +1,234 @@
{ {
"titles": { "titles": {
"trending": "ट्रेंडिंग", "trending": "रुझान",
"history": "इतिहास", "history": "इतिहास",
"register": "रजिस्टर करें", "register": "पंजीकृत करें",
"login": "लॉग इन करें", "login": "लॉगिन",
"preferences": "प्राथमिकताए", "preferences": "प्राथमिकताए",
"subscriptions": "सदस्यता", "subscriptions": "सदस्यता",
"feed": "फीड", "feed": "फीड",
"playlists": "प्लेलिस्ट", "playlists": "प्लेलिस्ट",
"livestreams": "लाइव स्ट्रीम", "livestreams": "लाइवस्ट्रीम",
"channels": "चैनल", "channels": "चैनल",
"player": "चालक", "player": "चालक",
"account": "खाता", "account": "खाता",
"instance": "इंस्टैंस" "instance": "इंस्टैंस",
"channel_groups": "चैनल ग्रुप",
"bookmarks": "बुकमार्क",
"dearrow": "DeArrow",
"albums": "एलबम",
"custom_instances": "तदनुकूल इंस्टैंस"
}, },
"actions": { "actions": {
"subscribe": "सदस्यता लें - {count}", "subscribe": "सदस्यता लें",
"back": "वापस जाओ", "back": "पीछे",
"unsubscribe": "सदस्यता ले ली है - {count}", "unsubscribe": "सदस्यता छोड़ें",
"no": "नहीं", "no": "नहीं",
"hide_replies": "जवाब छिपाएं", "hide_replies": "जवाब छिपाएं",
"search": "खोजें (Ctrl+K)", "search": "खोजें (Ctrl+K)",
"loop_this_video": "इस वीडियो को लूप करें", "loop_this_video": "इस वीडियो को लूप करें",
"loading": "लोड हो रहा है...", "loading": "लोड हो रहा है...",
"show_description": "विवरण दिखाएं", "show_description": "विवरण दिखाएं",
"minimize_description": "विवरण छिपाएं", "minimize_description": "विवरण संक्षेपित करें",
"yes": "हां", "yes": "हां",
"view_subscriptions": "सदस्यता देखें", "view_subscriptions": "सदस्यता देखें",
"most_recent": "सबसे हाला", "most_recent": "सबसे हालिया",
"least_recent": "कम से कम हाल का", "least_recent": "कम हालिया",
"channel_name_asc": "चैनल का नाम (ए-जेड)", "channel_name_asc": "चैनल नाम (A-Z)",
"channel_name_desc": "चैनल का नाम (जेड-ए)", "channel_name_desc": "चैनल नाम (Z-A)",
"uses_api_from": "से API का उपयोग करता है ", "uses_api_from": "यहां से API का उपयोग करता है ",
"skip_sponsors": "प्रायोजकों को छोड़ें", "skip_sponsors": "प्रायोजकों को छोड़ें",
"skip_outro": "एंडकार्ड्स/क्रेडिट छोड़ें", "skip_outro": "एंडकार्ड/क्रेडिट छोड़ें",
"skip_interaction": "इंटरैक्शन रिमाइंडर छोड़ें (सदस्यता लें)", "skip_interaction": "इंटरेक्शन अनुस्मारक छोड़ें (सदस्यता लें)",
"theme": "थीम", "theme": "थीम",
"dark": "डार्क", "dark": "गहरी",
"light": "प्रकाश", "light": "हल्की",
"autoplay_video": "ऑटोप्ले वीडियो", "autoplay_video": "वीडियो स्वत:चालू करें",
"audio_only": "सिर्फ़ ध्वनि", "audio_only": "सिर्फ ऑडियो",
"default_quality": "डिफ़ॉल्ट गुणवत्ता", "default_quality": "तयशुदा गुणवत्ता",
"country_selection": "देश", "country_selection": "देश",
"show_comments": "टिप्पणियाँ दिखाएँ", "show_comments": "टिप्पणियां दिखाएं",
"store_watch_history": "स्टोर देखने का इतिहास", "store_watch_history": "देखने का इतिहास संग्रहीत करें",
"language_selection": "भाषा", "language_selection": "भाषा",
"instances_list": "इंस्टंस सूची", "instances_list": "इंस्टंस सूची",
"instance_selection": "इंस्टंस", "instance_selection": "इंस्टंस",
"show_more": "और दिखाओ", "show_more": "अधिक दिखाएं",
"export_to_json": "JSON में निर्यात करें", "export_to_json": "JSON में निर्यात करें",
"import_from_json": "JSON/CSV से आयात करें", "import_from_json": "JSON से आयात करें",
"auto_play_next_video": "अगला वीडियो ऑटोप्ले करें", "auto_play_next_video": "अगला वीडियो स्वत:चालू करें",
"donations": "विकास दान", "donations": "विकास के लिए दान",
"minimize_recommendations": "सिफारिशों को कम करें", "minimize_recommendations": "अनुशंसाएं न्यूनतम करें",
"show_recommendations": "सिफारिशें दिखाएं", "show_recommendations": "अनुशंसाएं दिखाएं",
"disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें", "disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें",
"enable_lbry_proxy": "LBRY के लिए प्रॉक्सी सक्षम करें", "enable_lbry_proxy": "LBRY के लिए प्रॉक्सी सक्षम करें",
"view_ssl_score": "एसएसएल स्कोर देखें", "view_ssl_score": "SSL स्कोर देखें",
"filter": "फिल्टर", "filter": "फिल्टर",
"clear_history": "स्पष्ट इतिहास", "clear_history": "इतिहास साफ़ करें",
"load_more_replies": "और जवाब लोड करें", "load_more_replies": "अधिक जवाब लोड करें",
"enabled_codecs": "सक्षम कोडेक्स (एकाधिक)", "enabled_codecs": "सक्षम कोडेक्स (एकाधिक)",
"buffering_goal": "बफरिंग गोल (सेकंड में)", "buffering_goal": "बफरिंग क्ष्य (सेकंड में)",
"delete_playlist_confirm": "प्लेलिस्ट को मिटाना है?", "delete_playlist_confirm": "इस प्लेलिस्ट को मिटाएं?",
"add_to_playlist": "प्लेलिस्ट में जोड़ें", "add_to_playlist": "प्लेलिस्ट में जोड़ें",
"remove_from_playlist": "प्लेलिस्ट से निकाले", "remove_from_playlist": "प्लेलिस्ट से हटाएं",
"delete_playlist_video_confirm": "वीडियो को प्लेलिस्ट से निकालना है?", "delete_playlist_video_confirm": "प्लेलिस्ट से वीडियो हटाएं?",
"create_playlist": "प्लेलिस्ट बनायें", "create_playlist": "प्लेलिस्ट बनायें",
"select_playlist": "एक प्लेलिस्ट चुनें", "select_playlist": "प्लेलिस्ट चुनें",
"please_select_playlist": "कृपया एक प्लेलिस्ट चुनें", "please_select_playlist": "कृपया प्लेलिस्ट चुनें",
"delete_playlist": "प्लेलिस्ट टाएं", "delete_playlist": "प्लेलिस्ट मिटाएं",
"enable_sponsorblock": "विज्ञापन प्रतिबंध करें", "enable_sponsorblock": "स्पॉन्सरब्लॉक सक्षम करें",
"default_homepage": "स्वतः निर्धारित मुख्यपृष्ठ", "default_homepage": "तयशुदा मुख्यपृष्ठ",
"sort_by": "वर्गीकरण:", "sort_by": "ऐसे छांटें:",
"skip_automatically": "स्वतः", "skip_automatically": "स्वतः",
"delete_account": "खाता मिटाएँ" "delete_account": "खाता मिटाएं",
"skip_button_only": "स्किप बटन दिखाएं",
"skip_intro": "मध्यांतर/परिचय एनिमेशन छोड़ें",
"skip_self_promo": "अवैतनिक/स्व-प्रचार छोड़ें",
"skip_filler_tangent": "फिलर स्पर्शज्या छोड़ें",
"skip_non_music": "संगीत छोड़ें: गैर-संगीत अनुभाग",
"show_markers": "प्लेयर पर निशान दिखाएं",
"skip_preview": "पूर्वावलोकन/पुनर्कथनs छोड़ें",
"skip_highlight": "मुख्य आकर्षण छोड़ें",
"instance_auth_selection": "प्रमाणीकरण इंस्टैंस",
"different_auth_instance": "प्रमाणीकरण के लिए किसी भिन्न इंस्टैंस का उपयोग करें",
"reset_preferences": "प्राथमिकताएं रीसेट करें",
"back_to_home": "होम पर वापस",
"piped_link": "Piped लिंक",
"hide_watched": "देखी गई वीडियो फीड में छिपाएं",
"documentation": "दस्तावेज़ीकरण",
"status_page": "स्थिति",
"source_code": "स्रोत कोड",
"show_chapters": "अध्याय",
"follow_link": "लिंक का अनुसरण करें",
"store_search_history": "खोज इतिहास संग्रहित करें",
"copy_link": "लिंक कॉपी करें",
"with_timecode": "समय कोड के साथ साझा करें",
"edit_playlist": "प्लेलिस्ट संपादित करें",
"auto_display_captions": "अनुशीर्षक स्वत: प्रदर्शित करें",
"instances_not_shown": "जो सार्वजनिक इंस्टैंस यहां नहीं दिखाए जा रहे हैं, वे वर्तमान में अनुपलब्ध हैं।",
"enable_dearrow": "DeArrow सक्षम करें",
"auto": "स्वतः",
"minimize_description_default": "तयशुदा रूप से विवरण को संक्षेपित करें",
"import_from_json_csv": "JSON/CSV से आयात करें",
"logout": "इस उपकरण से लॉगआउट करें",
"chapters_layout_mobile": "मोबाइल पर अध्याय अभिन्यास",
"show_watch_on_youtube": "YouTube पर देखें बटन दिखाएं",
"invalidate_session": "सभी उपकरणों को लॉगआउट करें",
"clone_playlist": "प्लेलिस्ट की प्रतिलिपि बनाएं",
"clone_playlist_success": "सफलतापूर्वक प्रतिलिपि बनाई गई!",
"download_as_txt": ".txt के रूप में डाउनलोड करें",
"backup_preferences": "प्राथमिकताएं बैकअप करें",
"restore_preferences": "प्राथमिकताएं पुनर्स्थापित करें",
"playlist_name": "प्लेलिस्ट नाम",
"playlist_description": "प्लेलिस्ट विवरण",
"share": "साझा करें",
"time_code": "समय कोड (सेकंड में)",
"reply_count": "{count} जवाब",
"min_segment_length": "न्यूनतम खंड लंबाई (सेकंड में)",
"skip_segment": "खंड छोड़ें",
"autoplay_next_countdown": "अगले वीडियो तक तयशुदा उल्टीगिनती (सेकंड में)",
"minimize_comments_default": "तयशुदा रूप से टिप्पणियां संक्षेपित करें",
"minimize_comments": "टिप्पणियां को संक्षेपित करें",
"confirm_reset_preferences": "क्या आप वाकई अपनी प्राथमिकताएं रीसेट करना चाहते हैं?",
"no_valid_playlists": "फ़ाइल में मान्य प्लेलिस्ट नहीं हैं!",
"instance_privacy_policy": "गोपनीयता नीति",
"bookmark_playlist": "बुकमार्क करें",
"concurrent_prefetch_limit": "समवर्ती स्ट्रीम प्रीफ़ेच सीमा",
"cancel": "रद्द करें",
"okay": "ठीक है",
"playlist_bookmarked": "बुकमार्क किया गया",
"dismiss": "खारिज करें",
"show_less": "कम दिखाएं",
"create_group": "समूह बनाएं",
"group_name": "समूह नाम",
"show_search_suggestions": "खोज सुझाव दिखाएं",
"delete_automatically": "बाद में स्वचालित रूप से हटा दें",
"generate_qrcode": "QR कोड बनाएं",
"add_to_group": "समूह में जोड़ें",
"download_frame": "डाउनलोड फ्रेम",
"with_playlist": "प्लेलिस्ट के साथ साझा करें",
"instance_donations": "इंस्टैंस के लिए दान",
"minimize_chapters_default": "तयशुदा रूप से अध्यायों को न्यूनतम करें",
"minimize_recommendations_default": "तयशुदा रूप से अनुशंसाएं न्यूनतम करें",
"customize": "अनुकूलित करें",
"invalid_url": "अमान्य URL!",
"add": "जोड़ें",
"delete_group_confirm": "इस समूह को मिटाएं?",
"creator_replied": "निर्माता का जवाब",
"creator_liked": "निर्माता को पसंद",
"invalid_input": "अमान्य इनपुट",
"playback_speed": "प्लेबैक गति"
}, },
"video": { "video": {
"views": "{views} बार देखा गया", "views": "{views} बार देखा गया",
"videos": "वीडियो", "videos": "वीडियो",
"watched": "पहले ही देखा हुआ", "watched": "देखा गया",
"ratings_disabled": "रेटिंग अक्षम", "ratings_disabled": "रेटिंग अक्षम",
"chapters": "चैप्टर", "chapters": "अध्याय",
"live": "{0} लाइव" "live": "{0} लाइव",
"sponsor_segments": "प्रायोजक खंड",
"shorts": "शॉर्ट्स",
"all": "सभी",
"category": "श्रेणी",
"license": "लाईसेंस",
"visibility": "दृश्यता",
"chapters_horizontal": "क्षैतिज",
"chapters_vertical": "ऊर्ध्वाधर"
}, },
"login": { "login": {
"password": "पासवर्ड", "password": "पासवर्ड",
"username": "उपयोगकर्ता नाम" "username": "उपयोक्ता नाम",
"passwords_incorrect": "पासवर्ड मेल नहीं खाते हैं!",
"password_confirm": "पासवर्ड की पुष्टि करें"
}, },
"comment": { "comment": {
"pinned_by": "{author} ने पिन किया" "pinned_by": "{author} ने पिन किया",
"loading": "टिप्पणियां लोड हो रही हैं…",
"disabled": "टिप्पणियां अपलोडर द्वारा अक्षम की गई हैं।",
"user_disabled": "सेटिंग्स में टिप्पणियां अक्षम हैं।"
}, },
"preferences": { "preferences": {
"instance_locations": "इंस्टेंस स्थान", "instance_locations": "इंस्टैंस स्थान",
"has_cdn": "सीडीएन है?", "has_cdn": "CDN है?",
"ssl_score": "एसएसएल स्कोर" "ssl_score": "SSL स्कोर",
"uptime_30d": "अपटाइम (30 दिन)",
"instance_name": "इंस्टैंस का नाम",
"registered_users": "पंजीकृत उपयोक्ता",
"version": "संस्करण",
"up_to_date": "अद्यतित?",
"api_url": "Api URL"
}, },
"search": { "search": {
"did_you_mean": "क्या आपका मतलब यह था: {0}?" "did_you_mean": "क्या आपका मतलब यह था: {0}?",
"playlists": "YouTube: प्लेलिस्ट",
"music_videos": "YT Music: वीडियो",
"music_albums": "YT Music: एलबम",
"music_playlists": "YT Music: प्लेलिस्ट",
"all": "YouTube: सभी",
"videos": "YouTube: वीडियो",
"channels": "YouTube: चैनल्स",
"music_artists": "YT Music: कलाकार",
"music_songs": "YT Music: संगीत"
}, },
"player": { "player": {
"watch_on": "{0} में देखें" "watch_on": "{0} पर देखें",
"failed": "त्रुटि कोड {0} के साथ विफल, अधिक जानकारी के लिए लॉग देखें"
},
"info": {
"login_note": "इस इंस्टैंस पर बनाए गए खाते से लॉग इन करें।",
"page_not_found": "पृष्ठ नहीं मिला",
"copied": "कॉपी किया गया!",
"cannot_copy": "कॉपी नहीं कर सकते!",
"local_storage": "यह क्रिया को लोकलस्टोरेज की आवश्यकता है, क्या कुकीज़ सक्षम हैं?",
"preferences_note": "नोट: प्राथमिकताएं आपके ब्राउज़र के स्थानीय संग्रहण में सहेजी जाती हैं। अपने ब्राउज़र डेटा को हटाने से वे रीसेट हो जाएंगी।",
"register_no_email_note": "उपयोक्ता नाम के रूप में ईमेल का उपयोग करने की अनुशंसा नहीं की जाती है। फिर भी आगे बढ़ें?",
"next_video_countdown": "अगला वीडियो {0} सेकंड में चलाया जा रहा है",
"hours": "{amount} घंटा(टे)",
"days": "{amount} दिन(नों)",
"weeks": "{amount} हफ्ता(ते)",
"months": "{amount} महीना(ने)",
"register_note": "Piped इंस्टैंस के लिए एक खाता पंजीकृत करें। इससे आप अपनी सदस्यता और प्लेलिस्ट को अपने खाते के साथ सिंक कर सकते हैं, ताकि वे सर्वर साइड पर संग्रहित हों। आप खाते के बिना भी सभी विशेषताएं इस्तेमाल कर सकते हैं, लेकिन सभी डेटा आपके ब्राउज़र के स्थानीय कैशे में संग्रहित होगा। कृपया सुनिश्चित करें कि आप अपना ईमेल पता उपयोक्ता नाम के रूप में इस्तेमाल नहीं कर रहे हैं और एक सुरक्षित पासवर्ड चुनें जिसे आप कहीं और नहीं इस्तेमाल करते हैं।"
},
"subscriptions": {
"subscribed_channels_count": "इसकी सदस्यता ली गई: {0}"
} }
} }

View File

@ -22,7 +22,9 @@
"instance_name": "Ime instance", "instance_name": "Ime instance",
"registered_users": "Registrirani korisnici", "registered_users": "Registrirani korisnici",
"version": "Verzija", "version": "Verzija",
"up_to_date": "Najnovija verzija?" "up_to_date": "Najnovija verzija?",
"uptime_30d": "Vrijeme rada (30 dana)",
"api_url": "URL Api-ja"
}, },
"comment": { "comment": {
"pinned_by": "Prikvačio korisnik {author}", "pinned_by": "Prikvačio korisnik {author}",
@ -73,8 +75,8 @@
"most_recent": "Najnovije", "most_recent": "Najnovije",
"sort_by": "Redoslijed:", "sort_by": "Redoslijed:",
"view_subscriptions": "Pogledaj pretplate", "view_subscriptions": "Pogledaj pretplate",
"unsubscribe": "Otkaži pretplatu {count}", "unsubscribe": "Otkaži pretplatu",
"subscribe": "Pretplati se {count}", "subscribe": "Pretplati se",
"skip_interaction": "Preskoči podsjetnik za interakciju (pretplata)", "skip_interaction": "Preskoči podsjetnik za interakciju (pretplata)",
"skip_outro": "Preskoči odjavnu špicu", "skip_outro": "Preskoči odjavnu špicu",
"skip_intro": "Preskoči pauzu i uvodnu animaciju", "skip_intro": "Preskoči pauzu i uvodnu animaciju",
@ -156,7 +158,14 @@
"generate_qrcode": "Generiraj QR kod", "generate_qrcode": "Generiraj QR kod",
"import_from_json_csv": "Uvezi iz JSON/CSV formata", "import_from_json_csv": "Uvezi iz JSON/CSV formata",
"download_frame": "Preuzmi kadar", "download_frame": "Preuzmi kadar",
"instance_privacy_policy": "Politika privatnosti" "instance_privacy_policy": "Politika privatnosti",
"add_to_group": "Dodaj grupi",
"instances_not_shown": "Javne instance koje ovdje nisu prikazane trenutačno nisu dostupne.",
"concurrent_prefetch_limit": "Ograničenje istodobnog preuzimanja videa",
"customize": "Prilagodi",
"invalid_url": "Neispravni URL!",
"add": "Dodaj",
"delete_group_confirm": "Izbrisati ovu grupu?"
}, },
"player": { "player": {
"watch_on": "Gledaj na {0}", "watch_on": "Gledaj na {0}",
@ -178,7 +187,9 @@
"livestreams": "Prijenosi uživo", "livestreams": "Prijenosi uživo",
"bookmarks": "Zabilješke", "bookmarks": "Zabilješke",
"channel_groups": "Grupe kanala", "channel_groups": "Grupe kanala",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Albumi",
"custom_instances": "Prilagođene instance"
}, },
"login": { "login": {
"password": "Lozinka", "password": "Lozinka",
@ -215,6 +226,8 @@
"hours": "{amount} h", "hours": "{amount} h",
"days": "{amount} dan(a)", "days": "{amount} dan(a)",
"weeks": "{amount} tj", "weeks": "{amount} tj",
"months": "{amount} mj" "months": "{amount} mj",
"login_note": "Prijavi se s računom stvorenim na ovoj instanci.",
"register_note": "Registriraj račun za ovu Piped instancu. To će ti omogućiti sinkronizaciju tvojih pretplata i playlista s tvojim računom kako bi se spremile na poslužitelju. Možeš koristiti sve značajke bez računa, ali će se svi podaci spremiti u lokalnu predmemoriju tvog preglednika. NEMOJ koristiti e-mail adresu kao korisničko ime i odaberi sigurnu lozinku koju ne koristiš negdje drugdje."
} }
} }

View File

@ -12,14 +12,19 @@
"player": "Lejátszó", "player": "Lejátszó",
"instance": "Szerver", "instance": "Szerver",
"livestreams": "Élő adások", "livestreams": "Élő adások",
"channels": "Csatornák" "channels": "Csatornák",
"dearrow": "DeArrow",
"albums": "Albumok",
"bookmarks": "Könyvjelzők",
"channel_groups": "Csatorna csoportok",
"custom_instances": "Egyéni példányok"
}, },
"actions": { "actions": {
"subscribe": "Feliratkozás - {count}", "subscribe": "Feliratkozás",
"skip_highlight": "Kiemelés", "skip_highlight": "Kiemelés",
"light": "Világos", "light": "Világos",
"most_recent": "Legutóbbi", "most_recent": "Legutóbbi",
"unsubscribe": "Leiratkozás - {count}", "unsubscribe": "Leiratkozás",
"view_subscriptions": "Feliratkozások megtekintése", "view_subscriptions": "Feliratkozások megtekintése",
"least_recent": "Legrégebbi", "least_recent": "Legrégebbi",
"channel_name_asc": "Csatorna név (A-Z)", "channel_name_asc": "Csatorna név (A-Z)",
@ -39,12 +44,12 @@
"autoplay_video": "Automatikus lejátszás", "autoplay_video": "Automatikus lejátszás",
"audio_only": "Csak hang", "audio_only": "Csak hang",
"default_quality": "Alapértelmezett minőség", "default_quality": "Alapértelmezett minőség",
"country_selection": "Ország választás", "country_selection": "Ország",
"default_homepage": "Alapértelmezett kezdőlap", "default_homepage": "Alapértelmezett kezdőlap",
"show_comments": "Megjegyzések megjelenítése", "show_comments": "Megjegyzések megjelenítése",
"minimize_description_default": "Leírás minimalizásása alapból", "minimize_description_default": "Leírás minimalizásása alapból",
"store_watch_history": "Megtekintési előzmények tárolása", "store_watch_history": "Megtekintési előzmények tárolása",
"language_selection": "Nyelv választás", "language_selection": "Nyelv",
"enabled_codecs": "Engedélyezett kodekek (több)", "enabled_codecs": "Engedélyezett kodekek (több)",
"show_more": "További megjelenítése", "show_more": "További megjelenítése",
"yes": "Igen", "yes": "Igen",
@ -52,14 +57,14 @@
"export_to_json": "Exportálás JSON-ba", "export_to_json": "Exportálás JSON-ba",
"skip_preview": "Előzetes/Ismétlés", "skip_preview": "Előzetes/Ismétlés",
"buffering_goal": "Pufferelési cél (másodpercben)", "buffering_goal": "Pufferelési cél (másodpercben)",
"instance_selection": "Példány kiválasztása", "instance_selection": "Példány",
"skip_filler_tangent": "Témától eltérő töltelék/viccek", "skip_filler_tangent": "Témától eltérő töltelék/viccek",
"loop_this_video": "Videó ismétlése", "loop_this_video": "Videó ismétlése",
"donations": "Fejlesztési támogatások", "donations": "Fejlesztési támogatások",
"minimize_description": "Leírás minimalizálása", "minimize_description": "Leírás minimalizálása",
"show_recommendations": "Javaslatok megjelenítése", "show_recommendations": "Javaslatok megjelenítése",
"enable_lbry_proxy": "Proxy engedélyezése a LBRY számára", "enable_lbry_proxy": "Proxy engedélyezése a LBRY számára",
"search": "Keresés", "search": "Keresés (Ctrl+K)",
"filter": "Szűrés", "filter": "Szűrés",
"loading": "Betöltés...", "loading": "Betöltés...",
"clear_history": "Megtekintési előzmények törlése", "clear_history": "Megtekintési előzmények törlése",
@ -75,7 +80,7 @@
"view_ssl_score": "SSL pontszám megtekintése", "view_ssl_score": "SSL pontszám megtekintése",
"sort_by": "Rendezés:", "sort_by": "Rendezés:",
"show_description": "Leírás megjelenítése", "show_description": "Leírás megjelenítése",
"import_from_json": "Importálás JSON/CSV-ból", "import_from_json": "Importálás JSON-ból",
"delete_playlist_video_confirm": "Eltávolítja a videót a lejátszási listából?", "delete_playlist_video_confirm": "Eltávolítja a videót a lejátszási listából?",
"remove_from_playlist": "Törlés a lejátszási listából", "remove_from_playlist": "Törlés a lejátszási listából",
"auto_play_next_video": "Következő videó automatikus lejátszása", "auto_play_next_video": "Következő videó automatikus lejátszása",
@ -87,7 +92,7 @@
"minimize_recommendations_default": "Ajánlások alapértelmezett minimalizálása", "minimize_recommendations_default": "Ajánlások alapértelmezett minimalizálása",
"invalidate_session": "Kijelentkezés minden eszközről", "invalidate_session": "Kijelentkezés minden eszközről",
"different_auth_instance": "Másik példány használata a hitelesítéshez", "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", "instance_auth_selection": "Hitelesítési példány",
"clone_playlist": "Lejátszási lista klónozá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", "reset_preferences": "Alaphelyzetbe állítás",
@ -107,7 +112,7 @@
"backup_preferences": "Beállítások mentése", "backup_preferences": "Beállítások mentése",
"share": "Megosztás", "share": "Megosztás",
"with_timecode": "Megosztás & videó kezdés ettől a ponttól", "with_timecode": "Megosztás & videó kezdés ettől a ponttól",
"store_search_history": "Mentse a keresési előzményeket", "store_search_history": "Keresési előzmények tárolása",
"follow_link": "Követések link", "follow_link": "Követések link",
"copy_link": "Link másolása", "copy_link": "Link másolása",
"status_page": "Státusz", "status_page": "Státusz",
@ -115,7 +120,43 @@
"with_playlist": "Megosztás lejátszási listával", "with_playlist": "Megosztás lejátszási listával",
"minimize_comments_default": "Mindig tüntesd el a kommenteket", "minimize_comments_default": "Mindig tüntesd el a kommenteket",
"minimize_comments": "Kommentek eltüntetése", "minimize_comments": "Kommentek eltüntetése",
"back_to_home": "Vissza a főoldalra" "back_to_home": "Vissza a főoldalra",
"delete_automatically": "Automatikus törlés ezt követően",
"enable_dearrow": "DeArrow engedélyezése",
"playlist_name": "Lejátszási lista neve",
"instance_privacy_policy": "Adatvédelmi szabályzat",
"cancel": "Visszavonás",
"okay": "Rendben",
"concurrent_prefetch_limit": "Egyidejű adatfolyam előzetes letöltési korlátja",
"bookmark_playlist": "Könyvjelző",
"dismiss": "Elvetés",
"show_less": "Mutass kevesebbet",
"create_group": "Csoport létrehozása",
"playlist_bookmarked": "Könyvjelzőzött",
"edit_playlist": "Lejátszási lista szerkesztése",
"group_name": "Csoport neve",
"min_segment_length": "Minimális szegmens hossz (másodpercben)",
"skip_automatically": "Automatikusan",
"instances_not_shown": "Az itt nem látható nyilvános példányok jelenleg nem érhetők el.",
"skip_button_only": "Kihagyás gomb megjelenítése",
"autoplay_next_countdown": "Alapértelmezett visszaszámlálás a következő videóig (másodpercben)",
"import_from_json_csv": "Importálás JSON/CSV formátumból",
"auto_display_captions": "Feliratok automatikus megjelenítése",
"chapters_layout_mobile": "Fejezetek elrendezése telefonon",
"skip_segment": "Szegmens kihagyása",
"playlist_description": "Lejátszási lista leírása",
"show_search_suggestions": "Mutass keresési javaslatokat",
"generate_qrcode": "QR-kód generálása",
"add_to_group": "Hozzáadás a csoporthoz",
"download_frame": "Keret letöltése",
"customize": "Testreszab",
"invalid_url": "Érvénytelen URL!",
"add": "Hozzáadás",
"delete_group_confirm": "Törli ezt a csoportot?",
"creator_replied": "A készítő válaszolt",
"creator_liked": "A készítő kedvelte",
"playback_speed": "Visszajátszási sebesség",
"invalid_input": "Érvénytelen bevitel"
}, },
"video": { "video": {
"ratings_disabled": "Értékelések Letiltva", "ratings_disabled": "Értékelések Letiltva",
@ -125,10 +166,17 @@
"live": "{0} Élő", "live": "{0} Élő",
"videos": "Videók", "videos": "Videók",
"views": "{views} megtekintés", "views": "{views} megtekintés",
"shorts": "Rövid videók" "shorts": "Rövid videók",
"category": "Kategória",
"chapters_horizontal": "Vízszintes",
"chapters_vertical": "Függőleges",
"all": "Mind",
"license": "Licenc",
"visibility": "Láthatóság"
}, },
"player": { "player": {
"watch_on": "Lejátszásban {0}" "watch_on": "Megtekintés itt: {0}",
"failed": "Sikertelen hibakód: {0}, további információért tekintse meg a naplókat"
}, },
"preferences": { "preferences": {
"instance_name": "Példány neve", "instance_name": "Példány neve",
@ -137,7 +185,9 @@
"version": "Verzió", "version": "Verzió",
"ssl_score": "SSL pontszám", "ssl_score": "SSL pontszám",
"registered_users": "Regisztrált felhasználók", "registered_users": "Regisztrált felhasználók",
"up_to_date": "Naprakész?" "up_to_date": "Naprakész?",
"uptime_30d": "Üzemidő (30 nap)",
"api_url": "Api URL"
}, },
"search": { "search": {
"did_you_mean": "Erre gondoltál: {0}?", "did_you_mean": "Erre gondoltál: {0}?",
@ -148,11 +198,14 @@
"music_songs": "YT Music: Dalok", "music_songs": "YT Music: Dalok",
"music_videos": "YT Music: Videók", "music_videos": "YT Music: Videók",
"music_albums": "YT Music: Albumok", "music_albums": "YT Music: Albumok",
"music_playlists": "YT Music: Lejátszási listák" "music_playlists": "YT Music: Lejátszási listák",
"music_artists": "YT Zene: Előadók"
}, },
"login": { "login": {
"username": "Felhasználónév", "username": "Felhasználónév",
"password": "Jelszó" "password": "Jelszó",
"password_confirm": "Jelszó megerősítése",
"passwords_incorrect": "A jelszavak nem egyeznek!"
}, },
"comment": { "comment": {
"pinned_by": "Rögzítette {author}", "pinned_by": "Rögzítette {author}",
@ -168,6 +221,14 @@
"copied": "Másolva!", "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?", "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!", "cannot_copy": "Nem lehet másolni!",
"page_not_found": "Oldnal nem található" "page_not_found": "Oldnal nem található",
"register_no_email_note": "Nem ajánlott e-mailt használni felhasználónévként. Tovább folytatja?",
"next_video_countdown": "Következő videó lejátszása {0}másodperc múlva",
"weeks": "{amount} hét",
"login_note": "Jelentkezzen be az ezen a példányon létrehozott fiókkal.",
"months": "{amount} hónap",
"hours": "{amount} óra",
"days": "{amount} nap",
"register_note": "Regisztráljon egy fiókot ehhez a Piped példányhoz. Ez lehetővé teszi az előfizetések és lejátszási listák szinkronizálását a fiókjával, így azok a kiszolgáló oldalon kerülnek tárolásra. Az összes funkciót fiók nélkül is használhatja, de minden adat a böngésző helyi gyorsítótárában tárolódik. Kérjük, győződjön meg róla, hogy NE használjon e-mail címet felhasználónévként, és válasszon biztonságos jelszót, amelyet máshol nem használ."
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"actions": { "actions": {
"skip_automatically": "Ավտոմատվաց", "skip_automatically": "Ավտոմատվաց",
"subscribe": "Բաժանորդ - {count}", "subscribe": "Բաժանորդ",
"uses_api_from": "Օգտագործում է API -ից ", "uses_api_from": "Օգտագործում է API -ից ",
"enable_sponsorblock": "Միացնել հովանավորների արգելափակումը", "enable_sponsorblock": "Միացնել հովանավորների արգելափակումը",
"skip_intro": "Բաց թողնել ընդմիջում/ներածական անիմացիա", "skip_intro": "Բաց թողնել ընդմիջում/ներածական անիմացիա",

View File

@ -15,18 +15,20 @@
"channels": "Saluran", "channels": "Saluran",
"bookmarks": "Markah", "bookmarks": "Markah",
"channel_groups": "Grup saluran", "channel_groups": "Grup saluran",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Album",
"custom_instances": "Server khusus"
}, },
"player": { "player": {
"watch_on": "Lihat di {0}", "watch_on": "Lihat di {0}",
"failed": "Gagal dengan kode kesalahan {0}, lihat catatan untuk info lebih lanjut" "failed": "Gagal dengan kode kesalahan {0}, lihat catatan untuk info lebih lanjut"
}, },
"actions": { "actions": {
"subscribe": "Berlangganan - {count}", "subscribe": "Berlangganan",
"view_subscriptions": "Lihat Langganan", "view_subscriptions": "Lihat Langganan",
"sort_by": "Sortir bedasarkan oleh:", "sort_by": "Sortir bedasarkan oleh:",
"least_recent": "Baru", "least_recent": "Baru",
"unsubscribe": "Berhenti Berlangganan - {count}", "unsubscribe": "Berhenti Berlangganan",
"channel_name_asc": "Nama Saluran (A-Z)", "channel_name_asc": "Nama Saluran (A-Z)",
"channel_name_desc": "Nama Saluran (Z-A)", "channel_name_desc": "Nama Saluran (Z-A)",
"back": "Kembali", "back": "Kembali",
@ -147,7 +149,13 @@
"generate_qrcode": "Buat Kode QR", "generate_qrcode": "Buat Kode QR",
"import_from_json_csv": "Impor dari JSON/CSV", "import_from_json_csv": "Impor dari JSON/CSV",
"download_frame": "Unduh bingkai", "download_frame": "Unduh bingkai",
"instance_privacy_policy": "Kebijakan Privasi" "instance_privacy_policy": "Kebijakan Privasi",
"add_to_group": "Tambahkan ke grup",
"concurrent_prefetch_limit": "Batasan Concurrent Stream Prefetch",
"instances_not_shown": "Instance publik yang tidak ditampilkan disini saat ini tidak tersedia.",
"customize": "Ubah",
"add": "Tambahkan",
"invalid_url": "URL tidak valid!"
}, },
"comment": { "comment": {
"pinned_by": "Dipasangi pin oleh {author}", "pinned_by": "Dipasangi pin oleh {author}",
@ -162,7 +170,9 @@
"has_cdn": "Memakai CDN?", "has_cdn": "Memakai CDN?",
"up_to_date": "Sudah terkini?", "up_to_date": "Sudah terkini?",
"version": "Versi", "version": "Versi",
"registered_users": "Pengguna Terdaftar" "registered_users": "Pengguna Terdaftar",
"uptime_30d": "Waktu aktif (30hari)",
"api_url": "URL API"
}, },
"login": { "login": {
"username": "Nama Pengguna", "username": "Nama Pengguna",
@ -215,6 +225,8 @@
"weeks": "{amount} minggu", "weeks": "{amount} minggu",
"hours": "{amount} jam", "hours": "{amount} jam",
"days": "{amount} hari", "days": "{amount} hari",
"months": "{amount} bulan" "months": "{amount} bulan",
"login_note": "Masuk dengan akun yang dibuat di server ini.",
"register_note": "Daftarkan akun untuk server Piped ini. Ini akan memungkinkan Anda untuk menyinkronkan langganan dan daftar putar Anda dengan akun Anda, sehingga mereka disimpan di sisi server. Anda dapat menggunakan semua fitur tanpa akun, tetapi semua data akan disimpan di tembolok lokal browser Anda. Pastikan Anda TIDAK menggunakan alamat surel sebagai nama pengguna Anda dan pilih kata sandi yang aman yang tidak Anda gunakan di tempat lain."
} }
} }

View File

@ -23,8 +23,8 @@
"light": "Ljós", "light": "Ljós",
"theme": "Þema", "theme": "Þema",
"enable_sponsorblock": "Virkja Sponsorblock", "enable_sponsorblock": "Virkja Sponsorblock",
"subscribe": "Gerast Áskrifandi - {count}", "subscribe": "Gerast Áskrifandi",
"unsubscribe": "Segja Upp Áskrift - {count}", "unsubscribe": "Segja Upp Áskrift",
"auto": "Sjálfvirkt", "auto": "Sjálfvirkt",
"audio_only": "Aðeins Hljóð", "audio_only": "Aðeins Hljóð",
"most_recent": "Nýlegast", "most_recent": "Nýlegast",

View File

@ -31,8 +31,8 @@
"least_recent": "Meno recente", "least_recent": "Meno recente",
"sort_by": "Ordina per:", "sort_by": "Ordina per:",
"view_subscriptions": "Visualizza gli abbonamenti", "view_subscriptions": "Visualizza gli abbonamenti",
"unsubscribe": "Disiscriviti - {count}", "unsubscribe": "Disiscriviti",
"subscribe": "Iscriviti - {count}", "subscribe": "Iscriviti",
"enabled_codecs": "Abilita Codecs (Molteplici)", "enabled_codecs": "Abilita Codecs (Molteplici)",
"enable_lbry_proxy": "Abilita il proxy per LBRY", "enable_lbry_proxy": "Abilita il proxy per LBRY",
"disable_lbry": "Disabilita LBRY per lo streaming", "disable_lbry": "Disabilita LBRY per lo streaming",
@ -43,7 +43,7 @@
"donations": "Donazioni per lo sviluppo", "donations": "Donazioni per lo sviluppo",
"auto_play_next_video": "Riproduci automaticamente il prossimo video", "auto_play_next_video": "Riproduci automaticamente il prossimo video",
"loop_this_video": "Ripeti questo video", "loop_this_video": "Ripeti questo video",
"import_from_json": "Importa da JSON/CSV", "import_from_json": "Importa da JSON",
"export_to_json": "Esporta in JSON", "export_to_json": "Esporta in JSON",
"no": "No", "no": "No",
"yes": "Sì", "yes": "Sì",
@ -88,7 +88,7 @@
"follow_link": "Apri il collegamento", "follow_link": "Apri il collegamento",
"with_timecode": "Condividi con marca temporale", "with_timecode": "Condividi con marca temporale",
"show_chapters": "Capitoli", "show_chapters": "Capitoli",
"store_search_history": "Memorizza la cronologia delle ricerche", "store_search_history": "Archivia la cronologia delle ricerche",
"status_page": "Stato", "status_page": "Stato",
"documentation": "Documentazione", "documentation": "Documentazione",
"source_code": "Codice sorgente", "source_code": "Codice sorgente",
@ -117,10 +117,21 @@
"group_name": "Nome gruppo", "group_name": "Nome gruppo",
"create_group": "Crea gruppo", "create_group": "Crea gruppo",
"show_search_suggestions": "Mostra suggerimenti di ricerca", "show_search_suggestions": "Mostra suggerimenti di ricerca",
"dismiss": "Chiudi" "dismiss": "Chiudi",
"add_to_group": "Aggiungi al gruppo",
"import_from_json_csv": "Importa da JSON/CSV",
"enable_dearrow": "Abilita DeArrow",
"chapters_layout_mobile": "Disposizione dei capitoli sul cellulare",
"delete_automatically": "Cancella automaticamente dopo",
"auto_display_captions": "Visualizza automaticamente i sottotitoli",
"generate_qrcode": "Genera un codice QR",
"instance_privacy_policy": "Normativa sulla privacy",
"download_frame": "Scarica fotogramma",
"instances_not_shown": "Le istanze pubbliche che non compaiono in questa schermata non sono al momento disponibili."
}, },
"player": { "player": {
"watch_on": "Guarda su {0}" "watch_on": "Guarda su {0}",
"failed": "Operazione non riuscita (codice errore {0}). Vedi il registro per ulteriori dettagli"
}, },
"titles": { "titles": {
"history": "Cronologia", "history": "Cronologia",
@ -137,7 +148,8 @@
"livestreams": "Streaming live", "livestreams": "Streaming live",
"channels": "Canali", "channels": "Canali",
"bookmarks": "Segnalibri", "bookmarks": "Segnalibri",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"channel_groups": "Gruppi di canali"
}, },
"video": { "video": {
"sponsor_segments": "Segmenti sponsor", "sponsor_segments": "Segmenti sponsor",
@ -152,7 +164,8 @@
"category": "Categoria", "category": "Categoria",
"chapters_horizontal": "Orizzontale", "chapters_horizontal": "Orizzontale",
"chapters_vertical": "Verticale", "chapters_vertical": "Verticale",
"license": "Licenza" "license": "Licenza",
"visibility": "Visibilità"
}, },
"preferences": { "preferences": {
"ssl_score": "Valutazione SSL", "ssl_score": "Valutazione SSL",
@ -171,7 +184,9 @@
}, },
"login": { "login": {
"password": "Password", "password": "Password",
"username": "Nome utente" "username": "Nome utente",
"password_confirm": "Conferma password",
"passwords_incorrect": "Le password non combaciano!"
}, },
"search": { "search": {
"did_you_mean": "Forse intendevi: {0}?", "did_you_mean": "Forse intendevi: {0}?",
@ -198,6 +213,12 @@
"cannot_copy": "Impossibile copiare!", "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?", "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" "next_video_countdown": "Riproduzione prossimo video tra {0}s",
"login_note": "Accedi con un account creato su questa istanza.",
"register_note": "Registra un account per questa istanza di Piped. Ciò ti consentirà di sincronizzare le tue iscrizioni e le tue playlist con il tuo account, in modo che siano archiviate sul server. È possibile utilizzare tutte le funzionalità anche senza un account, ma tutti i dati verranno archiviati nella memoria locale del tuo browser. Assicurati di NON utilizzare un indirizzo email come nome utente e di scegliere una password sicura che non usi altrove.",
"hours": "{amount} ora/e",
"months": "{amount} mese/i",
"weeks": "{amount} settimana/e",
"days": "{amount} giorno/i"
} }
} }

View File

@ -3,7 +3,7 @@
"trending": "急上昇", "trending": "急上昇",
"login": "ログイン", "login": "ログイン",
"register": "新規登録", "register": "新規登録",
"feed": "フィード", "feed": "新着動画",
"preferences": "設定", "preferences": "設定",
"history": "履歴", "history": "履歴",
"subscriptions": "登録チャンネル", "subscriptions": "登録チャンネル",
@ -15,15 +15,17 @@
"livestreams": "ライブ配信", "livestreams": "ライブ配信",
"bookmarks": "ブックマーク", "bookmarks": "ブックマーク",
"channel_groups": "グループ", "channel_groups": "グループ",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "アルバム",
"custom_instances": "独自インスタンス"
}, },
"player": { "player": {
"watch_on": "{0}で視聴", "watch_on": "{0}で視聴",
"failed": "失敗しエラーコード {0} が返りました。詳細はログに記録" "failed": "失敗。エラーコード {0} 。詳細はログに記録"
}, },
"actions": { "actions": {
"subscribe": "チャンネル登録 - {count}", "subscribe": "チャンネル登録",
"unsubscribe": "登録解除 - {count}", "unsubscribe": "登録解除",
"view_subscriptions": "登録チャンネルを見る", "view_subscriptions": "登録チャンネルを見る",
"sort_by": "表示順:", "sort_by": "表示順:",
"most_recent": "新しい順", "most_recent": "新しい順",
@ -35,7 +37,7 @@
"enable_sponsorblock": "SponsorBlock を使用", "enable_sponsorblock": "SponsorBlock を使用",
"skip_sponsors": "広告をスキップ", "skip_sponsors": "広告をスキップ",
"skip_intro": "合間/導入アニメをスキップ", "skip_intro": "合間/導入アニメをスキップ",
"skip_outro": "終了シーン/クレジットをスキップ", "skip_outro": "終了画面/クレジットをスキップ",
"skip_preview": "予告/あらすじをスキップ", "skip_preview": "予告/あらすじをスキップ",
"skip_interaction": "登録など操作を頼む自己宣伝をスキップ", "skip_interaction": "登録など操作を頼む自己宣伝をスキップ",
"skip_self_promo": "無償または自己の宣伝をスキップ", "skip_self_promo": "無償または自己の宣伝をスキップ",
@ -102,7 +104,7 @@
"download_as_txt": ".txtでダウンロード", "download_as_txt": ".txtでダウンロード",
"logout": "この端末からログアウト", "logout": "この端末からログアウト",
"minimize_recommendations_default": "最初からおすすめを最小化", "minimize_recommendations_default": "最初からおすすめを最小化",
"hide_watched": "再生済みの動画をフィードに表示しない", "hide_watched": "再生済みの動画を新着動画に表示しない",
"minimize_chapters_default": "最初からチャプターを最小化", "minimize_chapters_default": "最初からチャプターを最小化",
"show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示", "show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
"invalidate_session": "すべての端末からログアウト", "invalidate_session": "すべての端末からログアウト",
@ -112,8 +114,8 @@
"restore_preferences": "設定を復元", "restore_preferences": "設定を復元",
"back_to_home": "ホームに戻る", "back_to_home": "ホームに戻る",
"copy_link": "リンクコピー", "copy_link": "リンクコピー",
"time_code": "タイムコード (秒)", "time_code": "開始時間 (秒)",
"documentation": "ドキュメント", "documentation": "説明書",
"reset_preferences": "設定を初期化", "reset_preferences": "設定を初期化",
"confirm_reset_preferences": "設定をリセットしますか?", "confirm_reset_preferences": "設定をリセットしますか?",
"piped_link": "Pipedリンク", "piped_link": "Pipedリンク",
@ -144,10 +146,17 @@
"show_search_suggestions": "検索語句の候補を表示", "show_search_suggestions": "検索語句の候補を表示",
"delete_automatically": "指定時間後に自動削除", "delete_automatically": "指定時間後に自動削除",
"enable_dearrow": "DeArrow を使用", "enable_dearrow": "DeArrow を使用",
"generate_qrcode": "QRコード生成", "generate_qrcode": "QRコード 生成",
"import_from_json_csv": "JSON/CSVから読み込む", "import_from_json_csv": "JSON/CSVから読み込む",
"download_frame": "この画像を保存", "download_frame": "この画像を保存",
"instance_privacy_policy": "個人情報保護方針" "instance_privacy_policy": "個人情報保護方針",
"add_to_group": "グループに追加",
"instances_not_shown": "現在利用できない公開インスタンスはここに表示されません。",
"concurrent_prefetch_limit": "同時に先読みするストリーム数上限",
"add": "追加",
"invalid_url": "無効なURLです",
"customize": "追加",
"delete_group_confirm": "このグループを削除しますか?"
}, },
"comment": { "comment": {
"pinned_by": "{author} によって固定", "pinned_by": "{author} によって固定",
@ -162,7 +171,9 @@
"ssl_score": "SSLの評価", "ssl_score": "SSLの評価",
"registered_users": "登録利用者数", "registered_users": "登録利用者数",
"version": "バージョン", "version": "バージョン",
"up_to_date": "最新?" "up_to_date": "最新?",
"uptime_30d": "稼働率(30日)",
"api_url": "API の URL"
}, },
"login": { "login": {
"username": "ユーザー名", "username": "ユーザー名",
@ -209,7 +220,9 @@
"days": "{amount}日", "days": "{amount}日",
"weeks": "{amount}週", "weeks": "{amount}週",
"hours": "{amount}時間", "hours": "{amount}時間",
"months": "{amount}月" "months": "{amount}月",
"login_note": "このインスタンス用に作成したアカウントにログインできます。",
"register_note": "この Piped インスタンス用にアカウントを作成します。登録チャンネルと再生リストを同期するために、それらの情報をサーバー内に保存します。アカウントなしでもすべての機能を使用できますが、その場合、すべてのデータは端末内のブラウザーのキャッシュとして保存されます。ユーザー名にメールアドレスを使用しないでください。他のサービスで使っていない安全なパスワードを登録してください。"
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "チャンネル登録: {0}" "subscribed_channels_count": "チャンネル登録: {0}"

View File

@ -40,8 +40,8 @@
"remove_from_playlist": "Kkes seg tebdart n tɣuri", "remove_from_playlist": "Kkes seg tebdart n tɣuri",
"delete_playlist_video_confirm": "Kkes tavidyut seg tebdart n tɣuri?", "delete_playlist_video_confirm": "Kkes tavidyut seg tebdart n tɣuri?",
"create_playlist": "Rnu tabdart n tɣuri", "create_playlist": "Rnu tabdart n tɣuri",
"subscribe": "Qqen - {count}", "subscribe": "Qqen",
"unsubscribe": "Sefsex tuqqna n - {count}", "unsubscribe": "Sefsex tuqqna n",
"view_subscriptions": "Wali imuktaɣen", "view_subscriptions": "Wali imuktaɣen",
"sort_by": "Semyizwer s:", "sort_by": "Semyizwer s:",
"most_recent": "Amaynut akk", "most_recent": "Amaynut akk",

View File

@ -6,7 +6,7 @@
"hide_replies": "답글 숨기기", "hide_replies": "답글 숨기기",
"skip_interaction": "상호 작용 알림 (구독) 스킵", "skip_interaction": "상호 작용 알림 (구독) 스킵",
"show_comments": "댓글 보이기", "show_comments": "댓글 보이기",
"unsubscribe": "구독 취소 - {count}", "unsubscribe": "구독 취소",
"view_subscriptions": "구독 보기", "view_subscriptions": "구독 보기",
"least_recent": "오래된 순", "least_recent": "오래된 순",
"theme": "테마", "theme": "테마",
@ -20,9 +20,9 @@
"skip_non_music": "음악: 음악이 아닌 구간 스킵", "skip_non_music": "음악: 음악이 아닌 구간 스킵",
"skip_self_promo": "셀프 프로모션 스킵", "skip_self_promo": "셀프 프로모션 스킵",
"buffering_goal": "버퍼링 목표 (초)", "buffering_goal": "버퍼링 목표 (초)",
"country_selection": "국가 선택", "country_selection": "국가",
"store_watch_history": "시청 기록 저장", "store_watch_history": "시청 기록 저장",
"language_selection": "언어 선택", "language_selection": "언어",
"no": "아니요", "no": "아니요",
"loop_this_video": "이 동영상 반복", "loop_this_video": "이 동영상 반복",
"auto_play_next_video": "다음 동영상 자동 재생", "auto_play_next_video": "다음 동영상 자동 재생",
@ -31,14 +31,14 @@
"sort_by": "정렬:", "sort_by": "정렬:",
"most_recent": "최신 순", "most_recent": "최신 순",
"channel_name_asc": "채널 이름 (A-Z)", "channel_name_asc": "채널 이름 (A-Z)",
"subscribe": "구독 - {count}", "subscribe": "구독",
"audio_only": "오디오만", "audio_only": "오디오만",
"skip_sponsors": "스폰서 스킵", "skip_sponsors": "스폰서 스킵",
"dark": "다크", "dark": "다크",
"minimize_description_default": "설명 가리기를 기본 설정값으로", "minimize_description_default": "설명 가리기를 기본 설정값으로",
"enabled_codecs": "활성화된 코덱 (여러 개)", "enabled_codecs": "활성화된 코덱 (여러 개)",
"instance_selection": "인스턴스 선택", "instance_selection": "인스턴스",
"import_from_json": "JSON/CSV에서 가져오기", "import_from_json": "JSON에서 가져오기",
"light": "라이트", "light": "라이트",
"autoplay_video": "동영상 자동 재생", "autoplay_video": "동영상 자동 재생",
"default_quality": "기본 화질", "default_quality": "기본 화질",
@ -84,7 +84,7 @@
"back_to_home": "홈으로 가기", "back_to_home": "홈으로 가기",
"minimize_recommendations_default": "추천 동영상 가리기를 기본 설정값으로", "minimize_recommendations_default": "추천 동영상 가리기를 기본 설정값으로",
"invalidate_session": "모든 기기에서 로그아웃", "invalidate_session": "모든 기기에서 로그아웃",
"instance_auth_selection": "인증 인스턴스 선택", "instance_auth_selection": "인증 인스턴스",
"different_auth_instance": "인증에 다른 인스턴스 사용", "different_auth_instance": "인증에 다른 인스턴스 사용",
"clone_playlist": "재생목록 복제", "clone_playlist": "재생목록 복제",
"clone_playlist_success": "성공적으로 복제되었습니다!", "clone_playlist_success": "성공적으로 복제되었습니다!",
@ -103,7 +103,32 @@
"autoplay_next_countdown": "다음 영상 재생까지 대기 시간(초)", "autoplay_next_countdown": "다음 영상 재생까지 대기 시간(초)",
"skip_button_only": "스킵 버튼 표시", "skip_button_only": "스킵 버튼 표시",
"skip_automatically": "자동", "skip_automatically": "자동",
"show_less": "덜 보기" "show_less": "덜 보기",
"create_group": "그룹 만들기",
"reply_count": "{count}개의 댓글",
"import_from_json_csv": "JSON/CSV에서 가져오기",
"concurrent_prefetch_limit": "동시에 스트림을 미리 받아두는 한도",
"no_valid_playlists": "이 파일은 올바른 플레이리스트를 갖고 있지 않습니다!",
"with_playlist": "플레이리스트와 함께 공유",
"playlist_description": "플레이리스트 설명",
"group_name": "그룹 이름",
"cancel": "취소",
"okay": "예",
"download_frame": "프레임 다운로드",
"auto_display_captions": "자동으로 자막 표시",
"instances_not_shown": "이곳에 보이지 않는 공개 인스턴스는 현재 이용할 수 없습니다.",
"min_segment_length": "최소 세그먼트 길이 (초 단위)",
"skip_segment": "세그먼트 건너뛰기",
"enable_dearrow": "DeArrow 활성화",
"chapters_layout_mobile": "모바일에서의 챕터 레이아웃",
"edit_playlist": "플레이리스트 편집",
"playlist_name": "플레이리스트 이름",
"playlist_bookmarked": "북마크됨",
"delete_automatically": "자동으로 삭제하기 시작하는 시간은",
"generate_qrcode": "QR 코드 생성",
"add_to_group": "그룹에 추가",
"instance_privacy_policy": "개인정보 보호 정책",
"show_search_suggestions": "검색 추천을 표시"
}, },
"titles": { "titles": {
"feed": "피드", "feed": "피드",
@ -119,10 +144,13 @@
"player": "플레이어", "player": "플레이어",
"bookmarks": "북마크", "bookmarks": "북마크",
"livestreams": "실시간", "livestreams": "실시간",
"channels": "채널" "channels": "채널",
"dearrow": "",
"channel_groups": "채널 그룹"
}, },
"player": { "player": {
"watch_on": "에서 보기 {0}" "watch_on": "{0}에서 보기",
"failed": "에러 코드 {0}로 인해 실패했습니다. 더 많은 정보는 로그를 확인해주세요"
}, },
"comment": { "comment": {
"pinned_by": "에 의해 고정됨 {author}", "pinned_by": "에 의해 고정됨 {author}",
@ -137,11 +165,14 @@
"instance_name": "인스턴스 이름", "instance_name": "인스턴스 이름",
"registered_users": "등록 된 사용자", "registered_users": "등록 된 사용자",
"version": "버전", "version": "버전",
"up_to_date": "최신 버전?" "up_to_date": "최신 버전?",
"uptime_30d": "가동률 (30일 동안)"
}, },
"login": { "login": {
"username": "유저 이름", "username": "유저 이름",
"password": "비밀번호" "password": "비밀번호",
"password_confirm": "비밀번호 확인",
"passwords_incorrect": "비밀번호가 일치하지 않습니다!"
}, },
"video": { "video": {
"videos": "동영상", "videos": "동영상",
@ -152,7 +183,12 @@
"live": "{0} 라이브", "live": "{0} 라이브",
"shorts": "Shorts", "shorts": "Shorts",
"chapters": "챕터", "chapters": "챕터",
"category": "카테고리" "category": "카테고리",
"all": "모두",
"license": "라이선스",
"visibility": "가시성",
"chapters_horizontal": "가로 방향",
"chapters_vertical": "수직 방향"
}, },
"search": { "search": {
"did_you_mean": "이것을 찾으셨나요: {0}?", "did_you_mean": "이것을 찾으셨나요: {0}?",
@ -163,13 +199,23 @@
"channels": "YouTube: 채널", "channels": "YouTube: 채널",
"playlists": "YouTube: 재생목록", "playlists": "YouTube: 재생목록",
"music_videos": "YT Music: 동영상", "music_videos": "YT Music: 동영상",
"music_albums": "YT Music: 앨범" "music_albums": "YT Music: 앨범",
"music_artists": "유튜브 뮤직: 아티스트"
}, },
"info": { "info": {
"cannot_copy": "복사할 수 없습니다!", "cannot_copy": "복사할 수 없습니다!",
"copied": "복사되었습니다!", "copied": "복사되었습니다!",
"preferences_note": "참고: 설정은 브라우저 로컬 저장소에 저장됩니다. 브라우저 데이터를 삭제하면 초기화됩니다.", "preferences_note": "참고: 설정은 브라우저 로컬 저장소에 저장됩니다. 브라우저 데이터를 삭제하면 초기화됩니다.",
"page_not_found": "페이지를 찾을 수 없음" "page_not_found": "페이지를 찾을 수 없음",
"local_storage": "이 액션은 localStorage가 필요합니다만, 쿠키가 활성화 되어있는지 확인해주실래요?",
"register_note": "계정을 이 Piped 인스턴스에 등록합니다. 이것은 당신의 구독 목록과 재생 목록을 서버에 보존하여 어디서든 동기화할 수 있게 해줍니다. 물론 계정 없이도 모든 기능을 쓸 순 있지만, 모든 데이터들은 당신이 사용중인 브라우저의 로컬 캐쉬에 저장될 것입니다. 메일주소는 \"절대\" 유저명으로 쓰지 말고, 비밀번호는 다른 곳에서 쓰지 않는 것으로 만들어주세요.",
"register_no_email_note": "이메일 주소를 유저명으로 쓰는건 추천하지 않습니다. 그래도 계속할까요?",
"next_video_countdown": "다음 동영상을 {0}초 안에 재생함",
"hours": "{amount}시간",
"login_note": "이 인스턴스에 생성된 계정으로 로그인합니다.",
"days": "{amount}일",
"weeks": "{amount}주",
"months": "{amount}달"
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "구독: {0}" "subscribed_channels_count": "구독: {0}"

View File

@ -1,7 +1,7 @@
{ {
"actions": { "actions": {
"unsubscribe": "Atšaukti prenumeratą - {count}", "unsubscribe": "Atšaukti prenumeratą",
"subscribe": "Prenumeruoti - {count}", "subscribe": "Prenumeruoti",
"instances_list": "Perdavimo šaltinių sąrašas", "instances_list": "Perdavimo šaltinių sąrašas",
"language_selection": "Kalbos pasirinkimas", "language_selection": "Kalbos pasirinkimas",
"store_watch_history": "Saugoti žiūrėjimo istoriją", "store_watch_history": "Saugoti žiūrėjimo istoriją",

230
src/locales/lv.json Normal file
View File

@ -0,0 +1,230 @@
{
"actions": {
"okay": "Labi",
"edit_playlist": "Rediģēt Atskaņošanas Sarakstu",
"group_name": "Grupas nosaukums",
"invalidate_session": "Izrakstīties no visām ierīcēm",
"playlist_bookmarked": "Grāmatzīme Izveidota",
"add_to_group": "Pievienot grupai",
"confirm_reset_preferences": "Vai Jūs vēlaties atiestatīt Jūsu iestatījumus?",
"reply_count": "{count} atbildes",
"restore_preferences": "Restaurēt Iestatījumus",
"piped_link": "Piped saite",
"buffering_goal": "Video Priekšielādes Ilgums (sekundēs)",
"skip_button_only": "Rādīt izlaist pogu",
"minimize_comments_default": "Paslēpt Komentārus pēc Noklusējuma",
"import_from_json_csv": "Importēt no JSON/CSV",
"auto": "Automātisks",
"skip_outro": "Izlaist Atzīšanas Titrus",
"cancel": "Atcelt",
"enable_dearrow": "Ieslēgt DeArrow",
"channel_name_asc": "Kanāla nosaukums (A-Z)",
"playlist_description": "Atskaņošanas Saraksta Apraksts",
"download_as_txt": "Lejupielādēt kā .txt",
"with_timecode": "Dalīties ar laika kodu",
"source_code": "Pirmkods",
"skip_intro": "Izlaist Starpbrīža/Ievada Animāciju",
"copy_link": "Kopēt saiti",
"auto_play_next_video": "Automātiski Atskaņot Nākošo Video",
"instance_selection": "Instance",
"dismiss": "Noraidīt",
"subscribe": "Abonēt",
"clear_history": "Tīrīt Vēsturi",
"loading": "Ielādē...",
"minimize_description": "Paslēpt Aprakstu",
"skip_interaction": "Izlaist Interakcijas atgādinājumu (Abonēt)",
"hide_watched": "Paslēpt skatītos video saturā",
"time_code": "Laika kods (sekundēs)",
"filter": "Filtrs",
"view_ssl_score": "Rādīt SSL Vērtējumu",
"delete_account": "Dzēst Kontu",
"minimize_description_default": "Paslēpt Aprakstu pēc Noklusējuma",
"chapters_layout_mobile": "Nodaļu Izvietojums un Mobilajām Ierīcēm",
"unsubscribe": "Atcelt abonomentu",
"channel_name_desc": "Kanāla nosaukums (Z-A)",
"backup_preferences": "Dublēt Iestatījumus",
"language_selection": "Valoda",
"instance_donations": "Ziedojumi instancei",
"with_playlist": "Dalīties ar atskaņošanas sarakstu",
"enable_lbry_proxy": "Ieslēgt Starpniekserveri priekš LBRY",
"donations": "Ziedojumi Izstrādei",
"store_search_history": "Saglabāt Skatījumu Vēsturi",
"share": "Dalīties",
"delete_automatically": "Automātiski dzēst vēlāk",
"show_less": "Rādīt mazāk",
"loop_this_video": "Atkārtoti Atskaņot Video",
"follow_link": "Atvērt saiti",
"theme": "Motīvs",
"logout": "Izrakstīties no šīs ierīces",
"reset_preferences": "Atiestatīt Iestatījumus",
"dark": "Tumšais",
"download_frame": "Lejupielādēt kadru",
"create_group": "Izveidot grupu",
"hide_replies": "Paslēpt Atbildes",
"least_recent": "Senākie",
"show_chapters": "Nodaļas",
"select_playlist": "Izvēlēties Atskaņošanas Sarakstu",
"most_recent": "Jaunākie",
"country_selection": "Valsts",
"back_to_home": "Atpakaļ uz Sākumlapu",
"skip_self_promo": "Izlaist Neapmaksātu/Pašreklamēšanu",
"default_quality": "Noklusējuma Kvalitāte",
"show_more": "Rādīt Vairāk",
"show_recommendations": "Rādīt Ieteikumus",
"skip_segment": "Izlaist Segmentu",
"delete_playlist": "Dzēst Atskaņošanas Sarakstu",
"minimize_recommendations": "Paslēpt Ieteikumus",
"show_search_suggestions": "Rādīt meklēšanas ieteikumus",
"audio_only": "Tikai Audio",
"skip_filler_tangent": "Izlaist Nesaistītu Saturu",
"disable_lbry": "Izslēgt LBRY Priekš Straumēšanas",
"show_comments": "Rādīt Komentārus",
"store_watch_history": "Saglabāt Skatījumu Vēsturi",
"clone_playlist_success": "Veiksmīgi klonēts!",
"no": "Nē",
"create_playlist": "Izveidot Atskaņošanas Sarakstu",
"instance_auth_selection": "Autentifikācijas Instance",
"yes": "Jā",
"documentation": "Dokumentācija",
"skip_sponsors": "Izlaist Sponsorus",
"sort_by": "Kārtot pēc:",
"delete_playlist_video_confirm": "Vai noņemt video no atskaņošanas saraksta?",
"back": "Atpakaļ",
"different_auth_instance": "Izmantot citu instanci autentifikācijai",
"load_more_replies": "Ielādēt vēl Atbildes",
"enabled_codecs": "Ieslēgtie Kodeki (vairāki)",
"auto_display_captions": "Automātiski Attēlot Subtitrus",
"minimize_comments": "Paslēpt Komentārus",
"view_subscriptions": "Skatīt abonomentus",
"skip_automatically": "Automātiski",
"status_page": "Statuss",
"skip_non_music": "Mūzikā Izlaist Nemūzikas Sadaļu",
"show_markers": "Rādīt Iezīmes Atskaņotājā",
"instances_list": "Instanču saraksts",
"autoplay_video": "Automātiski Atskaņot Video",
"show_watch_on_youtube": "Rādīt Skatīties ar YouTube pogu",
"import_from_json": "Importēt no JSON",
"autoplay_next_countdown": "Noklusējuma laiks līdz nākamajam video (sekundēs)",
"generate_qrcode": "Izveidot QR Kodu",
"enable_sponsorblock": "Ieslēgt Sponsorblock",
"playlist_name": "Atskaņošanas Saraksta Nosaukums",
"default_homepage": "Noklusējuma Sākumlapa",
"minimize_chapters_default": "Paslēpt Nodaļas pēc Noklusējuma",
"instance_privacy_policy": "Konfidencialitātes politika",
"minimize_recommendations_default": "Paslēpt Atskaņošanas Sarakstu pēc Noklusējuma",
"remove_from_playlist": "Noņemt no Atskaņošanas Saraksta",
"search": "Meklēt (Ctrl+K)",
"show_description": "Rādīt Aprakstu",
"light": "Gaišais",
"no_valid_playlists": "Fails nesatur derīgus atskaņošanas sarakstus!",
"skip_preview": "Izlaist Kopsavilkumu",
"please_select_playlist": "Izvēlieties Atskaņošanas Sarakstu",
"skip_highlight": "Izlaist uz Būtisko Momentu",
"export_to_json": "Eksportēt uz JSON",
"min_segment_length": "Minimālā Segmenta Garums (sekundēs)",
"delete_playlist_confirm": "Vai vēlaties dzēst šo atskaņošanas sarakstu?",
"bookmark_playlist": "Izveidot Grāmatzīmi",
"clone_playlist": "Klonēt Atskaņošanas Saturu",
"uses_api_from": "Izmanto API no ",
"add_to_playlist": "Pievienot Atskaņošanas Sarakstam",
"instances_not_shown": "Publiskās instances, kas šeit nav redzamas, pašlaik nav pieejamas.",
"delete_group_confirm": "Vai vēlaties dzēst šo grupu?",
"concurrent_prefetch_limit": "Vienlaicīgu Straumju Ielādes Limits",
"customize": "Pielāgot",
"invalid_url": "Nederīgs URL!",
"add": "Pievienot"
},
"search": {
"all": "YouTube: Visi",
"music_artists": "YT Music: Izpildītāji",
"did_you_mean": "Vai jūs domājāt: {0}?",
"channels": "YouTube: Kanāli",
"music_albums": "YT Music: Albumi",
"music_videos": "YT Music: Video",
"music_playlists": "YT Music: Atskaņošanas Saraksti",
"playlists": "YouTube: Atskaņošanas Saraksti",
"music_songs": "YT Music: Dziesmas",
"videos": "YouTube: Video"
},
"player": {
"watch_on": "Atskaņot uz {0}",
"failed": "Kļūda ar kodu {0}, papildus informācija pieejama žurnālos"
},
"titles": {
"subscriptions": "Abonomenti",
"trending": "Tendences",
"livestreams": "Straumes",
"login": "Ienākt",
"preferences": "Iestatījumi",
"feed": "Saturs",
"channel_groups": "Kanālu grupas",
"account": "Konts",
"instance": "Instance",
"history": "Vēsture",
"bookmarks": "Grāmatzīmes",
"channels": "Kanāli",
"playlists": "Atskaņošanas saraksts",
"register": "Reģistrēties",
"player": "Atskaņotājs",
"dearrow": "DeArrow",
"albums": "Albumi",
"custom_instances": "Pielāgotas instances"
},
"video": {
"all": "Visi",
"live": "{0} Skatītāji",
"shorts": "Shorts",
"ratings_disabled": "Vērtējumi atspējoti",
"visibility": "Redzamība",
"videos": "Video",
"license": "Licenze",
"category": "Kategorija",
"chapters": "Nodaļas",
"chapters_vertical": "Vertikāli",
"watched": "Skatīts",
"chapters_horizontal": "Horizontāli",
"sponsor_segments": "Sponsoru Sadaļas",
"views": "{views} skatījumi"
},
"info": {
"hours": "{amount} stunda/-as",
"next_video_countdown": "Atskaņo video pēc {0}s",
"preferences_note": "Piezīme: iestatījumi tiek saglabāti pārlūkprogrammas vietējā krātuvē. Izdzēšot pārlūkprogrammas datus, tie tiks atiestatīti.",
"local_storage": "Šai darbībai nepieciešams localStorage, vai sīkdatnes ir ieslēgtas?",
"cannot_copy": "Nevar kopēt!",
"login_note": "Ienākt ar kontu, kas izveidots šajā instancē.",
"months": "{amount} mēnesis/-ši",
"page_not_found": "Lapa nav atrasta",
"weeks": "{amount} nedēļa/-as",
"register_note": "Reģistrējiet kontu šai Piped instancei. Jums būs iespējams sinhronizēt abonomentus un atskaņošanas sarakstus, kas tiks saglabāti serverī. Visas funkcijas Jūs varat izmantot bez konta, bet visi Jūsu dati tiks saglabāti vietējā pārlūkprogrammas kešatmiņā. Lūdzu, pārliecinieties, ka nelietojat e-pasta adresi kā lietotājvārdu, un izvēlieties drošu paroli, ko neizmantojat citur.",
"register_no_email_note": "Nav ieteicams izmantot e-pastu kā lietotājvārdu. Vai turpināt?",
"days": "{amount} diena/-as",
"copied": "Kopēts!"
},
"comment": {
"loading": "Ielādē komentārus...",
"user_disabled": "Komentāri ir izslēgti iestatījumos.",
"disabled": "Augšupielādētājs atspējoja komentārus.",
"pinned_by": "{author} piesprauda"
},
"preferences": {
"ssl_score": "SSL Vērtējums",
"version": "Versija",
"up_to_date": "Jaunākā versija?",
"has_cdn": "Vai ir satura piegādes tīkls?",
"instance_name": "Instances Nosaukums",
"registered_users": "Reģistrētie Lietotāji",
"instance_locations": "Instances Atrašanās Vietas",
"uptime_30d": "Darbspējas laiks (30d)",
"api_url": "Api URL"
},
"login": {
"username": "Lietotājvārds",
"password": "Parole",
"password_confirm": "Apstiprināt paroli",
"passwords_incorrect": "Paroles nav vienādas!"
},
"subscriptions": {
"subscribed_channels_count": "Abonēts: {0}"
}
}

View File

@ -13,8 +13,8 @@
}, },
"actions": { "actions": {
"view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക", "view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
"unsubscribe": "സബ്സ്ക്രൈബ് ചെയ്യേണ്ട - {count}", "unsubscribe": "സബ്സ്ക്രൈബ് ചെയ്യേണ്ട",
"subscribe": "സബ്സ്ക്രൈബ് ചെയ്യുക - {count}", "subscribe": "സബ്സ്ക്രൈബ് ചെയ്യുക",
"instances_list": "ഇൻസ്റ്റൻസുകളുടെ പട്ടിക", "instances_list": "ഇൻസ്റ്റൻസുകളുടെ പട്ടിക",
"minimize_description_default": "സ്ഥിരമായി വിവരണം ചെറുതാക്കുക", "minimize_description_default": "സ്ഥിരമായി വിവരണം ചെറുതാക്കുക",
"skip_intro": "ഇടവേള/ആമുഖ ആനിമേഷൻ ഒഴിവാക്കുക", "skip_intro": "ഇടവേള/ആമുഖ ആനിമേഷൻ ഒഴിവാക്കുക",

View File

@ -31,8 +31,8 @@
"most_recent": "Nyest", "most_recent": "Nyest",
"sort_by": "Sorter etter:", "sort_by": "Sorter etter:",
"view_subscriptions": "Vis abonnementer", "view_subscriptions": "Vis abonnementer",
"unsubscribe": "Opphev abonnement - {count}", "unsubscribe": "Opphev abonnement",
"subscribe": "Abonner - {count}", "subscribe": "Abonner",
"enable_lbry_proxy": "Skru på mellomtjener for LBRY", "enable_lbry_proxy": "Skru på mellomtjener for LBRY",
"disable_lbry": "Skru av LBRY-strømming", "disable_lbry": "Skru av LBRY-strømming",
"enabled_codecs": "Aktiverte forskjellige kodek", "enabled_codecs": "Aktiverte forskjellige kodek",

View File

@ -14,14 +14,14 @@
"filter": "Filter", "filter": "Filter",
"skip_filler_tangent": "Opvultangens Overslaan", "skip_filler_tangent": "Opvultangens Overslaan",
"theme": "Thema", "theme": "Thema",
"subscribe": "Abonneren - {count}", "subscribe": "Abonneren",
"skip_non_music": "Muziek Overslaan: Niet-muzieksectie", "skip_non_music": "Muziek Overslaan: Niet-muzieksectie",
"show_comments": "Opmerkingen tonen", "show_comments": "Opmerkingen tonen",
"skip_self_promo": "Onbetaalde-/zelfpromotie overslaan", "skip_self_promo": "Onbetaalde-/zelfpromotie overslaan",
"skip_highlight": "Markering Overslaan", "skip_highlight": "Markering Overslaan",
"skip_interaction": "Interactieherinnering overslaan (abonneren)", "skip_interaction": "Interactieherinnering overslaan (abonneren)",
"show_more": "Meer tonen", "show_more": "Meer tonen",
"unsubscribe": "Afmelden - {count}", "unsubscribe": "Afmelden",
"view_subscriptions": "Abonnementen bekijken", "view_subscriptions": "Abonnementen bekijken",
"enable_sponsorblock": "Sponsorblok inschakelen", "enable_sponsorblock": "Sponsorblok inschakelen",
"skip_preview": "Voorbeschouwing/samenvatting overslaan", "skip_preview": "Voorbeschouwing/samenvatting overslaan",
@ -125,7 +125,14 @@
"auto_display_captions": "Ondertiteling automatisch tonen", "auto_display_captions": "Ondertiteling automatisch tonen",
"import_from_json_csv": "Importeren uit JSON/CSV", "import_from_json_csv": "Importeren uit JSON/CSV",
"download_frame": "Beeld downloaden", "download_frame": "Beeld downloaden",
"instance_privacy_policy": "Privacybeleid" "instance_privacy_policy": "Privacybeleid",
"add_to_group": "Toevoegen aan groep",
"instances_not_shown": "Openbare gevallen die hier niet worden weergegeven, zijn momenteel niet beschikbaar.",
"concurrent_prefetch_limit": "Limiet voor gelijk­tijdige stream-prefetching",
"customize": "Aanpassen",
"add": "Toevoegen",
"invalid_url": "Ongeldige URL!",
"delete_group_confirm": "Deze groep verwijderen?"
}, },
"titles": { "titles": {
"register": "Registreren", "register": "Registreren",
@ -134,19 +141,21 @@
"preferences": "Voorkeuren", "preferences": "Voorkeuren",
"history": "Geschiedenis", "history": "Geschiedenis",
"subscriptions": "Abonnementen", "subscriptions": "Abonnementen",
"trending": "Trending", "trending": "Populair",
"playlists": "Afspeellijsten", "playlists": "Afspeel­sten",
"account": "Account", "account": "Account",
"instance": "Instantie", "instance": "Instantie",
"player": "Speler", "player": "Speler",
"livestreams": "Livestreams", "livestreams": "Livestreams",
"channels": "Kanalen", "channels": "Kanalen",
"bookmarks": "Bladwijzers", "bookmarks": "Blad­zers",
"dearrow": "DeArrow", "dearrow": "DeArrow",
"channel_groups": "Kanaal­groepen" "channel_groups": "Kanaal­groepen",
"albums": "Albums",
"custom_instances": "Aangepaste instanties"
}, },
"player": { "player": {
"watch_on": "Bekijken op {0}", "watch_on": "Bekijken op {0}",
"failed": "Mislukt met foutcode {0}, zie logboeken voor meer informatie" "failed": "Mislukt met foutcode {0}, zie logboeken voor meer informatie"
}, },
"search": { "search": {
@ -190,7 +199,9 @@
"instance_locations": "Instantielocaties", "instance_locations": "Instantielocaties",
"version": "Versie", "version": "Versie",
"up_to_date": "Bijgewerkt?", "up_to_date": "Bijgewerkt?",
"ssl_score": "SSL-score" "ssl_score": "SSL-score",
"uptime_30d": "Uptime (30d)",
"api_url": "Api-URL"
}, },
"comment": { "comment": {
"pinned_by": "Vastgemaakt door {author}", "pinned_by": "Vastgemaakt door {author}",
@ -209,7 +220,9 @@
"days": "{amount} dag(en)", "days": "{amount} dag(en)",
"weeks": "{amount} week/weken", "weeks": "{amount} week/weken",
"months": "{amount} maand(en)", "months": "{amount} maand(en)",
"hours": "{amount} uur" "hours": "{amount} uur",
"login_note": "Log in met een account dat op deze instantie is aangemaakt.",
"register_note": "Registreer een account voor deze Piped-instantie. Hierdoor kunt u uw abonnementen en afspeellijsten synchroniseren met uw account, zodat ze aan de serverkant worden opgeslagen. U kunt alle functies gebruiken zonder account, maar alle gegevens worden opgeslagen in de lokale cache van uw browser. Zorg ervoor dat u GEEN e-mailadres als gebruikersnaam gebruikt en kies een veilig wachtwoord dat u niet elders gebruikt."
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "Geabonneerd op: {0}" "subscribed_channels_count": "Geabonneerd op: {0}"

View File

@ -15,15 +15,17 @@
"playlists": "Listas de lecturas", "playlists": "Listas de lecturas",
"bookmarks": "Marcapaginas", "bookmarks": "Marcapaginas",
"channel_groups": "Grops de cadenas", "channel_groups": "Grops de cadenas",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Albums",
"custom_instances": "Instàncias personalizadas"
}, },
"player": { "player": {
"watch_on": "Veire sus {0}", "watch_on": "Veire sus {0}",
"failed": "Fracàs amb lo còdi derror {0}, consultat los jornals daudit per mai dinformacions" "failed": "Fracàs amb lo còdi derror {0}, consultat los jornals daudit per mai dinformacions"
}, },
"actions": { "actions": {
"subscribe": "Sabonar - {count}", "subscribe": "Sabonar",
"unsubscribe": "Se desabonar - {count}", "unsubscribe": "Se desabonar",
"view_subscriptions": "Veire los abonaments", "view_subscriptions": "Veire los abonaments",
"sort_by": "Triar per:", "sort_by": "Triar per:",
"most_recent": "Mai recents", "most_recent": "Mai recents",
@ -147,7 +149,13 @@
"delete_automatically": "Suprimir automaticament aprèp", "delete_automatically": "Suprimir automaticament aprèp",
"download_frame": "Telecargar fotograma", "download_frame": "Telecargar fotograma",
"generate_qrcode": "Generar un còdi QR", "generate_qrcode": "Generar un còdi QR",
"instance_privacy_policy": "Politica de confidencialitat" "instance_privacy_policy": "Politica de confidencialitat",
"add_to_group": "Apondre al grop",
"instances_not_shown": "Las instàncias publicas que se veson pas aicí son pas disponiblas actualament.",
"customize": "Personalizar",
"invalid_url": "URL invalida !",
"concurrent_prefetch_limit": "Limit de precargament de flux simultanèus",
"add": "Ajustar"
}, },
"preferences": { "preferences": {
"instance_locations": "Localizacion de linstància", "instance_locations": "Localizacion de linstància",
@ -156,7 +164,9 @@
"has_cdn": "A un CDN ?", "has_cdn": "A un CDN ?",
"version": "Version", "version": "Version",
"up_to_date": "Actualizat ?", "up_to_date": "Actualizat ?",
"ssl_score": "Marca SSL" "ssl_score": "Marca SSL",
"api_url": "URL de l'Api",
"uptime_30d": "Temps d'activitat (30 jorns)"
}, },
"login": { "login": {
"username": "Nom dutilizaire", "username": "Nom dutilizaire",
@ -191,7 +201,8 @@
"hours": "{amount} ora(s)", "hours": "{amount} ora(s)",
"months": "{amount} mes(es)", "months": "{amount} mes(es)",
"days": "{amount} jorn(s)", "days": "{amount} jorn(s)",
"weeks": "{amount} setmana(s)" "weeks": "{amount} setmana(s)",
"login_note": "Se connectar amb un compte creat sus aquesta instància."
}, },
"comment": { "comment": {
"disabled": "Lautor a desactivat los comentaris.", "disabled": "Lautor a desactivat los comentaris.",

View File

@ -22,8 +22,8 @@
"failed": "ତ୍ରୁଟି ସଂକେତ {0} ସହିତ ବିଫଳ, ଅଧିକ ସୂଚନା ପାଇଁ ଲଗଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ" "failed": "ତ୍ରୁଟି ସଂକେତ {0} ସହିତ ବିଫଳ, ଅଧିକ ସୂଚନା ପାଇଁ ଲଗଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ"
}, },
"actions": { "actions": {
"subscribe": "ସଦସ୍ୟତା - {count}", "subscribe": "ସଦସ୍ୟତା",
"unsubscribe": "ସଦସ୍ୟତା ରଦ୍ଦ କରନ୍ତୁ - {count}", "unsubscribe": "ସଦସ୍ୟତା ରଦ୍ଦ କରନ୍ତୁ",
"view_subscriptions": "ସଦସ୍ୟତା ଗୁଡ଼ିକ ଦେଖନ୍ତୁ", "view_subscriptions": "ସଦସ୍ୟତା ଗୁଡ଼ିକ ଦେଖନ୍ତୁ",
"sort_by": "ଏହି କ୍ରମରେ ସଜାନ୍ତୁ:", "sort_by": "ଏହି କ୍ରମରେ ସଜାନ୍ତୁ:",
"most_recent": "ସଦ୍ୟତମ", "most_recent": "ସଦ୍ୟତମ",
@ -147,7 +147,10 @@
"cancel": "ବାତିଲ କରନ୍ତୁ", "cancel": "ବାତିଲ କରନ୍ତୁ",
"import_from_json_csv": "JSON/CSV ରୁ ଆମଦାନୀ କରନ୍ତୁ", "import_from_json_csv": "JSON/CSV ରୁ ଆମଦାନୀ କରନ୍ତୁ",
"download_frame": "ଫ୍ରେମକୁ ଆହରଣ କରନ୍ତୁ", "download_frame": "ଫ୍ରେମକୁ ଆହରଣ କରନ୍ତୁ",
"instance_privacy_policy": "ଗୋପନୀୟତା ନୀତି" "instance_privacy_policy": "ଗୋପନୀୟତା ନୀତି",
"add_to_group": "ସମୂହରେ ଯୋଗ କରନ୍ତୁ",
"instances_not_shown": "ଏଠାରେ ଦର୍ଶାଯାଇନଥିବା ସାର୍ବଜନୀନ ଉଦାହରଣଗୁଡ଼ିକ ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ ।",
"concurrent_prefetch_limit": "ସମସାମୟିକ ପ୍ରବାହ ପ୍ରାକ ଫେଚ୍ ସୀମା"
}, },
"comment": { "comment": {
"loading": "ମନ୍ତବ୍ୟ ଲୋଡ୍ ହେଉଛି ...", "loading": "ମନ୍ତବ୍ୟ ଲୋଡ୍ ହେଉଛି ...",
@ -197,7 +200,9 @@
"hours": "{amount} ଘଣ୍ଟା", "hours": "{amount} ଘଣ୍ଟା",
"days": "{amount} ଦିନ", "days": "{amount} ଦିନ",
"weeks": "{amount} ସପ୍ତାହ", "weeks": "{amount} ସପ୍ତାହ",
"months": "{amount} ମାସ" "months": "{amount} ମାସ",
"login_note": "ଏହି ପରିପ୍ରେକ୍ଷୀରେ ନିର୍ମିତ ଏକ ଖାତା ସହିତ ଲଗଇନ କରନ୍ତୁ ।",
"register_note": "ଏହି ପାଇପ ଉଦାହରଣ ପାଇଁ ଏକ ଖାତା ପଞ୍ଜିକରଣ କରନ୍ତୁ । ଏହା ଆପଣଙ୍କୁ ଆପଣଙ୍କର ସବସ୍କ୍ରିପସନ ଏବଂ ପ୍ଲେଲିଷ୍ଟଗୁଡ଼ିକୁ ଆପଣଙ୍କର ଖାତା ସହିତ ସିଙ୍କ କରିବାକୁ ଅନୁମତି ଦେବ, ତେଣୁ ସେଗୁଡ଼ିକ ସର୍ଭର ପାର୍ଶ୍ୱରେ ସଂରକ୍ଷିତ ହୋଇଥାଏ । ଆପଣ ଏକାଉଣ୍ଟ ବିନା ସମସ୍ତ ବୈଶିଷ୍ଟ୍ୟ ବ୍ୟବହାର କରିପାରିବେ, କିନ୍ତୁ ସମସ୍ତ ତଥ୍ୟ ଆପଣଙ୍କର ବ୍ରାଉଜରର ସ୍ଥାନୀୟ କ୍ୟାଶେରେ ସଂରକ୍ଷିତ ହେବ । ଦୟାକରି ନିଶ୍ଚିତ କରନ୍ତୁ ଯେ ଆପଣ ଗୋଟିଏ ଇମେଲ ଠିକଣାକୁ ଆପଣଙ୍କର ଚାଳକ ନାମ ଭାବରେ ବ୍ୟବହାର କରିବେ ନାହିଁ ଏବଂ ଏକ ସୁରକ୍ଷିତ ପ୍ରବେଶ ସଂକେତ ବାଛନ୍ତୁ ଯାହାକୁ ଆପଣ ଅନ୍ୟ କୌଣସି ସ୍ଥାନରେ ବ୍ୟବହାର କରିବେ ନାହିଁ ।"
}, },
"preferences": { "preferences": {
"instance_name": "ଇନଷ୍ଟାନ୍ସ ନାମ", "instance_name": "ଇନଷ୍ଟାନ୍ସ ନାମ",
@ -206,7 +211,8 @@
"instance_locations": "ଇନଷ୍ଟାନ୍ସ ଅବସ୍ଥାନ", "instance_locations": "ଇନଷ୍ଟାନ୍ସ ଅବସ୍ଥାନ",
"has_cdn": "CDN ଅଛି କି?", "has_cdn": "CDN ଅଛି କି?",
"up_to_date": "ଅଦ୍ୟାବଧି?", "up_to_date": "ଅଦ୍ୟାବଧି?",
"ssl_score": "SSL ସ୍କୋର" "ssl_score": "SSL ସ୍କୋର",
"uptime_30d": "ସମୟ ଅପରିବର୍ତ୍ତନୀୟ (30d)"
}, },
"login": { "login": {
"password": "ପାସୱାର୍ଡ", "password": "ପାସୱାର୍ଡ",

View File

@ -15,15 +15,17 @@
"channels": "Kanały", "channels": "Kanały",
"bookmarks": "Zakładki", "bookmarks": "Zakładki",
"channel_groups": "Grupy kanałów", "channel_groups": "Grupy kanałów",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Albumy",
"custom_instances": "Niestandardowe instancje"
}, },
"player": { "player": {
"watch_on": "Zobacz na {0}", "watch_on": "Zobacz na {0}",
"failed": "Niepowodzenie z powodu kodu błędu {0}, przejrzyj logi, aby uzyskać więcej informacji" "failed": "Niepowodzenie z powodu kodu błędu {0}, przejrzyj logi, aby uzyskać więcej informacji"
}, },
"actions": { "actions": {
"subscribe": "Subskrybuj - {count}", "subscribe": "Subskrybuj",
"unsubscribe": "Odsubskrybuj - {count}", "unsubscribe": "Odsubskrybuj",
"view_subscriptions": "Zarządzaj subskrybcjami", "view_subscriptions": "Zarządzaj subskrybcjami",
"sort_by": "Sortuj:", "sort_by": "Sortuj:",
"most_recent": "Najnowsze", "most_recent": "Najnowsze",
@ -43,7 +45,7 @@
"skip_highlight": "Przechodź do meritum filmu", "skip_highlight": "Przechodź do meritum filmu",
"skip_filler_tangent": "Pomijaj wstawki humorystyczne", "skip_filler_tangent": "Pomijaj wstawki humorystyczne",
"theme": "Motyw", "theme": "Motyw",
"auto": "Automatyczna", "auto": "Automatyczny",
"dark": "Ciemny", "dark": "Ciemny",
"light": "Jasny", "light": "Jasny",
"autoplay_video": "Autoodtwarzanie", "autoplay_video": "Autoodtwarzanie",
@ -147,7 +149,18 @@
"generate_qrcode": "Wygeneruj kod QR", "generate_qrcode": "Wygeneruj kod QR",
"import_from_json_csv": "Import z pliku JSON/CSV", "import_from_json_csv": "Import z pliku JSON/CSV",
"download_frame": "Pobierz klatkę", "download_frame": "Pobierz klatkę",
"instance_privacy_policy": "Polityki prywatności" "instance_privacy_policy": "Polityki prywatności",
"add_to_group": "Dodaj do grupy",
"instances_not_shown": "Instancje publiczne, które nie są tutaj pokazane, są obecnie niedostępne.",
"concurrent_prefetch_limit": "Limit równoczesnego pobierania wstępnego strumienia",
"customize": "Dostosuj",
"invalid_url": "Nieprawidłowy adres URL!",
"add": "Dodaj",
"delete_group_confirm": "Usunąć tę grupę?",
"creator_replied": "Twórca odpowiedział",
"creator_liked": "Twórca polubił",
"invalid_input": "Nieprawidłowe dane wejściowe",
"playback_speed": "Szybkość odtwarzania"
}, },
"comment": { "comment": {
"pinned_by": "Przypięty przez {author}", "pinned_by": "Przypięty przez {author}",
@ -162,7 +175,9 @@
"registered_users": "Zarejestrowani użytkownicy", "registered_users": "Zarejestrowani użytkownicy",
"version": "Wersja", "version": "Wersja",
"up_to_date": "Aktualna?", "up_to_date": "Aktualna?",
"ssl_score": "Ocena SSL" "ssl_score": "Ocena SSL",
"uptime_30d": "Czas pracy (30d)",
"api_url": "Adres URL interfejsu API"
}, },
"login": { "login": {
"username": "Nazwa użytkownika", "username": "Nazwa użytkownika",
@ -209,7 +224,9 @@
"days": "{amount} dni", "days": "{amount} dni",
"weeks": "{amount} tygodnie", "weeks": "{amount} tygodnie",
"hours": "{amount} godziny", "hours": "{amount} godziny",
"months": "{amount} miesiące" "months": "{amount} miesiące",
"login_note": "Zaloguj się na konto utworzone w tej instancji.",
"register_note": "Zarejestruj konto dla tej instancji Piped. Umożliwi to synchronizację subskrypcji i list odtwarzania z twoim kontem, dzięki czemu będą one przechowywane po stronie serwera. Możesz korzystać ze wszystkich funkcji bez konta, ale wszystkie dane będą przechowywane w lokalnej pamięci podręcznej Twojej przeglądarki. Upewnij się, że NIE używasz adresu e-mail jako nazwy użytkownika i wybierz bezpieczne hasło, którego nie używasz nigdzie indziej."
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "Licznik subskrybcji: {0}" "subscribed_channels_count": "Licznik subskrybcji: {0}"

View File

@ -15,7 +15,9 @@
"channels": "Canais", "channels": "Canais",
"bookmarks": "Marcadores", "bookmarks": "Marcadores",
"channel_groups": "Grupos de canais", "channel_groups": "Grupos de canais",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Álbuns",
"custom_instances": "Instâncias personalizadas"
}, },
"actions": { "actions": {
"sort_by": "Ordenar por:", "sort_by": "Ordenar por:",
@ -25,9 +27,9 @@
"back": "Recuar", "back": "Recuar",
"uses_api_from": "Utiliza a \"API\" de ", "uses_api_from": "Utiliza a \"API\" de ",
"enable_sponsorblock": "Ativar \"SponsorBlock\"", "enable_sponsorblock": "Ativar \"SponsorBlock\"",
"skip_intro": "Ignorar intermissão/animação de introdução", "skip_intro": "Ignorar intromissão/animação de introdução",
"skip_outro": "Ignorar \"Endcards\"/Créditos", "skip_outro": "Ignorar cartões finais/créditos",
"skip_preview": "Ignorar pré-visualização/recapitulando", "skip_preview": "Ignorar pré-visualização/recapitulação",
"auto": "Automático", "auto": "Automático",
"dark": "Escuro", "dark": "Escuro",
"autoplay_video": "Reproduzir vídeos automaticamente", "autoplay_video": "Reproduzir vídeos automaticamente",
@ -36,7 +38,7 @@
"country_selection": "País", "country_selection": "País",
"default_homepage": "Página inicial padrão", "default_homepage": "Página inicial padrão",
"show_comments": "Mostrar comentários", "show_comments": "Mostrar comentários",
"minimize_description_default": "Minimizar descrição por omissão", "minimize_description_default": "Por definição, minimizar descrição",
"store_watch_history": "Guardar histórico de visualizações", "store_watch_history": "Guardar histórico de visualizações",
"instances_list": "Lista de instâncias", "instances_list": "Lista de instâncias",
"enabled_codecs": "Codificadores ativados (vários)", "enabled_codecs": "Codificadores ativados (vários)",
@ -52,13 +54,13 @@
"show_recommendations": "Mostrar recomendações", "show_recommendations": "Mostrar recomendações",
"disable_lbry": "Desativar \"LBRY\" para emissões", "disable_lbry": "Desativar \"LBRY\" para emissões",
"enable_lbry_proxy": "Ativar proxy para \"LBRY\"", "enable_lbry_proxy": "Ativar proxy para \"LBRY\"",
"view_ssl_score": "Ver avaliação \"SSL\"", "view_ssl_score": "Ver avaliação SSL",
"search": "Pesquisa (Ctrl+K)", "search": "Pesquisa (Ctrl+K)",
"filter": "Filtrar", "filter": "Filtrar",
"loading": "A carregar...", "loading": "A carregar...",
"clear_history": "Limpar histórico", "clear_history": "Limpar histórico",
"subscribe": "Subscrever - {count}", "subscribe": "Subscrever",
"unsubscribe": "Anular subscrição - {count}", "unsubscribe": "Anular subscrição",
"view_subscriptions": "Ver subscrições", "view_subscriptions": "Ver subscrições",
"channel_name_desc": "Nome do canal (Z-A)", "channel_name_desc": "Nome do canal (Z-A)",
"skip_sponsors": "Ignorar patrocínios", "skip_sponsors": "Ignorar patrocínios",
@ -77,9 +79,9 @@
"buffering_goal": "Objetivo de 'buffer' (em segundos)", "buffering_goal": "Objetivo de 'buffer' (em segundos)",
"skip_filler_tangent": "Ignorar segmentos de preenchimento", "skip_filler_tangent": "Ignorar segmentos de preenchimento",
"add_to_playlist": "Adicionar à lista de reprodução", "add_to_playlist": "Adicionar à lista de reprodução",
"delete_playlist": "Apagar lista de reprodução", "delete_playlist": "Eliminar lista de reprodução",
"select_playlist": "Selecionar uma lista de reprodução", "select_playlist": "Selecionar uma lista de reprodução",
"delete_playlist_confirm": "Apagar esta lista de reprodução?", "delete_playlist_confirm": "Eliminar esta lista de reprodução?",
"please_select_playlist": "Selecione uma 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?", "delete_playlist_video_confirm": "Remover vídeo da lista de reprodução?",
"remove_from_playlist": "Remover da lista de reprodução", "remove_from_playlist": "Remover da lista de reprodução",
@ -87,9 +89,9 @@
"clone_playlist_success": "Clonada com sucesso!", "clone_playlist_success": "Clonada com sucesso!",
"clone_playlist": "Clonar lista de reprodução", "clone_playlist": "Clonar lista de reprodução",
"show_markers": "Mostrar marcas no reprodutor", "show_markers": "Mostrar marcas no reprodutor",
"delete_account": "Apagar conta", "delete_account": "Eliminar conta",
"logout": "Terminar sessão neste dispositivo", "logout": "Terminar sessão neste dispositivo",
"minimize_recommendations_default": "Minimizar recomendações por omissão", "minimize_recommendations_default": "Por definição, minimizar recomendações",
"invalidate_session": "Terminar sessão em todos os dispositivos", "invalidate_session": "Terminar sessão em todos os dispositivos",
"different_auth_instance": "Usar uma instância diferente para autenticação", "different_auth_instance": "Usar uma instância diferente para autenticação",
"instance_auth_selection": "Instância para autenticação", "instance_auth_selection": "Instância para autenticação",
@ -101,18 +103,18 @@
"piped_link": "Ligação do Piped", "piped_link": "Ligação do Piped",
"backup_preferences": "Exportar preferências", "backup_preferences": "Exportar preferências",
"store_search_history": "Guardar histórico de pesquisas", "store_search_history": "Guardar histórico de pesquisas",
"hide_watched": "Ocultar do feed os vídeos visualizados", "hide_watched": "Ocultar, do feed, os vídeos já vistos",
"documentation": "Documentação", "documentation": "Documentação",
"status_page": "Estado", "status_page": "Estado",
"source_code": "Código-fonte", "source_code": "Código-fonte",
"instance_donations": "Doações de instâncias", "instance_donations": "Doações de instâncias",
"minimize_chapters_default": "Minimizar capítulos por omissão", "minimize_chapters_default": "Por definição, minimizar capítulos",
"show_watch_on_youtube": "Mostrar botão Ver no YouTube", "show_watch_on_youtube": "Mostrar botão Ver no YouTube",
"minimize_comments": "Minimizar comentários", "minimize_comments": "Minimizar comentários",
"back_to_home": "Voltar ao início", "back_to_home": "Voltar ao início",
"copy_link": "Copiar ligação", "copy_link": "Copiar ligação",
"time_code": "Código de tempo (em segundos)", "time_code": "Código de tempo (em segundos)",
"minimize_comments_default": "Minimizar comentários por omissão", "minimize_comments_default": "Por definição, minimizar comentários",
"share": "Partilhar", "share": "Partilhar",
"with_timecode": "Partilhar com código de tempo", "with_timecode": "Partilhar com código de tempo",
"show_chapters": "Capítulos", "show_chapters": "Capítulos",
@ -137,13 +139,24 @@
"edit_playlist": "Editar lista de reprodução", "edit_playlist": "Editar lista de reprodução",
"playlist_name": "Nome da lista de reprodução", "playlist_name": "Nome da lista de reprodução",
"show_search_suggestions": "Mostrar sugestões de pesquisa", "show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Aplicações recentemente adicionadas", "chapters_layout_mobile": "Esquema de capítulos em dispositivos móveis",
"enable_dearrow": "Ativar o DeArrow", "enable_dearrow": "Ativar DeArrow",
"delete_automatically": "Eliminar automaticamente após", "delete_automatically": "Eliminar automaticamente após",
"generate_qrcode": "Gerar código QR", "generate_qrcode": "Gerar código QR",
"import_from_json_csv": "Importar de JSON/CSV", "import_from_json_csv": "Importar de JSON/CSV",
"download_frame": "Quadro de transferência", "download_frame": "Descarregar moldura",
"instance_privacy_policy": "Política de privacidade" "instance_privacy_policy": "Política de privacidade",
"add_to_group": "Adicionar ao grupo",
"instances_not_shown": "As instâncias públicas que, atualmente, estejam indisponíveis, não serão mostradas aqui.",
"concurrent_prefetch_limit": "Limite de obtenção para fluxos simultâneos",
"customize": "Personalizar",
"invalid_url": "URL inválido!",
"add": "Adicionar",
"delete_group_confirm": "Eliminar este grupo?",
"creator_replied": "O criador respondeu",
"creator_liked": "O criador gostou",
"invalid_input": "Entrada inválida",
"playback_speed": "Velocidade de reprodução"
}, },
"preferences": { "preferences": {
"instance_name": "Nome da instância", "instance_name": "Nome da instância",
@ -152,13 +165,15 @@
"has_cdn": "Tem CDN?", "has_cdn": "Tem CDN?",
"version": "Versão", "version": "Versão",
"registered_users": "Utilizadores registados", "registered_users": "Utilizadores registados",
"up_to_date": "Atualizada?" "up_to_date": "Atualizada?",
"uptime_30d": "Tempo de atividade (30d)",
"api_url": "URL da API"
}, },
"login": { "login": {
"password": "Palavra-passe", "password": "Palavra-passe",
"username": "Nome de utilizador", "username": "Nome de utilizador",
"passwords_incorrect": "As palavras-passe não coincidem!", "passwords_incorrect": "As palavras-passe não coincidem!",
"password_confirm": "Confirmar a palavra-passe" "password_confirm": "Confirmar palavra-passe"
}, },
"video": { "video": {
"videos": "Vídeos", "videos": "Vídeos",
@ -209,9 +224,11 @@
"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.", "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?", "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", "next_video_countdown": "O próximo vídeo será reproduzido dentro de {0} segundos",
"hours": "{quantidade} hora(s)", "hours": "{amount} hora(s)",
"days": "{quantidade} dia(s)", "days": "{amount} dia(s)",
"weeks": "{quantidade} semana(s)", "weeks": "{amount} semana(s)",
"months": "{quantidade} mês(es)" "months": "{amount} mês(es)",
"login_note": "Inicie sessão com uma conta criada nesta instância.",
"register_note": "Registe uma conta para esta instância Piped. Isso permitirá que sincronize as subscrições e listas de reprodução com a sua conta, para que elas sejam armazenadas nomo servidor. Pode usar todas as funções sem uma conta, mas todos os dados serão armazenados na cache do navegador. Certifique-se de que NÃO utiliza um endereço de e-mail como nome de utilizador e escolha uma palavra-passe segura."
} }
} }

View File

@ -1,138 +1,149 @@
{ {
"actions": { "actions": {
"view_subscriptions": "Ver Inscrições", "view_subscriptions": "Ver inscrições",
"back": "Voltar", "back": "Voltar",
"most_recent": "Mais Recente", "most_recent": "Mais recentes",
"least_recent": "Menos Recente", "least_recent": "Mais antigos",
"sort_by": "Ordenar por:", "sort_by": "Ordenar por:",
"channel_name_asc": "Nome do Canal (A-Z)", "channel_name_asc": "Nome do canal (A-Z)",
"channel_name_desc": "Nome do Canal (Z-A)", "channel_name_desc": "Nome do canal (Z-A)",
"dark": "Escuro", "dark": "Escuro",
"light": "Claro", "light": "Claro",
"show_comments": "Exibir Comentários", "show_comments": "Mostrar comentários",
"country_selection": "País", "country_selection": "Região",
"default_homepage": "Página Inicial Padrão", "default_homepage": "Página inicial padrão",
"default_quality": "Qualidade Padrão", "default_quality": "Qualidade padrão",
"autoplay_video": "Reprodução Automática", "autoplay_video": "Reprodução automática",
"minimize_description_default": "Minimizar Descrição por padrão", "minimize_description_default": "Ocultar descrição por padrão",
"theme": "Tema", "theme": "Tema",
"audio_only": "Apenas Áudio", "audio_only": "Apenas áudio",
"subscribe": "Inscrever-se - {count}", "subscribe": "Inscrever-se",
"unsubscribe": "Desinscrever-se - {count}", "unsubscribe": "Cancelar inscrição",
"skip_sponsors": "Pular Patrocinadores", "skip_sponsors": "Pular patrocinadores",
"auto": "Automático", "auto": "Automático",
"uses_api_from": "Usa a API de ", "uses_api_from": "Usa a API ",
"enable_sponsorblock": "Ativar Sponsorblock", "enable_sponsorblock": "Ativar Sponsorblock",
"skip_interaction": "Pular Lembrete de Interação (Inscrever-se)", "skip_interaction": "Pular lembrete de interação (inscrever-se)",
"skip_self_promo": "Pular Promoção não paga/Autopromoção", "skip_self_promo": "Pular autopromoção gratuita",
"show_markers": "Exibir Marcadores no Player", "show_markers": "Mostrar marcadores durante a reprodução",
"skip_intro": "Pular Intervalo/Introdução Animada", "skip_intro": "Pular intervalo/animação de introdução",
"skip_outro": "Pular Créditos/Cartões finais", "skip_outro": "Pular considerações/cartões finais",
"skip_preview": "Pular Pré-Visualização/Recapitulação", "skip_preview": "Pular pré-visualização/recapitulação",
"skip_highlight": "Pular Destaque", "skip_highlight": "Pular destaque",
"buffering_goal": "Cache de Buffer (em segundos)", "buffering_goal": "Meta de Buffer (em segundos)",
"skip_non_music": "Pular Música: Seção não Musical", "skip_non_music": "Pular música: Partes sem relação com música",
"skip_filler_tangent": "Pular Enchimento Tangencial", "skip_filler_tangent": "Pular segmentos de preenchimento",
"enabled_codecs": "Codecs Ativados (Múltiplos)", "enabled_codecs": "Codecs ativados (vários)",
"language_selection": "Idioma", "language_selection": "Idioma",
"yes": "Sim", "yes": "Sim",
"show_more": "Mostrar Mais", "show_more": "Mostrar mais",
"export_to_json": "Exportar para JSON", "export_to_json": "Exportar como JSON",
"donations": "Doações de desenvolvimento", "donations": "Fazer doação para o desenvolvimento",
"minimize_recommendations": "Minimizar Recomendações", "minimize_recommendations": "Ocultar recomendações",
"loading": "Carregando...", "loading": "Carregando...",
"hide_replies": "Ocultar Respostas", "hide_replies": "Ocultar respostas",
"minimize_description": "Minimizar Descrição", "minimize_description": "Ocultar descrição",
"load_more_replies": "Carregar mais Respostas", "load_more_replies": "Carregar mais respostas",
"create_playlist": "Criar Playlist", "create_playlist": "Criar playlist",
"delete_playlist": "Excluir Playlist", "delete_playlist": "Excluir playlist",
"select_playlist": "Selecionar uma Playlist", "select_playlist": "Escolha uma playlist",
"add_to_playlist": "Adicionar à playlist", "add_to_playlist": "Adicionar à playlist",
"delete_playlist_confirm": "Excluir esta playlist?", "delete_playlist_confirm": "Excluir esta playlist?",
"delete_playlist_video_confirm": "Remover vídeo da playlist?", "delete_playlist_video_confirm": "Remover vídeo da playlist?",
"please_select_playlist": "Por favor, selecione uma playlist", "please_select_playlist": "Por favor, escolha uma playlist",
"remove_from_playlist": "Remover da playlist", "remove_from_playlist": "Remover da playlist",
"view_ssl_score": "Ver Pontuação SSL", "view_ssl_score": "Ver pontuação SSL",
"disable_lbry": "Desativar LBRY para Streaming", "disable_lbry": "Desativar LBRY para transmissões",
"enable_lbry_proxy": "Ativar Proxy para LBRY", "enable_lbry_proxy": "Usar proxy em LBRY",
"import_from_json": "Importar de JSON", "import_from_json": "Importar arquivo JSON",
"loop_this_video": "Repetir este Vídeo", "loop_this_video": "Repetir este vídeo",
"instances_list": "Lista de Instâncias", "instances_list": "Lista de instâncias",
"clear_history": "Limpar Histórico", "clear_history": "Limpar histórico",
"search": "Pesquisar (Ctrl+K)", "search": "Pesquisar (Ctrl+K)",
"no": "Não", "no": "Não",
"show_description": "Exibir Descrição", "show_description": "Mostrar descrição",
"instance_selection": "Instância", "instance_selection": "Instância",
"auto_play_next_video": "Autorreproduzir Próximo Vídeo", "auto_play_next_video": "Reproduzir próximo vídeo automaticamente",
"filter": "Filtro", "filter": "Filtrar",
"store_watch_history": "Salvar Histórico de Exibição", "store_watch_history": "Salvar histórico de exibição",
"show_recommendations": "Exibir Recomendações", "show_recommendations": "Mostrar recomendações",
"minimize_comments_default": "Minimizar Comentários por padrão", "minimize_comments_default": "Ocultar comentários por padrão",
"minimize_comments": "Minimizar Comentários", "minimize_comments": "Ocultar comentários",
"different_auth_instance": "Use uma instância diferente para autenticação", "different_auth_instance": "Usar uma instância diferente para autenticação",
"delete_account": "Deletar Conta", "delete_account": "Excluir conta",
"invalidate_session": "Sair de todos os dispositivos", "invalidate_session": "Sair de todos os dispositivos",
"clone_playlist": "Clonar Playlist", "clone_playlist": "Clonar playlist",
"backup_preferences": "Fazer backup das preferências", "backup_preferences": "Exportar preferências",
"logout": "Sair deste dispositivo", "logout": "Sair deste dispositivo",
"copy_link": "Copiar link", "copy_link": "Copiar link",
"store_search_history": "Armazenar Histórico de Pesquisa", "store_search_history": "Salvar histórico de pesquisa",
"hide_watched": "Ocultar vídeos assistidos no feed", "hide_watched": "Ocultar vídeos assistidos no feed",
"status_page": "Estado", "status_page": "Status",
"source_code": "Código fonte", "source_code": "Código fonte",
"instance_donations": "Doações de instâncias", "instance_donations": "Fazer doação para instâncias",
"instance_privacy_policy": "Política de Privacidade", "instance_privacy_policy": "Política de Privacidade",
"instance_auth_selection": "Instância de Autenticação", "instance_auth_selection": "Instância para autenticação",
"clone_playlist_success": "Clonada com sucesso!", "clone_playlist_success": "Clonada com sucesso!",
"download_as_txt": "Baixar como .txt", "download_as_txt": "Baixar como .txt",
"restore_preferences": "Restaurar preferências", "restore_preferences": "Restaurar preferências",
"back_to_home": "Voltar ao início", "back_to_home": "Ir para a página inicial",
"share": "Compartilhar", "share": "Compartilhar",
"with_timecode": "Compartilhar com código de tempo", "with_timecode": "Compartilhar no momento atual",
"piped_link": "Link do Piped", "piped_link": "Link do Piped",
"follow_link": "Seguir link", "follow_link": "Abrir link",
"time_code": "Código de tempo (em segundos)", "time_code": "Momento atual (em segundos)",
"show_chapters": "Capítulos", "show_chapters": "Capítulos",
"confirm_reset_preferences": "Tem certeza de que deseja redefinir suas preferências?", "confirm_reset_preferences": "Tem certeza de que deseja redefinir suas preferências?",
"reset_preferences": "Redefinir preferências", "reset_preferences": "Redefinir preferências",
"documentation": "Documentação", "documentation": "Documentação",
"reply_count": "{count} respostas", "reply_count": "{count} resposta(s)",
"minimize_recommendations_default": "Minimizar Recomendações por padrão", "minimize_recommendations_default": "Ocultar recomendações por padrão",
"show_watch_on_youtube": "Mostrar Botão Assistir no YouTube", "show_watch_on_youtube": "Mostrar botão \"Assistir no YouTube\"",
"minimize_chapters_default": "Minimizar Capítulos por padrão", "minimize_chapters_default": "Ocultar capítulos por padrão",
"no_valid_playlists": "O arquivo não contém playlists válidas!", "no_valid_playlists": "O arquivo não contém playlists válidas!",
"with_playlist": "Compartilhar com playlist", "with_playlist": "Compartilhar com a playlist",
"bookmark_playlist": "Favorito", "bookmark_playlist": "Favorito",
"playlist_bookmarked": "Favoritado", "playlist_bookmarked": "Salvo como favorito",
"skip_automatically": "Automaticamente", "skip_automatically": "Automaticamente",
"skip_segment": "Ignorar Segmento", "skip_segment": "Pular segmento",
"min_segment_length": "Comprimento Mínimo do Segmento (em segundos)", "min_segment_length": "Comprimento mínimo do segmento (em segundos)",
"skip_button_only": "Mostrar botão pular", "skip_button_only": "Mostrar botão \"Pular\"",
"show_less": "Mostrar menos", "show_less": "Mostrar menos",
"autoplay_next_countdown": "Contagem regressiva padrão até o próximo vídeo (em segundos)", "autoplay_next_countdown": "Contagem regressiva padrão até o próximo vídeo (em segundos)",
"dismiss": "Liberar", "dismiss": "Dispensar",
"cancel": "Cancelar", "cancel": "Cancelar",
"edit_playlist": "Editar playlist", "edit_playlist": "Editar playlist",
"playlist_description": "Descrição da playlist", "playlist_description": "Descrição da playlist",
"okay": "Ok", "okay": "OK",
"playlist_name": "Nome da playlist", "playlist_name": "Nome da playlist",
"auto_display_captions": "Exibição Automática de Legendas", "auto_display_captions": "Exibição automática de legendas",
"create_group": "Criar grupo", "create_group": "Criar grupo",
"group_name": "Nome do grupo", "group_name": "Nome do grupo",
"show_search_suggestions": "Mostrar sugestões de pesquisa", "show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Layout dos Capítulos no Celular", "chapters_layout_mobile": "Layout de capítulos em dispositivos móveis",
"delete_automatically": "Deletar automaticamente após", "delete_automatically": "Excluir automaticamente após",
"generate_qrcode": "Gerar código QR", "generate_qrcode": "Código QR",
"enable_dearrow": "Ativar DeArrow", "enable_dearrow": "Ativar DeArrow",
"import_from_json_csv": "Importar de JSON/CSV", "import_from_json_csv": "Importar arquivo JSON/CSV",
"download_frame": "Baixar quadro" "download_frame": "Capturar imagem",
"add_to_group": "Adicionar ao grupo",
"instances_not_shown": "As instâncias públicas que não são mostradas aqui estão indisponíveis no momento.",
"concurrent_prefetch_limit": "Limite de pré-busca de fluxo simultâneo",
"customize": "Personalizar",
"invalid_url": "URL inválida!",
"add": "Adicionar",
"delete_group_confirm": "Excluir este grupo?",
"creator_replied": "O autor respondeu",
"creator_liked": "O autor gostou",
"playback_speed": "Velocidade de reprodução",
"invalid_input": "Entrada inválida"
}, },
"titles": { "titles": {
"history": "Histórico", "history": "Histórico",
"trending": "Em alta", "trending": "Em alta",
"preferences": "Preferências", "preferences": "Preferências",
"register": "Registrar", "register": "Criar conta",
"login": "Entrar", "login": "Fazer login",
"playlists": "Playlists", "playlists": "Playlists",
"feed": "Feed", "feed": "Feed",
"subscriptions": "Inscrições", "subscriptions": "Inscrições",
@ -142,42 +153,46 @@
"channels": "Canais", "channels": "Canais",
"livestreams": "Transmissões ao vivo", "livestreams": "Transmissões ao vivo",
"bookmarks": "Favoritos", "bookmarks": "Favoritos",
"channel_groups": "Grupos de Canais", "channel_groups": "Grupos de canais",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Álbuns",
"custom_instances": "Instâncias personalizadas"
}, },
"player": { "player": {
"watch_on": "Assistir no {0}", "watch_on": "Ver em {0}",
"failed": "Falhou com código de erro {0}, veja os logs para mais informações" "failed": "Falhou com código de erro {0}, consulte os logs para obter mais informações"
}, },
"comment": { "comment": {
"pinned_by": "Fixado por {author}", "pinned_by": "Fixado por {author}",
"user_disabled": "Os comentários estão desativados nas configurações.", "user_disabled": "Os comentários estão desativados nas configurações.",
"disabled": "Os comentários são desativados pelo remetente.", "disabled": "Os comentários foram desativados pelo autor.",
"loading": "Carregando comentários..." "loading": "Carregando comentários..."
}, },
"preferences": { "preferences": {
"registered_users": "Usuários Registrados", "registered_users": "Usuários registrados",
"version": "Versão", "version": "Versão",
"instance_name": "Nome da Instância", "instance_name": "Nome da instância",
"instance_locations": "Localizações da Instância", "instance_locations": "Localizações da instância",
"has_cdn": "Tem CDN?", "has_cdn": "Tem CDN?",
"up_to_date": "Atualizado?", "up_to_date": "Atualizado?",
"ssl_score": "Pontuação SSL" "ssl_score": "Pontuação SSL",
"uptime_30d": "Tempo de atividade (30d)",
"api_url": "URL da Api"
}, },
"login": { "login": {
"username": "Nome de usuário", "username": "Usuário",
"password": "Senha", "password": "Senha",
"password_confirm": "Confirme senha", "password_confirm": "Confirmar senha",
"passwords_incorrect": "As senhas não correspondem!" "passwords_incorrect": "As senhas não coincidem!"
}, },
"video": { "video": {
"videos": "Vídeos", "videos": "Vídeos",
"views": "{views} visualizações", "views": "{views} visualizações",
"chapters": "Capítulos", "chapters": "Capítulos",
"live": "{0} Ao vivo", "live": "{0} ao vivo",
"watched": "Assistido", "watched": "Assistido",
"ratings_disabled": "Avaliações Desativadas", "ratings_disabled": "Avaliações desativadas",
"sponsor_segments": "Segmentos de Patrocinadores", "sponsor_segments": "Segmentos patrocinados",
"shorts": "Shorts", "shorts": "Shorts",
"all": "Todos", "all": "Todos",
"category": "Categoria", "category": "Categoria",
@ -195,21 +210,23 @@
"music_videos": "YT Music: Vídeos", "music_videos": "YT Music: Vídeos",
"music_albums": "YT Music: Álbuns", "music_albums": "YT Music: Álbuns",
"music_playlists": "YT Music: Playlists", "music_playlists": "YT Music: Playlists",
"all": "YouTube: Tudo", "all": "YouTube: Todos",
"music_artists": "YT Music: Artistas" "music_artists": "YT Music: Artistas"
}, },
"info": { "info": {
"copied": "Copiado!", "copied": "Copiado!",
"cannot_copy": "Não foi possível copiar!", "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.", "preferences_note": "Nota: as preferências são salvas no armazenamento local do seu navegador. Excluir os dados do seu navegador irá redefini-los.",
"page_not_found": "página não encontrada", "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?", "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", "next_video_countdown": "Próximo vídeo em {0}s",
"hours": "{amount} hora(s)", "hours": "{amount} hora(s)",
"days": "{amount} dia(s)", "days": "{amount} dia(s)",
"weeks": "{amount} semana(s)", "weeks": "{amount} semana(s)",
"months": "{amount} mês/meses" "months": "{amount} mês(es)",
"login_note": "Faça login com uma conta registrada nesta instância.",
"register_note": "Registre uma conta para esta instância Piped. Isto irá permitir que você sincronize suas inscrições e playlists com sua conta, para que sejam armazenadas no lado do servidor. Você pode usar todas as funções sem uma conta, mas todos os dados serão armazenados no cache local do seu navegador. Por favor certifique-se de NÃO usar seu endereço de e-mail como nome de usuário e de escolher uma senha segura que não use em nenhum outro lugar."
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "Inscrito em: {0}" "subscribed_channels_count": "Inscrito em: {0}"

View File

@ -15,7 +15,9 @@
"channels": "Canais", "channels": "Canais",
"bookmarks": "Marcadores", "bookmarks": "Marcadores",
"channel_groups": "Grupos de canais", "channel_groups": "Grupos de canais",
"dearrow": "DeArrow" "dearrow": "DeArrow",
"albums": "Álbuns",
"custom_instances": "Instâncias personalizadas"
}, },
"actions": { "actions": {
"view_subscriptions": "Ver subscrições", "view_subscriptions": "Ver subscrições",
@ -49,7 +51,7 @@
"enabled_codecs": "Codificadores ativados (vários)", "enabled_codecs": "Codificadores ativados (vários)",
"instance_selection": "Instância", "instance_selection": "Instância",
"show_more": "Mostrar mais", "show_more": "Mostrar mais",
"import_from_json": "Importar de JSON/CSV", "import_from_json": "Importar de JSON",
"loop_this_video": "Repetir este vídeo", "loop_this_video": "Repetir este vídeo",
"auto_play_next_video": "Reproduzir vídeo seguinte automaticamente", "auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
"donations": "Doações para o desenvolvimento", "donations": "Doações para o desenvolvimento",
@ -61,8 +63,8 @@
"search": "Pesquisa (Ctrl+K)", "search": "Pesquisa (Ctrl+K)",
"hide_replies": "Ocultar respostas", "hide_replies": "Ocultar respostas",
"load_more_replies": "Carregar mais respostas", "load_more_replies": "Carregar mais respostas",
"unsubscribe": "Anular subscrição - {count}", "unsubscribe": "Anular subscrição",
"subscribe": "Subscrever - {count}", "subscribe": "Subscrever",
"back": "Recuar", "back": "Recuar",
"audio_only": "Apenas áudio", "audio_only": "Apenas áudio",
"default_quality": "Qualidade padrão", "default_quality": "Qualidade padrão",
@ -139,7 +141,17 @@
"show_search_suggestions": "Mostrar sugestões de pesquisa", "show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Aplicações recentemente adicionadas", "chapters_layout_mobile": "Aplicações recentemente adicionadas",
"enable_dearrow": "Ativar o DeArrow", "enable_dearrow": "Ativar o DeArrow",
"delete_automatically": "Eliminar automaticamente após" "delete_automatically": "Eliminar automaticamente após",
"generate_qrcode": "Gerar Código QR",
"add": "Adicionar",
"instance_privacy_policy": "Política de Privacidade",
"instances_not_shown": "Instâncias públicas que não aparecem aqui estão de momento em baixo.",
"import_from_json_csv": "Importar de JSON/CSV",
"download_frame": "Descarregar frame",
"add_to_group": "Adicionar a grupo",
"concurrent_prefetch_limit": "Limite de pré-busca de fluxo simultâneo",
"customize": "Personalizar",
"invalid_url": "URL inválido!"
}, },
"comment": { "comment": {
"pinned_by": "Afixado por {author}", "pinned_by": "Afixado por {author}",
@ -154,11 +166,15 @@
"registered_users": "Utilizadores registados", "registered_users": "Utilizadores registados",
"ssl_score": "Avaliação SSL", "ssl_score": "Avaliação SSL",
"up_to_date": "Atualizada?", "up_to_date": "Atualizada?",
"version": "Versão" "version": "Versão",
"uptime_30d": "Tempo Online (30d)",
"api_url": "URL do API"
}, },
"login": { "login": {
"username": "Nome de utilizador", "username": "Nome de utilizador",
"password": "Palavra-passe" "password": "Palavra-passe",
"password_confirm": "Confirmar senha",
"passwords_incorrect": "Senhas não coincidem!"
}, },
"video": { "video": {
"videos": "Vídeos", "videos": "Vídeos",
@ -189,7 +205,8 @@
"music_artists": "YT Music: Artistas" "music_artists": "YT Music: Artistas"
}, },
"player": { "player": {
"watch_on": "Ver em {0}" "watch_on": "Ver em {0}",
"failed": "Falha com o código de erro {0}, ver registos para mais informações"
}, },
"subscriptions": { "subscriptions": {
"subscribed_channels_count": "Subscreveu: {0}" "subscribed_channels_count": "Subscreveu: {0}"
@ -205,6 +222,8 @@
"hours": "{quantidade} hora(s)", "hours": "{quantidade} hora(s)",
"days": "{quantidade} dia(s)", "days": "{quantidade} dia(s)",
"weeks": "{quantidade} semana(s)", "weeks": "{quantidade} semana(s)",
"months": "{quantidade} mês(es)" "months": "{quantidade} mês(es)",
"register_note": "Registar uma conta para esta instância de Piped. Isto ir-lhe-á permitir sincronizar as suas subscrições e playlists com a sua conta, então estarão guardadas no lado do servidor. Pode usar todas as funções sem uma conta, mas os seus dados serão guardados na cache local do seu browser. Por favor, certifique-se que você NÃO use um endereço de e-mail como o seu nome de utilizador e escolha uma palavra-passe forte que não use em mais nenhum lugar.",
"login_note": "Entre com uma conta criada nesta instância."
} }
} }

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