Merge pull request #3641 from dragos-efy/efy

Latest EFY, Fix Bugs, New Card Layout, Seek Bar Gradient, Improved Captions
This commit is contained in:
Bnyro 2024-05-23 19:10:40 +02:00 committed by GitHub
commit 0933d1bb86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
121 changed files with 6415 additions and 3901 deletions

View File

@ -4,3 +4,4 @@ dist/
.*
*.md
!.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,
},
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
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:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
cache: "pnpm"
- run: pnpm install
- run: pnpm build
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: build
path: dist

View File

@ -33,7 +33,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# 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).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# 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
@ -60,6 +60,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

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

View File

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

View File

@ -14,7 +14,7 @@ jobs:
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
cache: "pnpm"
- 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
- Harassment of any form
- Publishing others' private information, such as a physical or electronic address, without explicit permission from the individual
- Derailling conversations unnecessarily in a way that is not constructive, such as repeatedly posting off-topic comments whilest not in an off-topic channel
- 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
- 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
COPY --from=build /app/dist/ /usr/share/nginx/html/
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 docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY docker/entrypoint.sh /entrypoint.sh
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) |
## 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 also join us at the libera.chat IRC network which is bridged to the Matrix room at [#piped](https://web.libera.chat/#piped).
## Public Communities
- You can join us on Lemmy on the [!piped@feddit.rocks](https://feddit.rocks/c/piped) community.
## Self-Hosting
### Self-Hosting
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.
## Documentation
### Documentation
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
```
pnpm serve
pnpm dev
```
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
- Email: kavin@kavin.rocks
Please note that isn't meant for support, see [Public Chat Rooms/Communities](#public-chat-roomscommunities) for that.
## Donations
Donations can be made at:
- bc1qhq8zjxmu405nvp37njj6zv3s980zg400pu9jfe (BTC)
- 0x1D77D4cfB1a947514241bcf19B1F04738495e2fD (ETH)
- 8A5Up8rKgagVAz6TuUduBqHp518H1U6fYc6GqCfWsaEfjGzbSccfYpgMqp5d4oe5Ws5MuFE1iKmhQTadhMhvuk3bHRT5Ebk (XMR, aka Monero)
- 84wyyeGTrg4U1daJufi78bAFrBQgdRhmxJZvgYv8dAFeFVwkJaBEmw5C7fNniUM9n4jfrz3NeG32Agxtp7JNAcCUFPACfwA (XMR, aka Monero)
- nano_1ngejzydncche4rdua3iebhj7sa95pw5geq4pb8phugtjf3tku933ktjb4pq (Nano)
- 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.
# Made with Piped
- [Yattee](https://github.com/yattee/yattee) - an alternative frontend for YouTube, for IOS.
- [LibreTube](https://github.com/Libre-tube/LibreTube) - an alternative frontend for YouTube, for Android.
- [Racoon](https://github.com/shailendramaurya/racoon) - A web based minimal YouTube downloader.
- [Hyperpipe](https://codeberg.org/Hyperpipe/Hyperpipe) - an alternative privacy respecting frontend for YouTube Music.
- [Musicale](https://github.com/Bellisario/musicale) - an alternative to YouTube Music, with style.
- [ytify](https://github.com/n-ce/ytify) - a complementary minimal audio streaming frontend for YouTube.
- [PsTube](https://github.com/prateekmedia/pstube) - Watch and download videos without ads on Android, Linux, Windows, iOS, and Mac OSX.
- [Piped-Material](https://github.com/mmjee/Piped-Material) - A fork of Piped, focusing on better performance and a more usable design.
- [ReacTube](https://github.com/NeeRaj-2401/ReacTube) - Privacy friendly & distraction free Youtube front-end using Piped API.
**Mobile/desktop apps**
- [LibreTube](https://github.com/Libre-tube/LibreTube) - Alternative frontend for YouTube, for Android.
- [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

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

View File

@ -1,7 +1,8 @@
#!/bin/sh
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
file="dist/fonts$(echo "$font" | sed -E "s#$base##")"
mkdir -p "$(dirname "$file")"

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -10,6 +10,129 @@
<FooterComponent />
</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>
#app {
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)) {
background: var(--efy_bg1);
&:has([class*="headphones"], [class*="circle-plus"]) {
aspect-ratio: 1;
}
i {
margin: 0;
}
}
.pp-video-card-buttons .pp-color {
color: var(--efy_text2);
@ -138,106 +267,103 @@ video {
:is(.pp-video-card-channel > a, .pp-video-card-channel > .pp-text):empty {
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>
<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>
<div class="flex flex-col efy_trans_filter efy_shadow_trans">
<div class="efy_trans_filter efy_shadow_trans flex flex-col">
<router-link
:to="props.item.url"
class="flex items-center p-[10rem] gap-[10rem]"
:to="item.url"
class="flex items-center gap-[10rem] p-[10rem]"
style="border-bottom: var(--efy_border)"
>
<img
class="efy_shadow_trans"
style="border-radius: var(--efy_radius); width: 40rem; aspect-ratio: 1"
:src="props.item.thumbnail"
:src="item.thumbnail"
loading="lazy"
width="40"
height="40"
/>
<div class="flex items-center overflow-hidden pp-text">
<p v-text="props.item.name" class="pp-video-card-title p-0!" />
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
<div class="pp-text flex items-center overflow-hidden">
<p class="pp-video-card-title p-0!" v-text="item.name" />
<i v-if="item.verified" class="i-fa6-solid:check ml-1.5" />
</div>
</router-link>
<div style="padding: 10rem">
<p v-if="props.item.description" v-text="props.item.description" />
<div v-if="props.item.videos >= 0" v-text="`${props.item.videos} ${$t('video.videos')}`" />
<p v-if="item.description" v-text="item.description" />
<div v-if="item.videos >= 0" v-text="`${item.videos} ${$t('video.videos')}`" />
</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>
</template>
<script setup>
const props = defineProps({
item: {
type: Object,
required: true,
<script>
export default {
props: {
item: {
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>

View File

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

View File

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

View File

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

View File

@ -25,5 +25,19 @@ export default {
},
},
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>

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

View File

@ -1,6 +1,6 @@
<template>
<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)">
<button v-t="'actions.clear_history'" class="m-0" @click="clearHistory" />
<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" />
</select>
</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>

View File

@ -9,10 +9,10 @@
<strong v-text="`Selected Subscriptions: ${selectedSubscriptions}`" />
</div>
<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>
</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>
<br />
<strong>Importing Subscriptions from YouTube</strong>
@ -88,10 +88,11 @@ export default {
});
}
// NewPipe
else if (text.indexOf("app_version") != -1) {
else if (text.indexOf("subscriptions") != -1) {
const json = JSON.parse(text);
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 => {
const url = item.url;
const id = url.slice(-24);

View File

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

View File

@ -1,8 +1,11 @@
<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 />
<div class="text-center">
<form class="children:pb-3">
<div class="w-full flex items-center justify-center text-center">
<form class="w-min children:pb-3">
<div>
<input
v-model="username"
@ -43,7 +46,7 @@ export default {
mounted() {
//TODO: Add Server Side check
if (this.getAuthToken()) {
this.$router.push("/");
this.$router.push(import.meta.env.BASE_URL);
}
},
activated() {
@ -61,7 +64,7 @@ export default {
}).then(resp => {
if (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);
});
},

View File

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

View File

@ -1,7 +1,7 @@
<template>
<nav class="pp-nav flex flex-wrap items-center justify-center px-2 sm:px-4 py-2.5 w-full relative">
<div class="flex-1 flex justify-start pp-logo">
<router-link class="flex font-bold text-3xl items-center font-sans" to="/">
<nav class="pp-nav relative w-full flex flex-wrap items-center justify-center px-2 py-2.5 sm:px-4">
<div class="pp-logo flex flex-1 justify-start">
<router-link class="flex items-center text-3xl font-bold font-sans" to="/">
<svg
id="svg-logo"
version="1.2"
@ -54,7 +54,7 @@
>iped</router-link
>
</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
ref="videoSearch"
v-model="searchText"
@ -88,7 +88,7 @@
<button
efy_sidebar_btn="relative, pp-desktop"
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" />
</button>
@ -96,18 +96,18 @@
</nav>
<!-- 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
v-model="searchText"
type="text"
role="search"
:title="$t('actions.search')"
:placeholder="$t('actions.search')"
style="margin: 15rem 0 0 0"
@keyup="onKeyUp"
@keypress="onKeyPress"
@focus="onInputFocus"
@blur="onInputBlur"
style="margin: 15rem 0 0 0"
/>
<span v-if="searchText" class="delete-search" @click="searchText = ''"></span>
</div>
@ -120,6 +120,104 @@
<LoginModal v-if="showLoginModal" @close="showLoginModal = !showLoginModal" />
</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>
.pp-nav {
gap: 15rem;
@ -182,95 +280,3 @@
align-content: center;
}
</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>
<div class="flex flex-col justify-center items-center min-h-[88vh]">
<h1 class="font-bold !text-[170rem] mb-[-6vh]">404</h1>
<div class="min-h-[88vh] flex flex-col items-center justify-center">
<h1 class="mb-[-6vh] font-bold !text-[170rem]">404</h1>
<h2 v-t="'info.page_not_found'" class="!text-[40rem]" />
<a v-t="'actions.back_to_home'" role="button" href="/" />
</div>

View File

@ -1,11 +1,16 @@
<template>
<ModalComponent @close="$emit('close')">
<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" />
</select>
<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
ref="addButton"
v-t="'actions.add_to_playlist'"
@ -14,14 +19,21 @@
/>
</div>
</ModalComponent>
<CreatePlaylistModal
v-if="showCreatePlaylistModal"
@close="showCreatePlaylistModal = false"
@created="addCreatedPlaylist"
/>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
import CreatePlaylistModal from "./CreatePlaylistModal.vue";
export default {
components: {
ModalComponent,
CreatePlaylistModal,
},
props: {
videoInfo: {
@ -39,10 +51,13 @@ export default {
playlists: [],
selectedPlaylist: null,
processing: false,
showCreatePlaylistModal: false,
};
},
mounted() {
this.fetchPlaylists();
this.getPlaylists().then(json => {
this.playlists = json;
});
this.selectedPlaylist = this.getPreferenceString("selectedPlaylist" + this.hashCode(this.authApiUrl()));
window.addEventListener("keydown", this.handleKeyDown);
window.blur();
@ -52,7 +67,7 @@ export default {
},
methods: {
handleKeyDown(event) {
if (event.code === "Enter") {
if (event.code === "Enter" && !this.showCreatePlaylistModal) {
this.handleClick(this.selectedPlaylist);
event.preventDefault();
}
@ -74,18 +89,9 @@ export default {
if (json.error) alert(json.error);
});
},
async fetchPlaylists() {
this.getPlaylists().then(json => {
this.playlists = json;
});
},
onCreatePlaylist() {
const name = prompt(this.$t("actions.create_playlist"));
if (!name) return;
this.createPlaylist(name).then(json => {
if (json.error) alert(json.error);
else this.fetchPlaylists();
});
addCreatedPlaylist(playlistId, playlistName) {
this.playlists.push({ id: playlistId, name: playlistName });
this.selectedPlaylist = playlistId;
},
},
};

View File

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

View File

@ -21,31 +21,31 @@
loading="lazy"
width="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">
<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>
</router-link>
<button
v-text="`${playlist.videos} ${$t('video.videos')}`"
class="efy_button_text_off efy_shadow_trans efy_shadow_button_off"
v-text="`${playlist.videos} ${$t('video.videos')}`"
/>
</div>
<div class="pp-flex-bookmarks">
<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"}`) }}
</button>
<button v-if="authenticated && !isPipedPlaylist" class="btn mr-1 ml-2" @click="clonePlaylist">
<font-awesome-icon class="mr-[5rem]" icon="clone" />{{ $t("actions.clone_playlist") }}
<button v-if="authenticated && !isPipedPlaylist" class="btn ml-2 mr-1" @click="clonePlaylist">
<i class="i-fa6-solid:clone mr-[5rem]" />{{ $t("actions.clone_playlist") }}
</button>
<button class="btn mr-1" @click="downloadPlaylistAsTxt">
{{ $t("actions.download_as_txt") }}
</button>
<a :href="getRssUrl" role="button" class="btn pp-square">
<font-awesome-icon icon="rss" />
<a :href="getRssUrl" role="button" class="btn pp-square" style="padding: 0">
<i class="i-fa6-solid:rss m-0" />
</a>
<WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" class="pp-square" />
</div>
@ -69,17 +69,6 @@
</LoadingIndicatorPage>
</template>
<style>
.pp-flex-bookmarks {
display: flex;
flex-wrap: wrap;
gap: var(--efy_gap0);
}
.pp-flex-bookmarks > * {
margin: 0;
}
</style>
<script>
import ErrorHandler from "./ErrorHandler.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
@ -132,16 +121,8 @@ export default {
window.removeEventListener("scroll", this.handleScroll);
},
methods: {
async fetchPlaylist() {
const playlistId = this.$route.query.list;
if (playlistId.startsWith("local")) {
return this.getPlaylist(playlistId);
}
return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
},
async getPlaylistData() {
this.fetchPlaylist()
this.getPlaylist(this.$route.query.list)
.then(data => (this.playlist = data))
.then(() => {
this.updateTitle();
@ -237,3 +218,14 @@ export default {
},
};
</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>
<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
v-for="(related, index) in playlist.relatedStreams"
:key="related.url"
:item="related"
:index="index"
:playlist-id="playlistId"
:prefer-listen="preferListen"
height="94"
width="168"
/>
@ -16,6 +17,7 @@
<script>
import { nextTick } from "vue";
import VideoItem from "./VideoItem.vue";
export default {
components: { VideoItem },
props: {
@ -31,6 +33,10 @@ export default {
type: Number,
required: true,
},
preferListen: {
type: Boolean,
default: false,
},
},
watch: {
playlist: {

View File

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

View File

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

View File

@ -1,8 +1,11 @@
<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 />
<div class="flex justify-center text-center">
<form class="items-center px-3 children:pb-3">
<div class="flex flex-col items-center justify-center text-center">
<form class="w-max items-center px-3 children:pb-3">
<div>
<input
v-model="username"
@ -17,7 +20,7 @@
<div class="flex justify-center">
<input
v-model="password"
class="input w-full"
class="input h-auto w-full"
:type="showPassword ? 'text' : 'password'"
autocomplete="password"
:placeholder="$t('login.password')"
@ -31,7 +34,7 @@
<div class="flex justify-center">
<input
v-model="passwordConfirm"
class="input w-full"
class="input h-auto w-full"
:type="showConfirmPassword ? 'text' : 'password'"
autocomplete="password"
:placeholder="$t('login.password_confirm')"
@ -79,7 +82,7 @@ export default {
mounted() {
//TODO: Add Server Side check
if (this.getAuthToken()) {
this.$router.push("/");
this.$router.push(import.meta.env.BASE_URL);
}
},
activated() {
@ -105,7 +108,7 @@ export default {
}).then(resp => {
if (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);
});
},

View File

@ -3,7 +3,7 @@
<div class="flex flex-wrap place-content-between items-center">
<h5 class="ml-[5rem]" v-text="$route.query.search_query" />
<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
id="ddlSearchFilters"
v-model="selectedFilter"

View File

@ -4,12 +4,14 @@
<li
v-for="(suggestion, i) in searchSuggestions"
:key="i"
class="suggestion"
:class="{ 'suggestion-selected': selected === i }"
@mouseover="onMouseOver(i)"
@mousedown.stop="onClick(i)"
v-text="suggestion"
/>
@click="setSelected(i)"
>
<router-link class="suggestion" :to="`/results?search_query=${encodeURIComponent(suggestion)}`">
{{ suggestion }}
</router-link>
</li>
</ul>
</div>
</template>
@ -69,13 +71,6 @@ export default {
this.selected = i;
}
},
onClick(i) {
this.setSelected(i);
this.$router.push({
name: "SearchResults",
query: { search_query: this.searchSuggestions[i] },
});
},
setSelected(val) {
this.selected = val;
this.$emit("searchchange", this.searchSuggestions[this.selected]);
@ -91,5 +86,15 @@ export default {
box-shadow: 0 0 20rem var(--efy_text_trans);
padding: var(--efy_gap);
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>

View File

@ -1,10 +1,11 @@
<template>
<ModalComponent>
<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'" />
<input v-model="pipedLink" type="checkbox" @change="onChange" />
</div>
<hr />
<div v-if="hasPlaylist" class="flex justify-between">
<label v-t="'actions.with_playlist'" />
<input v-model="withPlaylist" type="checkbox" @change="onChange" />
@ -13,10 +14,11 @@
<label v-t="'actions.with_timecode'" for="withTimeCode" />
<input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" />
</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'" />
<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>
<hr />
<a :href="generatedLink" target="_blank">
<h6 class="mb-2 mt-2" v-text="generatedLink" />
</a>

View File

@ -1,6 +1,6 @@
<template>
<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" />
</select>
</template>

View File

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

View File

@ -19,7 +19,12 @@ export default {
};
},
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");
this.fetchTrending(region).then(videos => {
@ -31,7 +36,7 @@ export default {
activated() {
document.title = this.$t("titles.trending") + " - Piped";
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);
if (homepage !== undefined) this.$router.push(homepage);
}

View File

@ -1,5 +1,5 @@
<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-->
<router-link
class="video_item_link"
@ -9,6 +9,7 @@
v: item.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
...(preferListen && { listen: 1 }),
},
}"
>
@ -19,10 +20,22 @@
class="thumbnail"
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 -->
<div
v-if="item.watched && item.duration > 0"
class="relative h-1 w-full"
class="watched_progress relative h-1 w-full"
style="
height: 4rem;
background: rgba(255, 255, 255, 0.067);
@ -42,120 +55,127 @@
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 class="flex items-center h-[44rem] overflow-hidden">
<p v-text="title" class="pp-video-card-title" />
<div class="h-[44rem] flex items-center overflow-hidden">
<p class="pp-video-card-title" v-text="title" />
</div>
</router-link>
<div class="pp-video-card-buttons">
<button
v-if="item.duration > 0"
v-text="timeFormat(item.duration)"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
tabindex="-1"
/>
<button
v-if="item.views >= 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
tabindex="-1"
>
<font-awesome-icon icon="eye" style="margin-right: 5rem" />
<span v-text="`${numberFormat(item.views)}`" />
</button>
<div class="pp-card-info">
<div class="pp-video-card-2 h-[44rem] flex items-center overflow-hidden">
<p class="pp-video-card-title" v-text="title" />
</div>
<div class="pp-video-card-buttons">
<button
v-if="item.duration > 0"
class="pp-time efy_shadow_trans efy_shadow_button_off efy_button_text_off"
tabindex="-1"
v-text="timeFormat(item.duration)"
/>
<button
v-if="item.views >= 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
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
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"
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
:to="item.uploaderUrl"
:title="item.uploaderName"
class="pp-video-card-channel"
>
<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>
<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>
<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>
</template>
<style>
.shorts-img {
@apply w-full object-contain;
}
.video_item_link {
display: block;
}
</style>
<script>
import PlaylistAddModal from "./PlaylistAddModal.vue";
import ShareModal from "./ShareModal.vue";
import ConfirmModal from "./ConfirmModal.vue";
export default {
components: { PlaylistAddModal, ConfirmModal },
components: { PlaylistAddModal, ConfirmModal, ShareModal },
props: {
item: {
type: Object,
@ -172,12 +192,14 @@ export default {
hideChannel: { type: Boolean, default: false },
index: { type: Number, default: -1 },
playlistId: { type: String, default: null },
preferListen: { type: Boolean, default: false },
admin: { type: Boolean, default: false },
},
emits: ["remove"],
data() {
return {
showModal: false,
showPlaylistModal: false,
showShareModal: false,
showVideo: true,
showConfirmRemove: false,
};
@ -217,3 +239,13 @@ export default {
},
};
</script>
<style>
.shorts-img {
@apply w-full object-contain;
}
.video_item_link {
display: block;
position: relative;
}
</style>

View File

@ -2,7 +2,7 @@
<div
ref="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 }"
>
<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"
/>
</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>
<script>
import "shaka-player/dist/controls.css";
import { parseTimeParam } from "@/utils/Misc";
import ModalComponent from "./ModalComponent.vue";
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");
export default {
components: { ModalComponent },
props: {
video: {
type: Object,
@ -79,6 +91,8 @@ export default {
destroying: false,
inSegment: false,
isHoveringTimebar: false,
showSpeedModal: false,
playbackSpeedInput: null,
currentTime: 0,
seekbarPadding: 2,
error: 0,
@ -119,7 +133,7 @@ export default {
this.hotkeysPromise.then(() => {
var self = this;
this.$hotkeys(
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.,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) {
const videoEl = self.$refs.videoEl;
switch (handler.key) {
@ -209,11 +223,14 @@ export default {
self.$emit("navigateNext");
e.preventDefault();
break;
case "shift+s":
self.showSpeedModal = true;
break;
case "shift+,":
self.$player.trickPlay(Math.max(videoEl.playbackRate - 0.25, 0.25));
self.adjustPlaybackSpeed(videoEl.playbackRate - 0.25);
break;
case "shift+.":
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
self.adjustPlaybackSpeed(videoEl.playbackRate + 0.25);
break;
case "alt+p":
document.pictureInPictureElement
@ -253,32 +270,6 @@ export default {
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;
var streams = [];
@ -342,11 +333,12 @@ export default {
}
if (noPrevPlayer)
this.shakaPromise.then(() => {
this.shakaPromise.then(async () => {
if (this.destroying) return;
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);
let proxyPath = proxyURL.pathname;
if (proxyPath.lastIndexOf("/") === proxyPath.length - 1) {
@ -435,7 +427,7 @@ export default {
videoEl.currentTime = segment.segment[1];
segment.skipped = true;
},
setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) {
async setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) {
const url = "/watch?v=" + this.video.id;
if (!this.$ui) {
@ -481,14 +473,7 @@ export default {
this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
const overflowMenuButtons = [
"quality",
"language",
"captions",
"picture_in_picture",
"playback_rate",
"airplay",
];
const overflowMenuButtons = ["quality", "captions", "picture_in_picture", "playback_rate", "airplay"];
if (this.isEmbed) {
overflowMenuButtons.push("open_new_tab");
@ -499,7 +484,7 @@ export default {
seekBarColors: {
base: "rgba(255, 255, 255, 0.3)",
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 prefetchLimit = Math.min(Math.max(this.getPreferenceNumber("prefetchLimit", 2), 0), 10);
this.$player.configure({
preferredVideoCodecs: this.preferredVideoCodecs,
preferredAudioCodecs: ["opus", "mp4a"],
@ -526,7 +513,12 @@ export default {
disableVideo: disableVideo,
},
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;
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
.load(uri, 0, mime)
.load(uri, startTime, mime)
.then(() => {
const isSafari = window.navigator?.vendor?.includes("Apple");
@ -553,6 +576,18 @@ export default {
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) {
var leastDiff = Number.MAX_VALUE;
var bestStream = null;
@ -603,6 +638,16 @@ export default {
const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
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 => {
console.error(e);
@ -636,7 +681,19 @@ export default {
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() {
const markers = this.$refs.container.querySelector(".shaka-ad-markers");
const array = ["to right"];
@ -874,6 +931,11 @@ html .shaka-range-element:focus {
.shaka-settings-menu button {
-webkit-text-fill-color: var(--efy_text) !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-settings-menu .material-icons-round {

View File

@ -16,14 +16,9 @@ export default {
<template>
<template v-if="getPreferenceBoolean('showWatchOnYouTube', false)">
<a
:href="link"
role="button"
class="pp-square flex items-center justify-center"
:aria-label="'Watch on Odysee'"
:title="`${$t('player.watch_on')}${platform}`"
>
<font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
<a :href="link" role="button" class="pp-square flex items-center" style="margin: 0; padding: 0">
<i v-if="platform == 'YouTube'" class="i-fa6-brands:youtube m-0" />
<i v-else-if="platform == 'Odysee'" class="i-fa6-brands:odysee m-0" />
</a>
</template>
</template>

View File

@ -10,7 +10,7 @@
/>
</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" />
<Transition>
<ToastComponent v-if="shouldShowToast" @dismissed="dismiss">
@ -46,7 +46,7 @@
<!-- views / date -->
<div class="flex flex-auto">
<span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
<span> </span>
<span> · </span>
<span v-text="uploadDate" />
</div>
<!-- Likes/dilikes -->
@ -77,21 +77,22 @@
video.uploader
}}</router-link>
<!-- 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 class="pp-watch-buttons">
<!-- Subscribe button -->
<button
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(video.uploaderSubscriberCount) },
}"
class="btn"
@click="subscribeHandler"
v-text="
$t('actions.' + (subscribed ? 'unsubscribe' : 'subscribe')) +
' - ' +
numberFormat(video.uploaderSubscriberCount)
"
/>
<!-- Playlist Add button -->
<button class="btn flex items-center" @click="showModal = !showModal">
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
<button class="pp-square btn flex items-center" style="padding: 0" @click="showModal = !showModal">
<i class="i-fa6-solid:circle-plus m-0" />
</button>
<PlaylistAddModal
v-if="showModal"
@ -108,9 +109,12 @@
:playlist-index="index"
@close="showShareModal = !showShareModal"
/>
<button class="btn flex items-center share-btn" @click="showShareModal = !showShareModal">
<font-awesome-icon class="mx-1.5 mr-1" icon="fa-share" />
<i18n-t keypath="actions.share" tag="strong"></i18n-t>
<button
class="pp-square btn share-btn flex items-center"
style="padding: 0"
@click="showShareModal = !showShareModal"
>
<i class="i-fa6-solid:share m-0" />
</button>
<!-- YouTube -->
<WatchOnButton :link="`https://youtu.be/${getVideoId()}`" />
@ -123,8 +127,9 @@
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
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>
<!-- RSS Feed button -->
<a
@ -135,11 +140,12 @@
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
target="_blank"
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>
<button class="btn flex items-center gap-1 <md:hidden" @click="downloadCurrentFrame">
<i class="i-fa6-solid:download" />{{ $t("actions.download_frame") }}
<button class="pp-square btn flex items-center" style="padding: 0" @click="downloadCurrentFrame">
<i class="i-fa6-solid:download m-0" />
</button>
</div>
</div>
@ -163,8 +169,8 @@
<label v-t="'actions.show_description'" for="showDesc" />
<input id="showComments" v-model="showComments" type="checkbox" @click="toggleComments" />
<label
v-text="`${$t('actions.show_comments')} - ${numberFormat(comments?.commentCount)}`"
for="showComments"
v-text="`${$t('actions.show_comments')} - ${numberFormat(comments?.commentCount)}`"
/>
<input id="showRecs" v-model="showRecs" type="checkbox" />
<label v-t="'actions.show_recommendations'" for="showRecs" />
@ -195,7 +201,7 @@
<router-link
v-for="tag in video.tags"
: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)}`"
>{{ tag }}</router-link
>
@ -219,6 +225,7 @@
:key="comment.commentId"
:comment="comment"
:uploader="video.uploader"
:uploader-avatar-url="video.uploaderAvatar"
:video-id="getVideoId()"
class="efy_trans_filter efy_shadow_trans"
/>
@ -230,6 +237,7 @@
:playlist-id="playlistId"
:playlist="playlist"
:selected-index="index"
:prefer-listen="isListening"
/>
<div v-show="showRecs" class="pp-show-recs">
<h6 efy_card style="padding: 5rem 10rem 3rem; margin: 0">Recommended</h6>
@ -237,6 +245,8 @@
v-for="related in video.relatedStreams"
:key="related.url"
:item="related"
:prefer-listen="isListening"
class="mb-4"
height="94"
width="168"
/>
@ -287,7 +297,7 @@ export default {
selectedAutoLoop: false,
selectedAutoPlay: null,
showComments: true,
showDesc: true,
showDesc: false,
showRecs: true,
showChapters: true,
comments: null,
@ -388,7 +398,7 @@ export default {
this.active = true;
this.selectedAutoPlay = this.getPreferenceBoolean("autoplay", 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.showChapters = !this.getPreferenceBoolean("minimizeChapters", false);
if (this.video?.duration) {
@ -428,7 +438,7 @@ export default {
});
sponsors?.segments?.forEach(segment => {
const option = skipOptions[segment.category];
const option = skipOptions?.[segment.category];
segment.autoskip = option === undefined || option === "auto";
});
@ -480,9 +490,7 @@ export default {
},
async getPlaylistData() {
if (this.playlistId) {
await this.fetchJson(this.apiUrl() + "/playlists/" + this.playlistId).then(data => {
this.playlist = data;
});
this.playlist = await this.getPlaylist(this.playlistId);
await this.fetchPlaylistPages().then(() => {
if (!(this.index >= 0)) {
for (let i = 0; i < this.playlist.relatedStreams.length; i++)
@ -516,65 +524,17 @@ export default {
this.fetchSponsors().then(data => (this.sponsors = data));
},
async getComments() {
this.fetchComments().then(data => {
this.rewriteComments(data.comments);
this.comments = data;
});
this.comments = await this.fetchComments();
},
async fetchSubscribedStatus() {
if (!this.channelId) return;
if (!this.authenticated) {
this.subscribed = this.isSubscribedLocally(this.channelId);
return;
}
this.fetchJson(
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",
);
});
this.subscribed = await this.fetchSubscriptionStatus(this.channelId);
},
subscribeHandler() {
if (this.authenticated) {
this.fetchJson(this.authApiUrl() + (this.subscribed ? "/unsubscribe" : "/subscribe"), null, {
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;
this.toggleSubscriptionState(this.channelId, this.subscribed).then(success => {
if (success) this.subscribed = !this.subscribed;
});
},
handleClick(event) {
if (!event || !event.target) return;
@ -614,12 +574,17 @@ export default {
}).then(json => {
this.comments.nextpage = json.nextpage;
this.loading = false;
this.rewriteComments(json.comments);
this.comments.comments = this.comments.comments.concat(json.comments);
});
}
},
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;
},
navigate(time) {
@ -659,7 +624,15 @@ export default {
},
navigateNext() {
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();
for (var param in params)
switch (param) {
@ -667,7 +640,8 @@ export default {
case "t":
break;
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;
case "list":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
@ -719,6 +693,39 @@ export default {
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) {
.share-btn {
aspect-ratio: 1;
@ -730,4 +737,10 @@ export default {
margin: 0;
}
}
@media (max-width: 639px) {
video::-webkit-media-text-track-display {
font-size: 16rem !important;
top: calc(100% - 120rem) !important;
}
}
</style>

View File

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

View File

@ -21,8 +21,8 @@
"watch_on": "{0} saytında bax"
},
"actions": {
"subscribe": "Abunə Ol - {count}",
"unsubscribe": "Abunəlikdən Çıx - {count}",
"subscribe": "Abunə Ol",
"unsubscribe": "Abunəlikdən Çıx",
"view_subscriptions": "Abunəliklərə Baxın",
"sort_by": "Çeşidlə:",
"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": {
"most_recent": "Най-скорошен",
"unsubscribe": "Отписване - {count}",
"unsubscribe": "Отписване",
"uses_api_from": "Използва API от ",
"skip_sponsors": "Пропускане на спонсори",
"skip_preview": "Пропускане на преглед/обобщение",
@ -27,7 +27,7 @@
"min_segment_length": "Минимална дължина на сегмента (в секунди)",
"default_quality": "Качество по подразбиране",
"minimize_comments_default": "Минимизиране на коментарите по подразбиране",
"subscribe": "Абониране - {count}",
"subscribe": "Абониране",
"view_subscriptions": "Преглед на абонаменти",
"sort_by": "Сортиране по:",
"least_recent": "Най-малко скорошен",

View File

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

View File

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

View File

@ -64,8 +64,8 @@
"hide_replies": "Amaga les respostes",
"load_more_replies": "Carrega més respostes",
"view_subscriptions": "Mostra les subscripcions",
"subscribe": "Subscriu-me - {count}",
"unsubscribe": "Anul·la la subscripció - {count}",
"subscribe": "Subscriu-me",
"unsubscribe": "Anul·la la subscripció",
"sort_by": "Ordena per:",
"most_recent": "Més recents",
"least_recent": "Menys recents",
@ -95,7 +95,7 @@
"clone_playlist_success": "S'ha clonat correctament!",
"download_as_txt": "Baixa com a .txt",
"reset_preferences": "Restableix les preferències",
"restore_preferences": "Restaura les preferències",
"restore_preferences": "Importa les preferències",
"backup_preferences": "Exporta les preferències",
"confirm_reset_preferences": "Segur que voleu restablir les vostres preferències?",
"back_to_home": "Torna a l'inici",
@ -142,7 +142,11 @@
"auto_display_captions": "Mostra els subtítols automàticament",
"autoplay_next_countdown": "Compte enrere predefinit fins al pròxim vídeo (en segons)",
"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": {
"pinned_by": "Fixat per {author}",
@ -157,7 +161,8 @@
"ssl_score": "Puntuació SSL",
"version": "Versió",
"up_to_date": "Actualitzada?",
"registered_users": "Usuaris registrats"
"registered_users": "Usuaris registrats",
"uptime_30d": "Temps d'activitat (30 d)"
},
"video": {
"watched": "Vist",
@ -214,6 +219,8 @@
"next_video_countdown": "Pròxim vídeo en {0}s",
"months": "{amount} mesos",
"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",
"bookmarks": "Záložky",
"channel_groups": "Skupiny kanálů",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Alba",
"custom_instances": "Vlastní instance"
},
"actions": {
"loop_this_video": "Přehrávat video ve smyčce",
"subscribe": "Odebírat - {count}",
"subscribe": "Odebírat",
"view_subscriptions": "Zobrazit odběry",
"sort_by": "Seřadit podle:",
"most_recent": "Nejnovější",
@ -68,7 +70,7 @@
"clear_history": "Smazat historii",
"hide_replies": "Skrýt 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",
"minimize_description": "Skrýt popis",
"skip_non_music": "Přeskočit hudbu: nehudební sekce",
@ -135,7 +137,7 @@
"cancel": "Zrušit",
"edit_playlist": "Upravit playlist",
"playlist_description": "Popis playlistu",
"okay": "Okay",
"okay": "Dobře",
"show_search_suggestions": "Zobrazit našeptávání ve vyhledávání",
"chapters_layout_mobile": "Rozložení kapitol na mobilu",
"enable_dearrow": "Povolit DeArrow",
@ -143,7 +145,18 @@
"generate_qrcode": "Vygenerovat QR kód",
"import_from_json_csv": "Importovat z JSON/CSV",
"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": {
"watch_on": "Zobrazit na {0}",
@ -162,7 +175,9 @@
"ssl_score": "Stav SSL",
"registered_users": "Registrovaní uživatelé",
"up_to_date": "Aktuální?",
"version": "Verze"
"version": "Verze",
"uptime_30d": "Doba online (30d)",
"api_url": "Api URL"
},
"login": {
"username": "Uživatelské jméno",
@ -215,6 +230,8 @@
"hours": "{amount} hodin",
"days": "{amount} 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",
"sort_by": "Sortieren nach:",
"view_subscriptions": "Abos anzeigen",
"unsubscribe": "Deabonnieren - {count}",
"subscribe": "Abonnieren - {count}",
"unsubscribe": "Deabonnieren",
"subscribe": "Abonnieren",
"enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)",
"enable_lbry_proxy": "Proxy für LBRY einschalten",
"disable_lbry": "LBRY für Streaming deaktivieren",
@ -125,7 +125,12 @@
"generate_qrcode": "QR-Code generieren",
"import_from_json_csv": "Aus JSON/CSV importieren",
"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": {
"watch_on": "Auf {0} ansehen",
@ -147,7 +152,9 @@
"channels": "Kanäle",
"bookmarks": "Lesezeichen",
"channel_groups": "Kanalgruppen",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Alben",
"custom_instances": "Benutzerdefinierte Instanzen"
},
"video": {
"sponsor_segments": "Sponsoren-Abschnitte",
@ -172,7 +179,9 @@
"instance_name": "Instanzname",
"registered_users": "Registrierte Benutzer",
"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": {
"pinned_by": "Angeheftet von {author}",
@ -212,6 +221,8 @@
"weeks": "{amount} Woche(n)",
"months": "{amount} Monat(en)",
"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": "Αποθήκευση ιστορικού παρακολούθησης",
"default_homepage": "Προεπιλεγμένη αρχική σελίδα",
"country_selection": "Επιλογή χώρας",
"audio_only": "Ήχος μόνο",
"audio_only": "Μόνο ήχος",
"skip_non_music": "Παράλειψη μουσικής: Μη-μουσικό τμήμα",
"skip_self_promo": "Παράλειψη μη-χορηγούμενης/προσωπικής προώθησης",
"skip_preview": "Παράλειψη προεπισκόπησης/ανακεφαλαίωσης",
@ -31,8 +31,8 @@
"enable_sponsorblock": "Ενεργοποίηση Sponsorblock",
"back": "Επιστροφή",
"sort_by": "Ταξινόμηση κατά:",
"unsubscribe": "Απεγγραφή - {count}",
"subscribe": "Εγγραφή - {count}",
"unsubscribe": "Απεγγραφή",
"subscribe": "Εγγραφή",
"loop_this_video": "Επανάληψη αυτού του βίντεο",
"instance_selection": "Επιλογή διακομιστή",
"enabled_codecs": "Ενεργοποιημένοι κωδικοποητές (Πολλαπλοί)",
@ -44,7 +44,7 @@
"show_recommendations": "Εμφάνιση συστάσεις",
"donations": "Δωρεές",
"auto_play_next_video": "Αυτόματη αναπαραγωγή επόμενου βίντεο",
"import_from_json": "Εισαγωγή από JSON/CSV",
"import_from_json": "Εισαγωγή από JSON",
"export_to_json": "Εξαγωγή σε JSON",
"no": "Όχι",
"yes": "Ναι",
@ -65,20 +65,89 @@
"add_to_playlist": "Προσθήκη στη λίστα αναπαραγωγής",
"clear_history": "Καθαρισμός ιστορικού",
"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": {
"feed": "Τροφοδοσία",
"feed": "Νεότερα",
"history": "Ιστορικό",
"preferences": "Ρυθμίσεις",
"register": "Εγγραφή",
"login": "Σύνδεση",
"trending": "Τάσεις",
"subscriptions": "Συνδρομές",
"playlists": "Λίστες αναπαραγωγής"
"playlists": "Λίστες αναπαραγωγής",
"livestreams": "Ζωντανές Ροές",
"channel_groups": "Γκρουπ Καναλιών",
"account": "Λογαριασμός",
"instance": "Διακομιστής",
"bookmarks": "Σελιδοδείκτες",
"channels": "Κανάλια",
"player": "Πρόγραμμα αναπαραγωγής",
"dearrow": "De Arrow"
},
"player": {
"watch_on": "Παρακολούθηση στο {0}"
"watch_on": "Παρακολούθηση στο {0}",
"failed": "Κωδικός σφάλματος {0}, δες τα logs για περισσότερες πληροφορίες"
},
"video": {
"watched": "Προβλήθηκε",
@ -87,7 +156,14 @@
"videos": "Βίντεο",
"live": "{0} Ζωντανή μετάδοση",
"ratings_disabled": "Οι αξιολογήσεις είναι απενεργοποιημένες",
"chapters": "Κεφάλαια"
"chapters": "Κεφάλαια",
"all": "Όλα",
"shorts": "Σύντομα βίντεο",
"visibility": "Εμφάνιση",
"license": "Άδεια",
"category": "Κατηγορία",
"chapters_vertical": "Κάθετα",
"chapters_horizontal": "Οριζόντια"
},
"preferences": {
"ssl_score": "SSL σκόρ",
@ -99,11 +175,16 @@
"up_to_date": "Ενημερωμένο;"
},
"comment": {
"pinned_by": "Καρφιτσώθηκε από τον {author}"
"pinned_by": "Καρφιτσώθηκε από τον {author}",
"loading": "Φόρτωση σχολίων...",
"user_disabled": "Τα σχόλια έχουν απενεργοποιηθεί στις ρυθμίσεις.",
"disabled": "Τα σχόλια έχουν απενεργοποιηθεί από το διαχειριστή του καναλιού."
},
"login": {
"password": "Κωδικός πρόσβασης",
"username": "Όνομα χρήστη"
"username": "Όνομα χρήστη",
"password_confirm": "Επιβεβαίωση κωδικού",
"passwords_incorrect": "Οι κωδικοί δεν ταιριάζουν!"
},
"search": {
"did_you_mean": "Μήπως εννοείτε: {0};",
@ -114,6 +195,25 @@
"music_songs": "YT Music: Τραγούδια",
"music_videos": "YT Music: Βίντεο",
"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",
"livestreams": "Livestreams",
"channels": "Channels",
"albums": "Albums",
"bookmarks": "Bookmarks",
"channel_groups": "Channel groups",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"custom_instances": "Custom instances"
},
"player": {
"watch_on": "View on {0}",
"failed": "Failed with error code {0}, see logs for more info"
},
"actions": {
"subscribe": "Subscribe - {count}",
"unsubscribe": "Unsubscribe - {count}",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"view_subscriptions": "View Subscriptions",
"sort_by": "Sort by:",
"most_recent": "Most Recent",
@ -32,6 +34,7 @@
"channel_name_desc": "Channel Name (Z-A)",
"back": "Back",
"uses_api_from": "Uses the API from ",
"instances_not_shown": "Public instances that are not shown here are currently unavailable.",
"enable_sponsorblock": "Enable Sponsorblock",
"skip_button_only": "Show skip button",
"skip_automatically": "Automatically",
@ -98,6 +101,7 @@
"delete_playlist": "Delete",
"select_playlist": "Select a Playlist",
"delete_playlist_confirm": "Delete this playlist?",
"delete_group_confirm": "Delete this group?",
"please_select_playlist": "Please select a playlist",
"delete_account": "Delete Account",
"logout": "Logout from this device",
@ -147,7 +151,16 @@
"show_search_suggestions": "Show search suggestions",
"delete_automatically": "Delete automatically after",
"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": {
"pinned_by": "Pinned by {author}",
@ -162,7 +175,9 @@
"registered_users": "Registered Users",
"version": "Version",
"up_to_date": "Up to date?",
"ssl_score": "SSL Score"
"ssl_score": "SSL Score",
"uptime_30d": "Uptime (30d)",
"api_url": "Api URL"
},
"login": {
"username": "Username",
@ -212,6 +227,8 @@
"hours": "{amount} hour(s)",
"days": "{amount} day(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",
"account": "Konto",
"player": "Ludilo",
"instance": "Nodo",
"instance": "Retejo",
"channels": "Kanaloj",
"livestreams": "Tujelsendoj",
"bookmarks": "Legosignoj",
@ -22,8 +22,8 @@
"failed": "Fiaskis kun erarkodo {0}, vidu protokolojn por pli da informo"
},
"actions": {
"subscribe": "Aboni - {count}",
"unsubscribe": "Malaboni - {count}",
"subscribe": "Aboni",
"unsubscribe": "Malaboni",
"view_subscriptions": "Vidi Abonojn",
"sort_by": "Ordigi laŭ:",
"most_recent": "Plej Freŝaj",
@ -81,11 +81,11 @@
"enable_lbry_proxy": "Ebligi Prokurilon por LBRY",
"import_from_json": "Importi el JSON",
"show_description": "Montri Priskribon",
"instances_list": "Listo de Nodoj",
"instances_list": "Listo de Piped-retejoj",
"auto_play_next_video": "Aŭtomate Ludi sekvan Videon",
"show_recommendations": "Montri Rekomendojn",
"reset_preferences": "Restarigi agordojn",
"instance_selection": "Nodo",
"instance_selection": "Retejo",
"view_ssl_score": "Vidu SSL-Poentaron",
"backup_preferences": "Savkopii agordojn",
"disable_lbry": "Malebligi LBRY-n por Elsendfluo",
@ -93,11 +93,11 @@
"store_search_history": "Konservi Ŝerĉhistorion",
"hide_watched": "Kaŝi viditajn videojn en la fluo",
"minimize_recommendations": "Plejetigi Rekomendojn",
"instance_auth_selection": "Aŭtentokontrola nodo",
"instance_auth_selection": "Aŭtentokontrola Piped-retejo",
"restore_preferences": "Restarigi agordojn",
"status_page": "Stato",
"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",
"time_code": "Tempkodo (en sekundoj)",
"skip_non_music": "Preterpasi Muzikon: Nemuzika Sekcio",
@ -147,7 +147,8 @@
"generate_qrcode": "Generi QR-kodon",
"import_from_json_csv": "Importi el JSON/CSV",
"download_frame": "Elŝuti bildon",
"instance_privacy_policy": "Privateca politiko"
"instance_privacy_policy": "Privateca politiko",
"add_to_group": "Aldoni al grupo"
},
"video": {
"chapters": "Sekcioj",
@ -188,7 +189,9 @@
"hours": "{amount} horo(j)",
"days": "{amount} tago(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": {
"username": "Uzantnomo",

View File

@ -22,7 +22,9 @@
"instance_name": "Nombre de la instancia",
"version": "Versión",
"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": {
"pinned_by": "Fijado por {author}",
@ -76,8 +78,8 @@
"most_recent": "Lo más reciente",
"sort_by": "Ordenar por:",
"view_subscriptions": "Ver suscripciones",
"unsubscribe": "Anular suscripción - {count}",
"subscribe": "Suscribirme - {count}",
"unsubscribe": "Anular suscripción",
"subscribe": "Suscribirme",
"loading": "Cargando…",
"filter": "Filtrar",
"search": "Buscar (Ctrl+K)",
@ -156,7 +158,18 @@
"generate_qrcode": "Generar código QR",
"import_from_json_csv": "Importar desde JSON/CSV",
"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": {
"feed": "Contenido",
@ -174,7 +187,9 @@
"channels": "Canales",
"bookmarks": "Marcadores",
"channel_groups": "Grupos de canales",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Álbumes",
"custom_instances": "Instancias personalizadas"
},
"player": {
"watch_on": "Ver en {0}",
@ -212,6 +227,8 @@
"hours": "{amount} hora(s)",
"days": "{amount} día(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",
"subscriptions": "Tellimused",
"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": {
"watch_on": "Vaata saidil {0}"
"watch_on": "Vaata {0} saidis",
"failed": "Tegevus ei õnnestunud, veakood {0}; lisateavet leiad logidest"
},
"actions": {
"subscribe": "Telli - {count}",
"unsubscribe": "Loobuda tellitud - {count}",
"subscribe": "Telli",
"unsubscribe": "Lõpeta tellimus",
"view_subscriptions": "Vaata tellimusi",
"sort_by": "Sorteerimine:",
"most_recent": "Viimane",
"least_recent": "Kõige vähem hiljutine",
"least_recent": "Kõige vanem",
"channel_name_asc": "Kanali nimi (A-Ü)",
"channel_name_desc": "Kanali nimi (Ü-A)",
"back": "Tagasi",
"uses_api_from": "Kasutab API-d ",
"enable_sponsorblock": "Sponsorblock lubamine",
"skip_sponsors": "Sponsorite vahelejätmine",
"skip_intro": "Vaheaja vahelejätmine/Intro animatsioon",
"skip_outro": "Jäta lõppkaardid/krediidid vahele",
"skip_preview": "Jäta eelvaade/kokkuvõte vahele",
"uses_api_from": "Kasutusel on API: ",
"enable_sponsorblock": "Luba Sponsorblock",
"skip_sponsors": "Jäta sponsorid vahele",
"skip_intro": "Jäta vahe- ja intriklipid vahele",
"skip_outro": "Jäta lõppkaardid ja tunustused vahele",
"skip_preview": "Jäta eelvaade või kokkuvõte vahele",
"skip_non_music": "Muusika vahelejätmine: Mitte-muusika sektsioon",
"theme": "Teema",
"default_quality": "Standardkvaliteet",
"country_selection": "Riigi valimine",
"country_selection": "Riik",
"default_homepage": "Vaikimisi koduleht",
"language_selection": "Kelle valik",
"instances_list": "Intsidentide nimekiri",
"language_selection": "Keel",
"instances_list": "Vaheserverite loend",
"show_more": "Näita rohkem",
"yes": "Jah",
"no": "Ei",
"export_to_json": "Eksportida JSON-vormingusse",
"import_from_json": "Importimine JSON/CSVist",
"loop_this_video": "Selle video pidev taasesitus",
"donations": "Annetused",
"export_to_json": "Ekspordi JSON-vormingusse",
"import_from_json": "Impordi JSON'ist",
"loop_this_video": "Esita seda videot lõputult",
"donations": "Toeta arendust",
"minimize_description": "Minimeeri kirjeldus",
"minimize_recommendations": "Soovituste minimeerimine",
"disable_lbry": "LBRY väljalülitamine voogedastuse jaoks",
"search": "Otsige",
"filter": "Filtreeri järgi",
"loading": "Laadimine...",
"clear_history": "Tühjendage ajalugu",
"minimize_recommendations": "Minimeeri soovitused",
"disable_lbry": "Lülita LBRY voogedastuse puhul välja",
"search": "Otsi (Ctrl+K)",
"filter": "Filtreeri",
"loading": "Laadime...",
"clear_history": "Tühjenda ajalugu",
"hide_replies": "Peida vastused",
"load_more_replies": "Lae alla rohkem vastuseid",
"load_more_replies": "Laadi alla rohkem vastuseid",
"add_to_playlist": "Lisa esitusloendisse",
"create_playlist": "Loo esitusloend",
"delete_playlist": "Kustuta esitusloend",
"skip_highlight": "Jäta tipphetk vahele",
"skip_interaction": "Jäta interaktsiooni meeldetuletus vahele (Telli)",
"light": "Valgus",
"skip_interaction": "Jäta interaktsiooni meeldetuletus vahele (Tellimine)",
"light": "Hele",
"auto": "Automaatne",
"skip_self_promo": "Jäta tasustamata/enesereklaami vahele",
"skip_filler_tangent": "Jäta filler'i vahelejätmine",
"skip_self_promo": "Jäta tasustamata või enesereklaam vahele",
"skip_filler_tangent": "Jäta filler'i vahele",
"dark": "Tume",
"autoplay_video": "Mängi videot automaatselt",
"buffering_goal": "Puhverdamise eesmärk (sekundites)",
"buffering_goal": "Puhverdamise kestus (sekundites)",
"show_comments": "Näita kommentaare",
"audio_only": "Ainult heli",
"store_watch_history": "Salvesta oma vaatamise ajalugu",
"instance_selection": "Instantsi valik",
"instance_selection": "Vaheserver",
"show_recommendations": "Näita soovitusi",
"minimize_description_default": "Vähenda kirjeldust vaikimisi",
"minimize_description_default": "Vaikimisi minimeeri kirjeldus",
"enabled_codecs": "Kasutatavad koodekid (mitu)",
"auto_play_next_video": "Järgmise video automaatne esitamine",
"view_ssl_score": "Näita SSL skoore",
"enable_lbry_proxy": "LBRY proxy-serveri aktiveerimine",
"auto_play_next_video": "Esita järgmist videot automaatselt",
"view_ssl_score": "Vaata SSL-punkte",
"enable_lbry_proxy": "Kasuta LBRY puhul puhverserverit",
"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",
"delete_playlist_video_confirm": "Kas olete kindel, et soovite selle video sellest esitusloendist eemaldada?",
"select_playlist": "Valige esitusloend",
"please_select_playlist": "Valige esitusloend"
"delete_playlist_video_confirm": "Kas soovid selle video sellest esitusloendist eemaldada?",
"select_playlist": "Vali 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": {
"has_cdn": "Kas on CDN?",
"has_cdn": "CDN'i olek?",
"ssl_score": "SSL-punktid",
"registered_users": "Registreeritud kasutajad",
"up_to_date": "Ajakohastatud?",
"instance_name": "Instantsi nimi",
"instance_locations": "Instantsi asukoht",
"version": "Versioon"
"up_to_date": "Kõik on ajakohane?",
"instance_name": "Vaheserveri nimi",
"instance_locations": "Vaheserveri asukohad",
"version": "Versioon",
"uptime_30d": "Tõrgeteta tööaeg (30p)",
"api_url": "API URL"
},
"video": {
"videos": "Videod",
@ -96,7 +178,14 @@
"sponsor_segments": "Sponsorite segmendid",
"chapters": "Peatükid",
"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": {
"did_you_mean": "Kas sa mõtlesid: {0}?",
@ -107,13 +196,37 @@
"music_songs": "YT Music: Laulud",
"music_videos": "YT Music: Videod",
"music_albums": "YT Music: Albumid",
"music_playlists": "YT Music: Esitusloendid"
"music_playlists": "YT Music: Esitusloendid",
"music_artists": "YT Music: Esitajad"
},
"login": {
"username": "Kasutajanimi",
"password": "Parool"
"password": "Parool",
"password_confirm": "Kinnita salasõna õigsust",
"passwords_incorrect": "Salasõnad ei klapi omavahel!"
},
"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",
"sort_by": "Ordenatu honen arabera:",
"view_subscriptions": "Harpidetzak ikusi",
"unsubscribe": "Kendu harpidetza - {count}",
"subscribe": "Harpidetu - {count}",
"unsubscribe": "Kendu harpidetza",
"subscribe": "Harpidetu",
"loading": "Kargatzen...",
"filter": "Iragazi",
"search": "Bilatu",

View File

@ -24,12 +24,12 @@
"most_recent": "تازه‌ترین‌ها",
"sort_by": "مرتب سازی بر اساس:",
"view_subscriptions": "مشاهده سابسکرایب ها",
"unsubscribe": "لغو سابسکرایب - {count}",
"subscribe": "سابسکرایب - {count}",
"unsubscribe": "لغو سابسکرایب",
"subscribe": "سابسکرایب",
"minimize_recommendations": "بستن توصیه ها",
"show_recommendations": "نمایش توصیه ها",
"uses_api_from": "با استفاده از APIای از ",
"instance_selection": "انتخاب نمونه",
"instance_selection": "انتخاب سرویس دهنده",
"show_more": "نمایش بیشتر",
"yes": "بله",
"no": "خیر",
@ -42,7 +42,7 @@
"skip_interaction": "رد کردن یاداوری سابسکرایب",
"skip_highlight": "رد کردن نکات برجسته",
"skip_filler_tangent": "رد کردن چرندیات نامربوط",
"import_from_json": "وارد کردن از JSON/CSV",
"import_from_json": "وارد کردن از JSON",
"export_to_json": "دادن خروجی با فرمت JSON",
"show_description": "نمایش توضیحات ویدئو",
"donations": "کمک های مالی برای توسعه",
@ -68,15 +68,67 @@
"logout": "خروج از این دستگاه",
"delete_playlist_video_confirm": "ویدیو از لیست پخش حذف شود؟",
"autoplay_next_countdown": "پیش‌فرض شمارش‌معکوس پخش ویدیوی بعدی ( به ثانیه )",
"please_select_playlist": "انتخاب لیست پخش",
"please_select_playlist": "لطفا فهرست پخش را انتخاب کنید",
"minimize_comments_default": "کوچک کردن کامنت‌ها به صورت پیش فرض",
"minimize_comments": "کوچک کردن کامنت (نظرات)",
"remove_from_playlist": "حذف از لیست پخش",
"skip_button_only": "دکمه‌ی گذر را نشان بده",
"skip_automatically": "خودکار",
"skip_segment": "گذر از بخش",
"minimize_recommendations_default": "به طور پیش فرض پیشنهادها را حداقل کنید.",
"min_segment_length": "حداقل طول بخش (به ثانیه)"
"minimize_recommendations_default": "به طور پیش فرض پیشنهادها را حداقل کنید",
"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": {
"history": "تاریخچه",
@ -93,13 +145,24 @@
"bookmarks": "نشان‌دارها",
"livestreams": "پخش زنده",
"channels": "کانال‌ها",
"channel_groups": "گروه های کانال"
"channel_groups": "گروه های کانال",
"dearrow": "DeArrow"
},
"player": {
"watch_on": "تماشا روی {0}"
"watch_on": "تماشا روی {0}",
"failed": "عدم موفقیت با خطای {0}، برای اطلاعات بیشتر logها رو بررسی کنید"
},
"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": {
"views": "{views} بازدید ها",
@ -107,7 +170,15 @@
"watched": "دیده شده",
"sponsor_segments": "قسمت های اسپانسری",
"ratings_disabled": "رتبه بندی غیرفعال",
"chapters": "فصل ها"
"chapters": "فصل ها",
"all": "همه",
"live": "{0} زنده",
"shorts": "Shortها",
"license": "مجوز",
"category": "دسته‌بندی",
"chapters_vertical": "عمودی",
"chapters_horizontal": "افقی",
"visibility": "قابل مشاهده"
},
"preferences": {
"instance_locations": "محل های سرویس",
@ -115,13 +186,38 @@
"ssl_score": "امتیاز SSL",
"instance_name": "نام سرویس",
"version": "نسخه",
"registered_users": "کاربران تایید شده"
"registered_users": "کاربران تایید شده",
"up_to_date": "به روز؟",
"uptime_30d": "Uptime (30d)"
},
"comment": {
"pinned_by": "سنجاق شده توسط {author}"
"pinned_by": "سنجاق شده توسط {author}",
"loading": "دریافت نظرها…",
"user_disabled": "نظرها در تنظیمات غیرفعال شده‌اند.",
"disabled": "ارسال کننده، نظرها را غیرفعال کرده است."
},
"login": {
"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",
"sort_by": "Järjestä:",
"view_subscriptions": "Näytä tilaukset",
"unsubscribe": "Poistu tilauksesta - {count}",
"subscribe": "Tilaa - {count}",
"unsubscribe": "Poistu tilauksesta",
"subscribe": "Tilaa",
"minimize_recommendations": "Minimoi suositukset",
"show_recommendations": "Näytä suositukset",
"hide_replies": "Piilota vastaukset",

View File

@ -14,11 +14,13 @@
"livestreams": "Diffusions en direct",
"channels": "Chaînes",
"bookmarks": "Marque-pages",
"channel_groups": "Groupes de chaînes"
"channel_groups": "Groupes de chaînes",
"dearrow": "DeArrow",
"albums": "Albums"
},
"actions": {
"subscribe": "S'abonner - {count}",
"unsubscribe": "Se désabonner - {count}",
"subscribe": "S'abonner",
"unsubscribe": "Se désabonner",
"buffering_goal": "Objectif de mise en mémoire tampon (en secondes)",
"skip_non_music": "Ignorer la musique : section non musicale",
"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_sponsors": "Ignorer les sponsors",
"instances_list": "Liste des instances",
"language_selection": "Sélection de la langue",
"language_selection": "Langue",
"store_watch_history": "Conserver l'historique de visionnage",
"minimize_description_default": "Minimiser la description par défaut",
"show_comments": "Afficher les commentaires",
"default_homepage": "Page d'accueil par défaut",
"country_selection": "Sélection du pays",
"country_selection": "Pays",
"autoplay_video": "Lire automatiquement la vidéo",
"light": "Clair",
"dark": "Sombre",
@ -46,7 +48,7 @@
"view_subscriptions": "Voir les abonnements",
"back": "Retour",
"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)",
"least_recent": "Le moins récent",
"most_recent": "Le plus récent",
@ -57,10 +59,10 @@
"donations": "Dons pour le développement",
"auto_play_next_video": "Lire la vidéo suivante automatiquement",
"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",
"show_more": "Afficher plus",
"instance_selection": "Sélection de l'instance",
"instance_selection": "Instance",
"minimize_description": "Minimiser la description",
"minimize_recommendations": "Minimiser les recommandations",
"show_recommendations": "Afficher les recommandations",
@ -89,7 +91,7 @@
"minimize_recommendations_default": "Minimiser les recommandations par défaut",
"invalidate_session": "Se déconnecter de tous les appareils",
"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_success": "Clonage réussi !",
"download_as_txt": "Télécharger en tant que .txt",
@ -125,7 +127,7 @@
"min_segment_length": "Longueur minimale du segment (en secondes)",
"skip_segment": "Sauter le segment",
"show_less": "Afficher moins",
"okay": "OK",
"okay": "Valider",
"edit_playlist": "Éditer la liste de lecture",
"playlist_name": "Nom de la liste de lecture",
"auto_display_captions": "Afficher sous-titres automatiquement",
@ -136,10 +138,20 @@
"group_name": "Nom du groupe",
"autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)",
"chapters_layout_mobile": "Format des chapitres sur mobile",
"show_search_suggestions": "Afficher les suggestions de recherche"
"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": {
"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": {
"sponsor_segments": "Segments de sponsors",
@ -153,16 +165,19 @@
"all": "Tout",
"category": "Catégorie",
"chapters_horizontal": "Horizontal",
"chapters_vertical": "Vertical"
"chapters_vertical": "Vertical",
"visibility": "Visibilité",
"license": "Licence"
},
"preferences": {
"ssl_score": "Score SSL",
"has_cdn": "A un RDC ?",
"has_cdn": "A un CDN ?",
"instance_locations": "Localisations de l'instance",
"instance_name": "Nom de l'instance",
"up_to_date": "À jour ?",
"registered_users": "Utilisateurs enregistrés",
"version": "Version"
"version": "Version",
"uptime_30d": "Uptime (30j)"
},
"comment": {
"pinned_by": "Épinglé par {author}",
@ -172,7 +187,9 @@
},
"login": {
"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": {
"did_you_mean": "Vouliez-vous dire : {0} ?",
@ -183,7 +200,8 @@
"music_songs": "YT Music : Chansons",
"music_videos": "YT Music : Vidéos",
"music_playlists": "YT Music : Listes de lecture",
"music_albums": "YT Music : Albums"
"music_albums": "YT Music : Albums",
"music_artists": "YT Music : Artistes"
},
"subscriptions": {
"subscribed_channels_count": "Abonné à : {0}"
@ -198,6 +216,12 @@
"cannot_copy": "Impossible de copier !",
"local_storage": "Cette action nécessite localStorage, les cookies sont-ils activés ?",
"register_no_email_note": "Il n'est pas recommandé d'utiliser une adresse courriel omme nom d'utilisateur. Continuer quand même ?",
"next_video_countdown": "Lecture de la prochaine vidéo dans {0}s"
"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"
},
"actions": {
"subscribe": "Subscribirse - {count}",
"subscribe": "Subscribirse",
"sort_by": "Orde por:",
"least_recent": "Máis antigo",
"most_recent": "Máis recente",
"channel_name_asc": "Nome da canle (A-Z)",
"unsubscribe": "Retirar subscrición - {count}",
"unsubscribe": "Retirar subscrición",
"view_subscriptions": "Ver Subscricións",
"back": "Volver",
"uses_api_from": "Usa a API desde ",

View File

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

View File

@ -1,105 +1,234 @@
{
"titles": {
"trending": "ट्रेंडिंग",
"trending": "रुझान",
"history": "इतिहास",
"register": "रजिस्टर करें",
"login": "लॉग इन करें",
"preferences": "प्राथमिकताए",
"register": "पंजीकृत करें",
"login": "लॉगिन",
"preferences": "प्राथमिकताए",
"subscriptions": "सदस्यता",
"feed": "फीड",
"feed": "फीड",
"playlists": "प्लेलिस्ट",
"livestreams": "लाइव स्ट्रीम",
"livestreams": "लाइवस्ट्रीम",
"channels": "चैनल",
"player": "चालक",
"account": "खाता",
"instance": "इंस्टैंस"
"instance": "इंस्टैंस",
"channel_groups": "चैनल ग्रुप",
"bookmarks": "बुकमार्क",
"dearrow": "DeArrow",
"albums": "एलबम",
"custom_instances": "तदनुकूल इंस्टैंस"
},
"actions": {
"subscribe": "सदस्यता लें - {count}",
"back": "वापस जाओ",
"unsubscribe": "सदस्यता ले ली है - {count}",
"subscribe": "सदस्यता लें",
"back": "पीछे",
"unsubscribe": "सदस्यता छोड़ें",
"no": "नहीं",
"hide_replies": "जवाब छिपाएं",
"search": "खोजें (Ctrl+K)",
"loop_this_video": "इस वीडियो को लूप करें",
"loading": "लोड हो रहा है...",
"show_description": "विवरण दिखाएं",
"minimize_description": "विवरण छिपाएं",
"minimize_description": "विवरण संक्षेपित करें",
"yes": "हां",
"view_subscriptions": "सदस्यता देखें",
"most_recent": "सबसे हाला",
"least_recent": "कम से कम हाल का",
"channel_name_asc": "चैनल का नाम (ए-जेड)",
"channel_name_desc": "चैनल का नाम (जेड-ए)",
"uses_api_from": "से API का उपयोग करता है ",
"most_recent": "सबसे हालिया",
"least_recent": "कम हालिया",
"channel_name_asc": "चैनल नाम (A-Z)",
"channel_name_desc": "चैनल नाम (Z-A)",
"uses_api_from": "यहां से API का उपयोग करता है ",
"skip_sponsors": "प्रायोजकों को छोड़ें",
"skip_outro": "एंडकार्ड्स/क्रेडिट छोड़ें",
"skip_interaction": "इंटरैक्शन रिमाइंडर छोड़ें (सदस्यता लें)",
"skip_outro": "एंडकार्ड/क्रेडिट छोड़ें",
"skip_interaction": "इंटरेक्शन अनुस्मारक छोड़ें (सदस्यता लें)",
"theme": "थीम",
"dark": "डार्क",
"light": "प्रकाश",
"autoplay_video": "ऑटोप्ले वीडियो",
"audio_only": "सिर्फ़ ध्वनि",
"default_quality": "डिफ़ॉल्ट गुणवत्ता",
"dark": "गहरी",
"light": "हल्की",
"autoplay_video": "वीडियो स्वत:चालू करें",
"audio_only": "सिर्फ ऑडियो",
"default_quality": "तयशुदा गुणवत्ता",
"country_selection": "देश",
"show_comments": "टिप्पणियाँ दिखाएँ",
"store_watch_history": "स्टोर देखने का इतिहास",
"show_comments": "टिप्पणियां दिखाएं",
"store_watch_history": "देखने का इतिहास संग्रहीत करें",
"language_selection": "भाषा",
"instances_list": "इंस्टंस सूची",
"instance_selection": "इंस्टंस",
"show_more": "और दिखाओ",
"instances_list": "इंस्टंस सूची",
"instance_selection": "इंस्टंस",
"show_more": "अधिक दिखाएं",
"export_to_json": "JSON में निर्यात करें",
"import_from_json": "JSON/CSV से आयात करें",
"auto_play_next_video": "अगला वीडियो ऑटोप्ले करें",
"donations": "विकास दान",
"minimize_recommendations": "सिफारिशों को कम करें",
"show_recommendations": "सिफारिशें दिखाएं",
"import_from_json": "JSON से आयात करें",
"auto_play_next_video": "अगला वीडियो स्वत:चालू करें",
"donations": "विकास के लिए दान",
"minimize_recommendations": "अनुशंसाएं न्यूनतम करें",
"show_recommendations": "अनुशंसाएं दिखाएं",
"disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें",
"enable_lbry_proxy": "LBRY के लिए प्रॉक्सी सक्षम करें",
"view_ssl_score": "एसएसएल स्कोर देखें",
"filter": "फिल्टर",
"clear_history": "स्पष्ट इतिहास",
"load_more_replies": "और जवाब लोड करें",
"view_ssl_score": "SSL स्कोर देखें",
"filter": "फिल्टर",
"clear_history": "इतिहास साफ़ करें",
"load_more_replies": "अधिक जवाब लोड करें",
"enabled_codecs": "सक्षम कोडेक्स (एकाधिक)",
"buffering_goal": "बफरिंग गोल (सेकंड में)",
"delete_playlist_confirm": "प्लेलिस्ट को मिटाना है?",
"buffering_goal": "बफरिंग क्ष्य (सेकंड में)",
"delete_playlist_confirm": "इस प्लेलिस्ट को मिटाएं?",
"add_to_playlist": "प्लेलिस्ट में जोड़ें",
"remove_from_playlist": "प्लेलिस्ट से निकाले",
"delete_playlist_video_confirm": "वीडियो को प्लेलिस्ट से निकालना है?",
"remove_from_playlist": "प्लेलिस्ट से हटाएं",
"delete_playlist_video_confirm": "प्लेलिस्ट से वीडियो हटाएं?",
"create_playlist": "प्लेलिस्ट बनायें",
"select_playlist": "एक प्लेलिस्ट चुनें",
"please_select_playlist": "कृपया एक प्लेलिस्ट चुनें",
"delete_playlist": "प्लेलिस्ट टाएं",
"enable_sponsorblock": "विज्ञापन प्रतिबंध करें",
"default_homepage": "स्वतः निर्धारित मुख्यपृष्ठ",
"sort_by": "वर्गीकरण:",
"select_playlist": "प्लेलिस्ट चुनें",
"please_select_playlist": "कृपया प्लेलिस्ट चुनें",
"delete_playlist": "प्लेलिस्ट मिटाएं",
"enable_sponsorblock": "स्पॉन्सरब्लॉक सक्षम करें",
"default_homepage": "तयशुदा मुख्यपृष्ठ",
"sort_by": "ऐसे छांटें:",
"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": {
"views": "{views} बार देखा गया",
"videos": "वीडियो",
"watched": "पहले ही देखा हुआ",
"watched": "देखा गया",
"ratings_disabled": "रेटिंग अक्षम",
"chapters": "चैप्टर",
"live": "{0} लाइव"
"chapters": "अध्याय",
"live": "{0} लाइव",
"sponsor_segments": "प्रायोजक खंड",
"shorts": "शॉर्ट्स",
"all": "सभी",
"category": "श्रेणी",
"license": "लाईसेंस",
"visibility": "दृश्यता",
"chapters_horizontal": "क्षैतिज",
"chapters_vertical": "ऊर्ध्वाधर"
},
"login": {
"password": "पासवर्ड",
"username": "उपयोगकर्ता नाम"
"username": "उपयोक्ता नाम",
"passwords_incorrect": "पासवर्ड मेल नहीं खाते हैं!",
"password_confirm": "पासवर्ड की पुष्टि करें"
},
"comment": {
"pinned_by": "{author} ने पिन किया"
"pinned_by": "{author} ने पिन किया",
"loading": "टिप्पणियां लोड हो रही हैं…",
"disabled": "टिप्पणियां अपलोडर द्वारा अक्षम की गई हैं।",
"user_disabled": "सेटिंग्स में टिप्पणियां अक्षम हैं।"
},
"preferences": {
"instance_locations": "इंस्टेंस स्थान",
"has_cdn": "सीडीएन है?",
"ssl_score": "एसएसएल स्कोर"
"instance_locations": "इंस्टैंस स्थान",
"has_cdn": "CDN है?",
"ssl_score": "SSL स्कोर",
"uptime_30d": "अपटाइम (30 दिन)",
"instance_name": "इंस्टैंस का नाम",
"registered_users": "पंजीकृत उपयोक्ता",
"version": "संस्करण",
"up_to_date": "अद्यतित?",
"api_url": "Api URL"
},
"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": {
"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",
"registered_users": "Registrirani korisnici",
"version": "Verzija",
"up_to_date": "Najnovija verzija?"
"up_to_date": "Najnovija verzija?",
"uptime_30d": "Vrijeme rada (30 dana)",
"api_url": "URL Api-ja"
},
"comment": {
"pinned_by": "Prikvačio korisnik {author}",
@ -73,8 +75,8 @@
"most_recent": "Najnovije",
"sort_by": "Redoslijed:",
"view_subscriptions": "Pogledaj pretplate",
"unsubscribe": "Otkaži pretplatu {count}",
"subscribe": "Pretplati se {count}",
"unsubscribe": "Otkaži pretplatu",
"subscribe": "Pretplati se",
"skip_interaction": "Preskoči podsjetnik za interakciju (pretplata)",
"skip_outro": "Preskoči odjavnu špicu",
"skip_intro": "Preskoči pauzu i uvodnu animaciju",
@ -156,7 +158,14 @@
"generate_qrcode": "Generiraj QR kod",
"import_from_json_csv": "Uvezi iz JSON/CSV formata",
"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": {
"watch_on": "Gledaj na {0}",
@ -178,7 +187,9 @@
"livestreams": "Prijenosi uživo",
"bookmarks": "Zabilješke",
"channel_groups": "Grupe kanala",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Albumi",
"custom_instances": "Prilagođene instance"
},
"login": {
"password": "Lozinka",
@ -215,6 +226,8 @@
"hours": "{amount} h",
"days": "{amount} dan(a)",
"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ó",
"instance": "Szerver",
"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": {
"subscribe": "Feliratkozás - {count}",
"subscribe": "Feliratkozás",
"skip_highlight": "Kiemelés",
"light": "Világos",
"most_recent": "Legutóbbi",
"unsubscribe": "Leiratkozás - {count}",
"unsubscribe": "Leiratkozás",
"view_subscriptions": "Feliratkozások megtekintése",
"least_recent": "Legrégebbi",
"channel_name_asc": "Csatorna név (A-Z)",
@ -39,12 +44,12 @@
"autoplay_video": "Automatikus lejátszás",
"audio_only": "Csak hang",
"default_quality": "Alapértelmezett minőség",
"country_selection": "Ország választás",
"country_selection": "Ország",
"default_homepage": "Alapértelmezett kezdőlap",
"show_comments": "Megjegyzések megjelenítése",
"minimize_description_default": "Leírás minimalizásása alapból",
"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)",
"show_more": "További megjelenítése",
"yes": "Igen",
@ -52,14 +57,14 @@
"export_to_json": "Exportálás JSON-ba",
"skip_preview": "Előzetes/Ismétlés",
"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",
"loop_this_video": "Videó ismétlése",
"donations": "Fejlesztési támogatások",
"minimize_description": "Leírás minimalizálása",
"show_recommendations": "Javaslatok megjelenítése",
"enable_lbry_proxy": "Proxy engedélyezése a LBRY számára",
"search": "Keresés",
"search": "Keresés (Ctrl+K)",
"filter": "Szűrés",
"loading": "Betöltés...",
"clear_history": "Megtekintési előzmények törlése",
@ -75,7 +80,7 @@
"view_ssl_score": "SSL pontszám megtekintése",
"sort_by": "Rendezés:",
"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?",
"remove_from_playlist": "Törlés a lejátszási listából",
"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",
"invalidate_session": "Kijelentkezés minden eszközről",
"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_success": "Sikeresen klónozva!",
"reset_preferences": "Alaphelyzetbe állítás",
@ -107,7 +112,7 @@
"backup_preferences": "Beállítások mentése",
"share": "Megosztás",
"with_timecode": "Megosztás & videó kezdés ettől a ponttól",
"store_search_history": "Mentse a keresési előzményeket",
"store_search_history": "Keresési előzmények tárolása",
"follow_link": "Követések link",
"copy_link": "Link másolása",
"status_page": "Státusz",
@ -115,7 +120,43 @@
"with_playlist": "Megosztás lejátszási listával",
"minimize_comments_default": "Mindig tüntesd el a kommenteket",
"minimize_comments": "Kommentek eltüntetése",
"back_to_home": "Vissza a főoldalra"
"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": {
"ratings_disabled": "Értékelések Letiltva",
@ -125,10 +166,17 @@
"live": "{0} Élő",
"videos": "Videók",
"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": {
"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": {
"instance_name": "Példány neve",
@ -137,7 +185,9 @@
"version": "Verzió",
"ssl_score": "SSL pontszám",
"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": {
"did_you_mean": "Erre gondoltál: {0}?",
@ -148,11 +198,14 @@
"music_songs": "YT Music: Dalok",
"music_videos": "YT Music: Videók",
"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": {
"username": "Felhasználónév",
"password": "Jelszó"
"password": "Jelszó",
"password_confirm": "Jelszó megerősítése",
"passwords_incorrect": "A jelszavak nem egyeznek!"
},
"comment": {
"pinned_by": "Rögzítette {author}",
@ -168,6 +221,14 @@
"copied": "Másolva!",
"local_storage": "Ennek a beállításnak szüksége van a \"lokális tárhely\" funkcióra, be vannak a sütik kapcsolva?",
"cannot_copy": "Nem lehet másolni!",
"page_not_found": "Oldnal nem található"
"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": {
"skip_automatically": "Ավտոմատվաց",
"subscribe": "Բաժանորդ - {count}",
"subscribe": "Բաժանորդ",
"uses_api_from": "Օգտագործում է API -ից ",
"enable_sponsorblock": "Միացնել հովանավորների արգելափակումը",
"skip_intro": "Բաց թողնել ընդմիջում/ներածական անիմացիա",

View File

@ -15,18 +15,20 @@
"channels": "Saluran",
"bookmarks": "Markah",
"channel_groups": "Grup saluran",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Album",
"custom_instances": "Server khusus"
},
"player": {
"watch_on": "Lihat di {0}",
"failed": "Gagal dengan kode kesalahan {0}, lihat catatan untuk info lebih lanjut"
},
"actions": {
"subscribe": "Berlangganan - {count}",
"subscribe": "Berlangganan",
"view_subscriptions": "Lihat Langganan",
"sort_by": "Sortir bedasarkan oleh:",
"least_recent": "Baru",
"unsubscribe": "Berhenti Berlangganan - {count}",
"unsubscribe": "Berhenti Berlangganan",
"channel_name_asc": "Nama Saluran (A-Z)",
"channel_name_desc": "Nama Saluran (Z-A)",
"back": "Kembali",
@ -147,7 +149,13 @@
"generate_qrcode": "Buat Kode QR",
"import_from_json_csv": "Impor dari JSON/CSV",
"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": {
"pinned_by": "Dipasangi pin oleh {author}",
@ -162,7 +170,9 @@
"has_cdn": "Memakai CDN?",
"up_to_date": "Sudah terkini?",
"version": "Versi",
"registered_users": "Pengguna Terdaftar"
"registered_users": "Pengguna Terdaftar",
"uptime_30d": "Waktu aktif (30hari)",
"api_url": "URL API"
},
"login": {
"username": "Nama Pengguna",
@ -215,6 +225,8 @@
"weeks": "{amount} minggu",
"hours": "{amount} jam",
"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",
"theme": "Þema",
"enable_sponsorblock": "Virkja Sponsorblock",
"subscribe": "Gerast Áskrifandi - {count}",
"unsubscribe": "Segja Upp Áskrift - {count}",
"subscribe": "Gerast Áskrifandi",
"unsubscribe": "Segja Upp Áskrift",
"auto": "Sjálfvirkt",
"audio_only": "Aðeins Hljóð",
"most_recent": "Nýlegast",

View File

@ -31,8 +31,8 @@
"least_recent": "Meno recente",
"sort_by": "Ordina per:",
"view_subscriptions": "Visualizza gli abbonamenti",
"unsubscribe": "Disiscriviti - {count}",
"subscribe": "Iscriviti - {count}",
"unsubscribe": "Disiscriviti",
"subscribe": "Iscriviti",
"enabled_codecs": "Abilita Codecs (Molteplici)",
"enable_lbry_proxy": "Abilita il proxy per LBRY",
"disable_lbry": "Disabilita LBRY per lo streaming",
@ -43,7 +43,7 @@
"donations": "Donazioni per lo sviluppo",
"auto_play_next_video": "Riproduci automaticamente il prossimo 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",
"no": "No",
"yes": "Sì",
@ -88,7 +88,7 @@
"follow_link": "Apri il collegamento",
"with_timecode": "Condividi con marca temporale",
"show_chapters": "Capitoli",
"store_search_history": "Memorizza la cronologia delle ricerche",
"store_search_history": "Archivia la cronologia delle ricerche",
"status_page": "Stato",
"documentation": "Documentazione",
"source_code": "Codice sorgente",
@ -117,10 +117,21 @@
"group_name": "Nome gruppo",
"create_group": "Crea gruppo",
"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": {
"watch_on": "Guarda su {0}"
"watch_on": "Guarda su {0}",
"failed": "Operazione non riuscita (codice errore {0}). Vedi il registro per ulteriori dettagli"
},
"titles": {
"history": "Cronologia",
@ -137,7 +148,8 @@
"livestreams": "Streaming live",
"channels": "Canali",
"bookmarks": "Segnalibri",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"channel_groups": "Gruppi di canali"
},
"video": {
"sponsor_segments": "Segmenti sponsor",
@ -152,7 +164,8 @@
"category": "Categoria",
"chapters_horizontal": "Orizzontale",
"chapters_vertical": "Verticale",
"license": "Licenza"
"license": "Licenza",
"visibility": "Visibilità"
},
"preferences": {
"ssl_score": "Valutazione SSL",
@ -171,7 +184,9 @@
},
"login": {
"password": "Password",
"username": "Nome utente"
"username": "Nome utente",
"password_confirm": "Conferma password",
"passwords_incorrect": "Le password non combaciano!"
},
"search": {
"did_you_mean": "Forse intendevi: {0}?",
@ -198,6 +213,12 @@
"cannot_copy": "Impossibile copiare!",
"local_storage": "Questa azione richiede localStorage, i cookie sono abilitati?",
"register_no_email_note": "L'utilizzo di un indirizzo e-mail come nome utente è sconsigliato. Continuare comunque?",
"next_video_countdown": "Riproduzione prossimo video tra {0}s"
"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": "急上昇",
"login": "ログイン",
"register": "新規登録",
"feed": "フィード",
"feed": "新着動画",
"preferences": "設定",
"history": "履歴",
"subscriptions": "登録チャンネル",
@ -15,15 +15,17 @@
"livestreams": "ライブ配信",
"bookmarks": "ブックマーク",
"channel_groups": "グループ",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "アルバム",
"custom_instances": "独自インスタンス"
},
"player": {
"watch_on": "{0}で視聴",
"failed": "失敗しエラーコード {0} が返りました。詳細はログに記録"
"failed": "失敗。エラーコード {0} 。詳細はログに記録"
},
"actions": {
"subscribe": "チャンネル登録 - {count}",
"unsubscribe": "登録解除 - {count}",
"subscribe": "チャンネル登録",
"unsubscribe": "登録解除",
"view_subscriptions": "登録チャンネルを見る",
"sort_by": "表示順:",
"most_recent": "新しい順",
@ -35,7 +37,7 @@
"enable_sponsorblock": "SponsorBlock を使用",
"skip_sponsors": "広告をスキップ",
"skip_intro": "合間/導入アニメをスキップ",
"skip_outro": "終了シーン/クレジットをスキップ",
"skip_outro": "終了画面/クレジットをスキップ",
"skip_preview": "予告/あらすじをスキップ",
"skip_interaction": "登録など操作を頼む自己宣伝をスキップ",
"skip_self_promo": "無償または自己の宣伝をスキップ",
@ -102,7 +104,7 @@
"download_as_txt": ".txtでダウンロード",
"logout": "この端末からログアウト",
"minimize_recommendations_default": "最初からおすすめを最小化",
"hide_watched": "再生済みの動画をフィードに表示しない",
"hide_watched": "再生済みの動画を新着動画に表示しない",
"minimize_chapters_default": "最初からチャプターを最小化",
"show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
"invalidate_session": "すべての端末からログアウト",
@ -112,8 +114,8 @@
"restore_preferences": "設定を復元",
"back_to_home": "ホームに戻る",
"copy_link": "リンクコピー",
"time_code": "タイムコード (秒)",
"documentation": "ドキュメント",
"time_code": "開始時間 (秒)",
"documentation": "説明書",
"reset_preferences": "設定を初期化",
"confirm_reset_preferences": "設定をリセットしますか?",
"piped_link": "Pipedリンク",
@ -144,10 +146,17 @@
"show_search_suggestions": "検索語句の候補を表示",
"delete_automatically": "指定時間後に自動削除",
"enable_dearrow": "DeArrow を使用",
"generate_qrcode": "QRコード生成",
"generate_qrcode": "QRコード 生成",
"import_from_json_csv": "JSON/CSVから読み込む",
"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": {
"pinned_by": "{author} によって固定",
@ -162,7 +171,9 @@
"ssl_score": "SSLの評価",
"registered_users": "登録利用者数",
"version": "バージョン",
"up_to_date": "最新?"
"up_to_date": "最新?",
"uptime_30d": "稼働率(30日)",
"api_url": "API の URL"
},
"login": {
"username": "ユーザー名",
@ -209,7 +220,9 @@
"days": "{amount}日",
"weeks": "{amount}週",
"hours": "{amount}時間",
"months": "{amount}月"
"months": "{amount}月",
"login_note": "このインスタンス用に作成したアカウントにログインできます。",
"register_note": "この Piped インスタンス用にアカウントを作成します。登録チャンネルと再生リストを同期するために、それらの情報をサーバー内に保存します。アカウントなしでもすべての機能を使用できますが、その場合、すべてのデータは端末内のブラウザーのキャッシュとして保存されます。ユーザー名にメールアドレスを使用しないでください。他のサービスで使っていない安全なパスワードを登録してください。"
},
"subscriptions": {
"subscribed_channels_count": "チャンネル登録: {0}"

View File

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

View File

@ -6,7 +6,7 @@
"hide_replies": "답글 숨기기",
"skip_interaction": "상호 작용 알림 (구독) 스킵",
"show_comments": "댓글 보이기",
"unsubscribe": "구독 취소 - {count}",
"unsubscribe": "구독 취소",
"view_subscriptions": "구독 보기",
"least_recent": "오래된 순",
"theme": "테마",
@ -20,9 +20,9 @@
"skip_non_music": "음악: 음악이 아닌 구간 스킵",
"skip_self_promo": "셀프 프로모션 스킵",
"buffering_goal": "버퍼링 목표 (초)",
"country_selection": "국가 선택",
"country_selection": "국가",
"store_watch_history": "시청 기록 저장",
"language_selection": "언어 선택",
"language_selection": "언어",
"no": "아니요",
"loop_this_video": "이 동영상 반복",
"auto_play_next_video": "다음 동영상 자동 재생",
@ -31,14 +31,14 @@
"sort_by": "정렬:",
"most_recent": "최신 순",
"channel_name_asc": "채널 이름 (A-Z)",
"subscribe": "구독 - {count}",
"subscribe": "구독",
"audio_only": "오디오만",
"skip_sponsors": "스폰서 스킵",
"dark": "다크",
"minimize_description_default": "설명 가리기를 기본 설정값으로",
"enabled_codecs": "활성화된 코덱 (여러 개)",
"instance_selection": "인스턴스 선택",
"import_from_json": "JSON/CSV에서 가져오기",
"instance_selection": "인스턴스",
"import_from_json": "JSON에서 가져오기",
"light": "라이트",
"autoplay_video": "동영상 자동 재생",
"default_quality": "기본 화질",
@ -84,7 +84,7 @@
"back_to_home": "홈으로 가기",
"minimize_recommendations_default": "추천 동영상 가리기를 기본 설정값으로",
"invalidate_session": "모든 기기에서 로그아웃",
"instance_auth_selection": "인증 인스턴스 선택",
"instance_auth_selection": "인증 인스턴스",
"different_auth_instance": "인증에 다른 인스턴스 사용",
"clone_playlist": "재생목록 복제",
"clone_playlist_success": "성공적으로 복제되었습니다!",
@ -103,7 +103,32 @@
"autoplay_next_countdown": "다음 영상 재생까지 대기 시간(초)",
"skip_button_only": "스킵 버튼 표시",
"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": {
"feed": "피드",
@ -119,10 +144,13 @@
"player": "플레이어",
"bookmarks": "북마크",
"livestreams": "실시간",
"channels": "채널"
"channels": "채널",
"dearrow": "",
"channel_groups": "채널 그룹"
},
"player": {
"watch_on": "에서 보기 {0}"
"watch_on": "{0}에서 보기",
"failed": "에러 코드 {0}로 인해 실패했습니다. 더 많은 정보는 로그를 확인해주세요"
},
"comment": {
"pinned_by": "에 의해 고정됨 {author}",
@ -137,11 +165,14 @@
"instance_name": "인스턴스 이름",
"registered_users": "등록 된 사용자",
"version": "버전",
"up_to_date": "최신 버전?"
"up_to_date": "최신 버전?",
"uptime_30d": "가동률 (30일 동안)"
},
"login": {
"username": "유저 이름",
"password": "비밀번호"
"password": "비밀번호",
"password_confirm": "비밀번호 확인",
"passwords_incorrect": "비밀번호가 일치하지 않습니다!"
},
"video": {
"videos": "동영상",
@ -152,7 +183,12 @@
"live": "{0} 라이브",
"shorts": "Shorts",
"chapters": "챕터",
"category": "카테고리"
"category": "카테고리",
"all": "모두",
"license": "라이선스",
"visibility": "가시성",
"chapters_horizontal": "가로 방향",
"chapters_vertical": "수직 방향"
},
"search": {
"did_you_mean": "이것을 찾으셨나요: {0}?",
@ -163,13 +199,23 @@
"channels": "YouTube: 채널",
"playlists": "YouTube: 재생목록",
"music_videos": "YT Music: 동영상",
"music_albums": "YT Music: 앨범"
"music_albums": "YT Music: 앨범",
"music_artists": "유튜브 뮤직: 아티스트"
},
"info": {
"cannot_copy": "복사할 수 없습니다!",
"copied": "복사되었습니다!",
"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": {
"subscribed_channels_count": "구독: {0}"

View File

@ -1,7 +1,7 @@
{
"actions": {
"unsubscribe": "Atšaukti prenumeratą - {count}",
"subscribe": "Prenumeruoti - {count}",
"unsubscribe": "Atšaukti prenumeratą",
"subscribe": "Prenumeruoti",
"instances_list": "Perdavimo šaltinių sąrašas",
"language_selection": "Kalbos pasirinkimas",
"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": {
"view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
"unsubscribe": "സബ്സ്ക്രൈബ് ചെയ്യേണ്ട - {count}",
"subscribe": "സബ്സ്ക്രൈബ് ചെയ്യുക - {count}",
"unsubscribe": "സബ്സ്ക്രൈബ് ചെയ്യേണ്ട",
"subscribe": "സബ്സ്ക്രൈബ് ചെയ്യുക",
"instances_list": "ഇൻസ്റ്റൻസുകളുടെ പട്ടിക",
"minimize_description_default": "സ്ഥിരമായി വിവരണം ചെറുതാക്കുക",
"skip_intro": "ഇടവേള/ആമുഖ ആനിമേഷൻ ഒഴിവാക്കുക",

View File

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

View File

@ -14,14 +14,14 @@
"filter": "Filter",
"skip_filler_tangent": "Opvultangens Overslaan",
"theme": "Thema",
"subscribe": "Abonneren - {count}",
"subscribe": "Abonneren",
"skip_non_music": "Muziek Overslaan: Niet-muzieksectie",
"show_comments": "Opmerkingen tonen",
"skip_self_promo": "Onbetaalde-/zelfpromotie overslaan",
"skip_highlight": "Markering Overslaan",
"skip_interaction": "Interactieherinnering overslaan (abonneren)",
"show_more": "Meer tonen",
"unsubscribe": "Afmelden - {count}",
"unsubscribe": "Afmelden",
"view_subscriptions": "Abonnementen bekijken",
"enable_sponsorblock": "Sponsorblok inschakelen",
"skip_preview": "Voorbeschouwing/samenvatting overslaan",
@ -125,7 +125,14 @@
"auto_display_captions": "Ondertiteling automatisch tonen",
"import_from_json_csv": "Importeren uit JSON/CSV",
"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": {
"register": "Registreren",
@ -134,19 +141,21 @@
"preferences": "Voorkeuren",
"history": "Geschiedenis",
"subscriptions": "Abonnementen",
"trending": "Trending",
"playlists": "Afspeellijsten",
"trending": "Populair",
"playlists": "Afspeel­sten",
"account": "Account",
"instance": "Instantie",
"player": "Speler",
"livestreams": "Livestreams",
"channels": "Kanalen",
"bookmarks": "Bladwijzers",
"bookmarks": "Blad­zers",
"dearrow": "DeArrow",
"channel_groups": "Kanaal­groepen"
"channel_groups": "Kanaal­groepen",
"albums": "Albums",
"custom_instances": "Aangepaste instanties"
},
"player": {
"watch_on": "Bekijken op {0}",
"watch_on": "Bekijken op {0}",
"failed": "Mislukt met foutcode {0}, zie logboeken voor meer informatie"
},
"search": {
@ -190,7 +199,9 @@
"instance_locations": "Instantielocaties",
"version": "Versie",
"up_to_date": "Bijgewerkt?",
"ssl_score": "SSL-score"
"ssl_score": "SSL-score",
"uptime_30d": "Uptime (30d)",
"api_url": "Api-URL"
},
"comment": {
"pinned_by": "Vastgemaakt door {author}",
@ -209,7 +220,9 @@
"days": "{amount} dag(en)",
"weeks": "{amount} week/weken",
"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": {
"subscribed_channels_count": "Geabonneerd op: {0}"

View File

@ -15,15 +15,17 @@
"playlists": "Listas de lecturas",
"bookmarks": "Marcapaginas",
"channel_groups": "Grops de cadenas",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Albums",
"custom_instances": "Instàncias personalizadas"
},
"player": {
"watch_on": "Veire sus {0}",
"failed": "Fracàs amb lo còdi derror {0}, consultat los jornals daudit per mai dinformacions"
},
"actions": {
"subscribe": "Sabonar - {count}",
"unsubscribe": "Se desabonar - {count}",
"subscribe": "Sabonar",
"unsubscribe": "Se desabonar",
"view_subscriptions": "Veire los abonaments",
"sort_by": "Triar per:",
"most_recent": "Mai recents",
@ -147,7 +149,13 @@
"delete_automatically": "Suprimir automaticament aprèp",
"download_frame": "Telecargar fotograma",
"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": {
"instance_locations": "Localizacion de linstància",
@ -156,7 +164,9 @@
"has_cdn": "A un CDN ?",
"version": "Version",
"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": {
"username": "Nom dutilizaire",
@ -191,7 +201,8 @@
"hours": "{amount} ora(s)",
"months": "{amount} mes(es)",
"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": {
"disabled": "Lautor a desactivat los comentaris.",

View File

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

View File

@ -15,15 +15,17 @@
"channels": "Kanały",
"bookmarks": "Zakładki",
"channel_groups": "Grupy kanałów",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Albumy",
"custom_instances": "Niestandardowe instancje"
},
"player": {
"watch_on": "Zobacz na {0}",
"failed": "Niepowodzenie z powodu kodu błędu {0}, przejrzyj logi, aby uzyskać więcej informacji"
},
"actions": {
"subscribe": "Subskrybuj - {count}",
"unsubscribe": "Odsubskrybuj - {count}",
"subscribe": "Subskrybuj",
"unsubscribe": "Odsubskrybuj",
"view_subscriptions": "Zarządzaj subskrybcjami",
"sort_by": "Sortuj:",
"most_recent": "Najnowsze",
@ -43,7 +45,7 @@
"skip_highlight": "Przechodź do meritum filmu",
"skip_filler_tangent": "Pomijaj wstawki humorystyczne",
"theme": "Motyw",
"auto": "Automatyczna",
"auto": "Automatyczny",
"dark": "Ciemny",
"light": "Jasny",
"autoplay_video": "Autoodtwarzanie",
@ -147,7 +149,18 @@
"generate_qrcode": "Wygeneruj kod QR",
"import_from_json_csv": "Import z pliku JSON/CSV",
"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": {
"pinned_by": "Przypięty przez {author}",
@ -162,7 +175,9 @@
"registered_users": "Zarejestrowani użytkownicy",
"version": "Wersja",
"up_to_date": "Aktualna?",
"ssl_score": "Ocena SSL"
"ssl_score": "Ocena SSL",
"uptime_30d": "Czas pracy (30d)",
"api_url": "Adres URL interfejsu API"
},
"login": {
"username": "Nazwa użytkownika",
@ -209,7 +224,9 @@
"days": "{amount} dni",
"weeks": "{amount} tygodnie",
"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": {
"subscribed_channels_count": "Licznik subskrybcji: {0}"

View File

@ -15,7 +15,9 @@
"channels": "Canais",
"bookmarks": "Marcadores",
"channel_groups": "Grupos de canais",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Álbuns",
"custom_instances": "Instâncias personalizadas"
},
"actions": {
"sort_by": "Ordenar por:",
@ -25,9 +27,9 @@
"back": "Recuar",
"uses_api_from": "Utiliza a \"API\" de ",
"enable_sponsorblock": "Ativar \"SponsorBlock\"",
"skip_intro": "Ignorar intermissão/animação de introdução",
"skip_outro": "Ignorar \"Endcards\"/Créditos",
"skip_preview": "Ignorar pré-visualização/recapitulando",
"skip_intro": "Ignorar intromissão/animação de introdução",
"skip_outro": "Ignorar cartões finais/créditos",
"skip_preview": "Ignorar pré-visualização/recapitulação",
"auto": "Automático",
"dark": "Escuro",
"autoplay_video": "Reproduzir vídeos automaticamente",
@ -36,7 +38,7 @@
"country_selection": "País",
"default_homepage": "Página inicial padrão",
"show_comments": "Mostrar comentários",
"minimize_description_default": "Minimizar descrição por omissão",
"minimize_description_default": "Por definição, minimizar descrição",
"store_watch_history": "Guardar histórico de visualizações",
"instances_list": "Lista de instâncias",
"enabled_codecs": "Codificadores ativados (vários)",
@ -52,13 +54,13 @@
"show_recommendations": "Mostrar recomendações",
"disable_lbry": "Desativar \"LBRY\" para emissões",
"enable_lbry_proxy": "Ativar proxy para \"LBRY\"",
"view_ssl_score": "Ver avaliação \"SSL\"",
"view_ssl_score": "Ver avaliação SSL",
"search": "Pesquisa (Ctrl+K)",
"filter": "Filtrar",
"loading": "A carregar...",
"clear_history": "Limpar histórico",
"subscribe": "Subscrever - {count}",
"unsubscribe": "Anular subscrição - {count}",
"subscribe": "Subscrever",
"unsubscribe": "Anular subscrição",
"view_subscriptions": "Ver subscrições",
"channel_name_desc": "Nome do canal (Z-A)",
"skip_sponsors": "Ignorar patrocínios",
@ -77,9 +79,9 @@
"buffering_goal": "Objetivo de 'buffer' (em segundos)",
"skip_filler_tangent": "Ignorar segmentos de preenchimento",
"add_to_playlist": "Adicionar à lista de reprodução",
"delete_playlist": "Apagar lista de reprodução",
"delete_playlist": "Eliminar 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",
"delete_playlist_video_confirm": "Remover vídeo 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": "Clonar lista de reprodução",
"show_markers": "Mostrar marcas no reprodutor",
"delete_account": "Apagar conta",
"delete_account": "Eliminar conta",
"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",
"different_auth_instance": "Usar uma instância diferente para autenticação",
"instance_auth_selection": "Instância para autenticação",
@ -101,18 +103,18 @@
"piped_link": "Ligação do Piped",
"backup_preferences": "Exportar preferências",
"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",
"status_page": "Estado",
"source_code": "Código-fonte",
"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",
"minimize_comments": "Minimizar comentários",
"back_to_home": "Voltar ao início",
"copy_link": "Copiar ligação",
"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",
"with_timecode": "Partilhar com código de tempo",
"show_chapters": "Capítulos",
@ -137,13 +139,24 @@
"edit_playlist": "Editar lista de reprodução",
"playlist_name": "Nome da lista de reprodução",
"show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Aplicações recentemente adicionadas",
"enable_dearrow": "Ativar o DeArrow",
"chapters_layout_mobile": "Esquema de capítulos em dispositivos móveis",
"enable_dearrow": "Ativar DeArrow",
"delete_automatically": "Eliminar automaticamente após",
"generate_qrcode": "Gerar código QR",
"import_from_json_csv": "Importar de JSON/CSV",
"download_frame": "Quadro de transferência",
"instance_privacy_policy": "Política de privacidade"
"download_frame": "Descarregar moldura",
"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": {
"instance_name": "Nome da instância",
@ -152,13 +165,15 @@
"has_cdn": "Tem CDN?",
"version": "Versão",
"registered_users": "Utilizadores registados",
"up_to_date": "Atualizada?"
"up_to_date": "Atualizada?",
"uptime_30d": "Tempo de atividade (30d)",
"api_url": "URL da API"
},
"login": {
"password": "Palavra-passe",
"username": "Nome de utilizador",
"passwords_incorrect": "As palavras-passe não coincidem!",
"password_confirm": "Confirmar a palavra-passe"
"password_confirm": "Confirmar palavra-passe"
},
"video": {
"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.",
"register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
"next_video_countdown": "O próximo vídeo será reproduzido dentro de {0} segundos",
"hours": "{quantidade} hora(s)",
"days": "{quantidade} dia(s)",
"weeks": "{quantidade} semana(s)",
"months": "{quantidade} mês(es)"
"hours": "{amount} hora(s)",
"days": "{amount} dia(s)",
"weeks": "{amount} semana(s)",
"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": {
"view_subscriptions": "Ver Inscrições",
"view_subscriptions": "Ver inscrições",
"back": "Voltar",
"most_recent": "Mais Recente",
"least_recent": "Menos Recente",
"most_recent": "Mais recentes",
"least_recent": "Mais antigos",
"sort_by": "Ordenar por:",
"channel_name_asc": "Nome do Canal (A-Z)",
"channel_name_desc": "Nome do Canal (Z-A)",
"channel_name_asc": "Nome do canal (A-Z)",
"channel_name_desc": "Nome do canal (Z-A)",
"dark": "Escuro",
"light": "Claro",
"show_comments": "Exibir Comentários",
"country_selection": "País",
"default_homepage": "Página Inicial Padrão",
"default_quality": "Qualidade Padrão",
"autoplay_video": "Reprodução Automática",
"minimize_description_default": "Minimizar Descrição por padrão",
"show_comments": "Mostrar comentários",
"country_selection": "Região",
"default_homepage": "Página inicial padrão",
"default_quality": "Qualidade padrão",
"autoplay_video": "Reprodução automática",
"minimize_description_default": "Ocultar descrição por padrão",
"theme": "Tema",
"audio_only": "Apenas Áudio",
"subscribe": "Inscrever-se - {count}",
"unsubscribe": "Desinscrever-se - {count}",
"skip_sponsors": "Pular Patrocinadores",
"audio_only": "Apenas áudio",
"subscribe": "Inscrever-se",
"unsubscribe": "Cancelar inscrição",
"skip_sponsors": "Pular patrocinadores",
"auto": "Automático",
"uses_api_from": "Usa a API de ",
"uses_api_from": "Usa a API ",
"enable_sponsorblock": "Ativar Sponsorblock",
"skip_interaction": "Pular Lembrete de Interação (Inscrever-se)",
"skip_self_promo": "Pular Promoção não paga/Autopromoção",
"show_markers": "Exibir Marcadores no Player",
"skip_intro": "Pular Intervalo/Introdução Animada",
"skip_outro": "Pular Créditos/Cartões finais",
"skip_preview": "Pular Pré-Visualização/Recapitulação",
"skip_highlight": "Pular Destaque",
"buffering_goal": "Cache de Buffer (em segundos)",
"skip_non_music": "Pular Música: Seção não Musical",
"skip_filler_tangent": "Pular Enchimento Tangencial",
"enabled_codecs": "Codecs Ativados (Múltiplos)",
"skip_interaction": "Pular lembrete de interação (inscrever-se)",
"skip_self_promo": "Pular autopromoção gratuita",
"show_markers": "Mostrar marcadores durante a reprodução",
"skip_intro": "Pular intervalo/animação de introdução",
"skip_outro": "Pular considerações/cartões finais",
"skip_preview": "Pular pré-visualização/recapitulação",
"skip_highlight": "Pular destaque",
"buffering_goal": "Meta de Buffer (em segundos)",
"skip_non_music": "Pular música: Partes sem relação com música",
"skip_filler_tangent": "Pular segmentos de preenchimento",
"enabled_codecs": "Codecs ativados (vários)",
"language_selection": "Idioma",
"yes": "Sim",
"show_more": "Mostrar Mais",
"export_to_json": "Exportar para JSON",
"donations": "Doações de desenvolvimento",
"minimize_recommendations": "Minimizar Recomendações",
"show_more": "Mostrar mais",
"export_to_json": "Exportar como JSON",
"donations": "Fazer doação para o desenvolvimento",
"minimize_recommendations": "Ocultar recomendações",
"loading": "Carregando...",
"hide_replies": "Ocultar Respostas",
"minimize_description": "Minimizar Descrição",
"load_more_replies": "Carregar mais Respostas",
"create_playlist": "Criar Playlist",
"delete_playlist": "Excluir Playlist",
"select_playlist": "Selecionar uma Playlist",
"hide_replies": "Ocultar respostas",
"minimize_description": "Ocultar descrição",
"load_more_replies": "Carregar mais respostas",
"create_playlist": "Criar playlist",
"delete_playlist": "Excluir playlist",
"select_playlist": "Escolha uma playlist",
"add_to_playlist": "Adicionar à playlist",
"delete_playlist_confirm": "Excluir esta 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",
"view_ssl_score": "Ver Pontuação SSL",
"disable_lbry": "Desativar LBRY para Streaming",
"enable_lbry_proxy": "Ativar Proxy para LBRY",
"import_from_json": "Importar de JSON",
"loop_this_video": "Repetir este Vídeo",
"instances_list": "Lista de Instâncias",
"clear_history": "Limpar Histórico",
"view_ssl_score": "Ver pontuação SSL",
"disable_lbry": "Desativar LBRY para transmissões",
"enable_lbry_proxy": "Usar proxy em LBRY",
"import_from_json": "Importar arquivo JSON",
"loop_this_video": "Repetir este vídeo",
"instances_list": "Lista de instâncias",
"clear_history": "Limpar histórico",
"search": "Pesquisar (Ctrl+K)",
"no": "Não",
"show_description": "Exibir Descrição",
"show_description": "Mostrar descrição",
"instance_selection": "Instância",
"auto_play_next_video": "Autorreproduzir Próximo Vídeo",
"filter": "Filtro",
"store_watch_history": "Salvar Histórico de Exibição",
"show_recommendations": "Exibir Recomendações",
"minimize_comments_default": "Minimizar Comentários por padrão",
"minimize_comments": "Minimizar Comentários",
"different_auth_instance": "Use uma instância diferente para autenticação",
"delete_account": "Deletar Conta",
"auto_play_next_video": "Reproduzir próximo vídeo automaticamente",
"filter": "Filtrar",
"store_watch_history": "Salvar histórico de exibição",
"show_recommendations": "Mostrar recomendações",
"minimize_comments_default": "Ocultar comentários por padrão",
"minimize_comments": "Ocultar comentários",
"different_auth_instance": "Usar uma instância diferente para autenticação",
"delete_account": "Excluir conta",
"invalidate_session": "Sair de todos os dispositivos",
"clone_playlist": "Clonar Playlist",
"backup_preferences": "Fazer backup das preferências",
"clone_playlist": "Clonar playlist",
"backup_preferences": "Exportar preferências",
"logout": "Sair deste dispositivo",
"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",
"status_page": "Estado",
"status_page": "Status",
"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_auth_selection": "Instância de Autenticação",
"instance_auth_selection": "Instância para autenticação",
"clone_playlist_success": "Clonada com sucesso!",
"download_as_txt": "Baixar como .txt",
"restore_preferences": "Restaurar preferências",
"back_to_home": "Voltar ao início",
"back_to_home": "Ir para a página inicial",
"share": "Compartilhar",
"with_timecode": "Compartilhar com código de tempo",
"with_timecode": "Compartilhar no momento atual",
"piped_link": "Link do Piped",
"follow_link": "Seguir link",
"time_code": "Código de tempo (em segundos)",
"follow_link": "Abrir link",
"time_code": "Momento atual (em segundos)",
"show_chapters": "Capítulos",
"confirm_reset_preferences": "Tem certeza de que deseja redefinir suas preferências?",
"reset_preferences": "Redefinir preferências",
"documentation": "Documentação",
"reply_count": "{count} respostas",
"minimize_recommendations_default": "Minimizar Recomendações por padrão",
"show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
"minimize_chapters_default": "Minimizar Capítulos por padrão",
"reply_count": "{count} resposta(s)",
"minimize_recommendations_default": "Ocultar recomendações por padrão",
"show_watch_on_youtube": "Mostrar botão \"Assistir no YouTube\"",
"minimize_chapters_default": "Ocultar capítulos por padrão",
"no_valid_playlists": "O arquivo não contém playlists válidas!",
"with_playlist": "Compartilhar com playlist",
"with_playlist": "Compartilhar com a playlist",
"bookmark_playlist": "Favorito",
"playlist_bookmarked": "Favoritado",
"playlist_bookmarked": "Salvo como favorito",
"skip_automatically": "Automaticamente",
"skip_segment": "Ignorar Segmento",
"min_segment_length": "Comprimento Mínimo do Segmento (em segundos)",
"skip_button_only": "Mostrar botão pular",
"skip_segment": "Pular segmento",
"min_segment_length": "Comprimento mínimo do segmento (em segundos)",
"skip_button_only": "Mostrar botão \"Pular\"",
"show_less": "Mostrar menos",
"autoplay_next_countdown": "Contagem regressiva padrão até o próximo vídeo (em segundos)",
"dismiss": "Liberar",
"dismiss": "Dispensar",
"cancel": "Cancelar",
"edit_playlist": "Editar playlist",
"playlist_description": "Descrição da playlist",
"okay": "Ok",
"okay": "OK",
"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",
"group_name": "Nome do grupo",
"show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Layout dos Capítulos no Celular",
"delete_automatically": "Deletar automaticamente após",
"generate_qrcode": "Gerar código QR",
"chapters_layout_mobile": "Layout de capítulos em dispositivos móveis",
"delete_automatically": "Excluir automaticamente após",
"generate_qrcode": "Código QR",
"enable_dearrow": "Ativar DeArrow",
"import_from_json_csv": "Importar de JSON/CSV",
"download_frame": "Baixar quadro"
"import_from_json_csv": "Importar arquivo JSON/CSV",
"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": {
"history": "Histórico",
"trending": "Em alta",
"preferences": "Preferências",
"register": "Registrar",
"login": "Entrar",
"register": "Criar conta",
"login": "Fazer login",
"playlists": "Playlists",
"feed": "Feed",
"subscriptions": "Inscrições",
@ -142,42 +153,46 @@
"channels": "Canais",
"livestreams": "Transmissões ao vivo",
"bookmarks": "Favoritos",
"channel_groups": "Grupos de Canais",
"dearrow": "DeArrow"
"channel_groups": "Grupos de canais",
"dearrow": "DeArrow",
"albums": "Álbuns",
"custom_instances": "Instâncias personalizadas"
},
"player": {
"watch_on": "Assistir no {0}",
"failed": "Falhou com código de erro {0}, veja os logs para mais informações"
"watch_on": "Ver em {0}",
"failed": "Falhou com código de erro {0}, consulte os logs para obter mais informações"
},
"comment": {
"pinned_by": "Fixado por {author}",
"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..."
},
"preferences": {
"registered_users": "Usuários Registrados",
"registered_users": "Usuários registrados",
"version": "Versão",
"instance_name": "Nome da Instância",
"instance_locations": "Localizações da Instância",
"instance_name": "Nome da instância",
"instance_locations": "Localizações da instância",
"has_cdn": "Tem CDN?",
"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": {
"username": "Nome de usuário",
"username": "Usuário",
"password": "Senha",
"password_confirm": "Confirme senha",
"passwords_incorrect": "As senhas não correspondem!"
"password_confirm": "Confirmar senha",
"passwords_incorrect": "As senhas não coincidem!"
},
"video": {
"videos": "Vídeos",
"views": "{views} visualizações",
"chapters": "Capítulos",
"live": "{0} Ao vivo",
"live": "{0} ao vivo",
"watched": "Assistido",
"ratings_disabled": "Avaliações Desativadas",
"sponsor_segments": "Segmentos de Patrocinadores",
"ratings_disabled": "Avaliações desativadas",
"sponsor_segments": "Segmentos patrocinados",
"shorts": "Shorts",
"all": "Todos",
"category": "Categoria",
@ -195,21 +210,23 @@
"music_videos": "YT Music: Vídeos",
"music_albums": "YT Music: Álbuns",
"music_playlists": "YT Music: Playlists",
"all": "YouTube: Tudo",
"all": "YouTube: Todos",
"music_artists": "YT Music: Artistas"
},
"info": {
"copied": "Copiado!",
"cannot_copy": "Não foi possível copiar!",
"preferences_note": "Nota: as preferências são salvas no armazenamento local do seu navegador. A exclusão dos dados do seu navegador irá redefini-los.",
"page_not_found": "página não encontrada",
"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",
"local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
"register_no_email_note": "Usar um e-mail como nome de usuário não é recomendado. Continuar mesmo assim?",
"next_video_countdown": "Reproduzindo o próximo vídeo em {0}s",
"next_video_countdown": "Próximo vídeo em {0}s",
"hours": "{amount} hora(s)",
"days": "{amount} dia(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": {
"subscribed_channels_count": "Inscrito em: {0}"

View File

@ -15,7 +15,9 @@
"channels": "Canais",
"bookmarks": "Marcadores",
"channel_groups": "Grupos de canais",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Álbuns",
"custom_instances": "Instâncias personalizadas"
},
"actions": {
"view_subscriptions": "Ver subscrições",
@ -49,7 +51,7 @@
"enabled_codecs": "Codificadores ativados (vários)",
"instance_selection": "Instância",
"show_more": "Mostrar mais",
"import_from_json": "Importar de JSON/CSV",
"import_from_json": "Importar de JSON",
"loop_this_video": "Repetir este vídeo",
"auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
"donations": "Doações para o desenvolvimento",
@ -61,8 +63,8 @@
"search": "Pesquisa (Ctrl+K)",
"hide_replies": "Ocultar respostas",
"load_more_replies": "Carregar mais respostas",
"unsubscribe": "Anular subscrição - {count}",
"subscribe": "Subscrever - {count}",
"unsubscribe": "Anular subscrição",
"subscribe": "Subscrever",
"back": "Recuar",
"audio_only": "Apenas áudio",
"default_quality": "Qualidade padrão",
@ -139,7 +141,17 @@
"show_search_suggestions": "Mostrar sugestões de pesquisa",
"chapters_layout_mobile": "Aplicações recentemente adicionadas",
"enable_dearrow": "Ativar o DeArrow",
"delete_automatically": "Eliminar automaticamente após"
"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": {
"pinned_by": "Afixado por {author}",
@ -154,11 +166,15 @@
"registered_users": "Utilizadores registados",
"ssl_score": "Avaliação SSL",
"up_to_date": "Atualizada?",
"version": "Versão"
"version": "Versão",
"uptime_30d": "Tempo Online (30d)",
"api_url": "URL do API"
},
"login": {
"username": "Nome de utilizador",
"password": "Palavra-passe"
"password": "Palavra-passe",
"password_confirm": "Confirmar senha",
"passwords_incorrect": "Senhas não coincidem!"
},
"video": {
"videos": "Vídeos",
@ -189,7 +205,8 @@
"music_artists": "YT Music: Artistas"
},
"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": {
"subscribed_channels_count": "Subscreveu: {0}"
@ -205,6 +222,8 @@
"hours": "{quantidade} hora(s)",
"days": "{quantidade} dia(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