├── src
├── locales
│ ├── ang.json
│ ├── th.json
│ ├── be.json
│ ├── eu.json
│ ├── bn.json
│ ├── gl.json
│ ├── ml.json
│ ├── nb_NO.json
│ ├── is.json
│ └── lt.json
├── registerServiceWorker.js
├── components
│ ├── PageNotFound.vue
│ ├── VideoRedirect.vue
│ ├── ClipsPage.vue
│ ├── ErrorHandler.vue
│ ├── ToastComponent.vue
│ ├── QrCode.vue
│ ├── ContentItem.vue
│ ├── CreateGroupModal.vue
│ ├── WatchOnButton.vue
│ ├── LoadingIndicatorPage.vue
│ ├── ConfirmModal.vue
│ ├── PlaylistItem.vue
│ ├── SortingSelector.vue
│ ├── ModalComponent.vue
│ ├── CollapsableText.vue
│ ├── CreatePlaylistModal.vue
│ ├── TrendingPage.vue
│ ├── VideoThumbnail.vue
│ ├── FooterComponent.vue
│ ├── ChannelItem.vue
│ ├── LoginPage.vue
│ ├── AddToGroupModal.vue
│ ├── CustomInstanceModal.vue
│ ├── PlaylistAddModal.vue
│ ├── SearchSuggestions.vue
│ ├── PlaylistVideos.vue
│ ├── ChaptersBar.vue
│ ├── ImportHistoryModal.vue
│ ├── RegisterPage.vue
│ ├── CommentItem.vue
│ ├── ExportHistoryModal.vue
│ ├── SearchResults.vue
│ ├── ShareModal.vue
│ ├── FeedPage.vue
│ ├── HistoryPage.vue
│ └── ImportPage.vue
├── utils
│ ├── HtmlUtils.js
│ ├── Misc.js
│ ├── DashUtils.js
│ └── CountryMaps
│ │ └── zh_Hant.json
└── router
│ └── router.js
├── .prettierignore
├── public
├── _redirects
├── robots.txt
├── favicon.ico
├── img
│ └── icons
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── mstile-150x150.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── msapplication-icon-144x144.png
│ │ ├── android-chrome-maskable-192x192.png
│ │ ├── android-chrome-maskable-512x512.png
│ │ ├── logo.svg
│ │ └── safari-pinned-tab.svg
└── opensearch.xml
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.yml
│ └── bug_report.yml
├── FUNDING.yml
└── workflows
│ ├── ci.yml
│ ├── weblate-merge.yml
│ ├── reviewdog.yml
│ ├── ipfs-build.yml
│ ├── deploy-azure.yml
│ ├── docker-build.yml
│ └── codeql.yml
├── babel.config.js
├── .dockerignore
├── .env
├── vercel.json
├── Dockerfile.ci
├── docker
├── nginx.conf
└── entrypoint.sh
├── .prettierrc.json
├── .gitignore
├── localizefonts.sh
├── .eslintrc.cjs
├── Dockerfile
├── sweep.yaml
├── renovate.json
├── index.html
├── uno.config.js
├── package.json
├── vite.config.js
└── CODE_OF_CONDUCT.md
/src/locales/ang.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/favicon.ico
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/cli-plugin-babel/preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .git/
4 | .*
5 | *.md
6 | !.prettier*
7 | !.eslintrc.cjs
8 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: TeamPiped
2 | liberapay: kavin
3 | custom: https://liberapay.com/bnyro
4 |
--------------------------------------------------------------------------------
/public/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/public/img/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-maskable-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/android-chrome-maskable-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-maskable-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code/app-piped-agpl/master/public/img/icons/android-chrome-maskable-512x512.png
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | VITE_PIPED_API=https://pipedapi.kavin.rocks
2 | VITE_PIPED_PROXY=https://pipedproxy.kavin.rocks
3 | VITE_PIPED_INSTANCES=https://piped-instances.kavin.rocks/
4 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { registerSW } from "virtual:pwa-register";
4 |
5 | if (process.env.NODE_ENV === "production") {
6 | registerSW();
7 | }
8 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | },
5 | "routes": [
6 | {
7 | "src": "/[^.]+",
8 | "dest": "/",
9 | "status": 200
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Dockerfile.ci:
--------------------------------------------------------------------------------
1 | FROM nginxinc/nginx-unprivileged:alpine
2 |
3 | COPY --chown=101:101 ./dist-ci/ /usr/share/nginx/html/
4 | COPY --chown=101:101 docker/nginx.conf /etc/nginx/conf.d/default.conf
5 | COPY docker/entrypoint.sh /entrypoint.sh
6 |
7 | EXPOSE 80
8 | ENTRYPOINT [ "/entrypoint.sh" ]
9 |
--------------------------------------------------------------------------------
/docker/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 | server_name localhost;
5 | error_log off;
6 |
7 | location / {
8 | root /usr/share/nginx/html;
9 | index index.html index.htm;
10 | }
11 |
12 | error_page 404 =200 /index.html;
13 | }
14 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": false,
3 | "trailingComma": "all",
4 | "semi": true,
5 | "tabWidth": 4,
6 | "embeddedLanguageFormatting": "auto",
7 | "endOfLine": "lf",
8 | "printWidth": 120,
9 | "vueIndentScriptAndStyle": false,
10 | "quoteProps": "as-needed",
11 | "arrowParens": "avoid"
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | /dist-ci
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/localizefonts.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | base='https://fonts\.(gstatic\.com|kavin\.rocks)'
4 | fonts=$(cat dist/assets/* | grep -Eo "${base}[^)]*" | sort | uniq)
5 |
6 | for font in $fonts; do
7 | file="dist/fonts$(echo "$font" | sed -E "s#$base##")"
8 | mkdir -p "$(dirname "$file")"
9 | curl -L "$font" -o "$file"
10 | done
11 | sed -Ei "s#$base#/fonts#g" dist/assets/*
12 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: ["plugin:vue/vue3-recommended", "eslint:recommended", "@unocss", "plugin:prettier/recommended"],
7 | rules: {
8 | "vue/no-undef-components": ["error", {
9 | ignorePatterns: ["router-link", "router-view", "i18n-t", "ErrorHandler"]
10 | }],
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/PageNotFound.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/VideoRedirect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/src/utils/HtmlUtils.js:
--------------------------------------------------------------------------------
1 | import DOMPurify from "dompurify";
2 |
3 | export const purifyHTML = html => {
4 | return DOMPurify.sanitize(html);
5 | };
6 |
7 | import linkifyHtml from "linkify-html";
8 |
9 | export const rewriteDescription = text => {
10 | return linkifyHtml(text)
11 | .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
12 | .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm, "/watch?v=$1")
13 | .replaceAll("\n", " ");
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/ClipsPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/src/components/ErrorHandler.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
--------------------------------------------------------------------------------
/docker/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ -z "${BACKEND_HOSTNAME}" ]; then
4 | echo "BACKEND_HOSTNAME not set"
5 | exit 1
6 | fi
7 |
8 | HTTP_MODE=${HTTP_MODE:-https}
9 |
10 | sed -i "s@https://pipedapi.kavin.rocks@${HTTP_MODE}://pipedapi.kavin.rocks@g" /usr/share/nginx/html/assets/*
11 | sed -i "s/pipedapi.kavin.rocks/${BACKEND_HOSTNAME}/g" /usr/share/nginx/html/assets/*
12 |
13 | if [ -n "${HTTP_WORKERS}" ]; then
14 | sed -i "s/worker_processes auto;/worker_processes ${HTTP_WORKERS};/g" /etc/nginx/nginx.conf
15 | fi
16 |
17 | if [ -n "${HTTP_PORT}" ]; then
18 | sed -i "s/80;/${HTTP_PORT};/g" /etc/nginx/conf.d/default.conf
19 | fi
20 |
21 | nginx -g "daemon off;"
22 |
--------------------------------------------------------------------------------
/src/components/ToastComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
30 |
--------------------------------------------------------------------------------
/src/components/QrCode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
31 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine AS build
2 |
3 | WORKDIR /app/
4 |
5 | RUN --mount=type=cache,target=/var/cache/apk \
6 | apk add --no-cache \
7 | curl
8 |
9 | COPY . .
10 |
11 | RUN corepack enable && corepack prepare pnpm@latest --activate
12 |
13 | RUN --mount=type=cache,target=/root/.local/share/pnpm \
14 | --mount=type=cache,target=/app/node_modules \
15 | pnpm install --prefer-offline && \
16 | pnpm build && ./localizefonts.sh
17 |
18 | FROM nginxinc/nginx-unprivileged:alpine
19 |
20 | COPY --chown=101:101 --from=build /app/dist/ /usr/share/nginx/html/
21 |
22 | COPY --chown=101:101 docker/nginx.conf /etc/nginx/conf.d/default.conf
23 |
24 | COPY docker/entrypoint.sh /entrypoint.sh
25 |
26 | ENTRYPOINT [ "/entrypoint.sh" ]
27 |
--------------------------------------------------------------------------------
/public/opensearch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Piped
4 | Piped Search
5 | Search for videos, channels, and playlists on Piped
6 | UTF-8
7 | https://piped.video/favicon.ico
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build and Lint
2 |
3 | on:
4 | pull_request:
5 | push:
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v5
12 | - name: Setup pnpm
13 | uses: pnpm/action-setup@v4
14 | with:
15 | version: latest
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | cache: "pnpm"
20 | - run: pnpm install
21 | - run: pnpm build
22 | - uses: actions/upload-artifact@v4
23 | with:
24 | name: build
25 | path: dist
26 | - run: pnpm lint --no-fix
27 |
--------------------------------------------------------------------------------
/.github/workflows/weblate-merge.yml:
--------------------------------------------------------------------------------
1 | name: Merge Weblate translations
2 |
3 | on:
4 | pull_request:
5 | types: [opened, reopened]
6 |
7 | jobs:
8 | merge:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v5
12 | - name: Check if en.json has been updated
13 | run: |
14 | if -n git diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} src/locales/en.json; then
15 | exit 1
16 | fi
17 | - name: AutoMerge Weblate translations
18 | if: github.event.pull_request.user.login == 'weblate'
19 | run: gh pr merge --auto --delete-branch --merge "$PR_URL"
20 | env:
21 | PR_URL: ${{github.event.pull_request.html_url}}
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/reviewdog.yml:
--------------------------------------------------------------------------------
1 | name: reviewdog
2 | on: [pull_request]
3 | jobs:
4 | eslint:
5 | name: runner / eslint
6 | runs-on: ubuntu-latest
7 | permissions:
8 | contents: read
9 | pull-requests: write
10 | steps:
11 | - uses: actions/checkout@v5
12 | - name: Setup pnpm
13 | uses: pnpm/action-setup@v4
14 | with:
15 | version: latest
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | cache: "pnpm"
20 | - run: pnpm install
21 | - uses: reviewdog/action-eslint@v1
22 | with:
23 | github_token: ${{ secrets.GITHUB_TOKEN }}
24 | reporter: github-pr-review
25 | eslint_flags: "--ignore-path .gitignore --ext .js,.vue ."
26 |
--------------------------------------------------------------------------------
/sweep.yaml:
--------------------------------------------------------------------------------
1 | # Sweep AI turns bug fixes & feature requests into code changes (https://sweep.dev)
2 | # For details on our config file, check out our docs at https://docs.sweep.dev
3 |
4 | # If you use this be sure to frequently sync your default branch(main, master) to dev.
5 | branch: 'master'
6 | # By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false.
7 | gha_enabled: True
8 | # This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want.
9 | # Here's an example: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8.
10 | description: 'TeamPiped/Piped is a Vue 3 project that uses UnoCSS (similar to Tailwind).'
11 |
12 | # Default Values: https://github.com/sweepai/sweep/blob/main/sweep.yaml
13 |
--------------------------------------------------------------------------------
/src/locales/th.json:
--------------------------------------------------------------------------------
1 | {
2 | "titles": {
3 | "login": "ลงชื่อเข้าใช้",
4 | "trending": "กำลังมาแรง",
5 | "preferences": "การตั้งค่า",
6 | "history": "ประวัติ",
7 | "subscriptions": "การสมัครรับข้อมูล",
8 | "register": "ลงทะเบียน"
9 | },
10 | "player": {
11 | "watch_on": "ดูบน {0}"
12 | },
13 | "actions": {
14 | "unsubscribe": "เลิกติดตาม",
15 | "view_subscriptions": "ดูการสมัครสมาชิก",
16 | "channel_name_asc": "ชื่อช่อง (A-Z)",
17 | "channel_name_desc": "ชื่อช่อง (Z-A)",
18 | "back": "กลับ",
19 | "uses_api_from": "ใช้ API จาก ",
20 | "skip_sponsors": "ข้ามผู้สนับสนุน",
21 | "skip_intro": "ข้ามช่วงพัก/แนะนำแอนิเมชั่น",
22 | "skip_outro": "ข้ามภาพตอนจบ/เครดิต",
23 | "subscribe": "ติดตาม",
24 | "sort_by": "เรียงตาม:",
25 | "most_recent": "ล่าสุด",
26 | "enable_sponsorblock": "เปิดใช้งานการบล็อกสปอนเซอร์"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", "group:recommended"],
4 | "ignorePresets": [":prHourlyLimit2"],
5 | "packageRules": [
6 | {
7 | "matchPackagePrefixes": ["@unocss/"],
8 | "matchPackageNames": ["unocss"],
9 | "groupName": "unocss"
10 | },
11 | {
12 | "matchPackagePrefixes": ["linkify"],
13 | "groupName": "linkify"
14 | },
15 | {
16 | "matchPackagePrefixes": ["@vitejs/"],
17 | "matchPackageNames": ["vite"],
18 | "groupName": "vite"
19 | },
20 | {
21 | "matchPackagePrefixes": ["@iconify-json/"],
22 | "groupName": "iconify",
23 | "automerge": true
24 | }
25 | ],
26 | "lockFileMaintenance": {
27 | "enabled": true,
28 | "automerge": true
29 | },
30 | "platformAutomerge": true
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/Misc.js:
--------------------------------------------------------------------------------
1 | export const isEmail = input => {
2 | // Taken from https://emailregex.com
3 | const result = input.match(
4 | //eslint-disable-next-line
5 | /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
6 | );
7 | return result;
8 | };
9 |
10 | export const parseTimeParam = time => {
11 | let start = 0;
12 | if (/^[\d]*$/g.test(time)) {
13 | start = time;
14 | } else {
15 | const hours = /([\d]*)h/gi.exec(time)?.[1];
16 | const minutes = /([\d]*)m/gi.exec(time)?.[1];
17 | const seconds = /([\d]*)s/gi.exec(time)?.[1];
18 | if (hours) {
19 | start += parseInt(hours) * 60 * 60;
20 | }
21 | if (minutes) {
22 | start += parseInt(minutes) * 60;
23 | }
24 | if (seconds) {
25 | start += parseInt(seconds);
26 | }
27 | }
28 | return start;
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/ContentItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
39 |
--------------------------------------------------------------------------------
/src/components/CreateGroupModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
36 |
--------------------------------------------------------------------------------
/src/components/WatchOnButton.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | {{ platform }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/LoadingIndicatorPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
56 |
--------------------------------------------------------------------------------
/src/components/ConfirmModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
43 |
--------------------------------------------------------------------------------
/.github/workflows/ipfs-build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - "**.md"
7 | branches:
8 | - master
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v5
15 | - name: Setup pnpm
16 | uses: pnpm/action-setup@v4
17 | with:
18 | version: latest
19 | - name: Setup Node.js
20 | uses: actions/setup-node@v4
21 | with:
22 | cache: "pnpm"
23 | - run: pnpm install
24 | - run: pnpm build && ./localizefonts.sh && cp dist/index.html dist/ipfs-404.html
25 | - uses: aquiladev/ipfs-action@v0.3.1-alpha.2
26 | id: ipfs-add
27 | with:
28 | path: ./dist
29 | service: infura
30 | infuraProjectId: ${{ secrets.INFURA_PROJECT_ID }}
31 | infuraProjectSecret: ${{ secrets.INFURA_PROJECT_SECRET }}
32 | - name: Update DNSLink
33 | run: npx dnslink-cloudflare -d kavin.rocks -l /ipfs/${{ steps.ipfs-add.outputs.hash }} -r _dnslink.piped-ipfs
34 | env:
35 | CF_API_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
36 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Piped
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 | We're sorry but Piped doesn't work properly without JavaScript enabled. Please enable it to
25 | continue.
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-azure.yml:
--------------------------------------------------------------------------------
1 | name: Azure Static Web Apps CI/CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build_and_deploy_job:
10 | if: github.event_name == 'push'
11 | runs-on: ubuntu-latest
12 | name: Build and Deploy Job
13 | steps:
14 | - uses: actions/checkout@v5
15 | with:
16 | submodules: true
17 | - name: Build And Deploy
18 | id: builddeploy
19 | uses: Azure/static-web-apps-deploy@v1
20 | with:
21 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LEMON_WATER_0063A7F03 }}
22 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
23 | action: "upload"
24 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
25 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
26 | app_location: "/" # App source code path
27 | app_build_command: "yarn build && ./localizefonts.sh"
28 | api_location: "" # Api source code path - optional
29 | output_location: "dist" # Built app content directory - optional
30 | ###### End of Repository/Build Configurations ######
31 |
--------------------------------------------------------------------------------
/src/components/PlaylistItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
36 |
--------------------------------------------------------------------------------
/uno.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "unocss";
2 | import transformerDirective from "@unocss/transformer-directives";
3 | import transformerVariantGroup from "@unocss/transformer-variant-group";
4 |
5 | import presetUno from "@unocss/preset-uno";
6 | import presetIcons from "@unocss/preset-icons";
7 | import presetWebFonts from "@unocss/preset-web-fonts";
8 |
9 | export default defineConfig({
10 | transformers: [transformerDirective(), transformerVariantGroup()],
11 | presets: [
12 | presetUno({
13 | dark: "media",
14 | }),
15 | presetIcons({
16 | extraProperties: {
17 | display: "inline-block",
18 | "vertical-align": "middle",
19 | },
20 | }),
21 | presetWebFonts({
22 | provider: "none",
23 | fonts: {
24 | sans: [
25 | "-apple-system",
26 | "BlinkMacSystemFont",
27 | "Segoe UI",
28 | "Roboto",
29 | "Helvetica Neue",
30 | "Arial",
31 | "Noto Sans",
32 | "sans-serif",
33 | "Apple Color Emoji",
34 | "Segoe UI Emoji",
35 | "Segoe UI Symbol",
36 | "Noto Color Emoji",
37 | ],
38 | },
39 | }),
40 | ],
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/SortingSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
48 |
--------------------------------------------------------------------------------
/src/components/ModalComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
35 |
36 |
59 |
--------------------------------------------------------------------------------
/src/components/CollapsableText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ...
8 |
13 | [{{ showFullText ? $t("actions.show_less") : $t("actions.show_more") }}]
14 |
15 |
16 |
17 |
18 |
47 |
48 |
53 |
--------------------------------------------------------------------------------
/.github/workflows/docker-build.yml:
--------------------------------------------------------------------------------
1 | name: Docker Multi-Architecture Build
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - "**.md"
7 | branches:
8 | - master
9 |
10 | jobs:
11 | build-docker-image:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v5
15 | - name: Setup pnpm
16 | uses: pnpm/action-setup@v4
17 | with:
18 | version: latest
19 | - name: Setup Node.js
20 | uses: actions/setup-node@v4
21 | with:
22 | cache: "pnpm"
23 | - run: pnpm install
24 | - run: pnpm build && ./localizefonts.sh && mv dist/ dist-ci/
25 | - name: Set up QEMU
26 | uses: docker/setup-qemu-action@v3
27 | with:
28 | platforms: all
29 | - name: Set up Docker Buildx
30 | id: buildx
31 | uses: docker/setup-buildx-action@v3
32 | with:
33 | version: latest
34 | - name: Login to DockerHub
35 | uses: docker/login-action@v3
36 | with:
37 | username: ${{ secrets.DOCKER_USERNAME }}
38 | password: ${{ secrets.DOCKER_PASSWORD }}
39 | - name: Build and push
40 | uses: docker/build-push-action@v6
41 | with:
42 | context: .
43 | file: ./Dockerfile.ci
44 | platforms: linux/amd64,linux/arm64
45 | push: true
46 | tags: 1337kavin/piped-frontend:latest
47 | cache-from: type=gha
48 | cache-to: type=gha,mode=max
49 |
--------------------------------------------------------------------------------
/src/components/CreatePlaylistModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
55 |
--------------------------------------------------------------------------------
/src/components/TrendingPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "piped",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "format": "prettier -w --ignore-path .gitignore **/**.{js,vue,json}",
11 | "lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
12 | },
13 | "dependencies": {
14 | "dompurify": "3.3.0",
15 | "fast-xml-parser": "5.2.5",
16 | "hotkeys-js": "3.13.15",
17 | "javascript-time-ago": "2.5.11",
18 | "linkify-html": "4.3.2",
19 | "linkifyjs": "4.3.2",
20 | "qrcode": "^1.5.3",
21 | "shaka-player": "4.15.9",
22 | "vue": "3.5.22",
23 | "vue-i18n": "11.1.12",
24 | "vue-router": "4.5.1"
25 | },
26 | "devDependencies": {
27 | "@iconify-json/fa6-brands": "1.2.6",
28 | "@iconify-json/fa6-solid": "1.2.4",
29 | "@intlify/unplugin-vue-i18n": "6.0.8",
30 | "@unocss/eslint-config": "66.4.1",
31 | "@unocss/preset-icons": "66.4.1",
32 | "@unocss/preset-uno": "66.4.1",
33 | "@unocss/preset-web-fonts": "66.4.1",
34 | "@unocss/reset": "66.4.1",
35 | "@unocss/transformer-directives": "66.4.1",
36 | "@unocss/transformer-variant-group": "66.4.1",
37 | "@vitejs/plugin-legacy": "7.2.1",
38 | "@vitejs/plugin-vue": "6.0.1",
39 | "@vue/compiler-sfc": "3.5.22",
40 | "eslint": "8.57.1",
41 | "eslint-config-prettier": "10.1.8",
42 | "eslint-plugin-prettier": "5.5.3",
43 | "eslint-plugin-vue": "9.33.0",
44 | "lightningcss": "1.30.1",
45 | "prettier": "3.5.3",
46 | "unocss": "66.4.1",
47 | "vite": "7.1.5",
48 | "vite-plugin-eslint": "1.8.1",
49 | "vite-plugin-pwa": "1.0.2",
50 | "workbox-window": "7.3.0"
51 | },
52 | "browserslist": [
53 | "last 1 chrome version",
54 | "last 1 firefox version"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest a feature you would like to see added
3 | labels: [enhancement]
4 | body:
5 |
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to suggest a new feature! Please follow these steps:
10 | 1. Make sure that nobody else [suggested the feature yet][issue search].
11 | 2. Write a short and informative title.
12 | 3. Fill in all the requested information in this form.
13 |
14 | [issue search]: ../search?q=is%3Aissue&type=issues
15 |
16 | - type: textarea
17 | id: description
18 | attributes:
19 | label: Describe the feature
20 | description: A clear and concise description of what the feature is and how it should work.
21 | placeholder: Option to ...
22 | validations:
23 | required: true
24 |
25 | - type: textarea
26 | id: benefits
27 | attributes:
28 | label: Why would this be useful to add?
29 | description: Explain why this feature would be useful to you.
30 | placeholder: This would make it easier to ...
31 | validations:
32 | required: true
33 |
34 | - type: textarea
35 | id: concepts
36 | attributes:
37 | label: Concept(s)
38 | description: Post concepts or drawings to better explain the feature here.
39 | validations:
40 | required: false
41 |
42 | - type: textarea
43 | id: additional-context
44 | attributes:
45 | label: Additional context
46 | description: Add any other context about the feature here.
47 | validations:
48 | required: false
49 |
50 | - type: checkboxes
51 | id: acknowledgements
52 | attributes:
53 | label: Acknowledgements
54 | description: Your issue will be closed if you don't follow these steps.
55 | options:
56 | - label: I have searched the existing issues and this is **NOT** a duplicate or related to another open issue.
57 | required: true
58 | - label: I have written a short but informative title.
59 | required: true
60 | - label: I filled out all of the requested information in this form.
61 | required: true
62 |
--------------------------------------------------------------------------------
/src/components/VideoThumbnail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
60 |
61 |
66 |
--------------------------------------------------------------------------------
/src/components/FooterComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
53 |
54 |
62 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | description: Create a bug report to help us improve.
4 | labels: [bug]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | **Thanks for taking a minute to file a bug report!**
10 |
11 | ⚠
12 | Verify first that your issue is not [already reported on
13 | GitHub][issue search].
14 |
15 | _Please fill out the form below with as many precise
16 | details as possible._
17 |
18 | [issue search]: ../search?q=is%3Aissue&type=issues
19 |
20 | - type: checkboxes
21 | attributes:
22 | label: Official Instance
23 | description: Can the bug be reproduced on the official instance?
24 | options:
25 | - label: The bug is reproducible on the [official hosted instance](http://piped.video/), or is API-related.
26 |
27 | - type: textarea
28 | attributes:
29 | label: Describe the bug
30 | description: >-
31 | A clear and concise description of what the bug is.
32 | validations:
33 | required: true
34 |
35 | - type: textarea
36 | attributes:
37 | label: To Reproduce
38 | description: >-
39 | Describe the steps to reproduce this bug.
40 | placeholder: |
41 | 1. Open this URL at '...'
42 | 2. Then do '...'
43 | 3. Observe error '...'
44 | validations:
45 | required: true
46 |
47 | - type: textarea
48 | attributes:
49 | label: Expected behavior
50 | description: >-
51 | A clear and concise description of what you expected to happen.
52 | validations:
53 | required: true
54 |
55 | - type: textarea
56 | attributes:
57 | label: Logs/Errors
58 | description: |
59 | If applicable, add logs from the JavaScript console and/or backend server.
60 | validations:
61 | required: false
62 |
63 | - type: textarea
64 | attributes:
65 | label: Browser, and OS with Version.
66 | description: >-
67 | If applicable, add browser, and OS with version.
68 | placeholder: >-
69 | Brave 1.x.x on Arch Linux.
70 | validations:
71 | required: true
72 |
73 | - type: textarea
74 | attributes:
75 | label: Additional context
76 | description: |
77 | Add any other context about the problem here.
78 |
--------------------------------------------------------------------------------
/src/components/ChannelItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
34 |
35 |
36 |
37 |
38 |
39 |
73 |
--------------------------------------------------------------------------------
/src/components/LoginPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
36 |
37 |
38 |
74 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ 'master' ]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [ 'master' ]
9 | schedule:
10 | - cron: '42 11 * * 4'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 | permissions:
17 | actions: read
18 | contents: read
19 | security-events: write
20 |
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | language: [ 'javascript' ]
25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
26 | # Use only 'java' to analyze code written in Java, Kotlin or both
27 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
28 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
29 |
30 | steps:
31 | - name: Checkout repository
32 | uses: actions/checkout@v5
33 |
34 | # Initializes the CodeQL tools for scanning.
35 | - name: Initialize CodeQL
36 | uses: github/codeql-action/init@v3
37 | with:
38 | languages: ${{ matrix.language }}
39 | # If you wish to specify custom queries, you can do so here or in a config file.
40 | # By default, queries listed here will override any specified in a config file.
41 | # Prefix the list here with "+" to use these queries and those in the config file.
42 |
43 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
44 | # queries: security-extended,security-and-quality
45 |
46 |
47 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
48 | # If this step fails, then you should remove it and run the build manually (see below)
49 | - name: Autobuild
50 | uses: github/codeql-action/autobuild@v3
51 |
52 | # ℹ️ Command-line programs to run using the OS shell.
53 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
54 |
55 | # If the Autobuild fails above, remove it and uncomment the following three lines.
56 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
57 |
58 | # - run: |
59 | # echo "Run, Build Application using script"
60 | # ./location_of_script_within_repo/buildscript.sh
61 |
62 | - name: Perform CodeQL Analysis
63 | uses: github/codeql-action/analyze@v3
64 | with:
65 | category: "/language:${{matrix.language}}"
66 |
--------------------------------------------------------------------------------
/src/components/AddToGroupModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 |
27 |
28 |
81 |
--------------------------------------------------------------------------------
/src/components/CustomInstanceModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ customInstance.name }} - {{ customInstance.api_url }}
10 |
14 |
15 |
16 |
17 |
18 |
29 |
30 |
31 |
32 |
33 |
80 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import vue from "@vitejs/plugin-vue";
3 | import Unocss from "unocss/vite";
4 | import legacy from "@vitejs/plugin-legacy";
5 | import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
6 | import { VitePWA } from "vite-plugin-pwa";
7 | import path from "path";
8 | import eslintPlugin from "vite-plugin-eslint";
9 |
10 | // https://vitejs.dev/config/
11 | export default defineConfig({
12 | plugins: [
13 | vue(),
14 | Unocss(),
15 | VueI18nPlugin({
16 | include: path.resolve(__dirname, "./src/locales/**"),
17 | }),
18 | legacy({
19 | targets: ["defaults", "not IE 11"],
20 | }),
21 | VitePWA({
22 | registerType: "autoUpdate",
23 | workbox: {
24 | globPatterns: [
25 | "**/*.{css,html}",
26 | "**/[A-Z]*.js",
27 | "**/index*.js",
28 | "**/shaka-player*.js",
29 | "manifest.webmanifest",
30 | ],
31 | globIgnores: ["**/*-legacy-*.js"],
32 | runtimeCaching: [
33 | {
34 | urlPattern: /https:\/\/[a-zA-Z./0-9_]*\.(?:otf|ttf)/i,
35 | handler: "CacheFirst",
36 | options: {
37 | cacheName: "fonts-cache",
38 | expiration: {
39 | maxEntries: 10,
40 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
41 | },
42 | cacheableResponse: {
43 | statuses: [0, 200],
44 | },
45 | },
46 | },
47 | ],
48 | },
49 | manifest: {
50 | name: "Piped",
51 | short_name: "Piped",
52 | background_color: "#000000",
53 | theme_color: "#fa4b4b",
54 | icons: [
55 | { src: "./img/icons/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
56 | { src: "./img/icons/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
57 | {
58 | src: "./img/icons/android-chrome-maskable-192x192.png",
59 | sizes: "192x192",
60 | type: "image/png",
61 | purpose: "maskable",
62 | },
63 | {
64 | src: "./img/icons/android-chrome-maskable-512x512.png",
65 | sizes: "512x512",
66 | type: "image/png",
67 | purpose: "maskable",
68 | },
69 | ],
70 | },
71 | }),
72 | eslintPlugin(),
73 | ],
74 | resolve: {
75 | alias: {
76 | "@": path.resolve(__dirname, "./src"),
77 | },
78 | },
79 | build: {
80 | sourcemap: true,
81 | cssMinify: "lightningcss",
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/src/components/PlaylistAddModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
20 |
21 |
22 |
27 |
28 |
29 |
99 |
--------------------------------------------------------------------------------
/src/router/router.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from "vue-router";
2 |
3 | const routes = [
4 | {
5 | path: "/",
6 | name: "Home",
7 | component: () => import("../components/TrendingPage.vue"),
8 | },
9 | {
10 | path: "/trending",
11 | name: "Trending",
12 | component: () => import("../components/TrendingPage.vue"),
13 | },
14 | {
15 | path: "/preferences",
16 | name: "Preferences",
17 | component: () => import("../components/PreferencesPage.vue"),
18 | },
19 | {
20 | path: "/results",
21 | name: "SearchResults",
22 | component: () => import("../components/SearchResults.vue"),
23 | },
24 | {
25 | path: "/playlist",
26 | name: "Playlist",
27 | component: () => import("../components/PlaylistPage.vue"),
28 | },
29 | {
30 | path: "/:path(v|w|embed|live|shorts|watch)/:v?",
31 | name: "WatchVideo",
32 | component: () => import("../components/WatchVideo.vue"),
33 | },
34 | {
35 | path: "/watch_videos",
36 | name: "WatchVideos",
37 | component: () => import("../components/WatchVideo.vue"),
38 | },
39 | {
40 | path: "/clip/:clipId",
41 | name: "Clips",
42 | component: () => import("../components/ClipsPage.vue"),
43 | },
44 | {
45 | path: "/:path(channel|user|c)/:channelId/:videos?",
46 | name: "Channel",
47 | component: () => import("../components/ChannelPage.vue"),
48 | },
49 | {
50 | path: "/@:channelId",
51 | name: "Channel handle",
52 | component: () => import("../components/ChannelPage.vue"),
53 | },
54 | {
55 | path: "/login",
56 | name: "Login",
57 | component: () => import("../components/LoginPage.vue"),
58 | },
59 | {
60 | path: "/register",
61 | name: "Register",
62 | component: () => import("../components/RegisterPage.vue"),
63 | },
64 | {
65 | path: "/feed",
66 | alias: ["/feed/subscriptions"],
67 | name: "Feed",
68 | component: () => import("../components/FeedPage.vue"),
69 | },
70 | {
71 | path: "/import",
72 | name: "Import",
73 | component: () => import("../components/ImportPage.vue"),
74 | },
75 | {
76 | path: "/:videoId([a-zA-Z0-9_-]{11})",
77 | component: () => import("../components/VideoRedirect.vue"),
78 | },
79 | {
80 | path: "/subscriptions",
81 | name: "Subscriptions",
82 | component: () => import("../components/SubscriptionsPage.vue"),
83 | },
84 | {
85 | path: "/history",
86 | name: "Watch History",
87 | component: () => import("../components/HistoryPage.vue"),
88 | },
89 | {
90 | path: "/playlists",
91 | name: "Playlists",
92 | component: () => import("../components/PlaylistsPage.vue"),
93 | },
94 | {
95 | path: "/:pathMatch(.*)*",
96 | name: "Page Not Found",
97 | component: () => import("../components/PageNotFound.vue"),
98 | },
99 | ];
100 |
101 | const router = createRouter({
102 | history: createWebHistory(),
103 | routes,
104 | scrollBehavior: function (_to, _from, savedPosition) {
105 | return savedPosition ? savedPosition : window.scrollTo(0, 0);
106 | },
107 | });
108 |
109 | export default router;
110 |
--------------------------------------------------------------------------------
/src/components/SearchSuggestions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 | {{ suggestion }}
16 |
17 |
18 |
19 |
20 |
21 |
83 |
84 |
105 |
--------------------------------------------------------------------------------
/src/locales/be.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": {
3 | "okay": "Добра",
4 | "reply_count": "{count} адказаў",
5 | "skip_button_only": "Паказаць кнопку прапусціць",
6 | "channel_name_asc": "Імя канала (А-Я)",
7 | "download_as_txt": "Спампаваць як .txt",
8 | "subscribe": "Падпісацца",
9 | "loading": "Загрузка...",
10 | "filter": "Фільтар",
11 | "unsubscribe": "Адпісацца",
12 | "channel_name_desc": "Імя канала (Я-А)",
13 | "language_selection": "Мова",
14 | "show_less": "Паказаць менш",
15 | "theme": "Тэма",
16 | "hide_replies": "Схаваць адказы",
17 | "least_recent": "Найменш актуальныя",
18 | "most_recent": "Самыя актуальныя",
19 | "no": "Не",
20 | "documentation": "Дакументацыя",
21 | "sort_by": "Сартаваць па:",
22 | "back": "Назад",
23 | "view_subscriptions": "Паказаць падпіскі",
24 | "status_page": "Статус",
25 | "import_from_json": "Імпартаваць з JSON",
26 | "instance_privacy_policy": "Палітыка прыватнасці",
27 | "bookmark_playlist": "Закладка",
28 | "skip_outro": "Прапусціць канчатковыя цітры/тытры",
29 | "skip_intro": "Прапусціць паўзу/застаўку",
30 | "skip_sponsors": "Прапусціць спонсарскую рэкламу",
31 | "skip_automatically": "Аўтаматычна",
32 | "enable_sponsorblock": "Уключыць Sponsorblock",
33 | "uses_api_from": "Выкарыстоўвае API ад ",
34 | "light": "Светлая",
35 | "autoplay_next_countdown": "Адлік па змаўчанні да наступнага відэа (у секундах)",
36 | "minimize_description_default": "Згарнуць апісанне па змаўчанні",
37 | "instances_list": "Спіс люстэркаў сэрвісу Piped",
38 | "skip_preview": "Прапускаць кароткі змест бягучага эпізоду/паўтор часткі мінулага",
39 | "skip_interaction": "Прапускаць просьбы падпісацца (Падпіска)",
40 | "skip_self_promo": "Прапускаць самарэкламу",
41 | "skip_non_music": "Прапускаць цішыню ў музычных роліках",
42 | "min_segment_length": "Мінімальная даўжыня сегмента (у секундах)",
43 | "skip_segment": "Прапусціць сегмент",
44 | "enable_dearrow": "Уключыць DeArrow",
45 | "auto": "Аўто",
46 | "instances_not_shown": "Агульнадаступныя экзэмпляры, якія тут не паказаны, у цяперашні час недаступныя.",
47 | "skip_highlight": "Перайсці да сутнасці відэа",
48 | "show_markers": "Паказаць маркеры на прайгравальніку",
49 | "dark": "Цёмная",
50 | "autoplay_video": "Аўтапрайграванне відэа",
51 | "audio_only": "Толькі гук",
52 | "default_quality": "Якасць па змаўчанні",
53 | "buffering_goal": "Памер буфера відэа (у секундах)",
54 | "country_selection": "Краіна/Рэгіён",
55 | "minimize_comments_default": "Згортваць каментары па змаўчанні",
56 | "store_watch_history": "Захоўваць гісторыю прагледжаных відэа",
57 | "enabled_codecs": "Уключаныя кодэкі (Можна выбраць некалькі)"
58 | },
59 | "titles": {
60 | "subscriptions": "Падпіскі",
61 | "trending": "Папулярнае",
62 | "login": "Уваход",
63 | "preferences": "Налады",
64 | "history": "Гісторыя",
65 | "bookmarks": "Закладкі",
66 | "channels": "Каналы",
67 | "register": "Рэгістрацыя",
68 | "livestreams": "Жывыя трансляцыі",
69 | "feed": "Стужка",
70 | "channel_groups": "Групы каналаў",
71 | "account": "Ўліковы запіс",
72 | "instance": "Інстанс",
73 | "playlists": "Плэйлісты",
74 | "player": "Прайгравальнік",
75 | "dearrow": "DeArrow",
76 | "albums": "Альбомы",
77 | "custom_instances": "Карыстальніцкія серверы"
78 | },
79 | "player": {
80 | "watch_on": "Прагляд на {0}",
81 | "failed": "Памылка з кодам памылкі {0}, для атрымання дадатковай інфармацыі глядзіце журналы"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/locales/eu.json:
--------------------------------------------------------------------------------
1 | {
2 | "video": {
3 | "sponsor_segments": "Babesletza-segmentuak",
4 | "watched": "Ikusiak",
5 | "views": "{views} ikustaldi",
6 | "videos": "Bideoak"
7 | },
8 | "preferences": {
9 | "ssl_score": "SSL puntuazioa",
10 | "has_cdn": "CDNrik ba al du?",
11 | "instance_locations": "Instantziaren kokalekuak",
12 | "instance_name": "Instantziaren izena"
13 | },
14 | "comment": {
15 | "pinned_by": "Finkatuta {author}"
16 | },
17 | "actions": {
18 | "enable_lbry_proxy": "Aktibatu Proxy LBRYrako",
19 | "disable_lbry": "Desaktibatu LBRY streaming-erako",
20 | "show_description": "Erakutsi deskribapena",
21 | "minimize_description": "Deskribapena minimizatu",
22 | "donations": "Dohaintzak",
23 | "auto_play_next_video": "Hurrengo bideoa automatikoki erreproduzitu",
24 | "loop_this_video": "Bideo hau begiztan jarri",
25 | "import_from_json": "Inportatu JSON/CSVetik",
26 | "export_to_json": "Esportatu JSONera",
27 | "no": "Ez",
28 | "yes": "Bai",
29 | "show_more": "Erakutsi gehiago",
30 | "instance_selection": "Instantzien hautaketa",
31 | "enabled_codecs": "Gaitutako kodeak (anizkoitzak)",
32 | "instances_list": "Instantzien zerrenda",
33 | "language_selection": "Hizkuntzaren hautaketa",
34 | "store_watch_history": "Bistaratze-historia gogoratu",
35 | "minimize_description_default": "Lehenetsitako deskribapena minimizatu",
36 | "show_comments": "Erakutsi iruzkinak",
37 | "default_homepage": "Lehenetsitako hasiera-orria",
38 | "country_selection": "Herrialdeen hautaketa",
39 | "buffering_goal": "Moteltzeko helburua (segundotan)",
40 | "default_quality": "Kalitate lehenetsia",
41 | "audio_only": "Audioa bakarrik",
42 | "autoplay_video": "Bideoaren erreprodukzio automatikoa",
43 | "light": "Gai argia",
44 | "dark": "Gai iluna",
45 | "auto": "Gailuaren gaia (Automatikoa)",
46 | "theme": "Gaia",
47 | "skip_non_music": "Musika saltatu: sekzio ez musikala",
48 | "skip_self_promo": "Ordaindu gabe salto egitea/autosustapena",
49 | "skip_interaction": "Ez aipatu interakzioaren oroigarria (Harpidetu)",
50 | "skip_preview": "Aurreko bista/laburpena saltatu",
51 | "skip_outro": "Amaierako txartelak/kredituak alde batera utzi",
52 | "skip_intro": "Bitartekoa/sarrerako animazioa saltatu",
53 | "skip_sponsors": "Babesleak alde batera utzi",
54 | "enable_sponsorblock": "Aktibatu SponsorBlock",
55 | "uses_api_from": "Honako API hau erabiltzen du: ",
56 | "back": "Itzuli",
57 | "channel_name_desc": "Kanalaren izena (Z-A)",
58 | "channel_name_asc": "Kanalaren izena (A-Z)",
59 | "least_recent": "Gutxien berriena",
60 | "most_recent": "Berriena",
61 | "sort_by": "Ordenatu honen arabera:",
62 | "view_subscriptions": "Harpidetzak ikusi",
63 | "unsubscribe": "Kendu harpidetza",
64 | "subscribe": "Harpidetu",
65 | "loading": "Kargatzen...",
66 | "filter": "Iragazi",
67 | "search": "Bilatu",
68 | "view_ssl_score": "Ikusi SSL puntuazioa",
69 | "minimize_recommendations": "Gomendioak minimizatu",
70 | "show_recommendations": "Erakutsi gomendioak"
71 | },
72 | "player": {
73 | "watch_on": "Ikusi hemen {0}"
74 | },
75 | "titles": {
76 | "subscriptions": "Harpidetzak",
77 | "history": "Historia",
78 | "preferences": "Ezarpenak",
79 | "feed": "Orri nagusia",
80 | "register": "Erregistratu",
81 | "login": "Hasi saioa",
82 | "trending": "Pil-pilean"
83 | },
84 | "login": {
85 | "password": "Pasahitza",
86 | "username": "Erabiltzailearen izena"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/locales/bn.json:
--------------------------------------------------------------------------------
1 | {
2 | "titles": {
3 | "trending": "জনপ্রিয় ভিডিও",
4 | "login": "অ্যাকাউন্ট লগইন",
5 | "register": "একাউন্ট বানান",
6 | "feed": "আপনার ফিড",
7 | "preferences": "সেটিংস",
8 | "history": "ইতিহাস",
9 | "subscriptions": "সাবস্ক্রিপশন"
10 | },
11 | "player": {
12 | "watch_on": "দেখুন {0}"
13 | },
14 | "actions": {
15 | "subscribe": "সদস্যতা নিন",
16 | "unsubscribe": "সদস্যতা পরিত্যাগ",
17 | "view_subscriptions": "সদস্যতার তালিকা",
18 | "sort_by": "ভিডিও গুলোর বিন্যাস:",
19 | "most_recent": "সবচেয়ে সাম্প্রতিক",
20 | "least_recent": "কম সাম্প্রতিক",
21 | "channel_name_asc": "চ্যানেলের নাম (A-Z)",
22 | "channel_name_desc": "চ্যানেলের নাম (Z-A)",
23 | "back": "ফেরত যান",
24 | "uses_api_from": "API টি যেখান থেকে ব্যবহার করা হয়েছে: ",
25 | "enable_sponsorblock": "স্পনসরব্লক সক্রিয় করুন",
26 | "skip_sponsors": "স্পনসর এড়িয়ে যান",
27 | "skip_intro": "সূচনা অংশ এড়িয়ে যান",
28 | "skip_outro": "কৃতিত্ব তালিকা (Credits) এড়িয়ে যান",
29 | "skip_preview": "পূর্বদৃশ্য/আগের পর্বের সংক্ষিপ্তসার এড়িয়ে যান",
30 | "skip_interaction": "",
31 | "skip_self_promo": "আত্মসম্প্রচার এড়িয়ে যান",
32 | "skip_non_music": "গানের মধ্যে গানহীন অংশ এড়িয়ে যান",
33 | "theme": "থিম",
34 | "auto": "সিস্টেম অনুযায়ী",
35 | "dark": "কালো",
36 | "light": "উজ্জ্বল",
37 | "autoplay_video": "ভিডিও নিজে থেকে চলবে কি না",
38 | "audio_only": "শুধু আওয়াজ শুনবেন কি না",
39 | "default_quality": "ভিডিওর স্বাভাবিক কোয়ালিটি",
40 | "buffering_goal": "বাফারিং গোল (সেকেন্ডে)",
41 | "country_selection": "দেশ নির্বাচন",
42 | "default_homepage": "ওয়েবসাইট খোলার পর কি দেখবেন গতানুগতিক",
43 | "show_comments": "মন্তব্যগুলো দেখুন",
44 | "minimize_description_default": "স্বাভাবিক ভাবে বিবরণ সংক্ষিপ্ত রাখুন",
45 | "store_watch_history": "দেখা ভিডিওর ইতিহাস সংরক্ষণ করুন",
46 | "language_selection": "ভাষা নির্বাচন",
47 | "instances_list": "সঙ্ঘটনের তালিকা",
48 | "enabled_codecs": "কোডেক্স স্বয়ংক্রিয় করুন",
49 | "minimize_recommendations": "সুপারিশগুলি ছোট করুন",
50 | "show_recommendations": "সুপারিশ দেখান",
51 | "disable_lbry": "স্ট্রিমিং এর জন্য LBRY নিষ্ক্রিয় করুন",
52 | "enable_lbry_proxy": "প্রক্সি এর জন্য LBRY স্বয়ংক্রিয় করুন",
53 | "yes": "হ্যাঁ",
54 | "no": "না",
55 | "export_to_json": "JSON ফাইলে সংরক্ষণ করুন",
56 | "import_from_json": "JSON/CSV ফাইল থেকে আনুন",
57 | "loop_this_video": "ভিডিওটি চক্রের মধ্যে রাখুন",
58 | "instance_selection": "সঙ্ঘটন নির্বাচন",
59 | "show_more": "আরো দেখুন",
60 | "show_description": "বিবরণ দেখুন",
61 | "auto_play_next_video": "পরবর্তী ভিডিও স্বয়ংক্রিয়ভাবে চালান",
62 | "donations": "দান করুন",
63 | "minimize_description": "বিবরণ ছোট করুন",
64 | "view_ssl_score": "SSL স্কোর দেখুন",
65 | "search": "অনুসন্ধান করুন",
66 | "filter": "ফিল্টার",
67 | "loading": "পরিচালিত হচ্ছে…",
68 | "clear_history": "ইতিহাস মুছুন",
69 | "hide_replies": "প্রতিক্রিয়াগুলো লুকান",
70 | "load_more_replies": "আরো প্রতিক্রিয়াগুলো দেখুন"
71 | },
72 | "video": {
73 | "watched": "দেখা হয়েছে",
74 | "sponsor_segments": "স্পনসর সেগমেন্ট",
75 | "videos": "ভিডিও",
76 | "views": "{views} ভিউজ",
77 | "ratings_disabled": "রেটিং নিষ্ক্রিয় আছে"
78 | },
79 | "login": {
80 | "username": "ইউজারনেম",
81 | "password": "পাসওয়ার্ড"
82 | },
83 | "search": {
84 | "did_you_mean": "আপনি কি বুঝাতে চেয়েছেন: "
85 | },
86 | "preferences": {
87 | "instance_locations": "সঙ্ঘটনের ঠিকানা",
88 | "ssl_score": "SSL স্কোর",
89 | "instance_name": "সঙ্ঘটনের নাম",
90 | "has_cdn": "CDN কি আছে?"
91 | },
92 | "comment": {
93 | "pinned_by": "পিন করেছেন {author}"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/locales/gl.json:
--------------------------------------------------------------------------------
1 | {
2 | "titles": {
3 | "register": "Crear conta",
4 | "feed": "Cronoloxía",
5 | "preferences": "Preferencias",
6 | "history": "Historial",
7 | "trending": "En voga",
8 | "account": "Conta",
9 | "player": "Reprodutor",
10 | "login": "Acceder",
11 | "instance": "Instancia",
12 | "bookmarks": "Marcadores",
13 | "subscriptions": "Subscricións",
14 | "playlists": "Listas",
15 | "livestreams": "En directo",
16 | "channels": "Canles",
17 | "channel_groups": "Grupos de canles"
18 | },
19 | "player": {
20 | "watch_on": "Ver en {0}",
21 | "failed": "Fallou con código do erro {0}, mira o rexistro para máis info"
22 | },
23 | "actions": {
24 | "subscribe": "Subscribirse",
25 | "sort_by": "Orde por:",
26 | "least_recent": "Máis antigo",
27 | "most_recent": "Máis recente",
28 | "channel_name_asc": "Nome da canle (A-Z)",
29 | "unsubscribe": "Retirar subscrición",
30 | "view_subscriptions": "Ver Subscricións",
31 | "back": "Volver",
32 | "uses_api_from": "Usa a API desde ",
33 | "enable_sponsorblock": "Activar Sponsoblock",
34 | "skip_button_only": "Mostrar botón omitir",
35 | "skip_automatically": "Automáticamente",
36 | "channel_name_desc": "Nome da canle (Z-A)",
37 | "skip_sponsors": "Omitir Sponsors",
38 | "show_markers": "Mostrar Marcadores no Reprodutor",
39 | "skip_segment": "Omitir Segmento",
40 | "dark": "Escuro",
41 | "min_segment_length": "Lonxitude mínima do segmento (en segundos)",
42 | "theme": "Decorado",
43 | "auto": "Auto",
44 | "light": "Claro",
45 | "autoplay_video": "Reprodución automática",
46 | "buffering_goal": "Tamaño do buffer (en segundos)",
47 | "minimize_comments_default": "Por defecto minimizar os comentarios",
48 | "import_from_json_csv": "Importar desde JSON/CSV",
49 | "skip_outro": "Omitir créditos finais",
50 | "skip_intro": "Omitir animación de entrada",
51 | "auto_play_next_video": "Reproducir autom. seguinte vídeo",
52 | "instance_selection": "Instancia",
53 | "clear_history": "Limpar historial",
54 | "loading": "A cargar...",
55 | "minimize_description": "Minimizar descrición",
56 | "skip_interaction": "Omitir Recordatorio para interactuar (Subscribirse)",
57 | "filter": "Filtro",
58 | "view_ssl_score": "Ver SSL Score",
59 | "minimize_description_default": "Por defecto minimizar a descrición",
60 | "language_selection": "Idioma",
61 | "enable_lbry_proxy": "Activar Proxy para LBRY",
62 | "donations": "Doar para o desenvolvemento",
63 | "loop_this_video": "Poñer vídeo en bucle",
64 | "hide_replies": "Agochar respostas",
65 | "country_selection": "País",
66 | "skip_self_promo": "Omitir autobombo",
67 | "default_quality": "Calidade por defecto",
68 | "show_more": "Mostrar máis",
69 | "show_recommendations": "Mostrar recomendacións",
70 | "minimize_recommendations": "Minimizar recomendacións",
71 | "audio_only": "Só audio",
72 | "disable_lbry": "Desactivar LBRY para Retransmisión",
73 | "show_comments": "Mostrar comentarios",
74 | "store_watch_history": "Gardar historial de visualizacións",
75 | "no": "Non",
76 | "yes": "Si",
77 | "load_more_replies": "Cargar máis respostas",
78 | "enabled_codecs": "Códecs activados (varios)",
79 | "auto_display_captions": "Mostar autom. Subtítulos",
80 | "minimize_comments": "Minimizar comentarios",
81 | "skip_non_music": "Omitir música: Sección sen música",
82 | "instances_list": "Lista de instancias",
83 | "import_from_json": "Importar desde JSON",
84 | "autoplay_next_countdown": "Conta atrás por defecto para o seguinte vídeo (en segundos)",
85 | "default_homepage": "Inicio por defecto",
86 | "remove_from_playlist": "Retirar da lista",
87 | "search": "Buscar (Ctrl+K)",
88 | "show_description": "Mostrar descrición",
89 | "skip_preview": "Omitir vista previa/resumo",
90 | "skip_highlight": "Omitir Destacado",
91 | "export_to_json": "Exportar a JSON",
92 | "add_to_playlist": "Engadir á lista"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/PlaylistVideos.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 | {{ playlist.uploader }}
10 |
11 | -
12 |
13 | {{ selectedIndex }} / {{ playlist.videos }}
14 |
15 |
16 |
17 |
24 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
110 |
--------------------------------------------------------------------------------
/src/components/ChaptersBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t("video.chapters") }} ({{ chapters.length }})
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 | {{ $t("video.chapters") }} ({{ chapters.length }})
32 |
33 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
93 |
94 |
118 |
--------------------------------------------------------------------------------
/src/components/ImportHistoryModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
35 |
36 |
37 |
109 |
--------------------------------------------------------------------------------
/src/components/RegisterPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
53 |
63 |
64 |
65 |
118 |
--------------------------------------------------------------------------------
/src/components/CommentItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
68 |
69 |
70 |
115 |
--------------------------------------------------------------------------------
/src/utils/DashUtils.js:
--------------------------------------------------------------------------------
1 | // Based of https://github.com/GilgusMaximus/yt-dash-manifest-generator/blob/master/src/DashGenerator.js
2 | import { XMLBuilder } from "fast-xml-parser";
3 |
4 | export function generate_dash_file_from_formats(VideoFormats, VideoLength) {
5 | const generatedJSON = generate_xmljs_json_from_data(VideoFormats, VideoLength);
6 | const builder = new XMLBuilder({
7 | ignoreAttributes: false,
8 | allowBooleanAttributes: true,
9 | suppressBooleanAttributes: false,
10 | attributeNamePrefix: "_",
11 | });
12 | return builder.build(generatedJSON);
13 | }
14 |
15 | function generate_xmljs_json_from_data(VideoFormatArray, VideoLength) {
16 | const convertJSON = {
17 | "?xml": {
18 | _version: "1.0",
19 | _encoding: "utf-8",
20 | MPD: {
21 | _xmlns: "urn:mpeg:dash:schema:mpd:2011",
22 | _profiles: "urn:mpeg:dash:profile:full:2011",
23 | _minBufferTime: "PT1.5S",
24 | _type: "static",
25 | _mediaPresentationDuration: `PT${VideoLength}S`,
26 | Period: {
27 | AdaptationSet: generate_adaptation_set(VideoFormatArray),
28 | },
29 | },
30 | },
31 | };
32 | return convertJSON;
33 | }
34 |
35 | function generate_adaptation_set(VideoFormatArray) {
36 | const adaptationSets = [];
37 |
38 | let mimeAudioObjs = [];
39 |
40 | VideoFormatArray.forEach(videoFormat => {
41 | // the dual formats should not be used
42 | if (
43 | (videoFormat.mimeType.includes("video") && !videoFormat.videoOnly) ||
44 | videoFormat.mimeType.includes("application")
45 | ) {
46 | return;
47 | }
48 |
49 | const audioTrackId = videoFormat.audioTrackId;
50 | const mimeType = videoFormat.mimeType;
51 |
52 | for (let i = 0; i < mimeAudioObjs.length; i++) {
53 | const mimeAudioObj = mimeAudioObjs[i];
54 |
55 | if (mimeAudioObj.audioTrackId == audioTrackId && mimeAudioObj.mimeType == mimeType) {
56 | mimeAudioObj.videoFormats.push(videoFormat);
57 | return;
58 | }
59 | }
60 |
61 | mimeAudioObjs.push({
62 | audioTrackId,
63 | mimeType,
64 | videoFormats: [videoFormat],
65 | });
66 | });
67 |
68 | mimeAudioObjs.forEach(mimeAudioObj => {
69 | const adapSet = {
70 | _id: mimeAudioObj.audioTrackId,
71 | _lang: mimeAudioObj.audioTrackId?.substr(0, 2),
72 | _mimeType: mimeAudioObj.mimeType,
73 | _startWithSAP: "1",
74 | _subsegmentAlignment: "true",
75 | Representation: [],
76 | };
77 |
78 | let isVideoFormat = false;
79 |
80 | if (mimeAudioObj.mimeType.includes("video")) {
81 | isVideoFormat = true;
82 | adapSet["_scanType"] = "progressive";
83 | }
84 |
85 | for (var i = 0; i < mimeAudioObj.videoFormats.length; i++) {
86 | const videoFormat = mimeAudioObj.videoFormats[i];
87 | if (isVideoFormat) {
88 | adapSet.Representation.push(generate_representation_video(videoFormat));
89 | } else {
90 | adapSet.Representation.push(generate_representation_audio(videoFormat));
91 | }
92 | }
93 |
94 | adaptationSets.push(adapSet);
95 | });
96 | return adaptationSets;
97 | }
98 |
99 | function generate_representation_audio(Format) {
100 | const representation = {
101 | _id: Format.itag,
102 | _codecs: Format.codec,
103 | _bandwidth: Format.bitrate,
104 | AudioChannelConfiguration: {
105 | _schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
106 | _value: "2",
107 | },
108 | BaseURL: Format.url,
109 | SegmentBase: {
110 | _indexRange: `${Format.indexStart}-${Format.indexEnd}`,
111 | Initialization: {
112 | _range: `${Format.initStart}-${Format.initEnd}`,
113 | },
114 | },
115 | };
116 | return representation;
117 | }
118 |
119 | function generate_representation_video(Format) {
120 | const representation = {
121 | _id: Format.itag,
122 | _codecs: Format.codec,
123 | _bandwidth: Format.bitrate,
124 | _width: Format.width,
125 | _height: Format.height,
126 | _maxPlayoutRate: "1",
127 | _frameRate: Format.fps,
128 | BaseURL: Format.url,
129 | SegmentBase: {
130 | _indexRange: `${Format.indexStart}-${Format.indexEnd}`,
131 | Initialization: {
132 | _range: `${Format.initStart}-${Format.initEnd}`,
133 | },
134 | },
135 | };
136 | return representation;
137 | }
138 |
--------------------------------------------------------------------------------
/src/components/ExportHistoryModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
33 |
34 |
35 |
143 |
--------------------------------------------------------------------------------
/src/locales/ml.json:
--------------------------------------------------------------------------------
1 | {
2 | "titles": {
3 | "trending": "പ്രചരിക്കുന്നത്",
4 | "register": "രജിസ്റ്റർ",
5 | "history": "ചരിത്രം",
6 | "preferences": "ക്രമീകരണം",
7 | "feed": "ഫീഡ്",
8 | "login": "പ്രവേശിക്കുക",
9 | "subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ",
10 | "playlists": "പ്ലേലിസ്റ്റുകൾ",
11 | "player": "കളിക്കാരൻ",
12 | "account": "അക്കോണട്"
13 | },
14 | "actions": {
15 | "view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
16 | "unsubscribe": "സബ്സ്ക്രൈബ് ചെയ്യേണ്ട",
17 | "subscribe": "സബ്സ്ക്രൈബ് ചെയ്യുക",
18 | "instances_list": "ഇൻസ്റ്റൻസുകളുടെ പട്ടിക",
19 | "minimize_description_default": "സ്ഥിരമായി വിവരണം ചെറുതാക്കുക",
20 | "skip_intro": "ഇടവേള/ആമുഖ ആനിമേഷൻ ഒഴിവാക്കുക",
21 | "skip_sponsors": "സ്പോൺസർമാരെ ഒഴിവാക്കുക",
22 | "default_quality": "സ്ഥിര വീഡിയോ നിലവാരം",
23 | "skip_outro": "എൻഡ്കാർഡുകൾ/ക്രെഡിറ്റുകൾ ഒഴിവാക്കുക",
24 | "skip_preview": "പ്രിവ്യൂ/റീക്യാപ്പ് ഒഴിവാക്കുക",
25 | "skip_interaction": "ഇടപെടൽ ഓർമ്മപ്പെടുത്തൽ ഒഴിവാക്കുക (സബ്സ്ക്രൈബ് ചെയ്യുക)",
26 | "skip_self_promo": "പണം അടയ്ക്കാത്ത/സ്വയം പ്രമോഷൻ ഒഴിവാക്കുക",
27 | "auto": "സ്വയം തിരഞ്ഞെടുക്കുക",
28 | "theme": "തീം",
29 | "dark": "കറുപ്പ് നിറം",
30 | "light": "വെള്ള നിറം",
31 | "default_homepage": "സ്ഥിര ഹോംപേജ്",
32 | "show_comments": "അഭിപ്രായങ്ങൾ കാണിക്കുക",
33 | "language_selection": "ഭാഷ തിരഞ്ഞെടുക്കുക",
34 | "store_watch_history": "വീഡിയോ ചരിത്രം സംരക്ഷിക്കുക",
35 | "country_selection": "രാജ്യം തിരഞ്ഞെടുക്കുക",
36 | "audio_only": "ശബ്ദം മാത്രം",
37 | "enable_sponsorblock": "Sponsorblock സജ്ജമാക്കുക",
38 | "uses_api_from": "ഉപയോഗിക്കുന്ന എപിഐ ",
39 | "back": "തിരികെ പോവുക",
40 | "channel_name_desc": "ചാനലിൻ്റെ പേര് (Z-A)",
41 | "channel_name_asc": "ചാനലിന്റെ പേര് (A-Z)",
42 | "most_recent": "ഏറ്റവും പുതിയത്",
43 | "sort_by": "ഇങ്ങനെ അടുക്കുക:",
44 | "enable_lbry_proxy": "LBRY- നായി പ്രോക്സി പ്രവർത്തനക്ഷമമാക്കുക",
45 | "disable_lbry": "LBRY സ്ട്രീമിംഗ് പ്രവർത്തനരഹിതമാക്കുക",
46 | "autoplay_video": "വീഡിയോ സ്വയം പ്ലേ ചെയ്യുക",
47 | "least_recent": "പഴയത്",
48 | "no": "ഇല്ല",
49 | "donations": "സംഭാവനകൾ",
50 | "show_description": "വിവരണം കാണിക്കുക",
51 | "auto_play_next_video": "അടുത്ത വിഡിയോ സ്വയം പ്ലേ ചെയ്യുക",
52 | "loop_this_video": "വിഡിയോ ലൂപ്പ് ചെയ്യുക",
53 | "minimize_description": "വിവരണം ചെറുതാക്കുക",
54 | "minimize_recommendations": "ശുപാർശകൾ ചുരുക്കുക",
55 | "show_recommendations": "ശുപാർശകൾ കാണിക്കുക",
56 | "yes": "അതെ",
57 | "show_more": "കൂടുതൽ കാണിക്കുക",
58 | "buffering_goal": "ബഫറിംഗ് ലക്ഷ്യം(സെക്കൻഡുകളിൽ)",
59 | "import_from_json": "JSONിൽ നിന്ന് ഇറക്കുമതി ചെയ്യൂ",
60 | "export_to_json": "JSON-ലേക്ക് എക്സ്പ്പോർട്ട് ചെയ്യുക",
61 | "instance_selection": "ഇൻസ്റ്റ്ൻസ് തിരഞ്ഞെടുക്കുക",
62 | "loading": "ലഭ്യമാക്കുന്നു...",
63 | "filter": "ഫിൽട്ടർ",
64 | "search": "തിരയുക",
65 | "view_ssl_score": "എസ്എസ്എൽ സ്കോർ കാണിക്കൂ",
66 | "skip_non_music": "പാട്ട് ഒഴിവാക്കുക: Non-Music Section",
67 | "clear_history": "ചരിത്രം മായ്ക്കുക",
68 | "hide_replies": "മറുപടികൾ മറയ്ക്കുക",
69 | "load_more_replies": "കൂടുതൽ മറുപടികൽ ലഭ്യമാക്കുക",
70 | "add_to_playlist": "പ്ലേലിസ്റ്റിലേക്ക് ചേർക്കൂ",
71 | "remove_from_playlist": "പ്ലേലിസ്റ്റിൽ നിന്ന് ഒഴിവാക്കൂ",
72 | "create_playlist": "പ്ലേലിസ്റ്റ് നിർമ്മിക്കൂ",
73 | "delete_playlist": "പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കൂ",
74 | "select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കൂ",
75 | "please_select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കുക",
76 | "delete_playlist_video_confirm": "ഈ പ്ലേലിസ്റ്റിൽ നിന്ന് ഈ വീഡിയോ ഒഴിവാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?",
77 | "delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?",
78 | "skip_automatically": "സ്വയമേവ",
79 | "show_watch_on_youtube": "YouTubeിൽ കാണാം എന്ന button കാണിക്കുകാ",
80 | "time_code": "സമയമായം (സെക്കന്റുകളിൽ)",
81 | "show_less": "കുറച്ച്മാതറം",
82 | "with_timecode": "സമയമായവുമായി പങ്കുവെക്കുക",
83 | "back_to_home": "തിരിച്ച് വീട്ടിലേക്ക്",
84 | "dismiss": "തിരിച്ച്",
85 | "create_group": "കൂട്ടം ഉണടാക്കുകാ",
86 | "okay": "ശരി",
87 | "invalidate_session": "എല ടിവയഡിൽ നിന്നും ലൊഗൗഠ അവുകാ",
88 | "enabled_codecs": "ഉപയോഗിക്കുന്ന കോടെക്കൃകൽ",
89 | "share": "പങ്കുവെക്കുക",
90 | "documentation": "സഹായക്കുറിപ്പുകൽ",
91 | "cancel": "റദ്ദാക്കുക",
92 | "generate_qrcode": "QR Code ഉണ്ടാക്കുക",
93 | "import_from_json_csv": "JSON/CSVിൽ നിന്ന് ഇറക്കുമതി ചെയ്യൂ"
94 | },
95 | "player": {
96 | "watch_on": "{0}ിൻ കാണൃകാ"
97 | },
98 | "video": {
99 | "watched": "കണ്ടതാണ്",
100 | "views": "{views} കാഴ്ചകൾ",
101 | "videos": "വിഡിയോകൾ",
102 | "sponsor_segments": "സ്പോൺസർമാരുടെ ഭാഗങ്ങൾ",
103 | "all": "എല്ലാം"
104 | },
105 | "comment": {
106 | "pinned_by": "പിൻ ചെയ്തിരിക്കുന്നത് {author}"
107 | },
108 | "preferences": {
109 | "ssl_score": "എസ് എസ് എൽ സ്കോർ",
110 | "has_cdn": "സിഡിഎൻ ഉപയോഗിക്കിന്നിണ്ടോ?",
111 | "instance_name": "ഇൻസ്റ്റ്ൻസിന്റെ പേര്",
112 | "instance_locations": "ഇൻസ്റ്റൻസ് ലൊക്കേഷനുകൾ",
113 | "version": "പതിപ്പ്"
114 | },
115 | "login": {
116 | "password": "രഹസ്യവാക്ക്",
117 | "username": "ഉപയോക്തൃനാമം"
118 | },
119 | "search": {
120 | "did_you_mean": "{0} ആണൊ ഉദേശിച്ചെ?"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/components/SearchResults.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
140 |
--------------------------------------------------------------------------------
/src/components/ShareModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 |
144 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior include but are not limited to:
18 |
19 | - The usage of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, threats, and personal or political attacks
21 | - Harassment of any form
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission from the individual
23 | - Derailling conversations unnecessarily in a way that is not constructive, such as repeatedly posting off-topic comments whilst not in an off-topic channel
24 | - Other conduct which could reasonably be considered inappropriate in a professional setting
25 | - Tagging maintainers or project members without being one yourself
26 |
27 | ## Enforcement Responsibilities
28 |
29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32 |
33 | ## Scope
34 |
35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36 |
37 | ## Enforcement
38 |
39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at the contact section of the project README, or alternatively any admin of an official medium community. All complaints will be reviewed and investigated promptly and fairly.
40 |
41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42 |
43 | ## Enforcement Guidelines
44 |
45 | Community leaders will follow these Community Impact Guidelines in determining
46 | the consequences for any action they deem in violation of this Code of Conduct:
47 |
48 | ### 1. Correction
49 |
50 | **Community Impact**: Use of inappropriate language or other behavior deemed
51 | unprofessional or unwelcome in the community.
52 |
53 | **Consequence**: A private or public, written warning from community leaders, providing
54 | clarity when necessary around the nature of the violation and an explanation of why the
55 | behavior was inappropriate. A public apology may be requested.
56 |
57 | ### 2. Warning
58 |
59 | **Community Impact**: A violation through a single incident or series of
60 | actions.
61 |
62 | **Consequence**: A written warning with consequences for continued behavior. No
63 | interaction with the people involved, including unsolicited interaction with
64 | those enforcing the Code of Conduct, for a specified period of time. This
65 | includes avoiding interactions in community spaces as well as external channels
66 | like social media. Violating these terms may lead to a temporary or permanent
67 | ban.
68 |
69 | ### 3. Kick / Temporary Ban
70 |
71 | **Community Impact**: A serious or repeated violation of community standards, including
72 | sustained inappropriate behavior.
73 |
74 | **Consequence**: A kick or temporary ban from any sort of interaction or public
75 | communication with the community for a specified period of time. No public or
76 | private interaction with the people involved, including unsolicited interaction
77 | with those enforcing the Code of Conduct, is allowed during this period.
78 | Violating these terms may lead to a permanent ban.
79 |
80 | ### 4. Permanent Ban
81 |
82 | **Community Impact**: Demonstrating a pattern of violation of community
83 | standards, including sustained inappropriate behavior, harassment of an
84 | individual, or aggression toward or disparagement of classes of individuals.
85 |
86 | **Consequence**: A permanent ban from any sort of public interaction within the
87 | community.
88 |
89 | ## Attribution
90 |
91 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
92 | version 2.1, available at
93 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
94 |
95 | Community Impact Guidelines were inspired by
96 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
97 |
98 | For answers to common questions about this code of conduct, see the FAQ at
99 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
100 | [https://www.contributor-covenant.org/translations][translations].
101 |
102 | [homepage]: https://www.contributor-covenant.org
103 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
104 | [Mozilla CoC]: https://github.com/mozilla/diversity
105 | [FAQ]: https://www.contributor-covenant.org/faq
106 | [translations]: https://www.contributor-covenant.org/translations
107 |
--------------------------------------------------------------------------------
/public/img/icons/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/FeedPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 |
36 | videos.sort(order)" />
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
168 |
--------------------------------------------------------------------------------
/src/components/HistoryPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | videos.sort(order)" />
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
149 |
--------------------------------------------------------------------------------
/src/components/ImportPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
Importing Subscriptions from YouTube
21 |
22 |
23 | Open
24 |
takeout.google.com/takeout/custom/youtube
25 |
26 | In "Select data to include", click on "All YouTube data included" and select only "subscriptions".
27 |
28 | Create the export and download the zip file.
29 |
30 | Extract subscriptions.csv from the zip file.
31 |
32 | Select and import the file above.
33 |
34 |
35 |
Importing Subscriptions from Invidious
36 |
37 |
38 | Open
39 |
invidiou.us/data_control
40 |
41 | Click on any of the export options.
42 |
43 | Select and import the file above.
44 |
45 |
46 |
Importing Subscriptions from NewPipe
47 |
48 |
49 | Go to the Feed tab.
50 |
51 | Click on the arrow on where it says "Subscriptions".
52 |
53 | Save the file somewhere.
54 |
55 | Select and import the file above.
56 |
57 |
58 |
59 |
60 |
177 |
--------------------------------------------------------------------------------
/src/locales/nb_NO.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": {
3 | "instances_list": "Liste over instanser",
4 | "minimize_description_default": "Minimer beskrivelse som standard",
5 | "country_selection": "Land",
6 | "buffering_goal": "Mellomlagringsmål (i sekunder)",
7 | "autoplay_video": "Spill video automatisk",
8 | "skip_non_music": "Hopp over musikk: del uten musikk",
9 | "auto": "Auto",
10 | "skip_self_promo": "Hopp over ubetalt/selvpromotering",
11 | "skip_interaction": "Hopp over interaksjonspåminnelse (abonnering)",
12 | "skip_preview": "Hopp over forhåndsvisning/reintroduksjon",
13 | "skip_outro": "Hopp over rulletekst/utro",
14 | "skip_intro": "Hopp over forvideo/introanimasjon",
15 | "enable_sponsorblock": "Skru på sponsorblokkering",
16 | "language_selection": "Språk",
17 | "store_watch_history": "Lagre visningshistorikk",
18 | "show_comments": "Vis kommentarer",
19 | "default_homepage": "Standard hjemmeside",
20 | "default_quality": "Standard kvalitet",
21 | "audio_only": "Kun lyd",
22 | "light": "Lys",
23 | "dark": "Mørk",
24 | "theme": "Tema",
25 | "skip_sponsors": "Hopp over sponsorer",
26 | "uses_api_from": "Bruker API-et fra ",
27 | "back": "Tilbake",
28 | "channel_name_desc": "Kanalnavn (Å-A)",
29 | "channel_name_asc": "Kanalnavn (A-Å)",
30 | "least_recent": "Eldst",
31 | "most_recent": "Nyest",
32 | "sort_by": "Sorter etter:",
33 | "view_subscriptions": "Vis abonnementer",
34 | "unsubscribe": "Opphev abonnement",
35 | "subscribe": "Abonner",
36 | "enable_lbry_proxy": "Skru på mellomtjener for LBRY",
37 | "disable_lbry": "Skru av LBRY-strømming",
38 | "enabled_codecs": "Aktiverte forskjellige kodek",
39 | "show_description": "Vis beskrivelse",
40 | "minimize_recommendations": "Minimer anbefalinger",
41 | "show_recommendations": "Vis anbefalinger",
42 | "donations": "Donasjoner",
43 | "auto_play_next_video": "Autospill neste video",
44 | "loop_this_video": "Gjenta denne videoen",
45 | "import_from_json": "Importer fra JSON/CSV",
46 | "export_to_json": "Eksporter til JSON",
47 | "no": "Nei",
48 | "yes": "Ja",
49 | "show_more": "Vis mer",
50 | "instance_selection": "Valg av instans",
51 | "search": "Søk",
52 | "filter": "Filtrer",
53 | "loading": "Laster inn …",
54 | "view_ssl_score": "Vis SSL-poengsum",
55 | "minimize_description": "Minimer beskrivelse",
56 | "clear_history": "Tøm historikk",
57 | "skip_filler_tangent": "Hopp over fyll",
58 | "hide_replies": "Skjul svar",
59 | "skip_highlight": "Hopp over hovedmoment",
60 | "load_more_replies": "Last inn flere svar",
61 | "create_playlist": "Opprett spilleliste",
62 | "delete_playlist_confirm": "Er du sikker på at du vil slette spillelisten?",
63 | "delete_playlist": "Slett spilleliste",
64 | "select_playlist": "Velg spilleliste",
65 | "please_select_playlist": "Vennligst velg en spilleliste",
66 | "delete_playlist_video_confirm": "Fjern video fra spilleliste?",
67 | "show_markers": "Vis markører i avspiller",
68 | "add_to_playlist": "Legg til i spilleliste",
69 | "remove_from_playlist": "Fjern fra spilleliste",
70 | "delete_account": "Slett konto",
71 | "logout": "Logg ut",
72 | "minimize_recommendations_default": "Minimer anbefalinger som forvalg",
73 | "download_as_txt": "Last ned som .txt",
74 | "invalidate_session": "Logg ut alle enheter",
75 | "different_auth_instance": "Bruk en annen instans for identitetsbekreftelse",
76 | "instance_auth_selection": "Valg av identitetbekreftelsesinstans",
77 | "clone_playlist": "Klon spillelisten",
78 | "clone_playlist_success": "Klonet",
79 | "reset_preferences": "Tilbakestill innstillinger",
80 | "backup_preferences": "Innstillinger for sikkerhetskopiering",
81 | "confirm_reset_preferences": "Tilbakestill alle innstillingene?",
82 | "restore_preferences": "Gjenopprett innstillinger",
83 | "show_chapters": "Kapitler",
84 | "share": "Del",
85 | "with_timecode": "Del med tidskode",
86 | "piped_link": "Piped-lenke",
87 | "follow_link": "Følg lenke",
88 | "copy_link": "Kopier lenke",
89 | "time_code": "Tidskode (sekunder)",
90 | "back_to_home": "Tilbake til startsiden",
91 | "store_search_history": "Husk søkehistorikk",
92 | "hide_watched": "Skjul sette videoer i strømmen",
93 | "documentation": "Dokumentasjon",
94 | "status_page": "Status",
95 | "source_code": "Kildekode",
96 | "instance_donations": "Instans-donasjoner"
97 | },
98 | "player": {
99 | "watch_on": "Vis på {0}"
100 | },
101 | "titles": {
102 | "feed": "Strøm",
103 | "history": "Historikk",
104 | "preferences": "Innstillinger",
105 | "register": "Registrering",
106 | "login": "Logg inn",
107 | "trending": "På vei opp",
108 | "subscriptions": "Abonnementer",
109 | "playlists": "Spillelister",
110 | "account": "Konto",
111 | "instance": "Instans",
112 | "player": "Avspiller"
113 | },
114 | "video": {
115 | "sponsor_segments": "Sponsor-segmenter",
116 | "watched": "Sett",
117 | "views": "{views} visninger",
118 | "videos": "Videoer",
119 | "ratings_disabled": "Vurdering deaktivert",
120 | "chapters": "Kapitler",
121 | "live": "{0} direkte",
122 | "shorts": "Korte"
123 | },
124 | "preferences": {
125 | "ssl_score": "SSL-poengsum",
126 | "has_cdn": "Har innholdsleveransenettverk?",
127 | "instance_locations": "Instansested",
128 | "instance_name": "Instansenavn",
129 | "registered_users": "Registrerte brukere",
130 | "up_to_date": "Oppdatert?",
131 | "version": "Versjon"
132 | },
133 | "comment": {
134 | "pinned_by": "Festet av {author}",
135 | "user_disabled": "Kommentarer er avskrudd i innstillingene.",
136 | "disabled": "Kommentarer er avskrudd av opplasteren.",
137 | "loading": "Laster inn kommentarer …"
138 | },
139 | "login": {
140 | "username": "Brukernavn",
141 | "password": "Passord"
142 | },
143 | "search": {
144 | "did_you_mean": "Mente du: {0}?",
145 | "music_playlists": "YT musikk: Spillelister",
146 | "all": "YouTube: Alle",
147 | "videos": "YouTube: Videoer",
148 | "music_songs": "YT musikk: Sanger",
149 | "channels": "YouTube: Kanaler",
150 | "playlists": "YouTube: Spillelister",
151 | "music_videos": "YT musikk: Videoer",
152 | "music_albums": "YT musikk: Album"
153 | },
154 | "subscriptions": {
155 | "subscribed_channels_count": "Abonnert på: {0}"
156 | },
157 | "information": {
158 | "preferences_note": "Merk: Innstillinger lagres lokalt i din nettleser. Sletting av nettleserdata sletter dem."
159 | },
160 | "info": {
161 | "preferences_note": "Merk: Innstillinger spares i nettleserens lokallager. Sletting av nettleserdata tilbakestiller dem.",
162 | "cannot_copy": "Kan ikke kopiere",
163 | "page_not_found": "Fant ikke siden",
164 | "copied": "Kopiert"
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/locales/is.json:
--------------------------------------------------------------------------------
1 | {
2 | "titles": {
3 | "login": "Innskrá",
4 | "register": "Nýr Notandi",
5 | "history": "Áhorfasaga",
6 | "subscriptions": "Áskriftir",
7 | "preferences": "Stillingar",
8 | "trending": "Vinsælt",
9 | "feed": "Straumur",
10 | "playlists": "Spilunarlistar",
11 | "player": "Spilari",
12 | "account": "Reikningur",
13 | "instance": "Tilvik",
14 | "livestreams": "Útsendingar í beinni",
15 | "channels": "Rásir",
16 | "bookmarks": "Bókamerki",
17 | "channel_groups": "Rásarhópar"
18 | },
19 | "actions": {
20 | "sort_by": "Raða eftir:",
21 | "back": "Til Baka",
22 | "dark": "Dökk",
23 | "light": "Ljós",
24 | "theme": "Þema",
25 | "enable_sponsorblock": "Virkja Sponsorblock",
26 | "subscribe": "Gerast Áskrifandi",
27 | "unsubscribe": "Segja Upp Áskrift",
28 | "auto": "Sjálfvirkt",
29 | "audio_only": "Aðeins Hljóð",
30 | "most_recent": "Nýlegast",
31 | "least_recent": "Síðast",
32 | "no": "Nei",
33 | "yes": "Já",
34 | "show_more": "Sýna Meira",
35 | "search": "Leita",
36 | "view_subscriptions": "Skoða Áskriftir",
37 | "channel_name_asc": "Rásarnafn (A-Ö)",
38 | "channel_name_desc": "Rásarnafn (Ö-A)",
39 | "uses_api_from": "Nota forritaskil frá ",
40 | "skip_sponsors": "Sleppa Stuðningum",
41 | "skip_intro": "Sleppa Kynningu",
42 | "skip_outro": "Sleppa Enda",
43 | "skip_preview": "Sleppa Forsýningu",
44 | "skip_interaction": "Sleppa Beiðnum",
45 | "skip_self_promo": "Sleppa Sjálfauglýsingu",
46 | "skip_non_music": "Sleppa Hluta Án Tónlist",
47 | "autoplay_video": "Sjálfvirkt Spila Myndbönd",
48 | "country_selection": "Landsval",
49 | "default_homepage": "Sjálfgefin Heimasíða",
50 | "show_comments": "Sýna Ummæli",
51 | "store_watch_history": "Geyma Áhorfasögu",
52 | "language_selection": "Tungumálaval",
53 | "minimize_description_default": "Lágmarka Lýsingu Sjálfgefið",
54 | "instances_list": "Tilvikalisti",
55 | "donations": "Framlög til þróunar",
56 | "minimize_description": "Minnka Lýsingu",
57 | "show_description": "Sýna Lýsingu",
58 | "minimize_recommendations": "Minnka Tillögur",
59 | "show_recommendations": "Sýna Tillögur",
60 | "disable_lbry": "Óvirkja LBRY Fyrir Straumspilun",
61 | "enable_lbry_proxy": "Virkja Staðgengilsþjón fyrir LBRY",
62 | "view_ssl_score": "Skoða SSL einkunn",
63 | "enabled_codecs": "Virkjir Afkóðarar (Marghæft)",
64 | "instance_selection": "Tilviksval",
65 | "import_from_json": "Flytja inn frá JSON/CSV",
66 | "loop_this_video": "Endurtaka þetta Myndband",
67 | "auto_play_next_video": "Spila Næsta Myndband Sjálfvirkt",
68 | "filter": "Sía",
69 | "loading": "Hleður...",
70 | "clear_history": "Hreinsa Feril",
71 | "hide_replies": "Fela Svör",
72 | "load_more_replies": "Hlaða Fleiri Svörum",
73 | "default_quality": "Sjálfgefin Gæði",
74 | "buffering_goal": "Biðminnismarkmið (í sekúndum)",
75 | "export_to_json": "Flytja út í JSON",
76 | "skip_highlight": "Sleppa Hápunkti",
77 | "create_playlist": "Skapa spilunarlista",
78 | "delete_playlist": "Eyða spilunarlista",
79 | "time_code": "Tímakóði (sekúndur)",
80 | "restore_preferences": "Flytja inn stillingar",
81 | "download_as_txt": "Sækja textaskrá",
82 | "different_auth_instance": "Nota annað tilvik til auðkenningar",
83 | "instance_auth_selection": "Val tilvika fyrir auðkenningu",
84 | "add_to_playlist": "Bæta við á spilunarlista",
85 | "reset_preferences": "Endurstilla stillingar",
86 | "remove_from_playlist": "Fjarlægja af spilunarlista",
87 | "select_playlist": "Velja spilunarlista",
88 | "invalidate_session": "Útskrá öll tæki",
89 | "backup_preferences": "Flytja út stillingar",
90 | "documentation": "Hjálparskjöl",
91 | "skip_filler_tangent": "Sleppa því óviðkomandi",
92 | "show_markers": "Sýna merki á spilara",
93 | "delete_account": "Eyða reikningi",
94 | "follow_link": "Fylgja hlekki",
95 | "copy_link": "Afrita hlekk",
96 | "instance_donations": "Framlög til netþjóns",
97 | "status_page": "Staða",
98 | "source_code": "Frumkóði",
99 | "share": "Deila",
100 | "with_timecode": "Deilа með tímakóða",
101 | "piped_link": "Hlekkur Piped",
102 | "please_select_playlist": "Vinsamlegast veldu spilunarlista",
103 | "clone_playlist": "Afrita spilunarlista",
104 | "clone_playlist_success": "Tókst að afrita spilunarlista!",
105 | "confirm_reset_preferences": "Ertu viss um að þú viljir endurstilla stillingarnar?",
106 | "back_to_home": "Aftur heim",
107 | "delete_playlist_video_confirm": "Fjarlægja myndband af spilunarlista?",
108 | "logout": "Útskrá þetta tæki",
109 | "delete_playlist_confirm": "Eyða þessum spilunarlista?",
110 | "minimize_recommendations_default": "Lágmarka ráðleggingar sjálfkrafa",
111 | "store_search_history": "Geyma leitarferil",
112 | "hide_watched": "Fela myndbönd sem þú hefur horft á",
113 | "show_chapters": "Kaflar",
114 | "reply_count": "{count} svör",
115 | "minimize_comments_default": "Fela ummæli sjálfgefið",
116 | "minimize_comments": "Fela ummæli",
117 | "show_watch_on_youtube": "Sýna hnapp til að horfa á YouTube",
118 | "minimize_chapters_default": "Lágmarka kafla sjálfgefið"
119 | },
120 | "player": {
121 | "watch_on": "Horfa á {0}"
122 | },
123 | "search": {
124 | "did_you_mean": "Áttirðu við: {0}?",
125 | "music_songs": "YT Tónlist: Lög",
126 | "playlists": "YouTube: Spilunarlistar",
127 | "music_videos": "YT Tónlist: Myndbönd",
128 | "music_albums": "YT Tónlist: Plötur",
129 | "music_playlists": "YT Tónlist: Lagalistar",
130 | "all": "YouTube: Allt",
131 | "videos": "YouTube: Myndbönd",
132 | "channels": "YouTube: Rásir"
133 | },
134 | "video": {
135 | "ratings_disabled": "Einkunnir Óvirkar",
136 | "videos": "Myndbönd",
137 | "views": "{views} áhorf",
138 | "watched": "Áhorft",
139 | "sponsor_segments": "Styrkjarahlutar",
140 | "chapters": "Kaflar",
141 | "live": "{0} Í beinni",
142 | "shorts": "Stutt"
143 | },
144 | "comment": {
145 | "pinned_by": "Fest af {author}",
146 | "disabled": "Höfundur lokaði fyrir ummælum.",
147 | "user_disabled": "Slökkt er á ummælum í stillingunum.",
148 | "loading": "Hleður ummæli…"
149 | },
150 | "preferences": {
151 | "instance_name": "Tilviksheiti",
152 | "instance_locations": "Tilviksstaðsetning",
153 | "has_cdn": "Hefur efnisflutningarnet (CDN)?",
154 | "ssl_score": "SSL einkunn",
155 | "version": "Útgáfa",
156 | "registered_users": "Skráðir notendur",
157 | "up_to_date": "Nýjasta útgáfa?"
158 | },
159 | "login": {
160 | "password": "Aðgangsorð",
161 | "username": "Notandanafn"
162 | },
163 | "info": {
164 | "page_not_found": "Síða fannst ekki",
165 | "cannot_copy": "Get ekki afritað!",
166 | "preferences_note": "Athugaðu: stillingar eru geymdar í staðbundinni geymslu vafrans þíns. Ef vafragögnum þínum eru eytt verða þau endurstillt.",
167 | "copied": "Afritað!"
168 | },
169 | "subscriptions": {
170 | "subscribed_channels_count": "Áskrifandi hjá: {0}"
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/utils/CountryMaps/zh_Hant.json:
--------------------------------------------------------------------------------
1 | [
2 | { "code": "AF", "name": "阿富汗" },
3 | { "code": "AL", "name": "阿爾巴尼亞" },
4 | { "code": "DZ", "name": "阿爾及利亞" },
5 | { "code": "AD", "name": "安道爾" },
6 | { "code": "AO", "name": "安哥拉" },
7 | { "code": "AG", "name": "安地卡及巴布達" },
8 | { "code": "AR", "name": "阿根廷" },
9 | { "code": "AM", "name": "亞美尼亞" },
10 | { "code": "AU", "name": "澳大利亞" },
11 | { "code": "AT", "name": "奧地利" },
12 | { "code": "AZ", "name": "亞塞拜然" },
13 | { "code": "BS", "name": "巴哈馬" },
14 | { "code": "BH", "name": "巴林" },
15 | { "code": "BD", "name": "孟加拉" },
16 | { "code": "BB", "name": "巴貝多" },
17 | { "code": "BY", "name": "白俄羅斯" },
18 | { "code": "BE", "name": "比利時" },
19 | { "code": "BZ", "name": "貝里斯" },
20 | { "code": "BJ", "name": "貝南" },
21 | { "code": "BT", "name": "不丹" },
22 | { "code": "BO", "name": "玻利維亞" },
23 | { "code": "BA", "name": "波士尼亞與赫塞哥維納" },
24 | { "code": "BW", "name": "波札那" },
25 | { "code": "BR", "name": "巴西" },
26 | { "code": "BN", "name": "汶萊" },
27 | { "code": "BG", "name": "保加利亞" },
28 | { "code": "BF", "name": "布吉納法索" },
29 | { "code": "BI", "name": "蒲隆地" },
30 | { "code": "CV", "name": "維德角" },
31 | { "code": "KH", "name": "柬埔寨" },
32 | { "code": "CM", "name": "喀麥隆" },
33 | { "code": "CA", "name": "加拿大" },
34 | { "code": "CF", "name": "中非" },
35 | { "code": "TD", "name": "查德" },
36 | { "code": "CL", "name": "智利" },
37 | { "code": "CN", "name": "中國" },
38 | { "code": "CO", "name": "哥倫比亞" },
39 | { "code": "KM", "name": "葛摩" },
40 | { "code": "CG", "name": "剛果共和國" },
41 | { "code": "CD", "name": "剛果民主共和國" },
42 | { "code": "CR", "name": "哥斯大黎加" },
43 | { "code": "CI", "name": "象牙海岸" },
44 | { "code": "HR", "name": "克羅埃西亞" },
45 | { "code": "CU", "name": "古巴" },
46 | { "code": "CY", "name": "賽普勒斯" },
47 | { "code": "CZ", "name": "捷克" },
48 | { "code": "DK", "name": "丹麥" },
49 | { "code": "DJ", "name": "吉布地" },
50 | { "code": "DM", "name": "多米尼克" },
51 | { "code": "DO", "name": "多明尼加" },
52 | { "code": "EC", "name": "厄瓜多" },
53 | { "code": "EG", "name": "埃及" },
54 | { "code": "SV", "name": "薩爾瓦多" },
55 | { "code": "GQ", "name": "赤道幾內亞" },
56 | { "code": "ER", "name": "厄利垂亞" },
57 | { "code": "EE", "name": "愛沙尼亞" },
58 | { "code": "SZ", "name": "史瓦帝尼" },
59 | { "code": "ET", "name": "衣索比亞" },
60 | { "code": "FJ", "name": "斐濟" },
61 | { "code": "FI", "name": "芬蘭" },
62 | { "code": "FR", "name": "法國" },
63 | { "code": "GA", "name": "加彭" },
64 | { "code": "GM", "name": "甘比亞" },
65 | { "code": "GE", "name": "喬治亞" },
66 | { "code": "DE", "name": "德國" },
67 | { "code": "GH", "name": "加納" },
68 | { "code": "GR", "name": "希臘" },
69 | { "code": "GD", "name": "格瑞那達" },
70 | { "code": "GT", "name": "瓜地馬拉" },
71 | { "code": "GN", "name": "幾內亞" },
72 | { "code": "GW", "name": "幾內亞比索" },
73 | { "code": "GY", "name": "蓋亞那" },
74 | { "code": "HT", "name": "海地" },
75 | { "code": "HN", "name": "宏都拉斯" },
76 | { "code": "HK", "name": "香港" },
77 | { "code": "HU", "name": "匈牙利" },
78 | { "code": "IS", "name": "冰島" },
79 | { "code": "IN", "name": "印度" },
80 | { "code": "ID", "name": "印尼" },
81 | { "code": "IR", "name": "伊朗" },
82 | { "code": "IQ", "name": "伊拉克" },
83 | { "code": "IE", "name": "愛爾蘭" },
84 | { "code": "IL", "name": "以色列" },
85 | { "code": "IT", "name": "義大利" },
86 | { "code": "JM", "name": "牙買加" },
87 | { "code": "JP", "name": "日本" },
88 | { "code": "JO", "name": "約旦" },
89 | { "code": "KZ", "name": "哈薩克" },
90 | { "code": "KE", "name": "肯亞" },
91 | { "code": "KI", "name": "吉里巴斯" },
92 | { "code": "KP", "name": "北韓" },
93 | { "code": "KR", "name": "南韓" },
94 | { "code": "KW", "name": "科威特" },
95 | { "code": "KG", "name": "吉爾吉斯" },
96 | { "code": "LA", "name": "寮國" },
97 | { "code": "LV", "name": "拉脫維亞" },
98 | { "code": "LB", "name": "黎巴嫩" },
99 | { "code": "LS", "name": "賴索托" },
100 | { "code": "LR", "name": "賴比瑞亞" },
101 | { "code": "LY", "name": "利比亞" },
102 | { "code": "LI", "name": "列支敦斯登" },
103 | { "code": "LT", "name": "立陶宛" },
104 | { "code": "LU", "name": "盧森堡" },
105 | { "code": "MG", "name": "馬達加斯加" },
106 | { "code": "MW", "name": "馬拉威" },
107 | { "code": "MY", "name": "馬來西亞" },
108 | { "code": "MV", "name": "馬爾地夫" },
109 | { "code": "ML", "name": "馬利" },
110 | { "code": "MT", "name": "馬爾他" },
111 | { "code": "MH", "name": "馬紹爾群島" },
112 | { "code": "MR", "name": "茅利塔尼亞" },
113 | { "code": "MU", "name": "模里西斯" },
114 | { "code": "MX", "name": "墨西哥" },
115 | { "code": "FM", "name": "密克羅尼西亞聯邦" },
116 | { "code": "MD", "name": "摩爾多瓦" },
117 | { "code": "MC", "name": "摩納哥" },
118 | { "code": "MN", "name": "蒙古" },
119 | { "code": "ME", "name": "蒙特內哥羅" },
120 | { "code": "MA", "name": "摩洛哥" },
121 | { "code": "MZ", "name": "莫三比克" },
122 | { "code": "MM", "name": "緬甸" },
123 | { "code": "NA", "name": "納米比亞" },
124 | { "code": "NR", "name": "諾魯" },
125 | { "code": "NP", "name": "尼泊爾" },
126 | { "code": "NL", "name": "荷蘭" },
127 | { "code": "NZ", "name": "紐西蘭" },
128 | { "code": "NI", "name": "尼加拉瓜" },
129 | { "code": "NE", "name": "尼日" },
130 | { "code": "NG", "name": "奈及利亞" },
131 | { "code": "MK", "name": "北馬其頓" },
132 | { "code": "NO", "name": "挪威" },
133 | { "code": "OM", "name": "阿曼" },
134 | { "code": "PK", "name": "巴基斯坦" },
135 | { "code": "PW", "name": "帛琉" },
136 | { "code": "PA", "name": "巴拿馬" },
137 | { "code": "PG", "name": "巴布亞紐幾內亞" },
138 | { "code": "PY", "name": "巴拉圭" },
139 | { "code": "PE", "name": "秘魯" },
140 | { "code": "PH", "name": "菲律賓" },
141 | { "code": "PL", "name": "波蘭" },
142 | { "code": "PT", "name": "葡萄牙" },
143 | { "code": "QA", "name": "卡達" },
144 | { "code": "RO", "name": "羅馬尼亞" },
145 | { "code": "RU", "name": "俄羅斯" },
146 | { "code": "RW", "name": "盧安達" },
147 | { "code": "KN", "name": "聖克里斯多福及尼維斯" },
148 | { "code": "LC", "name": "聖露西亞" },
149 | { "code": "VC", "name": "聖文森及格瑞那丁" },
150 | { "code": "WS", "name": "薩摩亞" },
151 | { "code": "SM", "name": "聖馬利諾" },
152 | { "code": "ST", "name": "聖多美普林西比" },
153 | { "code": "SA", "name": "沙烏地阿拉伯" },
154 | { "code": "SN", "name": "塞內加爾" },
155 | { "code": "RS", "name": "塞爾維亞" },
156 | { "code": "SC", "name": "塞席爾" },
157 | { "code": "SL", "name": "獅子山" },
158 | { "code": "SG", "name": "新加坡" },
159 | { "code": "SK", "name": "斯洛伐克" },
160 | { "code": "SI", "name": "斯洛維尼亞" },
161 | { "code": "SB", "name": "索羅門群島" },
162 | { "code": "SO", "name": "索馬利亞" },
163 | { "code": "ZA", "name": "南非" },
164 | { "code": "SS", "name": "南蘇丹" },
165 | { "code": "ES", "name": "西班牙" },
166 | { "code": "LK", "name": "斯里蘭卡" },
167 | { "code": "SD", "name": "蘇丹" },
168 | { "code": "SR", "name": "蘇利南" },
169 | { "code": "SE", "name": "瑞典" },
170 | { "code": "CH", "name": "瑞士" },
171 | { "code": "SY", "name": "敘利亞" },
172 | { "code": "TJ", "name": "塔吉克" },
173 | { "code": "TZ", "name": "坦尚尼亞" },
174 | { "code": "TH", "name": "泰國" },
175 | { "code": "TL", "name": "東帝汶" },
176 | { "code": "TG", "name": "多哥" },
177 | { "code": "TO", "name": "東加" },
178 | { "code": "TT", "name": "千里達及托巴哥" },
179 | { "code": "TN", "name": "突尼西亞" },
180 | { "code": "TR", "name": "土耳其" },
181 | { "code": "TM", "name": "土庫曼" },
182 | { "code": "TV", "name": "吐瓦魯" },
183 | { "code": "TW", "name": "台灣" },
184 | { "code": "UG", "name": "烏干達" },
185 | { "code": "UA", "name": "烏克蘭" },
186 | { "code": "AE", "name": "阿聯" },
187 | { "code": "GB", "name": "英國" },
188 | { "code": "US", "name": "美國" },
189 | { "code": "UY", "name": "烏拉圭" },
190 | { "code": "UZ", "name": "烏茲別克" },
191 | { "code": "VU", "name": "萬那杜" },
192 | { "code": "VE", "name": "委內瑞拉" },
193 | { "code": "VN", "name": "越南" },
194 | { "code": "YE", "name": "葉門" },
195 | { "code": "ZM", "name": "尚比亞" },
196 | { "code": "ZW", "name": "辛巴威" }
197 | ]
198 |
--------------------------------------------------------------------------------
/src/locales/lt.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": {
3 | "unsubscribe": "Atšaukti prenumeratą",
4 | "subscribe": "Prenumeruoti",
5 | "instances_list": "Perdavimo šaltinių sąrašas",
6 | "language_selection": "Kalbos pasirinkimas",
7 | "store_watch_history": "Saugoti žiūrėjimo istoriją",
8 | "minimize_description_default": "Suskleisti aprašymą automatiškai",
9 | "show_comments": "Rodyti komentarus",
10 | "default_homepage": "Numatytasis pagrindinis puslapis",
11 | "country_selection": "Šalies pasirinkimas",
12 | "buffering_goal": "Užkrovimo tikslas (sekundėmis)",
13 | "default_quality": "Numatytoji kokybė",
14 | "audio_only": "Tik garsas",
15 | "autoplay_video": "Automatiškai leisti vaizdo įrašus",
16 | "light": "Šviesi",
17 | "auto": "Automatinė",
18 | "dark": "Tamsi",
19 | "theme": "Tema",
20 | "skip_non_music": "Praleisti muziką: ne muzikos įkėlimų pasirinkimas",
21 | "skip_self_promo": "Praleisti nemokamą/savireklamą",
22 | "skip_interaction": "Praleisti sąveikos priminimą (prenumeruoti)",
23 | "skip_preview": "Praleisti peržiūrą/apibendrinimą",
24 | "skip_outro": "Praleisti pabaigos užsklandas/padėkas",
25 | "skip_intro": "Praleisti pertrauką/įžanginę animaciją",
26 | "skip_sponsors": "Praleisti rėmėjus",
27 | "enable_sponsorblock": "Įjungti SponsorBlock",
28 | "uses_api_from": "Naudoja API iš ",
29 | "back": "Atgal",
30 | "channel_name_desc": "Kanalo pavadinimas (Z-A)",
31 | "channel_name_asc": "Kanalo pavadinimas (A-Z)",
32 | "least_recent": "Seniausi",
33 | "most_recent": "Naujausi",
34 | "sort_by": "Rikiuoti pagal:",
35 | "view_subscriptions": "Peržiūrėti prenumeratas",
36 | "enabled_codecs": "Įgalinti kodekai (gali būti keli)",
37 | "enable_lbry_proxy": "Įjungti įgaliotąjį serverį skirtą LBRY",
38 | "disable_lbry": "Išjungti LBRY srautiniam siuntimui",
39 | "instance_selection": "Perdavimo šaltinio pasirinkimas",
40 | "auto_play_next_video": "Automatiškai paleisti sekantį vaizdo įrašą",
41 | "donations": "Parama plėtrai",
42 | "loop_this_video": "Sukti ratu šį vaizdo įrašą",
43 | "show_description": "Rodyti aprašymą",
44 | "minimize_description": "Suskleisti aprašymą",
45 | "minimize_recommendations": "Suskleisti rekomendacijas",
46 | "show_recommendations": "Rodyti rekomendacijas",
47 | "import_from_json": "Importuoti iš JSON/CSV",
48 | "export_to_json": "Eksportuoti į JSON",
49 | "no": "Ne",
50 | "yes": "Taip",
51 | "show_more": "Rodyti daugiau",
52 | "loading": "Įkeliama…",
53 | "filter": "Filtruoti",
54 | "search": "Ieškoti",
55 | "view_ssl_score": "Peržiūrėti SSL balą",
56 | "clear_history": "Išvalyti istoriją",
57 | "hide_replies": "Slėpti atsakymus",
58 | "load_more_replies": "Įkelti daugiau atsakymų",
59 | "select_playlist": "Pasirinkti grojaraštį",
60 | "add_to_playlist": "Pridėti į grojaraštį",
61 | "delete_playlist_video_confirm": "Pašalinti vaizdo įrašą iš grojaraščio?",
62 | "invalidate_session": "Atsijungti visuose įrenginiuose",
63 | "clone_playlist": "Klonuoti grojaraštį",
64 | "clone_playlist_success": "Sėkmingai klonuota!",
65 | "show_markers": "Rodyti žymeklius grotuve",
66 | "create_playlist": "Sukurti grojaraštį",
67 | "delete_playlist": "Ištrinti grojaraštį",
68 | "delete_playlist_confirm": "Ištrinti šį grojaraštį?",
69 | "please_select_playlist": "Pasirinkite grojaraštį",
70 | "download_as_txt": "Atsisiųsti kaip .txt",
71 | "delete_account": "Ištrinti paskyrą",
72 | "logout": "Atsijungti šiame įrenginyje",
73 | "remove_from_playlist": "Pašalinti iš grojaraščio",
74 | "confirm_reset_preferences": "Ar tikrai norite iš naujo nustatyti nuostatas?",
75 | "reset_preferences": "Iš naujo nustatyti nuostatas",
76 | "backup_preferences": "Atsarginė nuostatų kopija",
77 | "source_code": "Pirminis kodas",
78 | "documentation": "Dokumentacija",
79 | "with_timecode": "Dalintis su laiko kodu",
80 | "reply_count": "{count} atsakymai",
81 | "show_chapters": "Skirsniai",
82 | "piped_link": "Piped nuoroda",
83 | "follow_link": "Sekti nuorodą",
84 | "store_search_history": "Išsaugoti paieškos istoriją",
85 | "hide_watched": "Slėpti žiūrėtus vaizdo įrašus sklaidos kanale",
86 | "restore_preferences": "Atkurti nuostatas",
87 | "status_page": "Būsena",
88 | "copy_link": "Kopijuoti nuorodą",
89 | "share": "Dalintis",
90 | "minimize_recommendations_default": "Sumažinti rekomendacijas automatiškai",
91 | "instance_donations": "Perdavimo šaltinio parama",
92 | "instance_auth_selection": "Autentifikavimo perdavimo šaltinio pasirinkimas",
93 | "skip_filler_tangent": "Praleisti užpildymo dalį",
94 | "different_auth_instance": "Autentiškumo nustatymui naudoti kitą perdavimo šaltinį",
95 | "back_to_home": "Grįžti į pagrindinį",
96 | "skip_highlight": "Praleisti išskirtų dalių pakartojimus",
97 | "time_code": "Laiko kodas (sekundėmis)",
98 | "minimize_comments_default": "Suskleisti komentarus automatiškai",
99 | "minimize_comments": "Suskleisti komentarus",
100 | "show_watch_on_youtube": "Rodyti mygtuką „Žiūrėti YouTube“",
101 | "minimize_chapters_default": "Suskleisti skirsnius automatiškai",
102 | "no_valid_playlists": "Faile nėra galiojančių grojaraščių!"
103 | },
104 | "player": {
105 | "watch_on": "Žiūrėti per {0}"
106 | },
107 | "titles": {
108 | "preferences": "Nuostatos",
109 | "feed": "Sklaidos kanalas",
110 | "register": "Registruotis",
111 | "login": "Prisijungti",
112 | "trending": "Tendencijos",
113 | "history": "Istorija",
114 | "subscriptions": "Prenumeratos",
115 | "playlists": "Grojaraščiai",
116 | "account": "Paskyra",
117 | "player": "Grotuvas",
118 | "instance": "Perdavimo šaltinis",
119 | "livestreams": "Tiesioginės transliacijos",
120 | "channels": "Kanalai"
121 | },
122 | "preferences": {
123 | "instance_locations": "Perdavimo šaltinio vietovė",
124 | "instance_name": "Perdavimo šaltinio pavadinimas",
125 | "ssl_score": "SSL balas",
126 | "has_cdn": "Turi CDN?",
127 | "version": "Versija",
128 | "registered_users": "Registruoti naudotojai",
129 | "up_to_date": "Atnaujinta?"
130 | },
131 | "comment": {
132 | "pinned_by": "Prisegė {author}",
133 | "loading": "Įkeliami komentarai...",
134 | "disabled": "Įkėlėjas išjungė komentarus.",
135 | "user_disabled": "Komentarai yra išjungti nustatymuose."
136 | },
137 | "video": {
138 | "views": "{views} peržiūros",
139 | "videos": "Vaizdo įrašai",
140 | "sponsor_segments": "Rėmėjų segmentai",
141 | "watched": "Žiūrėta",
142 | "ratings_disabled": "Įvertinimai išjungti",
143 | "chapters": "Skirsniai",
144 | "live": "{0} tiesiogiai",
145 | "shorts": "Trumpi filmukai"
146 | },
147 | "login": {
148 | "password": "Slaptažodis",
149 | "username": "Vartotojo vardas"
150 | },
151 | "search": {
152 | "did_you_mean": "Ar turėjote omenyje: {0}?",
153 | "playlists": "YouTube: grojaraščiai",
154 | "music_playlists": "YT Music: grojaraščiai",
155 | "all": "YouTube: visi",
156 | "channels": "YouTube: kanalai",
157 | "videos": "YouTube: vaizdo įrašai",
158 | "music_videos": "YT Music: vaizdo įrašai",
159 | "music_songs": "YT Music: dainos",
160 | "music_albums": "YT Music: albumai"
161 | },
162 | "info": {
163 | "copied": "Nukopijuota!",
164 | "cannot_copy": "Negalima kopijuoti!",
165 | "page_not_found": "Puslapis nerastas",
166 | "preferences_note": "Pastaba: nuostatos išsaugomos vietinėje naršyklės atmintyje. Ištrynus naršyklės duomenis, jos bus nustatytos iš naujo.",
167 | "local_storage": "Šiam veiksmui reikia localStorage, ar slapukai įjungti?"
168 | },
169 | "subscriptions": {
170 | "subscribed_channels_count": "Prenumeruojama: {0}"
171 | }
172 | }
173 |
--------------------------------------------------------------------------------