├── .dockerignore ├── .env ├── .eslintrc.cjs ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ ├── deploy-azure.yml │ ├── docker-build.yml │ ├── ipfs-build.yml │ ├── reviewdog.yml │ └── weblate-merge.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile.ci ├── LICENSE ├── README.md ├── babel.config.js ├── docker ├── entrypoint.sh └── nginx.conf ├── index.html ├── localizefonts.sh ├── package.json ├── pnpm-lock.yaml ├── public ├── _redirects ├── favicon.ico ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── android-chrome-maskable-192x192.png │ │ ├── android-chrome-maskable-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── logo.svg │ │ ├── msapplication-icon-144x144.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg ├── opensearch.xml └── robots.txt ├── renovate.json ├── src ├── App.vue ├── components │ ├── AddToGroupModal.vue │ ├── ChannelItem.vue │ ├── ChannelPage.vue │ ├── ChaptersBar.vue │ ├── ClipsPage.vue │ ├── CollapsableText.vue │ ├── CommentItem.vue │ ├── ConfirmModal.vue │ ├── ContentItem.vue │ ├── CreateGroupModal.vue │ ├── CreatePlaylistModal.vue │ ├── CustomInstanceModal.vue │ ├── ErrorHandler.vue │ ├── ExportHistoryModal.vue │ ├── FeedPage.vue │ ├── FooterComponent.vue │ ├── HistoryPage.vue │ ├── ImportHistoryModal.vue │ ├── ImportPage.vue │ ├── LoadingIndicatorPage.vue │ ├── LoginPage.vue │ ├── ModalComponent.vue │ ├── NavBar.vue │ ├── PageNotFound.vue │ ├── PlaylistAddModal.vue │ ├── PlaylistItem.vue │ ├── PlaylistPage.vue │ ├── PlaylistVideos.vue │ ├── PlaylistsPage.vue │ ├── PreferencesPage.vue │ ├── QrCode.vue │ ├── RegisterPage.vue │ ├── SearchResults.vue │ ├── SearchSuggestions.vue │ ├── ShareModal.vue │ ├── SortingSelector.vue │ ├── SubscriptionsPage.vue │ ├── ToastComponent.vue │ ├── TrendingPage.vue │ ├── VideoItem.vue │ ├── VideoPlayer.vue │ ├── VideoRedirect.vue │ ├── VideoThumbnail.vue │ ├── WatchOnButton.vue │ └── WatchVideo.vue ├── locales │ ├── ang.json │ ├── ar.json │ ├── az.json │ ├── be.json │ ├── bg.json │ ├── bn.json │ ├── bs.json │ ├── ca.json │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── el.json │ ├── en.json │ ├── eo.json │ ├── es.json │ ├── et.json │ ├── eu.json │ ├── fa.json │ ├── fi.json │ ├── fr.json │ ├── gl.json │ ├── he.json │ ├── hi.json │ ├── hr.json │ ├── hu.json │ ├── hy.json │ ├── id.json │ ├── is.json │ ├── it.json │ ├── ja.json │ ├── kab.json │ ├── ko.json │ ├── lt.json │ ├── lv.json │ ├── ml.json │ ├── nb_NO.json │ ├── nl.json │ ├── oc.json │ ├── or.json │ ├── pl.json │ ├── pt.json │ ├── pt_BR.json │ ├── pt_PT.json │ ├── ro.json │ ├── ru.json │ ├── sc.json │ ├── si.json │ ├── sk.json │ ├── sl.json │ ├── sr.json │ ├── sv.json │ ├── ta.json │ ├── th.json │ ├── tr.json │ ├── uk.json │ ├── vi.json │ ├── zh_Hans.json │ └── zh_Hant.json ├── main.js ├── registerServiceWorker.js ├── router │ └── router.js └── utils │ ├── CountryMaps │ ├── de.json │ ├── el.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── it.json │ ├── lt.json │ ├── pl.json │ ├── sk.json │ ├── sr.json │ └── zh_Hant.json │ ├── DashUtils.js │ ├── HtmlUtils.js │ └── Misc.js ├── sweep.yaml ├── uno.config.js ├── vercel.json └── vite.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .git/ 4 | .* 5 | *.md 6 | !.prettier* 7 | !.eslintrc.cjs 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: TeamPiped 2 | liberapay: kavin 3 | custom: https://liberapay.com/bnyro 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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@v4 12 | - name: Setup pnpm 13 | uses: pnpm/action-setup@v2 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/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@v4 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 | -------------------------------------------------------------------------------- /.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@v4 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 | -------------------------------------------------------------------------------- /.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@v4 15 | - name: Setup pnpm 16 | uses: pnpm/action-setup@v2 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 | -------------------------------------------------------------------------------- /.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@v4 15 | - name: Setup pnpm 16 | uses: pnpm/action-setup@v2 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 | -------------------------------------------------------------------------------- /.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@v4 12 | - name: Setup pnpm 13 | uses: pnpm/action-setup@v2 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 | -------------------------------------------------------------------------------- /.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@v4 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Piped 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.2.4", 15 | "fast-xml-parser": "5.0.8", 16 | "hotkeys-js": "3.13.9", 17 | "javascript-time-ago": "2.5.11", 18 | "linkify-html": "4.2.0", 19 | "linkifyjs": "4.2.0", 20 | "qrcode": "^1.5.3", 21 | "shaka-player": "4.13.1", 22 | "vue": "3.5.13", 23 | "vue-i18n": "11.1.0", 24 | "vue-router": "4.5.0" 25 | }, 26 | "devDependencies": { 27 | "@iconify-json/fa6-brands": "1.2.5", 28 | "@iconify-json/fa6-solid": "1.2.3", 29 | "@intlify/unplugin-vue-i18n": "6.0.3", 30 | "@unocss/eslint-config": "66.0.0", 31 | "@unocss/preset-icons": "66.0.0", 32 | "@unocss/preset-uno": "66.0.0", 33 | "@unocss/preset-web-fonts": "66.0.0", 34 | "@unocss/reset": "66.0.0", 35 | "@unocss/transformer-directives": "66.0.0", 36 | "@unocss/transformer-variant-group": "66.0.0", 37 | "@vitejs/plugin-legacy": "6.0.0", 38 | "@vitejs/plugin-vue": "5.2.1", 39 | "@vue/compiler-sfc": "3.5.13", 40 | "eslint": "8.57.1", 41 | "eslint-config-prettier": "10.0.2", 42 | "eslint-plugin-prettier": "5.2.3", 43 | "eslint-plugin-vue": "9.32.0", 44 | "lightningcss": "1.29.1", 45 | "prettier": "3.5.2", 46 | "unocss": "66.0.0", 47 | "vite": "6.0.11", 48 | "vite-plugin-eslint": "1.8.1", 49 | "vite-plugin-pwa": "0.21.1", 50 | "workbox-window": "7.3.0" 51 | }, 52 | "browserslist": [ 53 | "last 1 chrome version", 54 | "last 1 firefox version" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamPiped/Piped/30a0ce334ea81dcc8f9a30869fdf1654611f9963/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /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/components/AddToGroupModal.vue: -------------------------------------------------------------------------------- 1 | 28 | 81 | -------------------------------------------------------------------------------- /src/components/ChannelItem.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 73 | -------------------------------------------------------------------------------- /src/components/ChaptersBar.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 93 | 94 | 118 | -------------------------------------------------------------------------------- /src/components/ClipsPage.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /src/components/CollapsableText.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 47 | 48 | 53 | -------------------------------------------------------------------------------- /src/components/CommentItem.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 115 | -------------------------------------------------------------------------------- /src/components/ConfirmModal.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | -------------------------------------------------------------------------------- /src/components/ContentItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | -------------------------------------------------------------------------------- /src/components/CreateGroupModal.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 36 | -------------------------------------------------------------------------------- /src/components/CreatePlaylistModal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 55 | -------------------------------------------------------------------------------- /src/components/CustomInstanceModal.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 80 | -------------------------------------------------------------------------------- /src/components/ErrorHandler.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /src/components/ExportHistoryModal.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 143 | -------------------------------------------------------------------------------- /src/components/FeedPage.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 168 | -------------------------------------------------------------------------------- /src/components/FooterComponent.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 53 | 54 | 62 | -------------------------------------------------------------------------------- /src/components/HistoryPage.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 149 | -------------------------------------------------------------------------------- /src/components/ImportHistoryModal.vue: -------------------------------------------------------------------------------- 1 | 37 | 109 | -------------------------------------------------------------------------------- /src/components/ImportPage.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 177 | -------------------------------------------------------------------------------- /src/components/LoadingIndicatorPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 56 | -------------------------------------------------------------------------------- /src/components/LoginPage.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 74 | -------------------------------------------------------------------------------- /src/components/ModalComponent.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 59 | -------------------------------------------------------------------------------- /src/components/PageNotFound.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/components/PlaylistAddModal.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 99 | -------------------------------------------------------------------------------- /src/components/PlaylistItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 36 | -------------------------------------------------------------------------------- /src/components/PlaylistVideos.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 110 | -------------------------------------------------------------------------------- /src/components/QrCode.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | -------------------------------------------------------------------------------- /src/components/RegisterPage.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 118 | -------------------------------------------------------------------------------- /src/components/SearchResults.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 140 | -------------------------------------------------------------------------------- /src/components/SearchSuggestions.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 83 | 84 | 105 | -------------------------------------------------------------------------------- /src/components/ShareModal.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 39 | 40 | 144 | -------------------------------------------------------------------------------- /src/components/SortingSelector.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 48 | -------------------------------------------------------------------------------- /src/components/ToastComponent.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /src/components/TrendingPage.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 57 | -------------------------------------------------------------------------------- /src/components/VideoRedirect.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /src/components/VideoThumbnail.vue: -------------------------------------------------------------------------------- 1 | 34 | 60 | 61 | 66 | -------------------------------------------------------------------------------- /src/components/WatchOnButton.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 32 | -------------------------------------------------------------------------------- /src/locales/ang.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /src/locales/zh_Hant.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": { 3 | "skip_intro": "跳過中場休息/開場動畫", 4 | "skip_sponsors": "跳過贊助廣告", 5 | "enable_sponsorblock": "啟用 SponsorBlock", 6 | "country_selection": "國家/地區", 7 | "channel_name_desc": "頻道名 (Z-A)", 8 | "channel_name_asc": "頻道名 (A-Z)", 9 | "unsubscribe": "取消訂閱", 10 | "subscribe": "訂閱", 11 | "sort_by": "排序依據:", 12 | "view_subscriptions": "檢視訂閱內容", 13 | "language_selection": "語言", 14 | "skip_non_music": "跳過音樂中的非音樂片段", 15 | "theme": "主題", 16 | "show_description": "顯示說明", 17 | "least_recent": "最早", 18 | "skip_interaction": "跳過互動(訂閱)提醒", 19 | "skip_self_promo": "跳過無酬/自我行銷", 20 | "audio_only": "僅聲音", 21 | "default_quality": "預設品質", 22 | "show_comments": "顯示留言", 23 | "default_homepage": "預設首頁", 24 | "store_watch_history": "儲存觀看記錄", 25 | "minimize_description": "最小化說明", 26 | "search": "搜尋 (Ctrl+K)", 27 | "show_recommendations": "顯示推薦", 28 | "minimize_recommendations": "最小化推薦", 29 | "most_recent": "最新", 30 | "back": "返回", 31 | "skip_outro": "跳過片尾插圖/工作人員名單", 32 | "skip_preview": "跳過預覽/回顧", 33 | "autoplay_video": "自動播放影片", 34 | "buffering_goal": "緩衝目標(秒)", 35 | "skip_highlight": "跳過精采整理", 36 | "auto": "自動", 37 | "dark": "暗色", 38 | "light": "亮色", 39 | "minimize_description_default": "預設情況下最小化影片說明", 40 | "enabled_codecs": "已啟用的轉碼器(可複選)", 41 | "show_more": "顯示詳情", 42 | "yes": "是", 43 | "no": "否", 44 | "export_to_json": "以 JSON 匯出", 45 | "import_from_json": "從 JSON 匯入", 46 | "loop_this_video": "循環播放此影片", 47 | "auto_play_next_video": "自動播放下一部影片", 48 | "donations": "開發捐款", 49 | "filter": "篩選", 50 | "loading": "載入中……", 51 | "clear_history": "清除記錄", 52 | "hide_replies": "隱藏回覆", 53 | "load_more_replies": "載入更多回覆", 54 | "remove_from_playlist": "從播放清單中移除", 55 | "create_playlist": "建立播放清單", 56 | "delete_playlist": "刪除播放清單", 57 | "delete_playlist_confirm": "要刪除這份播放清單嗎?", 58 | "please_select_playlist": "請選擇播放清單", 59 | "select_playlist": "選擇播放清單", 60 | "add_to_playlist": "增至播放清單", 61 | "delete_playlist_video_confirm": "要從播放清單中移除影片嗎?", 62 | "delete_account": "刪除帳戶", 63 | "show_chapters": "章節", 64 | "download_as_txt": "下載為 .txt", 65 | "share": "分享", 66 | "reset_preferences": "重設偏好設定", 67 | "confirm_reset_preferences": "確定要重設偏好設定嗎?", 68 | "backup_preferences": "備份偏好設定", 69 | "restore_preferences": "復原偏好設定", 70 | "back_to_home": "回首頁", 71 | "uses_api_from": "使用此API ", 72 | "instances_list": "站台列表", 73 | "show_markers": "在播放器上顯示標記", 74 | "skip_button_only": "顯示跳過按鈕", 75 | "skip_filler_tangent": "跳過與影片無關的片段", 76 | "autoplay_next_countdown": "預設播放下一段影片前的倒數時間(秒)", 77 | "min_segment_length": "最短分段的長度(秒)", 78 | "auto_display_captions": "自動顯示字幕", 79 | "minimize_comments": "收起留言", 80 | "disable_lbry": "不使用 LBRY 作為傳輸媒介", 81 | "enable_lbry_proxy": "使用 LBRY 作為代理伺服器", 82 | "view_ssl_score": "查看 SSL 分數", 83 | "logout": "從這個設置登出", 84 | "minimize_comments_default": "預設為收起留言", 85 | "instance_selection": "站台", 86 | "skip_automatically": "自動", 87 | "skip_segment": "跳過分段", 88 | "edit_playlist": "編輯播放清單", 89 | "playlist_name": "播放清單名稱", 90 | "instance_auth_selection": "身分驗證站台", 91 | "instance_donations": "站台捐款", 92 | "invalidate_session": "從所有裝置登出", 93 | "clone_playlist": "複製播放清單", 94 | "clone_playlist_success": "複製成功!", 95 | "different_auth_instance": "使用其他站台進行身分驗證", 96 | "with_timecode": "以時間碼分享", 97 | "follow_link": "追隨連結", 98 | "store_search_history": "儲存搜尋歷史", 99 | "okay": "好的", 100 | "no_valid_playlists": "此檔案不包含有效的播放清單!", 101 | "piped_link": "Piped 連結", 102 | "cancel": "取消", 103 | "playlist_description": "播放清單描述", 104 | "status_page": "狀態", 105 | "source_code": "原始碼", 106 | "bookmark_playlist": "書籤", 107 | "documentation": "文件", 108 | "with_playlist": "以播放清單分享", 109 | "playlist_bookmarked": "已存入書籤", 110 | "create_group": "建立群組", 111 | "group_name": "群組名稱", 112 | "show_search_suggestions": "顯示搜尋建議", 113 | "copy_link": "複製連結", 114 | "time_code": "時間碼(以秒計算)", 115 | "minimize_chapters_default": "預設收起章節", 116 | "dismiss": "解散", 117 | "chapters_layout_mobile": "在手提裝置上的章節佈局", 118 | "hide_watched": "在摘要中隱藏看過的影片", 119 | "reply_count": "{count} 個回覆", 120 | "minimize_recommendations_default": "預設收起推薦影片", 121 | "show_watch_on_youtube": "顯示「在Youtube 觀看」按鈕", 122 | "show_less": "顯示更少", 123 | "generate_qrcode": "產生 QR 碼", 124 | "add_to_group": "加到群組", 125 | "import_from_json_csv": "從 JSON / CSV 匯入", 126 | "enable_dearrow": "啟用 DeArrow 插件", 127 | "delete_automatically": "在多久後自動刪除", 128 | "download_frame": "下載影片幀", 129 | "instance_privacy_policy": "私隱政策" 130 | }, 131 | "titles": { 132 | "history": "歷史記錄", 133 | "preferences": "偏好設定", 134 | "feed": "摘要", 135 | "trending": "趨勢", 136 | "register": "註冊", 137 | "login": "登入", 138 | "subscriptions": "訂閱內容", 139 | "playlists": "播放清單", 140 | "account": "帳戶", 141 | "player": "播放器", 142 | "instance": "站台", 143 | "bookmarks": "書籤", 144 | "livestreams": "直播", 145 | "channels": "頻道", 146 | "channel_groups": "頻道群組", 147 | "dearrow": "DeArrow 插件" 148 | }, 149 | "preferences": { 150 | "registered_users": "已註冊的使用者", 151 | "version": "版本", 152 | "has_cdn": "是否有 CDN?", 153 | "instance_name": "站台名稱", 154 | "instance_locations": "站台位置", 155 | "up_to_date": "最新?", 156 | "ssl_score": "SSL 分數" 157 | }, 158 | "login": { 159 | "username": "使用者名", 160 | "password": "密碼", 161 | "password_confirm": "確認密碼", 162 | "passwords_incorrect": "密碼不正確!" 163 | }, 164 | "video": { 165 | "videos": "影片", 166 | "watched": "有看過", 167 | "sponsor_segments": "贊助廣告片段", 168 | "ratings_disabled": "評價已停用", 169 | "chapters": "章節", 170 | "shorts": "短影片", 171 | "category": "類別", 172 | "chapters_horizontal": "水平", 173 | "chapters_vertical": "垂直", 174 | "views": "觀看次數:{views}", 175 | "live": "{0} 直播", 176 | "all": "全部", 177 | "visibility": "是否顯示", 178 | "license": "授權" 179 | }, 180 | "search": { 181 | "did_you_mean": "您是否想找 {0}?", 182 | "music_playlists": "YT Music:播放清單", 183 | "videos": "YouTube:影片", 184 | "channels": "YouTube:頻道", 185 | "all": "YouTube:所有", 186 | "playlists": "YouTube:播放清單", 187 | "music_songs": "YT Music:歌曲", 188 | "music_videos": "YT Music:影片", 189 | "music_albums": "YT Music:專輯", 190 | "music_artists": "YT Music: 藝人" 191 | }, 192 | "comment": { 193 | "pinned_by": "置頂者: {author}", 194 | "disabled": "上傳者停用了留言功能。", 195 | "loading": "留言載入中……", 196 | "user_disabled": "留言在設置中被關閉。" 197 | }, 198 | "info": { 199 | "copied": "已複製!", 200 | "cannot_copy": "無法複製!", 201 | "page_not_found": "找不到頁面", 202 | "preferences_note": "註:偏好設定儲存在本機的瀏覽器的儲存空間內。如果清除瀏覽器的資料,偏好設定就會重設。", 203 | "register_no_email_note": "不建議使用電子郵件地址作為用戶名稱。仍要繼續嗎?", 204 | "local_storage": "此動作需要儲存資料在本機,請確認是否啟用了Cookies?", 205 | "next_video_countdown": "在{0}秒後播放下一段影片", 206 | "days": "{amount} 天", 207 | "weeks": "{amount} 個星期", 208 | "months": "{amount} 個月", 209 | "hours": "{amount} 小時", 210 | "login_note": "使用在此站台中註冊的賬戶登錄。", 211 | "register_note": "你可以在這個 Piped 站台註冊一個賬戶。由於資料會存放在伺服器端,這會允許你透過你的帳戶同步你的訂閱和播放清單。你亦可在不註冊的情況下使用所有功能,但所有資料將會存放在瀏覽器端的緩存中。請不要使用電郵地址再為你的用戶名稱,並使用一個未在其他地方使用過的安全密碼。" 212 | }, 213 | "player": { 214 | "watch_on": "在 {0} 觀看", 215 | "failed": "播放失敗,錯誤代碼 {0},詳情請翻查紀錄" 216 | }, 217 | "subscriptions": { 218 | "subscribed_channels_count": "己訂閱: {0}" 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | }, 5 | "routes": [ 6 | { 7 | "src": "/[^.]+", 8 | "dest": "/", 9 | "status": 200 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------