mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	merge master into oidc
This commit is contained in:
		
						commit
						7616a3d34c
					
				
					 90 changed files with 4770 additions and 2603 deletions
				
			
		
							
								
								
									
										7
									
								
								.eslintrc.cjs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.eslintrc.cjs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					    root: true,
 | 
				
			||||||
 | 
					    env: {
 | 
				
			||||||
 | 
					        node: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    extends: ["plugin:vue/vue3-recommended", "eslint:recommended", "@unocss", "plugin:prettier/recommended"],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										6
									
								
								.github/workflows/ci.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -8,7 +8,7 @@ jobs:
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
        runs-on: ubuntu-latest
 | 
					        runs-on: ubuntu-latest
 | 
				
			||||||
        steps:
 | 
					        steps:
 | 
				
			||||||
            - uses: actions/checkout@v3
 | 
					            - uses: actions/checkout@v4
 | 
				
			||||||
            - name: Setup pnpm
 | 
					            - name: Setup pnpm
 | 
				
			||||||
              uses: pnpm/action-setup@v2
 | 
					              uses: pnpm/action-setup@v2
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
| 
						 | 
					@ -19,4 +19,8 @@ jobs:
 | 
				
			||||||
                  cache: "pnpm"
 | 
					                  cache: "pnpm"
 | 
				
			||||||
            - run: pnpm install
 | 
					            - run: pnpm install
 | 
				
			||||||
            - run: pnpm build
 | 
					            - run: pnpm build
 | 
				
			||||||
 | 
					            - uses: actions/upload-artifact@v3
 | 
				
			||||||
 | 
					              with:
 | 
				
			||||||
 | 
					                  name: build
 | 
				
			||||||
 | 
					                  path: dist
 | 
				
			||||||
            - run: pnpm lint --no-fix
 | 
					            - run: pnpm lint --no-fix
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										65
									
								
								.github/workflows/codeql.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								.github/workflows/codeql.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					name: "CodeQL"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches: [ 'master' ]
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    # The branches below must be a subset of the branches above
 | 
				
			||||||
 | 
					    branches: [ 'master' ]
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: '42 11 * * 4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  analyze:
 | 
				
			||||||
 | 
					    name: Analyze
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    permissions:
 | 
				
			||||||
 | 
					      actions: read
 | 
				
			||||||
 | 
					      contents: read
 | 
				
			||||||
 | 
					      security-events: write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        language: [ 'javascript' ]
 | 
				
			||||||
 | 
					        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
 | 
				
			||||||
 | 
					        # Use only 'java' to analyze code written in Java, Kotlin or both
 | 
				
			||||||
 | 
					        # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
 | 
				
			||||||
 | 
					        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - name: Checkout repository
 | 
				
			||||||
 | 
					      uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Initializes the CodeQL tools for scanning.
 | 
				
			||||||
 | 
					    - name: Initialize CodeQL
 | 
				
			||||||
 | 
					      uses: github/codeql-action/init@v2
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        languages: ${{ matrix.language }}
 | 
				
			||||||
 | 
					        # If you wish to specify custom queries, you can do so here or in a config file.
 | 
				
			||||||
 | 
					        # By default, queries listed here will override any specified in a config file.
 | 
				
			||||||
 | 
					        # Prefix the list here with "+" to use these queries and those in the config file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
 | 
				
			||||||
 | 
					        # queries: security-extended,security-and-quality
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
 | 
				
			||||||
 | 
					    # If this step fails, then you should remove it and run the build manually (see below)
 | 
				
			||||||
 | 
					    - name: Autobuild
 | 
				
			||||||
 | 
					      uses: github/codeql-action/autobuild@v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # ℹ️ Command-line programs to run using the OS shell.
 | 
				
			||||||
 | 
					    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #   If the Autobuild fails above, remove it and uncomment the following three lines.
 | 
				
			||||||
 | 
					    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # - run: |
 | 
				
			||||||
 | 
					    #     echo "Run, Build Application using script"
 | 
				
			||||||
 | 
					    #     ./location_of_script_within_repo/buildscript.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - name: Perform CodeQL Analysis
 | 
				
			||||||
 | 
					      uses: github/codeql-action/analyze@v2
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        category: "/language:${{matrix.language}}"
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/deploy-azure.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/deploy-azure.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,7 +11,7 @@ jobs:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    name: Build and Deploy Job
 | 
					    name: Build and Deploy Job
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v3
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          submodules: true
 | 
					          submodules: true
 | 
				
			||||||
      - name: Build And Deploy
 | 
					      - name: Build And Deploy
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								.github/workflows/docker-build.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/docker-build.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,7 +11,7 @@ jobs:
 | 
				
			||||||
    build-docker-image:
 | 
					    build-docker-image:
 | 
				
			||||||
        runs-on: ubuntu-latest
 | 
					        runs-on: ubuntu-latest
 | 
				
			||||||
        steps:
 | 
					        steps:
 | 
				
			||||||
            - uses: actions/checkout@v3
 | 
					            - uses: actions/checkout@v4
 | 
				
			||||||
            - name: Setup pnpm
 | 
					            - name: Setup pnpm
 | 
				
			||||||
              uses: pnpm/action-setup@v2
 | 
					              uses: pnpm/action-setup@v2
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
| 
						 | 
					@ -23,21 +23,21 @@ jobs:
 | 
				
			||||||
            - run: pnpm install
 | 
					            - run: pnpm install
 | 
				
			||||||
            - run: pnpm build && ./localizefonts.sh && mv dist/ dist-ci/
 | 
					            - run: pnpm build && ./localizefonts.sh && mv dist/ dist-ci/
 | 
				
			||||||
            - name: Set up QEMU
 | 
					            - name: Set up QEMU
 | 
				
			||||||
              uses: docker/setup-qemu-action@v2
 | 
					              uses: docker/setup-qemu-action@v3
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
                  platforms: all
 | 
					                  platforms: all
 | 
				
			||||||
            - name: Set up Docker Buildx
 | 
					            - name: Set up Docker Buildx
 | 
				
			||||||
              id: buildx
 | 
					              id: buildx
 | 
				
			||||||
              uses: docker/setup-buildx-action@v2
 | 
					              uses: docker/setup-buildx-action@v3
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
                  version: latest
 | 
					                  version: latest
 | 
				
			||||||
            - name: Login to DockerHub
 | 
					            - name: Login to DockerHub
 | 
				
			||||||
              uses: docker/login-action@v2
 | 
					              uses: docker/login-action@v3
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
                  username: ${{ secrets.DOCKER_USERNAME }}
 | 
					                  username: ${{ secrets.DOCKER_USERNAME }}
 | 
				
			||||||
                  password: ${{ secrets.DOCKER_PASSWORD }}
 | 
					                  password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
            - name: Build and push
 | 
					            - name: Build and push
 | 
				
			||||||
              uses: docker/build-push-action@v4
 | 
					              uses: docker/build-push-action@v5
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
                  context: .
 | 
					                  context: .
 | 
				
			||||||
                  file: ./Dockerfile.ci
 | 
					                  file: ./Dockerfile.ci
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/ipfs-build.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ipfs-build.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,7 +11,7 @@ jobs:
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
        runs-on: ubuntu-latest
 | 
					        runs-on: ubuntu-latest
 | 
				
			||||||
        steps:
 | 
					        steps:
 | 
				
			||||||
            - uses: actions/checkout@v3
 | 
					            - uses: actions/checkout@v4
 | 
				
			||||||
            - name: Setup pnpm
 | 
					            - name: Setup pnpm
 | 
				
			||||||
              uses: pnpm/action-setup@v2
 | 
					              uses: pnpm/action-setup@v2
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										25
									
								
								.github/workflows/reviewdog.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/reviewdog.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					name: reviewdog
 | 
				
			||||||
 | 
					on: [pull_request]
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					    eslint:
 | 
				
			||||||
 | 
					        name: runner / eslint
 | 
				
			||||||
 | 
					        runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					        permissions:
 | 
				
			||||||
 | 
					            contents: read
 | 
				
			||||||
 | 
					            pull-requests: write
 | 
				
			||||||
 | 
					        steps:
 | 
				
			||||||
 | 
					            - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					            - name: Setup pnpm
 | 
				
			||||||
 | 
					              uses: pnpm/action-setup@v2
 | 
				
			||||||
 | 
					              with:
 | 
				
			||||||
 | 
					                  version: latest
 | 
				
			||||||
 | 
					            - name: Setup Node.js
 | 
				
			||||||
 | 
					              uses: actions/setup-node@v3
 | 
				
			||||||
 | 
					              with:
 | 
				
			||||||
 | 
					                  cache: "pnpm"
 | 
				
			||||||
 | 
					            - run: pnpm install
 | 
				
			||||||
 | 
					            - uses: reviewdog/action-eslint@v1
 | 
				
			||||||
 | 
					              with:
 | 
				
			||||||
 | 
					                  github_token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					                  reporter: github-pr-review
 | 
				
			||||||
 | 
					                  eslint_flags: "--ignore-path .gitignore --ext .js,.vue ."
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/weblate-merge.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/weblate-merge.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -8,7 +8,7 @@ jobs:
 | 
				
			||||||
  merge:
 | 
					  merge:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v3
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
      - name: Check if en.json has been updated
 | 
					      - name: Check if en.json has been updated
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          if -n git diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} src/locales/en.json; then
 | 
					          if -n git diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} src/locales/en.json; then
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
 | 
					[](https://www.gnu.org/licenses/agpl-3.0.en.html)
 | 
				
			||||||
[](https://matrix.to/#/#piped:matrix.org)
 | 
					[](https://matrix.to/#/#piped:matrix.org)
 | 
				
			||||||
 | 
					[](https://feddit.rocks/c/piped)
 | 
				
			||||||
[](https://piped.video/register)
 | 
					[](https://piped.video/register)
 | 
				
			||||||
[](https://piped-ipfs.kavin.rocks/)
 | 
					[](https://piped-ipfs.kavin.rocks/)
 | 
				
			||||||
[](https://github.com/TeamPiped/Piped/stargazers)
 | 
					[](https://github.com/TeamPiped/Piped/stargazers)
 | 
				
			||||||
| 
						 | 
					@ -61,6 +62,10 @@ By using Piped, you can freely watch and listen to content without the fear of p
 | 
				
			||||||
-   You can join us via Matrix at [#piped](https://matrix.to/#/#piped:matrix.org).
 | 
					-   You can join us via Matrix at [#piped](https://matrix.to/#/#piped:matrix.org).
 | 
				
			||||||
-   You can also join us at the libera.chat IRC network which is bridged to the Matrix room at [#piped](https://web.libera.chat/#piped).
 | 
					-   You can also join us at the libera.chat IRC network which is bridged to the Matrix room at [#piped](https://web.libera.chat/#piped).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Public Communities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-   You can join us on Lemmy on the [!piped@feddit.rocks](https://feddit.rocks/c/piped) community.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Self-Hosting
 | 
					## Self-Hosting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See https://docs.piped.video/docs/self-hosting/ for more details.
 | 
					See https://docs.piped.video/docs/self-hosting/ for more details.
 | 
				
			||||||
| 
						 | 
					@ -140,12 +145,15 @@ Contributions in any other form are also welcomed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   [Yattee](https://github.com/yattee/yattee) - an alternative frontend for YouTube, for IOS.
 | 
					-   [Yattee](https://github.com/yattee/yattee) - an alternative frontend for YouTube, for IOS.
 | 
				
			||||||
-   [LibreTube](https://github.com/Libre-tube/LibreTube) - an alternative frontend for YouTube, for Android.
 | 
					-   [LibreTube](https://github.com/Libre-tube/LibreTube) - an alternative frontend for YouTube, for Android.
 | 
				
			||||||
 | 
					-   [Racoon](https://github.com/shailendramaurya/racoon) - A web based minimal YouTube downloader.
 | 
				
			||||||
-   [Hyperpipe](https://codeberg.org/Hyperpipe/Hyperpipe) - an alternative privacy respecting frontend for YouTube Music.
 | 
					-   [Hyperpipe](https://codeberg.org/Hyperpipe/Hyperpipe) - an alternative privacy respecting frontend for YouTube Music.
 | 
				
			||||||
-   [Musicale](https://github.com/Bellisario/musicale) - an alternative to YouTube Music, with style.
 | 
					-   [Musicale](https://github.com/Bellisario/musicale) - an alternative to YouTube Music, with style.
 | 
				
			||||||
-   [ytify](https://github.com/n-ce/ytify) - a complementary minimal audio streaming frontend for YouTube.
 | 
					-   [ytify](https://github.com/n-ce/ytify) - a complementary minimal audio streaming frontend for YouTube.
 | 
				
			||||||
-   [PsTube](https://github.com/prateekmedia/pstube) - Watch and download videos without ads on Android, Linux, Windows, iOS, and Mac OSX.
 | 
					-   [PsTube](https://github.com/prateekmedia/pstube) - Watch and download videos without ads on Android, Linux, Windows, iOS, and Mac OSX.
 | 
				
			||||||
-   [Piped-Material](https://github.com/mmjee/Piped-Material) - A fork of Piped, focusing on better performance and a more usable design.
 | 
					-   [Piped-Material](https://github.com/mmjee/Piped-Material) - A fork of Piped, focusing on better performance and a more usable design.
 | 
				
			||||||
-   [ReacTube](https://github.com/NeeRaj-2401/ReacTube) - Privacy friendly & distraction free Youtube front-end using Piped API.
 | 
					-   [ReacTube](https://github.com/NeeRaj-2401/ReacTube) - Privacy friendly & distraction free Youtube front-end using Piped API.
 | 
				
			||||||
 | 
					-   [YTDLnis](https://github.com/deniscerri/ytdlnis) - Video and audio downloader for Android that uses Piped to update formats.
 | 
				
			||||||
 | 
					-   [DeskVideo](https://github.com/malisipi/DeskVideo) - A desktop styled, customizable alternative front-end for YouTube.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## YourKit
 | 
					## YourKit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,8 @@
 | 
				
			||||||
#!/bin/sh
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
base='https://fonts\.(gstatic\.com|kavin\.rocks)'
 | 
					base='https://fonts\.(gstatic\.com|kavin\.rocks)'
 | 
				
			||||||
fonts=$(cat dist/assets/* | grep -Po "$base[^)]*" | sort | uniq)
 | 
					fonts=$(cat dist/assets/* | grep -Eo "${base}[^)]*" | sort | uniq)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for font in $fonts; do
 | 
					for font in $fonts; do
 | 
				
			||||||
	file="dist/fonts$(echo "$font" | sed -E "s#$base##")"
 | 
						file="dist/fonts$(echo "$font" | sed -E "s#$base##")"
 | 
				
			||||||
	mkdir -p "$(dirname "$file")"
 | 
						mkdir -p "$(dirname "$file")"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										70
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										70
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -10,57 +10,51 @@
 | 
				
			||||||
        "lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
 | 
					        "lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
        "@fortawesome/fontawesome-svg-core": "6.4.0",
 | 
					        "@fortawesome/fontawesome-svg-core": "6.4.2",
 | 
				
			||||||
        "@fortawesome/free-brands-svg-icons": "6.4.0",
 | 
					        "@fortawesome/free-brands-svg-icons": "6.4.2",
 | 
				
			||||||
        "@fortawesome/free-solid-svg-icons": "6.4.0",
 | 
					        "@fortawesome/free-solid-svg-icons": "6.4.2",
 | 
				
			||||||
        "@fortawesome/vue-fontawesome": "3.0.3",
 | 
					        "@fortawesome/vue-fontawesome": "3.0.3",
 | 
				
			||||||
        "buffer": "6.0.3",
 | 
					        "buffer": "6.0.3",
 | 
				
			||||||
        "dompurify": "3.0.3",
 | 
					        "dompurify": "3.0.5",
 | 
				
			||||||
        "hotkeys-js": "3.10.2",
 | 
					        "hotkeys-js": "3.12.0",
 | 
				
			||||||
        "javascript-time-ago": "2.5.9",
 | 
					        "javascript-time-ago": "2.5.9",
 | 
				
			||||||
 | 
					        "linkify-html": "4.1.1",
 | 
				
			||||||
 | 
					        "linkifyjs": "4.1.1",
 | 
				
			||||||
        "mux.js": "6.3.0",
 | 
					        "mux.js": "6.3.0",
 | 
				
			||||||
        "shaka-player": "4.3.6",
 | 
					        "qrcode": "^1.5.3",
 | 
				
			||||||
 | 
					        "shaka-player": "4.4.2",
 | 
				
			||||||
        "stream-browserify": "3.0.0",
 | 
					        "stream-browserify": "3.0.0",
 | 
				
			||||||
        "vue": "3.3.4",
 | 
					        "vue": "3.3.4",
 | 
				
			||||||
        "vue-i18n": "9.2.2",
 | 
					        "vue-i18n": "9.4.1",
 | 
				
			||||||
        "vue-router": "4.2.2",
 | 
					        "vue-router": "4.2.5",
 | 
				
			||||||
        "xml-js": "1.6.11"
 | 
					        "xml-js": "1.6.11"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "devDependencies": {
 | 
					    "devDependencies": {
 | 
				
			||||||
        "@iconify-json/fa6-brands": "1.1.11",
 | 
					        "@iconify-json/fa6-brands": "1.1.13",
 | 
				
			||||||
        "@iconify-json/fa6-solid": "1.1.13",
 | 
					        "@iconify-json/fa6-solid": "1.1.15",
 | 
				
			||||||
        "@intlify/unplugin-vue-i18n": "0.11.0",
 | 
					        "@intlify/unplugin-vue-i18n": "1.2.0",
 | 
				
			||||||
        "@unocss/preset-icons": "0.53.1",
 | 
					        "@unocss/eslint-config": "0.56.1",
 | 
				
			||||||
        "@unocss/preset-web-fonts": "0.53.1",
 | 
					        "@unocss/preset-icons": "0.56.1",
 | 
				
			||||||
        "@unocss/reset": "0.53.1",
 | 
					        "@unocss/preset-uno": "0.56.1",
 | 
				
			||||||
        "@unocss/transformer-directives": "0.53.1",
 | 
					        "@unocss/preset-web-fonts": "0.56.1",
 | 
				
			||||||
        "@unocss/transformer-variant-group": "0.53.1",
 | 
					        "@unocss/reset": "0.56.1",
 | 
				
			||||||
        "@vitejs/plugin-legacy": "4.0.4",
 | 
					        "@unocss/transformer-directives": "0.56.1",
 | 
				
			||||||
        "@vitejs/plugin-vue": "4.2.3",
 | 
					        "@unocss/transformer-variant-group": "0.56.1",
 | 
				
			||||||
 | 
					        "@vitejs/plugin-legacy": "4.1.1",
 | 
				
			||||||
 | 
					        "@vitejs/plugin-vue": "4.3.4",
 | 
				
			||||||
        "@vue/compiler-sfc": "3.3.4",
 | 
					        "@vue/compiler-sfc": "3.3.4",
 | 
				
			||||||
        "eslint": "8.43.0",
 | 
					        "eslint": "8.50.0",
 | 
				
			||||||
        "eslint-config-prettier": "8.8.0",
 | 
					        "eslint-config-prettier": "9.0.0",
 | 
				
			||||||
        "eslint-plugin-prettier": "4.2.1",
 | 
					        "eslint-plugin-prettier": "5.0.0",
 | 
				
			||||||
        "eslint-plugin-vue": "9.14.1",
 | 
					        "eslint-plugin-vue": "9.17.0",
 | 
				
			||||||
        "prettier": "2.8.8",
 | 
					        "lightningcss": "1.22.0",
 | 
				
			||||||
        "unocss": "0.53.1",
 | 
					        "prettier": "3.0.3",
 | 
				
			||||||
        "vite": "4.3.9",
 | 
					        "unocss": "0.56.1",
 | 
				
			||||||
 | 
					        "vite": "4.4.9",
 | 
				
			||||||
        "vite-plugin-eslint": "1.8.1",
 | 
					        "vite-plugin-eslint": "1.8.1",
 | 
				
			||||||
        "vite-plugin-pwa": "0.16.4",
 | 
					        "vite-plugin-pwa": "0.16.5",
 | 
				
			||||||
        "workbox-window": "7.0.0"
 | 
					        "workbox-window": "7.0.0"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "eslintConfig": {
 | 
					 | 
				
			||||||
        "root": true,
 | 
					 | 
				
			||||||
        "env": {
 | 
					 | 
				
			||||||
            "node": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "extends": [
 | 
					 | 
				
			||||||
            "plugin:vue/vue3-essential",
 | 
					 | 
				
			||||||
            "plugin:prettier/recommended",
 | 
					 | 
				
			||||||
            "eslint:recommended"
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "rules": {}
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "browserslist": [
 | 
					    "browserslist": [
 | 
				
			||||||
        "last 1 chrome version",
 | 
					        "last 1 chrome version",
 | 
				
			||||||
        "last 1 firefox version"
 | 
					        "last 1 firefox version"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2993
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2993
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										50
									
								
								src/App.vue
									
										
									
									
									
								
							
							
						
						
									
										50
									
								
								src/App.vue
									
										
									
									
									
								
							| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="flex flex-col w-full min-h-screen px-1vw py-5 antialiased reset" :class="[theme]">
 | 
					    <div class="reset min-h-screen w-full flex flex-col px-1vw py-5 antialiased" :class="[theme]">
 | 
				
			||||||
        <div class="flex-1">
 | 
					        <div class="flex-1">
 | 
				
			||||||
            <NavBar />
 | 
					            <NavBar />
 | 
				
			||||||
            <router-view v-slot="{ Component }">
 | 
					            <router-view v-slot="{ Component }">
 | 
				
			||||||
| 
						 | 
					@ -29,25 +29,6 @@ export default {
 | 
				
			||||||
            theme: "dark",
 | 
					            theme: "dark",
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    methods: {
 | 
					 | 
				
			||||||
        setTheme() {
 | 
					 | 
				
			||||||
            let themePref = this.getPreferenceString("theme", "dark");
 | 
					 | 
				
			||||||
            if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
 | 
					 | 
				
			||||||
            else this.theme = themePref;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Change title bar color based on user's theme
 | 
					 | 
				
			||||||
            const themeColor = document.querySelector("meta[name='theme-color']");
 | 
					 | 
				
			||||||
            if (this.theme === "light") {
 | 
					 | 
				
			||||||
                themeColor.setAttribute("content", "#FFF");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                themeColor.setAttribute("content", "#0F0F0F");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Used for the scrollbar
 | 
					 | 
				
			||||||
            const root = document.querySelector(":root");
 | 
					 | 
				
			||||||
            this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        this.setTheme();
 | 
					        this.setTheme();
 | 
				
			||||||
        darkModePreference.addEventListener("change", () => {
 | 
					        darkModePreference.addEventListener("change", () => {
 | 
				
			||||||
| 
						 | 
					@ -55,7 +36,7 @@ export default {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ("indexedDB" in window) {
 | 
					        if ("indexedDB" in window) {
 | 
				
			||||||
            const request = indexedDB.open("piped-db", 4);
 | 
					            const request = indexedDB.open("piped-db", 5);
 | 
				
			||||||
            request.onupgradeneeded = ev => {
 | 
					            request.onupgradeneeded = ev => {
 | 
				
			||||||
                const db = request.result;
 | 
					                const db = request.result;
 | 
				
			||||||
                console.log("Upgrading object store.");
 | 
					                console.log("Upgrading object store.");
 | 
				
			||||||
| 
						 | 
					@ -77,6 +58,12 @@ export default {
 | 
				
			||||||
                    const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
 | 
					                    const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
 | 
				
			||||||
                    store.createIndex("groupName", "groupName", { unique: true });
 | 
					                    store.createIndex("groupName", "groupName", { unique: true });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                if (!db.objectStoreNames.contains("playlists")) {
 | 
				
			||||||
 | 
					                    const playlistStore = db.createObjectStore("playlists", { keyPath: "playlistId" });
 | 
				
			||||||
 | 
					                    playlistStore.createIndex("playlistId", "playlistId", { unique: true });
 | 
				
			||||||
 | 
					                    const playlistVideosStore = db.createObjectStore("playlist_videos", { keyPath: "videoId" });
 | 
				
			||||||
 | 
					                    playlistVideosStore.createIndex("videoId", "videoId", { unique: true });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            request.onsuccess = e => {
 | 
					            request.onsuccess = e => {
 | 
				
			||||||
                window.db = e.target.result;
 | 
					                window.db = e.target.result;
 | 
				
			||||||
| 
						 | 
					@ -106,6 +93,25 @@ export default {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })();
 | 
					        })();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					        setTheme() {
 | 
				
			||||||
 | 
					            let themePref = this.getPreferenceString("theme", "dark");
 | 
				
			||||||
 | 
					            if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
 | 
				
			||||||
 | 
					            else this.theme = themePref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Change title bar color based on user's theme
 | 
				
			||||||
 | 
					            const themeColor = document.querySelector("meta[name='theme-color']");
 | 
				
			||||||
 | 
					            if (this.theme === "light") {
 | 
				
			||||||
 | 
					                themeColor.setAttribute("content", "#FFF");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                themeColor.setAttribute("content", "#0F0F0F");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Used for the scrollbar
 | 
				
			||||||
 | 
					            const root = document.querySelector(":root");
 | 
				
			||||||
 | 
					            this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -212,7 +218,7 @@ b {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.input {
 | 
					.input {
 | 
				
			||||||
    @apply pl-2.5;
 | 
					    @apply px-2.5;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.input:focus {
 | 
					.input:focus {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										54
									
								
								src/components/AddToGroupModal.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/components/AddToGroupModal.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <ModalComponent @close="$emit('close')">
 | 
				
			||||||
 | 
					        <span v-t="'actions.add_to_group'" class="mb-3 inline-block w-max text-2xl" />
 | 
				
			||||||
 | 
					        <div v-for="(group, index) in channelGroups" :key="group.groupName" class="px-1">
 | 
				
			||||||
 | 
					            <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					                <span>{{ group.groupName }}</span>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="checkbox"
 | 
				
			||||||
 | 
					                    :checked="group.channels.includes(channelId)"
 | 
				
			||||||
 | 
					                    @change="onCheckedChange(index, group)"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <hr class="h-1 w-full" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </ModalComponent>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import ModalComponent from "./ModalComponent.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					        ModalComponent,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					        channelId: {
 | 
				
			||||||
 | 
					            type: String,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    emits: ["close"],
 | 
				
			||||||
 | 
					    data() {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            channelGroups: [],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mounted() {
 | 
				
			||||||
 | 
					        this.loadChannelGroups();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					        async loadChannelGroups() {
 | 
				
			||||||
 | 
					            const groups = await this.getChannelGroups();
 | 
				
			||||||
 | 
					            this.channelGroups.push(...groups);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        onCheckedChange(index, group) {
 | 
				
			||||||
 | 
					            if (group.channels.includes(this.channelId)) {
 | 
				
			||||||
 | 
					                group.channels.splice(index, 1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                group.channels.push(this.channelId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.createOrUpdateChannelGroup(group);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,19 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div class="flex flex-col flex-justify-between">
 | 
				
			||||||
        <router-link :to="props.item.url">
 | 
					        <router-link :to="props.item.url">
 | 
				
			||||||
            <div class="relative">
 | 
					            <div class="my-4 flex justify-center">
 | 
				
			||||||
                <img class="w-full" :src="props.item.thumbnail" loading="lazy" />
 | 
					                <img class="aspect-square w-[50%] rounded-full" :src="props.item.thumbnail" loading="lazy" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
                <span v-text="props.item.name" />
 | 
					                <span v-text="props.item.name" />
 | 
				
			||||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
 | 
					                <font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
        <p v-if="props.item.description" v-text="props.item.description" />
 | 
					        <p v-if="props.item.description" v-text="props.item.description" />
 | 
				
			||||||
        <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
 | 
					        <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
                <span v-text="props.item.uploader" />
 | 
					                <span v-text="props.item.uploader" />
 | 
				
			||||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
 | 
					                <font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup>
 | 
					<script setup>
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    item: Object,
 | 
					    item: {
 | 
				
			||||||
 | 
					        type: Object,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,34 +5,41 @@
 | 
				
			||||||
        <img
 | 
					        <img
 | 
				
			||||||
            v-if="channel.bannerUrl"
 | 
					            v-if="channel.bannerUrl"
 | 
				
			||||||
            :src="channel.bannerUrl"
 | 
					            :src="channel.bannerUrl"
 | 
				
			||||||
            class="w-full py-1.5 h-30 md:h-50 object-cover"
 | 
					            class="h-30 w-full object-cover py-1.5 md:h-50"
 | 
				
			||||||
            loading="lazy"
 | 
					            loading="lazy"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <div class="flex flex-col md:flex-row justify-between items-center">
 | 
					        <div class="flex flex-col items-center justify-between md:flex-row">
 | 
				
			||||||
            <div class="flex place-items-center">
 | 
					            <div class="flex place-items-center">
 | 
				
			||||||
                <img height="48" width="48" class="rounded-full m-1" :src="channel.avatarUrl" />
 | 
					                <img height="48" width="48" class="m-1 rounded-full" :src="channel.avatarUrl" />
 | 
				
			||||||
                <div class="flex gap-1 items-center">
 | 
					                <div class="flex items-center gap-1">
 | 
				
			||||||
                    <h1 v-text="channel.name" class="!text-xl" />
 | 
					                    <h1 class="!text-xl" v-text="channel.name" />
 | 
				
			||||||
                    <font-awesome-icon class="!text-xl" v-if="channel.verified" icon="check" />
 | 
					                    <font-awesome-icon v-if="channel.verified" class="!text-xl" icon="check" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div class="flex gap-2">
 | 
					            <div class="flex gap-2">
 | 
				
			||||||
                <button
 | 
					                <button
 | 
				
			||||||
                    class="btn"
 | 
					 | 
				
			||||||
                    @click="subscribeHandler"
 | 
					 | 
				
			||||||
                    v-t="{
 | 
					                    v-t="{
 | 
				
			||||||
                        path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
					                        path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
				
			||||||
                        args: { count: numberFormat(channel.subscriberCount) },
 | 
					                        args: { count: numberFormat(channel.subscriberCount) },
 | 
				
			||||||
                    }"
 | 
					                    }"
 | 
				
			||||||
 | 
					                    class="btn"
 | 
				
			||||||
 | 
					                    @click="subscribeHandler"
 | 
				
			||||||
 | 
					                ></button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <button
 | 
				
			||||||
 | 
					                    v-if="subscribed"
 | 
				
			||||||
 | 
					                    v-t="'actions.add_to_group'"
 | 
				
			||||||
 | 
					                    class="btn"
 | 
				
			||||||
 | 
					                    @click="showGroupModal = true"
 | 
				
			||||||
                ></button>
 | 
					                ></button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <!-- RSS Feed button -->
 | 
					                <!-- RSS Feed button -->
 | 
				
			||||||
                <a
 | 
					                <a
 | 
				
			||||||
 | 
					                    v-if="channel.id"
 | 
				
			||||||
                    aria-label="RSS feed"
 | 
					                    aria-label="RSS feed"
 | 
				
			||||||
                    title="RSS feed"
 | 
					                    title="RSS feed"
 | 
				
			||||||
                    role="button"
 | 
					                    role="button"
 | 
				
			||||||
                    v-if="channel.id"
 | 
					 | 
				
			||||||
                    :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
 | 
					                    :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
 | 
				
			||||||
                    target="_blank"
 | 
					                    target="_blank"
 | 
				
			||||||
                    class="btn flex-col"
 | 
					                    class="btn flex-col"
 | 
				
			||||||
| 
						 | 
					@ -44,15 +51,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <CollapsableText :text="channel.description" />
 | 
					        <CollapsableText :text="channel.description" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <WatchOnButton :link="`https://youtube.com/channel/${this.channel.id}`" />
 | 
					        <WatchOnButton :link="`https://youtube.com/channel/${channel.id}`" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="flex my-2 mx-1">
 | 
					        <div class="mx-1 my-2 flex">
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
                v-for="(tab, index) in tabs"
 | 
					                v-for="(tab, index) in tabs"
 | 
				
			||||||
                :key="tab.name"
 | 
					                :key="tab.name"
 | 
				
			||||||
                class="btn mr-2"
 | 
					                class="btn mr-2"
 | 
				
			||||||
                @click="loadTab(index)"
 | 
					 | 
				
			||||||
                :class="{ active: selectedTab == index }"
 | 
					                :class="{ active: selectedTab == index }"
 | 
				
			||||||
 | 
					                @click="loadTab(index)"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <span v-text="tab.translatedName"></span>
 | 
					                <span v-text="tab.translatedName"></span>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
| 
						 | 
					@ -70,6 +77,8 @@
 | 
				
			||||||
                hide-channel
 | 
					                hide-channel
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <AddToGroupModal v-if="showGroupModal" :channel-id="channel.id.substr(-11)" @close="showGroupModal = false" />
 | 
				
			||||||
    </LoadingIndicatorPage>
 | 
					    </LoadingIndicatorPage>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,6 +88,7 @@ import ContentItem from "./ContentItem.vue";
 | 
				
			||||||
import WatchOnButton from "./WatchOnButton.vue";
 | 
					import WatchOnButton from "./WatchOnButton.vue";
 | 
				
			||||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
					import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
				
			||||||
import CollapsableText from "./CollapsableText.vue";
 | 
					import CollapsableText from "./CollapsableText.vue";
 | 
				
			||||||
 | 
					import AddToGroupModal from "./AddToGroupModal.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    components: {
 | 
					    components: {
 | 
				
			||||||
| 
						 | 
					@ -87,6 +97,7 @@ export default {
 | 
				
			||||||
        WatchOnButton,
 | 
					        WatchOnButton,
 | 
				
			||||||
        LoadingIndicatorPage,
 | 
					        LoadingIndicatorPage,
 | 
				
			||||||
        CollapsableText,
 | 
					        CollapsableText,
 | 
				
			||||||
 | 
					        AddToGroupModal,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
| 
						 | 
					@ -95,6 +106,7 @@ export default {
 | 
				
			||||||
            tabs: [],
 | 
					            tabs: [],
 | 
				
			||||||
            selectedTab: 0,
 | 
					            selectedTab: 0,
 | 
				
			||||||
            contentItems: [],
 | 
					            contentItems: [],
 | 
				
			||||||
 | 
					            showGroupModal: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
| 
						 | 
					@ -148,6 +160,7 @@ export default {
 | 
				
			||||||
                        this.contentItems = this.channel.relatedStreams;
 | 
					                        this.contentItems = this.channel.relatedStreams;
 | 
				
			||||||
                        this.fetchSubscribedStatus();
 | 
					                        this.fetchSubscribedStatus();
 | 
				
			||||||
                        this.updateWatched(this.channel.relatedStreams);
 | 
					                        this.updateWatched(this.channel.relatedStreams);
 | 
				
			||||||
 | 
					                        this.fetchDeArrowContent(this.channel.relatedStreams);
 | 
				
			||||||
                        this.tabs.push({
 | 
					                        this.tabs.push({
 | 
				
			||||||
                            translatedName: this.$t("video.videos"),
 | 
					                            translatedName: this.$t("video.videos"),
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
| 
						 | 
					@ -186,6 +199,7 @@ export default {
 | 
				
			||||||
                this.loading = false;
 | 
					                this.loading = false;
 | 
				
			||||||
                this.updateWatched(json.relatedStreams);
 | 
					                this.updateWatched(json.relatedStreams);
 | 
				
			||||||
                json.relatedStreams.map(stream => this.contentItems.push(stream));
 | 
					                json.relatedStreams.map(stream => this.contentItems.push(stream));
 | 
				
			||||||
 | 
					                this.fetchDeArrowContent(this.contentItems);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        fetchChannelTabNextPage() {
 | 
					        fetchChannelTabNextPage() {
 | 
				
			||||||
| 
						 | 
					@ -196,6 +210,7 @@ export default {
 | 
				
			||||||
                this.tabs[this.selectedTab].tabNextPage = json.nextpage;
 | 
					                this.tabs[this.selectedTab].tabNextPage = json.nextpage;
 | 
				
			||||||
                this.loading = false;
 | 
					                this.loading = false;
 | 
				
			||||||
                json.content.map(item => this.contentItems.push(item));
 | 
					                json.content.map(item => this.contentItems.push(item));
 | 
				
			||||||
 | 
					                this.fetchDeArrowContent(this.contentItems);
 | 
				
			||||||
                this.tabs[this.selectedTab].content = this.contentItems;
 | 
					                this.tabs[this.selectedTab].content = this.contentItems;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -258,6 +273,7 @@ export default {
 | 
				
			||||||
                data: this.tabs[index].data,
 | 
					                data: this.tabs[index].data,
 | 
				
			||||||
            }).then(tab => {
 | 
					            }).then(tab => {
 | 
				
			||||||
                this.contentItems = this.tabs[index].content = tab.content;
 | 
					                this.contentItems = this.tabs[index].content = tab.content;
 | 
				
			||||||
 | 
					                this.fetchDeArrowContent(this.contentItems);
 | 
				
			||||||
                this.tabs[this.selectedTab].tabNextPage = tab.nextpage;
 | 
					                this.tabs[this.selectedTab].tabNextPage = tab.nextpage;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,20 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <!-- desktop view -->
 | 
					    <!-- desktop view -->
 | 
				
			||||||
    <div v-if="!mobileLayout" class="flex-col overflow-y-scroll max-w-35vw max-h-75vh min-h-64 lt-lg:hidden">
 | 
					    <div v-if="!mobileLayout" class="max-h-75vh max-w-35vw min-h-64 flex-col overflow-y-scroll lt-lg:hidden">
 | 
				
			||||||
        <h2 class="mb-2 bg-gray-500/50 p-2" aria-label="chapters" title="chapters">
 | 
					        <h2 class="mb-2 bg-gray-500/50 p-2" aria-label="chapters" title="chapters">
 | 
				
			||||||
            {{ $t("video.chapters") }} ({{ chapters.length }})
 | 
					            {{ $t("video.chapters") }} ({{ chapters.length }})
 | 
				
			||||||
        </h2>
 | 
					        </h2>
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
            :key="chapter.start"
 | 
					 | 
				
			||||||
            v-for="(chapter, index) in chapters"
 | 
					            v-for="(chapter, index) in chapters"
 | 
				
			||||||
            @click="$emit('seek', chapter.start)"
 | 
					            :key="chapter.start"
 | 
				
			||||||
            class="chapter-vertical"
 | 
					            class="chapter-vertical"
 | 
				
			||||||
            :class="{ 'bg-red-500/50': isCurrentChapter(index) }"
 | 
					            :class="{ 'bg-red-500/50': isCurrentChapter(index) }"
 | 
				
			||||||
 | 
					            @click="$emit('seek', chapter.start)"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <div class="flex">
 | 
					            <div class="flex">
 | 
				
			||||||
                <span class="mt-5 mr-2 text-current" v-text="index + 1" />
 | 
					                <span class="mr-2 mt-5 text-current" v-text="index + 1" />
 | 
				
			||||||
                <img class="shrink-0" :src="chapter.image" :alt="chapter.title" />
 | 
					                <img class="shrink-0" :src="chapter.image" :alt="chapter.title" />
 | 
				
			||||||
                <div class="flex flex-col m-2">
 | 
					                <div class="m-2 flex flex-col">
 | 
				
			||||||
                    <span class="text-sm" :title="chapter.title" v-text="chapter.title" />
 | 
					                    <span class="text-sm" :title="chapter.title" v-text="chapter.title" />
 | 
				
			||||||
                    <span class="text-sm font-bold text-blue-500" v-text="timeFormat(chapter.start)" />
 | 
					                    <span class="text-sm font-bold text-blue-500" v-text="timeFormat(chapter.start)" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					@ -25,22 +25,22 @@
 | 
				
			||||||
    <!-- mobile vertical view -->
 | 
					    <!-- mobile vertical view -->
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
        v-if="mobileLayout && getPreferenceString('mobileChapterLayout') == 'Vertical'"
 | 
					        v-if="mobileLayout && getPreferenceString('mobileChapterLayout') == 'Vertical'"
 | 
				
			||||||
        class="flex flex-col overflow-y-scroll max-h-64"
 | 
					        class="max-h-64 flex flex-col overflow-y-scroll"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        <h2 class="mb-2 bg-gray-500/50 p-2" aria-label="chapters" title="chapters">
 | 
					        <h2 class="mb-2 bg-gray-500/50 p-2" aria-label="chapters" title="chapters">
 | 
				
			||||||
            {{ $t("video.chapters") }} ({{ chapters.length }})
 | 
					            {{ $t("video.chapters") }} ({{ chapters.length }})
 | 
				
			||||||
        </h2>
 | 
					        </h2>
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
            :key="chapter.start"
 | 
					 | 
				
			||||||
            v-for="(chapter, index) in chapters"
 | 
					            v-for="(chapter, index) in chapters"
 | 
				
			||||||
            @click="$emit('seek', chapter.start)"
 | 
					            :key="chapter.start"
 | 
				
			||||||
            class="chapter-vertical"
 | 
					            class="chapter-vertical"
 | 
				
			||||||
            :class="{ 'bg-red-500/50': isCurrentChapter(index) }"
 | 
					            :class="{ 'bg-red-500/50': isCurrentChapter(index) }"
 | 
				
			||||||
 | 
					            @click="$emit('seek', chapter.start)"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <div class="flex">
 | 
					            <div class="flex">
 | 
				
			||||||
                <span class="mt-5 mr-2 text-current" v-text="index + 1" />
 | 
					                <span class="mr-2 mt-5 text-current" v-text="index + 1" />
 | 
				
			||||||
                <img class="shrink-0" :src="chapter.image" :alt="chapter.title" />
 | 
					                <img class="shrink-0" :src="chapter.image" :alt="chapter.title" />
 | 
				
			||||||
                <div class="flex flex-col m-2">
 | 
					                <div class="m-2 flex flex-col">
 | 
				
			||||||
                    <span class="text-sm" :title="chapter.title" v-text="chapter.title" />
 | 
					                    <span class="text-sm" :title="chapter.title" v-text="chapter.title" />
 | 
				
			||||||
                    <span class="text-sm font-bold text-blue-500" v-text="timeFormat(chapter.start)" />
 | 
					                    <span class="text-sm font-bold text-blue-500" v-text="timeFormat(chapter.start)" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					@ -50,11 +50,11 @@
 | 
				
			||||||
    <!-- mobile Horizontal view -->
 | 
					    <!-- mobile Horizontal view -->
 | 
				
			||||||
    <div v-if="getPreferenceString('mobileChapterLayout') == 'Horizontal' && mobileLayout" class="flex overflow-x-auto">
 | 
					    <div v-if="getPreferenceString('mobileChapterLayout') == 'Horizontal' && mobileLayout" class="flex overflow-x-auto">
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
            :key="chapter.start"
 | 
					 | 
				
			||||||
            v-for="(chapter, index) in chapters"
 | 
					            v-for="(chapter, index) in chapters"
 | 
				
			||||||
            @click="$emit('seek', chapter.start)"
 | 
					            :key="chapter.start"
 | 
				
			||||||
            class="chapter"
 | 
					            class="chapter"
 | 
				
			||||||
            :class="{ 'bg-red-500/50': isCurrentChapter(index) }"
 | 
					            :class="{ 'bg-red-500/50': isCurrentChapter(index) }"
 | 
				
			||||||
 | 
					            @click="$emit('seek', chapter.start)"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <img :src="chapter.image" :alt="chapter.title" />
 | 
					            <img :src="chapter.image" :alt="chapter.title" />
 | 
				
			||||||
            <div class="m-1 flex">
 | 
					            <div class="m-1 flex">
 | 
				
			||||||
| 
						 | 
					@ -65,6 +65,32 @@
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup>
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					    chapters: {
 | 
				
			||||||
 | 
					        type: Object,
 | 
				
			||||||
 | 
					        default: () => null,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mobileLayout: {
 | 
				
			||||||
 | 
					        type: Boolean,
 | 
				
			||||||
 | 
					        default: () => true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    playerPosition: {
 | 
				
			||||||
 | 
					        type: Number,
 | 
				
			||||||
 | 
					        default: () => 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isCurrentChapter = index => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        props.playerPosition >= props.chapters[index].start &&
 | 
				
			||||||
 | 
					        props.playerPosition < (props.chapters[index + 1]?.start ?? Infinity)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineEmits(["seek"]);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
::-webkit-scrollbar {
 | 
					::-webkit-scrollbar {
 | 
				
			||||||
    height: 5px;
 | 
					    height: 5px;
 | 
				
			||||||
| 
						 | 
					@ -89,26 +115,3 @@
 | 
				
			||||||
    @apply truncate overflow-hidden inline-block w-10em;
 | 
					    @apply truncate overflow-hidden inline-block w-10em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup>
 | 
					 | 
				
			||||||
const props = defineProps({
 | 
					 | 
				
			||||||
    chapters: Object,
 | 
					 | 
				
			||||||
    mobileLayout: {
 | 
					 | 
				
			||||||
        type: Boolean,
 | 
					 | 
				
			||||||
        default: () => true,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    playerPosition: {
 | 
					 | 
				
			||||||
        type: Number,
 | 
					 | 
				
			||||||
        default: () => 0,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const isCurrentChapter = index => {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        props.playerPosition >= props.chapters[index].start &&
 | 
					 | 
				
			||||||
        props.playerPosition < (props.chapters[index + 1]?.start ?? Infinity)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineEmits(["seek"]);
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,13 @@
 | 
				
			||||||
<template>
 | 
					<template v-if="text">
 | 
				
			||||||
    <!-- eslint-disable-next-line vue/no-v-html -->
 | 
					    <div class="mx-1 whitespace-pre-wrap py-2">
 | 
				
			||||||
    <div v-if="text" class="whitespace-pre-wrap py-2 mx-1">
 | 
					        <!-- eslint-disable-next-line vue/no-v-html -->
 | 
				
			||||||
        <span v-if="showFullText" v-html="purifyHTML(rewriteDescription(text))" />
 | 
					        <span v-if="showFullText" v-html="fullText()" />
 | 
				
			||||||
        <span v-else v-html="purifyHTML(rewriteDescription(text.slice(0, 100)))" />
 | 
					        <!-- eslint-disable-next-line vue/no-v-html -->
 | 
				
			||||||
        <span v-if="text.length > 100 && !showFullText">...</span>
 | 
					        <span v-else v-html="colapsedText()" />
 | 
				
			||||||
 | 
					        <span v-if="text.length > visibleLimit && !showFullText">...</span>
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
            v-if="text.length > 100"
 | 
					            v-if="text.length > visibleLimit"
 | 
				
			||||||
            class="hover:underline font-semibold text-neutral-500 block whitespace-normal"
 | 
					            class="block whitespace-normal font-semibold text-neutral-500 hover:underline"
 | 
				
			||||||
            @click="showFullText = !showFullText"
 | 
					            @click="showFullText = !showFullText"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            [{{ showFullText ? $t("actions.show_less") : $t("actions.show_more") }}]
 | 
					            [{{ showFullText ? $t("actions.show_less") : $t("actions.show_more") }}]
 | 
				
			||||||
| 
						 | 
					@ -15,14 +16,31 @@
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
 | 
					import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
        text: String,
 | 
					        text: {
 | 
				
			||||||
 | 
					            type: String,
 | 
				
			||||||
 | 
					            default: null,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        visibleLimit: {
 | 
				
			||||||
 | 
					            type: Number,
 | 
				
			||||||
 | 
					            default: 100,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            showFullText: false,
 | 
					            showFullText: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					        fullText() {
 | 
				
			||||||
 | 
					            return purifyHTML(rewriteDescription(this.text));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        colapsedText() {
 | 
				
			||||||
 | 
					            return purifyHTML(rewriteDescription(this.text.slice(0, this.visibleLimit)));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="comment flex mt-1.5">
 | 
					    <div class="comment mt-1.5 flex">
 | 
				
			||||||
        <img
 | 
					        <img
 | 
				
			||||||
            :src="comment.thumbnail"
 | 
					            :src="comment.thumbnail"
 | 
				
			||||||
            class="comment-avatar rounded-full w-12 h-12"
 | 
					            class="comment-avatar h-12 w-12 rounded-full"
 | 
				
			||||||
            height="48"
 | 
					            height="48"
 | 
				
			||||||
            width="48"
 | 
					            width="48"
 | 
				
			||||||
            loading="lazy"
 | 
					            loading="lazy"
 | 
				
			||||||
| 
						 | 
					@ -14,34 +14,35 @@
 | 
				
			||||||
                <div v-if="comment.pinned" class="comment-pinned">
 | 
					                <div v-if="comment.pinned" class="comment-pinned">
 | 
				
			||||||
                    <font-awesome-icon icon="thumbtack" />
 | 
					                    <font-awesome-icon icon="thumbtack" />
 | 
				
			||||||
                    <span
 | 
					                    <span
 | 
				
			||||||
                        class="ml-1.5"
 | 
					 | 
				
			||||||
                        v-t="{
 | 
					                        v-t="{
 | 
				
			||||||
                            path: 'comment.pinned_by',
 | 
					                            path: 'comment.pinned_by',
 | 
				
			||||||
                            args: { author: uploader },
 | 
					                            args: { author: uploader },
 | 
				
			||||||
                        }"
 | 
					                        }"
 | 
				
			||||||
 | 
					                        class="ml-1.5"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="comment-author">
 | 
					                <div class="comment-author">
 | 
				
			||||||
                    <router-link class="font-bold link" :to="comment.commentorUrl">{{ comment.author }}</router-link>
 | 
					                    <router-link class="link font-bold" :to="comment.commentorUrl">{{ comment.author }}</router-link>
 | 
				
			||||||
                    <font-awesome-icon class="ml-1.5" v-if="comment.verified" icon="check" />
 | 
					                    <font-awesome-icon v-if="comment.verified" class="ml-1.5" icon="check" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="comment-meta text-sm mb-1.5" v-text="comment.commentedTime" />
 | 
					                <div class="comment-meta mb-1.5 text-sm" v-text="comment.commentedTime" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="whitespace-pre-wrap" v-html="purifyHTML(comment.commentText)" />
 | 
					            <!-- eslint-disable-next-line vue/no-v-html -->
 | 
				
			||||||
 | 
					            <CollapsableText :text="comment.commentText" :visible-limit="500" />
 | 
				
			||||||
            <div class="comment-footer mt-1 flex items-center">
 | 
					            <div class="comment-footer mt-1 flex items-center">
 | 
				
			||||||
                <div class="i-fa6-solid:thumbs-up" />
 | 
					                <div class="i-fa6-solid:thumbs-up" />
 | 
				
			||||||
                <span class="ml-1" v-text="numberFormat(comment.likeCount)" />
 | 
					                <span class="ml-1" v-text="numberFormat(comment.likeCount)" />
 | 
				
			||||||
                <font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
 | 
					                <font-awesome-icon v-if="comment.hearted" class="ml-1" icon="heart" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
 | 
					            <template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
 | 
				
			||||||
                <div @click="loadReplies" class="cursor-pointer">
 | 
					                <div class="cursor-pointer" @click="loadReplies">
 | 
				
			||||||
                    <a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
 | 
					                    <a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
 | 
				
			||||||
                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
					                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
            <template v-if="showingReplies">
 | 
					            <template v-if="showingReplies">
 | 
				
			||||||
                <div @click="hideReplies" class="cursor-pointer">
 | 
					                <div class="cursor-pointer" @click="hideReplies">
 | 
				
			||||||
                    <a v-t="'actions.hide_replies'" />
 | 
					                    <a v-t="'actions.hide_replies'" />
 | 
				
			||||||
                    <font-awesome-icon class="ml-1.5" icon="level-up-alt" />
 | 
					                    <font-awesome-icon class="ml-1.5" icon="level-up-alt" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					@ -50,7 +51,7 @@
 | 
				
			||||||
                <div v-for="reply in replies" :key="reply.commentId" class="w-full">
 | 
					                <div v-for="reply in replies" :key="reply.commentId" class="w-full">
 | 
				
			||||||
                    <CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
 | 
					                    <CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div v-if="nextpage" @click="loadReplies" class="cursor-pointer">
 | 
					                <div v-if="nextpage" class="cursor-pointer" @click="loadReplies">
 | 
				
			||||||
                    <a v-t="'actions.load_more_replies'" />
 | 
					                    <a v-t="'actions.load_more_replies'" />
 | 
				
			||||||
                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
					                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					@ -60,7 +61,10 @@
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
 | 
					import CollapsableText from "./CollapsableText.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: { CollapsableText },
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
        comment: {
 | 
					        comment: {
 | 
				
			||||||
            type: Object,
 | 
					            type: Object,
 | 
				
			||||||
| 
						 | 
					@ -85,7 +89,6 @@ export default {
 | 
				
			||||||
                this.showingReplies = true;
 | 
					                this.showingReplies = true;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.loadingReplies = true;
 | 
					            this.loadingReplies = true;
 | 
				
			||||||
            this.showingReplies = true;
 | 
					            this.showingReplies = true;
 | 
				
			||||||
            this.fetchJson(this.apiUrl() + "/nextpage/comments/" + this.videoId, {
 | 
					            this.fetchJson(this.apiUrl() + "/nextpage/comments/" + this.videoId, {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,9 +2,9 @@
 | 
				
			||||||
    <ModalComponent @close="$emit('close')">
 | 
					    <ModalComponent @close="$emit('close')">
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
            <h3 class="text-xl" v-text="message" />
 | 
					            <h3 class="text-xl" v-text="message" />
 | 
				
			||||||
            <div class="ml-auto mt-8 flex gap-2 w-min">
 | 
					            <div class="ml-auto mt-8 w-min flex gap-2">
 | 
				
			||||||
                <button class="btn" v-t="'actions.cancel'" @click="$emit('close')" />
 | 
					                <button v-t="'actions.cancel'" class="btn" @click="$emit('close')" />
 | 
				
			||||||
                <button class="btn" v-t="'actions.okay'" @click="$emit('confirm')" />
 | 
					                <button v-t="'actions.okay'" class="btn" @click="$emit('confirm')" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </ModalComponent>
 | 
					    </ModalComponent>
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,10 @@ export default {
 | 
				
			||||||
        ModalComponent,
 | 
					        ModalComponent,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
        message: String,
 | 
					        message: {
 | 
				
			||||||
 | 
					            type: String,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    emits: ["close", "confirm"],
 | 
					    emits: ["close", "confirm"],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,10 @@
 | 
				
			||||||
import { defineAsyncComponent } from "vue";
 | 
					import { defineAsyncComponent } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    item: Object,
 | 
					    item: {
 | 
				
			||||||
 | 
					        type: Object,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));
 | 
					const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <p v-text="message" />
 | 
					    <p v-text="message" />
 | 
				
			||||||
    <button @click="toggleTrace" class="btn" v-t="'actions.show_more'" />
 | 
					    <button v-t="'actions.show_more'" class="btn" @click="toggleTrace" />
 | 
				
			||||||
    <p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
 | 
					    <p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 v-t="'titles.feed'" class="font-bold text-center my-4" />
 | 
					    <h1 v-t="'titles.feed'" class="my-4 text-center font-bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="flex flex-wrap md:items-center flex-col md:flex-row gap-2 children:(flex gap-1 items-center)">
 | 
					    <div class="flex flex-col flex-wrap gap-2 children:(flex items-center gap-1) md:flex-row md:items-center">
 | 
				
			||||||
        <span>
 | 
					        <span>
 | 
				
			||||||
            <label for="filters">
 | 
					            <label for="filters">
 | 
				
			||||||
                <strong v-text="`${$t('actions.filter')}:`" />
 | 
					                <strong v-text="`${$t('actions.filter')}:`" />
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@
 | 
				
			||||||
                class="select flex-grow"
 | 
					                class="select flex-grow"
 | 
				
			||||||
                @change="onFilterChange()"
 | 
					                @change="onFilterChange()"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`video.${filter}`" />
 | 
					                <option v-for="filter in availableFilters" :key="filter" v-t="`video.${filter}`" :value="filter" />
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@
 | 
				
			||||||
                <strong v-text="`${$t('titles.channel_groups')}:`" />
 | 
					                <strong v-text="`${$t('titles.channel_groups')}:`" />
 | 
				
			||||||
            </label>
 | 
					            </label>
 | 
				
			||||||
            <select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
 | 
					            <select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
 | 
				
			||||||
                <option value="" v-t="`video.all`" />
 | 
					                <option v-t="`video.all`" value="" />
 | 
				
			||||||
                <option
 | 
					                <option
 | 
				
			||||||
                    v-for="group in channelGroups"
 | 
					                    v-for="group in channelGroups"
 | 
				
			||||||
                    :key="group.groupName"
 | 
					                    :key="group.groupName"
 | 
				
			||||||
| 
						 | 
					@ -99,18 +99,7 @@ export default {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!window.db) return;
 | 
					        if (!window.db) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const cursor = this.getChannelGroupsCursor();
 | 
					        this.loadChannelGroups();
 | 
				
			||||||
        cursor.onsuccess = e => {
 | 
					 | 
				
			||||||
            const cursor = e.target.result;
 | 
					 | 
				
			||||||
            if (cursor) {
 | 
					 | 
				
			||||||
                const group = cursor.value;
 | 
					 | 
				
			||||||
                this.channelGroups.push({
 | 
					 | 
				
			||||||
                    groupName: group.groupName,
 | 
					 | 
				
			||||||
                    channels: JSON.parse(group.channels),
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                cursor.continue();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
        document.title = this.$t("titles.feed") + " - Piped";
 | 
					        document.title = this.$t("titles.feed") + " - Piped";
 | 
				
			||||||
| 
						 | 
					@ -135,10 +124,17 @@ export default {
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        async loadChannelGroups() {
 | 
				
			||||||
 | 
					            const groups = await this.getChannelGroups();
 | 
				
			||||||
 | 
					            this.channelGroups.push(...groups);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        loadMoreVideos() {
 | 
					        loadMoreVideos() {
 | 
				
			||||||
 | 
					            if (!this.videosStore) return;
 | 
				
			||||||
            this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
 | 
					            this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
 | 
				
			||||||
            if (this.videos.length != this.videosStore.length)
 | 
					            if (this.videos.length != this.videosStore.length) {
 | 
				
			||||||
                this.videos = this.videosStore.slice(0, this.currentVideoCount);
 | 
					                this.videos = this.videosStore.slice(0, this.currentVideoCount);
 | 
				
			||||||
 | 
					                this.fetchDeArrowContent(this.videos);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        handleScroll() {
 | 
					        handleScroll() {
 | 
				
			||||||
            if (window.innerHeight + window.scrollY >= document.body.offsetHeight - window.innerHeight) {
 | 
					            if (window.innerHeight + window.scrollY >= document.body.offsetHeight - window.innerHeight) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,28 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <footer class="text-center py-4 rounded-xl children:(mx-3) w-full mt-10">
 | 
					    <footer class="mt-10 w-full rounded-xl py-4 text-center children:(mx-3)">
 | 
				
			||||||
        <a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
 | 
					        <a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
 | 
				
			||||||
            <font-awesome-icon :icon="['fab', 'github']" />
 | 
					            <font-awesome-icon :icon="['fab', 'github']" />
 | 
				
			||||||
            <span class="ml-2" v-t="'actions.source_code'" />
 | 
					            <span v-t="'actions.source_code'" class="ml-2" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <a href="https://docs.piped.video/" target="_blank">
 | 
					        <a href="https://docs.piped.video/" target="_blank">
 | 
				
			||||||
            <font-awesome-icon :icon="['fa', 'book']" />
 | 
					            <font-awesome-icon :icon="['fa', 'book']" />
 | 
				
			||||||
            <span class="ml-2" v-t="'actions.documentation'" />
 | 
					            <span v-t="'actions.documentation'" class="ml-2" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <a href="https://github.com/TeamPiped/Piped#donations" target="_blank">
 | 
					        <a href="https://github.com/TeamPiped/Piped#donations" target="_blank">
 | 
				
			||||||
            <font-awesome-icon :icon="['fab', 'bitcoin']" />
 | 
					            <font-awesome-icon :icon="['fab', 'bitcoin']" />
 | 
				
			||||||
            <span class="ml-2" v-t="'actions.donations'" />
 | 
					            <span v-t="'actions.donations'" class="ml-2" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <a v-if="statusPageHref" :href="statusPageHref">
 | 
					        <a v-if="statusPageHref" :href="statusPageHref">
 | 
				
			||||||
            <font-awesome-icon :icon="['fa', 'server']" />
 | 
					            <font-awesome-icon :icon="['fa', 'server']" />
 | 
				
			||||||
            <span class="ml-2" v-t="'actions.status_page'" />
 | 
					            <span v-t="'actions.status_page'" class="ml-2" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <a v-if="donationHref" :href="donationHref">
 | 
					        <a v-if="donationHref" :href="donationHref">
 | 
				
			||||||
            <font-awesome-icon :icon="['fa', 'donate']" />
 | 
					            <font-awesome-icon :icon="['fa', 'donate']" />
 | 
				
			||||||
            <span class="ml-2" v-t="'actions.instance_donations'" />
 | 
					            <span v-t="'actions.instance_donations'" class="ml-2" />
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <a v-if="privacyPolicyHref" :href="privacyPolicyHref" target="_blank">
 | 
				
			||||||
 | 
					            <font-awesome-icon :icon="['fa', 'eye']" />
 | 
				
			||||||
 | 
					            <span v-t="'actions.instance_privacy_policy'" class="ml-2" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
    </footer>
 | 
					    </footer>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -29,6 +33,7 @@ export default {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            donationHref: null,
 | 
					            donationHref: null,
 | 
				
			||||||
            statusPageHref: null,
 | 
					            statusPageHref: null,
 | 
				
			||||||
 | 
					            privacyPolicyHref: null,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
| 
						 | 
					@ -39,6 +44,7 @@ export default {
 | 
				
			||||||
            this.fetchJson(this.apiUrl() + "/config").then(config => {
 | 
					            this.fetchJson(this.apiUrl() + "/config").then(config => {
 | 
				
			||||||
                this.donationHref = config?.donationUrl;
 | 
					                this.donationHref = config?.donationUrl;
 | 
				
			||||||
                this.statusPageHref = config?.statusPageUrl;
 | 
					                this.statusPageHref = config?.statusPageUrl;
 | 
				
			||||||
 | 
					                this.privacyPolicyHref = config?.privacyPolicyUrl;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,32 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 class="font-bold text-center" v-t="'titles.history'" />
 | 
					    <h1 v-t="'titles.history'" class="mb-3 text-center font-bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="flex md:items-center gap-2 flex-col md:flex-row">
 | 
					    <div class="flex">
 | 
				
			||||||
        <button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
 | 
					        <div class="flex flex-col gap-2 md:flex-row md:items-center">
 | 
				
			||||||
 | 
					            <button v-t="'actions.clear_history'" class="btn" @click="clearHistory" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <button class="btn" v-t="'actions.export_to_json'" @click="exportHistory" />
 | 
					            <button v-t="'actions.export_to_json'" class="btn" @click="exportHistory" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="ml-auto flex gap-1 items-center">
 | 
					            <div class="ml-auto flex items-center gap-1">
 | 
				
			||||||
            <SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
 | 
					                <SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="ml-4 flex items-center">
 | 
				
			||||||
 | 
					            <input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
 | 
				
			||||||
 | 
					            <label v-t="'actions.delete_automatically'" class="ml-2" for="autoDelete" />
 | 
				
			||||||
 | 
					            <select v-model="autoDeleteDelayHours" class="select ml-3 pl-3" @change="onChange">
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.hours', args: { amount: '12' } }" value="12" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.days', args: { amount: '1' } }" value="24" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.days', args: { amount: '3' } }" value="72" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.weeks', args: { amount: '1' } }" value="168" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.weeks', args: { amount: '3' } }" value="336" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.months', args: { amount: '1' } }" value="672" />
 | 
				
			||||||
 | 
					                <option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,30 +51,39 @@ export default {
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            videos: [],
 | 
					            videos: [],
 | 
				
			||||||
 | 
					            autoDeleteHistory: false,
 | 
				
			||||||
 | 
					            autoDeleteDelayHours: "24",
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
 | 
					        this.autoDeleteHistory = this.getPreferenceBoolean("autoDeleteWatchHistory", false);
 | 
				
			||||||
 | 
					        this.autoDeleteDelayHours = this.getPreferenceString("autoDeleteWatchHistoryDelayHours", "24");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (async () => {
 | 
					        (async () => {
 | 
				
			||||||
            if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
					            if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
				
			||||||
                var tx = window.db.transaction("watch_history", "readonly");
 | 
					                var tx = window.db.transaction("watch_history", "readwrite");
 | 
				
			||||||
                var store = tx.objectStore("watch_history");
 | 
					                var store = tx.objectStore("watch_history");
 | 
				
			||||||
                const cursorRequest = store.index("watchedAt").openCursor(null, "prev");
 | 
					                const cursorRequest = store.index("watchedAt").openCursor(null, "prev");
 | 
				
			||||||
                cursorRequest.onsuccess = e => {
 | 
					                cursorRequest.onsuccess = e => {
 | 
				
			||||||
                    const cursor = e.target.result;
 | 
					                    const cursor = e.target.result;
 | 
				
			||||||
                    if (cursor) {
 | 
					                    if (cursor) {
 | 
				
			||||||
                        const video = cursor.value;
 | 
					                        const video = cursor.value;
 | 
				
			||||||
                        this.videos.push({
 | 
					 | 
				
			||||||
                            url: "/watch?v=" + video.videoId,
 | 
					 | 
				
			||||||
                            title: video.title,
 | 
					 | 
				
			||||||
                            uploaderName: video.uploaderName,
 | 
					 | 
				
			||||||
                            uploaderUrl: video.uploaderUrl,
 | 
					 | 
				
			||||||
                            duration: video.duration,
 | 
					 | 
				
			||||||
                            thumbnail: video.thumbnail,
 | 
					 | 
				
			||||||
                            watchedAt: video.watchedAt,
 | 
					 | 
				
			||||||
                            watched: true,
 | 
					 | 
				
			||||||
                            currentTime: video.currentTime,
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                        if (this.videos.length < 1000) cursor.continue();
 | 
					                        if (this.videos.length < 1000) cursor.continue();
 | 
				
			||||||
 | 
					                        if (!this.shouldRemoveVideo(video)) {
 | 
				
			||||||
 | 
					                            this.videos.push({
 | 
				
			||||||
 | 
					                                url: "/watch?v=" + video.videoId,
 | 
				
			||||||
 | 
					                                title: video.title,
 | 
				
			||||||
 | 
					                                uploaderName: video.uploaderName,
 | 
				
			||||||
 | 
					                                uploaderUrl: video.uploaderUrl,
 | 
				
			||||||
 | 
					                                duration: video.duration,
 | 
				
			||||||
 | 
					                                thumbnail: video.thumbnail,
 | 
				
			||||||
 | 
					                                watchedAt: video.watchedAt,
 | 
				
			||||||
 | 
					                                watched: true,
 | 
				
			||||||
 | 
					                                currentTime: video.currentTime,
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            store.delete(video.videoId);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -89,6 +117,16 @@ export default {
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            this.download(JSON.stringify(json), `piped_history_${dateStr}.json`, "application/json");
 | 
					            this.download(JSON.stringify(json), `piped_history_${dateStr}.json`, "application/json");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        onChange() {
 | 
				
			||||||
 | 
					            this.setPreference("autoDeleteWatchHistory", this.autoDeleteHistory);
 | 
				
			||||||
 | 
					            this.setPreference("autoDeleteWatchHistoryDelayHours", this.autoDeleteDelayHours);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        shouldRemoveVideo(video) {
 | 
				
			||||||
 | 
					            if (!this.autoDeleteHistory) return false;
 | 
				
			||||||
 | 
					            // convert from hours to milliseconds
 | 
				
			||||||
 | 
					            let maximumTimeDiff = Number(this.autoDeleteDelayHours) * 60 * 60 * 1000;
 | 
				
			||||||
 | 
					            return Date.now() - video.watchedAt > maximumTimeDiff;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,10 +105,14 @@ export default {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // FreeTube DB
 | 
					                // FreeTube DB
 | 
				
			||||||
                else if (text.indexOf("allChannels") != -1) {
 | 
					                else if (text.indexOf("allChannels") != -1) {
 | 
				
			||||||
                    const json = JSON.parse(text);
 | 
					                    const lines = text.split("\n");
 | 
				
			||||||
                    json.subscriptions.forEach(item => {
 | 
					                    for (let line of lines) {
 | 
				
			||||||
                        this.subscriptions.push(item.id);
 | 
					                        if (line === "") continue;
 | 
				
			||||||
                    });
 | 
					                        const json = JSON.parse(line);
 | 
				
			||||||
 | 
					                        json.subscriptions.forEach(item => {
 | 
				
			||||||
 | 
					                            this.subscriptions.push(item.id);
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // Google Takeout JSON
 | 
					                // Google Takeout JSON
 | 
				
			||||||
                else if (text.indexOf("contentDetails") != -1) {
 | 
					                else if (text.indexOf("contentDetails") != -1) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div v-if="!showContent" class="flex min-h-[75vh] w-full justify-center items-center">
 | 
					    <div v-if="!showContent" class="min-h-[75vh] w-full flex items-center justify-center">
 | 
				
			||||||
        <span id="spinner" />
 | 
					        <span id="spinner" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div v-else>
 | 
					    <div v-else>
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,17 @@
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					        showContent: {
 | 
				
			||||||
 | 
					            type: Boolean,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
#spinner:after {
 | 
					#spinner:after {
 | 
				
			||||||
    --spinner-color: #000;
 | 
					    --spinner-color: #000;
 | 
				
			||||||
| 
						 | 
					@ -42,14 +53,3 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
    props: {
 | 
					 | 
				
			||||||
        showContent: {
 | 
					 | 
				
			||||||
            type: Boolean,
 | 
					 | 
				
			||||||
            required: true,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 v-t="'titles.login'" class="font-bold text-center my-4" />
 | 
					    <h1 v-t="'titles.login'" class="my-4 text-center font-bold" />
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
    <div class="text-center">
 | 
					    <div class="w-full flex items-center justify-center text-center">
 | 
				
			||||||
        <form class="children:pb-3">
 | 
					        <form class="w-min children:pb-3">
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                    v-model="username"
 | 
					                    v-model="username"
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@
 | 
				
			||||||
                    autocomplete="username"
 | 
					                    autocomplete="username"
 | 
				
			||||||
                    :placeholder="$t('login.username')"
 | 
					                    :placeholder="$t('login.username')"
 | 
				
			||||||
                    :aria-label="$t('login.username')"
 | 
					                    :aria-label="$t('login.username')"
 | 
				
			||||||
                    v-on:keyup.enter="login"
 | 
					                    @keyup.enter="login"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
| 
						 | 
					@ -22,23 +22,29 @@
 | 
				
			||||||
                    autocomplete="password"
 | 
					                    autocomplete="password"
 | 
				
			||||||
                    :placeholder="$t('login.password')"
 | 
					                    :placeholder="$t('login.password')"
 | 
				
			||||||
                    :aria-label="$t('login.password')"
 | 
					                    :aria-label="$t('login.password')"
 | 
				
			||||||
                    v-on:keyup.enter="login"
 | 
					                    @keyup.enter="login"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <a class="btn w-auto" @click="login" v-t="'titles.login'" />
 | 
					                <a v-t="'titles.login'" class="btn w-auto" @click="login" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <ul class="md:flex-1 md:justify-center md:flex">
 | 
					            <ul class="md:flex-1 md:justify-center md:flex">
 | 
				
			||||||
                <li v-for="provider in oidcProviders" :key="provider.name">
 | 
					                <li v-for="provider in oidcProviders" :key="provider.name">
 | 
				
			||||||
                    <a class="btn w-auto" :href="provider.authUri">Log in with {{ provider.name }}</a>
 | 
					                    <a class="btn w-auto" :href="provider.authUri">Log in with {{ provider.name }}</a>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
            </ul>
 | 
					            </ul>
 | 
				
			||||||
 | 
					            <TooltipIcon icon="i-fa6-solid:circle-info" :tooltip="$t('info.login_note')" />
 | 
				
			||||||
        </form>
 | 
					        </form>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
 | 
					import TooltipIcon from "./TooltipIcon.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					        TooltipIcon,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            username: null,
 | 
					            username: null,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/components/LoginPage.vue,
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/LoginPage.vue,
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    emits: ["close"],
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        window.addEventListener("keydown", this.handleKeyDown);
 | 
					        window.addEventListener("keydown", this.handleKeyDown);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,23 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <nav class="flex flex-wrap items-center justify-center px-2 sm:px-4 pb-2.5 w-full relative">
 | 
					    <nav class="relative w-full flex flex-wrap items-center justify-center px-2 pb-2.5 sm:px-4">
 | 
				
			||||||
        <div class="flex-1 flex justify-start">
 | 
					        <div class="flex flex-1 justify-start">
 | 
				
			||||||
            <router-link class="flex font-bold text-3xl items-center font-sans" to="/"
 | 
					            <router-link class="flex items-center text-3xl font-bold font-sans" :to="homePagePath"
 | 
				
			||||||
                ><img
 | 
					                ><img
 | 
				
			||||||
                    alt="logo"
 | 
					                    alt="logo"
 | 
				
			||||||
                    src="/img/icons/logo.svg"
 | 
					                    src="/img/icons/logo.svg"
 | 
				
			||||||
                    height="32"
 | 
					                    height="32"
 | 
				
			||||||
                    width="32"
 | 
					                    width="32"
 | 
				
			||||||
                    class="w-10 mr-[-0.6rem]"
 | 
					                    class="mr-[-0.6rem] w-10"
 | 
				
			||||||
                />iped</router-link
 | 
					                />iped</router-link
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="lt-md:hidden search-container">
 | 
					        <div class="search-container lt-md:hidden">
 | 
				
			||||||
            <input
 | 
					            <input
 | 
				
			||||||
 | 
					                ref="videoSearch"
 | 
				
			||||||
                v-model="searchText"
 | 
					                v-model="searchText"
 | 
				
			||||||
                class="input w-72 h-10 pr-20"
 | 
					                class="input h-10 w-72 pr-20"
 | 
				
			||||||
                type="text"
 | 
					                type="text"
 | 
				
			||||||
                role="search"
 | 
					                role="search"
 | 
				
			||||||
                ref="videoSearch"
 | 
					 | 
				
			||||||
                :title="$t('actions.search')"
 | 
					                :title="$t('actions.search')"
 | 
				
			||||||
                :placeholder="$t('actions.search')"
 | 
					                :placeholder="$t('actions.search')"
 | 
				
			||||||
                @keyup="onKeyUp"
 | 
					                @keyup="onKeyUp"
 | 
				
			||||||
| 
						 | 
					@ -27,14 +27,17 @@
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <span v-if="searchText" class="delete-search" @click="searchText = ''">⨉</span>
 | 
					            <span v-if="searchText" class="delete-search" @click="searchText = ''">⨉</span>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <button id="search-btn" class="input btn mx-1 h-10" @click="onSearchClick">
 | 
				
			||||||
 | 
					            <div class="i-fa6-solid:magnifying-glass"></div>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
        <!-- three vertical lines for toggling the hamburger menu on mobile -->
 | 
					        <!-- three vertical lines for toggling the hamburger menu on mobile -->
 | 
				
			||||||
        <button class="md:hidden flex flex-col justify-end mr-3" @click="showTopNav = !showTopNav">
 | 
					        <button class="mr-3 flex flex-col justify-end md:hidden" @click="showTopNav = !showTopNav">
 | 
				
			||||||
            <span class="line"></span>
 | 
					            <span class="line"></span>
 | 
				
			||||||
            <span class="line"></span>
 | 
					            <span class="line"></span>
 | 
				
			||||||
            <span class="line"></span>
 | 
					            <span class="line"></span>
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
        <!-- navigation bar for large screen devices -->
 | 
					        <!-- navigation bar for large screen devices -->
 | 
				
			||||||
        <ul class="hidden md:(flex-1 flex justify-end flex text-1xl children:pl-3)">
 | 
					        <ul class="md:text-1xl hidden md:(flex flex flex-1 justify-end children:pl-3)">
 | 
				
			||||||
            <li v-if="shouldShowTrending">
 | 
					            <li v-if="shouldShowTrending">
 | 
				
			||||||
                <router-link v-t="'titles.trending'" to="/trending" />
 | 
					                <router-link v-t="'titles.trending'" to="/trending" />
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
| 
						 | 
					@ -44,7 +47,7 @@
 | 
				
			||||||
            <li v-if="shouldShowLogin">
 | 
					            <li v-if="shouldShowLogin">
 | 
				
			||||||
                <router-link v-t="'titles.login'" to="/login" />
 | 
					                <router-link v-t="'titles.login'" to="/login" />
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
            <li v-if="shouldShowLogin">
 | 
					            <li v-if="shouldShowRegister">
 | 
				
			||||||
                <router-link v-t="'titles.register'" to="/register" />
 | 
					                <router-link v-t="'titles.register'" to="/register" />
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
            <li v-if="shouldShowHistory">
 | 
					            <li v-if="shouldShowHistory">
 | 
				
			||||||
| 
						 | 
					@ -61,7 +64,7 @@
 | 
				
			||||||
    <!-- navigation bar for mobile devices -->
 | 
					    <!-- navigation bar for mobile devices -->
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
        v-if="showTopNav"
 | 
					        v-if="showTopNav"
 | 
				
			||||||
        class="mobile-nav flex flex-col mb-4 children:(p-1 w-full border-b border-dark-100 flex items-center gap-1)"
 | 
					        class="mobile-nav mb-4 flex flex-col children:(w-full flex items-center gap-1 border-b border-dark-100 p-1)"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        <router-link v-if="shouldShowTrending" to="/trending">
 | 
					        <router-link v-if="shouldShowTrending" to="/trending">
 | 
				
			||||||
            <div class="i-fa6-solid:fire"></div>
 | 
					            <div class="i-fa6-solid:fire"></div>
 | 
				
			||||||
| 
						 | 
					@ -93,7 +96,7 @@
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <!-- search suggestions for mobile devices -->
 | 
					    <!-- search suggestions for mobile devices -->
 | 
				
			||||||
    <div class="w-full mb-2 md:hidden search-container">
 | 
					    <div class="search-container mb-2 w-full md:hidden">
 | 
				
			||||||
        <input
 | 
					        <input
 | 
				
			||||||
            v-model="searchText"
 | 
					            v-model="searchText"
 | 
				
			||||||
            class="input h-10 w-full"
 | 
					            class="input h-10 w-full"
 | 
				
			||||||
| 
						 | 
					@ -128,17 +131,17 @@ export default {
 | 
				
			||||||
            searchText: "",
 | 
					            searchText: "",
 | 
				
			||||||
            suggestionsVisible: false,
 | 
					            suggestionsVisible: false,
 | 
				
			||||||
            showTopNav: false,
 | 
					            showTopNav: false,
 | 
				
			||||||
 | 
					            homePagePath: "/",
 | 
				
			||||||
 | 
					            registrationDisabled: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					 | 
				
			||||||
        const query = new URLSearchParams(window.location.search).get("search_query");
 | 
					 | 
				
			||||||
        if (query) this.onSearchTextChange(query);
 | 
					 | 
				
			||||||
        this.focusOnSearchBar();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    computed: {
 | 
					    computed: {
 | 
				
			||||||
        shouldShowLogin(_this) {
 | 
					        shouldShowLogin(_this) {
 | 
				
			||||||
            return _this.getAuthToken() == null;
 | 
					            return _this.getAuthToken() == null;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        shouldShowRegister(_this) {
 | 
				
			||||||
 | 
					            return _this.registrationDisabled == false ? _this.shouldShowLogin : false;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        shouldShowHistory(_this) {
 | 
					        shouldShowHistory(_this) {
 | 
				
			||||||
            return _this.getPreferenceBoolean("watchHistory", false);
 | 
					            return _this.getPreferenceBoolean("watchHistory", false);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -149,6 +152,13 @@ export default {
 | 
				
			||||||
            return _this.getPreferenceBoolean("searchHistory", false) && localStorage.getItem("search_history");
 | 
					            return _this.getPreferenceBoolean("searchHistory", false) && localStorage.getItem("search_history");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    mounted() {
 | 
				
			||||||
 | 
					        this.fetchAuthConfig();
 | 
				
			||||||
 | 
					        const query = new URLSearchParams(window.location.search).get("search_query");
 | 
				
			||||||
 | 
					        if (query) this.onSearchTextChange(query);
 | 
				
			||||||
 | 
					        this.focusOnSearchBar();
 | 
				
			||||||
 | 
					        this.homePagePath = this.getHomePage(this);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        // focus on search bar when Ctrl+k is pressed
 | 
					        // focus on search bar when Ctrl+k is pressed
 | 
				
			||||||
        focusOnSearchBar() {
 | 
					        focusOnSearchBar() {
 | 
				
			||||||
| 
						 | 
					@ -165,12 +175,7 @@ export default {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        onKeyPress(e) {
 | 
					        onKeyPress(e) {
 | 
				
			||||||
            if (e.key === "Enter") {
 | 
					            if (e.key === "Enter") {
 | 
				
			||||||
                e.target.blur();
 | 
					                this.submitSearch(e);
 | 
				
			||||||
                this.$router.push({
 | 
					 | 
				
			||||||
                    name: "SearchResults",
 | 
					 | 
				
			||||||
                    query: { search_query: this.searchText },
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        onInputFocus() {
 | 
					        onInputFocus() {
 | 
				
			||||||
| 
						 | 
					@ -183,6 +188,22 @@ export default {
 | 
				
			||||||
        onSearchTextChange(searchText) {
 | 
					        onSearchTextChange(searchText) {
 | 
				
			||||||
            this.searchText = searchText;
 | 
					            this.searchText = searchText;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        async fetchAuthConfig() {
 | 
				
			||||||
 | 
					            this.fetchJson(this.authApiUrl() + "/config").then(config => {
 | 
				
			||||||
 | 
					                this.registrationDisabled = config?.registrationDisabled === true;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        onSearchClick(e) {
 | 
				
			||||||
 | 
					            this.submitSearch(e);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        submitSearch(e) {
 | 
				
			||||||
 | 
					            e.target.blur();
 | 
				
			||||||
 | 
					            this.$router.push({
 | 
				
			||||||
 | 
					                name: "SearchResults",
 | 
				
			||||||
 | 
					                query: { search_query: this.searchText },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -192,10 +213,15 @@ export default {
 | 
				
			||||||
    @apply relative inline-flex items-center;
 | 
					    @apply relative inline-flex items-center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.delete-search {
 | 
					.delete-search {
 | 
				
			||||||
    @apply absolute right-3 cursor-pointer rounded-full bg-[#ccc] w-4 h-4 text-center text-black opacity-50 hover:(opacity-70) text-size-[13px];
 | 
					    @apply absolute right-3 cursor-pointer rounded-full bg-[#ccc] w-4 h-4 text-center text-black opacity-50 hover:(opacity-70) text-size-[10px];
 | 
				
			||||||
    line-height: 1.05;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.mobile-nav div {
 | 
					.mobile-nav div {
 | 
				
			||||||
    @apply mx-1;
 | 
					    @apply mx-1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media screen and (max-width: 848px) {
 | 
				
			||||||
 | 
					    #search-btn {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="flex flex-col justify-center items-center min-h-[88vh]">
 | 
					    <div class="min-h-[88vh] flex flex-col items-center justify-center">
 | 
				
			||||||
        <h1 class="font-bold !text-9xl">404</h1>
 | 
					        <h1 class="font-bold !text-9xl">404</h1>
 | 
				
			||||||
        <h2 class="!text-2xl" v-t="'info.page_not_found'" />
 | 
					        <h2 v-t="'info.page_not_found'" class="!text-2xl" />
 | 
				
			||||||
        <a class="btn mt-16" href="/" v-t="'actions.back_to_home'" />
 | 
					        <a v-t="'actions.back_to_home'" class="btn mt-16" href="/" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,16 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <ModalComponent>
 | 
					    <ModalComponent @close="$emit('close')">
 | 
				
			||||||
        <span class="text-2xl w-max inline-block" v-t="'actions.select_playlist'" />
 | 
					        <span v-t="'actions.select_playlist'" class="inline-block w-max text-2xl" />
 | 
				
			||||||
        <select class="select w-full mt-3" v-model="selectedPlaylist">
 | 
					        <select v-model="selectedPlaylist" class="select mt-3 w-full">
 | 
				
			||||||
            <option v-for="playlist in playlists" :value="playlist.id" :key="playlist.id" v-text="playlist.name" />
 | 
					            <option v-for="playlist in playlists" :key="playlist.id" :value="playlist.id" v-text="playlist.name" />
 | 
				
			||||||
        </select>
 | 
					        </select>
 | 
				
			||||||
        <div class="flex justify-end mt-3">
 | 
					        <div class="mt-3 w-full flex justify-between">
 | 
				
			||||||
 | 
					            <button ref="addButton" v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
                class="btn"
 | 
					 | 
				
			||||||
                @click="handleClick(selectedPlaylist)"
 | 
					 | 
				
			||||||
                ref="addButton"
 | 
					                ref="addButton"
 | 
				
			||||||
                v-t="'actions.add_to_playlist'"
 | 
					                v-t="'actions.add_to_playlist'"
 | 
				
			||||||
 | 
					                class="btn"
 | 
				
			||||||
 | 
					                @click="handleClick(selectedPlaylist)"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </ModalComponent>
 | 
					    </ModalComponent>
 | 
				
			||||||
| 
						 | 
					@ -23,11 +24,16 @@ export default {
 | 
				
			||||||
        ModalComponent,
 | 
					        ModalComponent,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
 | 
					        videoInfo: {
 | 
				
			||||||
 | 
					            type: Object,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        videoId: {
 | 
					        videoId: {
 | 
				
			||||||
            type: String,
 | 
					            type: String,
 | 
				
			||||||
            required: true,
 | 
					            required: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    emits: ["close"],
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            playlists: [],
 | 
					            playlists: [],
 | 
				
			||||||
| 
						 | 
					@ -62,31 +68,25 @@ export default {
 | 
				
			||||||
            this.$refs.addButton.disabled = true;
 | 
					            this.$refs.addButton.disabled = true;
 | 
				
			||||||
            this.processing = true;
 | 
					            this.processing = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
 | 
					            this.addVideosToPlaylist(playlistId, [this.videoId], [this.videoInfo]).then(json => {
 | 
				
			||||||
                method: "POST",
 | 
					 | 
				
			||||||
                body: JSON.stringify({
 | 
					 | 
				
			||||||
                    playlistId: playlistId,
 | 
					 | 
				
			||||||
                    videoId: this.videoId,
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                    "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }).then(json => {
 | 
					 | 
				
			||||||
                this.setPreference("selectedPlaylist" + this.hashCode(this.authApiUrl()), playlistId);
 | 
					                this.setPreference("selectedPlaylist" + this.hashCode(this.authApiUrl()), playlistId);
 | 
				
			||||||
                this.$emit("close");
 | 
					                this.$emit("close");
 | 
				
			||||||
                if (json.error) alert(json.error);
 | 
					                if (json.error) alert(json.error);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async fetchPlaylists() {
 | 
					        async fetchPlaylists() {
 | 
				
			||||||
            this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
					            this.getPlaylists().then(json => {
 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }).then(json => {
 | 
					 | 
				
			||||||
                this.playlists = json;
 | 
					                this.playlists = json;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        onCreatePlaylist() {
 | 
				
			||||||
 | 
					            const name = prompt(this.$t("actions.create_playlist"));
 | 
				
			||||||
 | 
					            if (!name) return;
 | 
				
			||||||
 | 
					            this.createPlaylist(name).then(json => {
 | 
				
			||||||
 | 
					                if (json.error) alert(json.error);
 | 
				
			||||||
 | 
					                else this.fetchPlaylists();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div class="flex flex-col flex-justify-between">
 | 
				
			||||||
        <router-link :to="props.item.url">
 | 
					        <router-link :to="props.item.url">
 | 
				
			||||||
            <div class="relative">
 | 
					            <div class="relative">
 | 
				
			||||||
                <img class="w-full" :src="props.item.thumbnail" loading="lazy" />
 | 
					                <img class="w-full" :src="props.item.thumbnail" loading="lazy" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
                <span v-text="props.item.name" />
 | 
					                <span v-text="props.item.name" />
 | 
				
			||||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
 | 
					                <font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
        <p v-if="props.item.description" v-text="props.item.description" />
 | 
					        <p v-if="props.item.description" v-text="props.item.description" />
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
        <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
 | 
					        <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
                <span v-text="props.item.uploaderName" />
 | 
					                <span v-text="props.item.uploaderName" />
 | 
				
			||||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
 | 
					                <font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
        <a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
 | 
					        <a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup>
 | 
					<script setup>
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    item: Object,
 | 
					    item: {
 | 
				
			||||||
 | 
					        type: Object,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
 | 
					    <ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <LoadingIndicatorPage :show-content="playlist" v-show="!playlist?.error">
 | 
					    <LoadingIndicatorPage v-show="!playlist?.error" :show-content="playlist">
 | 
				
			||||||
        <h1 class="ml-1 mb-1 mt-4 text-3xl!" v-text="playlist.name" />
 | 
					        <h1 class="mb-1 ml-1 mt-4 text-3xl!" v-text="playlist.name" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <CollapsableText :text="playlist.description" />
 | 
					        <CollapsableText v-if="playlist?.description" :text="playlist.description" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="flex justify-between items-center mt-1">
 | 
					        <div class="mt-1 flex items-center justify-between">
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <router-link class="link flex items-center gap-3" :to="playlist.uploaderUrl || '/'">
 | 
					                <router-link class="link flex items-center gap-3" :to="playlist.uploaderUrl || '/'">
 | 
				
			||||||
                    <img :src="playlist.uploaderAvatar" loading="lazy" class="rounded-full" />
 | 
					                    <img :src="playlist.uploaderAvatar" loading="lazy" class="rounded-full" />
 | 
				
			||||||
| 
						 | 
					@ -14,12 +14,12 @@
 | 
				
			||||||
                </router-link>
 | 
					                </router-link>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <strong v-text="`${playlist.videos} ${$t('video.videos')}`" class="mr-2" />
 | 
					                <strong class="mr-2" v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
				
			||||||
                <button class="btn mx-1" v-if="!isPipedPlaylist" @click="bookmarkPlaylist">
 | 
					                <button v-if="!isPipedPlaylist" class="btn mx-1" @click="bookmarkPlaylist">
 | 
				
			||||||
                    {{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
 | 
					                    {{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
 | 
				
			||||||
                    }}<font-awesome-icon class="ml-3" icon="bookmark" />
 | 
					                    }}<font-awesome-icon class="ml-3" icon="bookmark" />
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <button class="btn mr-1" v-if="authenticated && !isPipedPlaylist" @click="clonePlaylist">
 | 
					                <button v-if="authenticated && !isPipedPlaylist" class="btn mr-1" @click="clonePlaylist">
 | 
				
			||||||
                    {{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
 | 
					                    {{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <button class="btn mr-1" @click="downloadPlaylistAsTxt">
 | 
					                <button class="btn mr-1" @click="downloadPlaylistAsTxt">
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@
 | 
				
			||||||
                <a class="btn mr-1" :href="getRssUrl">
 | 
					                <a class="btn mr-1" :href="getRssUrl">
 | 
				
			||||||
                    <font-awesome-icon icon="rss" />
 | 
					                    <font-awesome-icon icon="rss" />
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
                <WatchOnButton :link="`https://www.youtube.com/playlist?list=${this.$route.query.list}`" />
 | 
					                <WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,9 +42,9 @@
 | 
				
			||||||
                :index="index"
 | 
					                :index="index"
 | 
				
			||||||
                :playlist-id="$route.query.list"
 | 
					                :playlist-id="$route.query.list"
 | 
				
			||||||
                :admin="admin"
 | 
					                :admin="admin"
 | 
				
			||||||
                @remove="removeVideo(index)"
 | 
					 | 
				
			||||||
                height="94"
 | 
					                height="94"
 | 
				
			||||||
                width="168"
 | 
					                width="168"
 | 
				
			||||||
 | 
					                @remove="removeVideo(index)"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </LoadingIndicatorPage>
 | 
					    </LoadingIndicatorPage>
 | 
				
			||||||
| 
						 | 
					@ -86,14 +86,11 @@ export default {
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        const playlistId = this.$route.query.list;
 | 
					        const playlistId = this.$route.query.list;
 | 
				
			||||||
        if (this.authenticated && playlistId?.length == 36)
 | 
					        if (this.authenticated && playlistId?.length == 36)
 | 
				
			||||||
            this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
					            this.getPlaylists().then(json => {
 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }).then(json => {
 | 
					 | 
				
			||||||
                if (json.error) alert(json.error);
 | 
					                if (json.error) alert(json.error);
 | 
				
			||||||
                else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
 | 
					                else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        else if (playlistId.startsWith("local")) this.admin = true;
 | 
				
			||||||
        this.isPlaylistBookmarked();
 | 
					        this.isPlaylistBookmarked();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
| 
						 | 
					@ -106,6 +103,11 @@ export default {
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        async fetchPlaylist() {
 | 
					        async fetchPlaylist() {
 | 
				
			||||||
 | 
					            const playlistId = this.$route.query.list;
 | 
				
			||||||
 | 
					            if (playlistId.startsWith("local")) {
 | 
				
			||||||
 | 
					                return this.getPlaylist(playlistId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
 | 
					            return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async getPlaylistData() {
 | 
					        async getPlaylistData() {
 | 
				
			||||||
| 
						 | 
					@ -114,6 +116,7 @@ export default {
 | 
				
			||||||
                .then(() => {
 | 
					                .then(() => {
 | 
				
			||||||
                    this.updateTitle();
 | 
					                    this.updateTitle();
 | 
				
			||||||
                    this.updateWatched(this.playlist.relatedStreams);
 | 
					                    this.updateWatched(this.playlist.relatedStreams);
 | 
				
			||||||
 | 
					                    this.fetchDeArrowContent(this.playlist.relatedStreams);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async updateTitle() {
 | 
					        async updateTitle() {
 | 
				
			||||||
| 
						 | 
					@ -130,6 +133,7 @@ export default {
 | 
				
			||||||
                    this.playlist.nextpage = json.nextpage;
 | 
					                    this.playlist.nextpage = json.nextpage;
 | 
				
			||||||
                    this.loading = false;
 | 
					                    this.loading = false;
 | 
				
			||||||
                    json.relatedStreams.map(stream => this.playlist.relatedStreams.push(stream));
 | 
					                    json.relatedStreams.map(stream => this.playlist.relatedStreams.push(stream));
 | 
				
			||||||
 | 
					                    this.fetchDeArrowContent(this.playlist.relatedStreams);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="overflow-x-scroll h-screen-sm" ref="scrollable">
 | 
					    <div ref="scrollable" class="h-screen-sm overflow-x-scroll">
 | 
				
			||||||
        <VideoItem
 | 
					        <VideoItem
 | 
				
			||||||
            v-for="(related, index) in playlist.relatedStreams"
 | 
					            v-for="(related, index) in playlist.relatedStreams"
 | 
				
			||||||
            :key="related.url"
 | 
					            :key="related.url"
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,18 @@ export default {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        selectedIndex: {
 | 
					        selectedIndex: {
 | 
				
			||||||
            type: Number,
 | 
					            type: Number,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    watch: {
 | 
				
			||||||
 | 
					        playlist: {
 | 
				
			||||||
 | 
					            handler() {
 | 
				
			||||||
 | 
					                if (this.selectedIndex - 1 < this.playlist.relatedStreams.length)
 | 
				
			||||||
 | 
					                    nextTick(() => {
 | 
				
			||||||
 | 
					                        this.updateScroll();
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            deep: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
| 
						 | 
					@ -43,16 +55,5 @@ export default {
 | 
				
			||||||
                    elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
 | 
					                    elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    watch: {
 | 
					 | 
				
			||||||
        playlist: {
 | 
					 | 
				
			||||||
            handler() {
 | 
					 | 
				
			||||||
                if (this.selectedIndex - 1 < this.playlist.relatedStreams.length)
 | 
					 | 
				
			||||||
                    nextTick(() => {
 | 
					 | 
				
			||||||
                        this.updateScroll();
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            deep: true,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,19 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h2 v-if="authenticated" class="font-bold my-4" v-t="'titles.playlists'" />
 | 
					    <h2 v-t="'titles.playlists'" class="my-4 font-bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div v-if="authenticated" class="flex justify-between mb-3">
 | 
					    <div class="mb-3 flex justify-between">
 | 
				
			||||||
        <button v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
 | 
					        <button v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
 | 
				
			||||||
        <div class="flex">
 | 
					        <div class="flex">
 | 
				
			||||||
            <button
 | 
					            <button v-if="playlists.length > 0" v-t="'actions.export_to_json'" class="btn" @click="exportPlaylists" />
 | 
				
			||||||
                v-if="this.playlists.length > 0"
 | 
					 | 
				
			||||||
                v-t="'actions.export_to_json'"
 | 
					 | 
				
			||||||
                class="btn"
 | 
					 | 
				
			||||||
                @click="exportPlaylists"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <input
 | 
					            <input
 | 
				
			||||||
                id="fileSelector"
 | 
					                id="fileSelector"
 | 
				
			||||||
                ref="fileSelector"
 | 
					                ref="fileSelector"
 | 
				
			||||||
                type="file"
 | 
					                type="file"
 | 
				
			||||||
                class="display-none"
 | 
					                class="display-none"
 | 
				
			||||||
                @change="importPlaylists"
 | 
					 | 
				
			||||||
                multiple="multiple"
 | 
					                multiple="multiple"
 | 
				
			||||||
 | 
					                @change="importPlaylists"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" />
 | 
					            <label v-t="'actions.import_from_json_csv'" for="fileSelector" class="btn ml-2" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,42 +29,42 @@
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <p
 | 
					                <p
 | 
				
			||||||
                    style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
					                    style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
				
			||||||
                    class="my-2 overflow-hidden flex link"
 | 
					                    class="link my-2 flex overflow-hidden"
 | 
				
			||||||
                    :title="playlist.name"
 | 
					                    :title="playlist.name"
 | 
				
			||||||
                    v-text="playlist.name"
 | 
					                    v-text="playlist.name"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </router-link>
 | 
					            </router-link>
 | 
				
			||||||
            <button class="btn h-auto" @click="showPlaylistEditModal(playlist)" v-t="'actions.edit_playlist'" />
 | 
					            <button v-t="'actions.edit_playlist'" class="btn h-auto" @click="showPlaylistEditModal(playlist)" />
 | 
				
			||||||
            <button class="btn h-auto ml-2" @click="playlistToDelete = playlist.id" v-t="'actions.delete_playlist'" />
 | 
					            <button v-t="'actions.delete_playlist'" class="btn ml-2 h-auto" @click="playlistToDelete = playlist.id" />
 | 
				
			||||||
            <ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
 | 
					            <ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
 | 
				
			||||||
                <div class="flex flex-col gap-2">
 | 
					                <div class="flex flex-col gap-2">
 | 
				
			||||||
                    <h2 v-t="'actions.edit_playlist'" />
 | 
					                    <h2 v-t="'actions.edit_playlist'" />
 | 
				
			||||||
                    <input
 | 
					                    <input
 | 
				
			||||||
 | 
					                        v-model="newPlaylistName"
 | 
				
			||||||
                        class="input"
 | 
					                        class="input"
 | 
				
			||||||
                        type="text"
 | 
					                        type="text"
 | 
				
			||||||
                        v-model="newPlaylistName"
 | 
					 | 
				
			||||||
                        :placeholder="$t('actions.playlist_name')"
 | 
					                        :placeholder="$t('actions.playlist_name')"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    <input
 | 
					                    <input
 | 
				
			||||||
 | 
					                        v-model="newPlaylistDescription"
 | 
				
			||||||
                        class="input"
 | 
					                        class="input"
 | 
				
			||||||
                        type="text"
 | 
					                        type="text"
 | 
				
			||||||
                        v-model="newPlaylistDescription"
 | 
					 | 
				
			||||||
                        :placeholder="$t('actions.playlist_description')"
 | 
					                        :placeholder="$t('actions.playlist_description')"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    <button class="btn ml-auto" @click="editPlaylist(playlist)" v-t="'actions.okay'" />
 | 
					                    <button v-t="'actions.okay'" class="btn ml-auto" @click="editPlaylist(playlist)" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </ModalComponent>
 | 
					            </ModalComponent>
 | 
				
			||||||
            <ConfirmModal
 | 
					            <ConfirmModal
 | 
				
			||||||
                v-if="playlistToDelete == playlist.id"
 | 
					                v-if="playlistToDelete == playlist.id"
 | 
				
			||||||
                :message="$t('actions.delete_playlist_confirm')"
 | 
					                :message="$t('actions.delete_playlist_confirm')"
 | 
				
			||||||
                @close="playlistToDelete = null"
 | 
					                @close="playlistToDelete = null"
 | 
				
			||||||
                @confirm="deletePlaylist(playlist.id)"
 | 
					                @confirm="onDeletePlaylist(playlist.id)"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <h2 class="font-bold my-4" v-t="'titles.bookmarks'" />
 | 
					    <h2 v-t="'titles.bookmarks'" class="my-4 font-bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div v-if="bookmarks" class="video-grid">
 | 
					    <div v-if="bookmarks" class="video-grid">
 | 
				
			||||||
        <router-link
 | 
					        <router-link
 | 
				
			||||||
| 
						 | 
					@ -80,18 +75,18 @@
 | 
				
			||||||
            <img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
 | 
					            <img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
 | 
				
			||||||
            <div class="relative text-sm">
 | 
					            <div class="relative text-sm">
 | 
				
			||||||
                <span class="thumbnail-overlay thumbnail-right" v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
					                <span class="thumbnail-overlay thumbnail-right" v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
				
			||||||
                <div class="absolute bottom-100px right-5px px-5px z-100" @click.prevent="removeBookmark(index)">
 | 
					                <div class="absolute bottom-100px right-5px z-100 px-5px" @click.prevent="removeBookmark(index)">
 | 
				
			||||||
                    <font-awesome-icon class="ml-3" icon="bookmark" />
 | 
					                    <font-awesome-icon class="ml-3" icon="bookmark" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <p
 | 
					            <p
 | 
				
			||||||
                style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
					                style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
				
			||||||
                class="my-2 overflow-hidden flex link"
 | 
					                class="link my-2 flex overflow-hidden"
 | 
				
			||||||
                :title="playlist.name"
 | 
					                :title="playlist.name"
 | 
				
			||||||
                v-text="playlist.name"
 | 
					                v-text="playlist.name"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <a :href="playlist.uploaderUrl" class="flex items-center">
 | 
					            <a :href="playlist.uploaderUrl" class="flex items-center">
 | 
				
			||||||
                <img class="rounded-full w-32px h-32px" :src="playlist.uploaderAvatar" />
 | 
					                <img class="h-32px w-32px rounded-full" :src="playlist.uploaderAvatar" />
 | 
				
			||||||
                <span class="ml-3 hover:underline" v-text="playlist.uploader" />
 | 
					                <span class="ml-3 hover:underline" v-text="playlist.uploader" />
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
| 
						 | 
					@ -104,6 +99,7 @@ import ConfirmModal from "./ConfirmModal.vue";
 | 
				
			||||||
import ModalComponent from "./ModalComponent.vue";
 | 
					import ModalComponent from "./ModalComponent.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: { ConfirmModal, ModalComponent },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            playlists: [],
 | 
					            playlists: [],
 | 
				
			||||||
| 
						 | 
					@ -115,7 +111,7 @@ export default {
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        if (this.authenticated) this.fetchPlaylists();
 | 
					        this.fetchPlaylists();
 | 
				
			||||||
        this.loadPlaylistBookmarks();
 | 
					        this.loadPlaylistBookmarks();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
| 
						 | 
					@ -123,11 +119,7 @@ export default {
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        fetchPlaylists() {
 | 
					        fetchPlaylists() {
 | 
				
			||||||
            this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
					            this.getPlaylists().then(json => {
 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }).then(json => {
 | 
					 | 
				
			||||||
                this.playlists = json;
 | 
					                this.playlists = json;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -141,50 +133,21 @@ export default {
 | 
				
			||||||
            const newName = this.newPlaylistName;
 | 
					            const newName = this.newPlaylistName;
 | 
				
			||||||
            const newDescription = this.newPlaylistDescription;
 | 
					            const newDescription = this.newPlaylistDescription;
 | 
				
			||||||
            if (newName != selectedPlaylist.name) {
 | 
					            if (newName != selectedPlaylist.name) {
 | 
				
			||||||
                this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
 | 
					                this.renamePlaylist(selectedPlaylist.id, newName).then(json => {
 | 
				
			||||||
                    method: "POST",
 | 
					 | 
				
			||||||
                    body: JSON.stringify({
 | 
					 | 
				
			||||||
                        playlistId: selectedPlaylist.id,
 | 
					 | 
				
			||||||
                        newName: newName,
 | 
					 | 
				
			||||||
                    }),
 | 
					 | 
				
			||||||
                    headers: {
 | 
					 | 
				
			||||||
                        Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                        "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                }).then(json => {
 | 
					 | 
				
			||||||
                    if (json.error) alert(json.error);
 | 
					                    if (json.error) alert(json.error);
 | 
				
			||||||
                    else selectedPlaylist.name = newName;
 | 
					                    else selectedPlaylist.name = newName;
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (newDescription != selectedPlaylist.description) {
 | 
					            if (newDescription != selectedPlaylist.description) {
 | 
				
			||||||
                this.fetchJson(this.authApiUrl() + "/user/playlists/description", null, {
 | 
					                this.changePlaylistDescription(selectedPlaylist.id, newDescription).then(json => {
 | 
				
			||||||
                    method: "PATCH",
 | 
					 | 
				
			||||||
                    body: JSON.stringify({
 | 
					 | 
				
			||||||
                        playlistId: selectedPlaylist.id,
 | 
					 | 
				
			||||||
                        description: newDescription,
 | 
					 | 
				
			||||||
                    }),
 | 
					 | 
				
			||||||
                    headers: {
 | 
					 | 
				
			||||||
                        Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                        "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                }).then(json => {
 | 
					 | 
				
			||||||
                    if (json.error) alert(json.error);
 | 
					                    if (json.error) alert(json.error);
 | 
				
			||||||
                    else selectedPlaylist.description = newDescription;
 | 
					                    else selectedPlaylist.description = newDescription;
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.playlistToEdit = null;
 | 
					            this.playlistToEdit = null;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        deletePlaylist(id) {
 | 
					        onDeletePlaylist(id) {
 | 
				
			||||||
            this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
 | 
					            this.deletePlaylist(id).then(json => {
 | 
				
			||||||
                method: "POST",
 | 
					 | 
				
			||||||
                body: JSON.stringify({
 | 
					 | 
				
			||||||
                    playlistId: id,
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                    "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }).then(json => {
 | 
					 | 
				
			||||||
                if (json.error) alert(json.error);
 | 
					                if (json.error) alert(json.error);
 | 
				
			||||||
                else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
 | 
					                else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
| 
						 | 
					@ -198,19 +161,6 @@ export default {
 | 
				
			||||||
                else this.fetchPlaylists();
 | 
					                else this.fetchPlaylists();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async createPlaylist(name) {
 | 
					 | 
				
			||||||
            let json = await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
 | 
					 | 
				
			||||||
                method: "POST",
 | 
					 | 
				
			||||||
                body: JSON.stringify({
 | 
					 | 
				
			||||||
                    name: name,
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                    "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return json;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        async exportPlaylists() {
 | 
					        async exportPlaylists() {
 | 
				
			||||||
            if (!this.playlists) return;
 | 
					            if (!this.playlists) return;
 | 
				
			||||||
            let json = {
 | 
					            let json = {
 | 
				
			||||||
| 
						 | 
					@ -223,8 +173,8 @@ export default {
 | 
				
			||||||
            this.download(JSON.stringify(json), "playlists.json", "application/json");
 | 
					            this.download(JSON.stringify(json), "playlists.json", "application/json");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async fetchPlaylistJson(playlistId) {
 | 
					        async fetchPlaylistJson(playlistId) {
 | 
				
			||||||
            let playlist = await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
 | 
					            let playlist = await this.getPlaylist(playlistId);
 | 
				
			||||||
            let playlistJson = {
 | 
					            return {
 | 
				
			||||||
                name: playlist.name,
 | 
					                name: playlist.name,
 | 
				
			||||||
                // possible other types: history, watch later, ...
 | 
					                // possible other types: history, watch later, ...
 | 
				
			||||||
                type: "playlist",
 | 
					                type: "playlist",
 | 
				
			||||||
| 
						 | 
					@ -233,7 +183,6 @@ export default {
 | 
				
			||||||
                // list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
 | 
					                // list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
 | 
				
			||||||
                videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
 | 
					                videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            return playlistJson;
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async importPlaylists() {
 | 
					        async importPlaylists() {
 | 
				
			||||||
            const files = this.$refs.fileSelector.files;
 | 
					            const files = this.$refs.fileSelector.files;
 | 
				
			||||||
| 
						 | 
					@ -246,26 +195,28 @@ export default {
 | 
				
			||||||
            let text = await file.text();
 | 
					            let text = await file.text();
 | 
				
			||||||
            let tasks = [];
 | 
					            let tasks = [];
 | 
				
			||||||
            // list of playlists exported from Piped
 | 
					            // list of playlists exported from Piped
 | 
				
			||||||
            if (text.includes("playlists")) {
 | 
					            if (file.name.slice(-4).toLowerCase() == ".csv") {
 | 
				
			||||||
 | 
					                const lines = text.split("\n");
 | 
				
			||||||
 | 
					                const playlistName = lines[1].split(",")[4];
 | 
				
			||||||
 | 
					                const playlist = {
 | 
				
			||||||
 | 
					                    name: playlistName != "" ? playlistName : new Date().toJSON(),
 | 
				
			||||||
 | 
					                    videos: lines
 | 
				
			||||||
 | 
					                        .slice(4, lines.length)
 | 
				
			||||||
 | 
					                        .filter(line => line != "")
 | 
				
			||||||
 | 
					                        .slice(1)
 | 
				
			||||||
 | 
					                        .map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                tasks.push(this.createPlaylistWithVideos(playlist));
 | 
				
			||||||
 | 
					            } else if (text.includes('"Piped"')) {
 | 
				
			||||||
 | 
					                // CSV from Google Takeout
 | 
				
			||||||
                let playlists = JSON.parse(text).playlists;
 | 
					                let playlists = JSON.parse(text).playlists;
 | 
				
			||||||
                if (!playlists.length) {
 | 
					                if (!playlists.length) {
 | 
				
			||||||
                    alert(this.$t("actions.no_valid_playlists"));
 | 
					                    alert(this.$t("actions.no_valid_playlists"));
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                for (var i = 0; i < playlists.length; i++) {
 | 
					                for (let playlist of playlists) {
 | 
				
			||||||
                    tasks.push(this.createPlaylistWithVideos(playlists[i]));
 | 
					                    tasks.push(this.createPlaylistWithVideos(playlist));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // CSV from Google Takeout
 | 
					 | 
				
			||||||
            } else if (file.name.slice(-4).toLowerCase() == ".csv") {
 | 
					 | 
				
			||||||
                const lines = text.split("\n");
 | 
					 | 
				
			||||||
                const playlist = {
 | 
					 | 
				
			||||||
                    name: lines[1].split(",")[4],
 | 
					 | 
				
			||||||
                    videos: lines
 | 
					 | 
				
			||||||
                        .slice(4, lines.length)
 | 
					 | 
				
			||||||
                        .filter(line => line != "")
 | 
					 | 
				
			||||||
                        .map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                tasks.push(this.createPlaylistWithVideos(playlist));
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                alert(this.$t("actions.no_valid_playlists"));
 | 
					                alert(this.$t("actions.no_valid_playlists"));
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
| 
						 | 
					@ -277,19 +228,6 @@ export default {
 | 
				
			||||||
            let videoIds = playlist.videos.map(url => url.substr(-11));
 | 
					            let videoIds = playlist.videos.map(url => url.substr(-11));
 | 
				
			||||||
            await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
 | 
					            await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async addVideosToPlaylist(playlistId, videoIds) {
 | 
					 | 
				
			||||||
            await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
 | 
					 | 
				
			||||||
                method: "POST",
 | 
					 | 
				
			||||||
                body: JSON.stringify({
 | 
					 | 
				
			||||||
                    playlistId: playlistId,
 | 
					 | 
				
			||||||
                    videoIds: videoIds,
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                    "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        async loadPlaylistBookmarks() {
 | 
					        async loadPlaylistBookmarks() {
 | 
				
			||||||
            if (!window.db) return;
 | 
					            if (!window.db) return;
 | 
				
			||||||
            var tx = window.db.transaction("playlist_bookmarks", "readonly");
 | 
					            var tx = window.db.transaction("playlist_bookmarks", "readonly");
 | 
				
			||||||
| 
						 | 
					@ -298,8 +236,7 @@ export default {
 | 
				
			||||||
            cursorRequest.onsuccess = e => {
 | 
					            cursorRequest.onsuccess = e => {
 | 
				
			||||||
                const cursor = e.target.result;
 | 
					                const cursor = e.target.result;
 | 
				
			||||||
                if (cursor) {
 | 
					                if (cursor) {
 | 
				
			||||||
                    const bookmark = cursor.value;
 | 
					                    this.bookmarks.push(cursor.value);
 | 
				
			||||||
                    this.bookmarks.push(bookmark);
 | 
					 | 
				
			||||||
                    cursor.continue();
 | 
					                    cursor.continue();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
| 
						 | 
					@ -311,6 +248,5 @@ export default {
 | 
				
			||||||
            this.bookmarks.splice(index, 1);
 | 
					            this.bookmarks.splice(index, 1);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: { ConfirmModal, ModalComponent },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="flex">
 | 
					    <div class="flex">
 | 
				
			||||||
        <button @click="$router.go(-1) || $router.push('/')">
 | 
					        <button @click="$router.go(-1) || $router.push('/')">
 | 
				
			||||||
            <font-awesome-icon icon="chevron-left" /><span class="ml-1.5" v-t="'actions.back'" />
 | 
					            <font-awesome-icon icon="chevron-left" /><span v-t="'actions.back'" class="ml-1.5" />
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <h1 v-t="'titles.preferences'" class="font-bold text-center" />
 | 
					    <h1 v-t="'titles.preferences'" class="text-center font-bold" />
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
    <label for="ddlTheme" class="pref">
 | 
					    <label for="ddlTheme" class="pref">
 | 
				
			||||||
        <strong v-t="'actions.theme'" />
 | 
					        <strong v-t="'actions.theme'" />
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@
 | 
				
			||||||
        </select>
 | 
					        </select>
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <h2 class="text-center" v-t="'titles.player'" />
 | 
					    <h2 v-t="'titles.player'" class="text-center" />
 | 
				
			||||||
    <label class="pref" for="chkAutoPlayVideo">
 | 
					    <label class="pref" for="chkAutoPlayVideo">
 | 
				
			||||||
        <strong v-t="'actions.autoplay_video'" />
 | 
					        <strong v-t="'actions.autoplay_video'" />
 | 
				
			||||||
        <input
 | 
					        <input
 | 
				
			||||||
| 
						 | 
					@ -127,7 +127,7 @@
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
    <!-- chapters layout on mobile -->
 | 
					    <!-- chapters layout on mobile -->
 | 
				
			||||||
    <label class="lg:invisible pref" for="chkMinimizeChapters">
 | 
					    <label class="pref lg:invisible" for="chkMinimizeChapters">
 | 
				
			||||||
        <strong v-t="'actions.chapters_layout_mobile'" />
 | 
					        <strong v-t="'actions.chapters_layout_mobile'" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <select id="ddlDefaultHomepage" v-model="mobileChapterLayout" class="select w-auto" @change="onChange($event)">
 | 
					        <select id="ddlDefaultHomepage" v-model="mobileChapterLayout" class="select w-auto" @change="onChange($event)">
 | 
				
			||||||
| 
						 | 
					@ -184,7 +184,7 @@
 | 
				
			||||||
        <select
 | 
					        <select
 | 
				
			||||||
            id="ddlEnabledCodecs"
 | 
					            id="ddlEnabledCodecs"
 | 
				
			||||||
            v-model="enabledCodecs"
 | 
					            v-model="enabledCodecs"
 | 
				
			||||||
            class="select w-auto h-auto"
 | 
					            class="select h-auto w-auto"
 | 
				
			||||||
            multiple
 | 
					            multiple
 | 
				
			||||||
            @change="onChange($event)"
 | 
					            @change="onChange($event)"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
| 
						 | 
					@ -223,7 +223,7 @@
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
    <div v-if="sponsorBlock">
 | 
					    <div v-if="sponsorBlock">
 | 
				
			||||||
        <label v-for="[name, item] in skipOptions" class="pref" :for="'ddlSkip_' + name" :key="name">
 | 
					        <label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
 | 
				
			||||||
            <strong v-t="item.label" />
 | 
					            <strong v-t="item.label" />
 | 
				
			||||||
            <select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
 | 
					            <select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
 | 
				
			||||||
                <option v-t="'actions.no'" value="no" />
 | 
					                <option v-t="'actions.no'" value="no" />
 | 
				
			||||||
| 
						 | 
					@ -252,7 +252,17 @@
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <h2 class="text-center" v-t="'titles.instance'" />
 | 
					
 | 
				
			||||||
 | 
					    <h2 v-t="'titles.dearrow'" class="text-center" />
 | 
				
			||||||
 | 
					    <p class="text-center">
 | 
				
			||||||
 | 
					        <span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					    <label class="pref" for="chkDeArrow">
 | 
				
			||||||
 | 
					        <strong v-t="'actions.enable_dearrow'" />
 | 
				
			||||||
 | 
					        <input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <h2 v-t="'titles.instance'" class="text-center" />
 | 
				
			||||||
    <label class="pref" for="ddlInstanceSelection">
 | 
					    <label class="pref" for="ddlInstanceSelection">
 | 
				
			||||||
        <strong v-text="`${$t('actions.instance_selection')}:`" />
 | 
					        <strong v-text="`${$t('actions.instance_selection')}:`" />
 | 
				
			||||||
        <select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
 | 
					        <select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
 | 
				
			||||||
| 
						 | 
					@ -295,8 +305,8 @@
 | 
				
			||||||
    <br />
 | 
					    <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- options that are visible only when logged in -->
 | 
					    <!-- options that are visible only when logged in -->
 | 
				
			||||||
    <div v-if="this.authenticated">
 | 
					    <div v-if="authenticated">
 | 
				
			||||||
        <h2 class="text-center" v-t="'titles.account'"></h2>
 | 
					        <h2 v-t="'titles.account'" class="text-center"></h2>
 | 
				
			||||||
        <label class="pref" for="txtDeleteAccountPassword">
 | 
					        <label class="pref" for="txtDeleteAccountPassword">
 | 
				
			||||||
            <strong v-t="'actions.delete_account'" />
 | 
					            <strong v-t="'actions.delete_account'" />
 | 
				
			||||||
            <div class="flex items-center">
 | 
					            <div class="flex items-center">
 | 
				
			||||||
| 
						 | 
					@ -304,22 +314,22 @@
 | 
				
			||||||
                    id="txtDeleteAccountPassword"
 | 
					                    id="txtDeleteAccountPassword"
 | 
				
			||||||
                    ref="txtDeleteAccountPassword"
 | 
					                    ref="txtDeleteAccountPassword"
 | 
				
			||||||
                    v-model="password"
 | 
					                    v-model="password"
 | 
				
			||||||
                    v-on:keyup.enter="deleteAccount"
 | 
					 | 
				
			||||||
                    :placeholder="$t('login.password')"
 | 
					                    :placeholder="$t('login.password')"
 | 
				
			||||||
                    :aria-label="$t('login.password')"
 | 
					                    :aria-label="$t('login.password')"
 | 
				
			||||||
                    class="input w-auto mr-2"
 | 
					                    class="input mr-2 w-auto"
 | 
				
			||||||
                    type="password"
 | 
					                    type="password"
 | 
				
			||||||
 | 
					                    @keyup.enter="deleteAccount"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <a class="btn w-auto" @click="deleteAccount" v-t="'actions.delete_account'" />
 | 
					                <a v-t="'actions.delete_account'" class="btn w-auto" @click="deleteAccount" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
        <div class="pref">
 | 
					        <div class="pref">
 | 
				
			||||||
            <a class="btn w-auto" @click="logout" v-t="'actions.logout'" />
 | 
					            <a v-t="'actions.logout'" class="btn w-auto" @click="logout" />
 | 
				
			||||||
            <a
 | 
					            <a
 | 
				
			||||||
 | 
					                v-t="'actions.invalidate_session'"
 | 
				
			||||||
                class="btn w-auto"
 | 
					                class="btn w-auto"
 | 
				
			||||||
                style="margin-left: 0.5em"
 | 
					                style="margin-left: 0.5em"
 | 
				
			||||||
                @click="invalidateSession"
 | 
					                @click="invalidateSession"
 | 
				
			||||||
                v-t="'actions.invalidate_session'"
 | 
					 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <br />
 | 
					        <br />
 | 
				
			||||||
| 
						 | 
					@ -332,7 +342,7 @@
 | 
				
			||||||
                <th v-t="'preferences.instance_locations'" />
 | 
					                <th v-t="'preferences.instance_locations'" />
 | 
				
			||||||
                <th v-t="'preferences.has_cdn'" />
 | 
					                <th v-t="'preferences.has_cdn'" />
 | 
				
			||||||
                <th v-t="'preferences.registered_users'" />
 | 
					                <th v-t="'preferences.registered_users'" />
 | 
				
			||||||
                <th class="lt-md:hidden" v-t="'preferences.version'" />
 | 
					                <th v-t="'preferences.version'" class="lt-md:hidden" />
 | 
				
			||||||
                <th v-t="'preferences.up_to_date'" />
 | 
					                <th v-t="'preferences.up_to_date'" />
 | 
				
			||||||
                <th v-t="'preferences.ssl_score'" />
 | 
					                <th v-t="'preferences.ssl_score'" />
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
| 
						 | 
					@ -346,7 +356,7 @@
 | 
				
			||||||
                <td class="lt-md:hidden" v-text="instance.version" />
 | 
					                <td class="lt-md:hidden" v-text="instance.version" />
 | 
				
			||||||
                <td v-text="`${instance.up_to_date ? '✅' : '❌'}`" />
 | 
					                <td v-text="`${instance.up_to_date ? '✅' : '❌'}`" />
 | 
				
			||||||
                <td>
 | 
					                <td>
 | 
				
			||||||
                    <a :href="sslScore(instance.api_url)" target="_blank" v-t="'actions.view_ssl_score'" />
 | 
					                    <a v-t="'actions.view_ssl_score'" :href="sslScore(instance.api_url)" target="_blank" />
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
| 
						 | 
					@ -354,15 +364,15 @@
 | 
				
			||||||
    <br />
 | 
					    <br />
 | 
				
			||||||
    <p v-t="'info.preferences_note'" />
 | 
					    <p v-t="'info.preferences_note'" />
 | 
				
			||||||
    <br />
 | 
					    <br />
 | 
				
			||||||
    <button class="btn" v-t="'actions.reset_preferences'" @click="showConfirmResetPrefsDialog = true" />
 | 
					    <button v-t="'actions.reset_preferences'" class="btn" @click="showConfirmResetPrefsDialog = true" />
 | 
				
			||||||
    <button class="btn mx-4" v-t="'actions.backup_preferences'" @click="backupPreferences()" />
 | 
					    <button v-t="'actions.backup_preferences'" class="btn mx-4" @click="backupPreferences()" />
 | 
				
			||||||
    <label for="fileSelector" class="btn" v-t="'actions.restore_preferences'" @click="restorePreferences()" />
 | 
					    <label v-t="'actions.restore_preferences'" for="fileSelector" class="btn" @click="restorePreferences()" />
 | 
				
			||||||
    <input class="hidden" id="fileSelector" ref="fileSelector" type="file" @change="restorePreferences()" />
 | 
					    <input id="fileSelector" ref="fileSelector" class="hidden" type="file" @change="restorePreferences()" />
 | 
				
			||||||
    <ConfirmModal
 | 
					    <ConfirmModal
 | 
				
			||||||
        v-if="showConfirmResetPrefsDialog"
 | 
					        v-if="showConfirmResetPrefsDialog"
 | 
				
			||||||
 | 
					        :message="$t('actions.confirm_reset_preferences')"
 | 
				
			||||||
        @close="showConfirmResetPrefsDialog = false"
 | 
					        @close="showConfirmResetPrefsDialog = false"
 | 
				
			||||||
        @confirm="resetPreferences()"
 | 
					        @confirm="resetPreferences()"
 | 
				
			||||||
        :message="$t('actions.confirm_reset_preferences')"
 | 
					 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -370,6 +380,9 @@
 | 
				
			||||||
import CountryMap from "@/utils/CountryMaps/en.json";
 | 
					import CountryMap from "@/utils/CountryMaps/en.json";
 | 
				
			||||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
					import ConfirmModal from "./ConfirmModal.vue";
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					        ConfirmModal,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            mobileChapterLayout: "Vertical",
 | 
					            mobileChapterLayout: "Vertical",
 | 
				
			||||||
| 
						 | 
					@ -391,6 +404,7 @@ export default {
 | 
				
			||||||
            ]),
 | 
					            ]),
 | 
				
			||||||
            showMarkers: true,
 | 
					            showMarkers: true,
 | 
				
			||||||
            minSegmentLength: 0,
 | 
					            minSegmentLength: 0,
 | 
				
			||||||
 | 
					            dearrow: false,
 | 
				
			||||||
            selectedTheme: "dark",
 | 
					            selectedTheme: "dark",
 | 
				
			||||||
            autoPlayVideo: true,
 | 
					            autoPlayVideo: true,
 | 
				
			||||||
            autoDisplayCaptions: false,
 | 
					            autoDisplayCaptions: false,
 | 
				
			||||||
| 
						 | 
					@ -512,6 +526,7 @@ export default {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.showMarkers = this.getPreferenceBoolean("showMarkers", true);
 | 
					            this.showMarkers = this.getPreferenceBoolean("showMarkers", true);
 | 
				
			||||||
            this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
 | 
					            this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
 | 
				
			||||||
 | 
					            this.dearrow = this.getPreferenceBoolean("dearrow", false);
 | 
				
			||||||
            this.selectedTheme = this.getPreferenceString("theme", "dark");
 | 
					            this.selectedTheme = this.getPreferenceString("theme", "dark");
 | 
				
			||||||
            this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true);
 | 
					            this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true);
 | 
				
			||||||
            this.autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
 | 
					            this.autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
 | 
				
			||||||
| 
						 | 
					@ -570,6 +585,9 @@ export default {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                localStorage.setItem("showMarkers", this.showMarkers);
 | 
					                localStorage.setItem("showMarkers", this.showMarkers);
 | 
				
			||||||
                localStorage.setItem("minSegmentLength", this.minSegmentLength);
 | 
					                localStorage.setItem("minSegmentLength", this.minSegmentLength);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                localStorage.setItem("dearrow", this.dearrow);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                localStorage.setItem("theme", this.selectedTheme);
 | 
					                localStorage.setItem("theme", this.selectedTheme);
 | 
				
			||||||
                localStorage.setItem("playerAutoPlay", this.autoPlayVideo);
 | 
					                localStorage.setItem("playerAutoPlay", this.autoPlayVideo);
 | 
				
			||||||
                localStorage.setItem("autoDisplayCaptions", this.autoDisplayCaptions);
 | 
					                localStorage.setItem("autoDisplayCaptions", this.autoDisplayCaptions);
 | 
				
			||||||
| 
						 | 
					@ -657,9 +675,6 @@ export default {
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: {
 | 
					 | 
				
			||||||
        ConfirmModal,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/components/QrCode.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/components/QrCode.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,30 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <canvas ref="qrCodeCanvas" class="mx-auto my-2" />
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import QRCode from "qrcode";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					        text: {
 | 
				
			||||||
 | 
					            type: String,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    watch: {
 | 
				
			||||||
 | 
					        text() {
 | 
				
			||||||
 | 
					            this.generateQrCode();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mounted() {
 | 
				
			||||||
 | 
					        this.generateQrCode();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					        generateQrCode() {
 | 
				
			||||||
 | 
					            QRCode.toCanvas(this.$refs.qrCodeCanvas, this.text, error => {
 | 
				
			||||||
 | 
					                if (error) console.error(error);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -1,56 +1,79 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 v-t="'titles.register'" class="font-bold text-center my-4" />
 | 
					    <h1 v-t="'titles.register'" class="my-4 text-center font-bold" />
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
    <div class="text-center">
 | 
					    <div class="flex flex-col items-center justify-center text-center">
 | 
				
			||||||
        <form class="children:pb-3">
 | 
					        <form class="w-max items-center px-3 children:pb-3">
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                    v-model="username"
 | 
					                    v-model="username"
 | 
				
			||||||
                    class="input"
 | 
					                    class="input w-full"
 | 
				
			||||||
                    type="text"
 | 
					                    type="text"
 | 
				
			||||||
                    autocomplete="username"
 | 
					                    autocomplete="username"
 | 
				
			||||||
                    :placeholder="$t('login.username')"
 | 
					                    :placeholder="$t('login.username')"
 | 
				
			||||||
                    :aria-label="$t('login.username')"
 | 
					                    :aria-label="$t('login.username')"
 | 
				
			||||||
                    v-on:keyup.enter="register"
 | 
					                    @keyup.enter="register"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div>
 | 
					            <div class="flex justify-center">
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                    v-model="password"
 | 
					                    v-model="password"
 | 
				
			||||||
                    class="input"
 | 
					                    class="input w-full"
 | 
				
			||||||
                    type="password"
 | 
					                    :type="showPassword ? 'text' : 'password'"
 | 
				
			||||||
                    autocomplete="password"
 | 
					                    autocomplete="password"
 | 
				
			||||||
                    :placeholder="$t('login.password')"
 | 
					                    :placeholder="$t('login.password')"
 | 
				
			||||||
                    :aria-label="$t('login.password')"
 | 
					                    :aria-label="$t('login.password')"
 | 
				
			||||||
                    v-on:keyup.enter="register"
 | 
					                    @keyup.enter="register"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					                <button type="button" class="btn ml-2" @click="showPassword = !showPassword">
 | 
				
			||||||
 | 
					                    <div class="i-fa6-solid:eye" />
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="flex justify-center">
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    v-model="passwordConfirm"
 | 
				
			||||||
 | 
					                    class="input w-full"
 | 
				
			||||||
 | 
					                    :type="showConfirmPassword ? 'text' : 'password'"
 | 
				
			||||||
 | 
					                    autocomplete="password"
 | 
				
			||||||
 | 
					                    :placeholder="$t('login.password_confirm')"
 | 
				
			||||||
 | 
					                    :aria-label="$t('login.password_confirm')"
 | 
				
			||||||
 | 
					                    @keyup.enter="register"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <button type="button" class="btn ml-2" @click="showConfirmPassword = !showConfirmPassword">
 | 
				
			||||||
 | 
					                    <div class="i-fa6-solid:eye" />
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <a class="btn w-auto" @click="register" v-t="'titles.register'" />
 | 
					                <a v-t="'titles.register'" class="btn w-auto" @click="register" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <TooltipIcon icon="i-fa6-solid:circle-info" :tooltip="$t('info.register_note')" />
 | 
				
			||||||
        </form>
 | 
					        </form>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <ConfirmModal
 | 
					    <ConfirmModal
 | 
				
			||||||
        v-if="showUnsecureRegisterDialog"
 | 
					        v-if="showUnsecureRegisterDialog"
 | 
				
			||||||
 | 
					        :message="$t('info.register_no_email_note')"
 | 
				
			||||||
        @close="showUnsecureRegisterDialog = false"
 | 
					        @close="showUnsecureRegisterDialog = false"
 | 
				
			||||||
        @confirm="
 | 
					        @confirm="
 | 
				
			||||||
            forceUnsecureRegister = true;
 | 
					            forceUnsecureRegister = true;
 | 
				
			||||||
            showUnsecureRegisterDialog = false;
 | 
					            showUnsecureRegisterDialog = false;
 | 
				
			||||||
            register();
 | 
					            register();
 | 
				
			||||||
        "
 | 
					        "
 | 
				
			||||||
        :message="$t('info.register_no_email_note')"
 | 
					 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import { isEmail } from "../utils/Misc.js";
 | 
					import { isEmail } from "../utils/Misc.js";
 | 
				
			||||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
					import ConfirmModal from "./ConfirmModal.vue";
 | 
				
			||||||
 | 
					import TooltipIcon from "./TooltipIcon.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: { ConfirmModal, TooltipIcon },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            username: null,
 | 
					            username: null,
 | 
				
			||||||
            password: null,
 | 
					            password: null,
 | 
				
			||||||
 | 
					            passwordConfirm: null,
 | 
				
			||||||
 | 
					            showPassword: false,
 | 
				
			||||||
 | 
					            showConfirmPassword: false,
 | 
				
			||||||
            showUnsecureRegisterDialog: false,
 | 
					            showUnsecureRegisterDialog: false,
 | 
				
			||||||
            forceUnsecureRegister: false,
 | 
					            forceUnsecureRegister: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					@ -67,6 +90,10 @@ export default {
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        register() {
 | 
					        register() {
 | 
				
			||||||
            if (!this.username || !this.password) return;
 | 
					            if (!this.username || !this.password) return;
 | 
				
			||||||
 | 
					            if (this.password != this.passwordConfirm) {
 | 
				
			||||||
 | 
					                alert(this.$t("login.passwords_incorrect"));
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (isEmail(this.username) && !this.forceUnsecureRegister) {
 | 
					            if (isEmail(this.username) && !this.forceUnsecureRegister) {
 | 
				
			||||||
                this.showUnsecureRegisterDialog = true;
 | 
					                this.showUnsecureRegisterDialog = true;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
| 
						 | 
					@ -85,6 +112,5 @@ export default {
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: { ConfirmModal },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,11 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 class="text-center my-2" v-text="$route.query.search_query" />
 | 
					    <h1 class="my-2 text-center" v-text="$route.query.search_query" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <label for="ddlSearchFilters">
 | 
					    <label for="ddlSearchFilters">
 | 
				
			||||||
        <strong v-text="`${$t('actions.filter')}:`" />
 | 
					        <strong v-text="`${$t('actions.filter')}:`" />
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
    <select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
 | 
					    <select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
 | 
				
			||||||
        <option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`search.${filter}`" />
 | 
					        <option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
 | 
				
			||||||
    </select>
 | 
					    </select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
| 
						 | 
					@ -46,6 +46,7 @@ export default {
 | 
				
			||||||
                "music_videos",
 | 
					                "music_videos",
 | 
				
			||||||
                "music_albums",
 | 
					                "music_albums",
 | 
				
			||||||
                "music_playlists",
 | 
					                "music_playlists",
 | 
				
			||||||
 | 
					                "music_artists",
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            selectedFilter: this.$route.query.filter ?? "all",
 | 
					            selectedFilter: this.$route.query.filter ?? "all",
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="absolute suggestions-container">
 | 
					    <div class="suggestions-container absolute">
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
            <li
 | 
					            <li
 | 
				
			||||||
                v-for="(suggestion, i) in searchSuggestions"
 | 
					                v-for="(suggestion, i) in searchSuggestions"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,34 +3,45 @@
 | 
				
			||||||
        <h2 v-t="'actions.share'" />
 | 
					        <h2 v-t="'actions.share'" />
 | 
				
			||||||
        <div class="flex justify-between">
 | 
					        <div class="flex justify-between">
 | 
				
			||||||
            <label v-t="'actions.piped_link'" />
 | 
					            <label v-t="'actions.piped_link'" />
 | 
				
			||||||
            <input type="checkbox" v-model="pipedLink" @change="onChange" />
 | 
					            <input v-model="pipedLink" type="checkbox" @change="onChange" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div v-if="this.hasPlaylist" class="flex justify-between">
 | 
					        <div v-if="hasPlaylist" class="flex justify-between">
 | 
				
			||||||
            <label v-t="'actions.with_playlist'" />
 | 
					            <label v-t="'actions.with_playlist'" />
 | 
				
			||||||
            <input type="checkbox" v-model="withPlaylist" @change="onChange" />
 | 
					            <input v-model="withPlaylist" type="checkbox" @change="onChange" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="flex justify-between">
 | 
					        <div class="flex justify-between">
 | 
				
			||||||
            <label v-t="'actions.with_timecode'" for="withTimeCode" />
 | 
					            <label v-t="'actions.with_timecode'" for="withTimeCode" />
 | 
				
			||||||
            <input id="withTimeCode" type="checkbox" v-model="withTimeCode" @change="onChange" />
 | 
					            <input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div v-if="this.withTimeCode" class="flex justify-between mt-2">
 | 
					        <div v-if="withTimeCode" class="mt-2 flex items-center justify-between">
 | 
				
			||||||
            <label v-t="'actions.time_code'" />
 | 
					            <label v-t="'actions.time_code'" />
 | 
				
			||||||
            <input class="input w-12" type="text" v-model="timeStamp" />
 | 
					            <input v-model="timeStamp" class="input w-12" type="text" @change="onChange" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <a :href="generatedLink" target="_blank">
 | 
					        <a :href="generatedLink" target="_blank">
 | 
				
			||||||
            <h3 class="mt-4" v-text="generatedLink" />
 | 
					            <h3 class="mt-4" v-text="generatedLink" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <div class="flex justify-end mt-4">
 | 
					        <QrCode v-if="showQrCode" :text="generatedLink" />
 | 
				
			||||||
            <button class="btn" v-t="'actions.follow_link'" @click="followLink()" />
 | 
					        <div class="mt-4 flex justify-end">
 | 
				
			||||||
            <button class="btn ml-3" v-t="'actions.copy_link'" @click="copyLink()" />
 | 
					            <button v-t="'actions.generate_qrcode'" class="btn" @click="showQrCode = !showQrCode" />
 | 
				
			||||||
 | 
					            <button v-t="'actions.follow_link'" class="btn ml-3" @click="followLink()" />
 | 
				
			||||||
 | 
					            <button v-t="'actions.copy_link'" class="btn ml-3" @click="copyLink()" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </ModalComponent>
 | 
					    </ModalComponent>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup>
 | 
				
			||||||
 | 
					import { defineAsyncComponent } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const QrCode = defineAsyncComponent(() => import("./QrCode.vue"));
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import ModalComponent from "./ModalComponent.vue";
 | 
					import ModalComponent from "./ModalComponent.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					        ModalComponent,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
        videoId: {
 | 
					        videoId: {
 | 
				
			||||||
            type: String,
 | 
					            type: String,
 | 
				
			||||||
| 
						 | 
					@ -42,14 +53,13 @@ export default {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        playlistId: {
 | 
					        playlistId: {
 | 
				
			||||||
            type: String,
 | 
					            type: String,
 | 
				
			||||||
 | 
					            default: undefined,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        playlistIndex: {
 | 
					        playlistIndex: {
 | 
				
			||||||
            type: Number,
 | 
					            type: Number,
 | 
				
			||||||
 | 
					            default: undefined,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: {
 | 
					 | 
				
			||||||
        ModalComponent,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            withTimeCode: true,
 | 
					            withTimeCode: true,
 | 
				
			||||||
| 
						 | 
					@ -57,8 +67,23 @@ export default {
 | 
				
			||||||
            withPlaylist: true,
 | 
					            withPlaylist: true,
 | 
				
			||||||
            timeStamp: null,
 | 
					            timeStamp: null,
 | 
				
			||||||
            hasPlaylist: false,
 | 
					            hasPlaylist: false,
 | 
				
			||||||
 | 
					            showQrCode: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    computed: {
 | 
				
			||||||
 | 
					        generatedLink() {
 | 
				
			||||||
 | 
					            var baseUrl = this.pipedLink
 | 
				
			||||||
 | 
					                ? window.location.origin + "/watch?v=" + this.videoId
 | 
				
			||||||
 | 
					                : "https://youtu.be/" + this.videoId;
 | 
				
			||||||
 | 
					            var url = new URL(baseUrl);
 | 
				
			||||||
 | 
					            if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
 | 
				
			||||||
 | 
					            if (this.hasPlaylist && this.withPlaylist) {
 | 
				
			||||||
 | 
					                url.searchParams.append("list", this.playlistId);
 | 
				
			||||||
 | 
					                url.searchParams.append("index", this.playlistIndex);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return url.href;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        this.timeStamp = parseInt(this.currentTime);
 | 
					        this.timeStamp = parseInt(this.currentTime);
 | 
				
			||||||
        this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
 | 
					        this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
 | 
				
			||||||
| 
						 | 
					@ -87,19 +112,5 @@ export default {
 | 
				
			||||||
            this.setPreference("shareWithPlaylist", this.withPlaylist, true);
 | 
					            this.setPreference("shareWithPlaylist", this.withPlaylist, true);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    computed: {
 | 
					 | 
				
			||||||
        generatedLink() {
 | 
					 | 
				
			||||||
            var baseUrl = this.pipedLink
 | 
					 | 
				
			||||||
                ? window.location.origin + "/watch?v=" + this.videoId
 | 
					 | 
				
			||||||
                : "https://youtu.be/" + this.videoId;
 | 
					 | 
				
			||||||
            var url = new URL(baseUrl);
 | 
					 | 
				
			||||||
            if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
 | 
					 | 
				
			||||||
            if (this.hasPlaylist && this.withPlaylist) {
 | 
					 | 
				
			||||||
                url.searchParams.append("list", this.playlistId);
 | 
					 | 
				
			||||||
                url.searchParams.append("index", this.playlistIndex);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return url.href;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <label for="ddlSortBy" v-t="'actions.sort_by'" />
 | 
					    <label v-t="'actions.sort_by'" for="ddlSortBy" />
 | 
				
			||||||
    <select id="ddlSortBy" v-model="selectedSort" class="select flex-grow">
 | 
					    <select id="ddlSortBy" v-model="selectedSort" class="select flex-grow">
 | 
				
			||||||
        <option v-for="(value, key) in options" v-t="`actions.${key}`" :key="key" :value="value" />
 | 
					        <option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
 | 
				
			||||||
    </select>
 | 
					    </select>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,10 @@ const options = {
 | 
				
			||||||
const selectedSort = ref("descending");
 | 
					const selectedSort = ref("descending");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    byKey: String,
 | 
					    byKey: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits(["apply"]);
 | 
					const emit = defineEmits(["apply"]);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,30 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 class="font-bold text-center my-4" v-t="'titles.subscriptions'" />
 | 
					    <h1 v-t="'titles.subscriptions'" class="my-4 text-center font-bold" />
 | 
				
			||||||
    <!-- import / export section -->
 | 
					    <!-- import / export section -->
 | 
				
			||||||
    <div class="flex justify-between w-full">
 | 
					    <div class="w-full flex justify-between">
 | 
				
			||||||
        <div class="flex">
 | 
					        <div class="flex gap-2">
 | 
				
			||||||
            <button class="btn mx-1">
 | 
					            <button class="btn">
 | 
				
			||||||
                <router-link to="/import" v-t="'actions.import_from_json'" />
 | 
					                <router-link v-t="'actions.import_from_json_csv'" to="/import" />
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <button class="btn" @click="exportHandler" v-t="'actions.export_to_json'" />
 | 
					            <button v-t="'actions.export_to_json'" class="btn" @click="exportHandler" />
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					                id="fileSelector"
 | 
				
			||||||
 | 
					                ref="fileSelector"
 | 
				
			||||||
 | 
					                type="file"
 | 
				
			||||||
 | 
					                class="display-none"
 | 
				
			||||||
 | 
					                multiple="multiple"
 | 
				
			||||||
 | 
					                @change="importGroupsHandler"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <label
 | 
				
			||||||
 | 
					                for="fileSelector"
 | 
				
			||||||
 | 
					                class="btn"
 | 
				
			||||||
 | 
					                v-text="`${$t('actions.import_from_json')} (${$t('titles.channel_groups')})`"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <button
 | 
				
			||||||
 | 
					                class="btn"
 | 
				
			||||||
 | 
					                @click="exportGroupsHandler"
 | 
				
			||||||
 | 
					                v-text="`${$t('actions.export_to_json')} (${$t('titles.channel_groups')})`"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <!-- subscriptions count, only shown if there are any  -->
 | 
					        <!-- subscriptions count, only shown if there are any  -->
 | 
				
			||||||
        <i18n-t v-if="subscriptions.length > 0" keypath="subscriptions.subscribed_channels_count">{{
 | 
					        <i18n-t v-if="subscriptions.length > 0" keypath="subscriptions.subscribed_channels_count">{{
 | 
				
			||||||
| 
						 | 
					@ -18,10 +36,10 @@
 | 
				
			||||||
    <div class="w-full flex flex-wrap">
 | 
					    <div class="w-full flex flex-wrap">
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
            v-for="group in channelGroups"
 | 
					            v-for="group in channelGroups"
 | 
				
			||||||
 | 
					            :key="group.groupName"
 | 
				
			||||||
            class="btn mx-1 w-max"
 | 
					            class="btn mx-1 w-max"
 | 
				
			||||||
            :class="{ selected: selectedGroup === group }"
 | 
					            :class="{ selected: selectedGroup === group }"
 | 
				
			||||||
            :key="group.groupName"
 | 
					            @click="selectGroup(group)"
 | 
				
			||||||
            @click="selectedGroup = group"
 | 
					 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
 | 
					            <span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
 | 
				
			||||||
            <div v-if="group.groupName != '' && selectedGroup == group">
 | 
					            <div v-if="group.groupName != '' && selectedGroup == group">
 | 
				
			||||||
| 
						 | 
					@ -39,19 +57,19 @@
 | 
				
			||||||
    <div class="xl:grid xl:grid-cols-5 <md:flex-wrap">
 | 
					    <div class="xl:grid xl:grid-cols-5 <md:flex-wrap">
 | 
				
			||||||
        <!-- channel info card -->
 | 
					        <!-- channel info card -->
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
            class="col m-2 p-1 border rounded-lg border-gray-500"
 | 
					 | 
				
			||||||
            v-for="subscription in filteredSubscriptions"
 | 
					            v-for="subscription in filteredSubscriptions"
 | 
				
			||||||
            :key="subscription.url"
 | 
					            :key="subscription.url"
 | 
				
			||||||
 | 
					            class="col m-2 border border-gray-500 rounded-lg p-1"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <router-link :to="subscription.url" class="flex p-2 font-bold text-4x4">
 | 
					            <router-link :to="subscription.url" class="text-4x4 flex p-2 font-bold">
 | 
				
			||||||
                <img :src="subscription.avatar" class="rounded-full h-[fit-content]" width="48" height="48" />
 | 
					                <img :src="subscription.avatar" class="h-[fit-content] rounded-full" width="48" height="48" />
 | 
				
			||||||
                <span class="self-center mx-2" v-text="subscription.name" />
 | 
					                <span class="mx-2 self-center" v-text="subscription.name" />
 | 
				
			||||||
            </router-link>
 | 
					            </router-link>
 | 
				
			||||||
            <!-- subscribe / unsubscribe btn -->
 | 
					            <!-- subscribe / unsubscribe btn -->
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
                class="btn w-full mt-2"
 | 
					 | 
				
			||||||
                @click="handleButton(subscription)"
 | 
					 | 
				
			||||||
                v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
 | 
					                v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
 | 
				
			||||||
 | 
					                class="btn mt-2 w-full"
 | 
				
			||||||
 | 
					                @click="handleButton(subscription)"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					@ -60,17 +78,23 @@
 | 
				
			||||||
    <ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
 | 
					    <ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
 | 
				
			||||||
        <h2 v-t="'actions.create_group'" />
 | 
					        <h2 v-t="'actions.create_group'" />
 | 
				
			||||||
        <div class="flex flex-col">
 | 
					        <div class="flex flex-col">
 | 
				
			||||||
            <input class="input my-4" type="text" v-model="newGroupName" :placeholder="$t('actions.group_name')" />
 | 
					            <input v-model="newGroupName" class="input my-4" type="text" :placeholder="$t('actions.group_name')" />
 | 
				
			||||||
            <button class="ml-auto btn w-max" v-t="'actions.create_group'" @click="createGroup()" />
 | 
					            <button v-t="'actions.create_group'" class="btn ml-auto w-max" @click="createGroup()" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </ModalComponent>
 | 
					    </ModalComponent>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false">
 | 
					    <ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false">
 | 
				
			||||||
        <h2>{{ selectedGroup.groupName }}</h2>
 | 
					        <div class="mb-5 mt-3 flex justify-between">
 | 
				
			||||||
        <div class="flex flex-col mt-3 mb-2 overflow-y-scroll h-70">
 | 
					            <input v-model="editedGroupName" type="text" class="input" />
 | 
				
			||||||
 | 
					            <button v-t="'actions.okay'" class="btn" :placeholder="$t('actions.group_name')" @click="editGroupName()" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mb-2 mt-3 h-70 flex flex-col overflow-y-scroll">
 | 
				
			||||||
            <div v-for="subscription in subscriptions" :key="subscription.name">
 | 
					            <div v-for="subscription in subscriptions" :key="subscription.name">
 | 
				
			||||||
                <div class="flex justify-between mr-3">
 | 
					                <div class="mr-3 flex items-center justify-between">
 | 
				
			||||||
                    <span>{{ subscription.name }}</span>
 | 
					                    <a :href="subscription.url" target="_blank" class="flex items-center overflow-hidden">
 | 
				
			||||||
 | 
					                        <img :src="subscription.avatar" class="h-8 w-8 rounded-full" />
 | 
				
			||||||
 | 
					                        <span class="ml-2">{{ subscription.name }}</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
                    <input
 | 
					                    <input
 | 
				
			||||||
                        type="checkbox"
 | 
					                        type="checkbox"
 | 
				
			||||||
                        class="checkbox"
 | 
					                        class="checkbox"
 | 
				
			||||||
| 
						 | 
					@ -88,6 +112,7 @@
 | 
				
			||||||
import ModalComponent from "./ModalComponent.vue";
 | 
					import ModalComponent from "./ModalComponent.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: { ModalComponent },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            subscriptions: [],
 | 
					            subscriptions: [],
 | 
				
			||||||
| 
						 | 
					@ -99,8 +124,16 @@ export default {
 | 
				
			||||||
            showCreateGroupModal: false,
 | 
					            showCreateGroupModal: false,
 | 
				
			||||||
            showEditGroupModal: false,
 | 
					            showEditGroupModal: false,
 | 
				
			||||||
            newGroupName: "",
 | 
					            newGroupName: "",
 | 
				
			||||||
 | 
					            editedGroupName: "",
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    computed: {
 | 
				
			||||||
 | 
					        filteredSubscriptions(_this) {
 | 
				
			||||||
 | 
					            return _this.selectedGroup.groupName == ""
 | 
				
			||||||
 | 
					                ? _this.subscriptions
 | 
				
			||||||
 | 
					                : _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        this.fetchSubscriptions().then(json => {
 | 
					        this.fetchSubscriptions().then(json => {
 | 
				
			||||||
            this.subscriptions = json;
 | 
					            this.subscriptions = json;
 | 
				
			||||||
| 
						 | 
					@ -110,18 +143,8 @@ export default {
 | 
				
			||||||
        this.channelGroups.push(this.selectedGroup);
 | 
					        this.channelGroups.push(this.selectedGroup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!window.db) return;
 | 
					        if (!window.db) return;
 | 
				
			||||||
        const cursor = this.getChannelGroupsCursor();
 | 
					
 | 
				
			||||||
        cursor.onsuccess = e => {
 | 
					        this.loadChannelGroups();
 | 
				
			||||||
            const cursor = e.target.result;
 | 
					 | 
				
			||||||
            if (cursor) {
 | 
					 | 
				
			||||||
                const group = cursor.value;
 | 
					 | 
				
			||||||
                this.channelGroups.push({
 | 
					 | 
				
			||||||
                    groupName: group.groupName,
 | 
					 | 
				
			||||||
                    channels: JSON.parse(group.channels),
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                cursor.continue();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
        document.title = "Subscriptions - Piped";
 | 
					        document.title = "Subscriptions - Piped";
 | 
				
			||||||
| 
						 | 
					@ -140,6 +163,10 @@ export default {
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        async loadChannelGroups() {
 | 
				
			||||||
 | 
					            const groups = await this.getChannelGroups();
 | 
				
			||||||
 | 
					            this.channelGroups.push(...groups);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        handleButton(subscription) {
 | 
					        handleButton(subscription) {
 | 
				
			||||||
            const channelId = subscription.url.split("/")[2];
 | 
					            const channelId = subscription.url.split("/")[2];
 | 
				
			||||||
            if (this.authenticated) {
 | 
					            if (this.authenticated) {
 | 
				
			||||||
| 
						 | 
					@ -174,6 +201,10 @@ export default {
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            this.download(json, "subscriptions.json", "application/json");
 | 
					            this.download(json, "subscriptions.json", "application/json");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        selectGroup(group) {
 | 
				
			||||||
 | 
					            this.selectedGroup = group;
 | 
				
			||||||
 | 
					            this.editedGroupName = group.groupName;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        createGroup() {
 | 
					        createGroup() {
 | 
				
			||||||
            if (!this.newGroupName || this.channelGroups.some(group => group.groupName == this.newGroupName)) return;
 | 
					            if (!this.newGroupName || this.channelGroups.some(group => group.groupName == this.newGroupName)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -188,6 +219,21 @@ export default {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.showCreateGroupModal = false;
 | 
					            this.showCreateGroupModal = false;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        editGroupName() {
 | 
				
			||||||
 | 
					            const oldGroupName = this.selectedGroup.groupName;
 | 
				
			||||||
 | 
					            const newGroupName = this.editedGroupName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // the group mustn't yet exist and the name can't be empty
 | 
				
			||||||
 | 
					            if (!newGroupName || newGroupName == oldGroupName) return;
 | 
				
			||||||
 | 
					            if (this.channelGroups.some(group => group.groupName == newGroupName)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // create a new group with the same info and delete the old one
 | 
				
			||||||
 | 
					            this.selectedGroup.groupName = newGroupName;
 | 
				
			||||||
 | 
					            this.createOrUpdateChannelGroup(this.selectedGroup);
 | 
				
			||||||
 | 
					            this.deleteChannelGroup(oldGroupName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.showEditGroupModal = false;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        deleteGroup(group) {
 | 
					        deleteGroup(group) {
 | 
				
			||||||
            this.deleteChannelGroup(group.groupName);
 | 
					            this.deleteChannelGroup(group.groupName);
 | 
				
			||||||
            this.channelGroups = this.channelGroups.filter(g => g != group);
 | 
					            this.channelGroups = this.channelGroups.filter(g => g != group);
 | 
				
			||||||
| 
						 | 
					@ -200,15 +246,25 @@ export default {
 | 
				
			||||||
                : this.selectedGroup.channels.concat(channelId);
 | 
					                : this.selectedGroup.channels.concat(channelId);
 | 
				
			||||||
            this.createOrUpdateChannelGroup(this.selectedGroup);
 | 
					            this.createOrUpdateChannelGroup(this.selectedGroup);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					        async importGroupsHandler() {
 | 
				
			||||||
    computed: {
 | 
					            const files = this.$refs.fileSelector.files;
 | 
				
			||||||
        filteredSubscriptions(_this) {
 | 
					            for (let file of files) {
 | 
				
			||||||
            return _this.selectedGroup.groupName == ""
 | 
					                const groups = JSON.parse(await file.text()).groups;
 | 
				
			||||||
                ? _this.subscriptions
 | 
					                for (let group of groups) {
 | 
				
			||||||
                : _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
 | 
					                    this.createOrUpdateChannelGroup(group);
 | 
				
			||||||
 | 
					                    this.channelGroups.push(group);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        exportGroupsHandler() {
 | 
				
			||||||
 | 
					            const json = {
 | 
				
			||||||
 | 
					                format: "Piped",
 | 
				
			||||||
 | 
					                version: 1,
 | 
				
			||||||
 | 
					                groups: this.channelGroups.slice(1),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            this.download(JSON.stringify(json), "channel_groups.json", "application/json");
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: { ModalComponent },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,13 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="toast">
 | 
					    <div class="toast">
 | 
				
			||||||
        <slot />
 | 
					        <slot />
 | 
				
			||||||
        <button @click="dismiss" v-t="'actions.dismiss'" />
 | 
					        <button v-t="'actions.dismiss'" @click="dismiss" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    emits: ["dismissed"],
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        dismiss() {
 | 
					        dismiss() {
 | 
				
			||||||
            this.$emit("dismissed");
 | 
					            this.$emit("dismissed");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/components/TooltipIcon.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/TooltipIcon.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div id="container" class="w-full">
 | 
				
			||||||
 | 
					        <div :class="icon" class="cursor-pointer"></div>
 | 
				
			||||||
 | 
					        <p id="tooltip" class="absolute mr-[20vw] mt-2 hidden rounded-l bg-gray-800 px-2 py-1 text-gray-200">
 | 
				
			||||||
 | 
					            {{ tooltip }}
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					        icon: {
 | 
				
			||||||
 | 
					            type: String, // the class name of a font awesome icon
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        tooltip: {
 | 
				
			||||||
 | 
					            type: String,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					#container:hover #tooltip {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <h1 v-t="'titles.trending'" class="font-bold text-center my-4" />
 | 
					    <h1 v-t="'titles.trending'" class="my-4 text-center font-bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <hr />
 | 
					    <hr />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,21 +29,15 @@ export default {
 | 
				
			||||||
        this.fetchTrending(region).then(videos => {
 | 
					        this.fetchTrending(region).then(videos => {
 | 
				
			||||||
            this.videos = videos;
 | 
					            this.videos = videos;
 | 
				
			||||||
            this.updateWatched(this.videos);
 | 
					            this.updateWatched(this.videos);
 | 
				
			||||||
 | 
					            this.fetchDeArrowContent(this.videos);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    activated() {
 | 
					    activated() {
 | 
				
			||||||
        document.title = this.$t("titles.trending") + " - Piped";
 | 
					        document.title = this.$t("titles.trending") + " - Piped";
 | 
				
			||||||
        if (this.videos.length > 0) this.updateWatched(this.videos);
 | 
					        if (this.videos.length > 0) this.updateWatched(this.videos);
 | 
				
			||||||
        if (this.$route.path == "/") {
 | 
					        if (this.$route.path == "/") {
 | 
				
			||||||
            switch (this.getPreferenceString("homepage", "trending")) {
 | 
					            let homepage = this.getHomePage(this);
 | 
				
			||||||
                case "trending":
 | 
					            if (homepage !== undefined) this.$router.push(homepage);
 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                case "feed":
 | 
					 | 
				
			||||||
                    this.$router.push("/feed");
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                default:
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div v-if="showVideo">
 | 
					    <div v-if="showVideo" class="flex flex-col flex-justify-between">
 | 
				
			||||||
        <router-link
 | 
					        <router-link
 | 
				
			||||||
            class="focus:underline hover:underline inline-block w-full"
 | 
					            class="inline-block w-full focus:underline hover:underline"
 | 
				
			||||||
            :to="{
 | 
					            :to="{
 | 
				
			||||||
                path: '/watch',
 | 
					                path: '/watch',
 | 
				
			||||||
                query: {
 | 
					                query: {
 | 
				
			||||||
| 
						 | 
					@ -13,17 +13,17 @@
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            <div class="w-full">
 | 
					            <div class="w-full">
 | 
				
			||||||
                <img
 | 
					                <img
 | 
				
			||||||
                    class="w-full aspect-video object-contain"
 | 
					                    class="aspect-video w-full object-contain"
 | 
				
			||||||
                    :src="item.thumbnail"
 | 
					                    :src="thumbnail"
 | 
				
			||||||
                    :alt="item.title"
 | 
					                    :alt="title"
 | 
				
			||||||
                    :class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
 | 
					                    :class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
 | 
				
			||||||
                    loading="lazy"
 | 
					                    loading="lazy"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <!-- progress bar -->
 | 
					                <!-- progress bar -->
 | 
				
			||||||
                <div class="relative w-full h-1">
 | 
					                <div class="relative h-1 w-full">
 | 
				
			||||||
                    <div
 | 
					                    <div
 | 
				
			||||||
                        class="absolute bottom-0 left-0 h-1 bg-red-600"
 | 
					 | 
				
			||||||
                        v-if="item.watched && item.duration > 0"
 | 
					                        v-if="item.watched && item.duration > 0"
 | 
				
			||||||
 | 
					                        class="absolute bottom-0 left-0 h-1 bg-red-600"
 | 
				
			||||||
                        :style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
 | 
					                        :style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					@ -31,29 +31,29 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div class="relative text-sm">
 | 
					            <div class="relative text-sm">
 | 
				
			||||||
                <span
 | 
					                <span
 | 
				
			||||||
                    class="thumbnail-overlay thumbnail-right"
 | 
					 | 
				
			||||||
                    v-if="item.duration > 0"
 | 
					                    v-if="item.duration > 0"
 | 
				
			||||||
 | 
					                    class="thumbnail-overlay thumbnail-right"
 | 
				
			||||||
                    v-text="timeFormat(item.duration)"
 | 
					                    v-text="timeFormat(item.duration)"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <!-- shorts thumbnail -->
 | 
					                <!-- shorts thumbnail -->
 | 
				
			||||||
                <span class="thumbnail-overlay thumbnail-left" v-if="item.isShort" v-t="'video.shorts'" />
 | 
					                <span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
 | 
				
			||||||
                <span
 | 
					                <span
 | 
				
			||||||
                    class="thumbnail-overlay thumbnail-right"
 | 
					 | 
				
			||||||
                    v-else-if="item.duration >= 0"
 | 
					                    v-else-if="item.duration >= 0"
 | 
				
			||||||
 | 
					                    class="thumbnail-overlay thumbnail-right"
 | 
				
			||||||
                    v-text="timeFormat(item.duration)"
 | 
					                    v-text="timeFormat(item.duration)"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
 | 
					                <i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
 | 
				
			||||||
                    <font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
 | 
					                    <font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
 | 
				
			||||||
                </i18n-t>
 | 
					                </i18n-t>
 | 
				
			||||||
                <span v-if="item.watched" class="thumbnail-overlay bottom-5px left-5px" v-t="'video.watched'" />
 | 
					                <span v-if="item.watched" v-t="'video.watched'" class="thumbnail-overlay bottom-5px left-5px" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <p
 | 
					                <p
 | 
				
			||||||
                    style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
					                    style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
				
			||||||
                    class="pt-2 overflow-hidden flex link font-bold"
 | 
					                    class="link flex overflow-hidden pt-2 font-bold"
 | 
				
			||||||
                    :title="item.title"
 | 
					                    :title="title"
 | 
				
			||||||
                    v-text="item.title"
 | 
					                    v-text="title"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
| 
						 | 
					@ -64,24 +64,24 @@
 | 
				
			||||||
                    v-if="item.uploaderAvatar"
 | 
					                    v-if="item.uploaderAvatar"
 | 
				
			||||||
                    :src="item.uploaderAvatar"
 | 
					                    :src="item.uploaderAvatar"
 | 
				
			||||||
                    loading="lazy"
 | 
					                    loading="lazy"
 | 
				
			||||||
                    class="rounded-full mr-0.5 mt-0.5 w-32px h-32px"
 | 
					                    class="mr-0.5 mt-0.5 h-32px w-32px rounded-full"
 | 
				
			||||||
                    width="68"
 | 
					                    width="68"
 | 
				
			||||||
                    height="68"
 | 
					                    height="68"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </router-link>
 | 
					            </router-link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div class="px-2 flex-1">
 | 
					            <div class="flex-1 px-2">
 | 
				
			||||||
                <router-link
 | 
					                <router-link
 | 
				
			||||||
                    v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
 | 
					                    v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
 | 
				
			||||||
                    class="link-secondary overflow-hidden block text-sm"
 | 
					                    class="link-secondary block overflow-hidden text-sm"
 | 
				
			||||||
                    :to="item.uploaderUrl"
 | 
					                    :to="item.uploaderUrl"
 | 
				
			||||||
                    :title="item.uploaderName"
 | 
					                    :title="item.uploaderName"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <span v-text="item.uploaderName" />
 | 
					                    <span v-text="item.uploaderName" />
 | 
				
			||||||
                    <font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
 | 
					                    <font-awesome-icon v-if="item.uploaderVerified" class="ml-1.5" icon="check" />
 | 
				
			||||||
                </router-link>
 | 
					                </router-link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div v-if="item.views >= 0 || item.uploadedDate" class="text-xs font-normal text-gray-300 mt-1">
 | 
					                <div v-if="item.views >= 0 || item.uploadedDate" class="mt-1 text-xs font-normal text-gray-300">
 | 
				
			||||||
                    <span v-if="item.views >= 0">
 | 
					                    <span v-if="item.views >= 0">
 | 
				
			||||||
                        <font-awesome-icon icon="eye" />
 | 
					                        <font-awesome-icon icon="eye" />
 | 
				
			||||||
                        <span class="pl-1" v-text="`${numberFormat(item.views)} •`" />
 | 
					                        <span class="pl-1" v-text="`${numberFormat(item.views)} •`" />
 | 
				
			||||||
| 
						 | 
					@ -102,45 +102,45 @@
 | 
				
			||||||
                            listen: '1',
 | 
					                            listen: '1',
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                    }"
 | 
					                    }"
 | 
				
			||||||
                    :aria-label="'Listen to ' + item.title"
 | 
					                    :aria-label="'Listen to ' + title"
 | 
				
			||||||
                    :title="'Listen to ' + item.title"
 | 
					                    :title="'Listen to ' + title"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <font-awesome-icon icon="headphones" />
 | 
					                    <font-awesome-icon icon="headphones" />
 | 
				
			||||||
                </router-link>
 | 
					                </router-link>
 | 
				
			||||||
                <button v-if="authenticated" :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
 | 
					                <button :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
 | 
				
			||||||
                    <font-awesome-icon icon="circle-plus" />
 | 
					                    <font-awesome-icon icon="circle-plus" />
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <button
 | 
					                <button
 | 
				
			||||||
                    v-if="admin"
 | 
					                    v-if="admin"
 | 
				
			||||||
                    :title="$t('actions.remove_from_playlist')"
 | 
					 | 
				
			||||||
                    ref="removeButton"
 | 
					                    ref="removeButton"
 | 
				
			||||||
 | 
					                    :title="$t('actions.remove_from_playlist')"
 | 
				
			||||||
                    @click="showConfirmRemove = true"
 | 
					                    @click="showConfirmRemove = true"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <font-awesome-icon icon="circle-minus" />
 | 
					                    <font-awesome-icon icon="circle-minus" />
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <ConfirmModal
 | 
					                <ConfirmModal
 | 
				
			||||||
                    v-if="showConfirmRemove"
 | 
					                    v-if="showConfirmRemove"
 | 
				
			||||||
 | 
					                    :message="$t('actions.delete_playlist_video_confirm')"
 | 
				
			||||||
                    @close="showConfirmRemove = false"
 | 
					                    @close="showConfirmRemove = false"
 | 
				
			||||||
                    @confirm="removeVideo(item.url.substr(-11))"
 | 
					                    @confirm="removeVideo(item.url.substr(-11))"
 | 
				
			||||||
                    :message="$t('actions.delete_playlist_video_confirm')"
 | 
					 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <PlaylistAddModal v-if="showModal" :video-id="item.url.substr(-11)" @close="showModal = !showModal" />
 | 
					                <PlaylistAddModal
 | 
				
			||||||
 | 
					                    v-if="showModal"
 | 
				
			||||||
 | 
					                    :video-id="item.url.substr(-11)"
 | 
				
			||||||
 | 
					                    :video-info="item"
 | 
				
			||||||
 | 
					                    @close="showModal = !showModal"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
.shorts-img {
 | 
					 | 
				
			||||||
    @apply w-full object-contain;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import PlaylistAddModal from "./PlaylistAddModal.vue";
 | 
					import PlaylistAddModal from "./PlaylistAddModal.vue";
 | 
				
			||||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
					import ConfirmModal from "./ConfirmModal.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    components: { PlaylistAddModal, ConfirmModal },
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
        item: {
 | 
					        item: {
 | 
				
			||||||
            type: Object,
 | 
					            type: Object,
 | 
				
			||||||
| 
						 | 
					@ -159,6 +159,7 @@ export default {
 | 
				
			||||||
        playlistId: { type: String, default: null },
 | 
					        playlistId: { type: String, default: null },
 | 
				
			||||||
        admin: { type: Boolean, default: false },
 | 
					        admin: { type: Boolean, default: false },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    emits: ["remove"],
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            showModal: false,
 | 
					            showModal: false,
 | 
				
			||||||
| 
						 | 
					@ -166,23 +167,21 @@ export default {
 | 
				
			||||||
            showConfirmRemove: false,
 | 
					            showConfirmRemove: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    computed: {
 | 
				
			||||||
 | 
					        title() {
 | 
				
			||||||
 | 
					            return this.item.dearrow?.titles[0]?.title ?? this.item.title;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        thumbnail() {
 | 
				
			||||||
 | 
					            return this.item.dearrow?.thumbnails[0]?.thumbnail ?? this.item.thumbnail;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        this.shouldShowVideo();
 | 
					        this.shouldShowVideo();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        removeVideo() {
 | 
					        removeVideo() {
 | 
				
			||||||
            this.$refs.removeButton.disabled = true;
 | 
					            this.$refs.removeButton.disabled = true;
 | 
				
			||||||
            this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
 | 
					            this.removeVideoFromPlaylist(this.playlistId, this.index).then(json => {
 | 
				
			||||||
                method: "POST",
 | 
					 | 
				
			||||||
                body: JSON.stringify({
 | 
					 | 
				
			||||||
                    playlistId: this.playlistId,
 | 
					 | 
				
			||||||
                    index: this.index,
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                headers: {
 | 
					 | 
				
			||||||
                    Authorization: this.getAuthToken(),
 | 
					 | 
				
			||||||
                    "Content-Type": "application/json",
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }).then(json => {
 | 
					 | 
				
			||||||
                if (json.error) alert(json.error);
 | 
					                if (json.error) alert(json.error);
 | 
				
			||||||
                else this.$emit("remove");
 | 
					                else this.$emit("remove");
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
| 
						 | 
					@ -201,6 +200,11 @@ export default {
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: { PlaylistAddModal, ConfirmModal },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					.shorts-img {
 | 
				
			||||||
 | 
					    @apply w-full object-contain;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,18 @@
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
        ref="container"
 | 
					        ref="container"
 | 
				
			||||||
        data-shaka-player-container
 | 
					        data-shaka-player-container
 | 
				
			||||||
        class="w-full max-h-screen flex justify-center"
 | 
					        class="relative max-h-screen w-full flex justify-center"
 | 
				
			||||||
        :class="{ 'player-container': !isEmbed }"
 | 
					        :class="{ 'player-container': !isEmbed }"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        <video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
 | 
					        <video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
 | 
				
			||||||
        <canvas id="preview" />
 | 
					        <span
 | 
				
			||||||
 | 
					            id="preview-container"
 | 
				
			||||||
 | 
					            ref="previewContainer"
 | 
				
			||||||
 | 
					            class="absolute bottom-0 z-[2000] mb-[3.5%] hidden flex-col items-center"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <canvas id="preview" ref="preview" class="rounded-sm" />
 | 
				
			||||||
 | 
					            <span class="mt-2 w-min rounded-xl bg-dark-700 px-2 pb-1 pt-1.5 text-sm" v-text="timeFormat(currentTime)" />
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
            v-if="inSegment"
 | 
					            v-if="inSegment"
 | 
				
			||||||
            class="skip-segment-button"
 | 
					            class="skip-segment-button"
 | 
				
			||||||
| 
						 | 
					@ -18,6 +25,11 @@
 | 
				
			||||||
            <span v-t="'actions.skip_segment'" />
 | 
					            <span v-t="'actions.skip_segment'" />
 | 
				
			||||||
            <i class="material-icons-round">skip_next</i>
 | 
					            <i class="material-icons-round">skip_next</i>
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
 | 
					        <span
 | 
				
			||||||
 | 
					            v-if="error > 0"
 | 
				
			||||||
 | 
					            v-t="{ path: 'player.failed', args: [error] }"
 | 
				
			||||||
 | 
					            class="absolute top-8 rounded bg-black/80 p-2 text-lg backdrop-blur-sm"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,7 +62,7 @@ export default {
 | 
				
			||||||
        selectedAutoLoop: Boolean,
 | 
					        selectedAutoLoop: Boolean,
 | 
				
			||||||
        isEmbed: Boolean,
 | 
					        isEmbed: Boolean,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    emits: ["timeupdate"],
 | 
					    emits: ["timeupdate", "ended", "navigateNext"],
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            lastUpdate: new Date().getTime(),
 | 
					            lastUpdate: new Date().getTime(),
 | 
				
			||||||
| 
						 | 
					@ -58,6 +70,9 @@ export default {
 | 
				
			||||||
            destroying: false,
 | 
					            destroying: false,
 | 
				
			||||||
            inSegment: false,
 | 
					            inSegment: false,
 | 
				
			||||||
            isHoveringTimebar: false,
 | 
					            isHoveringTimebar: false,
 | 
				
			||||||
 | 
					            currentTime: 0,
 | 
				
			||||||
 | 
					            seekbarPadding: 2,
 | 
				
			||||||
 | 
					            error: 0,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    computed: {
 | 
					    computed: {
 | 
				
			||||||
| 
						 | 
					@ -182,7 +197,7 @@ export default {
 | 
				
			||||||
                            e.preventDefault();
 | 
					                            e.preventDefault();
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        case "shift+n":
 | 
					                        case "shift+n":
 | 
				
			||||||
                            self.navigateNext();
 | 
					                            self.$emit("navigateNext");
 | 
				
			||||||
                            e.preventDefault();
 | 
					                            e.preventDefault();
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        case "shift+,":
 | 
					                        case "shift+,":
 | 
				
			||||||
| 
						 | 
					@ -457,14 +472,7 @@ export default {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
 | 
					                this.$ui = new shaka.ui.Overlay(localPlayer, this.$refs.container, videoEl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const overflowMenuButtons = [
 | 
					                const overflowMenuButtons = ["quality", "captions", "picture_in_picture", "playback_rate", "airplay"];
 | 
				
			||||||
                    "quality",
 | 
					 | 
				
			||||||
                    "language",
 | 
					 | 
				
			||||||
                    "captions",
 | 
					 | 
				
			||||||
                    "picture_in_picture",
 | 
					 | 
				
			||||||
                    "playback_rate",
 | 
					 | 
				
			||||||
                    "airplay",
 | 
					 | 
				
			||||||
                ];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (this.isEmbed) {
 | 
					                if (this.isEmbed) {
 | 
				
			||||||
                    overflowMenuButtons.push("open_new_tab");
 | 
					                    overflowMenuButtons.push("open_new_tab");
 | 
				
			||||||
| 
						 | 
					@ -473,9 +481,9 @@ export default {
 | 
				
			||||||
                const config = {
 | 
					                const config = {
 | 
				
			||||||
                    overflowMenuButtons: overflowMenuButtons,
 | 
					                    overflowMenuButtons: overflowMenuButtons,
 | 
				
			||||||
                    seekBarColors: {
 | 
					                    seekBarColors: {
 | 
				
			||||||
                        base: "rgba(255, 255, 255, 0.3)",
 | 
					                        base: "var(--player-base)",
 | 
				
			||||||
                        buffered: "rgba(255, 255, 255, 0.54)",
 | 
					                        buffered: "var(--player-buffered)",
 | 
				
			||||||
                        played: "rgb(255, 0, 0)",
 | 
					                        played: "var(--player-played)",
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -501,6 +509,9 @@ export default {
 | 
				
			||||||
                manifest: {
 | 
					                manifest: {
 | 
				
			||||||
                    disableVideo: disableVideo,
 | 
					                    disableVideo: disableVideo,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                streaming: {
 | 
				
			||||||
 | 
					                    segmentPrefetchLimit: 10,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const quality = this.getPreferenceNumber("quality", 0);
 | 
					            const quality = this.getPreferenceNumber("quality", 0);
 | 
				
			||||||
| 
						 | 
					@ -508,73 +519,101 @@ export default {
 | 
				
			||||||
                quality > 0 && (this.video.audioStreams.length > 0 || this.video.livestream) && !disableVideo;
 | 
					                quality > 0 && (this.video.audioStreams.length > 0 || this.video.livestream) && !disableVideo;
 | 
				
			||||||
            if (qualityConds) this.$player.configure("abr.enabled", false);
 | 
					            if (qualityConds) this.$player.configure("abr.enabled", false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            player.load(uri, 0, mime).then(() => {
 | 
					            player
 | 
				
			||||||
                const isSafari = window.navigator?.vendor?.includes("Apple");
 | 
					                .load(uri, 0, mime)
 | 
				
			||||||
 | 
					                .then(() => {
 | 
				
			||||||
 | 
					                    const isSafari = window.navigator?.vendor?.includes("Apple");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!isSafari) {
 | 
					                    if (!isSafari) {
 | 
				
			||||||
                    // Set the audio language
 | 
					                        // Set the audio language
 | 
				
			||||||
                    const prefLang = this.getPreferenceString("hl", "en").substr(0, 2);
 | 
					                        const prefLang = this.getPreferenceString("hl", "en").substr(0, 2);
 | 
				
			||||||
                    var lang = "en";
 | 
					                        var lang = "en";
 | 
				
			||||||
                    for (var l in player.getAudioLanguages()) {
 | 
					                        for (var l in player.getAudioLanguages()) {
 | 
				
			||||||
                        if (l == prefLang) {
 | 
					                            if (l == prefLang) {
 | 
				
			||||||
                            lang = l;
 | 
					                                lang = l;
 | 
				
			||||||
                            return;
 | 
					                                return;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        player.selectAudioLanguage(lang);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const audioLanguages = player.getAudioLanguages();
 | 
				
			||||||
 | 
					                    if (audioLanguages.length > 1) {
 | 
				
			||||||
 | 
					                        const overflowMenuButtons = this.$ui.getConfiguration().overflowMenuButtons;
 | 
				
			||||||
 | 
					                        // append language menu on index 1
 | 
				
			||||||
 | 
					                        const newOverflowMenuButtons = [
 | 
				
			||||||
 | 
					                            ...overflowMenuButtons.slice(0, 1),
 | 
				
			||||||
 | 
					                            "language",
 | 
				
			||||||
 | 
					                            ...overflowMenuButtons.slice(1),
 | 
				
			||||||
 | 
					                        ];
 | 
				
			||||||
 | 
					                        this.$ui.configure("overflowMenuButtons", newOverflowMenuButtons);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (qualityConds) {
 | 
				
			||||||
 | 
					                        var leastDiff = Number.MAX_VALUE;
 | 
				
			||||||
 | 
					                        var bestStream = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var bestAudio = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        const tracks = player
 | 
				
			||||||
 | 
					                            .getVariantTracks()
 | 
				
			||||||
 | 
					                            .filter(track => track.language == lang || track.language == "und");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Choose the best audio stream
 | 
				
			||||||
 | 
					                        if (quality >= 480)
 | 
				
			||||||
 | 
					                            tracks.forEach(track => {
 | 
				
			||||||
 | 
					                                const audioBandwidth = track.audioBandwidth;
 | 
				
			||||||
 | 
					                                if (audioBandwidth > bestAudio) bestAudio = audioBandwidth;
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Find best matching stream based on resolution and bitrate
 | 
				
			||||||
 | 
					                        tracks
 | 
				
			||||||
 | 
					                            .sort((a, b) => a.bandwidth - b.bandwidth)
 | 
				
			||||||
 | 
					                            .forEach(stream => {
 | 
				
			||||||
 | 
					                                if (stream.audioBandwidth < bestAudio) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                const diff = Math.abs(quality - stream.height);
 | 
				
			||||||
 | 
					                                if (diff < leastDiff) {
 | 
				
			||||||
 | 
					                                    leastDiff = diff;
 | 
				
			||||||
 | 
					                                    bestStream = stream;
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        player.selectVariantTrack(bestStream);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    this.video.subtitles.map(subtitle => {
 | 
				
			||||||
 | 
					                        player.addTextTrackAsync(
 | 
				
			||||||
 | 
					                            subtitle.url,
 | 
				
			||||||
 | 
					                            subtitle.code,
 | 
				
			||||||
 | 
					                            "subtitles",
 | 
				
			||||||
 | 
					                            subtitle.mimeType,
 | 
				
			||||||
 | 
					                            null,
 | 
				
			||||||
 | 
					                            subtitle.name,
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    videoEl.volume = this.getPreferenceNumber("volume", 1);
 | 
				
			||||||
 | 
					                    const rate = this.getPreferenceNumber("rate", 1);
 | 
				
			||||||
 | 
					                    videoEl.playbackRate = rate;
 | 
				
			||||||
 | 
					                    videoEl.defaultPlaybackRate = rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
 | 
				
			||||||
 | 
					                    this.$player.setTextTrackVisibility(autoDisplayCaptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const prefSubtitles = this.getPreferenceString("subtitles", "");
 | 
				
			||||||
 | 
					                    if (prefSubtitles !== "") {
 | 
				
			||||||
 | 
					                        const textTracks = this.$player.getTextTracks();
 | 
				
			||||||
 | 
					                        const subtitleIdx = textTracks.findIndex(textTrack => textTrack.language == prefSubtitles);
 | 
				
			||||||
 | 
					                        if (subtitleIdx != -1) {
 | 
				
			||||||
 | 
					                            this.$player.setTextTrackVisibility(true);
 | 
				
			||||||
 | 
					                            this.$player.selectTextTrack(textTracks[subtitleIdx]);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    player.selectAudioLanguage(lang);
 | 
					                })
 | 
				
			||||||
                }
 | 
					                .catch(e => {
 | 
				
			||||||
 | 
					                    console.error(e);
 | 
				
			||||||
                if (qualityConds) {
 | 
					                    this.error = e.code;
 | 
				
			||||||
                    var leastDiff = Number.MAX_VALUE;
 | 
					 | 
				
			||||||
                    var bestStream = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var bestAudio = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const tracks = player
 | 
					 | 
				
			||||||
                        .getVariantTracks()
 | 
					 | 
				
			||||||
                        .filter(track => track.language == lang || track.language == "und");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Choose the best audio stream
 | 
					 | 
				
			||||||
                    if (quality >= 480)
 | 
					 | 
				
			||||||
                        tracks.forEach(track => {
 | 
					 | 
				
			||||||
                            const audioBandwidth = track.audioBandwidth;
 | 
					 | 
				
			||||||
                            if (audioBandwidth > bestAudio) bestAudio = audioBandwidth;
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Find best matching stream based on resolution and bitrate
 | 
					 | 
				
			||||||
                    tracks
 | 
					 | 
				
			||||||
                        .sort((a, b) => a.bandwidth - b.bandwidth)
 | 
					 | 
				
			||||||
                        .forEach(stream => {
 | 
					 | 
				
			||||||
                            if (stream.audioBandwidth < bestAudio) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            const diff = Math.abs(quality - stream.height);
 | 
					 | 
				
			||||||
                            if (diff < leastDiff) {
 | 
					 | 
				
			||||||
                                leastDiff = diff;
 | 
					 | 
				
			||||||
                                bestStream = stream;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    player.selectVariantTrack(bestStream);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                this.video.subtitles.map(subtitle => {
 | 
					 | 
				
			||||||
                    player.addTextTrackAsync(
 | 
					 | 
				
			||||||
                        subtitle.url,
 | 
					 | 
				
			||||||
                        subtitle.code,
 | 
					 | 
				
			||||||
                        "subtitles",
 | 
					 | 
				
			||||||
                        subtitle.mimeType,
 | 
					 | 
				
			||||||
                        null,
 | 
					 | 
				
			||||||
                        subtitle.name,
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
                videoEl.volume = this.getPreferenceNumber("volume", 1);
 | 
					 | 
				
			||||||
                const rate = this.getPreferenceNumber("rate", 1);
 | 
					 | 
				
			||||||
                videoEl.playbackRate = rate;
 | 
					 | 
				
			||||||
                videoEl.defaultPlaybackRate = rate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
 | 
					 | 
				
			||||||
                this.$player.setTextTrackVisibility(autoDisplayCaptions);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // expand the player to fullscreen when the fullscreen query equals true
 | 
					            // expand the player to fullscreen when the fullscreen query equals true
 | 
				
			||||||
            if (this.$route.query.fullscreen === "true" && !this.$ui.getControls().isFullScreenEnabled())
 | 
					            if (this.$route.query.fullscreen === "true" && !this.$ui.getControls().isFullScreenEnabled())
 | 
				
			||||||
| 
						 | 
					@ -603,6 +642,7 @@ export default {
 | 
				
			||||||
                this.$refs.videoEl.currentTime = time;
 | 
					                this.$refs.videoEl.currentTime = time;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        updateMarkers() {
 | 
					        updateMarkers() {
 | 
				
			||||||
            const markers = this.$refs.container.querySelector(".shaka-ad-markers");
 | 
					            const markers = this.$refs.container.querySelector(".shaka-ad-markers");
 | 
				
			||||||
            const array = ["to right"];
 | 
					            const array = ["to right"];
 | 
				
			||||||
| 
						 | 
					@ -610,38 +650,19 @@ export default {
 | 
				
			||||||
                const start = (segment.segment[0] / this.video.duration) * 100;
 | 
					                const start = (segment.segment[0] / this.video.duration) * 100;
 | 
				
			||||||
                const end = (segment.segment[1] / this.video.duration) * 100;
 | 
					                const end = (segment.segment[1] / this.video.duration) * 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var color;
 | 
					                var color = [
 | 
				
			||||||
                switch (segment.category) {
 | 
					                    "sponsor",
 | 
				
			||||||
                    case "sponsor":
 | 
					                    "selfpromo",
 | 
				
			||||||
                        color = "#00d400";
 | 
					                    "interaction",
 | 
				
			||||||
                        break;
 | 
					                    "poi_highlight",
 | 
				
			||||||
                    case "selfpromo":
 | 
					                    "intro",
 | 
				
			||||||
                        color = "#ffff00";
 | 
					                    "outro",
 | 
				
			||||||
                        break;
 | 
					                    "preview",
 | 
				
			||||||
                    case "interaction":
 | 
					                    "filler",
 | 
				
			||||||
                        color = "#cc00ff";
 | 
					                    "music_offtopic",
 | 
				
			||||||
                        break;
 | 
					                ].includes(segment.category)
 | 
				
			||||||
                    case "poi_highlight":
 | 
					                    ? `var(--spon-seg-${segment.category})`
 | 
				
			||||||
                        color = "#ff1684";
 | 
					                    : "var(--spon-seg-default)";
 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    case "intro":
 | 
					 | 
				
			||||||
                        color = "#00ffff";
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    case "outro":
 | 
					 | 
				
			||||||
                        color = "#0202ed";
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    case "preview":
 | 
					 | 
				
			||||||
                        color = "#008fd6";
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    case "filler":
 | 
					 | 
				
			||||||
                        color = "#7300FF";
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    case "music_offtopic":
 | 
					 | 
				
			||||||
                        color = "#ff9900";
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    default:
 | 
					 | 
				
			||||||
                        color = "white";
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                array.push(`transparent ${start}%`);
 | 
					                array.push(`transparent ${start}%`);
 | 
				
			||||||
                array.push(`${color} ${start}%`);
 | 
					                array.push(`${color} ${start}%`);
 | 
				
			||||||
| 
						 | 
					@ -656,11 +677,6 @@ export default {
 | 
				
			||||||
            if (markers) markers.style.background = `linear-gradient(${array.join(",")})`;
 | 
					            if (markers) markers.style.background = `linear-gradient(${array.join(",")})`;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        updateSponsors() {
 | 
					        updateSponsors() {
 | 
				
			||||||
            const skipOptions = this.getPreferenceJSON("skipOptions", {});
 | 
					 | 
				
			||||||
            this.sponsors?.segments?.forEach(segment => {
 | 
					 | 
				
			||||||
                const option = skipOptions[segment.category];
 | 
					 | 
				
			||||||
                segment.autoskip = option === undefined || option === "auto";
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            if (this.getPreferenceBoolean("showMarkers", true)) {
 | 
					            if (this.getPreferenceBoolean("showMarkers", true)) {
 | 
				
			||||||
                this.shakaPromise.then(() => {
 | 
					                this.shakaPromise.then(() => {
 | 
				
			||||||
                    this.updateMarkers();
 | 
					                    this.updateMarkers();
 | 
				
			||||||
| 
						 | 
					@ -679,8 +695,7 @@ export default {
 | 
				
			||||||
            // hide the preview when the user stops hovering the seekbar
 | 
					            // hide the preview when the user stops hovering the seekbar
 | 
				
			||||||
            seekBar.addEventListener("mouseout", () => {
 | 
					            seekBar.addEventListener("mouseout", () => {
 | 
				
			||||||
                this.isHoveringTimebar = false;
 | 
					                this.isHoveringTimebar = false;
 | 
				
			||||||
                let canvas = document.querySelector("#preview");
 | 
					                this.$refs.previewContainer.style.display = "none";
 | 
				
			||||||
                canvas.style.display = "none";
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async showSeekbarPreview(position) {
 | 
					        async showSeekbarPreview(position) {
 | 
				
			||||||
| 
						 | 
					@ -689,17 +704,12 @@ export default {
 | 
				
			||||||
            if (!this.isHoveringTimebar) return;
 | 
					            if (!this.isHoveringTimebar) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const seekBar = document.querySelector(".shaka-seek-bar");
 | 
					            const seekBar = document.querySelector(".shaka-seek-bar");
 | 
				
			||||||
            const canvas = document.querySelector("#preview");
 | 
					            const container = this.$refs.previewContainer;
 | 
				
			||||||
 | 
					            const canvas = this.$refs.preview;
 | 
				
			||||||
            const ctx = canvas.getContext("2d");
 | 
					            const ctx = canvas.getContext("2d");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // get the new sizes for the image to be drawn into the canvas
 | 
					            const offsetX = frame.positionX * frame.frameWidth;
 | 
				
			||||||
            const originalWidth = originalImage.naturalWidth;
 | 
					            const offsetY = frame.positionY * frame.frameHeight;
 | 
				
			||||||
            const originalHeight = originalImage.naturalHeight;
 | 
					 | 
				
			||||||
            // image can have less frames than server told us so calculate them ourselves
 | 
					 | 
				
			||||||
            const imageFramesPerPageX = originalImage.naturalWidth / frame.frameWidth;
 | 
					 | 
				
			||||||
            const imageFramesPerPageY = originalImage.naturalHeight / frame.frameHeight;
 | 
					 | 
				
			||||||
            const offsetX = originalWidth * (frame.positionX / imageFramesPerPageX);
 | 
					 | 
				
			||||||
            const offsetY = originalHeight * (frame.positionY / imageFramesPerPageY);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            canvas.width = frame.frameWidth > 100 ? frame.frameWidth : frame.frameWidth * 2;
 | 
					            canvas.width = frame.frameWidth > 100 ? frame.frameWidth : frame.frameWidth * 2;
 | 
				
			||||||
            canvas.height = frame.frameWidth > 100 ? frame.frameHeight : frame.frameHeight * 2;
 | 
					            canvas.height = frame.frameWidth > 100 ? frame.frameHeight : frame.frameHeight * 2;
 | 
				
			||||||
| 
						 | 
					@ -717,12 +727,15 @@ export default {
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // calculate the thumbnail preview offset and display it
 | 
					            // calculate the thumbnail preview offset and display it
 | 
				
			||||||
            const seekbarPadding = 2; // percentage of seekbar padding
 | 
					 | 
				
			||||||
            const centerOffset = position / this.video.duration / 10;
 | 
					            const centerOffset = position / this.video.duration / 10;
 | 
				
			||||||
            const left = centerOffset - ((0.5 * canvas.width) / seekBar.clientWidth) * 100;
 | 
					            const left = centerOffset - ((0.5 * canvas.width) / seekBar.clientWidth) * 100;
 | 
				
			||||||
            const maxLeft = ((seekBar.clientWidth - canvas.clientWidth) / seekBar.clientWidth) * 100 - seekbarPadding;
 | 
					            const maxLeft =
 | 
				
			||||||
            canvas.style.left = `max(${seekbarPadding}%, min(${left}%, ${maxLeft}%))`;
 | 
					                ((seekBar.clientWidth - canvas.clientWidth) / seekBar.clientWidth) * 100 - this.seekbarPadding;
 | 
				
			||||||
            canvas.style.display = "block";
 | 
					
 | 
				
			||||||
 | 
					            this.currentTime = position / 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            container.style.left = `max(${this.seekbarPadding}%, min(${left}%, ${maxLeft}%))`;
 | 
				
			||||||
 | 
					            container.style.display = "flex";
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        // ineffective algorithm to find the thumbnail corresponding to the currently hovered position in the video
 | 
					        // ineffective algorithm to find the thumbnail corresponding to the currently hovered position in the video
 | 
				
			||||||
        getFrame(position) {
 | 
					        getFrame(position) {
 | 
				
			||||||
| 
						 | 
					@ -773,6 +786,23 @@ export default {
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --player-base: rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					    --player-buffered: rgba(255, 255, 255, 0.54);
 | 
				
			||||||
 | 
					    --player-played: rgba(255, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    --spon-seg-sponsor: #00d400;
 | 
				
			||||||
 | 
					    --spon-seg-selfpromo: #ffff00;
 | 
				
			||||||
 | 
					    --spon-seg-interaction: #cc00ff;
 | 
				
			||||||
 | 
					    --spon-seg-poi_highlight: #ff1684;
 | 
				
			||||||
 | 
					    --spon-seg-intro: #00ffff;
 | 
				
			||||||
 | 
					    --spon-seg-outro: #0202ed;
 | 
				
			||||||
 | 
					    --spon-seg-preview: #008fd6;
 | 
				
			||||||
 | 
					    --spon-seg-filler: #7300ff;
 | 
				
			||||||
 | 
					    --spon-seg-music_offtopic: #ff9900;
 | 
				
			||||||
 | 
					    --spon-seg-default: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.player-container {
 | 
					.player-container {
 | 
				
			||||||
    @apply max-h-75vh min-h-64 bg-black;
 | 
					    @apply max-h-75vh min-h-64 bg-black;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -834,13 +864,4 @@ export default {
 | 
				
			||||||
    font-size: 1.6em !important;
 | 
					    font-size: 1.6em !important;
 | 
				
			||||||
    line-height: inherit !important;
 | 
					    line-height: inherit !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#preview {
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    z-index: 2000;
 | 
					 | 
				
			||||||
    bottom: 0;
 | 
					 | 
				
			||||||
    margin-bottom: 4.5%;
 | 
					 | 
				
			||||||
    border-radius: 0.3rem;
 | 
					 | 
				
			||||||
    display: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,10 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    props: {
 | 
					    props: {
 | 
				
			||||||
        link: String,
 | 
					        link: {
 | 
				
			||||||
 | 
					            type: String,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        platform: {
 | 
					        platform: {
 | 
				
			||||||
            type: String,
 | 
					            type: String,
 | 
				
			||||||
            required: false,
 | 
					            required: false,
 | 
				
			||||||
| 
						 | 
					@ -12,14 +15,14 @@ export default {
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <template v-if="this.getPreferenceBoolean('showWatchOnYouTube', false)">
 | 
					    <template v-if="getPreferenceBoolean('showWatchOnYouTube', false)">
 | 
				
			||||||
        <!-- For large screens -->
 | 
					        <!-- For large screens -->
 | 
				
			||||||
        <a :href="link" class="btn lt-lg:hidden flex items-center">
 | 
					        <a :href="link" class="btn flex items-center lt-lg:hidden">
 | 
				
			||||||
            <i18n-t keypath="player.watch_on" tag="strong">{{ platform }}</i18n-t>
 | 
					            <i18n-t keypath="player.watch_on" tag="strong">{{ platform }}</i18n-t>
 | 
				
			||||||
            <font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
 | 
					            <font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <!-- For small screens -->
 | 
					        <!-- For small screens -->
 | 
				
			||||||
        <a :href="link" class="btn lg:hidden flex items-center">
 | 
					        <a :href="link" class="btn flex items-center lg:hidden">
 | 
				
			||||||
            <font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
 | 
					            <font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div v-if="video && isEmbed" class="absolute top-0 left-0 h-full w-full bg-black z-50">
 | 
					    <div v-if="video && isEmbed" class="absolute left-0 top-0 z-50 h-full w-full bg-black">
 | 
				
			||||||
        <VideoPlayer
 | 
					        <VideoPlayer
 | 
				
			||||||
            ref="videoPlayer"
 | 
					            ref="videoPlayer"
 | 
				
			||||||
            :video="video"
 | 
					            :video="video"
 | 
				
			||||||
| 
						 | 
					@ -29,19 +29,20 @@
 | 
				
			||||||
                        :selected-auto-loop="selectedAutoLoop"
 | 
					                        :selected-auto-loop="selectedAutoLoop"
 | 
				
			||||||
                        @timeupdate="onTimeUpdate"
 | 
					                        @timeupdate="onTimeUpdate"
 | 
				
			||||||
                        @ended="onVideoEnded"
 | 
					                        @ended="onVideoEnded"
 | 
				
			||||||
 | 
					                        @navigate-next="navigateNext"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </keep-alive>
 | 
					                </keep-alive>
 | 
				
			||||||
                <ChaptersBar
 | 
					                <ChaptersBar
 | 
				
			||||||
                    :mobileLayout="isMobile"
 | 
					 | 
				
			||||||
                    v-if="video?.chapters?.length > 0 && showChapters"
 | 
					                    v-if="video?.chapters?.length > 0 && showChapters"
 | 
				
			||||||
 | 
					                    :mobile-layout="isMobile"
 | 
				
			||||||
                    :chapters="video.chapters"
 | 
					                    :chapters="video.chapters"
 | 
				
			||||||
                    :player-position="currentTime"
 | 
					                    :player-position="currentTime"
 | 
				
			||||||
                    @seek="navigate"
 | 
					                    @seek="navigate"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <!-- video title -->
 | 
					            <!-- video title -->
 | 
				
			||||||
            <div class="font-bold mt-2 text-2xl break-words" v-text="video.title" />
 | 
					            <div class="mt-2 break-words text-2xl font-bold" v-text="video.title" />
 | 
				
			||||||
            <div class="flex flex-wrap mt-3 mb-3">
 | 
					            <div class="mb-3 mt-3 flex flex-wrap">
 | 
				
			||||||
                <!-- views / date -->
 | 
					                <!-- views / date -->
 | 
				
			||||||
                <div class="flex flex-auto gap-2">
 | 
					                <div class="flex flex-auto gap-2">
 | 
				
			||||||
                    <span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
 | 
					                    <span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
 | 
				
			||||||
| 
						 | 
					@ -76,9 +77,14 @@
 | 
				
			||||||
                        video.uploader
 | 
					                        video.uploader
 | 
				
			||||||
                    }}</router-link>
 | 
					                    }}</router-link>
 | 
				
			||||||
                    <!-- Verified Badge -->
 | 
					                    <!-- Verified Badge -->
 | 
				
			||||||
                    <font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
 | 
					                    <font-awesome-icon v-if="video.uploaderVerified" class="ml-1" icon="check" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <PlaylistAddModal v-if="showModal" :video-id="getVideoId()" @close="showModal = !showModal" />
 | 
					                <PlaylistAddModal
 | 
				
			||||||
 | 
					                    v-if="showModal"
 | 
				
			||||||
 | 
					                    :video-id="getVideoId()"
 | 
				
			||||||
 | 
					                    :video-info="video"
 | 
				
			||||||
 | 
					                    @close="showModal = !showModal"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
                <ShareModal
 | 
					                <ShareModal
 | 
				
			||||||
                    v-if="showShareModal"
 | 
					                    v-if="showShareModal"
 | 
				
			||||||
                    :video-id="getVideoId()"
 | 
					                    :video-id="getVideoId()"
 | 
				
			||||||
| 
						 | 
					@ -87,26 +93,29 @@
 | 
				
			||||||
                    :playlist-index="index"
 | 
					                    :playlist-index="index"
 | 
				
			||||||
                    @close="showShareModal = !showShareModal"
 | 
					                    @close="showShareModal = !showShareModal"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <div class="flex flex-wrap gap-1 ml-auto">
 | 
					                <div class="ml-auto flex flex-wrap gap-1">
 | 
				
			||||||
                    <!-- Subscribe Button -->
 | 
					                    <!-- Subscribe Button -->
 | 
				
			||||||
                    <button class="btn flex items-center" v-if="authenticated" @click="showModal = !showModal">
 | 
					                    <button class="btn flex items-center gap-1 <md:hidden" @click="downloadCurrentFrame">
 | 
				
			||||||
 | 
					                        {{ $t("actions.download_frame") }}<i class="i-fa6-solid:download" />
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    <button class="btn flex items-center" @click="showModal = !showModal">
 | 
				
			||||||
                        {{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
 | 
					                        {{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                    <button
 | 
					                    <button
 | 
				
			||||||
                        class="btn"
 | 
					 | 
				
			||||||
                        @click="subscribeHandler"
 | 
					 | 
				
			||||||
                        v-t="{
 | 
					                        v-t="{
 | 
				
			||||||
                            path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
					                            path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
				
			||||||
                            args: { count: numberFormat(video.uploaderSubscriberCount) },
 | 
					                            args: { count: numberFormat(video.uploaderSubscriberCount) },
 | 
				
			||||||
                        }"
 | 
					                        }"
 | 
				
			||||||
 | 
					                        class="btn"
 | 
				
			||||||
 | 
					                        @click="subscribeHandler"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    <div class="flex flex-wrap gap-1">
 | 
					                    <div class="flex flex-wrap gap-1">
 | 
				
			||||||
                        <!-- RSS Feed button -->
 | 
					                        <!-- RSS Feed button -->
 | 
				
			||||||
                        <a
 | 
					                        <a
 | 
				
			||||||
 | 
					                            v-if="video.uploaderUrl"
 | 
				
			||||||
                            aria-label="RSS feed"
 | 
					                            aria-label="RSS feed"
 | 
				
			||||||
                            title="RSS feed"
 | 
					                            title="RSS feed"
 | 
				
			||||||
                            role="button"
 | 
					                            role="button"
 | 
				
			||||||
                            v-if="video.uploaderUrl"
 | 
					 | 
				
			||||||
                            :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
 | 
					                            :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
 | 
				
			||||||
                            target="_blank"
 | 
					                            target="_blank"
 | 
				
			||||||
                            class="btn flex items-center"
 | 
					                            class="btn flex items-center"
 | 
				
			||||||
| 
						 | 
					@ -121,7 +130,11 @@
 | 
				
			||||||
                        <!-- YouTube -->
 | 
					                        <!-- YouTube -->
 | 
				
			||||||
                        <WatchOnButton :link="`https://youtu.be/${getVideoId()}`" />
 | 
					                        <WatchOnButton :link="`https://youtu.be/${getVideoId()}`" />
 | 
				
			||||||
                        <!-- Odysee -->
 | 
					                        <!-- Odysee -->
 | 
				
			||||||
                        <WatchOnButton :link="`https://odysee.com/${video.lbryId}`" platform="Odysee" />
 | 
					                        <WatchOnButton
 | 
				
			||||||
 | 
					                            v-if="video.lbryId"
 | 
				
			||||||
 | 
					                            :link="`https://odysee.com/${video.lbryId}`"
 | 
				
			||||||
 | 
					                            platform="Odysee"
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
                        <!-- listen / watch toggle -->
 | 
					                        <!-- listen / watch toggle -->
 | 
				
			||||||
                        <router-link
 | 
					                        <router-link
 | 
				
			||||||
                            :to="toggleListenUrl"
 | 
					                            :to="toggleListenUrl"
 | 
				
			||||||
| 
						 | 
					@ -135,27 +148,53 @@
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <hr />
 | 
					            <hr class="mb-2" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div
 | 
				
			||||||
 | 
					                v-for="metaInfo in video?.metaInfo ?? []"
 | 
				
			||||||
 | 
					                :key="metaInfo.title"
 | 
				
			||||||
 | 
					                class="btn my-3 flex flex-wrap cursor-default gap-2 px-4 py-2"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <span>{{ metaInfo.description ?? metaInfo.title }}</span>
 | 
				
			||||||
 | 
					                <a v-for="(link, linkIndex) in metaInfo.urls" :key="linkIndex" :href="link" class="underline">{{
 | 
				
			||||||
 | 
					                    metaInfo.urlTexts[linkIndex]
 | 
				
			||||||
 | 
					                }}</a>
 | 
				
			||||||
 | 
					                <br />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
 | 
					                v-t="`actions.${showDesc ? 'minimize_description' : 'show_description'}`"
 | 
				
			||||||
                class="btn mb-2"
 | 
					                class="btn mb-2"
 | 
				
			||||||
                @click="showDesc = !showDesc"
 | 
					                @click="showDesc = !showDesc"
 | 
				
			||||||
                v-t="`actions.${showDesc ? 'minimize_description' : 'show_description'}`"
 | 
					 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <span class="btn ml-2" v-show="video?.chapters?.length > 0">
 | 
					            <span v-show="video?.chapters?.length > 0" class="btn ml-2">
 | 
				
			||||||
                <input id="showChapters" type="checkbox" v-model="showChapters" />
 | 
					                <input id="showChapters" v-model="showChapters" type="checkbox" />
 | 
				
			||||||
                <label class="ml-2" for="showChapters" v-t="'actions.show_chapters'" />
 | 
					                <label v-t="'actions.show_chapters'" class="ml-2" for="showChapters" />
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- eslint-disable-next-line vue/no-v-html -->
 | 
					 | 
				
			||||||
            <div v-show="showDesc" class="break-words" v-html="purifyHTML(video.description)" />
 | 
					 | 
				
			||||||
            <template v-if="showDesc">
 | 
					            <template v-if="showDesc">
 | 
				
			||||||
 | 
					                <!-- eslint-disable-next-line vue/no-v-html -->
 | 
				
			||||||
 | 
					                <div class="description break-words" v-html="purifiedDescription" />
 | 
				
			||||||
 | 
					                <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                    v-if="sponsors && sponsors.segments"
 | 
					                    v-if="sponsors && sponsors.segments"
 | 
				
			||||||
                    v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
 | 
					                    v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <div v-if="video.category" v-text="`${$t('video.category')}: ${video.category}`" />
 | 
					                <div v-if="video.category" v-text="`${$t('video.category')}: ${video.category}`" />
 | 
				
			||||||
 | 
					                <div v-text="`${$t('video.license')}: ${video.license}`" />
 | 
				
			||||||
 | 
					                <div class="capitalize" v-text="`${$t('video.visibility')}: ${video.visibility}`" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div v-if="video.tags" class="mt-2 flex flex-wrap gap-2">
 | 
				
			||||||
 | 
					                    <router-link
 | 
				
			||||||
 | 
					                        v-for="tag in video.tags"
 | 
				
			||||||
 | 
					                        :key="tag"
 | 
				
			||||||
 | 
					                        class="btn line-clamp-1 rounded-s px-2 py-1"
 | 
				
			||||||
 | 
					                        :to="`/results?search_query=${encodeURIComponent(tag)}`"
 | 
				
			||||||
 | 
					                        >{{ tag }}</router-link
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -169,22 +208,27 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <hr />
 | 
					        <hr />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="grid xl:grid-cols-5 sm:grid-cols-4 grid-cols-1">
 | 
					        <div class="grid grid-cols-1 sm:grid-cols-4 xl:grid-cols-5">
 | 
				
			||||||
            <div class="xl:col-span-4 sm:col-span-3">
 | 
					            <div class="sm:col-span-3 xl:col-span-4">
 | 
				
			||||||
                <button
 | 
					                <button
 | 
				
			||||||
 | 
					                    v-if="!comments?.disabled"
 | 
				
			||||||
                    class="btn mb-2"
 | 
					                    class="btn mb-2"
 | 
				
			||||||
                    @click="toggleComments"
 | 
					                    @click="toggleComments"
 | 
				
			||||||
                    v-t="`actions.${showComments ? 'minimize_comments' : 'show_comments'}`"
 | 
					                    v-text="
 | 
				
			||||||
 | 
					                        `${$t(showComments ? 'actions.minimize_comments' : 'actions.show_comments')} (${numberFormat(
 | 
				
			||||||
 | 
					                            comments?.commentCount,
 | 
				
			||||||
 | 
					                        )})`
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div v-if="!showComments" class="xl:col-span-4 sm:col-span-3"></div>
 | 
					            <div v-if="!showComments" class="sm:col-span-3 xl:col-span-4"></div>
 | 
				
			||||||
            <div v-else-if="!comments" class="xl:col-span-4 sm:col-span-3">
 | 
					            <div v-else-if="!comments" class="sm:col-span-3 xl:col-span-4">
 | 
				
			||||||
                <p class="text-center mt-8" v-t="'comment.loading'"></p>
 | 
					                <p v-t="'comment.loading'" class="mt-8 text-center"></p>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div v-else-if="comments.disabled" class="xl:col-span-4 sm:col-span-3">
 | 
					            <div v-else-if="comments.disabled" class="sm:col-span-3 xl:col-span-4">
 | 
				
			||||||
                <p class="text-center mt-8" v-t="'comment.disabled'"></p>
 | 
					                <p v-t="'comment.disabled'" class="mt-8 text-center"></p>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div v-else ref="comments" class="xl:col-span-4 sm:col-span-3">
 | 
					            <div v-else ref="comments" class="sm:col-span-3 xl:col-span-4">
 | 
				
			||||||
                <CommentItem
 | 
					                <CommentItem
 | 
				
			||||||
                    v-for="comment in comments.comments"
 | 
					                    v-for="comment in comments.comments"
 | 
				
			||||||
                    :key="comment.commentId"
 | 
					                    :key="comment.commentId"
 | 
				
			||||||
| 
						 | 
					@ -202,9 +246,9 @@
 | 
				
			||||||
                    :selected-index="index"
 | 
					                    :selected-index="index"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <a
 | 
					                <a
 | 
				
			||||||
 | 
					                    v-t="`actions.${showRecs ? 'minimize_recommendations' : 'show_recommendations'}`"
 | 
				
			||||||
                    class="btn mb-2"
 | 
					                    class="btn mb-2"
 | 
				
			||||||
                    @click="showRecs = !showRecs"
 | 
					                    @click="showRecs = !showRecs"
 | 
				
			||||||
                    v-t="`actions.${showRecs ? 'minimize_recommendations' : 'show_recommendations'}`"
 | 
					 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <hr v-show="showRecs" />
 | 
					                <hr v-show="showRecs" />
 | 
				
			||||||
                <div v-show="showRecs">
 | 
					                <div v-show="showRecs">
 | 
				
			||||||
| 
						 | 
					@ -235,6 +279,7 @@ import WatchOnButton from "./WatchOnButton.vue";
 | 
				
			||||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
					import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
				
			||||||
import ToastComponent from "./ToastComponent.vue";
 | 
					import ToastComponent from "./ToastComponent.vue";
 | 
				
			||||||
import { parseTimeParam } from "@/utils/Misc";
 | 
					import { parseTimeParam } from "@/utils/Misc";
 | 
				
			||||||
 | 
					import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    name: "App",
 | 
					    name: "App",
 | 
				
			||||||
| 
						 | 
					@ -302,6 +347,9 @@ export default {
 | 
				
			||||||
        defaultCounter(_this) {
 | 
					        defaultCounter(_this) {
 | 
				
			||||||
            return _this.getPreferenceNumber("autoPlayNextCountdown", 5);
 | 
					            return _this.getPreferenceNumber("autoPlayNextCountdown", 5);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        purifiedDescription() {
 | 
				
			||||||
 | 
					            return purifyHTML(this.video.description);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
        // check screen size
 | 
					        // check screen size
 | 
				
			||||||
| 
						 | 
					@ -350,6 +398,7 @@ export default {
 | 
				
			||||||
        this.getPlaylistData();
 | 
					        this.getPlaylistData();
 | 
				
			||||||
        this.getSponsors();
 | 
					        this.getSponsors();
 | 
				
			||||||
        if (!this.isEmbed && this.showComments) this.getComments();
 | 
					        if (!this.isEmbed && this.showComments) this.getComments();
 | 
				
			||||||
 | 
					        if (this.isEmbed) document.querySelector("html").style.overflow = "hidden";
 | 
				
			||||||
        window.addEventListener("click", this.handleClick);
 | 
					        window.addEventListener("click", this.handleClick);
 | 
				
			||||||
        window.addEventListener("resize", () => {
 | 
					        window.addEventListener("resize", () => {
 | 
				
			||||||
            this.smallView = this.smallViewQuery.matches;
 | 
					            this.smallView = this.smallViewQuery.matches;
 | 
				
			||||||
| 
						 | 
					@ -398,6 +447,11 @@ export default {
 | 
				
			||||||
                category: JSON.stringify(selectedSkip),
 | 
					                category: JSON.stringify(selectedSkip),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sponsors?.segments?.forEach(segment => {
 | 
				
			||||||
 | 
					                const option = skipOptions[segment.category];
 | 
				
			||||||
 | 
					                segment.autoskip = option === undefined || option === "auto";
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
 | 
					            const minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
 | 
				
			||||||
            sponsors.segments = sponsors.segments?.filter(segment => {
 | 
					            sponsors.segments = sponsors.segments?.filter(segment => {
 | 
				
			||||||
                const length = segment.segment[1] - segment.segment[0];
 | 
					                const length = segment.segment[1] - segment.segment[0];
 | 
				
			||||||
| 
						 | 
					@ -437,8 +491,10 @@ export default {
 | 
				
			||||||
                                elem.outerHTML = elem.getAttribute("href");
 | 
					                                elem.outerHTML = elem.getAttribute("href");
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                        xmlDoc.querySelectorAll("br").forEach(elem => (elem.outerHTML = "\n"));
 | 
					                        xmlDoc.querySelectorAll("br").forEach(elem => (elem.outerHTML = "\n"));
 | 
				
			||||||
                        this.video.description = this.rewriteDescription(xmlDoc.querySelector("body").innerHTML);
 | 
					                        this.video.description = rewriteDescription(xmlDoc.querySelector("body").innerHTML);
 | 
				
			||||||
                        this.updateWatched(this.video.relatedStreams);
 | 
					                        this.updateWatched(this.video.relatedStreams);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        this.fetchDeArrowContent(this.video.relatedStreams);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -459,6 +515,9 @@ export default {
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					                await this.fetchPlaylistPages().then(() => {
 | 
				
			||||||
 | 
					                    this.fetchDeArrowContent(this.playlist.relatedStreams);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async fetchPlaylistPages() {
 | 
					        async fetchPlaylistPages() {
 | 
				
			||||||
| 
						 | 
					@ -643,6 +702,21 @@ export default {
 | 
				
			||||||
            if (paramStr.length > 0) url += "&" + paramStr;
 | 
					            if (paramStr.length > 0) url += "&" + paramStr;
 | 
				
			||||||
            this.$router.push(url);
 | 
					            this.$router.push(url);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        downloadCurrentFrame() {
 | 
				
			||||||
 | 
					            const video = document.querySelector("video");
 | 
				
			||||||
 | 
					            const canvas = document.createElement("canvas");
 | 
				
			||||||
 | 
					            canvas.width = video.videoWidth;
 | 
				
			||||||
 | 
					            canvas.height = video.videoHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const context = canvas.getContext("2d");
 | 
				
			||||||
 | 
					            context.drawImage(video, 0, 0, canvas.width, canvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let link = document.createElement("a");
 | 
				
			||||||
 | 
					            const currentTime = Math.round(video.currentTime * 1000) / 1000;
 | 
				
			||||||
 | 
					            link.download = `${this.video.title}_${currentTime}s.png`;
 | 
				
			||||||
 | 
					            link.href = canvas.toDataURL();
 | 
				
			||||||
 | 
					            link.click();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -653,4 +727,9 @@ export default {
 | 
				
			||||||
    opacity: 0;
 | 
					    opacity: 0;
 | 
				
			||||||
    transform: translateX(100%) scale(0.5);
 | 
					    transform: translateX(100%) scale(0.5);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.description a {
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					    filter: brightness(0.75);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "البث المباشر",
 | 
					        "livestreams": "البث المباشر",
 | 
				
			||||||
        "channels": "القنوات",
 | 
					        "channels": "القنوات",
 | 
				
			||||||
        "bookmarks": "الاشارات المرجعيه",
 | 
					        "bookmarks": "الاشارات المرجعيه",
 | 
				
			||||||
        "channel_groups": "مجموعات القنوات"
 | 
					        "channel_groups": "مجموعات القنوات",
 | 
				
			||||||
 | 
					        "dearrow": "دي ارو"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "شاهد عبر"
 | 
					        "watch_on": "مشاهدة على {0}",
 | 
				
			||||||
 | 
					        "failed": "فشل مع رمز الخطأ {0}، راجع السجلات لمزيد من المعلومات"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "اشتراك - {count}",
 | 
					        "subscribe": "اشتراك - {count}",
 | 
				
			||||||
| 
						 | 
					@ -52,7 +54,7 @@
 | 
				
			||||||
        "skip_interaction": "تخطي تذكير التفاعل (اشتراك)",
 | 
					        "skip_interaction": "تخطي تذكير التفاعل (اشتراك)",
 | 
				
			||||||
        "skip_non_music": "تخطي الموسيقى: قسم غير الموسيقى",
 | 
					        "skip_non_music": "تخطي الموسيقى: قسم غير الموسيقى",
 | 
				
			||||||
        "theme": "السمة",
 | 
					        "theme": "السمة",
 | 
				
			||||||
        "instance_selection": "تحديد المثيل",
 | 
					        "instance_selection": "قائمة الخوادم",
 | 
				
			||||||
        "export_to_json": "تصدير إلى JSON",
 | 
					        "export_to_json": "تصدير إلى JSON",
 | 
				
			||||||
        "show_more": "اظهار المزيد",
 | 
					        "show_more": "اظهار المزيد",
 | 
				
			||||||
        "skip_outro": "تخطي بطاقات النهاية / الاعتمادات",
 | 
					        "skip_outro": "تخطي بطاقات النهاية / الاعتمادات",
 | 
				
			||||||
| 
						 | 
					@ -62,15 +64,15 @@
 | 
				
			||||||
        "skip_filler_tangent": "تخطي المحتوى الغير مهم",
 | 
					        "skip_filler_tangent": "تخطي المحتوى الغير مهم",
 | 
				
			||||||
        "show_markers": "إظهار العلامات على المشغل",
 | 
					        "show_markers": "إظهار العلامات على المشغل",
 | 
				
			||||||
        "buffering_goal": "هدف التخزين المؤقت (بالثواني)",
 | 
					        "buffering_goal": "هدف التخزين المؤقت (بالثواني)",
 | 
				
			||||||
        "country_selection": "اختيار البلد",
 | 
					        "country_selection": "البلد",
 | 
				
			||||||
        "default_homepage": "الصفحة الرئيسية الافتراضية",
 | 
					        "default_homepage": "الصفحة الرئيسية الافتراضية",
 | 
				
			||||||
        "show_comments": "إظهار التعليقات",
 | 
					        "show_comments": "إظهار التعليقات",
 | 
				
			||||||
        "minimize_description_default": "تصغير الوصف بشكل افتراضي",
 | 
					        "minimize_description_default": "تصغير الوصف بشكل افتراضي",
 | 
				
			||||||
        "store_watch_history": "تخزين سجل المشاهدة",
 | 
					        "store_watch_history": "تخزين سجل المشاهدة",
 | 
				
			||||||
        "language_selection": "اختيار اللغة",
 | 
					        "language_selection": "اللغة",
 | 
				
			||||||
        "instances_list": "قائمة المثيلات",
 | 
					        "instances_list": "قائمة المثيلات",
 | 
				
			||||||
        "enabled_codecs": "برامج الترميز الممكنة (متعددة)",
 | 
					        "enabled_codecs": "برامج الترميز الممكنة (متعددة)",
 | 
				
			||||||
        "import_from_json": "استيراد من JSON/CSV",
 | 
					        "import_from_json": "استيراد من JSON",
 | 
				
			||||||
        "loop_this_video": "تكرار هذا الفيديو",
 | 
					        "loop_this_video": "تكرار هذا الفيديو",
 | 
				
			||||||
        "auto_play_next_video": "التشغيل التلقائي للفيديو التالي",
 | 
					        "auto_play_next_video": "التشغيل التلقائي للفيديو التالي",
 | 
				
			||||||
        "donations": "التبرعات للتطوير",
 | 
					        "donations": "التبرعات للتطوير",
 | 
				
			||||||
| 
						 | 
					@ -92,7 +94,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "تقليل التوصيات بشكل افتراضي",
 | 
					        "minimize_recommendations_default": "تقليل التوصيات بشكل افتراضي",
 | 
				
			||||||
        "invalidate_session": "تسجيل الخروج من جميع الأجهزة",
 | 
					        "invalidate_session": "تسجيل الخروج من جميع الأجهزة",
 | 
				
			||||||
        "different_auth_instance": "استخدام مثيل مختلف للمصادقة",
 | 
					        "different_auth_instance": "استخدام مثيل مختلف للمصادقة",
 | 
				
			||||||
        "instance_auth_selection": "تحديد مثيل Autentication",
 | 
					        "instance_auth_selection": "خادم المصادقة",
 | 
				
			||||||
        "clone_playlist": "استنساخ قائمة التشغيل",
 | 
					        "clone_playlist": "استنساخ قائمة التشغيل",
 | 
				
			||||||
        "clone_playlist_success": "تم استنساخها بنجاح!",
 | 
					        "clone_playlist_success": "تم استنساخها بنجاح!",
 | 
				
			||||||
        "download_as_txt": "تنزيل بتنسيق .txt",
 | 
					        "download_as_txt": "تنزيل بتنسيق .txt",
 | 
				
			||||||
| 
						 | 
					@ -108,7 +110,7 @@
 | 
				
			||||||
        "copy_link": "نسخ الرابط",
 | 
					        "copy_link": "نسخ الرابط",
 | 
				
			||||||
        "time_code": "رمز الوقت (بالثواني)",
 | 
					        "time_code": "رمز الوقت (بالثواني)",
 | 
				
			||||||
        "show_chapters": "الفصول",
 | 
					        "show_chapters": "الفصول",
 | 
				
			||||||
        "store_search_history": "حفظ سجل البحث",
 | 
					        "store_search_history": "تخزين سجل البحث",
 | 
				
			||||||
        "documentation": "التوثيق",
 | 
					        "documentation": "التوثيق",
 | 
				
			||||||
        "status_page": "الحالة",
 | 
					        "status_page": "الحالة",
 | 
				
			||||||
        "source_code": "شفرة المصدر",
 | 
					        "source_code": "شفرة المصدر",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +141,14 @@
 | 
				
			||||||
        "playlist_name": "اسم قائمة التشغيل",
 | 
					        "playlist_name": "اسم قائمة التشغيل",
 | 
				
			||||||
        "edit_playlist": "تعديل قائمة التشغيل",
 | 
					        "edit_playlist": "تعديل قائمة التشغيل",
 | 
				
			||||||
        "show_search_suggestions": "إظهار اقتراحات البحث",
 | 
					        "show_search_suggestions": "إظهار اقتراحات البحث",
 | 
				
			||||||
        "chapters_layout_mobile": "تخطيط الفصول على الهاتف"
 | 
					        "chapters_layout_mobile": "تخطيط الفصول على الهاتف",
 | 
				
			||||||
 | 
					        "delete_automatically": "الحذف تلقائيا بعد",
 | 
				
			||||||
 | 
					        "enable_dearrow": "تمكين دي ارو",
 | 
				
			||||||
 | 
					        "generate_qrcode": "إنشاء رمز الاستجابة السريعة",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "استيراد من JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "إطار التحميل",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "سياسة الخصوصية",
 | 
				
			||||||
 | 
					        "add_to_group": "إضافة إلى المجموعة"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "sponsor_segments": "المقاطع الإعلانية",
 | 
					        "sponsor_segments": "المقاطع الإعلانية",
 | 
				
			||||||
| 
						 | 
					@ -153,7 +162,9 @@
 | 
				
			||||||
        "all": "الكل",
 | 
					        "all": "الكل",
 | 
				
			||||||
        "category": "الفئة",
 | 
					        "category": "الفئة",
 | 
				
			||||||
        "chapters_vertical": "رَأسِيّ",
 | 
					        "chapters_vertical": "رَأسِيّ",
 | 
				
			||||||
        "chapters_horizontal": "أفقي"
 | 
					        "chapters_horizontal": "أفقي",
 | 
				
			||||||
 | 
					        "visibility": "الظهور",
 | 
				
			||||||
 | 
					        "license": "الترخيص"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "channels": "يوتيوب: القنوات",
 | 
					        "channels": "يوتيوب: القنوات",
 | 
				
			||||||
| 
						 | 
					@ -164,7 +175,8 @@
 | 
				
			||||||
        "music_videos": "YT Music: مقاطع فيديو",
 | 
					        "music_videos": "YT Music: مقاطع فيديو",
 | 
				
			||||||
        "did_you_mean": "هل تقصد: {0}؟",
 | 
					        "did_you_mean": "هل تقصد: {0}؟",
 | 
				
			||||||
        "music_playlists": "YT Music: قوائم التشغيل",
 | 
					        "music_playlists": "YT Music: قوائم التشغيل",
 | 
				
			||||||
        "music_albums": "YT Music: ألبومات"
 | 
					        "music_albums": "YT Music: ألبومات",
 | 
				
			||||||
 | 
					        "music_artists": "YT الموسيقى: الفنانين"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "version": "الإصدار",
 | 
					        "version": "الإصدار",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +195,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "اسم المستخدم",
 | 
					        "username": "اسم المستخدم",
 | 
				
			||||||
        "password": "كلمة السر"
 | 
					        "password": "كلمة السر",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "كلمة المرور لم تتطابق!",
 | 
				
			||||||
 | 
					        "password_confirm": "تأكيد كلمة المرور"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "مشترك في: {0}"
 | 
					        "subscribed_channels_count": "مشترك في: {0}"
 | 
				
			||||||
| 
						 | 
					@ -198,6 +212,12 @@
 | 
				
			||||||
        "cannot_copy": "لا يمكن نسخه!",
 | 
					        "cannot_copy": "لا يمكن نسخه!",
 | 
				
			||||||
        "local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟",
 | 
					        "local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟",
 | 
				
			||||||
        "register_no_email_note": "لا ينصح باستخدام البريد الإلكتروني كاسم مستخدم. المضي قدما على أي حال؟",
 | 
					        "register_no_email_note": "لا ينصح باستخدام البريد الإلكتروني كاسم مستخدم. المضي قدما على أي حال؟",
 | 
				
			||||||
        "next_video_countdown": "تشغيل الفيديو التالي بعد { 0 } ق"
 | 
					        "next_video_countdown": "تشغيل الفيديو التالي بعد { 0 } ق",
 | 
				
			||||||
 | 
					        "weeks": "{amount} أسبوع (أسابيع)",
 | 
				
			||||||
 | 
					        "hours": "{amount} ساعة (ساعات)",
 | 
				
			||||||
 | 
					        "months": "{amount} شهر (أشهر)",
 | 
				
			||||||
 | 
					        "days": "{amount} يوم (أيام)",
 | 
				
			||||||
 | 
					        "login_note": "سجل الدخول باستخدام حساب تم إنشاؤه في هذا الخادم.",
 | 
				
			||||||
 | 
					        "register_note": "سجل حسابًا لخادم Piped هذا. سيسمح لك هذا بمزامنة اشتراكاتك وقوائم التشغيل مع حسابك، بحيث يتم تخزينها على جانب الخادم. يمكنك استخدام جميع الميزات بدون حساب، ولكن سيتم تخزين جميع البيانات في ذاكرة التخزين المؤقت المحلية لمتصفحك. يُرجى التأكد من عدم استخدام عنوان البريد الإلكتروني كاسم المستخدم الخاص بك واختيار كلمة مرور آمنة لا تستخدمها في أي مكان آخر."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,8 @@
 | 
				
			||||||
        "livestreams": "Canlı Yayımlar",
 | 
					        "livestreams": "Canlı Yayımlar",
 | 
				
			||||||
        "channels": "Kanallar",
 | 
					        "channels": "Kanallar",
 | 
				
			||||||
        "bookmarks": "Əlfəcinlər",
 | 
					        "bookmarks": "Əlfəcinlər",
 | 
				
			||||||
        "channel_groups": "Kanal qrupları"
 | 
					        "channel_groups": "Kanal qrupları",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "{0} saytında bax"
 | 
					        "watch_on": "{0} saytında bax"
 | 
				
			||||||
| 
						 | 
					@ -49,7 +50,7 @@
 | 
				
			||||||
        "default_quality": "Standart Keyfiyyət",
 | 
					        "default_quality": "Standart Keyfiyyət",
 | 
				
			||||||
        "buffering_goal": "Tamponlama hədəfi (saniyələrlə)",
 | 
					        "buffering_goal": "Tamponlama hədəfi (saniyələrlə)",
 | 
				
			||||||
        "export_to_json": "JSON-a İxrac Et",
 | 
					        "export_to_json": "JSON-a İxrac Et",
 | 
				
			||||||
        "import_from_json": "JSON/CSV-dan İdxal Et",
 | 
					        "import_from_json": "JSON -dan İdxal Et",
 | 
				
			||||||
        "loop_this_video": "Bu Videonu Təkrarla",
 | 
					        "loop_this_video": "Bu Videonu Təkrarla",
 | 
				
			||||||
        "auto_play_next_video": "Növbəti Videonu Avto-Oynat",
 | 
					        "auto_play_next_video": "Növbəti Videonu Avto-Oynat",
 | 
				
			||||||
        "donations": "İnkişaf ianələri",
 | 
					        "donations": "İnkişaf ianələri",
 | 
				
			||||||
| 
						 | 
					@ -74,14 +75,14 @@
 | 
				
			||||||
        "select_playlist": "Oynatma Siyahısı Seç",
 | 
					        "select_playlist": "Oynatma Siyahısı Seç",
 | 
				
			||||||
        "delete_playlist_confirm": "Bu oynatma siyahısı silinsin?",
 | 
					        "delete_playlist_confirm": "Bu oynatma siyahısı silinsin?",
 | 
				
			||||||
        "please_select_playlist": "Xahiş edilir, oynatma siyahısı seç",
 | 
					        "please_select_playlist": "Xahiş edilir, oynatma siyahısı seç",
 | 
				
			||||||
        "country_selection": "Ölkə Seçimi",
 | 
					        "country_selection": "Ölkə",
 | 
				
			||||||
        "default_homepage": "Standart Əsas Səhifə",
 | 
					        "default_homepage": "Standart Əsas Səhifə",
 | 
				
			||||||
        "show_comments": "Şərhləri Göstər",
 | 
					        "show_comments": "Şərhləri Göstər",
 | 
				
			||||||
        "instance_selection": "Nümunə Seçimi",
 | 
					        "instance_selection": "İnstansiya",
 | 
				
			||||||
        "minimize_description_default": "Açıqlamanı Standart Olaraq Kiçilt",
 | 
					        "minimize_description_default": "Açıqlamanı Standart Olaraq Kiçilt",
 | 
				
			||||||
        "language_selection": "Dil Seçimi",
 | 
					        "language_selection": "Dil",
 | 
				
			||||||
        "instances_list": "Nümunələr Siyahısı",
 | 
					        "instances_list": "Nümunələr Siyahısı",
 | 
				
			||||||
        "show_more": "Daha çox göstər",
 | 
					        "show_more": "Daha Çox Göstər",
 | 
				
			||||||
        "no": "Xeyr",
 | 
					        "no": "Xeyr",
 | 
				
			||||||
        "store_watch_history": "Baxış Tarixçəsin Saxla",
 | 
					        "store_watch_history": "Baxış Tarixçəsin Saxla",
 | 
				
			||||||
        "enabled_codecs": "Aktiv Kodlayıcılar (Çoxlu)",
 | 
					        "enabled_codecs": "Aktiv Kodlayıcılar (Çoxlu)",
 | 
				
			||||||
| 
						 | 
					@ -97,11 +98,11 @@
 | 
				
			||||||
        "restore_preferences": "Seçimləri bərpa et",
 | 
					        "restore_preferences": "Seçimləri bərpa et",
 | 
				
			||||||
        "invalidate_session": "Bütün cihazlardan çıxın",
 | 
					        "invalidate_session": "Bütün cihazlardan çıxın",
 | 
				
			||||||
        "different_auth_instance": "Təsdiqləmə üçün fərqli nümunə istifadə et",
 | 
					        "different_auth_instance": "Təsdiqləmə üçün fərqli nümunə istifadə et",
 | 
				
			||||||
        "instance_auth_selection": "Təsdiqləmə Nümunəsi Seçimi",
 | 
					        "instance_auth_selection": "Təsdiqləmə İnstansiyası",
 | 
				
			||||||
        "clone_playlist": "Oynatma Siyahısın Klonla",
 | 
					        "clone_playlist": "Oynatma Siyahısın Klonla",
 | 
				
			||||||
        "clone_playlist_success": "Uğurla klonlandı!",
 | 
					        "clone_playlist_success": "Uğurla klonlandı!",
 | 
				
			||||||
        "time_code": "Vaxt kodu (saniyələrlə)",
 | 
					        "time_code": "Vaxt kodu (saniyələrlə)",
 | 
				
			||||||
        "store_search_history": "Axtarış tarixçəsini saxla",
 | 
					        "store_search_history": "Axtarış Tarixçəsin Saxla",
 | 
				
			||||||
        "documentation": "Sənədləşdirmə",
 | 
					        "documentation": "Sənədləşdirmə",
 | 
				
			||||||
        "status_page": "Vəziyyət",
 | 
					        "status_page": "Vəziyyət",
 | 
				
			||||||
        "source_code": "Mənbə kodu",
 | 
					        "source_code": "Mənbə kodu",
 | 
				
			||||||
| 
						 | 
					@ -131,7 +132,20 @@
 | 
				
			||||||
        "autoplay_next_countdown": "Növbəti videoya qədər standart geri sayım (saniyə)",
 | 
					        "autoplay_next_countdown": "Növbəti videoya qədər standart geri sayım (saniyə)",
 | 
				
			||||||
        "dismiss": "Rədd et",
 | 
					        "dismiss": "Rədd et",
 | 
				
			||||||
        "create_group": "Qrup yarat",
 | 
					        "create_group": "Qrup yarat",
 | 
				
			||||||
        "group_name": "Qrup adı"
 | 
					        "group_name": "Qrup adı",
 | 
				
			||||||
 | 
					        "cancel": "Ləğv et",
 | 
				
			||||||
 | 
					        "edit_playlist": "Oynatma siyahısın redaktə et",
 | 
				
			||||||
 | 
					        "playlist_description": "Oynatma siyahısı təsviri",
 | 
				
			||||||
 | 
					        "okay": "Oldu",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Mobildə Bölmələrin Tərtibatı",
 | 
				
			||||||
 | 
					        "playlist_name": "Oynatma siyahısı adı",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Axtarış təkliflərin göstər",
 | 
				
			||||||
 | 
					        "auto_display_captions": "Titrləri Avtomatik Göstər",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "JSON/CSV-dən idxal et",
 | 
				
			||||||
 | 
					        "delete_automatically": "Sonranı avtomatik silin",
 | 
				
			||||||
 | 
					        "download_frame": "Yükləmə çərçivəsi",
 | 
				
			||||||
 | 
					        "enable_dearrow": "DeArrow'u Aktivləşdir",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR Kodu Yarat"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "{author} tərəfindən sabitlənib",
 | 
					        "pinned_by": "{author} tərəfindən sabitlənib",
 | 
				
			||||||
| 
						 | 
					@ -150,7 +164,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "İstifadəçi Adı",
 | 
					        "username": "İstifadəçi Adı",
 | 
				
			||||||
        "password": "Şifrə"
 | 
					        "password": "Şifrə",
 | 
				
			||||||
 | 
					        "password_confirm": "Parolu təsdiqlə",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Parollar uyğunlaşmır!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Videolar",
 | 
					        "videos": "Videolar",
 | 
				
			||||||
| 
						 | 
					@ -162,7 +178,11 @@
 | 
				
			||||||
        "live": "{0} Canlı",
 | 
					        "live": "{0} Canlı",
 | 
				
			||||||
        "shorts": "Qısa",
 | 
					        "shorts": "Qısa",
 | 
				
			||||||
        "all": "Hamısı",
 | 
					        "all": "Hamısı",
 | 
				
			||||||
        "category": "Kateqoriya"
 | 
					        "category": "Kateqoriya",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Üfüqi",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Şaquli",
 | 
				
			||||||
 | 
					        "license": "Lisenziya",
 | 
				
			||||||
 | 
					        "visibility": "Görünüş"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Bunu nəzərdə tutursunuz: {0}?",
 | 
					        "did_you_mean": "Bunu nəzərdə tutursunuz: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -173,7 +193,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Mahnılar",
 | 
					        "music_songs": "YT Music: Mahnılar",
 | 
				
			||||||
        "music_videos": "YT Music: Videolar",
 | 
					        "music_videos": "YT Music: Videolar",
 | 
				
			||||||
        "music_albums": "YT Music: Albomlar",
 | 
					        "music_albums": "YT Music: Albomlar",
 | 
				
			||||||
        "music_playlists": "YT Music: Oynatma Siyahıları"
 | 
					        "music_playlists": "YT Music: Oynatma Siyahıları",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Sənətkarlar"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Abunə oldu: {0}"
 | 
					        "subscribed_channels_count": "Abunə oldu: {0}"
 | 
				
			||||||
| 
						 | 
					@ -188,6 +209,10 @@
 | 
				
			||||||
        "cannot_copy": "Nüsxələnmir!",
 | 
					        "cannot_copy": "Nüsxələnmir!",
 | 
				
			||||||
        "local_storage": "Bu fəaliyyət yerli yaddaş tələb edir, məlumat bazası aktivdir?",
 | 
					        "local_storage": "Bu fəaliyyət yerli yaddaş tələb edir, məlumat bazası aktivdir?",
 | 
				
			||||||
        "register_no_email_note": "E-poçt-u istifadəçi adı kimi istifadə etmək tövsiyə edilmir. Baxmayaraq ki, davam edilsin?",
 | 
					        "register_no_email_note": "E-poçt-u istifadəçi adı kimi istifadə etmək tövsiyə edilmir. Baxmayaraq ki, davam edilsin?",
 | 
				
			||||||
        "next_video_countdown": "Növbəti video {0} saniyəyə oynadılır"
 | 
					        "next_video_countdown": "Növbəti video {0} saniyəyə oynadılır",
 | 
				
			||||||
 | 
					        "hours": "{amount} saat",
 | 
				
			||||||
 | 
					        "days": "{amount} gün",
 | 
				
			||||||
 | 
					        "months": "{amount} ay",
 | 
				
			||||||
 | 
					        "weeks": "{amount} həftə"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
        "channels": "Канали",
 | 
					        "channels": "Канали",
 | 
				
			||||||
        "login": "Вход",
 | 
					        "login": "Вход",
 | 
				
			||||||
        "register": "Регистрация",
 | 
					        "register": "Регистрация",
 | 
				
			||||||
        "feed": "Абонаменти",
 | 
					        "feed": "Емисия",
 | 
				
			||||||
        "history": "История",
 | 
					        "history": "История",
 | 
				
			||||||
        "playlists": "Плейлисти",
 | 
					        "playlists": "Плейлисти",
 | 
				
			||||||
        "instance": "Инстанция",
 | 
					        "instance": "Инстанция",
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,9 @@
 | 
				
			||||||
        "trending": "Набиращи популярност",
 | 
					        "trending": "Набиращи популярност",
 | 
				
			||||||
        "account": "Профил",
 | 
					        "account": "Профил",
 | 
				
			||||||
        "preferences": "Настройки",
 | 
					        "preferences": "Настройки",
 | 
				
			||||||
        "subscriptions": "Абонаменти"
 | 
					        "subscriptions": "Абонаменти",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow",
 | 
				
			||||||
 | 
					        "channel_groups": "Канални групи"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "most_recent": "Най-скорошен",
 | 
					        "most_recent": "Най-скорошен",
 | 
				
			||||||
| 
						 | 
					@ -110,7 +112,8 @@
 | 
				
			||||||
        "store_search_history": "Запазване на историята на търсене",
 | 
					        "store_search_history": "Запазване на историята на търсене",
 | 
				
			||||||
        "instance_auth_selection": "Избор на инстанция за удостоверяване",
 | 
					        "instance_auth_selection": "Избор на инстанция за удостоверяване",
 | 
				
			||||||
        "confirm_reset_preferences": "Сигурни ли сте, че искате да нулирате настройките?",
 | 
					        "confirm_reset_preferences": "Сигурни ли сте, че искате да нулирате настройките?",
 | 
				
			||||||
        "hide_watched": "Скриване на гледани видеоклипове в Абонаменти"
 | 
					        "hide_watched": "Скриване на гледани видеоклипове в Абонаменти",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Включи DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Гледай в {0}"
 | 
					        "watch_on": "Гледай в {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,174 +1,201 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "login": "Iniciar Sessió",
 | 
					        "login": "Inicia la sessió",
 | 
				
			||||||
        "preferences": "Preferències",
 | 
					        "preferences": "Preferències",
 | 
				
			||||||
        "feed": "Continguts",
 | 
					        "feed": "Continguts",
 | 
				
			||||||
        "history": "Historial",
 | 
					        "history": "Historial",
 | 
				
			||||||
        "subscriptions": "Subscripcions",
 | 
					        "subscriptions": "Subscripcions",
 | 
				
			||||||
        "trending": "Tendències",
 | 
					        "trending": "Tendències",
 | 
				
			||||||
        "register": "Registrar-se",
 | 
					        "register": "Registra'm",
 | 
				
			||||||
        "playlists": "Llistes de reproducció",
 | 
					        "playlists": "Llistes de reproducció",
 | 
				
			||||||
        "account": "Compte",
 | 
					        "account": "Compte",
 | 
				
			||||||
        "instance": "Instància",
 | 
					        "instance": "Instància",
 | 
				
			||||||
        "player": "Reproductor",
 | 
					        "player": "Reproductor",
 | 
				
			||||||
        "livestreams": "Retransmissió en directe",
 | 
					        "livestreams": "Directes",
 | 
				
			||||||
        "channels": "Canals",
 | 
					        "channels": "Canals",
 | 
				
			||||||
        "bookmarks": "Marcadors"
 | 
					        "bookmarks": "Marcadors",
 | 
				
			||||||
 | 
					        "channel_groups": "Grups de canals",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "channel_name_desc": "Nom del Canal (Z-A)",
 | 
					        "channel_name_desc": "Nom del canal (Z-A)",
 | 
				
			||||||
        "back": "Enrere",
 | 
					        "back": "Enrere",
 | 
				
			||||||
        "skip_intro": "Omet l'Animació d'Entreacte/Introducció",
 | 
					        "skip_intro": "Omet l'animació d'interludi/introducció",
 | 
				
			||||||
        "skip_outro": "Omet les Targetes/Crèdits",
 | 
					        "skip_outro": "Omet els crèdits finals",
 | 
				
			||||||
        "skip_preview": "Omet la Previsualització/Recapitulació",
 | 
					        "skip_preview": "Omet la previsualització/recapitulació",
 | 
				
			||||||
        "skip_interaction": "Omet el Recordatori d'Interacció (Subscriu-te)",
 | 
					        "skip_interaction": "Omet el recordatori d'interacció (Subscriviu-vos)",
 | 
				
			||||||
        "skip_self_promo": "Omet la Promoció no Remunerada/Autopromoció",
 | 
					        "skip_self_promo": "Omet la promoció no remunerada/autopromoció",
 | 
				
			||||||
        "skip_non_music": "Omet la Música: Secció No-Músical",
 | 
					        "skip_non_music": "Omet la música: Secció no musical",
 | 
				
			||||||
        "skip_highlight": "Omet els Destacats",
 | 
					        "skip_highlight": "Omet els destacats",
 | 
				
			||||||
        "skip_filler_tangent": "Omet les Tangents de Farciment",
 | 
					        "skip_filler_tangent": "Omet les tangents de farciment",
 | 
				
			||||||
        "theme": "Tema",
 | 
					        "theme": "Tema",
 | 
				
			||||||
        "auto": "Automàtic",
 | 
					        "auto": "Automàtic",
 | 
				
			||||||
        "dark": "Fosc",
 | 
					        "dark": "Fosc",
 | 
				
			||||||
        "light": "Clar",
 | 
					        "light": "Clar",
 | 
				
			||||||
        "autoplay_video": "Reprodueix Automàticament el Vídeo",
 | 
					        "autoplay_video": "Reprodueix automàticament el vídeo",
 | 
				
			||||||
        "audio_only": "Només Àudio",
 | 
					        "audio_only": "Només àudio",
 | 
				
			||||||
        "default_quality": "Qualitat Per Defecte",
 | 
					        "default_quality": "Qualitat predefinida",
 | 
				
			||||||
        "buffering_goal": "Objectiu de la Memòria Intermèdia (en segons)",
 | 
					        "buffering_goal": "Objectiu de la memòria intermèdia (en segons)",
 | 
				
			||||||
        "country_selection": "Selecciona el País",
 | 
					        "country_selection": "País",
 | 
				
			||||||
        "default_homepage": "Pàgina Inicial Per Defecte",
 | 
					        "default_homepage": "Pàgina d'inici predefinida",
 | 
				
			||||||
        "show_comments": "Mostra els Comentaris",
 | 
					        "show_comments": "Mostra els comentaris",
 | 
				
			||||||
        "minimize_description_default": "Minimitza la Descripció Per Defecte",
 | 
					        "minimize_description_default": "Minimitza la descripció per defecte",
 | 
				
			||||||
        "store_watch_history": "Desa l'Historial de Reproducció",
 | 
					        "store_watch_history": "Desa l'historial de visualitzacions",
 | 
				
			||||||
        "language_selection": "Selecciona l'Idioma",
 | 
					        "language_selection": "Llengua",
 | 
				
			||||||
        "enabled_codecs": "Còdecs Habilitats (Múltiple)",
 | 
					        "enabled_codecs": "Còdecs habilitats (múltiples)",
 | 
				
			||||||
        "instances_list": "Llista d'Instàncies",
 | 
					        "instances_list": "Llista d'instàncies",
 | 
				
			||||||
        "instance_selection": "Selecció d'Instàncies",
 | 
					        "instance_selection": "Instància",
 | 
				
			||||||
        "show_more": "Mostrar més",
 | 
					        "show_more": "Mostra'n més",
 | 
				
			||||||
        "yes": "Sí",
 | 
					        "yes": "Sí",
 | 
				
			||||||
        "no": "No",
 | 
					        "no": "No",
 | 
				
			||||||
        "export_to_json": "Exportar a JSON",
 | 
					        "export_to_json": "Exporta a JSON",
 | 
				
			||||||
        "import_from_json": "Importar de JSON/CSV",
 | 
					        "import_from_json": "Importa des d'un JSON",
 | 
				
			||||||
        "loop_this_video": "Reprodueix aquest Vídeo en bucle",
 | 
					        "loop_this_video": "Reprodueix el vídeo en bucle",
 | 
				
			||||||
        "donations": "Donacions de desenvolupament",
 | 
					        "donations": "Donacions al desenvolupament",
 | 
				
			||||||
        "minimize_description": "Minimitza la Descripció",
 | 
					        "minimize_description": "Minimitza la descripció",
 | 
				
			||||||
        "show_description": "Mostra la Descripció",
 | 
					        "show_description": "Mostra la descripció",
 | 
				
			||||||
        "disable_lbry": "Deshabilita LBRY per Streaming",
 | 
					        "disable_lbry": "Desactiva el LBRY per a les transmissions en temps real",
 | 
				
			||||||
        "enable_lbry_proxy": "Habilita servidor intermediari per LBRY",
 | 
					        "enable_lbry_proxy": "Activa un servidor intermediari per al LBRY",
 | 
				
			||||||
        "view_ssl_score": "Visualitza la Puntuació SSL",
 | 
					        "view_ssl_score": "Mostra la puntuació SSL",
 | 
				
			||||||
        "search": "Cercar",
 | 
					        "search": "Cerca (Ctrl+K)",
 | 
				
			||||||
        "filter": "Filtre",
 | 
					        "filter": "Filtre",
 | 
				
			||||||
        "loading": "Carregant...",
 | 
					        "loading": "S'està carregant...",
 | 
				
			||||||
        "clear_history": "Netejar Historial",
 | 
					        "clear_history": "Esborra l'historial",
 | 
				
			||||||
        "hide_replies": "Oculta Respostes",
 | 
					        "hide_replies": "Amaga les respostes",
 | 
				
			||||||
        "load_more_replies": "Carrega més Respostes",
 | 
					        "load_more_replies": "Carrega més respostes",
 | 
				
			||||||
        "view_subscriptions": "Veure Subscripcions",
 | 
					        "view_subscriptions": "Mostra les subscripcions",
 | 
				
			||||||
        "subscribe": "Subscriure - {count}",
 | 
					        "subscribe": "Subscriu-me - {count}",
 | 
				
			||||||
        "unsubscribe": "Anul·lar subscripció - {count}",
 | 
					        "unsubscribe": "Anul·la la subscripció - {count}",
 | 
				
			||||||
        "sort_by": "Ordenar per:",
 | 
					        "sort_by": "Ordena per:",
 | 
				
			||||||
        "most_recent": "Més Recents",
 | 
					        "most_recent": "Més recents",
 | 
				
			||||||
        "least_recent": "Menys Recents",
 | 
					        "least_recent": "Menys recents",
 | 
				
			||||||
        "skip_sponsors": "Omet Promocions",
 | 
					        "skip_sponsors": "Omet els patrocinadors",
 | 
				
			||||||
        "channel_name_asc": "Nom del Canal (A-Z)",
 | 
					        "channel_name_asc": "Nom del canal (A-Z)",
 | 
				
			||||||
        "enable_sponsorblock": "Habilita Sponsorblock",
 | 
					        "enable_sponsorblock": "Activa l'Sponsorblock",
 | 
				
			||||||
        "uses_api_from": "Utilitza l'API de ",
 | 
					        "uses_api_from": "Utilitza l'API de ",
 | 
				
			||||||
        "minimize_recommendations": "Minimitza Recomanacions",
 | 
					        "minimize_recommendations": "Minimitza les recomanacions",
 | 
				
			||||||
        "show_recommendations": "Mostra Recomanacions",
 | 
					        "show_recommendations": "Mostra les recomanacions",
 | 
				
			||||||
        "auto_play_next_video": "Reprodueix automàticament el pròxim vídeo",
 | 
					        "auto_play_next_video": "Reprodueix automàticament el pròxim vídeo",
 | 
				
			||||||
        "add_to_playlist": "Afegir a la llista de reproducció",
 | 
					        "add_to_playlist": "Afegeix a la llista de reproducció",
 | 
				
			||||||
        "remove_from_playlist": "Suprimeix de la llista de reproducció",
 | 
					        "remove_from_playlist": "Suprimeix de la llista de reproducció",
 | 
				
			||||||
        "create_playlist": "Crear llista de reproducció",
 | 
					        "create_playlist": "Crea una llista de reproducció",
 | 
				
			||||||
        "delete_playlist": "Suprimir llista de reproducció",
 | 
					        "delete_playlist": "Suprimeix la llista de reproducció",
 | 
				
			||||||
        "please_select_playlist": "Selecciona una llista de reproducció",
 | 
					        "please_select_playlist": "Seleccioneu una llista de reproducció",
 | 
				
			||||||
        "delete_playlist_confirm": "Eliminar aquesta llista de reproducció?",
 | 
					        "delete_playlist_confirm": "Voleu suprimir la llista de reproducció?",
 | 
				
			||||||
        "delete_playlist_video_confirm": "Eliminar aquest vídeo de la llista de reproducció?",
 | 
					        "delete_playlist_video_confirm": "Voleu suprimir el vídeo de la llista de reproducció?",
 | 
				
			||||||
        "select_playlist": "Selecciona una llista de reproducció",
 | 
					        "select_playlist": "Selecciona una llista de reproducció",
 | 
				
			||||||
        "show_markers": "Mostra Marcadors al Reproductor",
 | 
					        "show_markers": "Mostra marcadors al reproductor",
 | 
				
			||||||
        "delete_account": "Eliminar Compte",
 | 
					        "delete_account": "Suprimeix el compte",
 | 
				
			||||||
        "logout": "Desconnectar aquest dispositiu",
 | 
					        "logout": "Desconnecta aquest dispositiu",
 | 
				
			||||||
        "minimize_recommendations_default": "Minimitza les Recomanacions per defecte",
 | 
					        "minimize_recommendations_default": "Minimitza les recomanacions per defecte",
 | 
				
			||||||
        "invalidate_session": "Desconnectar tots els dispositius",
 | 
					        "invalidate_session": "Desconnecta tots els dispositius",
 | 
				
			||||||
        "different_auth_instance": "Usa una instància diferent per a l'autenticació",
 | 
					        "different_auth_instance": "Usa una instància diferent per a l'autenticació",
 | 
				
			||||||
        "instance_auth_selection": "Selecciona la instància d'autenticació",
 | 
					        "instance_auth_selection": "Instància d'autenticació",
 | 
				
			||||||
        "clone_playlist": "Clonar Llista de Reproducció",
 | 
					        "clone_playlist": "Clona la llista de reproducció",
 | 
				
			||||||
        "clone_playlist_success": "Clonada correctament!",
 | 
					        "clone_playlist_success": "S'ha clonat correctament!",
 | 
				
			||||||
        "download_as_txt": "Descarrega com a .txt",
 | 
					        "download_as_txt": "Baixa com a .txt",
 | 
				
			||||||
        "reset_preferences": "Restablir preferències",
 | 
					        "reset_preferences": "Restableix les preferències",
 | 
				
			||||||
        "restore_preferences": "Restaura les preferències",
 | 
					        "restore_preferences": "Restaura les preferències",
 | 
				
			||||||
        "backup_preferences": "Preferències de la còpia de seguretat",
 | 
					        "backup_preferences": "Exporta les preferències",
 | 
				
			||||||
        "confirm_reset_preferences": "Esteu segur que voleu restablir les vostres preferències?",
 | 
					        "confirm_reset_preferences": "Segur que voleu restablir les vostres preferències?",
 | 
				
			||||||
        "back_to_home": "Torna a l'inici",
 | 
					        "back_to_home": "Torna a l'inici",
 | 
				
			||||||
        "with_timecode": "Compartir moment concret",
 | 
					        "with_timecode": "Comparteix amb marca de temps",
 | 
				
			||||||
        "piped_link": "Enllaç de Piped",
 | 
					        "piped_link": "Enllaç de Piped",
 | 
				
			||||||
        "share": "Compartir",
 | 
					        "share": "Comparteix",
 | 
				
			||||||
        "time_code": "Moment (en segons)",
 | 
					        "time_code": "Marca de temps (en segons)",
 | 
				
			||||||
        "copy_link": "Copiar l'enllaç",
 | 
					        "copy_link": "Copia l'enllaç",
 | 
				
			||||||
        "follow_link": "Vés a l'enllaç",
 | 
					        "follow_link": "Vés a l'enllaç",
 | 
				
			||||||
        "store_search_history": "Emmagatzema l'historial de cerca",
 | 
					        "store_search_history": "Emmagatzema l'historial de cerca",
 | 
				
			||||||
        "instance_donations": "Donacions a instàncies",
 | 
					        "instance_donations": "Donacions a instàncies",
 | 
				
			||||||
        "hide_watched": "Amaga els vídeos vistos de Continguts",
 | 
					        "hide_watched": "Amaga els vídeos vistos dels continguts",
 | 
				
			||||||
        "show_chapters": "Capítols",
 | 
					        "show_chapters": "Capítols",
 | 
				
			||||||
        "status_page": "Estat",
 | 
					        "status_page": "Estat",
 | 
				
			||||||
        "source_code": "Codi font",
 | 
					        "source_code": "Codi font",
 | 
				
			||||||
        "documentation": "Documentació",
 | 
					        "documentation": "Documentació",
 | 
				
			||||||
        "show_watch_on_youtube": "Mostra el botó \"Veure a Youtube\"",
 | 
					        "show_watch_on_youtube": "Mostra el botó «Mostra'l a Youtube»",
 | 
				
			||||||
        "reply_count": "{count} respostes",
 | 
					        "reply_count": "{count} respostes",
 | 
				
			||||||
        "minimize_comments_default": "Minimitzar els comentaris per defecte",
 | 
					        "minimize_comments_default": "Minimitza els comentaris per defecte",
 | 
				
			||||||
        "minimize_comments": "Minimitza els comentaris",
 | 
					        "minimize_comments": "Minimitza els comentaris",
 | 
				
			||||||
        "no_valid_playlists": "L'arxiu no conté llistes de reproducció vàlides!",
 | 
					        "no_valid_playlists": "El fitxer no conté llistes de reproducció vàlides!",
 | 
				
			||||||
        "bookmark_playlist": "Marcador",
 | 
					        "bookmark_playlist": "Afegeix als marcadors",
 | 
				
			||||||
        "playlist_bookmarked": "Afegit a marcadors",
 | 
					        "playlist_bookmarked": "S'ha afegit als marcadors",
 | 
				
			||||||
        "minimize_chapters_default": "Minimitzar capítols per defecte",
 | 
					        "minimize_chapters_default": "Minimitza els capítols per defecte",
 | 
				
			||||||
        "skip_button_only": "Mostra el botó de saltar",
 | 
					        "skip_button_only": "Mostra el botó de saltar",
 | 
				
			||||||
        "skip_automatically": "Automàticament",
 | 
					        "skip_automatically": "Automàticament",
 | 
				
			||||||
        "min_segment_length": "Longitud de segment mínima (en segons)",
 | 
					        "min_segment_length": "Longitud de segment mínima (en segons)",
 | 
				
			||||||
        "skip_segment": "Saltar segment",
 | 
					        "skip_segment": "Omet el segment",
 | 
				
			||||||
        "with_playlist": "Comparteix amb llista de reproducció",
 | 
					        "with_playlist": "Comparteix amb la llista de reproducció",
 | 
				
			||||||
        "show_less": "Mostrar menys"
 | 
					        "show_less": "Mostra'n menys",
 | 
				
			||||||
 | 
					        "okay": "D'acord",
 | 
				
			||||||
 | 
					        "edit_playlist": "Edita la llista de reproducció",
 | 
				
			||||||
 | 
					        "group_name": "Nom del grup",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importa des d'un JSON/CSV",
 | 
				
			||||||
 | 
					        "cancel": "Cancel·la",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Activa el DeArrow",
 | 
				
			||||||
 | 
					        "playlist_description": "Descripció de la llista de reproducció",
 | 
				
			||||||
 | 
					        "dismiss": "Descarta",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Disposició dels capítols al mòbil",
 | 
				
			||||||
 | 
					        "delete_automatically": "Suprimeix automàticament després de",
 | 
				
			||||||
 | 
					        "download_frame": "Baixa el fotograma",
 | 
				
			||||||
 | 
					        "create_group": "Crea un grup",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Mostra suggeriments de cerca",
 | 
				
			||||||
 | 
					        "auto_display_captions": "Mostra els subtítols automàticament",
 | 
				
			||||||
 | 
					        "autoplay_next_countdown": "Compte enrere predefinit fins al pròxim vídeo (en segons)",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Genera un codi QR",
 | 
				
			||||||
 | 
					        "playlist_name": "Nom de la llista de reproducció"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Fixat per {author}",
 | 
					        "pinned_by": "Fixat per {author}",
 | 
				
			||||||
        "user_disabled": "Comentaris deshabilitats en els paràmetres.",
 | 
					        "user_disabled": "Els comentaris s'han desactivat en els paràmetres.",
 | 
				
			||||||
        "disabled": "El carregador ha desactivat els comentaris.",
 | 
					        "disabled": "Qui va pujar el vídeo ha desactivat els comentaris.",
 | 
				
			||||||
        "loading": "Carregant comentaris..."
 | 
					        "loading": "S'estan carregant els comentaris..."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "instance_name": "Nom de la Instància",
 | 
					        "instance_name": "Nom de la instància",
 | 
				
			||||||
        "instance_locations": "Ubicacións de la Instància",
 | 
					        "instance_locations": "Ubicacions de la instància",
 | 
				
			||||||
        "has_cdn": "Té CDN?",
 | 
					        "has_cdn": "Té CDN?",
 | 
				
			||||||
        "ssl_score": "Puntuació SSL",
 | 
					        "ssl_score": "Puntuació SSL",
 | 
				
			||||||
        "version": "Versió",
 | 
					        "version": "Versió",
 | 
				
			||||||
        "up_to_date": "Actualitzat?",
 | 
					        "up_to_date": "Actualitzada?",
 | 
				
			||||||
        "registered_users": "Usuaris Registrats"
 | 
					        "registered_users": "Usuaris registrats"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "watched": "Vist",
 | 
					        "watched": "Vist",
 | 
				
			||||||
        "sponsor_segments": "Segments de Patrocinadors",
 | 
					        "sponsor_segments": "Segments de patrocinadors",
 | 
				
			||||||
        "ratings_disabled": "Puntuacions Deshabilitades",
 | 
					        "ratings_disabled": "Puntuacions desactivades",
 | 
				
			||||||
        "chapters": "Capítols",
 | 
					        "chapters": "Capítols",
 | 
				
			||||||
        "live": "{0} En Directe",
 | 
					        "live": "{0} en directe",
 | 
				
			||||||
        "videos": "Vídeos",
 | 
					        "videos": "Vídeos",
 | 
				
			||||||
        "views": "{views} visualitzacions",
 | 
					        "views": "{views} visualitzacions",
 | 
				
			||||||
        "shorts": "Curts",
 | 
					        "shorts": "Curts",
 | 
				
			||||||
        "all": "Tot",
 | 
					        "all": "Tot",
 | 
				
			||||||
        "category": "Categoria"
 | 
					        "category": "Categoria",
 | 
				
			||||||
 | 
					        "visibility": "Visibilitat",
 | 
				
			||||||
 | 
					        "license": "Llicència",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Vertical",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Horitzontal"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Volies dir: {0}?",
 | 
					        "did_you_mean": "Volíeu dir: {0}?",
 | 
				
			||||||
        "all": "YouTube: Tot",
 | 
					        "all": "YouTube: Tot",
 | 
				
			||||||
        "videos": "YouTube: Vídeos",
 | 
					        "videos": "YouTube: Vídeos",
 | 
				
			||||||
        "channels": "YouTube: Canals",
 | 
					        "channels": "YouTube: Canals",
 | 
				
			||||||
        "playlists": "YouTube: Llistes de Reproducció",
 | 
					        "playlists": "YouTube: Llistes de reproducció",
 | 
				
			||||||
        "music_songs": "YT Music: Cançons",
 | 
					        "music_songs": "YT Music: Cançons",
 | 
				
			||||||
        "music_videos": "YT Music: Vídeos",
 | 
					        "music_videos": "YT Music: Vídeos",
 | 
				
			||||||
        "music_albums": "YT Music: Àlbums",
 | 
					        "music_albums": "YT Music: Àlbums",
 | 
				
			||||||
        "music_playlists": "YT Music: Llistes de Reproducció"
 | 
					        "music_playlists": "YT Music: Llistes de reproducció",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artistes"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Veure a {0}"
 | 
					        "watch_on": "Mostra a {0}",
 | 
				
			||||||
 | 
					        "failed": "Ha fallat amb codi d'error {0}, vegeu els registres per a més informació"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Nom d'Usuari",
 | 
					        "username": "Nom d'usuari",
 | 
				
			||||||
        "password": "Contrassenya"
 | 
					        "password": "Contrasenya",
 | 
				
			||||||
 | 
					        "password_confirm": "Confirmeu la contrasenya",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Les contrasenyes no coincideixen!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Subscrit a: {0}"
 | 
					        "subscribed_channels_count": "Subscrit a: {0}"
 | 
				
			||||||
| 
						 | 
					@ -179,9 +206,14 @@
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "preferences_note": "Nota: les preferències es desen a l'emmagatzematge local del navegador. Si elimineu les dades del navegador, es restabliran.",
 | 
					        "preferences_note": "Nota: les preferències es desen a l'emmagatzematge local del navegador. Si elimineu les dades del navegador, es restabliran.",
 | 
				
			||||||
        "page_not_found": "No s'ha torbat la pàgina",
 | 
					        "page_not_found": "No s'ha torbat la pàgina",
 | 
				
			||||||
        "copied": "Copiat!",
 | 
					        "copied": "S'ha copiat!",
 | 
				
			||||||
        "cannot_copy": "No es pot copiar!",
 | 
					        "cannot_copy": "No es pot copiar!",
 | 
				
			||||||
        "local_storage": "Aquesta acció requereix emmagatzematge local, estan les cookies habilitades?",
 | 
					        "local_storage": "Aquesta acció requereix localStorage. Es troben activades les galetes?",
 | 
				
			||||||
        "register_no_email_note": "Utilitzar un correu elextrònic com a usuari no és recomanable. Continuar de totes maneres?"
 | 
					        "register_no_email_note": "Usar una adreça electrònica com a nom d'usuari no és recomanable. Voleu continuar de totes maneres?",
 | 
				
			||||||
 | 
					        "hours": "{amount} hores",
 | 
				
			||||||
 | 
					        "next_video_countdown": "Pròxim vídeo en {0}s",
 | 
				
			||||||
 | 
					        "months": "{amount} mesos",
 | 
				
			||||||
 | 
					        "weeks": "{amount} setmanes",
 | 
				
			||||||
 | 
					        "days": "{amount} dies"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,8 @@
 | 
				
			||||||
        "livestreams": "Živé přenosy",
 | 
					        "livestreams": "Živé přenosy",
 | 
				
			||||||
        "channels": "Kanály",
 | 
					        "channels": "Kanály",
 | 
				
			||||||
        "bookmarks": "Záložky",
 | 
					        "bookmarks": "Záložky",
 | 
				
			||||||
        "channel_groups": "Skupiny kanálů"
 | 
					        "channel_groups": "Skupiny kanálů",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "loop_this_video": "Přehrávat video ve smyčce",
 | 
					        "loop_this_video": "Přehrávat video ve smyčce",
 | 
				
			||||||
| 
						 | 
					@ -37,20 +38,20 @@
 | 
				
			||||||
        "audio_only": "Pouze zvuk",
 | 
					        "audio_only": "Pouze zvuk",
 | 
				
			||||||
        "default_quality": "Výchozí kvalita",
 | 
					        "default_quality": "Výchozí kvalita",
 | 
				
			||||||
        "buffering_goal": "Ukládání do vyrovnávací paměti (v sekundách)",
 | 
					        "buffering_goal": "Ukládání do vyrovnávací paměti (v sekundách)",
 | 
				
			||||||
        "country_selection": "Výběr země",
 | 
					        "country_selection": "Země",
 | 
				
			||||||
        "default_homepage": "Výchozí domovská stránka",
 | 
					        "default_homepage": "Výchozí domovská stránka",
 | 
				
			||||||
        "show_comments": "Zobrazit komentáře",
 | 
					        "show_comments": "Zobrazit komentáře",
 | 
				
			||||||
        "minimize_description_default": "Automaticky minimalizovat popis",
 | 
					        "minimize_description_default": "Automaticky minimalizovat popis",
 | 
				
			||||||
        "store_watch_history": "Ukládat historii sledování",
 | 
					        "store_watch_history": "Ukládat historii sledování",
 | 
				
			||||||
        "language_selection": "Výběr jazyka",
 | 
					        "language_selection": "Jazyk",
 | 
				
			||||||
        "instances_list": "Seznam instancí",
 | 
					        "instances_list": "Seznam instancí",
 | 
				
			||||||
        "enabled_codecs": "Povolené kodeky (několik)",
 | 
					        "enabled_codecs": "Povolené kodeky (několik)",
 | 
				
			||||||
        "instance_selection": "Výběr instance",
 | 
					        "instance_selection": "Instance",
 | 
				
			||||||
        "show_more": "Zobrazit více",
 | 
					        "show_more": "Zobrazit více",
 | 
				
			||||||
        "yes": "Ano",
 | 
					        "yes": "Ano",
 | 
				
			||||||
        "no": "Ne",
 | 
					        "no": "Ne",
 | 
				
			||||||
        "export_to_json": "Exportovat do JSON",
 | 
					        "export_to_json": "Exportovat do JSON",
 | 
				
			||||||
        "import_from_json": "Importovat z JSON/CSV",
 | 
					        "import_from_json": "Importovat z JSON",
 | 
				
			||||||
        "auto_play_next_video": "Automaticky přehrát další video",
 | 
					        "auto_play_next_video": "Automaticky přehrát další video",
 | 
				
			||||||
        "donations": "Dary na vývoj",
 | 
					        "donations": "Dary na vývoj",
 | 
				
			||||||
        "show_description": "Zobrazit popis",
 | 
					        "show_description": "Zobrazit popis",
 | 
				
			||||||
| 
						 | 
					@ -89,7 +90,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "Ve výchozím nastavení minimalizovat doporučení",
 | 
					        "minimize_recommendations_default": "Ve výchozím nastavení minimalizovat doporučení",
 | 
				
			||||||
        "invalidate_session": "Odhlásit se ze všech zařízení",
 | 
					        "invalidate_session": "Odhlásit se ze všech zařízení",
 | 
				
			||||||
        "different_auth_instance": "Použít jinou instanci pro autentizaci",
 | 
					        "different_auth_instance": "Použít jinou instanci pro autentizaci",
 | 
				
			||||||
        "instance_auth_selection": "Výběr autentizační instance",
 | 
					        "instance_auth_selection": "Autentizační instance",
 | 
				
			||||||
        "clone_playlist": "Duplikovat playlist",
 | 
					        "clone_playlist": "Duplikovat playlist",
 | 
				
			||||||
        "clone_playlist_success": "Úspěšně duplikováno!",
 | 
					        "clone_playlist_success": "Úspěšně duplikováno!",
 | 
				
			||||||
        "download_as_txt": "Stáhnout jako .txt",
 | 
					        "download_as_txt": "Stáhnout jako .txt",
 | 
				
			||||||
| 
						 | 
					@ -136,10 +137,17 @@
 | 
				
			||||||
        "playlist_description": "Popis playlistu",
 | 
					        "playlist_description": "Popis playlistu",
 | 
				
			||||||
        "okay": "Okay",
 | 
					        "okay": "Okay",
 | 
				
			||||||
        "show_search_suggestions": "Zobrazit našeptávání ve vyhledávání",
 | 
					        "show_search_suggestions": "Zobrazit našeptávání ve vyhledávání",
 | 
				
			||||||
        "chapters_layout_mobile": "Rozložení kapitol na mobilu"
 | 
					        "chapters_layout_mobile": "Rozložení kapitol na mobilu",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Povolit DeArrow",
 | 
				
			||||||
 | 
					        "delete_automatically": "Automaticky odstranit po",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Vygenerovat QR kód",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importovat z JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Stáhnout snímek",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Ochrana údajů"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Sledovat na {0}"
 | 
					        "watch_on": "Zobrazit na {0}",
 | 
				
			||||||
 | 
					        "failed": "Akce se nezdařila. Chybový kód {0}, pro více informací viz protokol"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Připnuto uživatelem {author}",
 | 
					        "pinned_by": "Připnuto uživatelem {author}",
 | 
				
			||||||
| 
						 | 
					@ -158,7 +166,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Uživatelské jméno",
 | 
					        "username": "Uživatelské jméno",
 | 
				
			||||||
        "password": "Heslo"
 | 
					        "password": "Heslo",
 | 
				
			||||||
 | 
					        "password_confirm": "Potvrzení hesla",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Hesla se neshodují!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Videa",
 | 
					        "videos": "Videa",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +182,9 @@
 | 
				
			||||||
        "all": "Vše",
 | 
					        "all": "Vše",
 | 
				
			||||||
        "category": "Kategorie",
 | 
					        "category": "Kategorie",
 | 
				
			||||||
        "chapters_horizontal": "Horizontální",
 | 
					        "chapters_horizontal": "Horizontální",
 | 
				
			||||||
        "chapters_vertical": "Vertikální"
 | 
					        "chapters_vertical": "Vertikální",
 | 
				
			||||||
 | 
					        "license": "Licence",
 | 
				
			||||||
 | 
					        "visibility": "Viditelnost"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Mysleli jste: {0}?",
 | 
					        "did_you_mean": "Mysleli jste: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +195,8 @@
 | 
				
			||||||
        "playlists": "YouTube: Playlisty",
 | 
					        "playlists": "YouTube: Playlisty",
 | 
				
			||||||
        "music_videos": "YT Music: Videa",
 | 
					        "music_videos": "YT Music: Videa",
 | 
				
			||||||
        "music_albums": "YT Music: Alba",
 | 
					        "music_albums": "YT Music: Alba",
 | 
				
			||||||
        "music_playlists": "YT Music: Playlisty"
 | 
					        "music_playlists": "YT Music: Playlisty",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Umělci"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Přihlášeno k odběru: {0}"
 | 
					        "subscribed_channels_count": "Přihlášeno k odběru: {0}"
 | 
				
			||||||
| 
						 | 
					@ -198,6 +211,10 @@
 | 
				
			||||||
        "cannot_copy": "Nelze zkopírovat!",
 | 
					        "cannot_copy": "Nelze zkopírovat!",
 | 
				
			||||||
        "local_storage": "Tato akce vyžaduje localStorage, jsou povoleny cookies?",
 | 
					        "local_storage": "Tato akce vyžaduje localStorage, jsou povoleny cookies?",
 | 
				
			||||||
        "register_no_email_note": "Použití e-mailu jako uživatelského jména se nedoporučuje. Chcete přesto pokračovat?",
 | 
					        "register_no_email_note": "Použití e-mailu jako uživatelského jména se nedoporučuje. Chcete přesto pokračovat?",
 | 
				
			||||||
        "next_video_countdown": "Přehrávání dalšího videa za {0}s"
 | 
					        "next_video_countdown": "Přehrávání dalšího videa za {0}s",
 | 
				
			||||||
 | 
					        "hours": "{amount} hodin",
 | 
				
			||||||
 | 
					        "days": "{amount} dnů",
 | 
				
			||||||
 | 
					        "weeks": "{amount} týdnů",
 | 
				
			||||||
 | 
					        "months": "{amount} měsíců"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,12 +6,12 @@
 | 
				
			||||||
        "skip_interaction": "Interaktionserinnerungen überspringen (Daumen hoch, abonnieren, ...)",
 | 
					        "skip_interaction": "Interaktionserinnerungen überspringen (Daumen hoch, abonnieren, ...)",
 | 
				
			||||||
        "skip_preview": "Vorschau und Rückblick überspringen",
 | 
					        "skip_preview": "Vorschau und Rückblick überspringen",
 | 
				
			||||||
        "instances_list": "Liste der Instanzen",
 | 
					        "instances_list": "Liste der Instanzen",
 | 
				
			||||||
        "language_selection": "Sprachauswahl",
 | 
					        "language_selection": "Sprache",
 | 
				
			||||||
        "store_watch_history": "Wiedergabeverlauf speichern",
 | 
					        "store_watch_history": "Wiedergabeverlauf speichern",
 | 
				
			||||||
        "minimize_description_default": "Beschreibung standardmäßig minimieren",
 | 
					        "minimize_description_default": "Beschreibung standardmäßig minimieren",
 | 
				
			||||||
        "show_comments": "Kommentare anzeigen",
 | 
					        "show_comments": "Kommentare anzeigen",
 | 
				
			||||||
        "default_homepage": "Startseite",
 | 
					        "default_homepage": "Startseite",
 | 
				
			||||||
        "country_selection": "Länderauswahl",
 | 
					        "country_selection": "Land",
 | 
				
			||||||
        "buffering_goal": "Pufferungsziel (in Sekunden)",
 | 
					        "buffering_goal": "Pufferungsziel (in Sekunden)",
 | 
				
			||||||
        "default_quality": "Standardqualität",
 | 
					        "default_quality": "Standardqualität",
 | 
				
			||||||
        "audio_only": "Nur Audio",
 | 
					        "audio_only": "Nur Audio",
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@
 | 
				
			||||||
        "enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)",
 | 
					        "enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)",
 | 
				
			||||||
        "enable_lbry_proxy": "Proxy für LBRY einschalten",
 | 
					        "enable_lbry_proxy": "Proxy für LBRY einschalten",
 | 
				
			||||||
        "disable_lbry": "LBRY für Streaming deaktivieren",
 | 
					        "disable_lbry": "LBRY für Streaming deaktivieren",
 | 
				
			||||||
        "instance_selection": "Instanz auswählen",
 | 
					        "instance_selection": "Instanz",
 | 
				
			||||||
        "show_description": "Beschreibung anzeigen",
 | 
					        "show_description": "Beschreibung anzeigen",
 | 
				
			||||||
        "minimize_description": "Beschreibung minimieren",
 | 
					        "minimize_description": "Beschreibung minimieren",
 | 
				
			||||||
        "show_recommendations": "Empfehlungen anzeigen",
 | 
					        "show_recommendations": "Empfehlungen anzeigen",
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@
 | 
				
			||||||
        "donations": "Spenden",
 | 
					        "donations": "Spenden",
 | 
				
			||||||
        "auto_play_next_video": "Nächstes Video automatisch abspielen",
 | 
					        "auto_play_next_video": "Nächstes Video automatisch abspielen",
 | 
				
			||||||
        "loop_this_video": "Dieses Video wiederholen",
 | 
					        "loop_this_video": "Dieses Video wiederholen",
 | 
				
			||||||
        "import_from_json": "Aus JSON/CSV importieren",
 | 
					        "import_from_json": "Aus JSON importieren",
 | 
				
			||||||
        "export_to_json": "Als JSON exportieren",
 | 
					        "export_to_json": "Als JSON exportieren",
 | 
				
			||||||
        "show_more": "Mehr anzeigen",
 | 
					        "show_more": "Mehr anzeigen",
 | 
				
			||||||
        "no": "Nein",
 | 
					        "no": "Nein",
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "Empfehlungen standardmäßig minimieren",
 | 
					        "minimize_recommendations_default": "Empfehlungen standardmäßig minimieren",
 | 
				
			||||||
        "invalidate_session": "Von allen Geräten abmelden",
 | 
					        "invalidate_session": "Von allen Geräten abmelden",
 | 
				
			||||||
        "different_auth_instance": "Eine andere Instanz für die Authentifizierung verwenden",
 | 
					        "different_auth_instance": "Eine andere Instanz für die Authentifizierung verwenden",
 | 
				
			||||||
        "instance_auth_selection": "Auswahl der Autentifizierungsinstanz",
 | 
					        "instance_auth_selection": "Authentifizierungsinstanz",
 | 
				
			||||||
        "clone_playlist": "Playlist duplizieren",
 | 
					        "clone_playlist": "Playlist duplizieren",
 | 
				
			||||||
        "clone_playlist_success": "Erfolgreich dupliziert!",
 | 
					        "clone_playlist_success": "Erfolgreich dupliziert!",
 | 
				
			||||||
        "piped_link": "Piped-Link",
 | 
					        "piped_link": "Piped-Link",
 | 
				
			||||||
| 
						 | 
					@ -119,10 +119,18 @@
 | 
				
			||||||
        "playlist_name": "Name der Playlist",
 | 
					        "playlist_name": "Name der Playlist",
 | 
				
			||||||
        "playlist_description": "Beschreibung der Playlist",
 | 
					        "playlist_description": "Beschreibung der Playlist",
 | 
				
			||||||
        "show_search_suggestions": "Suchvorschläge anzeigen",
 | 
					        "show_search_suggestions": "Suchvorschläge anzeigen",
 | 
				
			||||||
        "chapters_layout_mobile": "Kapitel-Layout auf Mobilgeräten"
 | 
					        "chapters_layout_mobile": "Kapitel-Layout auf Mobilgeräten",
 | 
				
			||||||
 | 
					        "delete_automatically": "Automatisch löschen nach",
 | 
				
			||||||
 | 
					        "enable_dearrow": "DeArrow verwenden",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR-Code generieren",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Aus JSON/CSV importieren",
 | 
				
			||||||
 | 
					        "download_frame": "Einzelbild (Frame) downloaden",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Datenschutzerklärung",
 | 
				
			||||||
 | 
					        "add_to_group": "Zu Gruppe hinzufügen"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Auf {0} ansehen"
 | 
					        "watch_on": "Auf {0} ansehen",
 | 
				
			||||||
 | 
					        "failed": "Fehlgeschlagen mit Fehlercode {0}, siehe Protokolle für weitere Informationen"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "history": "Verlauf",
 | 
					        "history": "Verlauf",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +147,8 @@
 | 
				
			||||||
        "livestreams": "Livestreams",
 | 
					        "livestreams": "Livestreams",
 | 
				
			||||||
        "channels": "Kanäle",
 | 
					        "channels": "Kanäle",
 | 
				
			||||||
        "bookmarks": "Lesezeichen",
 | 
					        "bookmarks": "Lesezeichen",
 | 
				
			||||||
        "channel_groups": "Kanalgruppen"
 | 
					        "channel_groups": "Kanalgruppen",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "sponsor_segments": "Sponsoren-Abschnitte",
 | 
					        "sponsor_segments": "Sponsoren-Abschnitte",
 | 
				
			||||||
| 
						 | 
					@ -153,7 +162,9 @@
 | 
				
			||||||
        "all": "Alle",
 | 
					        "all": "Alle",
 | 
				
			||||||
        "category": "Kategorie",
 | 
					        "category": "Kategorie",
 | 
				
			||||||
        "chapters_horizontal": "Horizontal",
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
        "chapters_vertical": "Vertikal"
 | 
					        "chapters_vertical": "Vertikal",
 | 
				
			||||||
 | 
					        "license": "Lizenz",
 | 
				
			||||||
 | 
					        "visibility": "Sichtbarkeit"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "SSL-Bewertung",
 | 
					        "ssl_score": "SSL-Bewertung",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +183,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "Passwort",
 | 
					        "password": "Passwort",
 | 
				
			||||||
        "username": "Benutzername"
 | 
					        "username": "Benutzername",
 | 
				
			||||||
 | 
					        "password_confirm": "Passwort bestätigen",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Passwörter stimmen nicht überein!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Hast du gemeint: {0}?",
 | 
					        "did_you_mean": "Hast du gemeint: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,18 +196,25 @@
 | 
				
			||||||
        "music_songs": "YT Music: Lieder",
 | 
					        "music_songs": "YT Music: Lieder",
 | 
				
			||||||
        "music_videos": "YT Music: Videos",
 | 
					        "music_videos": "YT Music: Videos",
 | 
				
			||||||
        "music_albums": "YT Music: Alben",
 | 
					        "music_albums": "YT Music: Alben",
 | 
				
			||||||
        "music_playlists": "YT Music: Playlists"
 | 
					        "music_playlists": "YT Music: Playlists",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Künstler:innen"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Anzahl Abos: {0}"
 | 
					        "subscribed_channels_count": "Anzahl Abos: {0}"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "preferences_note": "Achtung: Die Einstellung werden lokal in deinem Browser gespeichert. Wenn du deine Browserdaten löschst, werden auch deine Einstellungen zurückgesetzt.",
 | 
					        "preferences_note": "Achtung: Die Einstellungen werden lokal in deinem Browser gespeichert. Wenn du deine Browserdaten löschst, werden auch deine Einstellungen zurückgesetzt.",
 | 
				
			||||||
        "page_not_found": "Seite nicht gefunden",
 | 
					        "page_not_found": "Seite nicht gefunden",
 | 
				
			||||||
        "copied": "Kopiert!",
 | 
					        "copied": "Kopiert!",
 | 
				
			||||||
        "cannot_copy": "Kopieren nicht möglich!",
 | 
					        "cannot_copy": "Kopieren nicht möglich!",
 | 
				
			||||||
        "local_storage": "Diese Aktion erfordert „localStorage“, sind Cookies aktiviert?",
 | 
					        "local_storage": "Diese Aktion erfordert „localStorage“, sind Cookies aktiviert?",
 | 
				
			||||||
        "register_no_email_note": "Es wird nicht empfohlen, eine E-Mail als Benutzernamen zu verwenden. Trotzdem fortfahren?",
 | 
					        "register_no_email_note": "Es wird nicht empfohlen, eine E-Mail als Benutzernamen zu verwenden. Trotzdem fortfahren?",
 | 
				
			||||||
        "next_video_countdown": "Nächstes Video startet in {0}s"
 | 
					        "next_video_countdown": "Nächstes Video startet in {0}s",
 | 
				
			||||||
 | 
					        "weeks": "{amount} Woche(n)",
 | 
				
			||||||
 | 
					        "months": "{amount} Monat(en)",
 | 
				
			||||||
 | 
					        "hours": "{amount} Stunde(n)",
 | 
				
			||||||
 | 
					        "days": "{amount} Tag(e)",
 | 
				
			||||||
 | 
					        "login_note": "Melde dich mit einem Konto an, das du auf dieser Instanz erstellt hast.",
 | 
				
			||||||
 | 
					        "register_note": "Erstelle ein Konto für diese Piped-Instanz. Dadurch kannst du deine Abos und Playlists mit deinem Konto synchronisieren, sie werden also serverseitig gespeichert. Du kannst all diese Funktionen auch ohne Konto nutzen, allerdings werden dann alle Daten nur im lokalen Speicher deines Browsers gespeichert. Bitte stelle sicher, dass du KEINE E-Mail-Adresse als Benutzernamen verwendest und ein sicheres Passwort wählst, welches du nicht bereits woanders nutzt."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "Livestreams",
 | 
					        "livestreams": "Livestreams",
 | 
				
			||||||
        "channels": "Channels",
 | 
					        "channels": "Channels",
 | 
				
			||||||
        "bookmarks": "Bookmarks",
 | 
					        "bookmarks": "Bookmarks",
 | 
				
			||||||
        "channel_groups": "Channel groups"
 | 
					        "channel_groups": "Channel groups",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Watch on {0}"
 | 
					        "watch_on": "View on {0}",
 | 
				
			||||||
 | 
					        "failed": "Failed with error code {0}, see logs for more info"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "Subscribe - {count}",
 | 
					        "subscribe": "Subscribe - {count}",
 | 
				
			||||||
| 
						 | 
					@ -45,6 +47,7 @@
 | 
				
			||||||
        "show_markers": "Show Markers on Player",
 | 
					        "show_markers": "Show Markers on Player",
 | 
				
			||||||
        "min_segment_length": "Minimum Segment Length (in seconds)",
 | 
					        "min_segment_length": "Minimum Segment Length (in seconds)",
 | 
				
			||||||
        "skip_segment": "Skip Segment",
 | 
					        "skip_segment": "Skip Segment",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Enable DeArrow",
 | 
				
			||||||
        "theme": "Theme",
 | 
					        "theme": "Theme",
 | 
				
			||||||
        "auto": "Auto",
 | 
					        "auto": "Auto",
 | 
				
			||||||
        "dark": "Dark",
 | 
					        "dark": "Dark",
 | 
				
			||||||
| 
						 | 
					@ -54,20 +57,21 @@
 | 
				
			||||||
        "audio_only": "Audio Only",
 | 
					        "audio_only": "Audio Only",
 | 
				
			||||||
        "default_quality": "Default Quality",
 | 
					        "default_quality": "Default Quality",
 | 
				
			||||||
        "buffering_goal": "Buffering Goal (in seconds)",
 | 
					        "buffering_goal": "Buffering Goal (in seconds)",
 | 
				
			||||||
        "country_selection": "Country Selection",
 | 
					        "country_selection": "Country",
 | 
				
			||||||
        "default_homepage": "Default Homepage",
 | 
					        "default_homepage": "Default Homepage",
 | 
				
			||||||
        "minimize_comments_default": "Minimize Comments by default",
 | 
					        "minimize_comments_default": "Minimize Comments by default",
 | 
				
			||||||
        "minimize_description_default": "Minimize Description by default",
 | 
					        "minimize_description_default": "Minimize Description by default",
 | 
				
			||||||
        "store_watch_history": "Store Watch History",
 | 
					        "store_watch_history": "Store Watch History",
 | 
				
			||||||
        "language_selection": "Language Selection",
 | 
					        "language_selection": "Language",
 | 
				
			||||||
        "instances_list": "Instances List",
 | 
					        "instances_list": "Instances List",
 | 
				
			||||||
        "enabled_codecs": "Enabled Codecs (Multiple)",
 | 
					        "enabled_codecs": "Enabled Codecs (Multiple)",
 | 
				
			||||||
        "instance_selection": "Instance Selection",
 | 
					        "instance_selection": "Instance",
 | 
				
			||||||
        "show_more": "Show More",
 | 
					        "show_more": "Show More",
 | 
				
			||||||
        "yes": "Yes",
 | 
					        "yes": "Yes",
 | 
				
			||||||
        "no": "No",
 | 
					        "no": "No",
 | 
				
			||||||
        "export_to_json": "Export to JSON",
 | 
					        "export_to_json": "Export to JSON",
 | 
				
			||||||
        "import_from_json": "Import from JSON/CSV",
 | 
					        "import_from_json": "Import from JSON",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Import from JSON/CSV",
 | 
				
			||||||
        "loop_this_video": "Loop this Video",
 | 
					        "loop_this_video": "Loop this Video",
 | 
				
			||||||
        "auto_play_next_video": "Auto Play next Video",
 | 
					        "auto_play_next_video": "Auto Play next Video",
 | 
				
			||||||
        "auto_display_captions": "Auto Display Captions",
 | 
					        "auto_display_captions": "Auto Display Captions",
 | 
				
			||||||
| 
						 | 
					@ -103,7 +107,7 @@
 | 
				
			||||||
        "show_watch_on_youtube": "Show Watch on YouTube button",
 | 
					        "show_watch_on_youtube": "Show Watch on YouTube button",
 | 
				
			||||||
        "invalidate_session": "Logout all devices",
 | 
					        "invalidate_session": "Logout all devices",
 | 
				
			||||||
        "different_auth_instance": "Use a different instance for authentication",
 | 
					        "different_auth_instance": "Use a different instance for authentication",
 | 
				
			||||||
        "instance_auth_selection": "Autentication Instance Selection",
 | 
					        "instance_auth_selection": "Authentication Instance",
 | 
				
			||||||
        "clone_playlist": "Clone Playlist",
 | 
					        "clone_playlist": "Clone Playlist",
 | 
				
			||||||
        "clone_playlist_success": "Successfully cloned!",
 | 
					        "clone_playlist_success": "Successfully cloned!",
 | 
				
			||||||
        "download_as_txt": "Download as .txt",
 | 
					        "download_as_txt": "Download as .txt",
 | 
				
			||||||
| 
						 | 
					@ -122,12 +126,13 @@
 | 
				
			||||||
        "copy_link": "Copy link",
 | 
					        "copy_link": "Copy link",
 | 
				
			||||||
        "time_code": "Time code (in seconds)",
 | 
					        "time_code": "Time code (in seconds)",
 | 
				
			||||||
        "show_chapters": "Chapters",
 | 
					        "show_chapters": "Chapters",
 | 
				
			||||||
        "store_search_history": "Store Search history",
 | 
					        "store_search_history": "Store Search History",
 | 
				
			||||||
        "hide_watched": "Hide watched videos in the feed",
 | 
					        "hide_watched": "Hide watched videos in the feed",
 | 
				
			||||||
        "documentation": "Documentation",
 | 
					        "documentation": "Documentation",
 | 
				
			||||||
        "status_page": "Status",
 | 
					        "status_page": "Status",
 | 
				
			||||||
        "source_code": "Source code",
 | 
					        "source_code": "Source code",
 | 
				
			||||||
        "instance_donations": "Instance donations",
 | 
					        "instance_donations": "Instance donations",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Privacy Policy",
 | 
				
			||||||
        "reply_count": "{count} replies",
 | 
					        "reply_count": "{count} replies",
 | 
				
			||||||
        "no_valid_playlists": "The file doesn't contain valid playlists!",
 | 
					        "no_valid_playlists": "The file doesn't contain valid playlists!",
 | 
				
			||||||
        "with_playlist": "Share with playlist",
 | 
					        "with_playlist": "Share with playlist",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +144,11 @@
 | 
				
			||||||
        "group_name": "Group name",
 | 
					        "group_name": "Group name",
 | 
				
			||||||
        "cancel": "Cancel",
 | 
					        "cancel": "Cancel",
 | 
				
			||||||
        "okay": "Okay",
 | 
					        "okay": "Okay",
 | 
				
			||||||
        "show_search_suggestions": "Show search suggestions"
 | 
					        "show_search_suggestions": "Show search suggestions",
 | 
				
			||||||
 | 
					        "delete_automatically": "Delete automatically after",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Generate QR Code",
 | 
				
			||||||
 | 
					        "download_frame": "Download frame",
 | 
				
			||||||
 | 
					        "add_to_group": "Add to group"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Pinned by {author}",
 | 
					        "pinned_by": "Pinned by {author}",
 | 
				
			||||||
| 
						 | 
					@ -158,7 +167,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Username",
 | 
					        "username": "Username",
 | 
				
			||||||
        "password": "Password"
 | 
					        "password": "Password",
 | 
				
			||||||
 | 
					        "password_confirm": "Confirm password",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Passwords don't match!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Videos",
 | 
					        "videos": "Videos",
 | 
				
			||||||
| 
						 | 
					@ -171,6 +182,8 @@
 | 
				
			||||||
        "shorts": "Shorts",
 | 
					        "shorts": "Shorts",
 | 
				
			||||||
        "all": "All",
 | 
					        "all": "All",
 | 
				
			||||||
        "category": "Category",
 | 
					        "category": "Category",
 | 
				
			||||||
 | 
					        "license": "License",
 | 
				
			||||||
 | 
					        "visibility": "Visibility",
 | 
				
			||||||
        "chapters_horizontal": "Horizontal",
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
        "chapters_vertical": "Vertical"
 | 
					        "chapters_vertical": "Vertical"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					@ -183,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Songs",
 | 
					        "music_songs": "YT Music: Songs",
 | 
				
			||||||
        "music_videos": "YT Music: Videos",
 | 
					        "music_videos": "YT Music: Videos",
 | 
				
			||||||
        "music_albums": "YT Music: Albums",
 | 
					        "music_albums": "YT Music: Albums",
 | 
				
			||||||
        "music_playlists": "YT Music: Playlists"
 | 
					        "music_playlists": "YT Music: Playlists",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artists"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Subscribed to: {0}"
 | 
					        "subscribed_channels_count": "Subscribed to: {0}"
 | 
				
			||||||
| 
						 | 
					@ -195,6 +209,12 @@
 | 
				
			||||||
        "cannot_copy": "Can't copy!",
 | 
					        "cannot_copy": "Can't copy!",
 | 
				
			||||||
        "local_storage": "This action requires localStorage, are cookies enabled?",
 | 
					        "local_storage": "This action requires localStorage, are cookies enabled?",
 | 
				
			||||||
        "register_no_email_note": "Using an e-mail as username is not recommended. Proceed anyways?",
 | 
					        "register_no_email_note": "Using an e-mail as username is not recommended. Proceed anyways?",
 | 
				
			||||||
        "next_video_countdown": "Playing next video in {0}s"
 | 
					        "next_video_countdown": "Playing next video in {0}s",
 | 
				
			||||||
 | 
					        "hours": "{amount} hour(s)",
 | 
				
			||||||
 | 
					        "days": "{amount} day(s)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} week(s)",
 | 
				
			||||||
 | 
					        "months": "{amount} month(s)",
 | 
				
			||||||
 | 
					        "register_note": "Register an account for this Piped instance. This will allow you to sync your subscriptions and playlists with your account, so they're stored on the server side. You can use all features without an account, but all data will be stored in your browser's local cache. Please make sure you do NOT use an email address as your username and choose a secure password that you do not use elsewhere.",
 | 
				
			||||||
 | 
					        "login_note": "Log in with an account created on this instance."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,14 +10,16 @@
 | 
				
			||||||
        "playlists": "Ludlistoj",
 | 
					        "playlists": "Ludlistoj",
 | 
				
			||||||
        "account": "Konto",
 | 
					        "account": "Konto",
 | 
				
			||||||
        "player": "Ludilo",
 | 
					        "player": "Ludilo",
 | 
				
			||||||
        "instance": "Nodo",
 | 
					        "instance": "Retejo",
 | 
				
			||||||
        "channels": "Kanaloj",
 | 
					        "channels": "Kanaloj",
 | 
				
			||||||
        "livestreams": "Tujelsendoj",
 | 
					        "livestreams": "Tujelsendoj",
 | 
				
			||||||
        "bookmarks": "Legosignoj",
 | 
					        "bookmarks": "Legosignoj",
 | 
				
			||||||
        "channel_groups": "Kanalaroj"
 | 
					        "channel_groups": "Kanalaroj",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Vidi en {0}"
 | 
					        "watch_on": "Vidi en {0}",
 | 
				
			||||||
 | 
					        "failed": "Fiaskis kun erarkodo {0}, vidu protokolojn por pli da informo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "Aboni - {count}",
 | 
					        "subscribe": "Aboni - {count}",
 | 
				
			||||||
| 
						 | 
					@ -41,10 +43,10 @@
 | 
				
			||||||
        "autoplay_video": "Aŭtomate Ludi Videon",
 | 
					        "autoplay_video": "Aŭtomate Ludi Videon",
 | 
				
			||||||
        "audio_only": "Nur Sono",
 | 
					        "audio_only": "Nur Sono",
 | 
				
			||||||
        "default_quality": "Implicita Kvalito",
 | 
					        "default_quality": "Implicita Kvalito",
 | 
				
			||||||
        "country_selection": "Landa Elekto",
 | 
					        "country_selection": "Lando",
 | 
				
			||||||
        "default_homepage": "Implicita Ĉefpaĝo",
 | 
					        "default_homepage": "Implicita Ĉefpaĝo",
 | 
				
			||||||
        "show_comments": "Montri Komentojn",
 | 
					        "show_comments": "Montri Komentojn",
 | 
				
			||||||
        "language_selection": "Lingva Elekto",
 | 
					        "language_selection": "Lingvo",
 | 
				
			||||||
        "donations": "Donacoj por programado",
 | 
					        "donations": "Donacoj por programado",
 | 
				
			||||||
        "show_more": "Montri pli",
 | 
					        "show_more": "Montri pli",
 | 
				
			||||||
        "yes": "Jes",
 | 
					        "yes": "Jes",
 | 
				
			||||||
| 
						 | 
					@ -77,13 +79,13 @@
 | 
				
			||||||
        "export_to_json": "Elporti JSON-n",
 | 
					        "export_to_json": "Elporti JSON-n",
 | 
				
			||||||
        "loop_this_video": "Ripetadi ĉi tiun Videon",
 | 
					        "loop_this_video": "Ripetadi ĉi tiun Videon",
 | 
				
			||||||
        "enable_lbry_proxy": "Ebligi Prokurilon por LBRY",
 | 
					        "enable_lbry_proxy": "Ebligi Prokurilon por LBRY",
 | 
				
			||||||
        "import_from_json": "Importi el JSON/CSV",
 | 
					        "import_from_json": "Importi el JSON",
 | 
				
			||||||
        "show_description": "Montri Priskribon",
 | 
					        "show_description": "Montri Priskribon",
 | 
				
			||||||
        "instances_list": "Listo de Nodoj",
 | 
					        "instances_list": "Listo de Piped-retejoj",
 | 
				
			||||||
        "auto_play_next_video": "Aŭtomate Ludi sekvan Videon",
 | 
					        "auto_play_next_video": "Aŭtomate Ludi sekvan Videon",
 | 
				
			||||||
        "show_recommendations": "Montri Rekomendojn",
 | 
					        "show_recommendations": "Montri Rekomendojn",
 | 
				
			||||||
        "reset_preferences": "Restarigi agordojn",
 | 
					        "reset_preferences": "Restarigi agordojn",
 | 
				
			||||||
        "instance_selection": "Noda Elekto",
 | 
					        "instance_selection": "Retejo",
 | 
				
			||||||
        "view_ssl_score": "Vidu SSL-Poentaron",
 | 
					        "view_ssl_score": "Vidu SSL-Poentaron",
 | 
				
			||||||
        "backup_preferences": "Savkopii agordojn",
 | 
					        "backup_preferences": "Savkopii agordojn",
 | 
				
			||||||
        "disable_lbry": "Malebligi LBRY-n por Elsendfluo",
 | 
					        "disable_lbry": "Malebligi LBRY-n por Elsendfluo",
 | 
				
			||||||
| 
						 | 
					@ -91,11 +93,11 @@
 | 
				
			||||||
        "store_search_history": "Konservi Ŝerĉhistorion",
 | 
					        "store_search_history": "Konservi Ŝerĉhistorion",
 | 
				
			||||||
        "hide_watched": "Kaŝi viditajn videojn en la fluo",
 | 
					        "hide_watched": "Kaŝi viditajn videojn en la fluo",
 | 
				
			||||||
        "minimize_recommendations": "Plejetigi Rekomendojn",
 | 
					        "minimize_recommendations": "Plejetigi Rekomendojn",
 | 
				
			||||||
        "instance_auth_selection": "Elekto de Aŭtentokontrola Nodo",
 | 
					        "instance_auth_selection": "Aŭtentokontrola Piped-retejo",
 | 
				
			||||||
        "restore_preferences": "Restarigi agordojn",
 | 
					        "restore_preferences": "Restarigi agordojn",
 | 
				
			||||||
        "status_page": "Stato",
 | 
					        "status_page": "Stato",
 | 
				
			||||||
        "please_select_playlist": "Bonvolu elekti ludliston",
 | 
					        "please_select_playlist": "Bonvolu elekti ludliston",
 | 
				
			||||||
        "different_auth_instance": "Uzi alian nodon por aŭtentokontrolo",
 | 
					        "different_auth_instance": "Uzi alian Piped-retejon por aŭtentokontrolo",
 | 
				
			||||||
        "back_to_home": "Ree hejmen",
 | 
					        "back_to_home": "Ree hejmen",
 | 
				
			||||||
        "time_code": "Tempkodo (en sekundoj)",
 | 
					        "time_code": "Tempkodo (en sekundoj)",
 | 
				
			||||||
        "skip_non_music": "Preterpasi Muzikon: Nemuzika Sekcio",
 | 
					        "skip_non_music": "Preterpasi Muzikon: Nemuzika Sekcio",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +141,14 @@
 | 
				
			||||||
        "playlist_description": "Priskribo de la ludlisto",
 | 
					        "playlist_description": "Priskribo de la ludlisto",
 | 
				
			||||||
        "cancel": "Nuligi",
 | 
					        "cancel": "Nuligi",
 | 
				
			||||||
        "show_search_suggestions": "Montri serĉ-sugestojn",
 | 
					        "show_search_suggestions": "Montri serĉ-sugestojn",
 | 
				
			||||||
        "chapters_layout_mobile": "Aranĝo de ĉapitroj en poŝtelefono"
 | 
					        "chapters_layout_mobile": "Aranĝo de ĉapitroj en poŝtelefono",
 | 
				
			||||||
 | 
					        "delete_automatically": "Aŭtomate forigi post",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Ebligi DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Generi QR-kodon",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importi el JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Elŝuti bildon",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Privateca politiko",
 | 
				
			||||||
 | 
					        "add_to_group": "Aldoni al grupo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "chapters": "Sekcioj",
 | 
					        "chapters": "Sekcioj",
 | 
				
			||||||
| 
						 | 
					@ -147,13 +156,15 @@
 | 
				
			||||||
        "live": "{0} Realtempe",
 | 
					        "live": "{0} Realtempe",
 | 
				
			||||||
        "views": "{views} spektoj",
 | 
					        "views": "{views} spektoj",
 | 
				
			||||||
        "sponsor_segments": "Sponsoraj Segmentoj",
 | 
					        "sponsor_segments": "Sponsoraj Segmentoj",
 | 
				
			||||||
        "watched": "Viditaj",
 | 
					        "watched": "Spektita",
 | 
				
			||||||
        "ratings_disabled": "Taksadoj Malebligitaj",
 | 
					        "ratings_disabled": "Taksadoj Malebligitaj",
 | 
				
			||||||
        "shorts": "Mallongaj",
 | 
					        "shorts": "Mallongaj",
 | 
				
			||||||
        "all": "Ĉiuj",
 | 
					        "all": "Ĉiuj",
 | 
				
			||||||
        "category": "Kategorio",
 | 
					        "category": "Kategorio",
 | 
				
			||||||
        "chapters_horizontal": "Horizontala",
 | 
					        "chapters_horizontal": "Horizontala",
 | 
				
			||||||
        "chapters_vertical": "Vertikala"
 | 
					        "chapters_vertical": "Vertikala",
 | 
				
			||||||
 | 
					        "license": "Permesilo",
 | 
				
			||||||
 | 
					        "visibility": "Videbleco"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "music_albums": "YT Music: Albumoj",
 | 
					        "music_albums": "YT Music: Albumoj",
 | 
				
			||||||
| 
						 | 
					@ -164,7 +175,8 @@
 | 
				
			||||||
        "music_videos": "YT Music: Videoj",
 | 
					        "music_videos": "YT Music: Videoj",
 | 
				
			||||||
        "music_songs": "YT Music: Muzikaĵoj",
 | 
					        "music_songs": "YT Music: Muzikaĵoj",
 | 
				
			||||||
        "all": "YouTube: Ĉio",
 | 
					        "all": "YouTube: Ĉio",
 | 
				
			||||||
        "did_you_mean": "Ĉu vi volis diri «{0}»?"
 | 
					        "did_you_mean": "Ĉu vi volis diri «{0}»?",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artistoj"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "copied": "Kopiita!",
 | 
					        "copied": "Kopiita!",
 | 
				
			||||||
| 
						 | 
					@ -173,11 +185,19 @@
 | 
				
			||||||
        "page_not_found": "Paĝo ne trovita",
 | 
					        "page_not_found": "Paĝo ne trovita",
 | 
				
			||||||
        "local_storage": "Ĉi tiu ago postulas localStorage, ĉu kuketoj estas ebligitaj?",
 | 
					        "local_storage": "Ĉi tiu ago postulas localStorage, ĉu kuketoj estas ebligitaj?",
 | 
				
			||||||
        "register_no_email_note": "Uzi retadreson kiel uzantnomon ne estas rekomendita. Ĉu daŭrigi ĉiuokaze?",
 | 
					        "register_no_email_note": "Uzi retadreson kiel uzantnomon ne estas rekomendita. Ĉu daŭrigi ĉiuokaze?",
 | 
				
			||||||
        "next_video_countdown": "Oni ludos la sekvan videon post {0}s"
 | 
					        "next_video_countdown": "Oni ludos la sekvan videon post {0}s",
 | 
				
			||||||
 | 
					        "hours": "{amount} horo(j)",
 | 
				
			||||||
 | 
					        "days": "{amount} tago(j)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} semajno(j)",
 | 
				
			||||||
 | 
					        "months": "{amount} monato(j)",
 | 
				
			||||||
 | 
					        "login_note": "Ensaluti per konto kreita en ĉi tiu retejo.",
 | 
				
			||||||
 | 
					        "register_note": "Registri konton por ĉi tiu Piped-retejo. Tio ebligos al vi sinkronigi viajn abonojn kaj ludlistojn kun via konto, do ili estos konservitaj en la servilo. Vi ankaŭ povas uzi ĉiujn funkciojn sen konto, sed ĉiuj datenoj estos konservitaj en la loka kaŝmemoro de via retumilo. Bonvolu elekti sekuran pasvorton, kiun vi ne uzas aliloke, kaj NE uzi retadreson kiel uzantnomon."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Uzantnomo",
 | 
					        "username": "Uzantnomo",
 | 
				
			||||||
        "password": "Pasvorto"
 | 
					        "password": "Pasvorto",
 | 
				
			||||||
 | 
					        "password_confirm": "Konfirmu la pasvorton",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Pasvortoj ne kongruas!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "version": "Versio",
 | 
					        "version": "Versio",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,9 @@
 | 
				
			||||||
        "all": "Todos",
 | 
					        "all": "Todos",
 | 
				
			||||||
        "category": "Categoría",
 | 
					        "category": "Categoría",
 | 
				
			||||||
        "chapters_horizontal": "Horizontal",
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
        "chapters_vertical": "Vertical"
 | 
					        "chapters_vertical": "Vertical",
 | 
				
			||||||
 | 
					        "license": "Licencia",
 | 
				
			||||||
 | 
					        "visibility": "Visibilidad"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "Puntuación SSL",
 | 
					        "ssl_score": "Puntuación SSL",
 | 
				
			||||||
| 
						 | 
					@ -36,20 +38,20 @@
 | 
				
			||||||
        "donations": "Donaciones para desarrollo",
 | 
					        "donations": "Donaciones para desarrollo",
 | 
				
			||||||
        "auto_play_next_video": "Reproducción automática del siguiente vídeo",
 | 
					        "auto_play_next_video": "Reproducción automática del siguiente vídeo",
 | 
				
			||||||
        "loop_this_video": "Poner en bucle este vídeo",
 | 
					        "loop_this_video": "Poner en bucle este vídeo",
 | 
				
			||||||
        "import_from_json": "Importar desde JSON/CSV",
 | 
					        "import_from_json": "Importar desde JSON",
 | 
				
			||||||
        "export_to_json": "Exportar a JSON",
 | 
					        "export_to_json": "Exportar a JSON",
 | 
				
			||||||
        "no": "No",
 | 
					        "no": "No",
 | 
				
			||||||
        "yes": "Sí",
 | 
					        "yes": "Sí",
 | 
				
			||||||
        "show_more": "Mostrar más",
 | 
					        "show_more": "Mostrar más",
 | 
				
			||||||
        "instance_selection": "Selección de instancias",
 | 
					        "instance_selection": "Instancia",
 | 
				
			||||||
        "enabled_codecs": "Códecs habilitados (múltiples)",
 | 
					        "enabled_codecs": "Códecs habilitados (múltiples)",
 | 
				
			||||||
        "instances_list": "Lista de instancias",
 | 
					        "instances_list": "Lista de instancias",
 | 
				
			||||||
        "language_selection": "Selección de idioma",
 | 
					        "language_selection": "Idioma",
 | 
				
			||||||
        "store_watch_history": "Recordar historial de visualización",
 | 
					        "store_watch_history": "Recordar historial de visualización",
 | 
				
			||||||
        "minimize_description_default": "Minimizar la descripción por defecto",
 | 
					        "minimize_description_default": "Minimizar la descripción por defecto",
 | 
				
			||||||
        "show_comments": "Mostrar comentarios",
 | 
					        "show_comments": "Mostrar comentarios",
 | 
				
			||||||
        "default_homepage": "Página de inicio predeterminada",
 | 
					        "default_homepage": "Página de inicio predeterminada",
 | 
				
			||||||
        "country_selection": "Selección de países",
 | 
					        "country_selection": "País",
 | 
				
			||||||
        "buffering_goal": "Objetivo de amortiguación (en segundos)",
 | 
					        "buffering_goal": "Objetivo de amortiguación (en segundos)",
 | 
				
			||||||
        "default_quality": "Calidad predeterminada",
 | 
					        "default_quality": "Calidad predeterminada",
 | 
				
			||||||
        "audio_only": "Sólo audio",
 | 
					        "audio_only": "Sólo audio",
 | 
				
			||||||
| 
						 | 
					@ -93,8 +95,8 @@
 | 
				
			||||||
        "create_playlist": "Crear una lista de reproducción",
 | 
					        "create_playlist": "Crear una lista de reproducción",
 | 
				
			||||||
        "add_to_playlist": "Añadir a la lista de reproducción",
 | 
					        "add_to_playlist": "Añadir a la lista de reproducción",
 | 
				
			||||||
        "delete_playlist_video_confirm": "¿Eliminar vídeo de lista de reproducción?",
 | 
					        "delete_playlist_video_confirm": "¿Eliminar vídeo de lista de reproducción?",
 | 
				
			||||||
        "please_select_playlist": "Seleccione una lista de reproducción",
 | 
					        "please_select_playlist": "Por favor, selecciona una lista de reproducción",
 | 
				
			||||||
        "select_playlist": "Seleccione una lista de reproducción",
 | 
					        "select_playlist": "Selecciona una lista de reproducción",
 | 
				
			||||||
        "show_markers": "Mostrar Marcadores en Reproductor",
 | 
					        "show_markers": "Mostrar Marcadores en Reproductor",
 | 
				
			||||||
        "delete_account": "Eliminar Cuenta",
 | 
					        "delete_account": "Eliminar Cuenta",
 | 
				
			||||||
        "different_auth_instance": "Usar una instancia diferente para autenticación",
 | 
					        "different_auth_instance": "Usar una instancia diferente para autenticación",
 | 
				
			||||||
| 
						 | 
					@ -103,7 +105,7 @@
 | 
				
			||||||
        "logout": "Cerrar sesión en este dispositivo",
 | 
					        "logout": "Cerrar sesión en este dispositivo",
 | 
				
			||||||
        "minimize_recommendations_default": "Minimizar Recomendaciones por defecto",
 | 
					        "minimize_recommendations_default": "Minimizar Recomendaciones por defecto",
 | 
				
			||||||
        "invalidate_session": "Cerrar sesión en todos los dispositivos",
 | 
					        "invalidate_session": "Cerrar sesión en todos los dispositivos",
 | 
				
			||||||
        "instance_auth_selection": "Selección de la Instancia de Autentificación",
 | 
					        "instance_auth_selection": "Instancia de autenticación",
 | 
				
			||||||
        "download_as_txt": "Descargar como .txt",
 | 
					        "download_as_txt": "Descargar como .txt",
 | 
				
			||||||
        "share": "Compartir",
 | 
					        "share": "Compartir",
 | 
				
			||||||
        "with_timecode": "Compartir con código de tiempo",
 | 
					        "with_timecode": "Compartir con código de tiempo",
 | 
				
			||||||
| 
						 | 
					@ -117,7 +119,7 @@
 | 
				
			||||||
        "restore_preferences": "Restablecer las preferencias",
 | 
					        "restore_preferences": "Restablecer las preferencias",
 | 
				
			||||||
        "back_to_home": "Volver a la página de inicio",
 | 
					        "back_to_home": "Volver a la página de inicio",
 | 
				
			||||||
        "show_chapters": "Capítulos",
 | 
					        "show_chapters": "Capítulos",
 | 
				
			||||||
        "store_search_history": "Guardar historial de búsqueda",
 | 
					        "store_search_history": "Guardar el historial de las búsquedas",
 | 
				
			||||||
        "source_code": "Código fuente",
 | 
					        "source_code": "Código fuente",
 | 
				
			||||||
        "documentation": "Documentación",
 | 
					        "documentation": "Documentación",
 | 
				
			||||||
        "instance_donations": "Donaciones para instancia",
 | 
					        "instance_donations": "Donaciones para instancia",
 | 
				
			||||||
| 
						 | 
					@ -138,7 +140,7 @@
 | 
				
			||||||
        "skip_segment": "Saltar Segmento",
 | 
					        "skip_segment": "Saltar Segmento",
 | 
				
			||||||
        "show_less": "Mostrar menos",
 | 
					        "show_less": "Mostrar menos",
 | 
				
			||||||
        "autoplay_next_countdown": "Cuenta atrás predeterminada antes del siguiente vídeo (en segundos)",
 | 
					        "autoplay_next_countdown": "Cuenta atrás predeterminada antes del siguiente vídeo (en segundos)",
 | 
				
			||||||
        "dismiss": "Cancelar",
 | 
					        "dismiss": "Descartar",
 | 
				
			||||||
        "group_name": "Nombre del grupo",
 | 
					        "group_name": "Nombre del grupo",
 | 
				
			||||||
        "create_group": "Crear grupo",
 | 
					        "create_group": "Crear grupo",
 | 
				
			||||||
        "auto_display_captions": "Mostrar automáticamente subtítulos",
 | 
					        "auto_display_captions": "Mostrar automáticamente subtítulos",
 | 
				
			||||||
| 
						 | 
					@ -148,10 +150,17 @@
 | 
				
			||||||
        "playlist_description": "Descripción de la lista de reproducción",
 | 
					        "playlist_description": "Descripción de la lista de reproducción",
 | 
				
			||||||
        "cancel": "Cancelar",
 | 
					        "cancel": "Cancelar",
 | 
				
			||||||
        "show_search_suggestions": "Mostrar sugerencias de búsqueda",
 | 
					        "show_search_suggestions": "Mostrar sugerencias de búsqueda",
 | 
				
			||||||
        "chapters_layout_mobile": "Disposición de capítulos en móvil"
 | 
					        "chapters_layout_mobile": "Disposición de capítulos en móvil",
 | 
				
			||||||
 | 
					        "delete_automatically": "Borrar automáticamente después de",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Activar DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Generar código QR",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importar desde JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Descargar fotograma",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Política de privacidad",
 | 
				
			||||||
 | 
					        "add_to_group": "Añadir a grupo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "feed": "Fuente web",
 | 
					        "feed": "Contenido",
 | 
				
			||||||
        "subscriptions": "Suscripciones",
 | 
					        "subscriptions": "Suscripciones",
 | 
				
			||||||
        "history": "Historial",
 | 
					        "history": "Historial",
 | 
				
			||||||
        "trending": "En tendencias",
 | 
					        "trending": "En tendencias",
 | 
				
			||||||
| 
						 | 
					@ -165,14 +174,18 @@
 | 
				
			||||||
        "livestreams": "Directos",
 | 
					        "livestreams": "Directos",
 | 
				
			||||||
        "channels": "Canales",
 | 
					        "channels": "Canales",
 | 
				
			||||||
        "bookmarks": "Marcadores",
 | 
					        "bookmarks": "Marcadores",
 | 
				
			||||||
        "channel_groups": "Grupos de canales"
 | 
					        "channel_groups": "Grupos de canales",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Ver en {0}"
 | 
					        "watch_on": "Ver en {0}",
 | 
				
			||||||
 | 
					        "failed": "Falló con el código de error {0}, consulta los registros para más información"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "Contraseña",
 | 
					        "password": "Contraseña",
 | 
				
			||||||
        "username": "Nombre de usuario"
 | 
					        "username": "Nombre de usuario",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "¡Las contraseñas no coinciden!",
 | 
				
			||||||
 | 
					        "password_confirm": "Confirma la contraseña"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "¿Quisiste decir {0}?",
 | 
					        "did_you_mean": "¿Quisiste decir {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +196,8 @@
 | 
				
			||||||
        "videos": "YouTube: Vídeos",
 | 
					        "videos": "YouTube: Vídeos",
 | 
				
			||||||
        "channels": "YouTube: Canales",
 | 
					        "channels": "YouTube: Canales",
 | 
				
			||||||
        "playlists": "YouTube: Listas de reproducción",
 | 
					        "playlists": "YouTube: Listas de reproducción",
 | 
				
			||||||
        "music_albums": "YT Music: Álbumes"
 | 
					        "music_albums": "YT Music: Álbumes",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artistas"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Suscrito a: {0}"
 | 
					        "subscribed_channels_count": "Suscrito a: {0}"
 | 
				
			||||||
| 
						 | 
					@ -195,6 +209,12 @@
 | 
				
			||||||
        "cannot_copy": "¡No se puede copiar!",
 | 
					        "cannot_copy": "¡No se puede copiar!",
 | 
				
			||||||
        "local_storage": "Esta acción requiere «localStorage», ¿están activadas las «cookies»?",
 | 
					        "local_storage": "Esta acción requiere «localStorage», ¿están activadas las «cookies»?",
 | 
				
			||||||
        "register_no_email_note": "No se recomienda usar un correo electrónico como nombre de usuario. ¿Continuar de todos modos?",
 | 
					        "register_no_email_note": "No se recomienda usar un correo electrónico como nombre de usuario. ¿Continuar de todos modos?",
 | 
				
			||||||
        "next_video_countdown": "El próximo vídeo se reproducirá en {0}s"
 | 
					        "next_video_countdown": "El próximo vídeo se reproducirá en {0}s",
 | 
				
			||||||
 | 
					        "hours": "{amount} hora(s)",
 | 
				
			||||||
 | 
					        "days": "{amount} día(s)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} semana(s)",
 | 
				
			||||||
 | 
					        "months": "{amount} mes(es)",
 | 
				
			||||||
 | 
					        "login_note": "Inicia sesión con una cuenta creada en esta instancia.",
 | 
				
			||||||
 | 
					        "register_note": "Registra una cuenta para esta instancia de Piped. Esto te permitirá sincronizar tus suscripciones y las listas de reproducción con tu cuenta, para que se almacenen en el servidor. Puedes utilizar todas las funciones sin una cuenta, pero todos los datos se almacenarán en la caché local de tu navegador. Asegúrate de NO utilizar una dirección de correo electrónico como nombre de usuario y elige una contraseña segura que no utilices en ningún otro sitio."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,13 +128,15 @@
 | 
				
			||||||
        "okay": "OK",
 | 
					        "okay": "OK",
 | 
				
			||||||
        "edit_playlist": "Éditer la liste de lecture",
 | 
					        "edit_playlist": "Éditer la liste de lecture",
 | 
				
			||||||
        "playlist_name": "Nom de la liste de lecture",
 | 
					        "playlist_name": "Nom de la liste de lecture",
 | 
				
			||||||
        "auto_display_captions": "Afficer sous-titres automatiquement",
 | 
					        "auto_display_captions": "Afficher sous-titres automatiquement",
 | 
				
			||||||
        "dismiss": "Rejeter",
 | 
					        "dismiss": "Rejeter",
 | 
				
			||||||
        "cancel": "Annuler",
 | 
					        "cancel": "Annuler",
 | 
				
			||||||
        "playlist_description": "Description de la liste de lecture",
 | 
					        "playlist_description": "Description de la liste de lecture",
 | 
				
			||||||
        "create_group": "Créer un groupe",
 | 
					        "create_group": "Créer un groupe",
 | 
				
			||||||
        "group_name": "Nom du groupe",
 | 
					        "group_name": "Nom du groupe",
 | 
				
			||||||
        "autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)"
 | 
					        "autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Format des chapitres sur mobile",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Afficher les suggestions de recherche"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Regarder sur {0}"
 | 
					        "watch_on": "Regarder sur {0}"
 | 
				
			||||||
| 
						 | 
					@ -149,7 +151,9 @@
 | 
				
			||||||
        "live": "{0} en direct",
 | 
					        "live": "{0} en direct",
 | 
				
			||||||
        "shorts": "Courtes",
 | 
					        "shorts": "Courtes",
 | 
				
			||||||
        "all": "Tout",
 | 
					        "all": "Tout",
 | 
				
			||||||
        "category": "Catégorie"
 | 
					        "category": "Catégorie",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Vertical"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "Score SSL",
 | 
					        "ssl_score": "Score SSL",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										94
									
								
								src/locales/gl.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/locales/gl.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,94 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "titles": {
 | 
				
			||||||
 | 
					        "register": "Crear conta",
 | 
				
			||||||
 | 
					        "feed": "Cronoloxía",
 | 
				
			||||||
 | 
					        "preferences": "Preferencias",
 | 
				
			||||||
 | 
					        "history": "Historial",
 | 
				
			||||||
 | 
					        "trending": "En voga",
 | 
				
			||||||
 | 
					        "account": "Conta",
 | 
				
			||||||
 | 
					        "player": "Reprodutor",
 | 
				
			||||||
 | 
					        "login": "Acceder",
 | 
				
			||||||
 | 
					        "instance": "Instancia",
 | 
				
			||||||
 | 
					        "bookmarks": "Marcadores",
 | 
				
			||||||
 | 
					        "subscriptions": "Subscricións",
 | 
				
			||||||
 | 
					        "playlists": "Listas",
 | 
				
			||||||
 | 
					        "livestreams": "En directo",
 | 
				
			||||||
 | 
					        "channels": "Canles",
 | 
				
			||||||
 | 
					        "channel_groups": "Grupos de canles"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "player": {
 | 
				
			||||||
 | 
					        "watch_on": "Ver en {0}",
 | 
				
			||||||
 | 
					        "failed": "Fallou con código do erro {0}, mira o rexistro para máis info"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "actions": {
 | 
				
			||||||
 | 
					        "subscribe": "Subscribirse - {count}",
 | 
				
			||||||
 | 
					        "sort_by": "Orde por:",
 | 
				
			||||||
 | 
					        "least_recent": "Máis antigo",
 | 
				
			||||||
 | 
					        "most_recent": "Máis recente",
 | 
				
			||||||
 | 
					        "channel_name_asc": "Nome da canle (A-Z)",
 | 
				
			||||||
 | 
					        "unsubscribe": "Retirar subscrición - {count}",
 | 
				
			||||||
 | 
					        "view_subscriptions": "Ver Subscricións",
 | 
				
			||||||
 | 
					        "back": "Volver",
 | 
				
			||||||
 | 
					        "uses_api_from": "Usa a API desde ",
 | 
				
			||||||
 | 
					        "enable_sponsorblock": "Activar Sponsoblock",
 | 
				
			||||||
 | 
					        "skip_button_only": "Mostrar botón omitir",
 | 
				
			||||||
 | 
					        "skip_automatically": "Automáticamente",
 | 
				
			||||||
 | 
					        "channel_name_desc": "Nome da canle (Z-A)",
 | 
				
			||||||
 | 
					        "skip_sponsors": "Omitir Sponsors",
 | 
				
			||||||
 | 
					        "show_markers": "Mostrar Marcadores no Reprodutor",
 | 
				
			||||||
 | 
					        "skip_segment": "Omitir Segmento",
 | 
				
			||||||
 | 
					        "dark": "Escuro",
 | 
				
			||||||
 | 
					        "min_segment_length": "Lonxitude mínima do segmento (en segundos)",
 | 
				
			||||||
 | 
					        "theme": "Decorado",
 | 
				
			||||||
 | 
					        "auto": "Auto",
 | 
				
			||||||
 | 
					        "light": "Claro",
 | 
				
			||||||
 | 
					        "autoplay_video": "Reprodución automática",
 | 
				
			||||||
 | 
					        "buffering_goal": "Tamaño do buffer (en segundos)",
 | 
				
			||||||
 | 
					        "minimize_comments_default": "Por defecto minimizar os comentarios",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importar desde JSON/CSV",
 | 
				
			||||||
 | 
					        "skip_outro": "Omitir créditos finais",
 | 
				
			||||||
 | 
					        "skip_intro": "Omitir animación de entrada",
 | 
				
			||||||
 | 
					        "auto_play_next_video": "Reproducir autom. seguinte vídeo",
 | 
				
			||||||
 | 
					        "instance_selection": "Instancia",
 | 
				
			||||||
 | 
					        "clear_history": "Limpar historial",
 | 
				
			||||||
 | 
					        "loading": "A cargar...",
 | 
				
			||||||
 | 
					        "minimize_description": "Minimizar descrición",
 | 
				
			||||||
 | 
					        "skip_interaction": "Omitir Recordatorio para interactuar (Subscribirse)",
 | 
				
			||||||
 | 
					        "filter": "Filtro",
 | 
				
			||||||
 | 
					        "view_ssl_score": "Ver SSL Score",
 | 
				
			||||||
 | 
					        "minimize_description_default": "Por defecto minimizar a descrición",
 | 
				
			||||||
 | 
					        "language_selection": "Idioma",
 | 
				
			||||||
 | 
					        "enable_lbry_proxy": "Activar Proxy para LBRY",
 | 
				
			||||||
 | 
					        "donations": "Doar para o desenvolvemento",
 | 
				
			||||||
 | 
					        "loop_this_video": "Poñer vídeo en bucle",
 | 
				
			||||||
 | 
					        "hide_replies": "Agochar respostas",
 | 
				
			||||||
 | 
					        "country_selection": "País",
 | 
				
			||||||
 | 
					        "skip_self_promo": "Omitir autobombo",
 | 
				
			||||||
 | 
					        "default_quality": "Calidade por defecto",
 | 
				
			||||||
 | 
					        "show_more": "Mostrar máis",
 | 
				
			||||||
 | 
					        "show_recommendations": "Mostrar recomendacións",
 | 
				
			||||||
 | 
					        "minimize_recommendations": "Minimizar recomendacións",
 | 
				
			||||||
 | 
					        "audio_only": "Só audio",
 | 
				
			||||||
 | 
					        "disable_lbry": "Desactivar LBRY para Retransmisión",
 | 
				
			||||||
 | 
					        "show_comments": "Mostrar comentarios",
 | 
				
			||||||
 | 
					        "store_watch_history": "Gardar historial de visualizacións",
 | 
				
			||||||
 | 
					        "no": "Non",
 | 
				
			||||||
 | 
					        "yes": "Si",
 | 
				
			||||||
 | 
					        "load_more_replies": "Cargar máis respostas",
 | 
				
			||||||
 | 
					        "enabled_codecs": "Códecs activados (varios)",
 | 
				
			||||||
 | 
					        "auto_display_captions": "Mostar autom. Subtítulos",
 | 
				
			||||||
 | 
					        "minimize_comments": "Minimizar comentarios",
 | 
				
			||||||
 | 
					        "skip_non_music": "Omitir música: Sección sen música",
 | 
				
			||||||
 | 
					        "instances_list": "Lista de instancias",
 | 
				
			||||||
 | 
					        "import_from_json": "Importar desde JSON",
 | 
				
			||||||
 | 
					        "autoplay_next_countdown": "Conta atrás por defecto para o seguinte vídeo (en segundos)",
 | 
				
			||||||
 | 
					        "default_homepage": "Inicio por defecto",
 | 
				
			||||||
 | 
					        "remove_from_playlist": "Retirar da lista",
 | 
				
			||||||
 | 
					        "search": "Buscar (Ctrl+K)",
 | 
				
			||||||
 | 
					        "show_description": "Mostrar descrición",
 | 
				
			||||||
 | 
					        "skip_preview": "Omitir vista previa/resumo",
 | 
				
			||||||
 | 
					        "skip_highlight": "Omitir Destacado",
 | 
				
			||||||
 | 
					        "export_to_json": "Exportar a JSON",
 | 
				
			||||||
 | 
					        "add_to_playlist": "Engadir á lista"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "שידורים חיים",
 | 
					        "livestreams": "שידורים חיים",
 | 
				
			||||||
        "channels": "ערוצים",
 | 
					        "channels": "ערוצים",
 | 
				
			||||||
        "bookmarks": "סימניות",
 | 
					        "bookmarks": "סימניות",
 | 
				
			||||||
        "channel_groups": "קבוצות ערוץ"
 | 
					        "channel_groups": "קבוצות ערוץ",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "לצפות ב־{0}"
 | 
					        "watch_on": "הצגה ב־{0}",
 | 
				
			||||||
 | 
					        "failed": "חל כשל עם קוד שגיאה {0}, מידע נוסף ביומנים"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "מינוי - {count}",
 | 
					        "subscribe": "מינוי - {count}",
 | 
				
			||||||
| 
						 | 
					@ -45,20 +47,20 @@
 | 
				
			||||||
        "audio_only": "שמע בלבד",
 | 
					        "audio_only": "שמע בלבד",
 | 
				
			||||||
        "default_quality": "איכות ברירת מחדל",
 | 
					        "default_quality": "איכות ברירת מחדל",
 | 
				
			||||||
        "buffering_goal": "יעד שמירה למטמון (בשניות)",
 | 
					        "buffering_goal": "יעד שמירה למטמון (בשניות)",
 | 
				
			||||||
        "country_selection": "בחירת מדינה",
 | 
					        "country_selection": "מדינה",
 | 
				
			||||||
        "default_homepage": "עמוד הבית כברירת מחדל",
 | 
					        "default_homepage": "עמוד הבית כברירת מחדל",
 | 
				
			||||||
        "show_comments": "הצגת תגובות",
 | 
					        "show_comments": "הצגת תגובות",
 | 
				
			||||||
        "minimize_description_default": "מזעור התגובות כברירת מחדל",
 | 
					        "minimize_description_default": "מזעור התגובות כברירת מחדל",
 | 
				
			||||||
        "store_watch_history": "שחזור היסטוריית הצפייה",
 | 
					        "store_watch_history": "שחזור היסטוריית הצפייה",
 | 
				
			||||||
        "language_selection": "בחירת שפה",
 | 
					        "language_selection": "שפה",
 | 
				
			||||||
        "instances_list": "רשימת עותקים",
 | 
					        "instances_list": "רשימת עותקים",
 | 
				
			||||||
        "enabled_codecs": "מפענחים פעילים (מגוון)",
 | 
					        "enabled_codecs": "מפענחים פעילים (מגוון)",
 | 
				
			||||||
        "instance_selection": "בחירת עותק",
 | 
					        "instance_selection": "עותק",
 | 
				
			||||||
        "show_more": "להציג יותר",
 | 
					        "show_more": "להציג יותר",
 | 
				
			||||||
        "yes": "כן",
 | 
					        "yes": "כן",
 | 
				
			||||||
        "no": "לא",
 | 
					        "no": "לא",
 | 
				
			||||||
        "export_to_json": "ייצוא ל־JSON",
 | 
					        "export_to_json": "ייצוא ל־JSON",
 | 
				
			||||||
        "import_from_json": "ייבוא מ־JSON/CSV",
 | 
					        "import_from_json": "ייבוא מ־JSON",
 | 
				
			||||||
        "show_markers": "הצגת סמנים בנגן",
 | 
					        "show_markers": "הצגת סמנים בנגן",
 | 
				
			||||||
        "auto_play_next_video": "לנגן את הסרטון הבא אוטומטית",
 | 
					        "auto_play_next_video": "לנגן את הסרטון הבא אוטומטית",
 | 
				
			||||||
        "donations": "תרומות בפיתוח",
 | 
					        "donations": "תרומות בפיתוח",
 | 
				
			||||||
| 
						 | 
					@ -79,7 +81,7 @@
 | 
				
			||||||
        "logout": "יציאה מהחשבון במכשיר הזה",
 | 
					        "logout": "יציאה מהחשבון במכשיר הזה",
 | 
				
			||||||
        "minimize_recommendations_default": "מזעור המלצות כברירת מחדל",
 | 
					        "minimize_recommendations_default": "מזעור המלצות כברירת מחדל",
 | 
				
			||||||
        "invalidate_session": "להוציא את כל המכשירים מהחשבון",
 | 
					        "invalidate_session": "להוציא את כל המכשירים מהחשבון",
 | 
				
			||||||
        "instance_auth_selection": "בחירת עותק אימות",
 | 
					        "instance_auth_selection": "עותק אימות",
 | 
				
			||||||
        "clone_playlist": "שכפול רשימת נגינה",
 | 
					        "clone_playlist": "שכפול רשימת נגינה",
 | 
				
			||||||
        "clone_playlist_success": "שוכפל בהצלחה!",
 | 
					        "clone_playlist_success": "שוכפל בהצלחה!",
 | 
				
			||||||
        "download_as_txt": "הורדה כ־.txt",
 | 
					        "download_as_txt": "הורדה כ־.txt",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +141,14 @@
 | 
				
			||||||
        "playlist_description": "תיאור רשימת הנגינה",
 | 
					        "playlist_description": "תיאור רשימת הנגינה",
 | 
				
			||||||
        "okay": "אישור",
 | 
					        "okay": "אישור",
 | 
				
			||||||
        "show_search_suggestions": "הצגת הצעות חיפוש",
 | 
					        "show_search_suggestions": "הצגת הצעות חיפוש",
 | 
				
			||||||
        "chapters_layout_mobile": "פריסת פרקים בנייד"
 | 
					        "chapters_layout_mobile": "פריסת פרקים בנייד",
 | 
				
			||||||
 | 
					        "delete_automatically": "למחוק אוטומטית לאחר",
 | 
				
			||||||
 | 
					        "enable_dearrow": "הפעלת DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "יצירת קוד QR",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "ייבוא מ־JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "הורדת תמונית",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "מדיניות פרטיות",
 | 
				
			||||||
 | 
					        "add_to_group": "הוספה לקבוצה"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "ננעץ על ידי {author}",
 | 
					        "pinned_by": "ננעץ על ידי {author}",
 | 
				
			||||||
| 
						 | 
					@ -158,7 +167,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "שם משתמש",
 | 
					        "username": "שם משתמש",
 | 
				
			||||||
        "password": "סיסמה"
 | 
					        "password": "סיסמה",
 | 
				
			||||||
 | 
					        "password_confirm": "אישור סיסמה",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "הסיסמאות שונות זו מזו!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "סרטונים",
 | 
					        "videos": "סרטונים",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +183,9 @@
 | 
				
			||||||
        "all": "הכול",
 | 
					        "all": "הכול",
 | 
				
			||||||
        "category": "קטגוריה",
 | 
					        "category": "קטגוריה",
 | 
				
			||||||
        "chapters_horizontal": "אופקית",
 | 
					        "chapters_horizontal": "אופקית",
 | 
				
			||||||
        "chapters_vertical": "אנכית"
 | 
					        "chapters_vertical": "אנכית",
 | 
				
			||||||
 | 
					        "visibility": "חשיפה",
 | 
				
			||||||
 | 
					        "license": "רישיון"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "האם התכוונת לביטוי {0}?",
 | 
					        "did_you_mean": "האם התכוונת לביטוי {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: שירים",
 | 
					        "music_songs": "YT Music: שירים",
 | 
				
			||||||
        "music_videos": "YT Music: סרטונים",
 | 
					        "music_videos": "YT Music: סרטונים",
 | 
				
			||||||
        "music_albums": "YT Music: אלבומים",
 | 
					        "music_albums": "YT Music: אלבומים",
 | 
				
			||||||
        "music_playlists": "YT Music: רשימות נגינה"
 | 
					        "music_playlists": "YT Music: רשימות נגינה",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: אומנים"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "preferences_note": "לתשומת לבך: ההעדפות נשמרות באחסון המקומי של הדפדפן שלך. מחיקת נתוני הדפדפן שלך תאפס אותם.",
 | 
					        "preferences_note": "לתשומת לבך: ההעדפות נשמרות באחסון המקומי של הדפדפן שלך. מחיקת נתוני הדפדפן שלך תאפס אותם.",
 | 
				
			||||||
| 
						 | 
					@ -192,7 +206,13 @@
 | 
				
			||||||
        "cannot_copy": "לא ניתן להעתיק!",
 | 
					        "cannot_copy": "לא ניתן להעתיק!",
 | 
				
			||||||
        "local_storage": "פעולה זו דורשת אחסון מקומי (localStorage), האם עוגיות פעילות?",
 | 
					        "local_storage": "פעולה זו דורשת אחסון מקומי (localStorage), האם עוגיות פעילות?",
 | 
				
			||||||
        "register_no_email_note": "לא מומלץ להשתמש בכתובת דוא״ל כשם משתמש. להמשיך בכל זאת?",
 | 
					        "register_no_email_note": "לא מומלץ להשתמש בכתובת דוא״ל כשם משתמש. להמשיך בכל זאת?",
 | 
				
			||||||
        "next_video_countdown": "הסרטון הבא יתנגן בעוד {0} שניות"
 | 
					        "next_video_countdown": "הסרטון הבא יתנגן בעוד {0} שניות",
 | 
				
			||||||
 | 
					        "days": "{amount} ימים",
 | 
				
			||||||
 | 
					        "weeks": "{amount} שבועות",
 | 
				
			||||||
 | 
					        "months": "{amount} חודשים",
 | 
				
			||||||
 | 
					        "hours": "{amount} שעות",
 | 
				
			||||||
 | 
					        "login_note": "כניסה עם חשבון שנוצר בעותק הזה.",
 | 
				
			||||||
 | 
					        "register_note": "הרשמה ל־Piped הזה. תאפשר לך לסנכרן את המינויים ואת רשימות הנגינה שלך עם החשבון שלך כך שיאוחסנו בצד השרת. אפשר להשתמש בכל התכונות בלי חשבון אך כל הנתונים יאוחסנו במטמון המקומי של הדפדפן שלך. נא לוודא שלא בחרת בכתובת דוא״ל כשם המשתמש שלך ורצוי לבחור בסיסמה מאובטחת שלא משמשת אותך בשום מקום אחר."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "נרשמת אל: {0}"
 | 
					        "subscribed_channels_count": "נרשמת אל: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,8 @@
 | 
				
			||||||
        "livestreams": "लाइव स्ट्रीम",
 | 
					        "livestreams": "लाइव स्ट्रीम",
 | 
				
			||||||
        "channels": "चैनल",
 | 
					        "channels": "चैनल",
 | 
				
			||||||
        "player": "चालक",
 | 
					        "player": "चालक",
 | 
				
			||||||
        "account": "खाता"
 | 
					        "account": "खाता",
 | 
				
			||||||
 | 
					        "instance": "इंस्टैंस"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "सदस्यता लें - {count}",
 | 
					        "subscribe": "सदस्यता लें - {count}",
 | 
				
			||||||
| 
						 | 
					@ -19,7 +20,7 @@
 | 
				
			||||||
        "unsubscribe": "सदस्यता ले ली है - {count}",
 | 
					        "unsubscribe": "सदस्यता ले ली है - {count}",
 | 
				
			||||||
        "no": "नहीं",
 | 
					        "no": "नहीं",
 | 
				
			||||||
        "hide_replies": "जवाब छिपाएं",
 | 
					        "hide_replies": "जवाब छिपाएं",
 | 
				
			||||||
        "search": "खोजें",
 | 
					        "search": "खोजें (Ctrl+K)",
 | 
				
			||||||
        "loop_this_video": "इस वीडियो को लूप करें",
 | 
					        "loop_this_video": "इस वीडियो को लूप करें",
 | 
				
			||||||
        "loading": "लोड हो रहा है...",
 | 
					        "loading": "लोड हो रहा है...",
 | 
				
			||||||
        "show_description": "विवरण दिखाएं",
 | 
					        "show_description": "विवरण दिखाएं",
 | 
				
			||||||
| 
						 | 
					@ -40,17 +41,17 @@
 | 
				
			||||||
        "autoplay_video": "ऑटोप्ले वीडियो",
 | 
					        "autoplay_video": "ऑटोप्ले वीडियो",
 | 
				
			||||||
        "audio_only": "सिर्फ़ ध्वनि",
 | 
					        "audio_only": "सिर्फ़ ध्वनि",
 | 
				
			||||||
        "default_quality": "डिफ़ॉल्ट गुणवत्ता",
 | 
					        "default_quality": "डिफ़ॉल्ट गुणवत्ता",
 | 
				
			||||||
        "country_selection": "देश चयन",
 | 
					        "country_selection": "देश",
 | 
				
			||||||
        "show_comments": "टिप्पणियाँ दिखाएँ",
 | 
					        "show_comments": "टिप्पणियाँ दिखाएँ",
 | 
				
			||||||
        "store_watch_history": "स्टोर देखने का इतिहास",
 | 
					        "store_watch_history": "स्टोर देखने का इतिहास",
 | 
				
			||||||
        "language_selection": "भाषा चयन",
 | 
					        "language_selection": "भाषा",
 | 
				
			||||||
        "instances_list": "इंस्टेंस सूची",
 | 
					        "instances_list": "इंस्टेंस सूची",
 | 
				
			||||||
        "instance_selection": "इंस्टेंस चयन",
 | 
					        "instance_selection": "इंस्टेंस",
 | 
				
			||||||
        "show_more": "और दिखाओ",
 | 
					        "show_more": "और दिखाओ",
 | 
				
			||||||
        "export_to_json": "JSON में निर्यात करें",
 | 
					        "export_to_json": "JSON में निर्यात करें",
 | 
				
			||||||
        "import_from_json": "JSON/CSV से आयात करें",
 | 
					        "import_from_json": "JSON/CSV से आयात करें",
 | 
				
			||||||
        "auto_play_next_video": "अगला वीडियो ऑटोप्ले करें",
 | 
					        "auto_play_next_video": "अगला वीडियो ऑटोप्ले करें",
 | 
				
			||||||
        "donations": "दान",
 | 
					        "donations": "विकास दान",
 | 
				
			||||||
        "minimize_recommendations": "सिफारिशों को कम करें",
 | 
					        "minimize_recommendations": "सिफारिशों को कम करें",
 | 
				
			||||||
        "show_recommendations": "सिफारिशें दिखाएं",
 | 
					        "show_recommendations": "सिफारिशें दिखाएं",
 | 
				
			||||||
        "disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें",
 | 
					        "disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें",
 | 
				
			||||||
| 
						 | 
					@ -61,10 +62,10 @@
 | 
				
			||||||
        "load_more_replies": "और जवाब लोड करें",
 | 
					        "load_more_replies": "और जवाब लोड करें",
 | 
				
			||||||
        "enabled_codecs": "सक्षम कोडेक्स (एकाधिक)",
 | 
					        "enabled_codecs": "सक्षम कोडेक्स (एकाधिक)",
 | 
				
			||||||
        "buffering_goal": "बफरिंग गोल (सेकंड में)",
 | 
					        "buffering_goal": "बफरिंग गोल (सेकंड में)",
 | 
				
			||||||
        "delete_playlist_confirm": "क्या आप वाकई इस प्लेलिस्ट को हटाना चाहते हैं?",
 | 
					        "delete_playlist_confirm": "प्लेलिस्ट को मिटाना है?",
 | 
				
			||||||
        "add_to_playlist": "प्लेलिस्ट में जोड़ें",
 | 
					        "add_to_playlist": "प्लेलिस्ट में जोड़ें",
 | 
				
			||||||
        "remove_from_playlist": "प्लेलिस्ट से निकाले",
 | 
					        "remove_from_playlist": "प्लेलिस्ट से निकाले",
 | 
				
			||||||
        "delete_playlist_video_confirm": "क्या आप वाकई इस प्लेलिस्ट से इस वीडियो को निकालना चाहेंगे?",
 | 
					        "delete_playlist_video_confirm": "वीडियो को प्लेलिस्ट से निकालना है?",
 | 
				
			||||||
        "create_playlist": "प्लेलिस्ट बनायें",
 | 
					        "create_playlist": "प्लेलिस्ट बनायें",
 | 
				
			||||||
        "select_playlist": "एक प्लेलिस्ट चुनें",
 | 
					        "select_playlist": "एक प्लेलिस्ट चुनें",
 | 
				
			||||||
        "please_select_playlist": "कृपया एक प्लेलिस्ट चुनें",
 | 
					        "please_select_playlist": "कृपया एक प्लेलिस्ट चुनें",
 | 
				
			||||||
| 
						 | 
					@ -72,12 +73,13 @@
 | 
				
			||||||
        "enable_sponsorblock": "विज्ञापन प्रतिबंध करें",
 | 
					        "enable_sponsorblock": "विज्ञापन प्रतिबंध करें",
 | 
				
			||||||
        "default_homepage": "स्वतः निर्धारित मुख्यपृष्ठ",
 | 
					        "default_homepage": "स्वतः निर्धारित मुख्यपृष्ठ",
 | 
				
			||||||
        "sort_by": "वर्गीकरण:",
 | 
					        "sort_by": "वर्गीकरण:",
 | 
				
			||||||
        "skip_automatically": "स्वतः"
 | 
					        "skip_automatically": "स्वतः",
 | 
				
			||||||
 | 
					        "delete_account": "खाता मिटाएँ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "views": "{views} बार देखा गया",
 | 
					        "views": "{views} बार देखा गया",
 | 
				
			||||||
        "videos": "वीडियो",
 | 
					        "videos": "वीडियो",
 | 
				
			||||||
        "watched": "पहले ही देखा",
 | 
					        "watched": "पहले ही देखा हुआ",
 | 
				
			||||||
        "ratings_disabled": "रेटिंग अक्षम",
 | 
					        "ratings_disabled": "रेटिंग अक्षम",
 | 
				
			||||||
        "chapters": "चैप्टर",
 | 
					        "chapters": "चैप्टर",
 | 
				
			||||||
        "live": "{0} लाइव"
 | 
					        "live": "{0} लाइव"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,11 @@
 | 
				
			||||||
        "live": "{0} uživo",
 | 
					        "live": "{0} uživo",
 | 
				
			||||||
        "shorts": "Kratka videa",
 | 
					        "shorts": "Kratka videa",
 | 
				
			||||||
        "all": "Sva",
 | 
					        "all": "Sva",
 | 
				
			||||||
        "category": "Kategorija"
 | 
					        "category": "Kategorija",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Vodoravno",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Okomito",
 | 
				
			||||||
 | 
					        "license": "Licenca",
 | 
				
			||||||
 | 
					        "visibility": "Vidljivost"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "SSL ocjena",
 | 
					        "ssl_score": "SSL ocjena",
 | 
				
			||||||
| 
						 | 
					@ -37,19 +41,19 @@
 | 
				
			||||||
        "donations": "Donacije za razvoj",
 | 
					        "donations": "Donacije za razvoj",
 | 
				
			||||||
        "auto_play_next_video": "Automatski reproduciraj idući video",
 | 
					        "auto_play_next_video": "Automatski reproduciraj idući video",
 | 
				
			||||||
        "loop_this_video": "Ponavljaj ovaj video",
 | 
					        "loop_this_video": "Ponavljaj ovaj video",
 | 
				
			||||||
        "import_from_json": "Uvezi iz JSON/CSV formata",
 | 
					        "import_from_json": "Uvezi iz JSON formata",
 | 
				
			||||||
        "export_to_json": "Izvezi u JSON",
 | 
					        "export_to_json": "Izvezi u JSON",
 | 
				
			||||||
        "no": "Ne",
 | 
					        "no": "Ne",
 | 
				
			||||||
        "yes": "Da",
 | 
					        "yes": "Da",
 | 
				
			||||||
        "show_more": "Prikaži više",
 | 
					        "show_more": "Prikaži više",
 | 
				
			||||||
        "instance_selection": "Izbor instance",
 | 
					        "instance_selection": "Instanca",
 | 
				
			||||||
        "enabled_codecs": "Uključeni kodeki (moguće je odabrati nekoliko kodeka)",
 | 
					        "enabled_codecs": "Uključeni kodeki (moguće je odabrati nekoliko kodeka)",
 | 
				
			||||||
        "instances_list": "Popis instanci",
 | 
					        "instances_list": "Popis instanci",
 | 
				
			||||||
        "language_selection": "Izbor jezika",
 | 
					        "language_selection": "Jezik",
 | 
				
			||||||
        "store_watch_history": "Spremi povijest gledanja",
 | 
					        "store_watch_history": "Spremi povijest gledanja",
 | 
				
			||||||
        "show_comments": "Prikaži komentare",
 | 
					        "show_comments": "Prikaži komentare",
 | 
				
			||||||
        "default_homepage": "Standardna početna stranica",
 | 
					        "default_homepage": "Standardna početna stranica",
 | 
				
			||||||
        "country_selection": "Izbor zemlje",
 | 
					        "country_selection": "Zemlja",
 | 
				
			||||||
        "buffering_goal": "Cilj međuspremnika (u sekundama)",
 | 
					        "buffering_goal": "Cilj međuspremnika (u sekundama)",
 | 
				
			||||||
        "default_quality": "Standardna kvaliteta",
 | 
					        "default_quality": "Standardna kvaliteta",
 | 
				
			||||||
        "audio_only": "Samo zvuk",
 | 
					        "audio_only": "Samo zvuk",
 | 
				
			||||||
| 
						 | 
					@ -99,7 +103,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "Standardno sakrij preporuke",
 | 
					        "minimize_recommendations_default": "Standardno sakrij preporuke",
 | 
				
			||||||
        "invalidate_session": "Odjavi sve uređaje",
 | 
					        "invalidate_session": "Odjavi sve uređaje",
 | 
				
			||||||
        "different_auth_instance": "Koristi drugu instancu za autentifikaciju",
 | 
					        "different_auth_instance": "Koristi drugu instancu za autentifikaciju",
 | 
				
			||||||
        "instance_auth_selection": "Odabir instance autentifikacije",
 | 
					        "instance_auth_selection": "Instanca autentifikacije",
 | 
				
			||||||
        "clone_playlist": "Dupliciraj playlistu",
 | 
					        "clone_playlist": "Dupliciraj playlistu",
 | 
				
			||||||
        "clone_playlist_success": "Dupliciranje uspjelo!",
 | 
					        "clone_playlist_success": "Dupliciranje uspjelo!",
 | 
				
			||||||
        "download_as_txt": "Preuzmi kao .txt",
 | 
					        "download_as_txt": "Preuzmi kao .txt",
 | 
				
			||||||
| 
						 | 
					@ -118,7 +122,7 @@
 | 
				
			||||||
        "documentation": "Dokumentacija",
 | 
					        "documentation": "Dokumentacija",
 | 
				
			||||||
        "source_code": "Izvorni kod",
 | 
					        "source_code": "Izvorni kod",
 | 
				
			||||||
        "instance_donations": "Donacije instance",
 | 
					        "instance_donations": "Donacije instance",
 | 
				
			||||||
        "store_search_history": "Spremi povijest pretrage",
 | 
					        "store_search_history": "Povijest pretrage trgovine",
 | 
				
			||||||
        "hide_watched": "Sakrij gledana videa u novostima",
 | 
					        "hide_watched": "Sakrij gledana videa u novostima",
 | 
				
			||||||
        "status_page": "Stanje",
 | 
					        "status_page": "Stanje",
 | 
				
			||||||
        "reply_count": "{count} odgovora",
 | 
					        "reply_count": "{count} odgovora",
 | 
				
			||||||
| 
						 | 
					@ -144,10 +148,20 @@
 | 
				
			||||||
        "okay": "U redu",
 | 
					        "okay": "U redu",
 | 
				
			||||||
        "edit_playlist": "Uredi playlistu",
 | 
					        "edit_playlist": "Uredi playlistu",
 | 
				
			||||||
        "playlist_name": "Ime playliste",
 | 
					        "playlist_name": "Ime playliste",
 | 
				
			||||||
        "playlist_description": "Opis playliste"
 | 
					        "playlist_description": "Opis playliste",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Raspored poglavlja na mobilnim uređajima",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Prikaži prijedloge pretrage",
 | 
				
			||||||
 | 
					        "delete_automatically": "Automatski izbriši nakon",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Aktiviraj DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Generiraj QR kod",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Uvezi iz JSON/CSV formata",
 | 
				
			||||||
 | 
					        "download_frame": "Preuzmi kadar",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Politika privatnosti",
 | 
				
			||||||
 | 
					        "add_to_group": "Dodaj grupi"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Gledaj na {0}"
 | 
					        "watch_on": "Gledaj na {0}",
 | 
				
			||||||
 | 
					        "failed": "Neuspjelo s greškom {0}. Pogledaj zapise za više informacija"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "subscriptions": "Pretplate",
 | 
					        "subscriptions": "Pretplate",
 | 
				
			||||||
| 
						 | 
					@ -164,11 +178,14 @@
 | 
				
			||||||
        "channels": "Kanali",
 | 
					        "channels": "Kanali",
 | 
				
			||||||
        "livestreams": "Prijenosi uživo",
 | 
					        "livestreams": "Prijenosi uživo",
 | 
				
			||||||
        "bookmarks": "Zabilješke",
 | 
					        "bookmarks": "Zabilješke",
 | 
				
			||||||
        "channel_groups": "Grupe kanala"
 | 
					        "channel_groups": "Grupe kanala",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "Lozinka",
 | 
					        "password": "Lozinka",
 | 
				
			||||||
        "username": "Korisničko ime"
 | 
					        "username": "Korisničko ime",
 | 
				
			||||||
 | 
					        "password_confirm": "Potvrdi lozinku",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Lozinke se ne poklapaju!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Misliš li: {0}?",
 | 
					        "did_you_mean": "Misliš li: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -179,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Pjesme",
 | 
					        "music_songs": "YT Music: Pjesme",
 | 
				
			||||||
        "music_videos": "YT Music: Videa",
 | 
					        "music_videos": "YT Music: Videa",
 | 
				
			||||||
        "music_albums": "YT Music: Albumi",
 | 
					        "music_albums": "YT Music: Albumi",
 | 
				
			||||||
        "music_playlists": "YT Music: Playliste"
 | 
					        "music_playlists": "YT Music: Playliste",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Izvođači"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Broj pretplata: {0}"
 | 
					        "subscribed_channels_count": "Broj pretplata: {0}"
 | 
				
			||||||
| 
						 | 
					@ -194,6 +212,12 @@
 | 
				
			||||||
        "cannot_copy": "Nije moguće kopirati!",
 | 
					        "cannot_copy": "Nije moguće kopirati!",
 | 
				
			||||||
        "local_storage": "Ova radnja zahtijeva lokalno spremište. Jesu li kolačići uključeni?",
 | 
					        "local_storage": "Ova radnja zahtijeva lokalno spremište. Jesu li kolačići uključeni?",
 | 
				
			||||||
        "register_no_email_note": "Korištenje e-mail adrese kao korisničkog imena se ne preporučuje. Svejedno nastaviti?",
 | 
					        "register_no_email_note": "Korištenje e-mail adrese kao korisničkog imena se ne preporučuje. Svejedno nastaviti?",
 | 
				
			||||||
        "next_video_countdown": "Reprodukcija sljedećeg videa za {0} s"
 | 
					        "next_video_countdown": "Reprodukcija sljedećeg videa za {0} s",
 | 
				
			||||||
 | 
					        "hours": "{amount} h",
 | 
				
			||||||
 | 
					        "days": "{amount} dan(a)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} tj",
 | 
				
			||||||
 | 
					        "months": "{amount} mj",
 | 
				
			||||||
 | 
					        "login_note": "Prijavi se s računom stvorenim na ovoj instanci.",
 | 
				
			||||||
 | 
					        "register_note": "Registriraj račun za ovu Piped instancu. To će ti omogućiti sinkronizaciju tvojih pretplata i playlista s tvojim računom kako bi se spremile na poslužitelju. Možeš koristiti sve značajke bez računa, ali će se svi podaci spremiti u lokalnu predmemoriju tvog preglednika. NEMOJ koristiti e-mail adresu kao korisničko ime i odaberi sigurnu lozinku koju ne koristiš negdje drugdje."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "Siaran Langsung",
 | 
					        "livestreams": "Siaran Langsung",
 | 
				
			||||||
        "channels": "Saluran",
 | 
					        "channels": "Saluran",
 | 
				
			||||||
        "bookmarks": "Markah",
 | 
					        "bookmarks": "Markah",
 | 
				
			||||||
        "channel_groups": "Grup saluran"
 | 
					        "channel_groups": "Grup saluran",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Tonton di {0}"
 | 
					        "watch_on": "Lihat di {0}",
 | 
				
			||||||
 | 
					        "failed": "Gagal dengan kode kesalahan {0}, lihat catatan untuk info lebih lanjut"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "Berlangganan - {count}",
 | 
					        "subscribe": "Berlangganan - {count}",
 | 
				
			||||||
| 
						 | 
					@ -43,18 +45,18 @@
 | 
				
			||||||
        "audio_only": "Audio Saja",
 | 
					        "audio_only": "Audio Saja",
 | 
				
			||||||
        "default_quality": "Kualitas Bawaan",
 | 
					        "default_quality": "Kualitas Bawaan",
 | 
				
			||||||
        "buffering_goal": "Tujuan Buffering (dalam detik)",
 | 
					        "buffering_goal": "Tujuan Buffering (dalam detik)",
 | 
				
			||||||
        "country_selection": "Pemilihan Negara",
 | 
					        "country_selection": "Negara",
 | 
				
			||||||
        "default_homepage": "Halaman Beranda Bawaan",
 | 
					        "default_homepage": "Halaman Beranda Bawaan",
 | 
				
			||||||
        "show_comments": "Tampilkan Komentar",
 | 
					        "show_comments": "Tampilkan Komentar",
 | 
				
			||||||
        "minimize_description_default": "Kecilkan Deskripsi secara default",
 | 
					        "minimize_description_default": "Kecilkan Deskripsi secara default",
 | 
				
			||||||
        "language_selection": "Pemilihan Bahasa",
 | 
					        "language_selection": "Bahasa",
 | 
				
			||||||
        "instances_list": "Daftar Instansi",
 | 
					        "instances_list": "Daftar Instansi",
 | 
				
			||||||
        "enabled_codecs": "Kodek yang Diaktifkan (Beberapa)",
 | 
					        "enabled_codecs": "Kodek yang Diaktifkan (Beberapa)",
 | 
				
			||||||
        "instance_selection": "Pemilihan Instansi",
 | 
					        "instance_selection": "Instansi",
 | 
				
			||||||
        "show_more": "Tampilkan Lebih Banyak",
 | 
					        "show_more": "Tampilkan Lebih Banyak",
 | 
				
			||||||
        "yes": "Iya",
 | 
					        "yes": "Iya",
 | 
				
			||||||
        "no": "Tidak",
 | 
					        "no": "Tidak",
 | 
				
			||||||
        "import_from_json": "Impor dari JSON/CSV",
 | 
					        "import_from_json": "Impor dari JSON",
 | 
				
			||||||
        "loop_this_video": "Ulangi Video ini",
 | 
					        "loop_this_video": "Ulangi Video ini",
 | 
				
			||||||
        "auto_play_next_video": "Mainkan video berikutnya secara otomatis",
 | 
					        "auto_play_next_video": "Mainkan video berikutnya secara otomatis",
 | 
				
			||||||
        "donations": "Donasi pengembangan",
 | 
					        "donations": "Donasi pengembangan",
 | 
				
			||||||
| 
						 | 
					@ -91,7 +93,7 @@
 | 
				
			||||||
        "logout": "Keluar dari perangkat ini",
 | 
					        "logout": "Keluar dari perangkat ini",
 | 
				
			||||||
        "minimize_recommendations_default": "Kecilkan Rekomendasi secara bawaan",
 | 
					        "minimize_recommendations_default": "Kecilkan Rekomendasi secara bawaan",
 | 
				
			||||||
        "invalidate_session": "Keluarkan semua perangkat",
 | 
					        "invalidate_session": "Keluarkan semua perangkat",
 | 
				
			||||||
        "instance_auth_selection": "Pemilihan Instansi Otentikasi",
 | 
					        "instance_auth_selection": "Instance Otentikasi",
 | 
				
			||||||
        "different_auth_instance": "Gunakan instansi lain untuk otentikasi",
 | 
					        "different_auth_instance": "Gunakan instansi lain untuk otentikasi",
 | 
				
			||||||
        "clone_playlist_success": "Berhasil disalin!",
 | 
					        "clone_playlist_success": "Berhasil disalin!",
 | 
				
			||||||
        "clone_playlist": "Salin Daftar Putar",
 | 
					        "clone_playlist": "Salin Daftar Putar",
 | 
				
			||||||
| 
						 | 
					@ -138,7 +140,15 @@
 | 
				
			||||||
        "edit_playlist": "Sunting daftar putar",
 | 
					        "edit_playlist": "Sunting daftar putar",
 | 
				
			||||||
        "playlist_name": "Nama daftar putar",
 | 
					        "playlist_name": "Nama daftar putar",
 | 
				
			||||||
        "okay": "Oke",
 | 
					        "okay": "Oke",
 | 
				
			||||||
        "show_search_suggestions": "Tampilkan saran pencarian"
 | 
					        "show_search_suggestions": "Tampilkan saran pencarian",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Tata Letak Bab di Ponsel",
 | 
				
			||||||
 | 
					        "delete_automatically": "Hapus secara otomatis setelah",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Aktifkan DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Buat Kode QR",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Impor dari JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Unduh bingkai",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Kebijakan Privasi",
 | 
				
			||||||
 | 
					        "add_to_group": "Tambahkan ke grup"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Dipasangi pin oleh {author}",
 | 
					        "pinned_by": "Dipasangi pin oleh {author}",
 | 
				
			||||||
| 
						 | 
					@ -157,7 +167,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Nama Pengguna",
 | 
					        "username": "Nama Pengguna",
 | 
				
			||||||
        "password": "Kata Sandi"
 | 
					        "password": "Kata Sandi",
 | 
				
			||||||
 | 
					        "password_confirm": "Konfirmasi kata sandi",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Kata sandi tidak cocok!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Video",
 | 
					        "videos": "Video",
 | 
				
			||||||
| 
						 | 
					@ -169,7 +181,11 @@
 | 
				
			||||||
        "live": "{0} Langsung",
 | 
					        "live": "{0} Langsung",
 | 
				
			||||||
        "shorts": "Shorts",
 | 
					        "shorts": "Shorts",
 | 
				
			||||||
        "all": "Semua",
 | 
					        "all": "Semua",
 | 
				
			||||||
        "category": "Kategori"
 | 
					        "category": "Kategori",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Horisontal",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Vertikal",
 | 
				
			||||||
 | 
					        "license": "Lisensi",
 | 
				
			||||||
 | 
					        "visibility": "Visibilitas"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Apakah Anda bermaksud: {0}?",
 | 
					        "did_you_mean": "Apakah Anda bermaksud: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -180,7 +196,8 @@
 | 
				
			||||||
        "music_videos": "YT Music: Video",
 | 
					        "music_videos": "YT Music: Video",
 | 
				
			||||||
        "music_albums": "YT Music: Album",
 | 
					        "music_albums": "YT Music: Album",
 | 
				
			||||||
        "music_playlists": "YT Music: Daftar Putar",
 | 
					        "music_playlists": "YT Music: Daftar Putar",
 | 
				
			||||||
        "all": "YouTube: Semua"
 | 
					        "all": "YouTube: Semua",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artis"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Berlangganan ke: {0}"
 | 
					        "subscribed_channels_count": "Berlangganan ke: {0}"
 | 
				
			||||||
| 
						 | 
					@ -195,6 +212,12 @@
 | 
				
			||||||
        "cannot_copy": "Tidak dapat menyalin!",
 | 
					        "cannot_copy": "Tidak dapat menyalin!",
 | 
				
			||||||
        "local_storage": "Tindakan ini membutuhkan localStorage, apakah kuki diaktifkan?",
 | 
					        "local_storage": "Tindakan ini membutuhkan localStorage, apakah kuki diaktifkan?",
 | 
				
			||||||
        "register_no_email_note": "Menggunakan surel sebagai nama pengguna tidak disarankan. Lanjut?",
 | 
					        "register_no_email_note": "Menggunakan surel sebagai nama pengguna tidak disarankan. Lanjut?",
 | 
				
			||||||
        "next_video_countdown": "Memutar video berikutnya dalam {0} detik"
 | 
					        "next_video_countdown": "Memutar video berikutnya dalam {0} detik",
 | 
				
			||||||
 | 
					        "weeks": "{amount} minggu",
 | 
				
			||||||
 | 
					        "hours": "{amount} jam",
 | 
				
			||||||
 | 
					        "days": "{amount} hari",
 | 
				
			||||||
 | 
					        "months": "{amount} bulan",
 | 
				
			||||||
 | 
					        "login_note": "Masuk dengan akun yang dibuat di server ini.",
 | 
				
			||||||
 | 
					        "register_note": "Daftarkan akun untuk server Piped ini. Ini akan memungkinkan Anda untuk menyinkronkan langganan dan daftar putar Anda dengan akun Anda, sehingga mereka disimpan di sisi server. Anda dapat menggunakan semua fitur tanpa akun, tetapi semua data akan disimpan di tembolok lokal browser Anda. Pastikan Anda TIDAK menggunakan alamat surel sebagai nama pengguna Anda dan pilih kata sandi yang aman yang tidak Anda gunakan di tempat lain."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "instances_list": "Elenco delle istanze",
 | 
					        "instances_list": "Elenco delle istanze",
 | 
				
			||||||
        "language_selection": "Selezione della lingua",
 | 
					        "language_selection": "Lingua",
 | 
				
			||||||
        "store_watch_history": "Conserva la cronologia di visualizzazione",
 | 
					        "store_watch_history": "Conserva la cronologia di visualizzazione",
 | 
				
			||||||
        "minimize_description_default": "Minimizza la descrizione per impostazione predefinita",
 | 
					        "minimize_description_default": "Minimizza la descrizione per impostazione predefinita",
 | 
				
			||||||
        "show_comments": "Mostra i commenti",
 | 
					        "show_comments": "Mostra i commenti",
 | 
				
			||||||
        "default_homepage": "Pagina iniziale predefinita",
 | 
					        "default_homepage": "Pagina iniziale predefinita",
 | 
				
			||||||
        "country_selection": "Selezione del paese",
 | 
					        "country_selection": "Paese",
 | 
				
			||||||
        "buffering_goal": "Obiettivo di buffering (in secondi)",
 | 
					        "buffering_goal": "Obiettivo di buffering (in secondi)",
 | 
				
			||||||
        "default_quality": "Qualità predefinita",
 | 
					        "default_quality": "Qualità predefinita",
 | 
				
			||||||
        "audio_only": "Solo audio",
 | 
					        "audio_only": "Solo audio",
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@
 | 
				
			||||||
        "enable_lbry_proxy": "Abilita il proxy per LBRY",
 | 
					        "enable_lbry_proxy": "Abilita il proxy per LBRY",
 | 
				
			||||||
        "disable_lbry": "Disabilita LBRY per lo streaming",
 | 
					        "disable_lbry": "Disabilita LBRY per lo streaming",
 | 
				
			||||||
        "show_description": "Mostra la descrizione",
 | 
					        "show_description": "Mostra la descrizione",
 | 
				
			||||||
        "minimize_description": "Minimizza la descrizione per impostazione predefinita",
 | 
					        "minimize_description": "Minimizza descrizione",
 | 
				
			||||||
        "minimize_recommendations": "Minimizza consigli",
 | 
					        "minimize_recommendations": "Minimizza consigli",
 | 
				
			||||||
        "show_recommendations": "Mostra consigli",
 | 
					        "show_recommendations": "Mostra consigli",
 | 
				
			||||||
        "donations": "Donazioni per lo sviluppo",
 | 
					        "donations": "Donazioni per lo sviluppo",
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@
 | 
				
			||||||
        "no": "No",
 | 
					        "no": "No",
 | 
				
			||||||
        "yes": "Sì",
 | 
					        "yes": "Sì",
 | 
				
			||||||
        "show_more": "Mostra di più",
 | 
					        "show_more": "Mostra di più",
 | 
				
			||||||
        "instance_selection": "Selezione dell'istanza",
 | 
					        "instance_selection": "Istanza",
 | 
				
			||||||
        "loading": "Caricamento…",
 | 
					        "loading": "Caricamento…",
 | 
				
			||||||
        "filter": "Filtra",
 | 
					        "filter": "Filtra",
 | 
				
			||||||
        "search": "Cerca (Ctrl+K)",
 | 
					        "search": "Cerca (Ctrl+K)",
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "Riduci al minimo le raccomandazioni per impostazione predefinita",
 | 
					        "minimize_recommendations_default": "Riduci al minimo le raccomandazioni per impostazione predefinita",
 | 
				
			||||||
        "different_auth_instance": "Usa un'istanza diversa per l'autenticazione",
 | 
					        "different_auth_instance": "Usa un'istanza diversa per l'autenticazione",
 | 
				
			||||||
        "invalidate_session": "Disconnetti su tutti i dispositivi",
 | 
					        "invalidate_session": "Disconnetti su tutti i dispositivi",
 | 
				
			||||||
        "instance_auth_selection": "Selezione dell'istanza di autenticazione",
 | 
					        "instance_auth_selection": "Istanza di autenticazione",
 | 
				
			||||||
        "clone_playlist_success": "Clonato con successo!",
 | 
					        "clone_playlist_success": "Clonato con successo!",
 | 
				
			||||||
        "clone_playlist": "Clona la playlist",
 | 
					        "clone_playlist": "Clona la playlist",
 | 
				
			||||||
        "download_as_txt": "Scarica come .txt",
 | 
					        "download_as_txt": "Scarica come .txt",
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,8 @@
 | 
				
			||||||
        "playlist_description": "Descrizione playlist",
 | 
					        "playlist_description": "Descrizione playlist",
 | 
				
			||||||
        "group_name": "Nome gruppo",
 | 
					        "group_name": "Nome gruppo",
 | 
				
			||||||
        "create_group": "Crea gruppo",
 | 
					        "create_group": "Crea gruppo",
 | 
				
			||||||
        "show_search_suggestions": "Mostra suggerimenti di ricerca"
 | 
					        "show_search_suggestions": "Mostra suggerimenti di ricerca",
 | 
				
			||||||
 | 
					        "dismiss": "Chiudi"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Guarda su {0}"
 | 
					        "watch_on": "Guarda su {0}"
 | 
				
			||||||
| 
						 | 
					@ -135,7 +136,8 @@
 | 
				
			||||||
        "player": "Riproduttore",
 | 
					        "player": "Riproduttore",
 | 
				
			||||||
        "livestreams": "Streaming live",
 | 
					        "livestreams": "Streaming live",
 | 
				
			||||||
        "channels": "Canali",
 | 
					        "channels": "Canali",
 | 
				
			||||||
        "bookmarks": "Segnalibri"
 | 
					        "bookmarks": "Segnalibri",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "sponsor_segments": "Segmenti sponsor",
 | 
					        "sponsor_segments": "Segmenti sponsor",
 | 
				
			||||||
| 
						 | 
					@ -147,7 +149,10 @@
 | 
				
			||||||
        "chapters": "Capitoli",
 | 
					        "chapters": "Capitoli",
 | 
				
			||||||
        "shorts": "Short",
 | 
					        "shorts": "Short",
 | 
				
			||||||
        "all": "Tutti",
 | 
					        "all": "Tutti",
 | 
				
			||||||
        "category": "Categoria"
 | 
					        "category": "Categoria",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Orizzontale",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Verticale",
 | 
				
			||||||
 | 
					        "license": "Licenza"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "Valutazione SSL",
 | 
					        "ssl_score": "Valutazione SSL",
 | 
				
			||||||
| 
						 | 
					@ -177,7 +182,8 @@
 | 
				
			||||||
        "channels": "YouTube: Canali",
 | 
					        "channels": "YouTube: Canali",
 | 
				
			||||||
        "music_songs": "YT Music: Canzoni",
 | 
					        "music_songs": "YT Music: Canzoni",
 | 
				
			||||||
        "all": "YouTube: Tutto",
 | 
					        "all": "YouTube: Tutto",
 | 
				
			||||||
        "videos": "YouTube: Video"
 | 
					        "videos": "YouTube: Video",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artisti"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Abbonato a: {0}"
 | 
					        "subscribed_channels_count": "Abbonato a: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "channels": "チャンネル",
 | 
					        "channels": "チャンネル",
 | 
				
			||||||
        "livestreams": "ライブ配信",
 | 
					        "livestreams": "ライブ配信",
 | 
				
			||||||
        "bookmarks": "ブックマーク",
 | 
					        "bookmarks": "ブックマーク",
 | 
				
			||||||
        "channel_groups": "グループ"
 | 
					        "channel_groups": "グループ",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "{0}で視聴"
 | 
					        "watch_on": "{0}で視聴",
 | 
				
			||||||
 | 
					        "failed": "失敗しエラーコード {0} が返りました。詳細はログに記録"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "チャンネル登録 - {count}",
 | 
					        "subscribe": "チャンネル登録 - {count}",
 | 
				
			||||||
| 
						 | 
					@ -30,7 +32,7 @@
 | 
				
			||||||
        "channel_name_desc": "チャンネル名 (ZからA)",
 | 
					        "channel_name_desc": "チャンネル名 (ZからA)",
 | 
				
			||||||
        "back": "戻る",
 | 
					        "back": "戻る",
 | 
				
			||||||
        "uses_api_from": "API 提供元 ",
 | 
					        "uses_api_from": "API 提供元 ",
 | 
				
			||||||
        "enable_sponsorblock": "SponsorBlock を有効化",
 | 
					        "enable_sponsorblock": "SponsorBlock を使用",
 | 
				
			||||||
        "skip_sponsors": "広告をスキップ",
 | 
					        "skip_sponsors": "広告をスキップ",
 | 
				
			||||||
        "skip_intro": "合間/導入アニメをスキップ",
 | 
					        "skip_intro": "合間/導入アニメをスキップ",
 | 
				
			||||||
        "skip_outro": "終了シーン/クレジットをスキップ",
 | 
					        "skip_outro": "終了シーン/クレジットをスキップ",
 | 
				
			||||||
| 
						 | 
					@ -46,20 +48,20 @@
 | 
				
			||||||
        "audio_only": "音声のみのモード",
 | 
					        "audio_only": "音声のみのモード",
 | 
				
			||||||
        "default_quality": "標準の画質",
 | 
					        "default_quality": "標準の画質",
 | 
				
			||||||
        "buffering_goal": "バッファリング目標値 (秒)",
 | 
					        "buffering_goal": "バッファリング目標値 (秒)",
 | 
				
			||||||
        "country_selection": "国の選択",
 | 
					        "country_selection": "国",
 | 
				
			||||||
        "default_homepage": "ホームに表示するページ",
 | 
					        "default_homepage": "ホームに表示するページ",
 | 
				
			||||||
        "show_comments": "コメントを表示",
 | 
					        "show_comments": "コメントを表示",
 | 
				
			||||||
        "minimize_description_default": "最初から説明を最小化",
 | 
					        "minimize_description_default": "最初から説明を最小化",
 | 
				
			||||||
        "store_watch_history": "再生履歴を保存",
 | 
					        "store_watch_history": "再生履歴を保存",
 | 
				
			||||||
        "language_selection": "言語の選択",
 | 
					        "language_selection": "言語",
 | 
				
			||||||
        "instances_list": "インスタンス一覧",
 | 
					        "instances_list": "インスタンス一覧",
 | 
				
			||||||
        "enabled_codecs": "コーデックの有効化 (複数選択)",
 | 
					        "enabled_codecs": "コーデックの有効化 (複数選択)",
 | 
				
			||||||
        "instance_selection": "インスタンスを選択",
 | 
					        "instance_selection": "インスタンス",
 | 
				
			||||||
        "show_more": "もっと見る",
 | 
					        "show_more": "もっと見る",
 | 
				
			||||||
        "yes": "はい",
 | 
					        "yes": "はい",
 | 
				
			||||||
        "no": "いいえ",
 | 
					        "no": "いいえ",
 | 
				
			||||||
        "export_to_json": "JSONに出力",
 | 
					        "export_to_json": "JSONに出力",
 | 
				
			||||||
        "import_from_json": "JSON/CSVを読み込む",
 | 
					        "import_from_json": "JSONから読み込む",
 | 
				
			||||||
        "loop_this_video": "この動画をループ再生",
 | 
					        "loop_this_video": "この動画をループ再生",
 | 
				
			||||||
        "auto_play_next_video": "次の動画を自動再生",
 | 
					        "auto_play_next_video": "次の動画を自動再生",
 | 
				
			||||||
        "donations": "開発者に寄付",
 | 
					        "donations": "開発者に寄付",
 | 
				
			||||||
| 
						 | 
					@ -95,8 +97,8 @@
 | 
				
			||||||
        "instance_donations": "インスタンスに寄付",
 | 
					        "instance_donations": "インスタンスに寄付",
 | 
				
			||||||
        "minimize_comments": "コメントを最小化",
 | 
					        "minimize_comments": "コメントを最小化",
 | 
				
			||||||
        "share": "共有",
 | 
					        "share": "共有",
 | 
				
			||||||
        "with_timecode": "時間指定で共有",
 | 
					        "with_timecode": "時間指定",
 | 
				
			||||||
        "different_auth_instance": "認証には別のインスタンスを使う",
 | 
					        "different_auth_instance": "認証に別のインスタンスを使用",
 | 
				
			||||||
        "download_as_txt": ".txtでダウンロード",
 | 
					        "download_as_txt": ".txtでダウンロード",
 | 
				
			||||||
        "logout": "この端末からログアウト",
 | 
					        "logout": "この端末からログアウト",
 | 
				
			||||||
        "minimize_recommendations_default": "最初からおすすめを最小化",
 | 
					        "minimize_recommendations_default": "最初からおすすめを最小化",
 | 
				
			||||||
| 
						 | 
					@ -104,12 +106,12 @@
 | 
				
			||||||
        "minimize_chapters_default": "最初からチャプターを最小化",
 | 
					        "minimize_chapters_default": "最初からチャプターを最小化",
 | 
				
			||||||
        "show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
 | 
					        "show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
 | 
				
			||||||
        "invalidate_session": "すべての端末からログアウト",
 | 
					        "invalidate_session": "すべての端末からログアウト",
 | 
				
			||||||
        "instance_auth_selection": "認証用インスタンスの選択",
 | 
					        "instance_auth_selection": "認証用インスタンス",
 | 
				
			||||||
        "clone_playlist_success": "複製に成功しました!",
 | 
					        "clone_playlist_success": "複製に成功しました!",
 | 
				
			||||||
        "backup_preferences": "設定をバックアップ",
 | 
					        "backup_preferences": "設定をバックアップ",
 | 
				
			||||||
        "restore_preferences": "設定を復元",
 | 
					        "restore_preferences": "設定を復元",
 | 
				
			||||||
        "back_to_home": "ホームに戻る",
 | 
					        "back_to_home": "ホームに戻る",
 | 
				
			||||||
        "copy_link": "リンクをコピー",
 | 
					        "copy_link": "リンクコピー",
 | 
				
			||||||
        "time_code": "タイムコード (秒)",
 | 
					        "time_code": "タイムコード (秒)",
 | 
				
			||||||
        "documentation": "ドキュメント",
 | 
					        "documentation": "ドキュメント",
 | 
				
			||||||
        "reset_preferences": "設定を初期化",
 | 
					        "reset_preferences": "設定を初期化",
 | 
				
			||||||
| 
						 | 
					@ -129,7 +131,7 @@
 | 
				
			||||||
        "min_segment_length": "最小の区切りの長さ (秒)",
 | 
					        "min_segment_length": "最小の区切りの長さ (秒)",
 | 
				
			||||||
        "show_less": "少なく見る",
 | 
					        "show_less": "少なく見る",
 | 
				
			||||||
        "autoplay_next_countdown": "次の動画の再生までの時間 (秒)",
 | 
					        "autoplay_next_countdown": "次の動画の再生までの時間 (秒)",
 | 
				
			||||||
        "dismiss": "無視",
 | 
					        "dismiss": "却下",
 | 
				
			||||||
        "create_group": "グループ作成",
 | 
					        "create_group": "グループ作成",
 | 
				
			||||||
        "group_name": "グループ名",
 | 
					        "group_name": "グループ名",
 | 
				
			||||||
        "auto_display_captions": "自動で字幕を表示",
 | 
					        "auto_display_captions": "自動で字幕を表示",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +141,13 @@
 | 
				
			||||||
        "playlist_name": "再生リスト名",
 | 
					        "playlist_name": "再生リスト名",
 | 
				
			||||||
        "playlist_description": "再生リストの説明",
 | 
					        "playlist_description": "再生リストの説明",
 | 
				
			||||||
        "chapters_layout_mobile": "モバイル端末でのチャプターの配置",
 | 
					        "chapters_layout_mobile": "モバイル端末でのチャプターの配置",
 | 
				
			||||||
        "show_search_suggestions": "検索語句の候補を表示"
 | 
					        "show_search_suggestions": "検索語句の候補を表示",
 | 
				
			||||||
 | 
					        "delete_automatically": "指定時間後に自動削除",
 | 
				
			||||||
 | 
					        "enable_dearrow": "DeArrow を使用",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QRコードの生成",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "JSON/CSVから読み込む",
 | 
				
			||||||
 | 
					        "download_frame": "この画像を保存",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "個人情報保護方針"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "{author} によって固定",
 | 
					        "pinned_by": "{author} によって固定",
 | 
				
			||||||
| 
						 | 
					@ -158,13 +166,15 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "ユーザー名",
 | 
					        "username": "ユーザー名",
 | 
				
			||||||
        "password": "パスワード"
 | 
					        "password": "パスワード",
 | 
				
			||||||
 | 
					        "password_confirm": "パスワードの確認",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "パスワードが一致しません!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "動画",
 | 
					        "videos": "動画",
 | 
				
			||||||
        "views": "{views} 回再生",
 | 
					        "views": "{views} 回再生",
 | 
				
			||||||
        "watched": "再生済み",
 | 
					        "watched": "再生済み",
 | 
				
			||||||
        "sponsor_segments": "スポンサーによる広告",
 | 
					        "sponsor_segments": "広告シーン数",
 | 
				
			||||||
        "ratings_disabled": "評価は無効化されています",
 | 
					        "ratings_disabled": "評価は無効化されています",
 | 
				
			||||||
        "chapters": "チャプター",
 | 
					        "chapters": "チャプター",
 | 
				
			||||||
        "live": "{0} ライブ配信",
 | 
					        "live": "{0} ライブ配信",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +182,9 @@
 | 
				
			||||||
        "all": "すべて",
 | 
					        "all": "すべて",
 | 
				
			||||||
        "category": "分類",
 | 
					        "category": "分類",
 | 
				
			||||||
        "chapters_horizontal": "横方向",
 | 
					        "chapters_horizontal": "横方向",
 | 
				
			||||||
        "chapters_vertical": "縦方向"
 | 
					        "chapters_vertical": "縦方向",
 | 
				
			||||||
 | 
					        "visibility": "公開状態",
 | 
				
			||||||
 | 
					        "license": "ライセンス"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "もしかして: {0}?",
 | 
					        "did_you_mean": "もしかして: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +195,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: 音楽",
 | 
					        "music_songs": "YT Music: 音楽",
 | 
				
			||||||
        "music_videos": "YT Music: 動画",
 | 
					        "music_videos": "YT Music: 動画",
 | 
				
			||||||
        "music_albums": "YT Music: アルバム",
 | 
					        "music_albums": "YT Music: アルバム",
 | 
				
			||||||
        "music_playlists": "YT Music: 再生リスト"
 | 
					        "music_playlists": "YT Music: 再生リスト",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: アーティスト"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "page_not_found": "ページが見つかりません",
 | 
					        "page_not_found": "ページが見つかりません",
 | 
				
			||||||
| 
						 | 
					@ -192,7 +205,11 @@
 | 
				
			||||||
        "preferences_note": "注意: 設定は、お使いのブラウザの保存領域に保存されます。ブラウザのデータを削除すると初期化されます。",
 | 
					        "preferences_note": "注意: 設定は、お使いのブラウザの保存領域に保存されます。ブラウザのデータを削除すると初期化されます。",
 | 
				
			||||||
        "local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか?",
 | 
					        "local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか?",
 | 
				
			||||||
        "register_no_email_note": "ユーザー名としてのメールアドレスの使用は推奨しません。それでも続けますか?",
 | 
					        "register_no_email_note": "ユーザー名としてのメールアドレスの使用は推奨しません。それでも続けますか?",
 | 
				
			||||||
        "next_video_countdown": "{0} 秒後に次の動画"
 | 
					        "next_video_countdown": "{0} 秒後に次の動画",
 | 
				
			||||||
 | 
					        "days": "{amount}日",
 | 
				
			||||||
 | 
					        "weeks": "{amount}週",
 | 
				
			||||||
 | 
					        "hours": "{amount}時間",
 | 
				
			||||||
 | 
					        "months": "{amount}月"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "チャンネル登録: {0}"
 | 
					        "subscribed_channels_count": "チャンネル登録: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,8 @@
 | 
				
			||||||
        "login": "പ്രവേശിക്കുക",
 | 
					        "login": "പ്രവേശിക്കുക",
 | 
				
			||||||
        "subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ",
 | 
					        "subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ",
 | 
				
			||||||
        "playlists": "പ്ലേലിസ്റ്റുകൾ",
 | 
					        "playlists": "പ്ലേലിസ്റ്റുകൾ",
 | 
				
			||||||
        "player": "കളിക്കാരൻ"
 | 
					        "player": "കളിക്കാരൻ",
 | 
				
			||||||
 | 
					        "account": "അക്കോണട്"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
 | 
					        "view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
 | 
				
			||||||
| 
						 | 
					@ -55,7 +56,7 @@
 | 
				
			||||||
        "yes": "അതെ",
 | 
					        "yes": "അതെ",
 | 
				
			||||||
        "show_more": "കൂടുതൽ കാണിക്കുക",
 | 
					        "show_more": "കൂടുതൽ കാണിക്കുക",
 | 
				
			||||||
        "buffering_goal": "ബഫറിംഗ് ലക്ഷ്യം(സെക്കൻഡുകളിൽ)",
 | 
					        "buffering_goal": "ബഫറിംഗ് ലക്ഷ്യം(സെക്കൻഡുകളിൽ)",
 | 
				
			||||||
        "import_from_json": "JSON/CSV നിന്ന് ഇറക്കുമതി ചെയ്യൂ",
 | 
					        "import_from_json": "JSONിൽ നിന്ന് ഇറക്കുമതി ചെയ്യൂ",
 | 
				
			||||||
        "export_to_json": "JSON-ലേക്ക് എക്സ്പ്പോർട്ട് ചെയ്യുക",
 | 
					        "export_to_json": "JSON-ലേക്ക് എക്സ്പ്പോർട്ട് ചെയ്യുക",
 | 
				
			||||||
        "instance_selection": "ഇൻസ്റ്റ്ൻസ് തിരഞ്ഞെടുക്കുക",
 | 
					        "instance_selection": "ഇൻസ്റ്റ്ൻസ് തിരഞ്ഞെടുക്കുക",
 | 
				
			||||||
        "loading": "ലഭ്യമാക്കുന്നു...",
 | 
					        "loading": "ലഭ്യമാക്കുന്നു...",
 | 
				
			||||||
| 
						 | 
					@ -74,16 +75,32 @@
 | 
				
			||||||
        "please_select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കുക",
 | 
					        "please_select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കുക",
 | 
				
			||||||
        "delete_playlist_video_confirm": "ഈ പ്ലേലിസ്റ്റിൽ നിന്ന് ഈ വീഡിയോ ഒഴിവാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?",
 | 
					        "delete_playlist_video_confirm": "ഈ പ്ലേലിസ്റ്റിൽ നിന്ന് ഈ വീഡിയോ ഒഴിവാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?",
 | 
				
			||||||
        "delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?",
 | 
					        "delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?",
 | 
				
			||||||
        "skip_automatically": "സ്വയമേവ"
 | 
					        "skip_automatically": "സ്വയമേവ",
 | 
				
			||||||
 | 
					        "show_watch_on_youtube": "YouTubeിൽ കാണാം എന്ന button കാണിക്കുകാ",
 | 
				
			||||||
 | 
					        "time_code": "സമയമായം (സെക്കന്റുകളിൽ)",
 | 
				
			||||||
 | 
					        "show_less": "കുറച്ച്മാതറം",
 | 
				
			||||||
 | 
					        "with_timecode": "സമയമായവുമായി പങ്കുവെക്കുക",
 | 
				
			||||||
 | 
					        "back_to_home": "തിരിച്ച് വീട്ടിലേക്ക്",
 | 
				
			||||||
 | 
					        "dismiss": "തിരിച്ച്",
 | 
				
			||||||
 | 
					        "create_group": "കൂട്ടം ഉണടാക്കുകാ",
 | 
				
			||||||
 | 
					        "okay": "ശരി",
 | 
				
			||||||
 | 
					        "invalidate_session": "എല ടിവയഡിൽ നിന്നും ലൊഗൗഠ അവുകാ",
 | 
				
			||||||
 | 
					        "enabled_codecs": "ഉപയോഗിക്കുന്ന കോടെക്കൃകൽ",
 | 
				
			||||||
 | 
					        "share": "പങ്കുവെക്കുക",
 | 
				
			||||||
 | 
					        "documentation": "സഹായക്കുറിപ്പുകൽ",
 | 
				
			||||||
 | 
					        "cancel": "റദ്ദാക്കുക",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR Code ഉണ്ടാക്കുക",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "JSON/CSVിൽ നിന്ന് ഇറക്കുമതി ചെയ്യൂ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "കാണുക {0}"
 | 
					        "watch_on": "{0}ിൻ കാണൃകാ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "watched": "കണ്ടതാണ്",
 | 
					        "watched": "കണ്ടതാണ്",
 | 
				
			||||||
        "views": "{views} കാഴ്ചകൾ",
 | 
					        "views": "{views} കാഴ്ചകൾ",
 | 
				
			||||||
        "videos": "വിഡിയോകൾ",
 | 
					        "videos": "വിഡിയോകൾ",
 | 
				
			||||||
        "sponsor_segments": "സ്പോൺസർമാരുടെ ഭാഗങ്ങൾ"
 | 
					        "sponsor_segments": "സ്പോൺസർമാരുടെ ഭാഗങ്ങൾ",
 | 
				
			||||||
 | 
					        "all": "എല്ലാം"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "പിൻ ചെയ്തിരിക്കുന്നത് {author}"
 | 
					        "pinned_by": "പിൻ ചെയ്തിരിക്കുന്നത് {author}"
 | 
				
			||||||
| 
						 | 
					@ -98,5 +115,8 @@
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "രഹസ്യവാക്ക്",
 | 
					        "password": "രഹസ്യവാക്ക്",
 | 
				
			||||||
        "username": "ഉപയോക്തൃനാമം"
 | 
					        "username": "ഉപയോക്തൃനാമം"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "search": {
 | 
				
			||||||
 | 
					        "did_you_mean": "{0} ആണൊ ഉദേശിച്ചെ?"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "skip_sponsors": "Sponsors Overslaan",
 | 
					        "skip_sponsors": "Sponsors overslaan",
 | 
				
			||||||
        "skip_outro": "Eindkaarten/Credits Overslaan",
 | 
					        "skip_outro": "Eindkaarten/credits overslaan",
 | 
				
			||||||
        "add_to_playlist": "Aan Afspeellijst Toevoegen",
 | 
					        "add_to_playlist": "Toevoegen aan afspeellijst",
 | 
				
			||||||
        "sort_by": "Sorteer op:",
 | 
					        "sort_by": "Sorteren op:",
 | 
				
			||||||
        "buffering_goal": "Bufferdoel (in seconden)",
 | 
					        "buffering_goal": "Bufferdoel (in seconden)",
 | 
				
			||||||
        "country_selection": "Land Selectie",
 | 
					        "country_selection": "Land",
 | 
				
			||||||
        "show_recommendations": "Aanbevelingen Weergeven",
 | 
					        "show_recommendations": "Aanbevelingen tonen",
 | 
				
			||||||
        "disable_lbry": "LBRY voor Streamen Uitschakelen",
 | 
					        "disable_lbry": "LBRY voor streamen uitschakelen",
 | 
				
			||||||
        "enable_lbry_proxy": "Proxy voor LBRY Inschakelen",
 | 
					        "enable_lbry_proxy": "Proxy voor LBRY Inschakelen",
 | 
				
			||||||
        "view_ssl_score": "SSL-score Bekijken",
 | 
					        "view_ssl_score": "SSL-score Bekijken",
 | 
				
			||||||
        "search": "Zoeken (Ctrl+K)",
 | 
					        "search": "Zoeken (Ctrl+K)",
 | 
				
			||||||
| 
						 | 
					@ -16,68 +16,68 @@
 | 
				
			||||||
        "theme": "Thema",
 | 
					        "theme": "Thema",
 | 
				
			||||||
        "subscribe": "Abonneren - {count}",
 | 
					        "subscribe": "Abonneren - {count}",
 | 
				
			||||||
        "skip_non_music": "Muziek Overslaan: Niet-muzieksectie",
 | 
					        "skip_non_music": "Muziek Overslaan: Niet-muzieksectie",
 | 
				
			||||||
        "show_comments": "Toon Reacties",
 | 
					        "show_comments": "Opmerkingen tonen",
 | 
				
			||||||
        "skip_self_promo": "Onbetaalde/Zelf-promotie Overslaan",
 | 
					        "skip_self_promo": "Onbetaalde-/zelfpromotie overslaan",
 | 
				
			||||||
        "skip_highlight": "Markering Overslaan",
 | 
					        "skip_highlight": "Markering Overslaan",
 | 
				
			||||||
        "skip_interaction": "Interactieherinnering Overslaan (Abonneren)",
 | 
					        "skip_interaction": "Interactieherinnering overslaan (abonneren)",
 | 
				
			||||||
        "show_more": "Toon meer",
 | 
					        "show_more": "Meer tonen",
 | 
				
			||||||
        "unsubscribe": "Afmelden - {count}",
 | 
					        "unsubscribe": "Afmelden - {count}",
 | 
				
			||||||
        "view_subscriptions": "Abonnementen Bekijken",
 | 
					        "view_subscriptions": "Abonnementen bekijken",
 | 
				
			||||||
        "enable_sponsorblock": "Sponsorblok Inschakelen",
 | 
					        "enable_sponsorblock": "Sponsorblok inschakelen",
 | 
				
			||||||
        "skip_preview": "Voorbeeld/Samenvatting Overslaan",
 | 
					        "skip_preview": "Voorbeschouwing/samenvatting overslaan",
 | 
				
			||||||
        "auto": "Auto",
 | 
					        "auto": "Auto",
 | 
				
			||||||
        "dark": "Donker",
 | 
					        "dark": "Donker",
 | 
				
			||||||
        "light": "Licht",
 | 
					        "light": "Licht",
 | 
				
			||||||
        "default_quality": "Standaard Kwaliteit",
 | 
					        "default_quality": "Standaard Kwaliteit",
 | 
				
			||||||
        "loop_this_video": "Deze Video Herhalen",
 | 
					        "loop_this_video": "Deze video herhalen",
 | 
				
			||||||
        "donations": "Ontwikkelingsdonaties",
 | 
					        "donations": "Ontwikkelingsdonaties",
 | 
				
			||||||
        "minimize_description": "Beschrijving Minimaliseren",
 | 
					        "minimize_description": "Omschrijving minimaliseren",
 | 
				
			||||||
        "show_description": "Toon Beschrijving",
 | 
					        "show_description": "Omschrijving tonen",
 | 
				
			||||||
        "minimize_recommendations": "Aanbevelingen Minimaliseren",
 | 
					        "minimize_recommendations": "Aanbevelingen minimaliseren",
 | 
				
			||||||
        "most_recent": "Meest Recente",
 | 
					        "most_recent": "Meest recent",
 | 
				
			||||||
        "least_recent": "Minst Recente",
 | 
					        "least_recent": "Minst recent",
 | 
				
			||||||
        "channel_name_asc": "Kanaalnaam (A-Z)",
 | 
					        "channel_name_asc": "Kanaalnaam (A-Z)",
 | 
				
			||||||
        "channel_name_desc": "Kanaalnaam (Z-A)",
 | 
					        "channel_name_desc": "Kanaalnaam (Z-A)",
 | 
				
			||||||
        "back": "Terug",
 | 
					        "back": "Terug",
 | 
				
			||||||
        "uses_api_from": "Gebruikt de API van ",
 | 
					        "uses_api_from": "API gebruiken van ",
 | 
				
			||||||
        "skip_intro": "Pauze/Intro-animatie Overslaan",
 | 
					        "skip_intro": "Onderbrekings-/intro-animatie overslaan",
 | 
				
			||||||
        "autoplay_video": "Video Automatisch Afspelen",
 | 
					        "autoplay_video": "Video Automatisch Afspelen",
 | 
				
			||||||
        "store_watch_history": "Kijkgeschiedenis Opslaan",
 | 
					        "store_watch_history": "Kijkgeschiedenis Opslaan",
 | 
				
			||||||
        "loading": "Laden...",
 | 
					        "loading": "Laden...",
 | 
				
			||||||
        "audio_only": "Alleen Audio",
 | 
					        "audio_only": "Alleen Audio",
 | 
				
			||||||
        "default_homepage": "Standaard Startpagina",
 | 
					        "default_homepage": "Standaard Startpagina",
 | 
				
			||||||
        "minimize_description_default": "Beschrijving Standaard Minimaliseren",
 | 
					        "minimize_description_default": "Omschrijving standaard minimaliseren",
 | 
				
			||||||
        "language_selection": "Taalkeuze",
 | 
					        "language_selection": "Taal",
 | 
				
			||||||
        "instances_list": "Instantieslijst",
 | 
					        "instances_list": "Instantieslijst",
 | 
				
			||||||
        "yes": "Ja",
 | 
					        "yes": "Ja",
 | 
				
			||||||
        "export_to_json": "Exporteren naar JSON",
 | 
					        "export_to_json": "Exporteren naar JSON",
 | 
				
			||||||
        "hide_replies": "Verberg Antwoorden",
 | 
					        "hide_replies": "Reacties verbergen",
 | 
				
			||||||
        "enabled_codecs": "Ingeschakelde Codecs (Meerdere)",
 | 
					        "enabled_codecs": "Ingeschakelde Codecs (Meerdere)",
 | 
				
			||||||
        "no": "Nee",
 | 
					        "no": "Nee",
 | 
				
			||||||
        "auto_play_next_video": "Volgende Video Automatisch Afspelen",
 | 
					        "auto_play_next_video": "Volgende video automatisch afspelen",
 | 
				
			||||||
        "remove_from_playlist": "Uit Afspeellijst Verwijderen",
 | 
					        "remove_from_playlist": "Uit afspeellijst verwijderen",
 | 
				
			||||||
        "select_playlist": "Selecteer een Afspeellijst",
 | 
					        "select_playlist": "Afspeellijst selecteren",
 | 
				
			||||||
        "delete_playlist_confirm": "Deze afspeellijst verwijderen?",
 | 
					        "delete_playlist_confirm": "Deze afspeellijst verwijderen?",
 | 
				
			||||||
        "please_select_playlist": "Selecteer een afspeellijst alsjeblief",
 | 
					        "please_select_playlist": "Selecteer een afspeellijst",
 | 
				
			||||||
        "instance_selection": "Instantie Selectie",
 | 
					        "instance_selection": "Instantie",
 | 
				
			||||||
        "import_from_json": "Importeren uit JSON/CSV",
 | 
					        "import_from_json": "Importeren uit JSON",
 | 
				
			||||||
        "clear_history": "Geschiedenis Wissen",
 | 
					        "clear_history": "Geschiedenis wissen",
 | 
				
			||||||
        "load_more_replies": "Laad meer Antwoorden",
 | 
					        "load_more_replies": "Meer reacties laden",
 | 
				
			||||||
        "delete_playlist_video_confirm": "Video uit deze afspeellijst verwijderen?",
 | 
					        "delete_playlist_video_confirm": "Video uit deze afspeellijst verwijderen?",
 | 
				
			||||||
        "create_playlist": "Afspeellijst Maken",
 | 
					        "create_playlist": "Afspeellijst aanmaken",
 | 
				
			||||||
        "delete_playlist": "Afspeellijst Verwijderen",
 | 
					        "delete_playlist": "Afspeellijst verwijderen",
 | 
				
			||||||
        "show_markers": "Laat markeringen op speler zien",
 | 
					        "show_markers": "Laat markeringen op speler zien",
 | 
				
			||||||
        "store_search_history": "Zoekgeschiedenis Opslaan",
 | 
					        "store_search_history": "Zoekgeschiedenis bijhouden",
 | 
				
			||||||
        "minimize_chapters_default": "Hoofdstukken Standaard Minimaliseren",
 | 
					        "minimize_chapters_default": "Hoofdstukken standaard minimaliseren",
 | 
				
			||||||
        "show_watch_on_youtube": "Toon Bekijk op YouTube knop",
 | 
					        "show_watch_on_youtube": "De knop ‘Bekijken op YouTube’ tonen",
 | 
				
			||||||
        "restore_preferences": "Voorkeuren herstellen",
 | 
					        "restore_preferences": "Voorkeuren herstellen",
 | 
				
			||||||
        "with_timecode": "Delen met tijdcode",
 | 
					        "with_timecode": "Delen met tijdcode",
 | 
				
			||||||
        "piped_link": "Piped link",
 | 
					        "piped_link": "Piped-link",
 | 
				
			||||||
        "follow_link": "Volg link",
 | 
					        "follow_link": "Volglink",
 | 
				
			||||||
        "copy_link": "Link kopiëren",
 | 
					        "copy_link": "Link kopiëren",
 | 
				
			||||||
        "hide_watched": "Verberg bekeken video's in de feed",
 | 
					        "hide_watched": "Bekeken video's in feed verbergen",
 | 
				
			||||||
        "minimize_comments": "Opmerkingen minimaliseren",
 | 
					        "minimize_comments": "Opmerkingen minimaliseren",
 | 
				
			||||||
        "instance_auth_selection": "Selectie authenticatie-instantie",
 | 
					        "instance_auth_selection": "Authenticatie-instantie",
 | 
				
			||||||
        "clone_playlist": "Afspeellijst dupliceren",
 | 
					        "clone_playlist": "Afspeellijst dupliceren",
 | 
				
			||||||
        "download_as_txt": "Downloaden als .txt",
 | 
					        "download_as_txt": "Downloaden als .txt",
 | 
				
			||||||
        "share": "Delen",
 | 
					        "share": "Delen",
 | 
				
			||||||
| 
						 | 
					@ -86,30 +86,47 @@
 | 
				
			||||||
        "time_code": "Tijdcode (in seconden)",
 | 
					        "time_code": "Tijdcode (in seconden)",
 | 
				
			||||||
        "show_chapters": "Hoofdstukken",
 | 
					        "show_chapters": "Hoofdstukken",
 | 
				
			||||||
        "source_code": "Broncode",
 | 
					        "source_code": "Broncode",
 | 
				
			||||||
        "instance_donations": "Instantie donaties",
 | 
					        "instance_donations": "Instantiedonaties",
 | 
				
			||||||
        "reply_count": "{count} antwoorden",
 | 
					        "reply_count": "{count} reacties",
 | 
				
			||||||
        "no_valid_playlists": "Het bestand bevat geen geldige afspeellijsten!",
 | 
					        "no_valid_playlists": "Het bestand bevat geen geldige afspeellijsten!",
 | 
				
			||||||
        "clone_playlist_success": "Dupliceren gelukt!",
 | 
					        "clone_playlist_success": "Dupliceren gelukt!",
 | 
				
			||||||
        "reset_preferences": "Voorkeuren herstellen",
 | 
					        "reset_preferences": "Voorkeuren resetten",
 | 
				
			||||||
        "back_to_home": "Terug naar de start",
 | 
					        "back_to_home": "Terug naar de start",
 | 
				
			||||||
        "minimize_comments_default": "Opmerkingen Standaard Minimaliseren",
 | 
					        "minimize_comments_default": "Opmerkingen standaard minimaliseren",
 | 
				
			||||||
        "delete_account": "Account Verwijderen",
 | 
					        "delete_account": "Account verwijderen",
 | 
				
			||||||
        "logout": "Uitloggen op dit apparaat",
 | 
					        "logout": "Uitloggen op dit apparaat",
 | 
				
			||||||
        "minimize_recommendations_default": "Aanbevelingen Standaard Minimaliseren",
 | 
					        "minimize_recommendations_default": "Aanbevelingen standaard minimaliseren",
 | 
				
			||||||
        "confirm_reset_preferences": "Weet u zeker dat u uw voorkeuren opnieuw wilt instellen?",
 | 
					        "confirm_reset_preferences": "Weet u zeker dat u uw voorkeuren wilt resetten?",
 | 
				
			||||||
        "backup_preferences": "Back-up voorkeuren",
 | 
					        "backup_preferences": "Back-up-voorkeuren",
 | 
				
			||||||
        "invalidate_session": "Uitloggen op alle apparaten",
 | 
					        "invalidate_session": "Uitloggen op alle apparaten",
 | 
				
			||||||
        "different_auth_instance": "Gebruik een andere instantie voor authenticatie",
 | 
					        "different_auth_instance": "Gebruik een andere instantie voor authenticatie",
 | 
				
			||||||
        "with_playlist": "Delen met afspeellijst",
 | 
					        "with_playlist": "Delen met afspeellijst",
 | 
				
			||||||
        "playlist_bookmarked": "Bladwijzer gemaakt",
 | 
					        "playlist_bookmarked": "Bladwijzer gemaakt",
 | 
				
			||||||
        "bookmark_playlist": "Bladwijzer",
 | 
					        "bookmark_playlist": "Bladwijzer",
 | 
				
			||||||
        "skip_automatically": "Automatisch",
 | 
					        "skip_automatically": "Automatisch",
 | 
				
			||||||
        "skip_button_only": "toon de overslaan knop",
 | 
					        "skip_button_only": "Overslaan-knop tonen",
 | 
				
			||||||
        "min_segment_length": "Minimale segmentlengte (in seconden)",
 | 
					        "min_segment_length": "Minimale segmentlengte (in seconden)",
 | 
				
			||||||
        "skip_segment": "segment overslaan",
 | 
					        "skip_segment": "segment overslaan",
 | 
				
			||||||
        "show_less": "Toon minder",
 | 
					        "show_less": "Minder tonen",
 | 
				
			||||||
        "autoplay_next_countdown": "Standaard aftellen tot de volgende video (in seconden)",
 | 
					        "autoplay_next_countdown": "Standaard aftellen tot de volgende video (in seconden)",
 | 
				
			||||||
        "dismiss": "Afwijzen"
 | 
					        "dismiss": "Afwijzen",
 | 
				
			||||||
 | 
					        "enable_dearrow": "DeArrow inschakelen",
 | 
				
			||||||
 | 
					        "playlist_description": "Afspeellijstomschrijving",
 | 
				
			||||||
 | 
					        "cancel": "Annuleren",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Zoeksuggesties tonen",
 | 
				
			||||||
 | 
					        "delete_automatically": "Automatisch verwijderen na",
 | 
				
			||||||
 | 
					        "okay": "Oké",
 | 
				
			||||||
 | 
					        "playlist_name": "Afspeellijstnaam",
 | 
				
			||||||
 | 
					        "edit_playlist": "Afspeellijst bewerken",
 | 
				
			||||||
 | 
					        "create_group": "Groep aanmaken",
 | 
				
			||||||
 | 
					        "group_name": "Groepnaam",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR-code genereren",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Mobiele lay-out voor hoofdstukken",
 | 
				
			||||||
 | 
					        "auto_display_captions": "Ondertiteling automatisch tonen",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importeren uit JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Beeld downloaden",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Privacybeleid",
 | 
				
			||||||
 | 
					        "add_to_group": "Toevoegen aan groep"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "register": "Registreren",
 | 
					        "register": "Registreren",
 | 
				
			||||||
| 
						 | 
					@ -118,32 +135,38 @@
 | 
				
			||||||
        "preferences": "Voorkeuren",
 | 
					        "preferences": "Voorkeuren",
 | 
				
			||||||
        "history": "Geschiedenis",
 | 
					        "history": "Geschiedenis",
 | 
				
			||||||
        "subscriptions": "Abonnementen",
 | 
					        "subscriptions": "Abonnementen",
 | 
				
			||||||
        "trending": "populair",
 | 
					        "trending": "Trending",
 | 
				
			||||||
        "playlists": "Afspeellijsten",
 | 
					        "playlists": "Afspeellijsten",
 | 
				
			||||||
        "account": "profiel",
 | 
					        "account": "Account",
 | 
				
			||||||
        "instance": "Instantie",
 | 
					        "instance": "Instantie",
 | 
				
			||||||
        "player": "Speler",
 | 
					        "player": "Speler",
 | 
				
			||||||
        "livestreams": "Livestreams",
 | 
					        "livestreams": "Livestreams",
 | 
				
			||||||
        "channels": "Kanalen",
 | 
					        "channels": "Kanalen",
 | 
				
			||||||
        "bookmarks": "Bladwijzers"
 | 
					        "bookmarks": "Bladwijzers",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow",
 | 
				
			||||||
 | 
					        "channel_groups": "Kanaalgroepen"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Kijk op {0}"
 | 
					        "watch_on": "Bekijken op {0}",
 | 
				
			||||||
 | 
					        "failed": "Mislukt met foutcode {0}, zie logboeken voor meer informatie"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "videos": "YouTube: Video's",
 | 
					        "videos": "YouTube: Video's",
 | 
				
			||||||
        "channels": "YouTube: Kanalen",
 | 
					        "channels": "YouTube: Kanalen",
 | 
				
			||||||
        "playlists": "YouTube: Afspeellijsten",
 | 
					        "playlists": "YouTube: Afspeellijsten",
 | 
				
			||||||
        "music_songs": "YT Muziek: Liedjes",
 | 
					        "music_songs": "YT Music: Nummers",
 | 
				
			||||||
        "music_videos": "YT Muziek: Video's",
 | 
					        "music_videos": "YT Music: Video's",
 | 
				
			||||||
        "music_albums": "YT Muziek: Albums",
 | 
					        "music_albums": "YT Music: Albums",
 | 
				
			||||||
        "music_playlists": "YT Muziek: Afspeellijsten",
 | 
					        "music_playlists": "YT Music: Afspeellijsten",
 | 
				
			||||||
        "did_you_mean": "Bedoelde u: {0}?",
 | 
					        "did_you_mean": "Bedoelde u: {0}?",
 | 
				
			||||||
        "all": "YouTube: Alles"
 | 
					        "all": "YouTube: Alles",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artiesten"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Gebruikersnaam",
 | 
					        "username": "Gebruikersnaam",
 | 
				
			||||||
        "password": "Wachtwoord"
 | 
					        "password": "Wachtwoord",
 | 
				
			||||||
 | 
					        "password_confirm": "Wachtwoord bevestigen",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Wachtwoorden komen niet overeen!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Video's",
 | 
					        "videos": "Video's",
 | 
				
			||||||
| 
						 | 
					@ -151,17 +174,21 @@
 | 
				
			||||||
        "chapters": "Hoofdstukken",
 | 
					        "chapters": "Hoofdstukken",
 | 
				
			||||||
        "watched": "Gekeken",
 | 
					        "watched": "Gekeken",
 | 
				
			||||||
        "sponsor_segments": "Sponsorsegmenten",
 | 
					        "sponsor_segments": "Sponsorsegmenten",
 | 
				
			||||||
        "ratings_disabled": "Beoordelingen Uitgeschakeld",
 | 
					        "ratings_disabled": "Beoordelingen uitgeschakeld",
 | 
				
			||||||
        "live": "{0} Live",
 | 
					        "live": "{0} live",
 | 
				
			||||||
        "shorts": "Shorts",
 | 
					        "shorts": "Shorts",
 | 
				
			||||||
        "category": "Categorie",
 | 
					        "category": "Categorie",
 | 
				
			||||||
        "all": "Alle"
 | 
					        "all": "Alle",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Horizontaal",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Verticaal",
 | 
				
			||||||
 | 
					        "license": "Licentie",
 | 
				
			||||||
 | 
					        "visibility": "Zichtbaarheid"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "has_cdn": "Heeft CDN?",
 | 
					        "has_cdn": "Heeft CDN?",
 | 
				
			||||||
        "registered_users": "Geregistreerde Gebruikers",
 | 
					        "registered_users": "Geregistreerde gebruikers",
 | 
				
			||||||
        "instance_name": "Instantie Naam",
 | 
					        "instance_name": "Instantienaam",
 | 
				
			||||||
        "instance_locations": "Locaties van Instanties",
 | 
					        "instance_locations": "Instantielocaties",
 | 
				
			||||||
        "version": "Versie",
 | 
					        "version": "Versie",
 | 
				
			||||||
        "up_to_date": "Bijgewerkt?",
 | 
					        "up_to_date": "Bijgewerkt?",
 | 
				
			||||||
        "ssl_score": "SSL-score"
 | 
					        "ssl_score": "SSL-score"
 | 
				
			||||||
| 
						 | 
					@ -169,8 +196,8 @@
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Vastgemaakt door {author}",
 | 
					        "pinned_by": "Vastgemaakt door {author}",
 | 
				
			||||||
        "user_disabled": "Opmerkingen zijn uitgeschakeld in de instellingen.",
 | 
					        "user_disabled": "Opmerkingen zijn uitgeschakeld in de instellingen.",
 | 
				
			||||||
        "loading": "Opmerkingen laden...",
 | 
					        "loading": "Opmerkingen laden…",
 | 
				
			||||||
        "disabled": "Reacties zijn uitgeschakeld door de uploader."
 | 
					        "disabled": "Opmerkingen zijn uitgeschakeld door de uploader."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "preferences_note": "Let op: voorkeuren worden opgeslagen in de lokale opslag van uw browser. Als u uw browsergegevens verwijdert, worden ze opnieuw ingesteld.",
 | 
					        "preferences_note": "Let op: voorkeuren worden opgeslagen in de lokale opslag van uw browser. Als u uw browsergegevens verwijdert, worden ze opnieuw ingesteld.",
 | 
				
			||||||
| 
						 | 
					@ -179,7 +206,12 @@
 | 
				
			||||||
        "page_not_found": "Pagina niet gevonden",
 | 
					        "page_not_found": "Pagina niet gevonden",
 | 
				
			||||||
        "local_storage": "Deze actie vereist lokale opslag, zijn cookies ingeschakeld?",
 | 
					        "local_storage": "Deze actie vereist lokale opslag, zijn cookies ingeschakeld?",
 | 
				
			||||||
        "register_no_email_note": "Een e-mailadres als gebruikersnaam gebruiken wordt afgeraden. Toch doorgaan?",
 | 
					        "register_no_email_note": "Een e-mailadres als gebruikersnaam gebruiken wordt afgeraden. Toch doorgaan?",
 | 
				
			||||||
        "next_video_countdown": "Volgende video afspelen in {0}s"
 | 
					        "next_video_countdown": "Volgende video wordt afgespeeld over {0}s",
 | 
				
			||||||
 | 
					        "days": "{amount} dag(en)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} week/weken",
 | 
				
			||||||
 | 
					        "months": "{amount} maand(en)",
 | 
				
			||||||
 | 
					        "hours": "{amount} uur",
 | 
				
			||||||
 | 
					        "login_note": "Log in met een account dat op deze instantie is aangemaakt."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Geabonneerd op: {0}"
 | 
					        "subscribed_channels_count": "Geabonneerd op: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "subscriptions": "Abonaments",
 | 
					        "subscriptions": "Abonaments",
 | 
				
			||||||
        "playlists": "Listas de lecturas",
 | 
					        "playlists": "Listas de lecturas",
 | 
				
			||||||
        "bookmarks": "Marcapaginas",
 | 
					        "bookmarks": "Marcapaginas",
 | 
				
			||||||
        "channel_groups": "Grops de cadenas"
 | 
					        "channel_groups": "Grops de cadenas",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Agachar sus {0}"
 | 
					        "watch_on": "Veire sus {0}",
 | 
				
			||||||
 | 
					        "failed": "Fracàs amb lo còdi d’error {0}, consultat los jornals d’audit per mai d’informacions"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "S’abonar - {count}",
 | 
					        "subscribe": "S’abonar - {count}",
 | 
				
			||||||
| 
						 | 
					@ -53,7 +55,7 @@
 | 
				
			||||||
        "add_to_playlist": "Apondre a la listra de lectura",
 | 
					        "add_to_playlist": "Apondre a la listra de lectura",
 | 
				
			||||||
        "minimize_comments": "Minimizar los comentaris",
 | 
					        "minimize_comments": "Minimizar los comentaris",
 | 
				
			||||||
        "theme": "Tèma",
 | 
					        "theme": "Tèma",
 | 
				
			||||||
        "language_selection": "Seleccion de la lenga",
 | 
					        "language_selection": "Lenga",
 | 
				
			||||||
        "loop_this_video": "Legir en bocla la vidèo",
 | 
					        "loop_this_video": "Legir en bocla la vidèo",
 | 
				
			||||||
        "reset_preferences": "Restablir las preferéncias",
 | 
					        "reset_preferences": "Restablir las preferéncias",
 | 
				
			||||||
        "auto": "Auto",
 | 
					        "auto": "Auto",
 | 
				
			||||||
| 
						 | 
					@ -75,14 +77,14 @@
 | 
				
			||||||
        "yes": "Òc",
 | 
					        "yes": "Òc",
 | 
				
			||||||
        "no": "Non",
 | 
					        "no": "Non",
 | 
				
			||||||
        "export_to_json": "Exportar en JSON",
 | 
					        "export_to_json": "Exportar en JSON",
 | 
				
			||||||
        "import_from_json": "Importar d’un JSON/CSV",
 | 
					        "import_from_json": "Importar d’un JSON",
 | 
				
			||||||
        "auto_play_next_video": "Legir la vidèo seguenta automaticament",
 | 
					        "auto_play_next_video": "Legir la vidèo seguenta automaticament",
 | 
				
			||||||
        "create_playlist": "Crear una lista de lectura",
 | 
					        "create_playlist": "Crear una lista de lectura",
 | 
				
			||||||
        "delete_playlist": "Levar de la lista de lectura",
 | 
					        "delete_playlist": "Levar de la lista de lectura",
 | 
				
			||||||
        "select_playlist": "Seleccionatz una lista de lectura",
 | 
					        "select_playlist": "Seleccionatz una lista de lectura",
 | 
				
			||||||
        "delete_playlist_confirm": "Suprimir aquesta lista de lectura ?",
 | 
					        "delete_playlist_confirm": "Suprimir aquesta lista de lectura ?",
 | 
				
			||||||
        "clone_playlist": "Clonar la lista de lectura",
 | 
					        "clone_playlist": "Clonar la lista de lectura",
 | 
				
			||||||
        "instance_auth_selection": "Seleccion de l’instància d’autentificacion",
 | 
					        "instance_auth_selection": "Instància d’autentificacion",
 | 
				
			||||||
        "clone_playlist_success": "Clonatge capitat !",
 | 
					        "clone_playlist_success": "Clonatge capitat !",
 | 
				
			||||||
        "follow_link": "Dobrir lo ligam",
 | 
					        "follow_link": "Dobrir lo ligam",
 | 
				
			||||||
        "skip_self_promo": "Sautar la promocion gratuita / autopromocion",
 | 
					        "skip_self_promo": "Sautar la promocion gratuita / autopromocion",
 | 
				
			||||||
| 
						 | 
					@ -92,7 +94,7 @@
 | 
				
			||||||
        "autoplay_video": "Legir automaticament la vidèo",
 | 
					        "autoplay_video": "Legir automaticament la vidèo",
 | 
				
			||||||
        "audio_only": "Sonque àudio",
 | 
					        "audio_only": "Sonque àudio",
 | 
				
			||||||
        "minimize_comments_default": "Minimizar los comentaris per defaut",
 | 
					        "minimize_comments_default": "Minimizar los comentaris per defaut",
 | 
				
			||||||
        "instance_selection": "Seleccion d’instàncias",
 | 
					        "instance_selection": "Instància",
 | 
				
			||||||
        "please_select_playlist": "Seleccionatz una lista de lectura",
 | 
					        "please_select_playlist": "Seleccionatz una lista de lectura",
 | 
				
			||||||
        "show_watch_on_youtube": "Mostrar lo boton « Veire sus YouTube »",
 | 
					        "show_watch_on_youtube": "Mostrar lo boton « Veire sus YouTube »",
 | 
				
			||||||
        "invalidate_session": "Se desconnectar de totes los aparelhs",
 | 
					        "invalidate_session": "Se desconnectar de totes los aparelhs",
 | 
				
			||||||
| 
						 | 
					@ -101,7 +103,7 @@
 | 
				
			||||||
        "with_timecode": "Partejar amb còdi orari",
 | 
					        "with_timecode": "Partejar amb còdi orari",
 | 
				
			||||||
        "piped_link": "Ligam cap a Piped",
 | 
					        "piped_link": "Ligam cap a Piped",
 | 
				
			||||||
        "show_chapters": "Capítols",
 | 
					        "show_chapters": "Capítols",
 | 
				
			||||||
        "country_selection": "Seleccion del país",
 | 
					        "country_selection": "País",
 | 
				
			||||||
        "default_homepage": "Pagina d’acuèlh per defaut",
 | 
					        "default_homepage": "Pagina d’acuèlh per defaut",
 | 
				
			||||||
        "minimize_recommendations_default": "Minimizar las recomandacions per defaut",
 | 
					        "minimize_recommendations_default": "Minimizar las recomandacions per defaut",
 | 
				
			||||||
        "store_watch_history": "Servar l’istoric de visualizacion",
 | 
					        "store_watch_history": "Servar l’istoric de visualizacion",
 | 
				
			||||||
| 
						 | 
					@ -138,7 +140,14 @@
 | 
				
			||||||
        "playlist_name": "Nom de la lista de lectura",
 | 
					        "playlist_name": "Nom de la lista de lectura",
 | 
				
			||||||
        "playlist_description": "Descripcion de la lista de lectura",
 | 
					        "playlist_description": "Descripcion de la lista de lectura",
 | 
				
			||||||
        "auto_display_captions": "Afichatge auto dels sostítols",
 | 
					        "auto_display_captions": "Afichatge auto dels sostítols",
 | 
				
			||||||
        "show_search_suggestions": "Mostrar las suggestions de recèrcas"
 | 
					        "show_search_suggestions": "Mostrar las suggestions de recèrcas",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Disposicion capítols sus mobil",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Activar DeArrow",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importar d’un JSON/CSV",
 | 
				
			||||||
 | 
					        "delete_automatically": "Suprimir automaticament aprèp",
 | 
				
			||||||
 | 
					        "download_frame": "Telecargar fotograma",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Generar un còdi QR",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Politica de confidencialitat"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "instance_locations": "Localizacion de l’instància",
 | 
					        "instance_locations": "Localizacion de l’instància",
 | 
				
			||||||
| 
						 | 
					@ -151,7 +160,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Nom d’utilizaire",
 | 
					        "username": "Nom d’utilizaire",
 | 
				
			||||||
        "password": "Senhal"
 | 
					        "password": "Senhal",
 | 
				
			||||||
 | 
					        "password_confirm": "Confirmar lo senhal",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Los senhals correspondon pas !"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "views": "{views} visualizacions",
 | 
					        "views": "{views} visualizacions",
 | 
				
			||||||
| 
						 | 
					@ -163,7 +174,11 @@
 | 
				
			||||||
        "chapters": "Capítols",
 | 
					        "chapters": "Capítols",
 | 
				
			||||||
        "videos": "Vidèos",
 | 
					        "videos": "Vidèos",
 | 
				
			||||||
        "all": "Totas",
 | 
					        "all": "Totas",
 | 
				
			||||||
        "category": "Categoria"
 | 
					        "category": "Categoria",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Orizontal",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Vertical",
 | 
				
			||||||
 | 
					        "license": "Licéncia",
 | 
				
			||||||
 | 
					        "visibility": "Visibilitat"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "preferences_note": "Nòta : las preferéncias son gardadas dins l’espaci d’emmagazinatge del navegador. La supression de las donadas del navegador las restablirà.",
 | 
					        "preferences_note": "Nòta : las preferéncias son gardadas dins l’espaci d’emmagazinatge del navegador. La supression de las donadas del navegador las restablirà.",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +187,11 @@
 | 
				
			||||||
        "cannot_copy": "Còpia impossibla !",
 | 
					        "cannot_copy": "Còpia impossibla !",
 | 
				
			||||||
        "local_storage": "Aquesta accion requerís lo localStorage, son activats los cookies ?",
 | 
					        "local_storage": "Aquesta accion requerís lo localStorage, son activats los cookies ?",
 | 
				
			||||||
        "register_no_email_note": "Es pas recomandat d’utilizar una adreça electronica coma nom d’utilizaire. Contunhar çaquelà ?",
 | 
					        "register_no_email_note": "Es pas recomandat d’utilizar una adreça electronica coma nom d’utilizaire. Contunhar çaquelà ?",
 | 
				
			||||||
        "next_video_countdown": "La vidèo seguenta començarà d’aquí {0}s"
 | 
					        "next_video_countdown": "La vidèo seguenta començarà d’aquí {0}s",
 | 
				
			||||||
 | 
					        "hours": "{amount} ora(s)",
 | 
				
			||||||
 | 
					        "months": "{amount} mes(es)",
 | 
				
			||||||
 | 
					        "days": "{amount} jorn(s)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} setmana(s)"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "disabled": "L’autor a desactivat los comentaris.",
 | 
					        "disabled": "L’autor a desactivat los comentaris.",
 | 
				
			||||||
| 
						 | 
					@ -189,7 +208,8 @@
 | 
				
			||||||
        "music_songs": "YT Music : Cançons",
 | 
					        "music_songs": "YT Music : Cançons",
 | 
				
			||||||
        "music_videos": "YT Music : Vidèos",
 | 
					        "music_videos": "YT Music : Vidèos",
 | 
				
			||||||
        "music_albums": "YT Music : Albums",
 | 
					        "music_albums": "YT Music : Albums",
 | 
				
			||||||
        "music_playlists": "YT Music : Listas de lectura"
 | 
					        "music_playlists": "YT Music : Listas de lectura",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music : Artistas"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Abonat a : {0}"
 | 
					        "subscribed_channels_count": "Abonat a : {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "ସିଧାପ୍ରସାରଣ ଗୁଡ଼ିକ",
 | 
					        "livestreams": "ସିଧାପ୍ରସାରଣ ଗୁଡ଼ିକ",
 | 
				
			||||||
        "channels": "ସ୍ରୋତ ଗୁଡ଼ିକ",
 | 
					        "channels": "ସ୍ରୋତ ଗୁଡ଼ିକ",
 | 
				
			||||||
        "bookmarks": "ବୁକମାର୍କଗୁଡିକ",
 | 
					        "bookmarks": "ବୁକମାର୍କଗୁଡିକ",
 | 
				
			||||||
        "channel_groups": "ଚ୍ୟାନେଲ୍ ଗୋଷ୍ଠୀଗୁଡିକ"
 | 
					        "channel_groups": "ଚ୍ୟାନେଲ୍ ଗୋଷ୍ଠୀଗୁଡିକ",
 | 
				
			||||||
 | 
					        "dearrow": "ଡି ତୀର"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "{0} ରେ ଦେଖନ୍ତୁ"
 | 
					        "watch_on": "{0} ରେ ଦେଖନ୍ତୁ",
 | 
				
			||||||
 | 
					        "failed": "ତ୍ରୁଟି ସଂକେତ {0} ସହିତ ବିଫଳ, ଅଧିକ ସୂଚନା ପାଇଁ ଲଗଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "ସଦସ୍ୟତା - {count}",
 | 
					        "subscribe": "ସଦସ୍ୟତା - {count}",
 | 
				
			||||||
| 
						 | 
					@ -57,16 +59,16 @@
 | 
				
			||||||
        "autoplay_video": "ଅଟୋପ୍ଲେ ଭିଡିଓ",
 | 
					        "autoplay_video": "ଅଟୋପ୍ଲେ ଭିଡିଓ",
 | 
				
			||||||
        "enabled_codecs": "ସକ୍ଷମ କୋଡେକସ୍ (ଏକାଧିକ)",
 | 
					        "enabled_codecs": "ସକ୍ଷମ କୋଡେକସ୍ (ଏକାଧିକ)",
 | 
				
			||||||
        "audio_only": "କେବଳ ସ୍ୱର",
 | 
					        "audio_only": "କେବଳ ସ୍ୱର",
 | 
				
			||||||
        "language_selection": "ଭାଷା ଚୟନ",
 | 
					        "language_selection": "ଭାଷା",
 | 
				
			||||||
        "show_more": "ଅଧିକ ଦେଖାନ୍ତୁ",
 | 
					        "show_more": "ଅଧିକ ଦେଖାନ୍ତୁ",
 | 
				
			||||||
        "buffering_goal": "ବଫରିଂ ଲକ୍ଷ୍ୟ (ସେକେଣ୍ଡରେ)",
 | 
					        "buffering_goal": "ବଫରିଂ ଲକ୍ଷ୍ୟ (ସେକେଣ୍ଡରେ)",
 | 
				
			||||||
        "country_selection": "ଦେଶ ଚୟନ",
 | 
					        "country_selection": "ଦେଶ",
 | 
				
			||||||
        "minimize_description_default": "ଡିଫଲ୍ଟ ଭାବରେ ବର୍ଣ୍ଣନାକୁ କମ୍ କରନ୍ତୁ",
 | 
					        "minimize_description_default": "ଡିଫଲ୍ଟ ଭାବରେ ବର୍ଣ୍ଣନାକୁ କମ୍ କରନ୍ତୁ",
 | 
				
			||||||
        "store_watch_history": "ଦେଖିଥିବା ଭିଡିଓ ଗୁଡ଼ିକର ଇତିହାସ ରଖନ୍ତୁ",
 | 
					        "store_watch_history": "ଦେଖିଥିବା ଭିଡିଓ ଗୁଡ଼ିକର ଇତିହାସ ରଖନ୍ତୁ",
 | 
				
			||||||
        "instances_list": "ଉଦାହରଣ ତାଲିକା",
 | 
					        "instances_list": "ଉଦାହରଣ ତାଲିକା",
 | 
				
			||||||
        "instance_selection": "ଇନଷ୍ଟାନ୍ସ ଚୟନ",
 | 
					        "instance_selection": "ଇନଷ୍ଟାନ୍ସ",
 | 
				
			||||||
        "yes": "ହଁ",
 | 
					        "yes": "ହଁ",
 | 
				
			||||||
        "import_from_json": "JSON / CSV ରୁ ଆମଦାନୀ କରନ୍ତୁ",
 | 
					        "import_from_json": "JSON ରୁ ଆମଦାନୀ କରନ୍ତୁ",
 | 
				
			||||||
        "no": "ନାହିଁ",
 | 
					        "no": "ନାହିଁ",
 | 
				
			||||||
        "export_to_json": "JSON କୁ ରପ୍ତାନି କରନ୍ତୁ",
 | 
					        "export_to_json": "JSON କୁ ରପ୍ତାନି କରନ୍ତୁ",
 | 
				
			||||||
        "loop_this_video": "ଏହି ଭିଡିଓକୁ ଲୁପ୍ କରନ୍ତୁ",
 | 
					        "loop_this_video": "ଏହି ଭିଡିଓକୁ ଲୁପ୍ କରନ୍ତୁ",
 | 
				
			||||||
| 
						 | 
					@ -98,7 +100,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "ଡିଫଲ୍ଟ ଭାବରେ ସୁପାରିଶକୁ କମ୍ କରନ୍ତୁ",
 | 
					        "minimize_recommendations_default": "ଡିଫଲ୍ଟ ଭାବରେ ସୁପାରିଶକୁ କମ୍ କରନ୍ତୁ",
 | 
				
			||||||
        "invalidate_session": "ସମସ୍ତ ଡିଭାଇସ୍ ରୁ ଲଗଆଉଟ୍ କରନ୍ତୁ",
 | 
					        "invalidate_session": "ସମସ୍ତ ଡିଭାଇସ୍ ରୁ ଲଗଆଉଟ୍ କରନ୍ତୁ",
 | 
				
			||||||
        "download_as_txt": ".Txt ଭାବରେ ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
 | 
					        "download_as_txt": ".Txt ଭାବରେ ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
 | 
				
			||||||
        "instance_auth_selection": "ପ୍ରାମାଣିକିକରଣ ଇନଷ୍ଟାନ୍ସ ଚୟନ",
 | 
					        "instance_auth_selection": "ପ୍ରାମାଣିକିକରଣ ଇନଷ୍ଟାନ୍ସ",
 | 
				
			||||||
        "confirm_reset_preferences": "ଆପଣ ନିଶ୍ଚିତ କି ଆପଣ ଆପଣଙ୍କର ପସନ୍ଦଗୁଡିକ ପୁନଃ ସେଟ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି?",
 | 
					        "confirm_reset_preferences": "ଆପଣ ନିଶ୍ଚିତ କି ଆପଣ ଆପଣଙ୍କର ପସନ୍ଦଗୁଡିକ ପୁନଃ ସେଟ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି?",
 | 
				
			||||||
        "status_page": "ସ୍ଥିତି",
 | 
					        "status_page": "ସ୍ଥିତି",
 | 
				
			||||||
        "different_auth_instance": "ପ୍ରାମାଣିକିକରଣ ପାଇଁ ଏକ ଭିନ୍ନ ଉଦାହରଣ ବ୍ୟବହାର କରନ୍ତୁ",
 | 
					        "different_auth_instance": "ପ୍ରାମାଣିକିକରଣ ପାଇଁ ଏକ ଭିନ୍ନ ଉଦାହରଣ ବ୍ୟବହାର କରନ୍ତୁ",
 | 
				
			||||||
| 
						 | 
					@ -131,7 +133,21 @@
 | 
				
			||||||
        "autoplay_next_countdown": "ପରବର୍ତ୍ତୀ ଭିଡିଓ ପର୍ଯ୍ୟନ୍ତ ଡିଫଲ୍ଟ କାଉଣ୍ଟଡାଉନ୍ (ସେକେଣ୍ଡରେ)",
 | 
					        "autoplay_next_countdown": "ପରବର୍ତ୍ତୀ ଭିଡିଓ ପର୍ଯ୍ୟନ୍ତ ଡିଫଲ୍ଟ କାଉଣ୍ଟଡାଉନ୍ (ସେକେଣ୍ଡରେ)",
 | 
				
			||||||
        "dismiss": "ବରଖାସ୍ତ",
 | 
					        "dismiss": "ବରଖାସ୍ତ",
 | 
				
			||||||
        "create_group": "ଗୋଷ୍ଠୀ ସୃଷ୍ଟି କରନ୍ତୁ",
 | 
					        "create_group": "ଗୋଷ୍ଠୀ ସୃଷ୍ଟି କରନ୍ତୁ",
 | 
				
			||||||
        "group_name": "ଗୋଷ୍ଠୀ ନାମ"
 | 
					        "group_name": "ଗୋଷ୍ଠୀ ନାମ",
 | 
				
			||||||
 | 
					        "enable_dearrow": "DeArrow କୁ ସକ୍ରିୟ କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "ସନ୍ଧାନ ପ୍ରସ୍ତାବଗୁଡ଼ିକୁ ଦର୍ଶାନ୍ତୁ",
 | 
				
			||||||
 | 
					        "delete_automatically": "ଏହା ପରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଅପସାରଣ କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "okay": "ଠିକ୍ ଅଛି",
 | 
				
			||||||
 | 
					        "edit_playlist": "ପ୍ଲେଲିଷ୍ଟକୁ ସମ୍ପାଦନ କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "playlist_name": "ପ୍ଲେ-ଲିଷ୍ଟ ନାମ",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR କୋଡ ସୃଷ୍ଟି କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "ମୋବାଇଲରେ ଅଧ୍ୟାୟ ବିନ୍ୟାସ",
 | 
				
			||||||
 | 
					        "auto_display_captions": "ଶୀର୍ଷକଗୁଡ଼ିକୁ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଦର୍ଶାନ୍ତୁ",
 | 
				
			||||||
 | 
					        "playlist_description": "ପ୍ଲେଲିଷ୍ଟ ବର୍ଣ୍ଣନା",
 | 
				
			||||||
 | 
					        "cancel": "ବାତିଲ କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "JSON/CSV ରୁ ଆମଦାନୀ କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "download_frame": "ଫ୍ରେମକୁ ଆହରଣ କରନ୍ତୁ",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "ଗୋପନୀୟତା ନୀତି"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "loading": "ମନ୍ତବ୍ୟ ଲୋଡ୍ ହେଉଛି ...",
 | 
					        "loading": "ମନ୍ତବ୍ୟ ଲୋଡ୍ ହେଉଛି ...",
 | 
				
			||||||
| 
						 | 
					@ -149,7 +165,11 @@
 | 
				
			||||||
        "chapters": "ଅଧ୍ୟାୟ ଗୁଡ଼ିକ",
 | 
					        "chapters": "ଅଧ୍ୟାୟ ଗୁଡ଼ିକ",
 | 
				
			||||||
        "live": "{0} ସିଧାପ୍ରସାରଣ",
 | 
					        "live": "{0} ସିଧାପ୍ରସାରଣ",
 | 
				
			||||||
        "all": "ସମସ୍ତ",
 | 
					        "all": "ସମସ୍ତ",
 | 
				
			||||||
        "category": "ବର୍ଗ"
 | 
					        "category": "ବର୍ଗ",
 | 
				
			||||||
 | 
					        "visibility": "ଦୃଶ୍ୟମାନତା",
 | 
				
			||||||
 | 
					        "license": "ଲାଇସେନ୍ସ",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "ଭୂସମାନ୍ତର",
 | 
				
			||||||
 | 
					        "chapters_vertical": "ଭୂଲମ୍ବ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "ଆପଣ କହିବାକୁ ଚାହୁଁଛନ୍ତି କି: {0}?",
 | 
					        "did_you_mean": "ଆପଣ କହିବାକୁ ଚାହୁଁଛନ୍ତି କି: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -160,7 +180,8 @@
 | 
				
			||||||
        "videos": "ୟୁଟ୍ୟୁବ୍: ଭିଡିଓଗୁଡିକ",
 | 
					        "videos": "ୟୁଟ୍ୟୁବ୍: ଭିଡିଓଗୁଡିକ",
 | 
				
			||||||
        "channels": "ୟୁଟ୍ୟୁବ୍: ଚ୍ୟାନେଲଗୁଡିକ",
 | 
					        "channels": "ୟୁଟ୍ୟୁବ୍: ଚ୍ୟାନେଲଗୁଡିକ",
 | 
				
			||||||
        "music_songs": "ୟୁଟିଉବ୍ ସଙ୍ଗୀତ: ଗୀତ ଗୁଡ଼ିକ",
 | 
					        "music_songs": "ୟୁଟିଉବ୍ ସଙ୍ଗୀତ: ଗୀତ ଗୁଡ଼ିକ",
 | 
				
			||||||
        "playlists": "ୟୁଟ୍ୟୁବ୍: ପ୍ଲେଲିଷ୍ଟଗୁଡିକ"
 | 
					        "playlists": "ୟୁଟ୍ୟୁବ୍: ପ୍ଲେଲିଷ୍ଟଗୁଡିକ",
 | 
				
			||||||
 | 
					        "music_artists": "ୱାଇଟି ମ୍ୟୁଜିକ: ଆର୍ଟିଷ୍ଟ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "ସଦସ୍ୟତା: {0}"
 | 
					        "subscribed_channels_count": "ସଦସ୍ୟତା: {0}"
 | 
				
			||||||
| 
						 | 
					@ -172,7 +193,11 @@
 | 
				
			||||||
        "page_not_found": "ପୃଷ୍ଠାଟି ମିଳିଲା ନାହିଁ",
 | 
					        "page_not_found": "ପୃଷ୍ଠାଟି ମିଳିଲା ନାହିଁ",
 | 
				
			||||||
        "local_storage": "ଏହି କ୍ରିୟା ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍ ଆବଶ୍ୟକ କରେ, କୁକିଜ୍ ସକ୍ଷମ ଅଛି କି?",
 | 
					        "local_storage": "ଏହି କ୍ରିୟା ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍ ଆବଶ୍ୟକ କରେ, କୁକିଜ୍ ସକ୍ଷମ ଅଛି କି?",
 | 
				
			||||||
        "register_no_email_note": "ଉପଯୋଗକର୍ତ୍ତା ନାମ ଭାବରେ ଏକ ଇ-ମେଲ୍ ବ୍ୟବହାର କରିବା ସୁପାରିଶ କରାଯାଏ ନାହିଁ । ଯେକୌଣସି ପ୍ରକାରେ ଅଗ୍ରଗତି କରନ୍ତୁ?",
 | 
					        "register_no_email_note": "ଉପଯୋଗକର୍ତ୍ତା ନାମ ଭାବରେ ଏକ ଇ-ମେଲ୍ ବ୍ୟବହାର କରିବା ସୁପାରିଶ କରାଯାଏ ନାହିଁ । ଯେକୌଣସି ପ୍ରକାରେ ଅଗ୍ରଗତି କରନ୍ତୁ?",
 | 
				
			||||||
        "next_video_countdown": "{0} ସେକେଣ୍ଡ ରେ ପରବର୍ତ୍ତୀ ଭିଡିଓ ଖେଳିବାକୁ ଆରମ୍ଭ ହେବ"
 | 
					        "next_video_countdown": "{0} ସେକେଣ୍ଡ ରେ ପରବର୍ତ୍ତୀ ଭିଡିଓ ଖେଳିବାକୁ ଆରମ୍ଭ ହେବ",
 | 
				
			||||||
 | 
					        "hours": "{amount} ଘଣ୍ଟା",
 | 
				
			||||||
 | 
					        "days": "{amount} ଦିନ",
 | 
				
			||||||
 | 
					        "weeks": "{amount} ସପ୍ତାହ",
 | 
				
			||||||
 | 
					        "months": "{amount} ମାସ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "instance_name": "ଇନଷ୍ଟାନ୍ସ ନାମ",
 | 
					        "instance_name": "ଇନଷ୍ଟାନ୍ସ ନାମ",
 | 
				
			||||||
| 
						 | 
					@ -185,6 +210,8 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "ପାସୱାର୍ଡ",
 | 
					        "password": "ପାସୱାର୍ଡ",
 | 
				
			||||||
        "username": "ଉପଯୋଗକର୍ତ୍ତା ନାମ"
 | 
					        "username": "ଉପଯୋଗକର୍ତ୍ତା ନାମ",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "ପ୍ରବେଶ ସଂକେତ ମେଳ ଖାଉନାହିଁ!",
 | 
				
			||||||
 | 
					        "password_confirm": "ପ୍ରବେଶ ସଂକେତ ନିଶ୍ଚିତ କରନ୍ତୁ"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "Na żywo",
 | 
					        "livestreams": "Na żywo",
 | 
				
			||||||
        "channels": "Kanały",
 | 
					        "channels": "Kanały",
 | 
				
			||||||
        "bookmarks": "Zakładki",
 | 
					        "bookmarks": "Zakładki",
 | 
				
			||||||
        "channel_groups": "Grupy kanałów"
 | 
					        "channel_groups": "Grupy kanałów",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Obejrzyj na {0}"
 | 
					        "watch_on": "Zobacz na {0}",
 | 
				
			||||||
 | 
					        "failed": "Niepowodzenie z powodu kodu błędu {0}, przejrzyj logi, aby uzyskać więcej informacji"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "Subskrybuj - {count}",
 | 
					        "subscribe": "Subskrybuj - {count}",
 | 
				
			||||||
| 
						 | 
					@ -48,20 +50,20 @@
 | 
				
			||||||
        "audio_only": "Tylko audio",
 | 
					        "audio_only": "Tylko audio",
 | 
				
			||||||
        "default_quality": "Domyślna jakość",
 | 
					        "default_quality": "Domyślna jakość",
 | 
				
			||||||
        "buffering_goal": "Cel buforowania (w sekundach)",
 | 
					        "buffering_goal": "Cel buforowania (w sekundach)",
 | 
				
			||||||
        "country_selection": "Wybór kraju",
 | 
					        "country_selection": "Kraj",
 | 
				
			||||||
        "default_homepage": "Domyślna strona główna",
 | 
					        "default_homepage": "Domyślna strona główna",
 | 
				
			||||||
        "show_comments": "Pokaż komentarze",
 | 
					        "show_comments": "Pokaż komentarze",
 | 
				
			||||||
        "minimize_description_default": "Ukryj opis",
 | 
					        "minimize_description_default": "Ukryj opis",
 | 
				
			||||||
        "store_watch_history": "Zapamiętaj historię oglądania",
 | 
					        "store_watch_history": "Zapamiętaj historię oglądania",
 | 
				
			||||||
        "language_selection": "Wybór języka",
 | 
					        "language_selection": "Język",
 | 
				
			||||||
        "instances_list": "Lista instancji",
 | 
					        "instances_list": "Lista instancji",
 | 
				
			||||||
        "enabled_codecs": "Włączone kodeki (lista wielokrotnego wyboru)",
 | 
					        "enabled_codecs": "Włączone kodeki (lista wielokrotnego wyboru)",
 | 
				
			||||||
        "instance_selection": "Wybór instancji",
 | 
					        "instance_selection": "Instancja",
 | 
				
			||||||
        "show_more": "Pokaż więcej",
 | 
					        "show_more": "Pokaż więcej",
 | 
				
			||||||
        "yes": "Tak",
 | 
					        "yes": "Tak",
 | 
				
			||||||
        "no": "Nie",
 | 
					        "no": "Nie",
 | 
				
			||||||
        "export_to_json": "Eksport do pliku JSON",
 | 
					        "export_to_json": "Eksport do pliku JSON",
 | 
				
			||||||
        "import_from_json": "Import z pliku JSON/CSV",
 | 
					        "import_from_json": "Import z pliku JSON",
 | 
				
			||||||
        "loop_this_video": "Zapętlaj ten film",
 | 
					        "loop_this_video": "Zapętlaj ten film",
 | 
				
			||||||
        "auto_play_next_video": "Autoodtwarzanie następnego filmu",
 | 
					        "auto_play_next_video": "Autoodtwarzanie następnego filmu",
 | 
				
			||||||
        "donations": "Wsparcie",
 | 
					        "donations": "Wsparcie",
 | 
				
			||||||
| 
						 | 
					@ -94,7 +96,7 @@
 | 
				
			||||||
        "documentation": "Dokumentacja",
 | 
					        "documentation": "Dokumentacja",
 | 
				
			||||||
        "instance_donations": "Darowizny na rzecz instancji",
 | 
					        "instance_donations": "Darowizny na rzecz instancji",
 | 
				
			||||||
        "back_to_home": "Idź do strony głównej",
 | 
					        "back_to_home": "Idź do strony głównej",
 | 
				
			||||||
        "instance_auth_selection": "Wybrana instancja autoryzacyjna",
 | 
					        "instance_auth_selection": "Instancja uwierzytelniania",
 | 
				
			||||||
        "time_code": "Kod czasowy (w sekundach)",
 | 
					        "time_code": "Kod czasowy (w sekundach)",
 | 
				
			||||||
        "show_markers": "Pokaż segmenty na odtwarzaczu",
 | 
					        "show_markers": "Pokaż segmenty na odtwarzaczu",
 | 
				
			||||||
        "store_search_history": "Zapamiętaj historię wyszukiwania",
 | 
					        "store_search_history": "Zapamiętaj historię wyszukiwania",
 | 
				
			||||||
| 
						 | 
					@ -139,7 +141,14 @@
 | 
				
			||||||
        "okay": "OK",
 | 
					        "okay": "OK",
 | 
				
			||||||
        "cancel": "Anuluj",
 | 
					        "cancel": "Anuluj",
 | 
				
			||||||
        "show_search_suggestions": "Pokaż sugestie wyszukiwania",
 | 
					        "show_search_suggestions": "Pokaż sugestie wyszukiwania",
 | 
				
			||||||
        "chapters_layout_mobile": "Układ rozdziałów na urządzeniach mobilnych"
 | 
					        "chapters_layout_mobile": "Układ rozdziałów na urządzeniach mobilnych",
 | 
				
			||||||
 | 
					        "delete_automatically": "Usuń automatycznie po",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Włącz DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Wygeneruj kod QR",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Import z pliku JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Pobierz klatkę",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Polityki prywatności",
 | 
				
			||||||
 | 
					        "add_to_group": "Dodaj do grupy"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Przypięty przez {author}",
 | 
					        "pinned_by": "Przypięty przez {author}",
 | 
				
			||||||
| 
						 | 
					@ -158,7 +167,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Nazwa użytkownika",
 | 
					        "username": "Nazwa użytkownika",
 | 
				
			||||||
        "password": "Hasło"
 | 
					        "password": "Hasło",
 | 
				
			||||||
 | 
					        "password_confirm": "Potwierdź hasło",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Hasła się nie zgadzają!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Filmy",
 | 
					        "videos": "Filmy",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +183,9 @@
 | 
				
			||||||
        "all": "Wszystkie",
 | 
					        "all": "Wszystkie",
 | 
				
			||||||
        "category": "Kategoria",
 | 
					        "category": "Kategoria",
 | 
				
			||||||
        "chapters_horizontal": "Poziomy",
 | 
					        "chapters_horizontal": "Poziomy",
 | 
				
			||||||
        "chapters_vertical": "Pionowy"
 | 
					        "chapters_vertical": "Pionowy",
 | 
				
			||||||
 | 
					        "license": "Licencja",
 | 
				
			||||||
 | 
					        "visibility": "Widoczność"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Czy chodziło ci o: {0}?",
 | 
					        "did_you_mean": "Czy chodziło ci o: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Utwory",
 | 
					        "music_songs": "YT Music: Utwory",
 | 
				
			||||||
        "music_videos": "YT Music: Teledyski",
 | 
					        "music_videos": "YT Music: Teledyski",
 | 
				
			||||||
        "music_albums": "YT Music: Albumy",
 | 
					        "music_albums": "YT Music: Albumy",
 | 
				
			||||||
        "music_playlists": "YT Music: Playlisty"
 | 
					        "music_playlists": "YT Music: Playlisty",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Wykonawcy"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "cannot_copy": "Nie można skopiować!",
 | 
					        "cannot_copy": "Nie można skopiować!",
 | 
				
			||||||
| 
						 | 
					@ -192,7 +206,13 @@
 | 
				
			||||||
        "preferences_note": "Uwaga: ustawienia są zapisywane w lokalnej pamięci przeglądarki. Usunięcie danych przeglądarki spowoduje ich zresetowanie.",
 | 
					        "preferences_note": "Uwaga: ustawienia są zapisywane w lokalnej pamięci przeglądarki. Usunięcie danych przeglądarki spowoduje ich zresetowanie.",
 | 
				
			||||||
        "local_storage": "Ta akcja wymaga dostępu do lokalnej pamięci, czy pliki cookie są włączone?",
 | 
					        "local_storage": "Ta akcja wymaga dostępu do lokalnej pamięci, czy pliki cookie są włączone?",
 | 
				
			||||||
        "register_no_email_note": "Użycie adresu email jako nazwy użytkownika jest niezalecane. Kontynuować mimo to?",
 | 
					        "register_no_email_note": "Użycie adresu email jako nazwy użytkownika jest niezalecane. Kontynuować mimo to?",
 | 
				
			||||||
        "next_video_countdown": "Odtwarzanie następnego filmu za {0} s"
 | 
					        "next_video_countdown": "Odtwarzanie następnego filmu za {0} s",
 | 
				
			||||||
 | 
					        "days": "{amount} dni",
 | 
				
			||||||
 | 
					        "weeks": "{amount} tygodnie",
 | 
				
			||||||
 | 
					        "hours": "{amount} godziny",
 | 
				
			||||||
 | 
					        "months": "{amount} miesiące",
 | 
				
			||||||
 | 
					        "login_note": "Zaloguj się na konto utworzone w tej instancji.",
 | 
				
			||||||
 | 
					        "register_note": "Zarejestruj konto dla tej instancji Piped. Umożliwi to synchronizację subskrypcji i list odtwarzania ze swoim kontem, dzięki czemu będą one przechowywane po stronie serwera. Możesz korzystać ze wszystkich funkcji bez konta, ale wszystkie dane będą przechowywane w lokalnej pamięci podręcznej Twojej przeglądarki. Upewnij się, że NIE używasz adresu e-mail jako nazwy użytkownika i wybierz bezpieczne hasło, którego nie używasz gdzie indziej."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Licznik subskrybcji: {0}"
 | 
					        "subscribed_channels_count": "Licznik subskrybcji: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,8 @@
 | 
				
			||||||
        "livestreams": "Emissões em direto",
 | 
					        "livestreams": "Emissões em direto",
 | 
				
			||||||
        "channels": "Canais",
 | 
					        "channels": "Canais",
 | 
				
			||||||
        "bookmarks": "Marcadores",
 | 
					        "bookmarks": "Marcadores",
 | 
				
			||||||
        "channel_groups": "Grupos de canais"
 | 
					        "channel_groups": "Grupos de canais",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "sort_by": "Ordenar por:",
 | 
					        "sort_by": "Ordenar por:",
 | 
				
			||||||
| 
						 | 
					@ -32,16 +33,16 @@
 | 
				
			||||||
        "autoplay_video": "Reproduzir vídeos automaticamente",
 | 
					        "autoplay_video": "Reproduzir vídeos automaticamente",
 | 
				
			||||||
        "audio_only": "Apenas áudio",
 | 
					        "audio_only": "Apenas áudio",
 | 
				
			||||||
        "default_quality": "Qualidade padrão",
 | 
					        "default_quality": "Qualidade padrão",
 | 
				
			||||||
        "country_selection": "Seleção de país",
 | 
					        "country_selection": "País",
 | 
				
			||||||
        "default_homepage": "Página inicial padrão",
 | 
					        "default_homepage": "Página inicial padrão",
 | 
				
			||||||
        "show_comments": "Mostrar comentários",
 | 
					        "show_comments": "Mostrar comentários",
 | 
				
			||||||
        "minimize_description_default": "Minimizar descrição por omissão",
 | 
					        "minimize_description_default": "Minimizar descrição por omissão",
 | 
				
			||||||
        "store_watch_history": "Guardar histórico de visualizações",
 | 
					        "store_watch_history": "Guardar histórico de visualizações",
 | 
				
			||||||
        "instances_list": "Lista de instâncias",
 | 
					        "instances_list": "Lista de instâncias",
 | 
				
			||||||
        "enabled_codecs": "Codificadores ativados (vários)",
 | 
					        "enabled_codecs": "Codificadores ativados (vários)",
 | 
				
			||||||
        "instance_selection": "Seleção de instância",
 | 
					        "instance_selection": "Instância",
 | 
				
			||||||
        "show_more": "Mostrar mais",
 | 
					        "show_more": "Mostrar mais",
 | 
				
			||||||
        "import_from_json": "Importar de JSON/CSV",
 | 
					        "import_from_json": "Importar de JSON",
 | 
				
			||||||
        "export_to_json": "Exportar para JSON",
 | 
					        "export_to_json": "Exportar para JSON",
 | 
				
			||||||
        "loop_this_video": "Repetir este vídeo",
 | 
					        "loop_this_video": "Repetir este vídeo",
 | 
				
			||||||
        "auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
 | 
					        "auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
 | 
				
			||||||
| 
						 | 
					@ -65,7 +66,7 @@
 | 
				
			||||||
        "skip_non_music": "Ignorar música: secção não musical",
 | 
					        "skip_non_music": "Ignorar música: secção não musical",
 | 
				
			||||||
        "no": "Não",
 | 
					        "no": "Não",
 | 
				
			||||||
        "theme": "Tema",
 | 
					        "theme": "Tema",
 | 
				
			||||||
        "language_selection": "Seleção de idioma",
 | 
					        "language_selection": "Idioma",
 | 
				
			||||||
        "minimize_recommendations": "Minimizar recomendações",
 | 
					        "minimize_recommendations": "Minimizar recomendações",
 | 
				
			||||||
        "light": "Claro",
 | 
					        "light": "Claro",
 | 
				
			||||||
        "hide_replies": "Ocultar respostas",
 | 
					        "hide_replies": "Ocultar respostas",
 | 
				
			||||||
| 
						 | 
					@ -91,7 +92,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "Minimizar recomendações por omissão",
 | 
					        "minimize_recommendations_default": "Minimizar recomendações por omissão",
 | 
				
			||||||
        "invalidate_session": "Terminar sessão em todos os dispositivos",
 | 
					        "invalidate_session": "Terminar sessão em todos os dispositivos",
 | 
				
			||||||
        "different_auth_instance": "Usar uma instância diferente para autenticação",
 | 
					        "different_auth_instance": "Usar uma instância diferente para autenticação",
 | 
				
			||||||
        "instance_auth_selection": "Seleção da instância para autenticação",
 | 
					        "instance_auth_selection": "Instância para autenticação",
 | 
				
			||||||
        "confirm_reset_preferences": "Tem a certeza de que deseja repor as preferências?",
 | 
					        "confirm_reset_preferences": "Tem a certeza de que deseja repor as preferências?",
 | 
				
			||||||
        "download_as_txt": "Descarregar como .txt",
 | 
					        "download_as_txt": "Descarregar como .txt",
 | 
				
			||||||
        "reset_preferences": "Repor preferências",
 | 
					        "reset_preferences": "Repor preferências",
 | 
				
			||||||
| 
						 | 
					@ -136,7 +137,14 @@
 | 
				
			||||||
        "edit_playlist": "Editar lista de reprodução",
 | 
					        "edit_playlist": "Editar lista de reprodução",
 | 
				
			||||||
        "playlist_name": "Nome da lista de reprodução",
 | 
					        "playlist_name": "Nome da lista de reprodução",
 | 
				
			||||||
        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
					        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
				
			||||||
        "chapters_layout_mobile": "Aplicações recentemente adicionadas"
 | 
					        "chapters_layout_mobile": "Aplicações recentemente adicionadas",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Ativar o DeArrow",
 | 
				
			||||||
 | 
					        "delete_automatically": "Eliminar automaticamente após",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Gerar código QR",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importar de JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Quadro de transferência",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Política de privacidade",
 | 
				
			||||||
 | 
					        "add_to_group": "Adicionar ao grupo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "instance_name": "Nome da instância",
 | 
					        "instance_name": "Nome da instância",
 | 
				
			||||||
| 
						 | 
					@ -149,7 +157,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "Palavra-passe",
 | 
					        "password": "Palavra-passe",
 | 
				
			||||||
        "username": "Nome de utilizador"
 | 
					        "username": "Nome de utilizador",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "As palavras-passe não coincidem!",
 | 
				
			||||||
 | 
					        "password_confirm": "Confirmar a palavra-passe"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Vídeos",
 | 
					        "videos": "Vídeos",
 | 
				
			||||||
| 
						 | 
					@ -163,7 +173,9 @@
 | 
				
			||||||
        "all": "Todos",
 | 
					        "all": "Todos",
 | 
				
			||||||
        "category": "Categoria",
 | 
					        "category": "Categoria",
 | 
				
			||||||
        "chapters_horizontal": "Horizontal",
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
        "chapters_vertical": "Vertical"
 | 
					        "chapters_vertical": "Vertical",
 | 
				
			||||||
 | 
					        "license": "Licença",
 | 
				
			||||||
 | 
					        "visibility": "Visibilidade"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Será que queria dizer: {0}?",
 | 
					        "did_you_mean": "Será que queria dizer: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -174,10 +186,12 @@
 | 
				
			||||||
        "music_videos": "YT Music: Vídeos",
 | 
					        "music_videos": "YT Music: Vídeos",
 | 
				
			||||||
        "music_albums": "YT Music: Álbuns",
 | 
					        "music_albums": "YT Music: Álbuns",
 | 
				
			||||||
        "music_playlists": "YT Music: Listas de reprodução",
 | 
					        "music_playlists": "YT Music: Listas de reprodução",
 | 
				
			||||||
        "playlists": "YouTube: Listas de reprodução"
 | 
					        "playlists": "YouTube: Listas de reprodução",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artistas"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Ver em {0}"
 | 
					        "watch_on": "Ver em {0}",
 | 
				
			||||||
 | 
					        "failed": "Falha com o código de erro {0}, ver registos para mais informações"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Afixado por {author}",
 | 
					        "pinned_by": "Afixado por {author}",
 | 
				
			||||||
| 
						 | 
					@ -195,6 +209,12 @@
 | 
				
			||||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
					        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
				
			||||||
        "preferences_note": "Nota: as preferências são guardadas no armazenamento local do seu navegador. Se limpar os dados de navegação, também limpa as preferências.",
 | 
					        "preferences_note": "Nota: as preferências são guardadas no armazenamento local do seu navegador. Se limpar os dados de navegação, também limpa as preferências.",
 | 
				
			||||||
        "register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
 | 
					        "register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
 | 
				
			||||||
        "next_video_countdown": "O próximo vídeo será reproduzido dentro de {0} segundos"
 | 
					        "next_video_countdown": "O próximo vídeo será reproduzido dentro de {0} segundos",
 | 
				
			||||||
 | 
					        "hours": "{quantidade} hora(s)",
 | 
				
			||||||
 | 
					        "days": "{quantidade} dia(s)",
 | 
				
			||||||
 | 
					        "weeks": "{quantidade} semana(s)",
 | 
				
			||||||
 | 
					        "months": "{quantidade} mês(es)",
 | 
				
			||||||
 | 
					        "login_note": "Inicie sessão com uma conta criada nesta instância.",
 | 
				
			||||||
 | 
					        "register_note": "Registar uma conta para esta instância do Piped. Isso permitirá que você sincronize suas assinaturas e listas de reprodução com sua conta, para que elas sejam armazenadas no lado do servidor. Você pode usar todos os recursos sem uma conta, mas todos os dados serão armazenados no cache local do seu navegador. Certifique-se de que NÃO utiliza um endereço de e-mail como nome de utilizador e escolha uma palavra-passe segura que não utilize noutros locais."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@
 | 
				
			||||||
        "dark": "Escuro",
 | 
					        "dark": "Escuro",
 | 
				
			||||||
        "light": "Claro",
 | 
					        "light": "Claro",
 | 
				
			||||||
        "show_comments": "Exibir Comentários",
 | 
					        "show_comments": "Exibir Comentários",
 | 
				
			||||||
        "country_selection": "Seleção de País",
 | 
					        "country_selection": "País",
 | 
				
			||||||
        "default_homepage": "Página Inicial Padrão",
 | 
					        "default_homepage": "Página Inicial Padrão",
 | 
				
			||||||
        "default_quality": "Qualidade Padrão",
 | 
					        "default_quality": "Qualidade Padrão",
 | 
				
			||||||
        "autoplay_video": "Reprodução Automática",
 | 
					        "autoplay_video": "Reprodução Automática",
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@
 | 
				
			||||||
        "skip_non_music": "Pular Música: Seção não Musical",
 | 
					        "skip_non_music": "Pular Música: Seção não Musical",
 | 
				
			||||||
        "skip_filler_tangent": "Pular Enchimento Tangencial",
 | 
					        "skip_filler_tangent": "Pular Enchimento Tangencial",
 | 
				
			||||||
        "enabled_codecs": "Codecs Ativados (Múltiplos)",
 | 
					        "enabled_codecs": "Codecs Ativados (Múltiplos)",
 | 
				
			||||||
        "language_selection": "Seleção de Idioma",
 | 
					        "language_selection": "Idioma",
 | 
				
			||||||
        "yes": "Sim",
 | 
					        "yes": "Sim",
 | 
				
			||||||
        "show_more": "Mostrar Mais",
 | 
					        "show_more": "Mostrar Mais",
 | 
				
			||||||
        "export_to_json": "Exportar para JSON",
 | 
					        "export_to_json": "Exportar para JSON",
 | 
				
			||||||
| 
						 | 
					@ -55,14 +55,14 @@
 | 
				
			||||||
        "view_ssl_score": "Ver Pontuação SSL",
 | 
					        "view_ssl_score": "Ver Pontuação SSL",
 | 
				
			||||||
        "disable_lbry": "Desativar LBRY para Streaming",
 | 
					        "disable_lbry": "Desativar LBRY para Streaming",
 | 
				
			||||||
        "enable_lbry_proxy": "Ativar Proxy para LBRY",
 | 
					        "enable_lbry_proxy": "Ativar Proxy para LBRY",
 | 
				
			||||||
        "import_from_json": "Importar de JSON/CSV",
 | 
					        "import_from_json": "Importar de JSON",
 | 
				
			||||||
        "loop_this_video": "Repetir este Vídeo",
 | 
					        "loop_this_video": "Repetir este Vídeo",
 | 
				
			||||||
        "instances_list": "Lista de Instâncias",
 | 
					        "instances_list": "Lista de Instâncias",
 | 
				
			||||||
        "clear_history": "Limpar Histórico",
 | 
					        "clear_history": "Limpar Histórico",
 | 
				
			||||||
        "search": "Pesquisar (Ctrl+K)",
 | 
					        "search": "Pesquisar (Ctrl+K)",
 | 
				
			||||||
        "no": "Não",
 | 
					        "no": "Não",
 | 
				
			||||||
        "show_description": "Exibir Descrição",
 | 
					        "show_description": "Exibir Descrição",
 | 
				
			||||||
        "instance_selection": "Seleção de Instância",
 | 
					        "instance_selection": "Instância",
 | 
				
			||||||
        "auto_play_next_video": "Autorreproduzir Próximo Vídeo",
 | 
					        "auto_play_next_video": "Autorreproduzir Próximo Vídeo",
 | 
				
			||||||
        "filter": "Filtro",
 | 
					        "filter": "Filtro",
 | 
				
			||||||
        "store_watch_history": "Salvar Histórico de Exibição",
 | 
					        "store_watch_history": "Salvar Histórico de Exibição",
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,8 @@
 | 
				
			||||||
        "status_page": "Estado",
 | 
					        "status_page": "Estado",
 | 
				
			||||||
        "source_code": "Código fonte",
 | 
					        "source_code": "Código fonte",
 | 
				
			||||||
        "instance_donations": "Doações de instâncias",
 | 
					        "instance_donations": "Doações de instâncias",
 | 
				
			||||||
        "instance_auth_selection": "Seleção de Instância de Autenticação",
 | 
					        "instance_privacy_policy": "Política de Privacidade",
 | 
				
			||||||
 | 
					        "instance_auth_selection": "Instância de Autenticação",
 | 
				
			||||||
        "clone_playlist_success": "Clonada com sucesso!",
 | 
					        "clone_playlist_success": "Clonada com sucesso!",
 | 
				
			||||||
        "download_as_txt": "Baixar como .txt",
 | 
					        "download_as_txt": "Baixar como .txt",
 | 
				
			||||||
        "restore_preferences": "Restaurar preferências",
 | 
					        "restore_preferences": "Restaurar preferências",
 | 
				
			||||||
| 
						 | 
					@ -119,7 +120,13 @@
 | 
				
			||||||
        "create_group": "Criar grupo",
 | 
					        "create_group": "Criar grupo",
 | 
				
			||||||
        "group_name": "Nome do grupo",
 | 
					        "group_name": "Nome do grupo",
 | 
				
			||||||
        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
					        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
				
			||||||
        "chapters_layout_mobile": "Layout dos Capítulos no Celular"
 | 
					        "chapters_layout_mobile": "Layout dos Capítulos no Celular",
 | 
				
			||||||
 | 
					        "delete_automatically": "Deletar automaticamente após",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Gerar código QR",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Ativar DeArrow",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importar de JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Baixar quadro",
 | 
				
			||||||
 | 
					        "add_to_group": "Adicionar ao grupo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "history": "Histórico",
 | 
					        "history": "Histórico",
 | 
				
			||||||
| 
						 | 
					@ -136,10 +143,12 @@
 | 
				
			||||||
        "channels": "Canais",
 | 
					        "channels": "Canais",
 | 
				
			||||||
        "livestreams": "Transmissões ao vivo",
 | 
					        "livestreams": "Transmissões ao vivo",
 | 
				
			||||||
        "bookmarks": "Favoritos",
 | 
					        "bookmarks": "Favoritos",
 | 
				
			||||||
        "channel_groups": "Grupos de Canais"
 | 
					        "channel_groups": "Grupos de Canais",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Assistir no {0}"
 | 
					        "watch_on": "Ver em {0}",
 | 
				
			||||||
 | 
					        "failed": "Falhou com código de erro {0}, veja os logs para mais informações"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Fixado por {author}",
 | 
					        "pinned_by": "Fixado por {author}",
 | 
				
			||||||
| 
						 | 
					@ -158,7 +167,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Nome de usuário",
 | 
					        "username": "Nome de usuário",
 | 
				
			||||||
        "password": "Senha"
 | 
					        "password": "Senha",
 | 
				
			||||||
 | 
					        "password_confirm": "Confirme senha",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "As senhas não correspondem!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Vídeos",
 | 
					        "videos": "Vídeos",
 | 
				
			||||||
| 
						 | 
					@ -172,7 +183,9 @@
 | 
				
			||||||
        "all": "Todos",
 | 
					        "all": "Todos",
 | 
				
			||||||
        "category": "Categoria",
 | 
					        "category": "Categoria",
 | 
				
			||||||
        "chapters_horizontal": "Horizontal",
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
        "chapters_vertical": "Vertical"
 | 
					        "chapters_vertical": "Vertical",
 | 
				
			||||||
 | 
					        "license": "Licença",
 | 
				
			||||||
 | 
					        "visibility": "Visibilidade"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Você quis dizer: {0}?",
 | 
					        "did_you_mean": "Você quis dizer: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +196,8 @@
 | 
				
			||||||
        "music_videos": "YT Music: Vídeos",
 | 
					        "music_videos": "YT Music: Vídeos",
 | 
				
			||||||
        "music_albums": "YT Music: Álbuns",
 | 
					        "music_albums": "YT Music: Álbuns",
 | 
				
			||||||
        "music_playlists": "YT Music: Playlists",
 | 
					        "music_playlists": "YT Music: Playlists",
 | 
				
			||||||
        "all": "YouTube: Tudo"
 | 
					        "all": "YouTube: Tudo",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artistas"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "copied": "Copiado!",
 | 
					        "copied": "Copiado!",
 | 
				
			||||||
| 
						 | 
					@ -192,7 +206,13 @@
 | 
				
			||||||
        "page_not_found": "página não encontrada",
 | 
					        "page_not_found": "página não encontrada",
 | 
				
			||||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
					        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
				
			||||||
        "register_no_email_note": "Usar um e-mail como nome de usuário não é recomendado. Continuar mesmo assim?",
 | 
					        "register_no_email_note": "Usar um e-mail como nome de usuário não é recomendado. Continuar mesmo assim?",
 | 
				
			||||||
        "next_video_countdown": "Reproduzindo o próximo vídeo em {0}s"
 | 
					        "next_video_countdown": "Reproduzindo o próximo vídeo em {0}s",
 | 
				
			||||||
 | 
					        "hours": "{amount} hora(s)",
 | 
				
			||||||
 | 
					        "days": "{amount} dia(s)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} semana(s)",
 | 
				
			||||||
 | 
					        "months": "{amount} mês/meses",
 | 
				
			||||||
 | 
					        "login_note": "Entre com uma conta criada nesta instância.",
 | 
				
			||||||
 | 
					        "register_note": "Registre uma conta para esta instância Piped. Isto irá permitir que você sincronize suas inscrições e playlists com sua conta, para que sejam armazenadas no lado do servidor. Você pode usar todas as funções sem uma conta, mas todos os dados serão armazenados no cache local do seu navegador. Por favor certifique-se de NÃO usar seu endereço de e-mail como nome de usuário e de escolher uma senha segura que não use em nenhum outro lugar."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Inscrito em: {0}"
 | 
					        "subscribed_channels_count": "Inscrito em: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,8 @@
 | 
				
			||||||
        "livestreams": "Emissões em direto",
 | 
					        "livestreams": "Emissões em direto",
 | 
				
			||||||
        "channels": "Canais",
 | 
					        "channels": "Canais",
 | 
				
			||||||
        "bookmarks": "Marcadores",
 | 
					        "bookmarks": "Marcadores",
 | 
				
			||||||
        "channel_groups": "Grupos de canais"
 | 
					        "channel_groups": "Grupos de canais",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "view_subscriptions": "Ver subscrições",
 | 
					        "view_subscriptions": "Ver subscrições",
 | 
				
			||||||
| 
						 | 
					@ -39,14 +40,14 @@
 | 
				
			||||||
        "dark": "Escuro",
 | 
					        "dark": "Escuro",
 | 
				
			||||||
        "light": "Claro",
 | 
					        "light": "Claro",
 | 
				
			||||||
        "buffering_goal": "Objetivo de 'buffer' (em segundos)",
 | 
					        "buffering_goal": "Objetivo de 'buffer' (em segundos)",
 | 
				
			||||||
        "country_selection": "Seleção de país",
 | 
					        "country_selection": "País",
 | 
				
			||||||
        "default_homepage": "Página inicial padrão",
 | 
					        "default_homepage": "Página inicial padrão",
 | 
				
			||||||
        "show_comments": "Mostrar comentários",
 | 
					        "show_comments": "Mostrar comentários",
 | 
				
			||||||
        "minimize_description_default": "Minimizar descrição por omissão",
 | 
					        "minimize_description_default": "Minimizar descrição por omissão",
 | 
				
			||||||
        "store_watch_history": "Guardar histórico de visualizações",
 | 
					        "store_watch_history": "Guardar histórico de visualizações",
 | 
				
			||||||
        "language_selection": "Seleção de idioma",
 | 
					        "language_selection": "Idioma",
 | 
				
			||||||
        "enabled_codecs": "Codificadores ativados (vários)",
 | 
					        "enabled_codecs": "Codificadores ativados (vários)",
 | 
				
			||||||
        "instance_selection": "Seleção de instância",
 | 
					        "instance_selection": "Instância",
 | 
				
			||||||
        "show_more": "Mostrar mais",
 | 
					        "show_more": "Mostrar mais",
 | 
				
			||||||
        "import_from_json": "Importar de JSON/CSV",
 | 
					        "import_from_json": "Importar de JSON/CSV",
 | 
				
			||||||
        "loop_this_video": "Repetir este vídeo",
 | 
					        "loop_this_video": "Repetir este vídeo",
 | 
				
			||||||
| 
						 | 
					@ -89,7 +90,7 @@
 | 
				
			||||||
        "logout": "Terminar sessão neste dispositivo",
 | 
					        "logout": "Terminar sessão neste dispositivo",
 | 
				
			||||||
        "minimize_recommendations_default": "Minimizar recomendações por omissão",
 | 
					        "minimize_recommendations_default": "Minimizar recomendações por omissão",
 | 
				
			||||||
        "different_auth_instance": "Usar uma instância diferente para autenticação",
 | 
					        "different_auth_instance": "Usar uma instância diferente para autenticação",
 | 
				
			||||||
        "instance_auth_selection": "Seleção da instância para autenticação",
 | 
					        "instance_auth_selection": "Instância de autenticação",
 | 
				
			||||||
        "invalidate_session": "Terminar sessão em todos os dispositivos",
 | 
					        "invalidate_session": "Terminar sessão em todos os dispositivos",
 | 
				
			||||||
        "clone_playlist": "Clonar lista de reprodução",
 | 
					        "clone_playlist": "Clonar lista de reprodução",
 | 
				
			||||||
        "clone_playlist_success": "Clonada com sucesso!",
 | 
					        "clone_playlist_success": "Clonada com sucesso!",
 | 
				
			||||||
| 
						 | 
					@ -105,7 +106,7 @@
 | 
				
			||||||
        "backup_preferences": "Exportar preferências",
 | 
					        "backup_preferences": "Exportar preferências",
 | 
				
			||||||
        "back_to_home": "Voltar ao início",
 | 
					        "back_to_home": "Voltar ao início",
 | 
				
			||||||
        "minimize_comments_default": "Minimizar comentários por omissão",
 | 
					        "minimize_comments_default": "Minimizar comentários por omissão",
 | 
				
			||||||
        "store_search_history": "Guardar histórico de pesquisas",
 | 
					        "store_search_history": "Histórico de pesquisa da loja",
 | 
				
			||||||
        "minimize_chapters_default": "Minimizar capítulos por omissão",
 | 
					        "minimize_chapters_default": "Minimizar capítulos por omissão",
 | 
				
			||||||
        "show_watch_on_youtube": "Mostrar botão Ver no YouTube",
 | 
					        "show_watch_on_youtube": "Mostrar botão Ver no YouTube",
 | 
				
			||||||
        "show_chapters": "Capítulos",
 | 
					        "show_chapters": "Capítulos",
 | 
				
			||||||
| 
						 | 
					@ -136,7 +137,9 @@
 | 
				
			||||||
        "cancel": "Cancelar",
 | 
					        "cancel": "Cancelar",
 | 
				
			||||||
        "okay": "Ok",
 | 
					        "okay": "Ok",
 | 
				
			||||||
        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
					        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
				
			||||||
        "chapters_layout_mobile": "Aplicações recentemente adicionadas"
 | 
					        "chapters_layout_mobile": "Aplicações recentemente adicionadas",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Ativar o DeArrow",
 | 
				
			||||||
 | 
					        "delete_automatically": "Eliminar automaticamente após"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Afixado por {author}",
 | 
					        "pinned_by": "Afixado por {author}",
 | 
				
			||||||
| 
						 | 
					@ -169,7 +172,9 @@
 | 
				
			||||||
        "all": "Todos",
 | 
					        "all": "Todos",
 | 
				
			||||||
        "category": "Categoria",
 | 
					        "category": "Categoria",
 | 
				
			||||||
        "chapters_horizontal": "Horizontal",
 | 
					        "chapters_horizontal": "Horizontal",
 | 
				
			||||||
        "chapters_vertical": "Vertical"
 | 
					        "chapters_vertical": "Vertical",
 | 
				
			||||||
 | 
					        "license": "Licença",
 | 
				
			||||||
 | 
					        "visibility": "Visibilidade"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Será que queria dizer: {0}?",
 | 
					        "did_you_mean": "Será que queria dizer: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -180,7 +185,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Músicas",
 | 
					        "music_songs": "YT Music: Músicas",
 | 
				
			||||||
        "music_videos": "YT Music: Vídeos",
 | 
					        "music_videos": "YT Music: Vídeos",
 | 
				
			||||||
        "music_albums": "YT Music: Álbuns",
 | 
					        "music_albums": "YT Music: Álbuns",
 | 
				
			||||||
        "music_playlists": "YT Music: Listas de reprodução"
 | 
					        "music_playlists": "YT Music: Listas de reprodução",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artistas"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Ver em {0}"
 | 
					        "watch_on": "Ver em {0}"
 | 
				
			||||||
| 
						 | 
					@ -195,6 +201,10 @@
 | 
				
			||||||
        "cannot_copy": "Não foi possível copiar!",
 | 
					        "cannot_copy": "Não foi possível copiar!",
 | 
				
			||||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
					        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
				
			||||||
        "register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
 | 
					        "register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
 | 
				
			||||||
        "next_video_countdown": "A reproduzir o vídeo seguinte em {0}s"
 | 
					        "next_video_countdown": "A reproduzir o vídeo seguinte em {0}s",
 | 
				
			||||||
 | 
					        "hours": "{quantidade} hora(s)",
 | 
				
			||||||
 | 
					        "days": "{quantidade} dia(s)",
 | 
				
			||||||
 | 
					        "weeks": "{quantidade} semana(s)",
 | 
				
			||||||
 | 
					        "months": "{quantidade} mês(es)"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,11 +32,11 @@
 | 
				
			||||||
        "auto": "Auto",
 | 
					        "auto": "Auto",
 | 
				
			||||||
        "audio_only": "Doar audio",
 | 
					        "audio_only": "Doar audio",
 | 
				
			||||||
        "default_quality": "Calitate implicită",
 | 
					        "default_quality": "Calitate implicită",
 | 
				
			||||||
        "country_selection": "Selecție țară",
 | 
					        "country_selection": "Țară",
 | 
				
			||||||
        "default_homepage": "Pagina principală implicită",
 | 
					        "default_homepage": "Pagina principală implicită",
 | 
				
			||||||
        "minimize_comments_default": "Minimizați comentariile în mod implicit",
 | 
					        "minimize_comments_default": "Minimizați comentariile în mod implicit",
 | 
				
			||||||
        "minimize_description_default": "Minimizați descrierea în mod implicit",
 | 
					        "minimize_description_default": "Minimizați descrierea în mod implicit",
 | 
				
			||||||
        "language_selection": "Selecție limbă",
 | 
					        "language_selection": "Limbă",
 | 
				
			||||||
        "instances_list": "Listă de Instanțe",
 | 
					        "instances_list": "Listă de Instanțe",
 | 
				
			||||||
        "enabled_codecs": "Codecuri activate (multiple)",
 | 
					        "enabled_codecs": "Codecuri activate (multiple)",
 | 
				
			||||||
        "loop_this_video": "Repetare video",
 | 
					        "loop_this_video": "Repetare video",
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@
 | 
				
			||||||
        "delete_account": "Ștergeți-vă contul",
 | 
					        "delete_account": "Ștergeți-vă contul",
 | 
				
			||||||
        "show_watch_on_youtube": "Afișați butonul „Vizionați pe YouTube”",
 | 
					        "show_watch_on_youtube": "Afișați butonul „Vizionați pe YouTube”",
 | 
				
			||||||
        "invalidate_session": "Deconectați toate dispozitivele",
 | 
					        "invalidate_session": "Deconectați toate dispozitivele",
 | 
				
			||||||
        "instance_auth_selection": "Selecție instanță de autentificare",
 | 
					        "instance_auth_selection": "Instanța de autentificare",
 | 
				
			||||||
        "clone_playlist_success": "Clonată cu succes!",
 | 
					        "clone_playlist_success": "Clonată cu succes!",
 | 
				
			||||||
        "reset_preferences": "Resetați preferințele",
 | 
					        "reset_preferences": "Resetați preferințele",
 | 
				
			||||||
        "confirm_reset_preferences": "Sunteți sigur că doriți să vă resetați preferințele?",
 | 
					        "confirm_reset_preferences": "Sunteți sigur că doriți să vă resetați preferințele?",
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@
 | 
				
			||||||
        "theme": "Temă",
 | 
					        "theme": "Temă",
 | 
				
			||||||
        "autoplay_video": "Redare automată video",
 | 
					        "autoplay_video": "Redare automată video",
 | 
				
			||||||
        "buffering_goal": "Obiectiv de tamponare (în secunde)",
 | 
					        "buffering_goal": "Obiectiv de tamponare (în secunde)",
 | 
				
			||||||
        "instance_selection": "Selecție instanță",
 | 
					        "instance_selection": "Instanță",
 | 
				
			||||||
        "store_watch_history": "Rețineți istoricul de vizionări",
 | 
					        "store_watch_history": "Rețineți istoricul de vizionări",
 | 
				
			||||||
        "minimize_comments": "Minimizați comentariile",
 | 
					        "minimize_comments": "Minimizați comentariile",
 | 
				
			||||||
        "minimize_description": "Minimizați descrierea",
 | 
					        "minimize_description": "Minimizați descrierea",
 | 
				
			||||||
| 
						 | 
					@ -112,11 +112,16 @@
 | 
				
			||||||
        "dismiss": "Concediază",
 | 
					        "dismiss": "Concediază",
 | 
				
			||||||
        "group_name": "Numele grupului",
 | 
					        "group_name": "Numele grupului",
 | 
				
			||||||
        "create_group": "Creați un grup",
 | 
					        "create_group": "Creați un grup",
 | 
				
			||||||
        "auto_display_captions": "Afișează automat subtitrările",
 | 
					        "auto_display_captions": "Afișare automată subtitrări",
 | 
				
			||||||
        "playlist_name": "Numele playlist-ului",
 | 
					        "playlist_name": "Numele playlist-ului",
 | 
				
			||||||
        "okay": "Bine",
 | 
					        "okay": "OK",
 | 
				
			||||||
        "playlist_description": "Descrierea playlist-ului",
 | 
					        "playlist_description": "Descrierea playlist-ului",
 | 
				
			||||||
        "edit_playlist": "Editează playlist-ul"
 | 
					        "edit_playlist": "Editează playlist-ul",
 | 
				
			||||||
 | 
					        "cancel": "Anulare",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Mod afișare capitole pe mobil",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Afișare sugestii căutare",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Activați DeArrow",
 | 
				
			||||||
 | 
					        "delete_automatically": "Șterge automat după"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "Scor SSL",
 | 
					        "ssl_score": "Scor SSL",
 | 
				
			||||||
| 
						 | 
					@ -143,7 +148,9 @@
 | 
				
			||||||
        "live": "{0} în direct",
 | 
					        "live": "{0} în direct",
 | 
				
			||||||
        "videos": "Videoclipuri",
 | 
					        "videos": "Videoclipuri",
 | 
				
			||||||
        "category": "Categorie",
 | 
					        "category": "Categorie",
 | 
				
			||||||
        "all": "Tot"
 | 
					        "all": "Tot",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Orizontal",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Vertical"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Nume de utilizator",
 | 
					        "username": "Nume de utilizator",
 | 
				
			||||||
| 
						 | 
					@ -158,7 +165,8 @@
 | 
				
			||||||
        "playlists": "YouTube: Liste de redare",
 | 
					        "playlists": "YouTube: Liste de redare",
 | 
				
			||||||
        "music_songs": "YT Music: Muzică",
 | 
					        "music_songs": "YT Music: Muzică",
 | 
				
			||||||
        "music_videos": "YT Music: Videoclipuri",
 | 
					        "music_videos": "YT Music: Videoclipuri",
 | 
				
			||||||
        "music_albums": "YT Music: Albume"
 | 
					        "music_albums": "YT Music: Albume",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artiști"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "cannot_copy": "Nu s-a putut copia!",
 | 
					        "cannot_copy": "Nu s-a putut copia!",
 | 
				
			||||||
| 
						 | 
					@ -167,7 +175,8 @@
 | 
				
			||||||
        "copied": "Copiat!",
 | 
					        "copied": "Copiat!",
 | 
				
			||||||
        "register_no_email_note": "Utilizarea unui e-mail ca nume de utilizator nu este recomandată. Continuați oricum?",
 | 
					        "register_no_email_note": "Utilizarea unui e-mail ca nume de utilizator nu este recomandată. Continuați oricum?",
 | 
				
			||||||
        "local_storage": "Această acțiune necesită localStorage, sunt activate cookie-urile?",
 | 
					        "local_storage": "Această acțiune necesită localStorage, sunt activate cookie-urile?",
 | 
				
			||||||
        "next_video_countdown": "Redarea următorului videoclip în {0}s"
 | 
					        "next_video_countdown": "Redarea următorului videoclip în {0}s",
 | 
				
			||||||
 | 
					        "days": "{amount} zi(le)"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Abonat la: {0}"
 | 
					        "subscribed_channels_count": "Abonat la: {0}"
 | 
				
			||||||
| 
						 | 
					@ -187,7 +196,8 @@
 | 
				
			||||||
        "preferences": "Preferințe",
 | 
					        "preferences": "Preferințe",
 | 
				
			||||||
        "player": "Player-ul",
 | 
					        "player": "Player-ul",
 | 
				
			||||||
        "bookmarks": "Marcaje",
 | 
					        "bookmarks": "Marcaje",
 | 
				
			||||||
        "channel_groups": "Grupuri de canale"
 | 
					        "channel_groups": "Grupuri de canale",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Vizionați pe {0}"
 | 
					        "watch_on": "Vizionați pe {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
        "livestreams": "Прямые трансляции",
 | 
					        "livestreams": "Прямые трансляции",
 | 
				
			||||||
        "channels": "Каналы",
 | 
					        "channels": "Каналы",
 | 
				
			||||||
        "bookmarks": "Закладки",
 | 
					        "bookmarks": "Закладки",
 | 
				
			||||||
        "channel_groups": "Группы каналов"
 | 
					        "channel_groups": "Группы каналов",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Смотреть на {0}"
 | 
					        "watch_on": "Посмотреть на {0}",
 | 
				
			||||||
 | 
					        "failed": "Ошибка с кодом {0}, откройте логи, чтобы узнать больше"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "Подписаться - {count}",
 | 
					        "subscribe": "Подписаться - {count}",
 | 
				
			||||||
| 
						 | 
					@ -59,7 +61,7 @@
 | 
				
			||||||
        "yes": "Да",
 | 
					        "yes": "Да",
 | 
				
			||||||
        "no": "Нет",
 | 
					        "no": "Нет",
 | 
				
			||||||
        "export_to_json": "Экспорт в JSON",
 | 
					        "export_to_json": "Экспорт в JSON",
 | 
				
			||||||
        "import_from_json": "Импорт из JSON/CSV",
 | 
					        "import_from_json": "Импорт из JSON",
 | 
				
			||||||
        "loop_this_video": "Повтор текущего видео",
 | 
					        "loop_this_video": "Повтор текущего видео",
 | 
				
			||||||
        "auto_play_next_video": "Сразу проигрывать следующее рекомендованное видео",
 | 
					        "auto_play_next_video": "Сразу проигрывать следующее рекомендованное видео",
 | 
				
			||||||
        "donations": "Пожертвования на разработку",
 | 
					        "donations": "Пожертвования на разработку",
 | 
				
			||||||
| 
						 | 
					@ -138,7 +140,15 @@
 | 
				
			||||||
        "okay": "Хорошо",
 | 
					        "okay": "Хорошо",
 | 
				
			||||||
        "auto_display_captions": "Авто-отображение субтитров",
 | 
					        "auto_display_captions": "Авто-отображение субтитров",
 | 
				
			||||||
        "playlist_name": "Название плейлиста",
 | 
					        "playlist_name": "Название плейлиста",
 | 
				
			||||||
        "show_search_suggestions": "Показать поисковые предложения"
 | 
					        "show_search_suggestions": "Показать поисковые предложения",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Расположение глав в мобильном виде",
 | 
				
			||||||
 | 
					        "delete_automatically": "Автоматическое удаление после",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Включить DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Сгенерировать QR Код",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Импорт из JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Сделать скриншот видео",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Политика конфиденциальности",
 | 
				
			||||||
 | 
					        "add_to_group": "Добавить в группу"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Закреплено пользователем {author}",
 | 
					        "pinned_by": "Закреплено пользователем {author}",
 | 
				
			||||||
| 
						 | 
					@ -157,7 +167,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Имя пользователя",
 | 
					        "username": "Имя пользователя",
 | 
				
			||||||
        "password": "Пароль"
 | 
					        "password": "Пароль",
 | 
				
			||||||
 | 
					        "password_confirm": "Повторите пароль",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Пароль не совпадает!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Видео",
 | 
					        "videos": "Видео",
 | 
				
			||||||
| 
						 | 
					@ -169,7 +181,11 @@
 | 
				
			||||||
        "chapters": "Содержание",
 | 
					        "chapters": "Содержание",
 | 
				
			||||||
        "shorts": "Shorts",
 | 
					        "shorts": "Shorts",
 | 
				
			||||||
        "all": "Все",
 | 
					        "all": "Все",
 | 
				
			||||||
        "category": "Категория"
 | 
					        "category": "Категория",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Горизонтально",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Вертикально",
 | 
				
			||||||
 | 
					        "visibility": "Видимость",
 | 
				
			||||||
 | 
					        "license": "Лицензия"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Может быть вы имели в виду: {0}?",
 | 
					        "did_you_mean": "Может быть вы имели в виду: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -180,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Композиции",
 | 
					        "music_songs": "YT Music: Композиции",
 | 
				
			||||||
        "music_videos": "YT Music: Видео",
 | 
					        "music_videos": "YT Music: Видео",
 | 
				
			||||||
        "music_albums": "YT Music: Альбомы",
 | 
					        "music_albums": "YT Music: Альбомы",
 | 
				
			||||||
        "music_playlists": "YT Music: Плейлисты"
 | 
					        "music_playlists": "YT Music: Плейлисты",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Исполнители"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Подписан на: {0}"
 | 
					        "subscribed_channels_count": "Подписан на: {0}"
 | 
				
			||||||
| 
						 | 
					@ -192,6 +209,12 @@
 | 
				
			||||||
        "page_not_found": "Страница не найдена",
 | 
					        "page_not_found": "Страница не найдена",
 | 
				
			||||||
        "local_storage": "Это действие требует разрешения localStorage, включены ли cookie-файлы?",
 | 
					        "local_storage": "Это действие требует разрешения localStorage, включены ли cookie-файлы?",
 | 
				
			||||||
        "register_no_email_note": "Использование электронной почты в качестве имени пользователя не рекомендуется. Продолжить?",
 | 
					        "register_no_email_note": "Использование электронной почты в качестве имени пользователя не рекомендуется. Продолжить?",
 | 
				
			||||||
        "next_video_countdown": "Воспроизведение следующего видео через {0} с."
 | 
					        "next_video_countdown": "Следующие видео через {0} с",
 | 
				
			||||||
 | 
					        "days": "{amount} дней",
 | 
				
			||||||
 | 
					        "hours": "{amount} час(ов)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} недель",
 | 
				
			||||||
 | 
					        "months": "{amount} месяцев",
 | 
				
			||||||
 | 
					        "login_note": "Войти в аккаунт созданном на этом экземпляре.",
 | 
				
			||||||
 | 
					        "register_note": "Зарегистрируйте учетную запись для данного экземпляра Piped. Это позволит синхронизировать подписки и списки воспроизведения с учетной записью, так что они будут храниться на стороне сервера. Вы можете использовать все функции без учетной записи, но все данные будут храниться в локальном кэше вашего браузера. Пожалуйста, убедитесь, что вы НЕ используете адрес электронной почты в качестве имени пользователя, и выберите надежный пароль, который вы не используете в других местах."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,194 +1,205 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "trending": "නැගී එන",
 | 
					        "trending": "නැගී එන",
 | 
				
			||||||
        "login": "ඇතුළු වන්න",
 | 
					        "login": "පිවිසෙන්න",
 | 
				
			||||||
        "register": "ලියාපදිංචි වන්න",
 | 
					        "register": "ලියාපදිංචිය",
 | 
				
			||||||
        "preferences": "සැකසුම්",
 | 
					        "preferences": "අභිප්රේත",
 | 
				
			||||||
        "history": "ඉතිහාසය",
 | 
					        "history": "ඉතිහාසය",
 | 
				
			||||||
        "subscriptions": "දායකත්ව",
 | 
					        "subscriptions": "දායකත්ව",
 | 
				
			||||||
        "account": "ගිණුම",
 | 
					        "account": "ගිණුම",
 | 
				
			||||||
        "player": "වාදකය",
 | 
					        "player": "වාදකය",
 | 
				
			||||||
        "livestreams": "සජීවී ප්රවාහ",
 | 
					        "livestreams": "සජීව ප්රචාර",
 | 
				
			||||||
        "channels": "නාලිකා",
 | 
					        "channels": "නාලිකා",
 | 
				
			||||||
        "playlists": "වාදන ලැයිස්තු",
 | 
					        "playlists": "වාදන ලැයිස්තු",
 | 
				
			||||||
        "instance": "සේවාදායකය",
 | 
					        "instance": "සේවාදායකය",
 | 
				
			||||||
        "bookmarks": "පොත් සලකුණු",
 | 
					        "bookmarks": "පොත්යොමු",
 | 
				
			||||||
        "feed": "නවතම",
 | 
					        "feed": "සංග්රහය",
 | 
				
			||||||
        "channel_groups": "නාලිකා කණ්ඩායම්"
 | 
					        "channel_groups": "නාලිකා සමූහ"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "දායකවන්න - {count}",
 | 
					        "subscribe": "දායකවන්න - {count}",
 | 
				
			||||||
        "unsubscribe": "දායක නොවන්න - {count}",
 | 
					        "unsubscribe": "දායක නොවන්න - {count}",
 | 
				
			||||||
        "most_recent": "නවතම",
 | 
					        "most_recent": "වඩාත් මෑත",
 | 
				
			||||||
        "least_recent": "පැරණිතම",
 | 
					        "least_recent": "ආසන්න මෑත",
 | 
				
			||||||
        "channel_name_asc": "නාලිකාවේ නම (A-Z)",
 | 
					        "channel_name_asc": "නාලිකාවේ නම (අ-ෆ)",
 | 
				
			||||||
        "channel_name_desc": "නාලිකාවේ නම (Z-A)",
 | 
					        "channel_name_desc": "නාලිකාවේ නම (ෆ-අ)",
 | 
				
			||||||
        "back": "ආපසු",
 | 
					        "back": "ආපසු",
 | 
				
			||||||
        "skip_sponsors": "අනුග්රහ මඟ හරින්න",
 | 
					        "skip_sponsors": "අනුග්රහකයින් මඟහරින්න",
 | 
				
			||||||
        "skip_outro": "අවසන් කාඩ්පත/දායක ලැයිස්තුව මඟ හරින්න",
 | 
					        "skip_outro": "අවසන් කාඩ්පත/දායක ලැයිස්තුව මඟ හරින්න",
 | 
				
			||||||
        "skip_preview": "පෙරදසුන/සාරාංශය මඟ හරින්න",
 | 
					        "skip_preview": "පෙරදසුන/සාරාංශය මඟ හරින්න",
 | 
				
			||||||
        "skip_self_promo": "නොගෙවූ/ස්වයං ප්රවර්ධන මඟ හරින්න",
 | 
					        "skip_self_promo": "නොගෙවූ/ස්වයං ප්රවර්ධන මඟ හරින්න",
 | 
				
			||||||
        "skip_filler_tangent": "අදාළ නොවන කොටස් මඟහරින්න",
 | 
					        "skip_filler_tangent": "අදාළ නොවන කොටස් මඟහරින්න",
 | 
				
			||||||
        "theme": "පෙනුම",
 | 
					        "theme": "තේමාව",
 | 
				
			||||||
        "dark": "අඳුරු",
 | 
					        "dark": "අඳුරු",
 | 
				
			||||||
        "light": "එළිය",
 | 
					        "light": "දීප්ත",
 | 
				
			||||||
        "autoplay_video": "ස්වයංක්රීයව වීඩියෝව වාදනය කරන්න",
 | 
					        "autoplay_video": "දෘශ්යක ඉබේ වාදනය",
 | 
				
			||||||
        "auto": "ස්වයං තේරීම",
 | 
					        "auto": "ස්වයං",
 | 
				
			||||||
        "default_quality": "පෙරනිමි ගුණත්වය",
 | 
					        "default_quality": "පෙරනිමි ගුණත්වය",
 | 
				
			||||||
        "default_homepage": "පෙරනිමි මුල් පිටුව",
 | 
					        "default_homepage": "පෙරනිමි මුල් පිටුව",
 | 
				
			||||||
        "show_markers": "වාදකයේ මාකර් පෙන්වන්න",
 | 
					        "show_markers": "වාදකයේ මාකර් පෙන්වන්න",
 | 
				
			||||||
        "buffering_goal": "බෆරින් ඉලක්කය (තත්පර වලින්)",
 | 
					        "buffering_goal": "අන්තරාචය ඉලක්කය (තත්. වලින්)",
 | 
				
			||||||
        "enable_sponsorblock": "Sponsorblock සබල කරන්න",
 | 
					        "enable_sponsorblock": "Sponsorblock සබල කරන්න",
 | 
				
			||||||
        "sort_by": "තේරීම:",
 | 
					        "sort_by": "පෙළගසන්න:",
 | 
				
			||||||
        "skip_highlight": "ඉස්මතු කිරීම් මඟ හරින්න",
 | 
					        "skip_highlight": "ඉස්මතු කිරීම් මඟ හරින්න",
 | 
				
			||||||
        "language_selection": "භාෂා තේරීම",
 | 
					        "language_selection": "භාෂාව",
 | 
				
			||||||
        "show_more": "තව පෙන්වන්න",
 | 
					        "show_more": "තව පෙන්වන්න",
 | 
				
			||||||
        "yes": "ඔව්",
 | 
					        "yes": "ඔව්",
 | 
				
			||||||
        "no": "නැත",
 | 
					        "no": "නැහැ",
 | 
				
			||||||
        "export_to_json": "JSON වෙත අපනයනය කරන්න",
 | 
					        "export_to_json": "JSON ලෙස නිර්යාත කරන්න",
 | 
				
			||||||
        "import_from_json": "JSON/CSV වෙතින් ආනයනය කරන්න",
 | 
					        "import_from_json": "JSON/CSV වෙතින් ආයාත කරන්න",
 | 
				
			||||||
        "loop_this_video": "මෙම වීඩියෝව ලූප් කරන්න",
 | 
					        "loop_this_video": "මෙම දෘශ්යකය පුඩුලන්න",
 | 
				
			||||||
        "auto_play_next_video": "මීළඟ වීඩියෝව ස්වයංව වාදනය කරන්න",
 | 
					        "auto_play_next_video": "ඊළඟ දෘශ්යකය ඉබේ වාදනය කරන්න",
 | 
				
			||||||
        "donations": "සංවර්ධන පරිත්යාග",
 | 
					        "donations": "සංවර්ධන පරිත්යාග",
 | 
				
			||||||
        "minimize_comments": "අදහස් සඟවන්න",
 | 
					        "minimize_comments": "අදහස් හකුළන්න",
 | 
				
			||||||
        "show_comments": "අදහස් පෙන්වන්න",
 | 
					        "show_comments": "අදහස් පෙන්වන්න",
 | 
				
			||||||
        "minimize_description": "විස්තරය සඟවන්න",
 | 
					        "minimize_description": "විස්තරය හකුළන්න",
 | 
				
			||||||
        "show_description": "විස්තරය පෙන්වන්න",
 | 
					        "show_description": "සවිස්තරය පෙන්වන්න",
 | 
				
			||||||
        "minimize_recommendations": "නිර්දේශ සඟවන්න",
 | 
					        "minimize_recommendations": "නිර්දේශ හකුළන්න",
 | 
				
			||||||
        "show_recommendations": "නිර්දේශ පෙන්වන්න",
 | 
					        "show_recommendations": "නිර්දේශ පෙන්වන්න",
 | 
				
			||||||
        "store_watch_history": "නැරඹීමේ ඉතිහාසය ගබඩා කරන්න",
 | 
					        "store_watch_history": "නැරඹුම් ඉතිහාසය ගබඩා කරන්න",
 | 
				
			||||||
        "enabled_codecs": "සබල කර ඇති කෝඩෙක්ස් (බහු)",
 | 
					        "enabled_codecs": "සබල කර ඇති කෝඩෙක්ස් (බහු)",
 | 
				
			||||||
        "minimize_description_default": "පෙරනිමියෙන් විස්තරය සඟවන්න",
 | 
					        "minimize_description_default": "පෙරනිමි පරිදි සවිස්තරය සඟවන්න",
 | 
				
			||||||
        "instances_list": "සේවාදායක ලැයිස්තුව",
 | 
					        "instances_list": "සේවාදායක ලැයිස්තුව",
 | 
				
			||||||
        "instance_selection": "සේවාදායකය තේරීම",
 | 
					        "instance_selection": "සේවාදායකය",
 | 
				
			||||||
        "view_ssl_score": "SSL ලකුණු බලන්න",
 | 
					        "view_ssl_score": "SSL ලකුණු බලන්න",
 | 
				
			||||||
        "search": "සොයන්න (Ctrl+K)",
 | 
					        "search": "සොයන්න (Ctrl+K)",
 | 
				
			||||||
        "loading": "පූරණය වෙමින්...",
 | 
					        "loading": "පූරණය වෙමින්...",
 | 
				
			||||||
        "hide_replies": "පිළිතුරු සඟවන්න",
 | 
					        "hide_replies": "පිළිතුරු සඟවන්න",
 | 
				
			||||||
        "load_more_replies": "තවත් පිළිතුරු පූරණය කරන්න",
 | 
					        "load_more_replies": "තවත් පිළිතුරු පෙන්වන්න",
 | 
				
			||||||
        "add_to_playlist": "වාදන ලැයිස්තුවට එක් කරන්න",
 | 
					        "add_to_playlist": "වාදන ලැයිස්තුවට දමන්න",
 | 
				
			||||||
        "create_playlist": "වාදන ලැයිස්තුව සාදන්න",
 | 
					        "create_playlist": "වාදන ලැයිස්තුව සාදන්න",
 | 
				
			||||||
        "delete_playlist": "වාදන ලැයිස්තුව මකන්න",
 | 
					        "delete_playlist": "වාදන ලැයිස්තුව මකන්න",
 | 
				
			||||||
        "select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
 | 
					        "select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
 | 
				
			||||||
        "please_select_playlist": "කරුණාකර වාදන ලැයිස්තුවක් තෝරන්න",
 | 
					        "please_select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
 | 
				
			||||||
        "delete_account": "ගිණුම මකන්න",
 | 
					        "delete_account": "ගිණුම මකන්න",
 | 
				
			||||||
        "logout": "මෙම උපාංගයෙන් වරනය වන්න",
 | 
					        "logout": "මෙම උපාංගයෙන් නික්මෙන්න",
 | 
				
			||||||
        "minimize_recommendations_default": "පෙරනිමියෙන් නිර්දේශ සඟවන්න",
 | 
					        "minimize_recommendations_default": "පෙරනිමි පරිදි නිර්දේශ හකුළන්න",
 | 
				
			||||||
        "invalidate_session": "සියලුම උපාංග වලින් වරනය වන්න",
 | 
					        "invalidate_session": "සියළුම උපාංග නික්මවන්න",
 | 
				
			||||||
        "clone_playlist": "වාදන ලැයිස්තුව ක්ලෝනය කරන්න",
 | 
					        "clone_playlist": "වාදන ලැයිස්තුවේ අනුපිටපතක්",
 | 
				
			||||||
        "download_as_txt": ".txt ලෙස බාගන්න",
 | 
					        "download_as_txt": ".txt ලෙස බාගන්න",
 | 
				
			||||||
        "reset_preferences": "සැකසුම් නැවත සකසන්න",
 | 
					        "reset_preferences": "අභිප්රේත නැවත සකසන්න",
 | 
				
			||||||
        "backup_preferences": "සැකසුම් උපස්ථ කරන්න",
 | 
					        "backup_preferences": "අභිප්රේත උපස්ථ කරන්න",
 | 
				
			||||||
        "restore_preferences": "සැකසුම් නැවත පිහිටුවන්න",
 | 
					        "restore_preferences": "අභිප්රේත ප්රත්යර්පණය කරන්න",
 | 
				
			||||||
        "back_to_home": "ආපසු මුල් පිටුවට",
 | 
					        "back_to_home": "ආපසු මුල් පිටුවට",
 | 
				
			||||||
        "share": "බෙදාගන්න",
 | 
					        "share": "බෙදාගන්න",
 | 
				
			||||||
        "with_timecode": "කාල කේතය සමඟ බෙදා ගන්න",
 | 
					        "with_timecode": "කාල කේතය සමඟ බෙදා ගන්න",
 | 
				
			||||||
        "piped_link": "පයිප්ඩ් සබැඳිය",
 | 
					        "piped_link": "පයිප්ඩ් සබැඳිය",
 | 
				
			||||||
        "copy_link": "සබැඳිය පිටපත් කරන්න",
 | 
					        "copy_link": "සබැඳියේ පිටපතක්",
 | 
				
			||||||
        "time_code": "කාල කේතය (තත්පර වලින්)",
 | 
					        "time_code": "කාල කේතය (තත්පර වලින්)",
 | 
				
			||||||
        "show_chapters": "පරිච්ඡේද",
 | 
					        "show_chapters": "පරිච්ඡේද",
 | 
				
			||||||
        "status_page": "තත්ත්වය",
 | 
					        "status_page": "තත්වය",
 | 
				
			||||||
        "source_code": "ප්රභව කේතය",
 | 
					        "source_code": "ප්රභව කේතය",
 | 
				
			||||||
        "documentation": "ප්රලේඛනය",
 | 
					        "documentation": "ප්රලේඛනය",
 | 
				
			||||||
        "reply_count": "පිළිතුරු {count}",
 | 
					        "reply_count": "පිළිතුරු {count}",
 | 
				
			||||||
        "with_playlist": "වාදන ලැයිස්තුව සමඟ බෙදා ගන්න",
 | 
					        "with_playlist": "වාදන ලැයිස්තුව සමඟ බෙදා ගන්න",
 | 
				
			||||||
        "bookmark_playlist": "පොත් සලකුණ",
 | 
					        "bookmark_playlist": "පොත්යොමුවක්",
 | 
				
			||||||
        "show_watch_on_youtube": "YouTube එකේ නරඹන්න බොත්තම පෙන්වන්න",
 | 
					        "show_watch_on_youtube": "යූටියුබ්හි නරඹන්න බොත්තම පෙන්වන්න",
 | 
				
			||||||
        "filter": "පෙරහන",
 | 
					        "filter": "පෙරහන",
 | 
				
			||||||
        "instance_donations": "සේවාදායක පරිත්යාග",
 | 
					        "instance_donations": "සේවාදායක පරිත්යාග",
 | 
				
			||||||
        "instance_auth_selection": "සත්යතාව තහවුරු කිරීම සඳහා සේවාදායකයක් තේරීම",
 | 
					        "instance_auth_selection": "සත්යතාව තහවුරු කිරීම සඳහා සේවාදායකයක් තේරීම",
 | 
				
			||||||
        "view_subscriptions": "දායකත්ව බලන්න",
 | 
					        "view_subscriptions": "දායකත්ව බලන්න",
 | 
				
			||||||
        "uses_api_from": "මොවුන්ගේ API භාවිතා වේ ",
 | 
					        "uses_api_from": "භාවිතා වන යෙ.ක්ර.මු. (API): ",
 | 
				
			||||||
        "skip_intro": "විරාම/හඳුන්වාදීමේ සජීවිකරණය මඟ හරින්න",
 | 
					        "skip_intro": "විරාම/හඳුන්වාදීමේ සජීවිකරණය මඟ හරින්න",
 | 
				
			||||||
        "skip_interaction": "අන්තර් ක්රියා මතක් කිරීම මඟ හරින්න (දායක වන්න)",
 | 
					        "skip_interaction": "අන්තර් ක්රියා මතක් කිරීම මඟ හරින්න (දායක වන්න)",
 | 
				
			||||||
        "skip_non_music": "ගීත: ගීතය නොවන කොටස මඟ හරින්න",
 | 
					        "skip_non_music": "ගීත: ගීතය නොවන කොටස මඟ හරින්න",
 | 
				
			||||||
        "remove_from_playlist": "වාදන ලැයිස්තුවෙන් ඉවත් කරන්න",
 | 
					        "remove_from_playlist": "වාදන ලැයිස්තුවෙන් ඉවතලන්න",
 | 
				
			||||||
        "audio_only": "ශ්රව්ය පමණක්",
 | 
					        "audio_only": "හඬ පමණි",
 | 
				
			||||||
        "country_selection": "රට තේරීම",
 | 
					        "country_selection": "රට",
 | 
				
			||||||
        "minimize_comments_default": "පෙරනිමියෙන් අදහස් සඟවන්න",
 | 
					        "minimize_comments_default": "පෙරනිමි පරිදි අදහස් සඟවන්න",
 | 
				
			||||||
        "clear_history": "ඉතිහාසය හිස් කරන්න",
 | 
					        "clear_history": "ඉතිහාසය මකන්න",
 | 
				
			||||||
        "disable_lbry": "ප්රවාහය සඳහා LBRY අබල කරන්න",
 | 
					        "disable_lbry": "ප්රචාරය සඳහා LBRY අබල කරන්න",
 | 
				
			||||||
        "delete_playlist_video_confirm": "වාදන ලැයිස්තුවෙන් වීඩියෝව ඉවත් කරන්නද?",
 | 
					        "delete_playlist_video_confirm": "වාදන ලැයිස්තුවෙන් දෘශ්යකය ඉවත් කරන්නද?",
 | 
				
			||||||
        "delete_playlist_confirm": "මෙම වාදන ලැයිස්තුව මකන්නද?",
 | 
					        "delete_playlist_confirm": "මෙම වාදන ලැයිස්තුව මකන්නද?",
 | 
				
			||||||
        "minimize_chapters_default": "පෙරනිමියෙන් පරිච්ඡේද සඟවන්න",
 | 
					        "minimize_chapters_default": "පෙරනිමි පරිදි පරිච්ඡේද හකුළන්න",
 | 
				
			||||||
        "clone_playlist_success": "සාර්ථකව ක්ලෝන කරන ලදී!",
 | 
					        "clone_playlist_success": "අනුපිටපතක් සෑදිණි!",
 | 
				
			||||||
        "confirm_reset_preferences": "ඔබට ඔබේ සැකසුම් යළි සැකසීමට අවශ්ය බව විශ්වාසද?",
 | 
					        "confirm_reset_preferences": "ඔබගේ අභිප්රේත නැවත සැකසීමට වුවමනා ද?",
 | 
				
			||||||
        "follow_link": "සබැඳිය අනුගමනය කරන්න",
 | 
					        "follow_link": "සබැඳියට යන්න",
 | 
				
			||||||
        "store_search_history": "සෙවුම් ඉතිහාසය ගබඩා කරන්න",
 | 
					        "store_search_history": "සෙවුම් ඉතිහාසය ගබඩා කරන්න",
 | 
				
			||||||
        "no_valid_playlists": "ගොනුවේ වලංගු වාදන ලැයිස්තු අඩංගු නොවේ!",
 | 
					        "no_valid_playlists": "ගොනුවේ වලංගු වාදන ලැයිස්තු අඩංගු නොවේ!",
 | 
				
			||||||
        "playlist_bookmarked": "පොත් සලකුණු කර ඇත",
 | 
					        "playlist_bookmarked": "පොත්යොමුවක් යෙදිණි",
 | 
				
			||||||
        "enable_lbry_proxy": "LBRY සඳහා Proxy සබල කරන්න",
 | 
					        "enable_lbry_proxy": "LBRY සඳහා ප්රතියුක්තය සබල කරන්න",
 | 
				
			||||||
        "different_auth_instance": "සත්යතාව තහවුරු කිරීම සඳහා වෙනත් සේවාදායකයක් භාවිතා කරන්න",
 | 
					        "different_auth_instance": "සත්යතාව තහවුරු කිරීම සඳහා වෙනත් සේවාදායකයක් භාවිතා කරන්න",
 | 
				
			||||||
        "hide_watched": "නවතම කොටසෙහි නැරඹූ වීඩියෝ සඟවන්න",
 | 
					        "hide_watched": "සංග්රහයෙන් නැරඹූ දෘශ්යක සඟවන්න",
 | 
				
			||||||
        "skip_button_only": "මඟ හරින්න බොත්තම පෙන්වන්න",
 | 
					        "skip_button_only": "මඟහරින බොත්තම පෙන්වන්න",
 | 
				
			||||||
        "skip_automatically": "ස්වයංක්රීයව",
 | 
					        "skip_automatically": "ස්වයංක්රීයව",
 | 
				
			||||||
        "skip_segment": "කොටස මඟ හරින්න",
 | 
					        "skip_segment": "කොටස මඟ හරින්න",
 | 
				
			||||||
        "min_segment_length": "අවම කොටස් දිග (තත්පර වලින්)",
 | 
					        "min_segment_length": "අවම කොටස් දිග (තත්පර වලින්)",
 | 
				
			||||||
        "show_less": "අඩුවෙන් පෙන්වන්න",
 | 
					        "show_less": "අඩුවෙන් පෙන්වන්න",
 | 
				
			||||||
        "dismiss": "අස් කරන්න",
 | 
					        "dismiss": "අයින් කරන්න",
 | 
				
			||||||
        "autoplay_next_countdown": "ඊළඟ වීඩියෝව තෙක් ගණන් කිරීම (තත්පර වලින්)",
 | 
					        "autoplay_next_countdown": "ඊළඟ දෘශ්යකය තෙක් ගණන් කිරීම (තත්. වලින්)",
 | 
				
			||||||
        "group_name": "කණ්ඩායමේ නම",
 | 
					        "group_name": "සමූහයේ නම",
 | 
				
			||||||
        "create_group": "කණ්ඩායම සාදන්න",
 | 
					        "create_group": "සමූහය සාදන්න",
 | 
				
			||||||
        "cancel": "අවලංගු කරන්න",
 | 
					        "cancel": "අවලංගු",
 | 
				
			||||||
        "okay": "හරි",
 | 
					        "okay": "හරි",
 | 
				
			||||||
        "edit_playlist": "වාදන ලැයිස්තුව සංස්කරණය කරන්න",
 | 
					        "edit_playlist": "වාදන ලැයිස්තුව සංස්කරණය",
 | 
				
			||||||
        "playlist_name": "වාදන ලැයිස්තුවේ නම",
 | 
					        "playlist_name": "වාදන ලැයිස්තුවේ නම",
 | 
				
			||||||
        "playlist_description": "වාදන ලැයිස්තු විස්තරය",
 | 
					        "playlist_description": "වාදන ලැයිස්තුවේ සවිස්තරය",
 | 
				
			||||||
        "auto_display_captions": "ස්වයංක්රීය උපසිරැසි පෙන්වන්න"
 | 
					        "auto_display_captions": "උපසිරැසි ස්වයංක්රීයව පෙන්වන්න",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "සෙවුම් යෝජනා පෙන්වන්න",
 | 
				
			||||||
 | 
					        "delete_automatically": "මෙයින් පසුව මකන්න",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR කේතයක් උත්පාදනය"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "{0} එකේ නරඹන්න"
 | 
					        "watch_on": "{0} හි නරඹන්න"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "{author} විසින් අමුණන ලදී",
 | 
					        "pinned_by": "{author} විසින් අමුණන ලදී",
 | 
				
			||||||
        "loading": "අදහස් පූරණය වෙමින්...",
 | 
					        "loading": "අදහස් පූරණය වෙමින්...",
 | 
				
			||||||
        "disabled": "උඩුගත කරන්නා විසින් අදහස් අබල කර ඇත.",
 | 
					        "disabled": "උඩුගත කරන්නා විසින් අදහස් අබල කර ඇත.",
 | 
				
			||||||
        "user_disabled": "සැකසුම් තුළ අදහස් අබල කර ඇත."
 | 
					        "user_disabled": "සැකසුම් හරහා අදහස් අබල කර ඇත."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "has_cdn": "CDN තිබේද?",
 | 
					        "has_cdn": "CDN තිබේද?",
 | 
				
			||||||
        "version": "නිකුතු අංකය",
 | 
					        "version": "අනුවාදය",
 | 
				
			||||||
        "up_to_date": "යාවත්කාලීනද?",
 | 
					        "up_to_date": "යාවත්කාලීනද?",
 | 
				
			||||||
        "instance_name": "සේවාදායකයේ නම",
 | 
					        "instance_name": "සේවාදායකයේ නම",
 | 
				
			||||||
        "registered_users": "ලියාපදිංචි පරිශීලකයන්",
 | 
					        "registered_users": "ලියාපදිංචි පරිශ්රීලකයින්",
 | 
				
			||||||
        "ssl_score": "SSL ලකුණු",
 | 
					        "ssl_score": "SSL ලකුණු",
 | 
				
			||||||
        "instance_locations": "සේවාදායක ස්ථාන"
 | 
					        "instance_locations": "සේවාදායකයේ ස්ථානය"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "පරිශීලක නාමය",
 | 
					        "username": "පරිශ්රීලක නාමය",
 | 
				
			||||||
        "password": "මුරපදය"
 | 
					        "password": "මුරපදය"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "වීඩියෝ",
 | 
					        "videos": "දෘශ්යක",
 | 
				
			||||||
        "views": "බැලීම් {views}",
 | 
					        "views": "බැලීම් {views}",
 | 
				
			||||||
        "watched": "නැරඹුවා",
 | 
					        "watched": "නැරඹුවා",
 | 
				
			||||||
        "sponsor_segments": "අනුග්රාහක අංශ",
 | 
					        "sponsor_segments": "අනුග්රාහක අංශ",
 | 
				
			||||||
        "chapters": "පරිච්ඡේද",
 | 
					        "chapters": "පරිච්ඡේද",
 | 
				
			||||||
        "shorts": "කෙටි වීඩියෝ",
 | 
					        "shorts": "කෙටි දෘශ්යක",
 | 
				
			||||||
        "ratings_disabled": "ශ්රේණිගත කිරීම් අබල කර ඇත",
 | 
					        "ratings_disabled": "ශ්රේණිගත කිරීම් අබල කර ඇත",
 | 
				
			||||||
        "live": "{0} සජීවී",
 | 
					        "live": "{0} සජීවී",
 | 
				
			||||||
        "all": "සියල්ල",
 | 
					        "all": "සියල්ල",
 | 
				
			||||||
        "category": "කාණ්ඩය"
 | 
					        "category": "ප්රවර්ගය",
 | 
				
			||||||
 | 
					        "chapters_vertical": "සිරස්",
 | 
				
			||||||
 | 
					        "license": "බලපත්රය",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "තිරස්"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "ඔබ අදහස් කළේ: {0}?",
 | 
					        "did_you_mean": "ඔබ අදහස් කළේ: {0}?",
 | 
				
			||||||
        "videos": "YouTube: වීඩියෝ",
 | 
					        "videos": "යූටියුබ්: දෘශ්යක",
 | 
				
			||||||
        "playlists": "YouTube: වාදන ලැයිස්තු",
 | 
					        "playlists": "යූටියුබි: වාදන ලැයිස්තු",
 | 
				
			||||||
        "music_songs": "YT Music: ගීත",
 | 
					        "music_songs": "යූටියුබි ගීත: ගීත",
 | 
				
			||||||
        "music_videos": "YT Music: වීඩියෝ",
 | 
					        "music_videos": "යූටියුබි ගීත: දෘශ්යක",
 | 
				
			||||||
        "music_albums": "YT Music: ඇල්බම",
 | 
					        "music_albums": "YT Music: ඇල්බම",
 | 
				
			||||||
        "music_playlists": "YT Music: වාදන ලැයිස්තු",
 | 
					        "music_playlists": "යූටියුබි ගීත: වාදන ලැයිස්තු",
 | 
				
			||||||
        "channels": "YouTube: නාලිකා",
 | 
					        "channels": "යූටියුබ්: නාලිකා",
 | 
				
			||||||
        "all": "YouTube: සියල්ල"
 | 
					        "all": "යූටියුබි: සියල්ල",
 | 
				
			||||||
 | 
					        "music_artists": "යූටියුබ් ගීත: කලාකරුවන්"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "page_not_found": "පිටුව හමු නොවීය",
 | 
					        "page_not_found": "පිටුව හමු නොවිණි",
 | 
				
			||||||
        "copied": "පිටපත් කළා!",
 | 
					        "copied": "පිටපත් විය!",
 | 
				
			||||||
        "cannot_copy": "පිටපත් කළ නොහැක!",
 | 
					        "cannot_copy": "පිටපත් නොවේ!",
 | 
				
			||||||
        "local_storage": "මෙම ක්රියාවට localStorage අවශ්ය වේ, cookies සබල කර තිබේද?",
 | 
					        "local_storage": "මෙම ක්රියාමාර්ගයට ස්ථානීය-ආචයනය වුවමනාය, දත්තකඩ සබල කර තිබේද?",
 | 
				
			||||||
        "register_no_email_note": "පරිශීලක නාමය ලෙස විද්යුත් තැපෑලක් භාවිතා කිරීම නිර්දේශ නොකරයි. කෙසේ හෝ ඉදිරියට යන්නද?",
 | 
					        "register_no_email_note": "පරිශ්රීලක නාමය ලෙස වි-තැපෑලක් භාවිතය නිර්දේශ නොකෙරේ. ඉදිරියට යන්නද?",
 | 
				
			||||||
        "preferences_note": "සටහන: සැකසුම් ඔබගේ බ්රවුසරයේ දේශීය ගබඩාවේ සුරකිනු ලැබේ. ඔබගේ බ්රවුසර දත්ත මැකීමෙන් ඒවා නැවත සකසනු ඇත.",
 | 
					        "preferences_note": "සටහන: ඔබගේ අතිරික්සුවේ ස්ථානීය ආචයනයේ අභිප්රේත සුරැකෙයි. අතිරික්සුවේ දත්ත මැකීමෙන් ඒවා අහිමි වනු ඇත.",
 | 
				
			||||||
        "next_video_countdown": "මීළඟ වීඩියෝව තත්පර {0}කින් වාදනය වේ"
 | 
					        "next_video_countdown": "ඊළඟ දෘශ්යකය තත්. {0} කින් වාදනය වේ",
 | 
				
			||||||
 | 
					        "days": "දවස් {amount}",
 | 
				
			||||||
 | 
					        "weeks": "සති {amount}",
 | 
				
			||||||
 | 
					        "hours": "පැය {amount}",
 | 
				
			||||||
 | 
					        "months": "මාස {amount}"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "දායක වූයේ: {0}"
 | 
					        "subscribed_channels_count": "දායක වූයේ: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,12 @@
 | 
				
			||||||
        "playlists": "Spellistor",
 | 
					        "playlists": "Spellistor",
 | 
				
			||||||
        "account": "Konto",
 | 
					        "account": "Konto",
 | 
				
			||||||
        "instance": "Instans",
 | 
					        "instance": "Instans",
 | 
				
			||||||
        "player": "Spelare"
 | 
					        "player": "Spelare",
 | 
				
			||||||
 | 
					        "bookmarks": "Bokmärken",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow",
 | 
				
			||||||
 | 
					        "livestreams": "Livesändningar",
 | 
				
			||||||
 | 
					        "channels": "Kanaler",
 | 
				
			||||||
 | 
					        "channel_groups": "Kanal Grupper"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "subscribe": "Prenumerera - {count}",
 | 
					        "subscribe": "Prenumerera - {count}",
 | 
				
			||||||
| 
						 | 
					@ -46,18 +51,18 @@
 | 
				
			||||||
        "store_watch_history": "Spara titthistorik",
 | 
					        "store_watch_history": "Spara titthistorik",
 | 
				
			||||||
        "instances_list": "Lista över instanser",
 | 
					        "instances_list": "Lista över instanser",
 | 
				
			||||||
        "enabled_codecs": "Aktivera codecs (flera)",
 | 
					        "enabled_codecs": "Aktivera codecs (flera)",
 | 
				
			||||||
        "import_from_json": "Importera från JSON/CSV",
 | 
					        "import_from_json": "Importera från JSON",
 | 
				
			||||||
        "donations": "Donationer",
 | 
					        "donations": "Donationer till utveckling",
 | 
				
			||||||
        "filter": "Filter",
 | 
					        "filter": "Filter",
 | 
				
			||||||
        "hide_replies": "Dölj svar",
 | 
					        "hide_replies": "Dölj svar",
 | 
				
			||||||
        "load_more_replies": "Ladda fler svar",
 | 
					        "load_more_replies": "Ladda fler svar",
 | 
				
			||||||
        "enable_sponsorblock": "Aktivera sponsorblockering",
 | 
					        "enable_sponsorblock": "Aktivera sponsorblockering",
 | 
				
			||||||
        "skip_preview": "Hoppa över förhandsgranskning/sammanfattning",
 | 
					        "skip_preview": "Hoppa över förhandsgranskning/sammanfattning",
 | 
				
			||||||
        "autoplay_video": "Spela upp video automatiskt",
 | 
					        "autoplay_video": "Spela upp video automatiskt",
 | 
				
			||||||
        "country_selection": "Val av land",
 | 
					        "country_selection": "Land",
 | 
				
			||||||
        "language_selection": "Val av språk",
 | 
					        "language_selection": "Språk",
 | 
				
			||||||
        "skip_non_music": "Hoppa över musik: Icke-musikaliskt avsnitt",
 | 
					        "skip_non_music": "Hoppa över musik: Icke-musikaliskt avsnitt",
 | 
				
			||||||
        "instance_selection": "Val av instans",
 | 
					        "instance_selection": "Instans",
 | 
				
			||||||
        "show_more": "Visa mer",
 | 
					        "show_more": "Visa mer",
 | 
				
			||||||
        "yes": "Ja",
 | 
					        "yes": "Ja",
 | 
				
			||||||
        "no": "Nej",
 | 
					        "no": "Nej",
 | 
				
			||||||
| 
						 | 
					@ -67,7 +72,7 @@
 | 
				
			||||||
        "show_recommendations": "Visa rekommendationer",
 | 
					        "show_recommendations": "Visa rekommendationer",
 | 
				
			||||||
        "disable_lbry": "Inaktivera LBRY för strömning",
 | 
					        "disable_lbry": "Inaktivera LBRY för strömning",
 | 
				
			||||||
        "enable_lbry_proxy": "Aktivera proxy för LBRY",
 | 
					        "enable_lbry_proxy": "Aktivera proxy för LBRY",
 | 
				
			||||||
        "search": "Sök",
 | 
					        "search": "Sök (Ctrl+K)",
 | 
				
			||||||
        "clear_history": "Rensa historik",
 | 
					        "clear_history": "Rensa historik",
 | 
				
			||||||
        "skip_filler_tangent": "Hoppa över påfyllningstangent",
 | 
					        "skip_filler_tangent": "Hoppa över påfyllningstangent",
 | 
				
			||||||
        "skip_highlight": "Hoppa över höjdpunkt",
 | 
					        "skip_highlight": "Hoppa över höjdpunkt",
 | 
				
			||||||
| 
						 | 
					@ -87,15 +92,62 @@
 | 
				
			||||||
        "minimize_recommendations_default": "Minimera rekommendationer som standard",
 | 
					        "minimize_recommendations_default": "Minimera rekommendationer som standard",
 | 
				
			||||||
        "invalidate_session": "Logga ut alla enheter",
 | 
					        "invalidate_session": "Logga ut alla enheter",
 | 
				
			||||||
        "different_auth_instance": "Använd en annan instans för autentisering",
 | 
					        "different_auth_instance": "Använd en annan instans för autentisering",
 | 
				
			||||||
        "instance_auth_selection": "Val av autentiseringsinstans",
 | 
					        "instance_auth_selection": "Autentiseringsinstans",
 | 
				
			||||||
        "download_as_txt": "Ladda ner som .txt",
 | 
					        "download_as_txt": "Ladda ner som .txt",
 | 
				
			||||||
        "reset_preferences": "Återställ inställningar",
 | 
					        "reset_preferences": "Återställ inställningar",
 | 
				
			||||||
        "confirm_reset_preferences": "Är du säker på att du vill återställa dina inställningar?",
 | 
					        "confirm_reset_preferences": "Är du säker på att du vill återställa dina inställningar?",
 | 
				
			||||||
        "backup_preferences": "Inställningar för säkerhetskopiering",
 | 
					        "backup_preferences": "Inställningar för säkerhetskopiering",
 | 
				
			||||||
        "restore_preferences": "Återställa inställningar"
 | 
					        "restore_preferences": "Återställa inställningar",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Aktivera DeArrow",
 | 
				
			||||||
 | 
					        "autoplay_next_countdown": "Antal sekunder tills nästa video startar automatiskt",
 | 
				
			||||||
 | 
					        "minimize_comments_default": "Minimera kommentarer som standard",
 | 
				
			||||||
 | 
					        "show_watch_on_youtube": "Visa knappen \"Titta på YouTube\"",
 | 
				
			||||||
 | 
					        "back_to_home": "Tillbaka till startsidan",
 | 
				
			||||||
 | 
					        "delete_automatically": "Ta bort automatiskt efter",
 | 
				
			||||||
 | 
					        "with_timecode": "Dela med tidsstämpel",
 | 
				
			||||||
 | 
					        "reply_count": "{count} svar",
 | 
				
			||||||
 | 
					        "with_playlist": "Dela med spellista",
 | 
				
			||||||
 | 
					        "dismiss": "Avböj",
 | 
				
			||||||
 | 
					        "min_segment_length": "Minsta segmentlängd (i sekunder)",
 | 
				
			||||||
 | 
					        "skip_segment": "Hoppa över segment",
 | 
				
			||||||
 | 
					        "minimize_comments": "Minimera kommentarer",
 | 
				
			||||||
 | 
					        "show_less": "Visa mindre",
 | 
				
			||||||
 | 
					        "cancel": "Avbryt",
 | 
				
			||||||
 | 
					        "store_search_history": "Spara sökhistorik",
 | 
				
			||||||
 | 
					        "documentation": "Dokumentation",
 | 
				
			||||||
 | 
					        "okay": "Okej",
 | 
				
			||||||
 | 
					        "status_page": "Status",
 | 
				
			||||||
 | 
					        "minimize_chapters_default": "Minimera kapitel som standard",
 | 
				
			||||||
 | 
					        "time_code": "Tidsstämpel (i sekunder)",
 | 
				
			||||||
 | 
					        "hide_watched": "Dölj tittade videor i flödet",
 | 
				
			||||||
 | 
					        "share": "Dela",
 | 
				
			||||||
 | 
					        "show_chapters": "Kapitel",
 | 
				
			||||||
 | 
					        "source_code": "Källkod",
 | 
				
			||||||
 | 
					        "edit_playlist": "Redigera spellista",
 | 
				
			||||||
 | 
					        "playlist_name": "Spellistans namn",
 | 
				
			||||||
 | 
					        "playlist_description": "Beskrivning av spellista",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Generera QR-kod",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Layout för kapitel på mobil",
 | 
				
			||||||
 | 
					        "piped_link": "Piped-länk",
 | 
				
			||||||
 | 
					        "follow_link": "Följ-länk",
 | 
				
			||||||
 | 
					        "copy_link": "Kopiera länk",
 | 
				
			||||||
 | 
					        "group_name": "Gruppnamn",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "Visa sökförslag",
 | 
				
			||||||
 | 
					        "auto_display_captions": "Automatisk visning av textning",
 | 
				
			||||||
 | 
					        "bookmark_playlist": "Bokmärke",
 | 
				
			||||||
 | 
					        "instance_donations": "Instans donationer",
 | 
				
			||||||
 | 
					        "no_valid_playlists": "Filen innehåller inga giltiga spellistor!",
 | 
				
			||||||
 | 
					        "playlist_bookmarked": "Bokmärkt",
 | 
				
			||||||
 | 
					        "create_group": "Skapa grupp",
 | 
				
			||||||
 | 
					        "skip_button_only": "Visa hoppa över-knapp",
 | 
				
			||||||
 | 
					        "skip_automatically": "Automatiskt",
 | 
				
			||||||
 | 
					        "download_frame": "Ladda ner bildruta",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Importera från JSON/CSV",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Sekretesspolicy"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Titta på {0}"
 | 
					        "watch_on": "Se på {0}",
 | 
				
			||||||
 | 
					        "failed": "Misslyckades med felkod {0}, se loggar för mer information"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "instance_name": "Instansnamn",
 | 
					        "instance_name": "Instansnamn",
 | 
				
			||||||
| 
						 | 
					@ -108,7 +160,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Användarnamn",
 | 
					        "username": "Användarnamn",
 | 
				
			||||||
        "password": "Lösenord"
 | 
					        "password": "Lösenord",
 | 
				
			||||||
 | 
					        "password_confirm": "Bekräfta lösenord",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Lösenorden stämmer inte överens!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "Videor",
 | 
					        "videos": "Videor",
 | 
				
			||||||
| 
						 | 
					@ -118,7 +172,13 @@
 | 
				
			||||||
        "ratings_disabled": "Betyg inaktiverade",
 | 
					        "ratings_disabled": "Betyg inaktiverade",
 | 
				
			||||||
        "chapters": "Kapitel",
 | 
					        "chapters": "Kapitel",
 | 
				
			||||||
        "live": "{0} Live",
 | 
					        "live": "{0} Live",
 | 
				
			||||||
        "shorts": "Shorts"
 | 
					        "shorts": "Shorts",
 | 
				
			||||||
 | 
					        "license": "Licens",
 | 
				
			||||||
 | 
					        "all": "Alla",
 | 
				
			||||||
 | 
					        "category": "Kategori",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Horisontell",
 | 
				
			||||||
 | 
					        "visibility": "Synlighet",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Vertikal"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Fäst av {author}",
 | 
					        "pinned_by": "Fäst av {author}",
 | 
				
			||||||
| 
						 | 
					@ -135,7 +195,8 @@
 | 
				
			||||||
        "all": "YouTube: Alla",
 | 
					        "all": "YouTube: Alla",
 | 
				
			||||||
        "videos": "YouTube: Videor",
 | 
					        "videos": "YouTube: Videor",
 | 
				
			||||||
        "playlists": "YouTube: Spellistor",
 | 
					        "playlists": "YouTube: Spellistor",
 | 
				
			||||||
        "music_songs": "YT Music: Låtar"
 | 
					        "music_songs": "YT Music: Låtar",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Artister"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Prenumererar på: {0}"
 | 
					        "subscribed_channels_count": "Prenumererar på: {0}"
 | 
				
			||||||
| 
						 | 
					@ -144,6 +205,18 @@
 | 
				
			||||||
        "preferences_note": "Observera: inställningar sparas i webbläsarens lokala lagring. Om du raderar dina webbläsardata återställs de."
 | 
					        "preferences_note": "Observera: inställningar sparas i webbläsarens lokala lagring. Om du raderar dina webbläsardata återställs de."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "register_no_email_note": "Det rekommenderas inte att använda e-post som användarnamn. Fortsätt ändå?"
 | 
					        "register_no_email_note": "Det rekommenderas inte att använda e-post som användarnamn. Fortsätt ändå?",
 | 
				
			||||||
 | 
					        "hours": "{amount} timma(r)",
 | 
				
			||||||
 | 
					        "preferences_note": "Obs: Inställningarna sparas i det lokala lagringsutrymmet i din webbläsare. Om du raderar dina webbläsardata återställs de.",
 | 
				
			||||||
 | 
					        "days": "{amount} dag(ar)",
 | 
				
			||||||
 | 
					        "weeks": "{amount} vecka/veckor",
 | 
				
			||||||
 | 
					        "months": "{amount} månad(er)",
 | 
				
			||||||
 | 
					        "next_video_countdown": "Spelar nästa video om {0}s",
 | 
				
			||||||
 | 
					        "cannot_copy": "Kan inte kopiera!",
 | 
				
			||||||
 | 
					        "page_not_found": "Sida hittas ej",
 | 
				
			||||||
 | 
					        "copied": "Kopierad!",
 | 
				
			||||||
 | 
					        "local_storage": "Det här kräver localStorage, är cookies aktiverat?",
 | 
				
			||||||
 | 
					        "login_note": "Logga in med ett konto som skapats på denna instans.",
 | 
				
			||||||
 | 
					        "register_note": "Registrera ett konto för den här Piped-instansen. Då kan du synkronisera dina prenumerationer och spellistor med ditt konto, så att de lagras på serversidan. Du kan använda alla funktioner utan konto, men alla data lagras i webbläsarens lokala cache. Se till att du INTE använder en e-postadress som användarnamn och välj ett säkert lösenord som du inte använder någon annanstans."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "instances_list": "Örnek Listesi",
 | 
					        "instances_list": "Sunucu Listesi",
 | 
				
			||||||
        "language_selection": "Dil Seçimi",
 | 
					        "language_selection": "Dil",
 | 
				
			||||||
        "store_watch_history": "İzleme Geçmişini Sakla",
 | 
					        "store_watch_history": "İzleme Geçmişini Sakla",
 | 
				
			||||||
        "minimize_description_default": "Açıklamayı Öntanımlı Olarak Küçült",
 | 
					        "minimize_description_default": "Açıklamayı Öntanımlı Olarak Küçült",
 | 
				
			||||||
        "show_comments": "Yorumları Göster",
 | 
					        "show_comments": "Yorumları Göster",
 | 
				
			||||||
        "default_homepage": "Öntanımlı Ana Sayfa",
 | 
					        "default_homepage": "Öntanımlı Ana Sayfa",
 | 
				
			||||||
        "country_selection": "Ülke Seçimi",
 | 
					        "country_selection": "Ülke",
 | 
				
			||||||
        "buffering_goal": "Arabelleğe Alma Hedefi (Saniye Cinsinden)",
 | 
					        "buffering_goal": "Arabelleğe Alma Hedefi (Saniye Cinsinden)",
 | 
				
			||||||
        "default_quality": "Öntanımlı Kalite",
 | 
					        "default_quality": "Öntanımlı Kalite",
 | 
				
			||||||
        "audio_only": "Yalnızca Ses",
 | 
					        "audio_only": "Yalnızca Ses",
 | 
				
			||||||
| 
						 | 
					@ -41,12 +41,12 @@
 | 
				
			||||||
        "donations": "Geliştirme Bağışları",
 | 
					        "donations": "Geliştirme Bağışları",
 | 
				
			||||||
        "auto_play_next_video": "Sonraki Videoyu Otomatik Oynat",
 | 
					        "auto_play_next_video": "Sonraki Videoyu Otomatik Oynat",
 | 
				
			||||||
        "loop_this_video": "Bu Videoyu Döngüye Al",
 | 
					        "loop_this_video": "Bu Videoyu Döngüye Al",
 | 
				
			||||||
        "import_from_json": "JSON/CSV Dosyasından İçe Aktar",
 | 
					        "import_from_json": "JSON Dosyasından İçe Aktar",
 | 
				
			||||||
        "export_to_json": "JSON Olarak Dışa Aktar",
 | 
					        "export_to_json": "JSON Olarak Dışa Aktar",
 | 
				
			||||||
        "no": "Hayır",
 | 
					        "no": "Hayır",
 | 
				
			||||||
        "yes": "Evet",
 | 
					        "yes": "Evet",
 | 
				
			||||||
        "show_more": "Daha Fazla Göster",
 | 
					        "show_more": "Daha Fazla Göster",
 | 
				
			||||||
        "instance_selection": "Örnek Seçimi",
 | 
					        "instance_selection": "Sunucu",
 | 
				
			||||||
        "loading": "Yükleniyor...",
 | 
					        "loading": "Yükleniyor...",
 | 
				
			||||||
        "filter": "Filtrele",
 | 
					        "filter": "Filtrele",
 | 
				
			||||||
        "search": "Ara (Ctrl+K)",
 | 
					        "search": "Ara (Ctrl+K)",
 | 
				
			||||||
| 
						 | 
					@ -70,9 +70,9 @@
 | 
				
			||||||
        "delete_account": "Hesabı Sil",
 | 
					        "delete_account": "Hesabı Sil",
 | 
				
			||||||
        "logout": "Bu Aygıttan Oturumu Kapat",
 | 
					        "logout": "Bu Aygıttan Oturumu Kapat",
 | 
				
			||||||
        "minimize_recommendations_default": "Önerileri Öntanımlı Olarak Küçült",
 | 
					        "minimize_recommendations_default": "Önerileri Öntanımlı Olarak Küçült",
 | 
				
			||||||
        "different_auth_instance": "Kimlik Doğrulama İçin Farklı Bir Örnek Kullan",
 | 
					        "different_auth_instance": "Kimlik Doğrulama İçin Farklı Bir Sunucu Kullan",
 | 
				
			||||||
        "invalidate_session": "Tüm Aygıtlardan Oturumu Kapat",
 | 
					        "invalidate_session": "Tüm Aygıtlardan Oturumu Kapat",
 | 
				
			||||||
        "instance_auth_selection": "Kimlik Doğrulama Örneği Seçimi",
 | 
					        "instance_auth_selection": "Kimlik Doğrulama Sunucusu",
 | 
				
			||||||
        "clone_playlist": "Oynatma Listesini Kopyala",
 | 
					        "clone_playlist": "Oynatma Listesini Kopyala",
 | 
				
			||||||
        "clone_playlist_success": "Başarıyla kopyalandı!",
 | 
					        "clone_playlist_success": "Başarıyla kopyalandı!",
 | 
				
			||||||
        "download_as_txt": ".txt Olarak İndir",
 | 
					        "download_as_txt": ".txt Olarak İndir",
 | 
				
			||||||
| 
						 | 
					@ -92,7 +92,7 @@
 | 
				
			||||||
        "hide_watched": "Akışta İzlenen Videoları Gizle",
 | 
					        "hide_watched": "Akışta İzlenen Videoları Gizle",
 | 
				
			||||||
        "source_code": "Kaynak Kodu",
 | 
					        "source_code": "Kaynak Kodu",
 | 
				
			||||||
        "documentation": "Belgelendirme",
 | 
					        "documentation": "Belgelendirme",
 | 
				
			||||||
        "instance_donations": "Örnek Bağışları",
 | 
					        "instance_donations": "Sunucu Bağışları",
 | 
				
			||||||
        "status_page": "Durum",
 | 
					        "status_page": "Durum",
 | 
				
			||||||
        "reply_count": "{count} Yanıt",
 | 
					        "reply_count": "{count} Yanıt",
 | 
				
			||||||
        "minimize_comments": "Yorumları Küçült",
 | 
					        "minimize_comments": "Yorumları Küçült",
 | 
				
			||||||
| 
						 | 
					@ -118,10 +118,19 @@
 | 
				
			||||||
        "edit_playlist": "Oynatma listesini düzenle",
 | 
					        "edit_playlist": "Oynatma listesini düzenle",
 | 
				
			||||||
        "playlist_name": "Oynatma listesi adı",
 | 
					        "playlist_name": "Oynatma listesi adı",
 | 
				
			||||||
        "playlist_description": "Oynatma listesi açıklaması",
 | 
					        "playlist_description": "Oynatma listesi açıklaması",
 | 
				
			||||||
        "show_search_suggestions": "Arama önerilerini göster"
 | 
					        "show_search_suggestions": "Arama önerilerini göster",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Mobilde Bölüm Düzeni",
 | 
				
			||||||
 | 
					        "delete_automatically": "Şundan sonra otomatik olarak sil",
 | 
				
			||||||
 | 
					        "enable_dearrow": "DeArrow'u Etkinleştir",
 | 
				
			||||||
 | 
					        "generate_qrcode": "QR Kodu Oluştur",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "JSON/CSV Dosyasından İçe Aktar",
 | 
				
			||||||
 | 
					        "download_frame": "Kareyi indir",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Gizlilik Politikası",
 | 
				
			||||||
 | 
					        "add_to_group": "Gruba ekle"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "{0} Üzerinde İzle"
 | 
					        "watch_on": "{0} üzerinde görüntüle",
 | 
				
			||||||
 | 
					        "failed": "{0} hata kodu ile başarısız oldu, daha fazla bilgi için günlüklere bakın"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "history": "Geçmiş",
 | 
					        "history": "Geçmiş",
 | 
				
			||||||
| 
						 | 
					@ -133,12 +142,13 @@
 | 
				
			||||||
        "subscriptions": "Abonelikler",
 | 
					        "subscriptions": "Abonelikler",
 | 
				
			||||||
        "playlists": "Oynatma Listeleri",
 | 
					        "playlists": "Oynatma Listeleri",
 | 
				
			||||||
        "account": "Hesap",
 | 
					        "account": "Hesap",
 | 
				
			||||||
        "instance": "Örnek",
 | 
					        "instance": "Sunucu",
 | 
				
			||||||
        "player": "Oynatıcı",
 | 
					        "player": "Oynatıcı",
 | 
				
			||||||
        "livestreams": "Canlı Yayınlar",
 | 
					        "livestreams": "Canlı Yayınlar",
 | 
				
			||||||
        "channels": "Kanallar",
 | 
					        "channels": "Kanallar",
 | 
				
			||||||
        "bookmarks": "Yer İmleri",
 | 
					        "bookmarks": "Yer İmleri",
 | 
				
			||||||
        "channel_groups": "Kanal grupları"
 | 
					        "channel_groups": "Kanal grupları",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "sponsor_segments": "Sponsorlar Bölümleri",
 | 
					        "sponsor_segments": "Sponsorlar Bölümleri",
 | 
				
			||||||
| 
						 | 
					@ -150,13 +160,17 @@
 | 
				
			||||||
        "live": "{0} Canlı",
 | 
					        "live": "{0} Canlı",
 | 
				
			||||||
        "shorts": "Kısa çekimler",
 | 
					        "shorts": "Kısa çekimler",
 | 
				
			||||||
        "all": "Tümü",
 | 
					        "all": "Tümü",
 | 
				
			||||||
        "category": "Kategori"
 | 
					        "category": "Kategori",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Yatay",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Dikey",
 | 
				
			||||||
 | 
					        "license": "Lisans",
 | 
				
			||||||
 | 
					        "visibility": "Görünürlük"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "SSL Puanı",
 | 
					        "ssl_score": "SSL Puanı",
 | 
				
			||||||
        "has_cdn": "CDN Var Mı?",
 | 
					        "has_cdn": "CDN Var Mı?",
 | 
				
			||||||
        "instance_locations": "Örnek Konumları",
 | 
					        "instance_locations": "Sunucu Konumları",
 | 
				
			||||||
        "instance_name": "Örnek Adı",
 | 
					        "instance_name": "Sunucu Adı",
 | 
				
			||||||
        "registered_users": "Kayıtlı Kullanıcılar",
 | 
					        "registered_users": "Kayıtlı Kullanıcılar",
 | 
				
			||||||
        "version": "Sürüm",
 | 
					        "version": "Sürüm",
 | 
				
			||||||
        "up_to_date": "Güncel Mi?"
 | 
					        "up_to_date": "Güncel Mi?"
 | 
				
			||||||
| 
						 | 
					@ -169,7 +183,9 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "Parola",
 | 
					        "password": "Parola",
 | 
				
			||||||
        "username": "Kullanıcı Adı"
 | 
					        "username": "Kullanıcı Adı",
 | 
				
			||||||
 | 
					        "password_confirm": "Parolayı doğrula",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Parolalar eşleşmiyor!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Bunu mu demek istediniz: {0}?",
 | 
					        "did_you_mean": "Bunu mu demek istediniz: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -180,7 +196,8 @@
 | 
				
			||||||
        "videos": "YouTube: Videolar",
 | 
					        "videos": "YouTube: Videolar",
 | 
				
			||||||
        "music_songs": "YT Müzik: Şarkılar",
 | 
					        "music_songs": "YT Müzik: Şarkılar",
 | 
				
			||||||
        "music_videos": "YT Müzik: Videolar",
 | 
					        "music_videos": "YT Müzik: Videolar",
 | 
				
			||||||
        "music_albums": "YT Müzik: Albümler"
 | 
					        "music_albums": "YT Müzik: Albümler",
 | 
				
			||||||
 | 
					        "music_artists": "YT Müzik: Sanatçılar"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Abone Olunan: {0}"
 | 
					        "subscribed_channels_count": "Abone Olunan: {0}"
 | 
				
			||||||
| 
						 | 
					@ -195,6 +212,12 @@
 | 
				
			||||||
        "cannot_copy": "Kopyalanamıyor!",
 | 
					        "cannot_copy": "Kopyalanamıyor!",
 | 
				
			||||||
        "local_storage": "Bu eylem yerel depolama gerektirir, çerezler etkin mi?",
 | 
					        "local_storage": "Bu eylem yerel depolama gerektirir, çerezler etkin mi?",
 | 
				
			||||||
        "register_no_email_note": "Kullanıcı adı olarak e-posta kullanılması tavsiye edilmez. Yine de devam edilsin mi?",
 | 
					        "register_no_email_note": "Kullanıcı adı olarak e-posta kullanılması tavsiye edilmez. Yine de devam edilsin mi?",
 | 
				
			||||||
        "next_video_countdown": "Sonraki video {0}s içinde oynatılıyor"
 | 
					        "next_video_countdown": "Sonraki video {0}s içinde oynatılıyor",
 | 
				
			||||||
 | 
					        "days": "{amount} gün",
 | 
				
			||||||
 | 
					        "months": "{amount} ay",
 | 
				
			||||||
 | 
					        "hours": "{amount} saat",
 | 
				
			||||||
 | 
					        "weeks": "{amount} hafta",
 | 
				
			||||||
 | 
					        "login_note": "Bu sunucuda oluşturulan bir hesapla oturum açın.",
 | 
				
			||||||
 | 
					        "register_note": "Bu Piped sunucusu için bir hesap açın. Bu, aboneliklerinizi ve oynatma listelerinizi hesabınızla eşzamanlamanızı sağlar, böylece sunucu tarafında kaydedilirler. Tüm özellikleri bir hesap olmadan da kullanabilirsiniz, ancak bu şekilde tüm veriler yalnızca tarayıcınızınn yerel önbelleğinde saklanacaktır. Lütfen kullanıcı adı olarak bir e-posta adresi KULLANMADIĞINIZDAN ve başka bir yerde kullanmadığınız güvenli bir parola seçtiğinizden emin olun."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,13 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "Дивитися на {0}"
 | 
					        "watch_on": "Переглянути на {0}",
 | 
				
			||||||
 | 
					        "failed": "Помилка з кодом {0}, дивіться журнали для отримання додаткової інформації"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "Ім'я користувача",
 | 
					        "username": "Ім'я користувача",
 | 
				
			||||||
        "password": "Пароль"
 | 
					        "password": "Пароль",
 | 
				
			||||||
 | 
					        "password_confirm": "Підтвердіть пароль",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "Паролі не збігаються!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "actions": {
 | 
					    "actions": {
 | 
				
			||||||
        "unsubscribe": "Відписатися - {count}",
 | 
					        "unsubscribe": "Відписатися - {count}",
 | 
				
			||||||
| 
						 | 
					@ -23,12 +26,12 @@
 | 
				
			||||||
        "default_homepage": "Домашня сторінка за замовчуванням",
 | 
					        "default_homepage": "Домашня сторінка за замовчуванням",
 | 
				
			||||||
        "show_comments": "Показати коментарі",
 | 
					        "show_comments": "Показати коментарі",
 | 
				
			||||||
        "store_watch_history": "Зберігати історію перегляду",
 | 
					        "store_watch_history": "Зберігати історію перегляду",
 | 
				
			||||||
        "language_selection": "Вибір мови",
 | 
					        "language_selection": "Мова",
 | 
				
			||||||
        "instance_selection": "Вибір екземпляра",
 | 
					        "instance_selection": "Екземпляр",
 | 
				
			||||||
        "show_more": "Показати більше",
 | 
					        "show_more": "Показати більше",
 | 
				
			||||||
        "no": "Ні",
 | 
					        "no": "Ні",
 | 
				
			||||||
        "export_to_json": "Експортувати в JSON",
 | 
					        "export_to_json": "Експортувати в JSON",
 | 
				
			||||||
        "minimize_description": "Згорнути опис",
 | 
					        "minimize_description": "Приховати опис",
 | 
				
			||||||
        "show_recommendations": "Показати рекомендації",
 | 
					        "show_recommendations": "Показати рекомендації",
 | 
				
			||||||
        "enable_lbry_proxy": "Увімкнути проксі для LBRY",
 | 
					        "enable_lbry_proxy": "Увімкнути проксі для LBRY",
 | 
				
			||||||
        "search": "Пошук (Ctrl+K)",
 | 
					        "search": "Пошук (Ctrl+K)",
 | 
				
			||||||
| 
						 | 
					@ -39,7 +42,7 @@
 | 
				
			||||||
        "most_recent": "Найновішими",
 | 
					        "most_recent": "Найновішими",
 | 
				
			||||||
        "channel_name_desc": "Назвою каналу (Я-А)",
 | 
					        "channel_name_desc": "Назвою каналу (Я-А)",
 | 
				
			||||||
        "least_recent": "Найстарішими",
 | 
					        "least_recent": "Найстарішими",
 | 
				
			||||||
        "minimize_recommendations": "Згорнути рекомендації",
 | 
					        "minimize_recommendations": "Приховати рекомендації",
 | 
				
			||||||
        "skip_sponsors": "Пропускати спонсорську рекламу",
 | 
					        "skip_sponsors": "Пропускати спонсорську рекламу",
 | 
				
			||||||
        "skip_interaction": "Пропускати нагадування про взаємодію (підписка)",
 | 
					        "skip_interaction": "Пропускати нагадування про взаємодію (підписка)",
 | 
				
			||||||
        "skip_non_music": "Пропускати сегменти без музики в музикальних відео",
 | 
					        "skip_non_music": "Пропускати сегменти без музики в музикальних відео",
 | 
				
			||||||
| 
						 | 
					@ -50,10 +53,10 @@
 | 
				
			||||||
        "instances_list": "Список екземплярів",
 | 
					        "instances_list": "Список екземплярів",
 | 
				
			||||||
        "enabled_codecs": "Увімкнені кодеки (можна вибрати декілька)",
 | 
					        "enabled_codecs": "Увімкнені кодеки (можна вибрати декілька)",
 | 
				
			||||||
        "default_quality": "Якість за замовчуванням",
 | 
					        "default_quality": "Якість за замовчуванням",
 | 
				
			||||||
        "country_selection": "Вибір країни (для трендів)",
 | 
					        "country_selection": "Країна",
 | 
				
			||||||
        "minimize_description_default": "Не розгортати опис за замовчуванням",
 | 
					        "minimize_description_default": "Не розгортати опис за замовчуванням",
 | 
				
			||||||
        "yes": "Так",
 | 
					        "yes": "Так",
 | 
				
			||||||
        "import_from_json": "Імпортувати з JSON/CSV",
 | 
					        "import_from_json": "Імпортувати з JSON",
 | 
				
			||||||
        "loop_this_video": "Зациклити це відео",
 | 
					        "loop_this_video": "Зациклити це відео",
 | 
				
			||||||
        "auto_play_next_video": "Автоматичне відтворення наступного відео",
 | 
					        "auto_play_next_video": "Автоматичне відтворення наступного відео",
 | 
				
			||||||
        "donations": "Пожертвування на розробку",
 | 
					        "donations": "Пожертвування на розробку",
 | 
				
			||||||
| 
						 | 
					@ -74,7 +77,7 @@
 | 
				
			||||||
        "select_playlist": "Вибрати список відтворення",
 | 
					        "select_playlist": "Вибрати список відтворення",
 | 
				
			||||||
        "please_select_playlist": "Будь ласка, виберіть список відтворення",
 | 
					        "please_select_playlist": "Будь ласка, виберіть список відтворення",
 | 
				
			||||||
        "confirm_reset_preferences": "Ви впевнені, що бажаєте скинути свої налаштування?",
 | 
					        "confirm_reset_preferences": "Ви впевнені, що бажаєте скинути свої налаштування?",
 | 
				
			||||||
        "show_markers": "Показувати маркери на Програвачі",
 | 
					        "show_markers": "Показувати маркери на програвачі",
 | 
				
			||||||
        "minimize_recommendations_default": "Згортати рекомендації за замовчуванням",
 | 
					        "minimize_recommendations_default": "Згортати рекомендації за замовчуванням",
 | 
				
			||||||
        "logout": "Вийти з цього пристрою",
 | 
					        "logout": "Вийти з цього пристрою",
 | 
				
			||||||
        "backup_preferences": "Налаштування резервного копіювання",
 | 
					        "backup_preferences": "Налаштування резервного копіювання",
 | 
				
			||||||
| 
						 | 
					@ -92,9 +95,9 @@
 | 
				
			||||||
        "copy_link": "Копіювати посилання",
 | 
					        "copy_link": "Копіювати посилання",
 | 
				
			||||||
        "store_search_history": "Зберігати історію пошуку",
 | 
					        "store_search_history": "Зберігати історію пошуку",
 | 
				
			||||||
        "documentation": "Документація",
 | 
					        "documentation": "Документація",
 | 
				
			||||||
        "instance_auth_selection": "Вибір екземпляра для автентифікації",
 | 
					        "instance_auth_selection": "Екземпляр для автентифікації",
 | 
				
			||||||
        "minimize_chapters_default": "Згортати розділи за замовчуванням",
 | 
					        "minimize_chapters_default": "Згортати розділи за замовчуванням",
 | 
				
			||||||
        "show_watch_on_youtube": "Показати кнопку Дивитися на YouTube",
 | 
					        "show_watch_on_youtube": "Показувати кнопку Дивитися на YouTube",
 | 
				
			||||||
        "restore_preferences": "Відновити налаштування",
 | 
					        "restore_preferences": "Відновити налаштування",
 | 
				
			||||||
        "different_auth_instance": "Використовувати інший екземпляр для автентифікації",
 | 
					        "different_auth_instance": "Використовувати інший екземпляр для автентифікації",
 | 
				
			||||||
        "clone_playlist_success": "Успішно клоновано!",
 | 
					        "clone_playlist_success": "Успішно клоновано!",
 | 
				
			||||||
| 
						 | 
					@ -104,7 +107,7 @@
 | 
				
			||||||
        "time_code": "Відмітка часу (у секундах)",
 | 
					        "time_code": "Відмітка часу (у секундах)",
 | 
				
			||||||
        "reply_count": "{count} відповідей",
 | 
					        "reply_count": "{count} відповідей",
 | 
				
			||||||
        "minimize_comments_default": "Згортати коментарі за замовчуванням",
 | 
					        "minimize_comments_default": "Згортати коментарі за замовчуванням",
 | 
				
			||||||
        "minimize_comments": "Згорнути коментарі",
 | 
					        "minimize_comments": "Приховати коментарі",
 | 
				
			||||||
        "delete_account": "Видалити обліковий запис",
 | 
					        "delete_account": "Видалити обліковий запис",
 | 
				
			||||||
        "no_valid_playlists": "Файл не містить дійсних списків відтворення!",
 | 
					        "no_valid_playlists": "Файл не містить дійсних списків відтворення!",
 | 
				
			||||||
        "bookmark_playlist": "Закладка",
 | 
					        "bookmark_playlist": "Закладка",
 | 
				
			||||||
| 
						 | 
					@ -124,16 +127,25 @@
 | 
				
			||||||
        "playlist_description": "Опис списку відтворення",
 | 
					        "playlist_description": "Опис списку відтворення",
 | 
				
			||||||
        "auto_display_captions": "Автоматичне відображення субтитрів",
 | 
					        "auto_display_captions": "Автоматичне відображення субтитрів",
 | 
				
			||||||
        "cancel": "Скасувати",
 | 
					        "cancel": "Скасувати",
 | 
				
			||||||
        "show_search_suggestions": "Показати пошукові пропозицій"
 | 
					        "show_search_suggestions": "Показувати пошукові пропозицій",
 | 
				
			||||||
 | 
					        "okay": "Добре",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "Макет розділів на телефоні",
 | 
				
			||||||
 | 
					        "enable_dearrow": "Увімкнути DeArrow",
 | 
				
			||||||
 | 
					        "delete_automatically": "Видаляти автоматично після",
 | 
				
			||||||
 | 
					        "generate_qrcode": "Згенерувати QR-код",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "Імпортувати з JSON/CSV",
 | 
				
			||||||
 | 
					        "download_frame": "Завантажити кадр",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "Політика конфіденційності",
 | 
				
			||||||
 | 
					        "add_to_group": "Додати до групи"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "register": "Реєстрація",
 | 
					        "register": "Зареєструватися",
 | 
				
			||||||
        "feed": "Підписки",
 | 
					        "feed": "Підписки",
 | 
				
			||||||
        "preferences": "Налаштування",
 | 
					        "preferences": "Налаштування",
 | 
				
			||||||
        "history": "Історія перегляду",
 | 
					        "history": "Історія перегляду",
 | 
				
			||||||
        "subscriptions": "Канали, на які ви підписані",
 | 
					        "subscriptions": "Канали, на які ви підписані",
 | 
				
			||||||
        "trending": "Тренди",
 | 
					        "trending": "Тренди",
 | 
				
			||||||
        "login": "Логін",
 | 
					        "login": "Увійти",
 | 
				
			||||||
        "playlists": "Списки відтворення",
 | 
					        "playlists": "Списки відтворення",
 | 
				
			||||||
        "instance": "Екземпляр",
 | 
					        "instance": "Екземпляр",
 | 
				
			||||||
        "player": "Програвач",
 | 
					        "player": "Програвач",
 | 
				
			||||||
| 
						 | 
					@ -141,7 +153,8 @@
 | 
				
			||||||
        "livestreams": "Наживо",
 | 
					        "livestreams": "Наживо",
 | 
				
			||||||
        "channels": "Канали",
 | 
					        "channels": "Канали",
 | 
				
			||||||
        "bookmarks": "Закладки",
 | 
					        "bookmarks": "Закладки",
 | 
				
			||||||
        "channel_groups": "Групи каналів"
 | 
					        "channel_groups": "Групи каналів",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "Прикріплено користувачем {author}",
 | 
					        "pinned_by": "Прикріплено користувачем {author}",
 | 
				
			||||||
| 
						 | 
					@ -168,7 +181,11 @@
 | 
				
			||||||
        "live": "{0} Наживо",
 | 
					        "live": "{0} Наживо",
 | 
				
			||||||
        "shorts": "Shorts",
 | 
					        "shorts": "Shorts",
 | 
				
			||||||
        "all": "Усі",
 | 
					        "all": "Усі",
 | 
				
			||||||
        "category": "Категорія"
 | 
					        "category": "Категорія",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "Горизонтальний",
 | 
				
			||||||
 | 
					        "chapters_vertical": "Вертикальний",
 | 
				
			||||||
 | 
					        "visibility": "Видимість",
 | 
				
			||||||
 | 
					        "license": "Ліцензія"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "Можливо, ви мали на увазі: {0}?",
 | 
					        "did_you_mean": "Можливо, ви мали на увазі: {0}?",
 | 
				
			||||||
| 
						 | 
					@ -179,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music: Пісні",
 | 
					        "music_songs": "YT Music: Пісні",
 | 
				
			||||||
        "music_videos": "TY Music: Відео",
 | 
					        "music_videos": "TY Music: Відео",
 | 
				
			||||||
        "playlists": "YouTube: Списки відтворення",
 | 
					        "playlists": "YouTube: Списки відтворення",
 | 
				
			||||||
        "music_albums": "YT Music: Альбоми"
 | 
					        "music_albums": "YT Music: Альбоми",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: Артисти"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Підписано на: {0}"
 | 
					        "subscribed_channels_count": "Підписано на: {0}"
 | 
				
			||||||
| 
						 | 
					@ -191,6 +209,12 @@
 | 
				
			||||||
        "preferences_note": "Примітка: налаштування зберігаються в локальній пам'яті вашого браузера. Видалення даних браузера призведе до їх скидання.",
 | 
					        "preferences_note": "Примітка: налаштування зберігаються в локальній пам'яті вашого браузера. Видалення даних браузера призведе до їх скидання.",
 | 
				
			||||||
        "local_storage": "Ця дія потребує localStorage, чи ввімкнуті файли cookie?",
 | 
					        "local_storage": "Ця дія потребує localStorage, чи ввімкнуті файли cookie?",
 | 
				
			||||||
        "register_no_email_note": "Використання електронної пошти як імені користувача не рекомендується. Все одно продовжити?",
 | 
					        "register_no_email_note": "Використання електронної пошти як імені користувача не рекомендується. Все одно продовжити?",
 | 
				
			||||||
        "next_video_countdown": "Наступне відео через {0} секунд"
 | 
					        "next_video_countdown": "Наступне відео через {0} секунд",
 | 
				
			||||||
 | 
					        "weeks": "{amount} тиждень(-і)",
 | 
				
			||||||
 | 
					        "hours": "{amount} годин(-и)",
 | 
				
			||||||
 | 
					        "months": "{amount} місяць(-і)",
 | 
				
			||||||
 | 
					        "days": "{amount} день(-і)",
 | 
				
			||||||
 | 
					        "login_note": "Увійдіть за допомогою облікового запису, створеного на цьому екземплярі.",
 | 
				
			||||||
 | 
					        "register_note": "Зареєструйте обліковий запис для цього екземпляра Piped. Це дозволить вам синхронізувати підписки та списки відтворення з вашим обліковим записом, щоб вони зберігалися на сервері. Ви також можете використовувати всі доступні функції без облікового запису, але всі дані будуть зберігатися в локальному кеші вашого браузера. Будь ласка, переконайтеся, що ви НЕ використовуєте свою адресу електронної пошти як ім'я користувача й обрали надійний пароль, який ви не використовуєте в інших місцях."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
        "subscribe": "Đăng ký - {count}",
 | 
					        "subscribe": "Đăng ký - {count}",
 | 
				
			||||||
        "autoplay_video": "Video tự động phát",
 | 
					        "autoplay_video": "Video tự động phát",
 | 
				
			||||||
        "unsubscribe": "Hủy đăng ký - {count}",
 | 
					        "unsubscribe": "Hủy đăng ký - {count}",
 | 
				
			||||||
        "view_subscriptions": "Lượt đăng ký",
 | 
					        "view_subscriptions": "Xem những kênh đã đăng ký",
 | 
				
			||||||
        "sort_by": "Sắp xếp theo:",
 | 
					        "sort_by": "Sắp xếp theo:",
 | 
				
			||||||
        "most_recent": "Gần đây nhất",
 | 
					        "most_recent": "Gần đây nhất",
 | 
				
			||||||
        "channel_name_asc": "Tên kênh (A-Z)",
 | 
					        "channel_name_asc": "Tên kênh (A-Z)",
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
        "theme": "Giao diện",
 | 
					        "theme": "Giao diện",
 | 
				
			||||||
        "auto": "Tự động",
 | 
					        "auto": "Tự động",
 | 
				
			||||||
        "buffering_goal": "Bộ nhớ đệm (tính bằng giây)",
 | 
					        "buffering_goal": "Bộ nhớ đệm (tính bằng giây)",
 | 
				
			||||||
        "least_recent": "Ít nhất gần đây",
 | 
					        "least_recent": "Cũ nhất",
 | 
				
			||||||
        "skip_intro": "Bỏ qua gián đoạn/hoạt hình intro",
 | 
					        "skip_intro": "Bỏ qua gián đoạn/hoạt hình intro",
 | 
				
			||||||
        "skip_outro": "Bỏ qua màn hình kết thúc/danh đề",
 | 
					        "skip_outro": "Bỏ qua màn hình kết thúc/danh đề",
 | 
				
			||||||
        "skip_interaction": "Bỏ qua lời nhắc tương tác (Đăng ký)",
 | 
					        "skip_interaction": "Bỏ qua lời nhắc tương tác (Đăng ký)",
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@
 | 
				
			||||||
        "disable_lbry": "Tắt LBRY để phát trực tuyến",
 | 
					        "disable_lbry": "Tắt LBRY để phát trực tuyến",
 | 
				
			||||||
        "enable_lbry_proxy": "Bật proxy cho LBRY",
 | 
					        "enable_lbry_proxy": "Bật proxy cho LBRY",
 | 
				
			||||||
        "view_ssl_score": "Hiện thị điểm số SSL",
 | 
					        "view_ssl_score": "Hiện thị điểm số SSL",
 | 
				
			||||||
        "search": "Tìm kiếm",
 | 
					        "search": "Tìm kiếm (Ctrl+K)",
 | 
				
			||||||
        "filter": "Bộ lọc",
 | 
					        "filter": "Bộ lọc",
 | 
				
			||||||
        "loading": "Đang tải...",
 | 
					        "loading": "Đang tải...",
 | 
				
			||||||
        "clear_history": "Xóa lịch sử",
 | 
					        "clear_history": "Xóa lịch sử",
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,11 @@
 | 
				
			||||||
        "minimize_comments": "Thu nhỏ bình luận",
 | 
					        "minimize_comments": "Thu nhỏ bình luận",
 | 
				
			||||||
        "reply_count": "{count} phản hồi",
 | 
					        "reply_count": "{count} phản hồi",
 | 
				
			||||||
        "status_page": "Trạng thái",
 | 
					        "status_page": "Trạng thái",
 | 
				
			||||||
        "skip_automatically": "Tự động"
 | 
					        "skip_automatically": "Tự động",
 | 
				
			||||||
 | 
					        "show_chapters": "Chương",
 | 
				
			||||||
 | 
					        "show_less": "Hiển thị ít hơn",
 | 
				
			||||||
 | 
					        "cancel": "Hủy",
 | 
				
			||||||
 | 
					        "okay": "OK"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "register": "Đăng ký",
 | 
					        "register": "Đăng ký",
 | 
				
			||||||
| 
						 | 
					@ -142,7 +146,8 @@
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "copied": "Đã sao chép!",
 | 
					        "copied": "Đã sao chép!",
 | 
				
			||||||
        "cannot_copy": "Không thể sao chép!",
 | 
					        "cannot_copy": "Không thể sao chép!",
 | 
				
			||||||
        "page_not_found": "Không tìm thấy trang"
 | 
					        "page_not_found": "Không tìm thấy trang",
 | 
				
			||||||
 | 
					        "next_video_countdown": "Tự động phát video tiếp theo trong {0} giây"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "Đã đăng ký cho: {0}"
 | 
					        "subscribed_channels_count": "Đã đăng ký cho: {0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,20 +9,20 @@
 | 
				
			||||||
        "donations": "开发捐赠",
 | 
					        "donations": "开发捐赠",
 | 
				
			||||||
        "auto_play_next_video": "自动播放下一个视频",
 | 
					        "auto_play_next_video": "自动播放下一个视频",
 | 
				
			||||||
        "loop_this_video": "循环播放此视频",
 | 
					        "loop_this_video": "循环播放此视频",
 | 
				
			||||||
        "import_from_json": "从 JSON/CSV 导入",
 | 
					        "import_from_json": "从 JSON 导入",
 | 
				
			||||||
        "export_to_json": "导出为 JSON",
 | 
					        "export_to_json": "导出为 JSON",
 | 
				
			||||||
        "no": "否",
 | 
					        "no": "否",
 | 
				
			||||||
        "yes": "是",
 | 
					        "yes": "是",
 | 
				
			||||||
        "show_more": "显示更多",
 | 
					        "show_more": "显示更多",
 | 
				
			||||||
        "instance_selection": "实例选择",
 | 
					        "instance_selection": "实例",
 | 
				
			||||||
        "enabled_codecs": "启用的编解码器 (多个)",
 | 
					        "enabled_codecs": "启用的编解码器 (多个)",
 | 
				
			||||||
        "instances_list": "实例列表",
 | 
					        "instances_list": "实例列表",
 | 
				
			||||||
        "language_selection": "语言选择",
 | 
					        "language_selection": "语言",
 | 
				
			||||||
        "store_watch_history": "保存观看历史",
 | 
					        "store_watch_history": "保存观看历史",
 | 
				
			||||||
        "minimize_description_default": "默认情况下折叠描述",
 | 
					        "minimize_description_default": "默认情况下折叠描述",
 | 
				
			||||||
        "show_comments": "显示评论",
 | 
					        "show_comments": "显示评论",
 | 
				
			||||||
        "default_homepage": "默认主页",
 | 
					        "default_homepage": "默认主页",
 | 
				
			||||||
        "country_selection": "国家/地区选择",
 | 
					        "country_selection": "国家/地区",
 | 
				
			||||||
        "buffering_goal": "缓冲目标 (以秒为单位)",
 | 
					        "buffering_goal": "缓冲目标 (以秒为单位)",
 | 
				
			||||||
        "default_quality": "默认质量",
 | 
					        "default_quality": "默认质量",
 | 
				
			||||||
        "audio_only": "仅音频",
 | 
					        "audio_only": "仅音频",
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@
 | 
				
			||||||
        "minimize_recommendations_default": "默认最小化推荐",
 | 
					        "minimize_recommendations_default": "默认最小化推荐",
 | 
				
			||||||
        "invalidate_session": "注销所有设备",
 | 
					        "invalidate_session": "注销所有设备",
 | 
				
			||||||
        "different_auth_instance": "使用不同的实例进行身份验证",
 | 
					        "different_auth_instance": "使用不同的实例进行身份验证",
 | 
				
			||||||
        "instance_auth_selection": "身份验证实例选择",
 | 
					        "instance_auth_selection": "身份验证实例",
 | 
				
			||||||
        "clone_playlist": "克隆播放列表",
 | 
					        "clone_playlist": "克隆播放列表",
 | 
				
			||||||
        "clone_playlist_success": "克隆成功!",
 | 
					        "clone_playlist_success": "克隆成功!",
 | 
				
			||||||
        "download_as_txt": "下载为 .txt",
 | 
					        "download_as_txt": "下载为 .txt",
 | 
				
			||||||
| 
						 | 
					@ -119,7 +119,14 @@
 | 
				
			||||||
        "okay": "好的",
 | 
					        "okay": "好的",
 | 
				
			||||||
        "edit_playlist": "编辑播放列表",
 | 
					        "edit_playlist": "编辑播放列表",
 | 
				
			||||||
        "show_search_suggestions": "显示搜索建议",
 | 
					        "show_search_suggestions": "显示搜索建议",
 | 
				
			||||||
        "chapters_layout_mobile": "移动设备上的章节布局"
 | 
					        "chapters_layout_mobile": "移动设备上的章节布局",
 | 
				
			||||||
 | 
					        "delete_automatically": "多久后自动删除",
 | 
				
			||||||
 | 
					        "enable_dearrow": "启用 DeArrow",
 | 
				
			||||||
 | 
					        "generate_qrcode": "生成二维码",
 | 
				
			||||||
 | 
					        "import_from_json_csv": "从 JSON/CSV 文件导入",
 | 
				
			||||||
 | 
					        "download_frame": "下载视频帧",
 | 
				
			||||||
 | 
					        "instance_privacy_policy": "隐私政策",
 | 
				
			||||||
 | 
					        "add_to_group": "添加到组"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "sponsor_segments": "赞助商部分",
 | 
					        "sponsor_segments": "赞助商部分",
 | 
				
			||||||
| 
						 | 
					@ -133,7 +140,9 @@
 | 
				
			||||||
        "all": "全部",
 | 
					        "all": "全部",
 | 
				
			||||||
        "category": "类别",
 | 
					        "category": "类别",
 | 
				
			||||||
        "chapters_horizontal": "水平",
 | 
					        "chapters_horizontal": "水平",
 | 
				
			||||||
        "chapters_vertical": "垂直"
 | 
					        "chapters_vertical": "垂直",
 | 
				
			||||||
 | 
					        "license": "许可证",
 | 
				
			||||||
 | 
					        "visibility": "可见性"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "ssl_score": "SSL 分数",
 | 
					        "ssl_score": "SSL 分数",
 | 
				
			||||||
| 
						 | 
					@ -151,7 +160,8 @@
 | 
				
			||||||
        "user_disabled": "评论在设置中被禁用。"
 | 
					        "user_disabled": "评论在设置中被禁用。"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "player": {
 | 
					    "player": {
 | 
				
			||||||
        "watch_on": "在 {0} 观看"
 | 
					        "watch_on": "在 {0} 观看",
 | 
				
			||||||
 | 
					        "failed": "播放器出错,错误代码 {0},查看日志了解更多信息"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "feed": "订阅流",
 | 
					        "feed": "订阅流",
 | 
				
			||||||
| 
						 | 
					@ -168,11 +178,14 @@
 | 
				
			||||||
        "livestreams": "直播",
 | 
					        "livestreams": "直播",
 | 
				
			||||||
        "channels": "频道",
 | 
					        "channels": "频道",
 | 
				
			||||||
        "bookmarks": "书签",
 | 
					        "bookmarks": "书签",
 | 
				
			||||||
        "channel_groups": "频道组"
 | 
					        "channel_groups": "频道组",
 | 
				
			||||||
 | 
					        "dearrow": "DeArrow"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "password": "密码",
 | 
					        "password": "密码",
 | 
				
			||||||
        "username": "帐号"
 | 
					        "username": "帐号",
 | 
				
			||||||
 | 
					        "password_confirm": "确认密码",
 | 
				
			||||||
 | 
					        "passwords_incorrect": "密码不匹配!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "你是指 {0} 吗?",
 | 
					        "did_you_mean": "你是指 {0} 吗?",
 | 
				
			||||||
| 
						 | 
					@ -183,7 +196,8 @@
 | 
				
			||||||
        "music_songs": "YT Music:歌曲",
 | 
					        "music_songs": "YT Music:歌曲",
 | 
				
			||||||
        "music_videos": "YT Music:视频",
 | 
					        "music_videos": "YT Music:视频",
 | 
				
			||||||
        "music_albums": "YT Music:专辑",
 | 
					        "music_albums": "YT Music:专辑",
 | 
				
			||||||
        "music_playlists": "YT Music:播放列表"
 | 
					        "music_playlists": "YT Music:播放列表",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music:艺人"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "subscriptions": {
 | 
					    "subscriptions": {
 | 
				
			||||||
        "subscribed_channels_count": "已订阅:{0}"
 | 
					        "subscribed_channels_count": "已订阅:{0}"
 | 
				
			||||||
| 
						 | 
					@ -198,6 +212,12 @@
 | 
				
			||||||
        "cannot_copy": "无法复制!",
 | 
					        "cannot_copy": "无法复制!",
 | 
				
			||||||
        "local_storage": "此操作需要本地存储,是否启用了Cookie?",
 | 
					        "local_storage": "此操作需要本地存储,是否启用了Cookie?",
 | 
				
			||||||
        "register_no_email_note": "不建议使用电子邮件作为用户名。仍要继续吗?",
 | 
					        "register_no_email_note": "不建议使用电子邮件作为用户名。仍要继续吗?",
 | 
				
			||||||
        "next_video_countdown": "在{0}秒后播放下一个视频"
 | 
					        "next_video_countdown": "在{0}秒后播放下一个视频",
 | 
				
			||||||
 | 
					        "days": "{amount} 天",
 | 
				
			||||||
 | 
					        "weeks": "{amount} 周",
 | 
				
			||||||
 | 
					        "months": "{amount} 个月",
 | 
				
			||||||
 | 
					        "hours": "{amount} 小时",
 | 
				
			||||||
 | 
					        "login_note": "使用在此实例上创建的账户登录。",
 | 
				
			||||||
 | 
					        "register_note": "在这个 Piped 实例上注册一个账户。你可以通过账户同步订阅和播放列表,所有数据存储在实例的服务器上。没有账户你也可以使用所有功能,但数据均储存在你所用浏览器的本地缓存中。请确保你没有将电子邮箱地址用作账户的用户名,并选择一个你不在其他地方使用的安全密码。"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,14 +3,14 @@
 | 
				
			||||||
        "skip_intro": "跳過中場休息/開場動畫",
 | 
					        "skip_intro": "跳過中場休息/開場動畫",
 | 
				
			||||||
        "skip_sponsors": "跳過贊助廣告",
 | 
					        "skip_sponsors": "跳過贊助廣告",
 | 
				
			||||||
        "enable_sponsorblock": "啟用 SponsorBlock",
 | 
					        "enable_sponsorblock": "啟用 SponsorBlock",
 | 
				
			||||||
        "country_selection": "選擇國家",
 | 
					        "country_selection": "國家",
 | 
				
			||||||
        "channel_name_desc": "頻道名 (Z-A)",
 | 
					        "channel_name_desc": "頻道名 (Z-A)",
 | 
				
			||||||
        "channel_name_asc": "頻道名 (A-Z)",
 | 
					        "channel_name_asc": "頻道名 (A-Z)",
 | 
				
			||||||
        "unsubscribe": "取消訂閱 - {count}",
 | 
					        "unsubscribe": "取消訂閱 - {count}",
 | 
				
			||||||
        "subscribe": "訂閱 - {count}",
 | 
					        "subscribe": "訂閱 - {count}",
 | 
				
			||||||
        "sort_by": "排序依據:",
 | 
					        "sort_by": "排序依據:",
 | 
				
			||||||
        "view_subscriptions": "檢視訂閱內容",
 | 
					        "view_subscriptions": "檢視訂閱內容",
 | 
				
			||||||
        "language_selection": "選擇語言",
 | 
					        "language_selection": "語言",
 | 
				
			||||||
        "skip_non_music": "跳過音樂中的非音樂片段",
 | 
					        "skip_non_music": "跳過音樂中的非音樂片段",
 | 
				
			||||||
        "theme": "主題",
 | 
					        "theme": "主題",
 | 
				
			||||||
        "show_description": "顯示說明",
 | 
					        "show_description": "顯示說明",
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@
 | 
				
			||||||
        "default_homepage": "預設首頁",
 | 
					        "default_homepage": "預設首頁",
 | 
				
			||||||
        "store_watch_history": "儲存觀看記錄",
 | 
					        "store_watch_history": "儲存觀看記錄",
 | 
				
			||||||
        "minimize_description": "最小化說明",
 | 
					        "minimize_description": "最小化說明",
 | 
				
			||||||
        "search": "搜尋",
 | 
					        "search": "搜尋 (Ctrl+K)",
 | 
				
			||||||
        "show_recommendations": "顯示推薦",
 | 
					        "show_recommendations": "顯示推薦",
 | 
				
			||||||
        "minimize_recommendations": "最小化推薦",
 | 
					        "minimize_recommendations": "最小化推薦",
 | 
				
			||||||
        "most_recent": "最新",
 | 
					        "most_recent": "最新",
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@
 | 
				
			||||||
        "import_from_json": "從 JSON/CSV 匯入",
 | 
					        "import_from_json": "從 JSON/CSV 匯入",
 | 
				
			||||||
        "loop_this_video": "循環播放此影片",
 | 
					        "loop_this_video": "循環播放此影片",
 | 
				
			||||||
        "auto_play_next_video": "自動播放下一部影片",
 | 
					        "auto_play_next_video": "自動播放下一部影片",
 | 
				
			||||||
        "donations": "捐款",
 | 
					        "donations": "開發捐款",
 | 
				
			||||||
        "filter": "篩選",
 | 
					        "filter": "篩選",
 | 
				
			||||||
        "loading": "載入中……",
 | 
					        "loading": "載入中……",
 | 
				
			||||||
        "clear_history": "清除記錄",
 | 
					        "clear_history": "清除記錄",
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@
 | 
				
			||||||
        "delete_playlist_confirm": "要刪除這份播放清單嗎?",
 | 
					        "delete_playlist_confirm": "要刪除這份播放清單嗎?",
 | 
				
			||||||
        "please_select_playlist": "請選擇播放清單",
 | 
					        "please_select_playlist": "請選擇播放清單",
 | 
				
			||||||
        "select_playlist": "選擇播放清單",
 | 
					        "select_playlist": "選擇播放清單",
 | 
				
			||||||
        "add_to_playlist": "加到播放清單",
 | 
					        "add_to_playlist": "增至播放清單",
 | 
				
			||||||
        "delete_playlist_video_confirm": "要從播放清單中移除影片嗎?",
 | 
					        "delete_playlist_video_confirm": "要從播放清單中移除影片嗎?",
 | 
				
			||||||
        "delete_account": "刪除帳戶",
 | 
					        "delete_account": "刪除帳戶",
 | 
				
			||||||
        "show_chapters": "章節",
 | 
					        "show_chapters": "章節",
 | 
				
			||||||
| 
						 | 
					@ -67,7 +67,60 @@
 | 
				
			||||||
        "confirm_reset_preferences": "確定要重設偏好設定嗎?",
 | 
					        "confirm_reset_preferences": "確定要重設偏好設定嗎?",
 | 
				
			||||||
        "backup_preferences": "備份偏好設定",
 | 
					        "backup_preferences": "備份偏好設定",
 | 
				
			||||||
        "restore_preferences": "復原偏好設定",
 | 
					        "restore_preferences": "復原偏好設定",
 | 
				
			||||||
        "back_to_home": "回首頁"
 | 
					        "back_to_home": "回首頁",
 | 
				
			||||||
 | 
					        "uses_api_from": "使用此API ",
 | 
				
			||||||
 | 
					        "instances_list": "站台列表",
 | 
				
			||||||
 | 
					        "show_markers": "在播放器上顯示標記",
 | 
				
			||||||
 | 
					        "skip_button_only": "顯示跳過按鈕",
 | 
				
			||||||
 | 
					        "skip_filler_tangent": "跳過與影片無關的片段",
 | 
				
			||||||
 | 
					        "autoplay_next_countdown": "預設播放下一段影片前的倒數時間(秒)",
 | 
				
			||||||
 | 
					        "min_segment_length": "最短分段的長度(秒)",
 | 
				
			||||||
 | 
					        "auto_display_captions": "自動顯示字幕",
 | 
				
			||||||
 | 
					        "minimize_comments": "收起留言",
 | 
				
			||||||
 | 
					        "disable_lbry": "不使用 LBRY 作為傳輸媒介",
 | 
				
			||||||
 | 
					        "enable_lbry_proxy": "使用 LBRY 作為代理伺服器",
 | 
				
			||||||
 | 
					        "view_ssl_score": "查看 SSL 分數",
 | 
				
			||||||
 | 
					        "logout": "從這個設置登出",
 | 
				
			||||||
 | 
					        "minimize_comments_default": "預設為收起留言",
 | 
				
			||||||
 | 
					        "instance_selection": "選擇站台",
 | 
				
			||||||
 | 
					        "skip_automatically": "自動",
 | 
				
			||||||
 | 
					        "skip_segment": "跳過分段",
 | 
				
			||||||
 | 
					        "edit_playlist": "編輯播放清單",
 | 
				
			||||||
 | 
					        "playlist_name": "播放清單名稱",
 | 
				
			||||||
 | 
					        "instance_auth_selection": "選擇身分驗證站台",
 | 
				
			||||||
 | 
					        "instance_donations": "站台捐款",
 | 
				
			||||||
 | 
					        "invalidate_session": "從所有裝置登出",
 | 
				
			||||||
 | 
					        "clone_playlist": "複製播放清單",
 | 
				
			||||||
 | 
					        "clone_playlist_success": "複製成功!",
 | 
				
			||||||
 | 
					        "different_auth_instance": "使用其他站台進行身分驗證",
 | 
				
			||||||
 | 
					        "with_timecode": "以時間碼分享",
 | 
				
			||||||
 | 
					        "follow_link": "追隨連結",
 | 
				
			||||||
 | 
					        "store_search_history": "儲存搜尋歷史",
 | 
				
			||||||
 | 
					        "okay": "好的",
 | 
				
			||||||
 | 
					        "no_valid_playlists": "此檔案不包含有效的播放清單!",
 | 
				
			||||||
 | 
					        "piped_link": "Piped 連結",
 | 
				
			||||||
 | 
					        "cancel": "取消",
 | 
				
			||||||
 | 
					        "playlist_description": "播放清單描述",
 | 
				
			||||||
 | 
					        "status_page": "狀態",
 | 
				
			||||||
 | 
					        "source_code": "原始碼",
 | 
				
			||||||
 | 
					        "bookmark_playlist": "書籤",
 | 
				
			||||||
 | 
					        "documentation": "文件",
 | 
				
			||||||
 | 
					        "with_playlist": "以播放清單分享",
 | 
				
			||||||
 | 
					        "playlist_bookmarked": "已存入書籤",
 | 
				
			||||||
 | 
					        "create_group": "建立群組",
 | 
				
			||||||
 | 
					        "group_name": "群組名稱",
 | 
				
			||||||
 | 
					        "show_search_suggestions": "顯示搜尋建議",
 | 
				
			||||||
 | 
					        "copy_link": "複製連結",
 | 
				
			||||||
 | 
					        "time_code": "時間碼(以秒計算)",
 | 
				
			||||||
 | 
					        "minimize_chapters_default": "預設收起章節",
 | 
				
			||||||
 | 
					        "dismiss": "解散",
 | 
				
			||||||
 | 
					        "chapters_layout_mobile": "在手提裝置上的章節佈局",
 | 
				
			||||||
 | 
					        "hide_watched": "在摘要中隱藏看過的影片",
 | 
				
			||||||
 | 
					        "reply_count": "{count} 個回覆",
 | 
				
			||||||
 | 
					        "minimize_recommendations_default": "預設收起推薦影片",
 | 
				
			||||||
 | 
					        "show_watch_on_youtube": "顯示「在Youtube 觀看」按鈕",
 | 
				
			||||||
 | 
					        "show_less": "顯示更少",
 | 
				
			||||||
 | 
					        "generate_qrcode": "產生 QR 碼"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "titles": {
 | 
					    "titles": {
 | 
				
			||||||
        "history": "歷史記錄",
 | 
					        "history": "歷史記錄",
 | 
				
			||||||
| 
						 | 
					@ -79,23 +132,40 @@
 | 
				
			||||||
        "subscriptions": "訂閱內容",
 | 
					        "subscriptions": "訂閱內容",
 | 
				
			||||||
        "playlists": "播放清單",
 | 
					        "playlists": "播放清單",
 | 
				
			||||||
        "account": "帳戶",
 | 
					        "account": "帳戶",
 | 
				
			||||||
        "player": "播放器"
 | 
					        "player": "播放器",
 | 
				
			||||||
 | 
					        "instance": "站台",
 | 
				
			||||||
 | 
					        "bookmarks": "書籤",
 | 
				
			||||||
 | 
					        "livestreams": "直播",
 | 
				
			||||||
 | 
					        "channels": "頻道",
 | 
				
			||||||
 | 
					        "channel_groups": "頻道群組"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "preferences": {
 | 
					    "preferences": {
 | 
				
			||||||
        "registered_users": "已註冊的使用者",
 | 
					        "registered_users": "已註冊的使用者",
 | 
				
			||||||
        "version": "版本",
 | 
					        "version": "版本",
 | 
				
			||||||
        "has_cdn": "是否有 CDN?"
 | 
					        "has_cdn": "是否有 CDN?",
 | 
				
			||||||
 | 
					        "instance_name": "站台名稱",
 | 
				
			||||||
 | 
					        "instance_locations": "站台位置",
 | 
				
			||||||
 | 
					        "up_to_date": "最新?",
 | 
				
			||||||
 | 
					        "ssl_score": "SSL 分數"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "login": {
 | 
					    "login": {
 | 
				
			||||||
        "username": "使用者名",
 | 
					        "username": "使用者名",
 | 
				
			||||||
        "password": "密碼"
 | 
					        "password": "密碼",
 | 
				
			||||||
 | 
					        "password_confirm": "確認密碼"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "video": {
 | 
					    "video": {
 | 
				
			||||||
        "videos": "影片",
 | 
					        "videos": "影片",
 | 
				
			||||||
        "watched": "有看過",
 | 
					        "watched": "有看過",
 | 
				
			||||||
        "sponsor_segments": "贊助廣告片段",
 | 
					        "sponsor_segments": "贊助廣告片段",
 | 
				
			||||||
        "ratings_disabled": "評價已停用",
 | 
					        "ratings_disabled": "評價已停用",
 | 
				
			||||||
        "chapters": "章節"
 | 
					        "chapters": "章節",
 | 
				
			||||||
 | 
					        "shorts": "短影片",
 | 
				
			||||||
 | 
					        "category": "類別",
 | 
				
			||||||
 | 
					        "chapters_horizontal": "水平",
 | 
				
			||||||
 | 
					        "chapters_vertical": "垂直",
 | 
				
			||||||
 | 
					        "views": "觀看次數:{views}",
 | 
				
			||||||
 | 
					        "live": "{0} 直播",
 | 
				
			||||||
 | 
					        "all": "全部"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "search": {
 | 
					    "search": {
 | 
				
			||||||
        "did_you_mean": "您是否想找 {0}?",
 | 
					        "did_you_mean": "您是否想找 {0}?",
 | 
				
			||||||
| 
						 | 
					@ -106,17 +176,32 @@
 | 
				
			||||||
        "playlists": "YouTube:播放清單",
 | 
					        "playlists": "YouTube:播放清單",
 | 
				
			||||||
        "music_songs": "YT Music:歌曲",
 | 
					        "music_songs": "YT Music:歌曲",
 | 
				
			||||||
        "music_videos": "YT Music:影片",
 | 
					        "music_videos": "YT Music:影片",
 | 
				
			||||||
        "music_albums": "YT Music:專輯"
 | 
					        "music_albums": "YT Music:專輯",
 | 
				
			||||||
 | 
					        "music_artists": "YT Music: 藝人"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "comment": {
 | 
					    "comment": {
 | 
				
			||||||
        "pinned_by": "置頂者: {author}",
 | 
					        "pinned_by": "置頂者: {author}",
 | 
				
			||||||
        "disabled": "上傳者停用了留言功能。",
 | 
					        "disabled": "上傳者停用了留言功能。",
 | 
				
			||||||
        "loading": "留言載入中……"
 | 
					        "loading": "留言載入中……",
 | 
				
			||||||
 | 
					        "user_disabled": "留言在設置中被關閉。"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "info": {
 | 
					    "info": {
 | 
				
			||||||
        "copied": "已複製!",
 | 
					        "copied": "已複製!",
 | 
				
			||||||
        "cannot_copy": "無法複製!",
 | 
					        "cannot_copy": "無法複製!",
 | 
				
			||||||
        "page_not_found": "找不到頁面",
 | 
					        "page_not_found": "找不到頁面",
 | 
				
			||||||
        "preferences_note": "註:偏好設定儲存在本機的瀏覽器的儲存空間內。如果清除瀏覽器的資料,偏好設定就會重設。"
 | 
					        "preferences_note": "註:偏好設定儲存在本機的瀏覽器的儲存空間內。如果清除瀏覽器的資料,偏好設定就會重設。",
 | 
				
			||||||
 | 
					        "register_no_email_note": "不建議使用電子郵件地址作為用戶名稱。仍要繼續嗎?",
 | 
				
			||||||
 | 
					        "local_storage": "此動作需要儲存資料在本機,請確認是否啟用了Cookies?",
 | 
				
			||||||
 | 
					        "next_video_countdown": "在{0}秒後播放下一段影片",
 | 
				
			||||||
 | 
					        "days": "{amount} 天",
 | 
				
			||||||
 | 
					        "weeks": "{amount} 個星期",
 | 
				
			||||||
 | 
					        "months": "{amount} 個月",
 | 
				
			||||||
 | 
					        "hours": "{amount} 小時"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "player": {
 | 
				
			||||||
 | 
					        "watch_on": "在 {0} 觀看"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "subscriptions": {
 | 
				
			||||||
 | 
					        "subscribed_channels_count": "己訂閱: {0}"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										321
									
								
								src/main.js
									
										
									
									
									
								
							
							
						
						
									
										321
									
								
								src/main.js
									
										
									
									
									
								
							| 
						 | 
					@ -56,8 +56,6 @@ library.add(
 | 
				
			||||||
import router from "@/router/router.js";
 | 
					import router from "@/router/router.js";
 | 
				
			||||||
import App from "./App.vue";
 | 
					import App from "./App.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import DOMPurify from "dompurify";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import TimeAgo from "javascript-time-ago";
 | 
					import TimeAgo from "javascript-time-ago";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import en from "javascript-time-ago/locale/en";
 | 
					import en from "javascript-time-ago/locale/en";
 | 
				
			||||||
| 
						 | 
					@ -120,9 +118,6 @@ const mixin = {
 | 
				
			||||||
                return response.json();
 | 
					                return response.json();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        purifyHTML(original) {
 | 
					 | 
				
			||||||
            return DOMPurify.sanitize(original);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        setPreference(key, value, disableAlert = false) {
 | 
					        setPreference(key, value, disableAlert = false) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                localStorage.setItem(key, value);
 | 
					                localStorage.setItem(key, value);
 | 
				
			||||||
| 
						 | 
					@ -195,19 +190,6 @@ const mixin = {
 | 
				
			||||||
        timeAgo(time) {
 | 
					        timeAgo(time) {
 | 
				
			||||||
            return timeAgo.format(time);
 | 
					            return timeAgo.format(time);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        urlify(string) {
 | 
					 | 
				
			||||||
            if (!string) return "";
 | 
					 | 
				
			||||||
            const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
 | 
					 | 
				
			||||||
            const emailRegex = /([\w-\\.]+@(?:[\w-]+\.)+[\w-]{2,4})/g;
 | 
					 | 
				
			||||||
            return string
 | 
					 | 
				
			||||||
                .replace(urlRegex, url => {
 | 
					 | 
				
			||||||
                    if (url.endsWith("</a>") || url.endsWith("<a")) return url;
 | 
					 | 
				
			||||||
                    return `<a href="${url}" target="_blank">${url}</a>`;
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .replace(emailRegex, email => {
 | 
					 | 
				
			||||||
                    return `<a href="mailto:${email}">${email}</a>`;
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        async updateWatched(videos) {
 | 
					        async updateWatched(videos) {
 | 
				
			||||||
            if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
					            if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
				
			||||||
                var tx = window.db.transaction("watch_history", "readonly");
 | 
					                var tx = window.db.transaction("watch_history", "readonly");
 | 
				
			||||||
| 
						 | 
					@ -265,20 +247,26 @@ const mixin = {
 | 
				
			||||||
            elem.click();
 | 
					            elem.click();
 | 
				
			||||||
            elem.remove();
 | 
					            elem.remove();
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        rewriteDescription(text) {
 | 
					        async getChannelGroups() {
 | 
				
			||||||
            return this.urlify(text)
 | 
					            return new Promise(resolve => {
 | 
				
			||||||
                .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
 | 
					                let channelGroups = [];
 | 
				
			||||||
                .replaceAll(
 | 
					                var tx = window.db.transaction("channel_groups", "readonly");
 | 
				
			||||||
                    /(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm,
 | 
					                var store = tx.objectStore("channel_groups");
 | 
				
			||||||
                    "/watch?v=$1",
 | 
					                const cursor = store.index("groupName").openCursor();
 | 
				
			||||||
                )
 | 
					                cursor.onsuccess = e => {
 | 
				
			||||||
                .replaceAll("\n", "<br>");
 | 
					                    const cursor = e.target.result;
 | 
				
			||||||
        },
 | 
					                    if (cursor) {
 | 
				
			||||||
        getChannelGroupsCursor() {
 | 
					                        const group = cursor.value;
 | 
				
			||||||
            if (!window.db) return;
 | 
					                        channelGroups.push({
 | 
				
			||||||
            var tx = window.db.transaction("channel_groups", "readonly");
 | 
					                            groupName: group.groupName,
 | 
				
			||||||
            var store = tx.objectStore("channel_groups");
 | 
					                            channels: JSON.parse(group.channels),
 | 
				
			||||||
            return store.index("groupName").openCursor();
 | 
					                        });
 | 
				
			||||||
 | 
					                        cursor.continue();
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        resolve(channelGroups);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        createOrUpdateChannelGroup(group) {
 | 
					        createOrUpdateChannelGroup(group) {
 | 
				
			||||||
            var tx = window.db.transaction("channel_groups", "readwrite");
 | 
					            var tx = window.db.transaction("channel_groups", "readwrite");
 | 
				
			||||||
| 
						 | 
					@ -293,6 +281,275 @@ const mixin = {
 | 
				
			||||||
            var store = tx.objectStore("channel_groups");
 | 
					            var store = tx.objectStore("channel_groups");
 | 
				
			||||||
            store.delete(groupName);
 | 
					            store.delete(groupName);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        async getLocalPlaylist(playlistId) {
 | 
				
			||||||
 | 
					            return await new Promise(resolve => {
 | 
				
			||||||
 | 
					                var tx = window.db.transaction("playlists", "readonly");
 | 
				
			||||||
 | 
					                var store = tx.objectStore("playlists");
 | 
				
			||||||
 | 
					                const req = store.openCursor(playlistId);
 | 
				
			||||||
 | 
					                let playlist = null;
 | 
				
			||||||
 | 
					                req.onsuccess = e => {
 | 
				
			||||||
 | 
					                    playlist = e.target.result.value;
 | 
				
			||||||
 | 
					                    playlist.videos = JSON.parse(playlist.videoIds).length;
 | 
				
			||||||
 | 
					                    resolve(playlist);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        createOrUpdateLocalPlaylist(playlist) {
 | 
				
			||||||
 | 
					            var tx = window.db.transaction("playlists", "readwrite");
 | 
				
			||||||
 | 
					            var store = tx.objectStore("playlists");
 | 
				
			||||||
 | 
					            store.put(playlist);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        // needs to handle both, streamInfo items and streams items
 | 
				
			||||||
 | 
					        createLocalPlaylistVideo(videoId, videoInfo) {
 | 
				
			||||||
 | 
					            if (videoInfo === undefined || videoId === null || videoInfo?.error) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var tx = window.db.transaction("playlist_videos", "readwrite");
 | 
				
			||||||
 | 
					            var store = tx.objectStore("playlist_videos");
 | 
				
			||||||
 | 
					            const video = {
 | 
				
			||||||
 | 
					                videoId: videoId,
 | 
				
			||||||
 | 
					                title: videoInfo.title,
 | 
				
			||||||
 | 
					                type: "stream",
 | 
				
			||||||
 | 
					                shortDescription: videoInfo.shortDescription ?? videoInfo.description,
 | 
				
			||||||
 | 
					                url: `/watch?v=${videoId}`,
 | 
				
			||||||
 | 
					                thumbnail: videoInfo.thumbnail ?? videoInfo.thumbnailUrl,
 | 
				
			||||||
 | 
					                uploaderVerified: videoInfo.uploaderVerified,
 | 
				
			||||||
 | 
					                duration: videoInfo.duration,
 | 
				
			||||||
 | 
					                uploaderAvatar: videoInfo.uploaderAvatar,
 | 
				
			||||||
 | 
					                uploaderUrl: videoInfo.uploaderUrl,
 | 
				
			||||||
 | 
					                uploaderName: videoInfo.uploaderName ?? videoInfo.uploader,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            store.put(video);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async getLocalPlaylistVideo(videoId) {
 | 
				
			||||||
 | 
					            return await new Promise(resolve => {
 | 
				
			||||||
 | 
					                var tx = window.db.transaction("playlist_videos", "readonly");
 | 
				
			||||||
 | 
					                var store = tx.objectStore("playlist_videos");
 | 
				
			||||||
 | 
					                const req = store.openCursor(videoId);
 | 
				
			||||||
 | 
					                req.onsuccess = e => {
 | 
				
			||||||
 | 
					                    resolve(e.target.result.value);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async getPlaylists() {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                if (!window.db) return [];
 | 
				
			||||||
 | 
					                return await new Promise(resolve => {
 | 
				
			||||||
 | 
					                    let playlists = [];
 | 
				
			||||||
 | 
					                    var tx = window.db.transaction("playlists", "readonly");
 | 
				
			||||||
 | 
					                    var store = tx.objectStore("playlists");
 | 
				
			||||||
 | 
					                    const cursorRequest = store.openCursor();
 | 
				
			||||||
 | 
					                    cursorRequest.onsuccess = e => {
 | 
				
			||||||
 | 
					                        const cursor = e.target.result;
 | 
				
			||||||
 | 
					                        if (cursor) {
 | 
				
			||||||
 | 
					                            let playlist = cursor.value;
 | 
				
			||||||
 | 
					                            playlist.videos = JSON.parse(playlist.videoIds).length;
 | 
				
			||||||
 | 
					                            playlists.push(playlist);
 | 
				
			||||||
 | 
					                            cursor.continue();
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            resolve(playlists);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async getPlaylist(playlistId) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const playlist = await this.getLocalPlaylist(playlistId);
 | 
				
			||||||
 | 
					                const videoIds = JSON.parse(playlist.videoIds);
 | 
				
			||||||
 | 
					                const videosFuture = videoIds.map(videoId => this.getLocalPlaylistVideo(videoId));
 | 
				
			||||||
 | 
					                playlist.relatedStreams = await Promise.all(videosFuture);
 | 
				
			||||||
 | 
					                return playlist;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async createPlaylist(name) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const uuid = crypto.randomUUID();
 | 
				
			||||||
 | 
					                const playlistId = `local-${uuid}`;
 | 
				
			||||||
 | 
					                this.createOrUpdateLocalPlaylist({
 | 
				
			||||||
 | 
					                    playlistId: playlistId,
 | 
				
			||||||
 | 
					                    // remapping needed for the playlists page
 | 
				
			||||||
 | 
					                    id: playlistId,
 | 
				
			||||||
 | 
					                    name: name,
 | 
				
			||||||
 | 
					                    description: "",
 | 
				
			||||||
 | 
					                    thumbnail: "https://pipedproxy.kavin.rocks/?host=i.ytimg.com",
 | 
				
			||||||
 | 
					                    videoIds: "[]", // empty list
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                return { playlistId: playlistId };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
 | 
				
			||||||
 | 
					                method: "POST",
 | 
				
			||||||
 | 
					                body: JSON.stringify({
 | 
				
			||||||
 | 
					                    name: name,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async deletePlaylist(playlistId) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const playlist = await this.getLocalPlaylist(playlistId);
 | 
				
			||||||
 | 
					                var tx = window.db.transaction("playlists", "readwrite");
 | 
				
			||||||
 | 
					                var store = tx.objectStore("playlists");
 | 
				
			||||||
 | 
					                store.delete(playlistId);
 | 
				
			||||||
 | 
					                // delete videos that don't need to be store anymore
 | 
				
			||||||
 | 
					                const playlists = await this.getPlaylists();
 | 
				
			||||||
 | 
					                const usedVideoIds = playlists
 | 
				
			||||||
 | 
					                    .filter(playlist => playlist.id != playlistId)
 | 
				
			||||||
 | 
					                    .map(playlist => JSON.parse(playlist.videoIds))
 | 
				
			||||||
 | 
					                    .flat();
 | 
				
			||||||
 | 
					                const potentialDeletableVideos = JSON.parse(playlist.videoIds);
 | 
				
			||||||
 | 
					                var videoTx = window.db.transaction("playlist_videos", "readwrite");
 | 
				
			||||||
 | 
					                var videoStore = videoTx.objectStore("playlist_videos");
 | 
				
			||||||
 | 
					                for (let videoId of potentialDeletableVideos) {
 | 
				
			||||||
 | 
					                    if (!usedVideoIds.includes(videoId)) videoStore.delete(videoId);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return { message: "ok" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
 | 
				
			||||||
 | 
					                method: "POST",
 | 
				
			||||||
 | 
					                body: JSON.stringify({
 | 
				
			||||||
 | 
					                    playlistId: playlistId,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async renamePlaylist(playlistId, newName) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const playlist = await this.getLocalPlaylist(playlistId);
 | 
				
			||||||
 | 
					                playlist.name = newName;
 | 
				
			||||||
 | 
					                this.createOrUpdateLocalPlaylist(playlist);
 | 
				
			||||||
 | 
					                return { message: "ok" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
 | 
				
			||||||
 | 
					                method: "POST",
 | 
				
			||||||
 | 
					                body: JSON.stringify({
 | 
				
			||||||
 | 
					                    playlistId: playlistId,
 | 
				
			||||||
 | 
					                    newName: newName,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async changePlaylistDescription(playlistId, newDescription) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const playlist = await this.getLocalPlaylist(playlistId);
 | 
				
			||||||
 | 
					                playlist.description = newDescription;
 | 
				
			||||||
 | 
					                this.createOrUpdateLocalPlaylist(playlist);
 | 
				
			||||||
 | 
					                return { message: "ok" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists/description", null, {
 | 
				
			||||||
 | 
					                method: "PATCH",
 | 
				
			||||||
 | 
					                body: JSON.stringify({
 | 
				
			||||||
 | 
					                    playlistId: playlistId,
 | 
				
			||||||
 | 
					                    description: newDescription,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async addVideosToPlaylist(playlistId, videoIds, videoInfos) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const playlist = await this.getLocalPlaylist(playlistId);
 | 
				
			||||||
 | 
					                const currentVideoIds = JSON.parse(playlist.videoIds);
 | 
				
			||||||
 | 
					                currentVideoIds.push(...videoIds);
 | 
				
			||||||
 | 
					                playlist.videoIds = JSON.stringify(currentVideoIds);
 | 
				
			||||||
 | 
					                let streamInfos =
 | 
				
			||||||
 | 
					                    videoInfos ??
 | 
				
			||||||
 | 
					                    (await Promise.all(videoIds.map(videoId => this.fetchJson(this.apiUrl() + "/streams/" + videoId))));
 | 
				
			||||||
 | 
					                playlist.thumbnail = streamInfos[0].thumbnail || streamInfos[0].thumbnailUrl;
 | 
				
			||||||
 | 
					                this.createOrUpdateLocalPlaylist(playlist);
 | 
				
			||||||
 | 
					                for (let i in videoIds) {
 | 
				
			||||||
 | 
					                    this.createLocalPlaylistVideo(videoIds[i], streamInfos[i]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return { message: "ok" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
 | 
				
			||||||
 | 
					                method: "POST",
 | 
				
			||||||
 | 
					                body: JSON.stringify({
 | 
				
			||||||
 | 
					                    playlistId: playlistId,
 | 
				
			||||||
 | 
					                    videoIds: videoIds,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        async removeVideoFromPlaylist(playlistId, index) {
 | 
				
			||||||
 | 
					            if (!this.authenticated) {
 | 
				
			||||||
 | 
					                const playlist = await this.getLocalPlaylist(playlistId);
 | 
				
			||||||
 | 
					                const videoIds = JSON.parse(playlist.videoIds);
 | 
				
			||||||
 | 
					                videoIds.splice(index, 1);
 | 
				
			||||||
 | 
					                playlist.videoIds = JSON.stringify(videoIds);
 | 
				
			||||||
 | 
					                if (videoIds.length == 0) playlist.thumbnail = "https://pipedproxy.kavin.rocks/?host=i.ytimg.com";
 | 
				
			||||||
 | 
					                this.createOrUpdateLocalPlaylist(playlist);
 | 
				
			||||||
 | 
					                return { message: "ok" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return await this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
 | 
				
			||||||
 | 
					                method: "POST",
 | 
				
			||||||
 | 
					                body: JSON.stringify({
 | 
				
			||||||
 | 
					                    playlistId: playlistId,
 | 
				
			||||||
 | 
					                    index: index,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    Authorization: this.getAuthToken(),
 | 
				
			||||||
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        getHomePage(_this) {
 | 
				
			||||||
 | 
					            switch (_this.getPreferenceString("homepage", "trending")) {
 | 
				
			||||||
 | 
					                case "trending":
 | 
				
			||||||
 | 
					                    return "/trending";
 | 
				
			||||||
 | 
					                case "feed":
 | 
				
			||||||
 | 
					                    return "/feed";
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    return undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        fetchDeArrowContent(content) {
 | 
				
			||||||
 | 
					            if (!this.getPreferenceBoolean("dearrow", false)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const videoIds = content
 | 
				
			||||||
 | 
					                .filter(item => item.type === "stream")
 | 
				
			||||||
 | 
					                .filter(item => item.dearrow === undefined)
 | 
				
			||||||
 | 
					                .map(item => item.url.substr(-11))
 | 
				
			||||||
 | 
					                .sort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (videoIds.length === 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.fetchJson(this.apiUrl() + "/dearrow", {
 | 
				
			||||||
 | 
					                videoIds: videoIds.join(","),
 | 
				
			||||||
 | 
					            }).then(json => {
 | 
				
			||||||
 | 
					                Object.keys(json).forEach(videoId => {
 | 
				
			||||||
 | 
					                    const item = content.find(item => item.url.endsWith(videoId));
 | 
				
			||||||
 | 
					                    if (item) item.dearrow = json[videoId];
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    computed: {
 | 
					    computed: {
 | 
				
			||||||
        authenticated(_this) {
 | 
					        authenticated(_this) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/utils/HtmlUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/utils/HtmlUtils.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import DOMPurify from "dompurify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const purifyHTML = html => {
 | 
				
			||||||
 | 
					    return DOMPurify.sanitize(html);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import linkifyHtml from "linkify-html";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const rewriteDescription = text => {
 | 
				
			||||||
 | 
					    return linkifyHtml(text)
 | 
				
			||||||
 | 
					        .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
 | 
				
			||||||
 | 
					        .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm, "/watch?v=$1")
 | 
				
			||||||
 | 
					        .replaceAll("\n", "<br>");
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										12
									
								
								sweep.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sweep.yaml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					# Sweep AI turns bug fixes & feature requests into code changes (https://sweep.dev)
 | 
				
			||||||
 | 
					# For details on our config file, check out our docs at https://docs.sweep.dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If you use this be sure to frequently sync your default branch(main, master) to dev.
 | 
				
			||||||
 | 
					branch: 'master'
 | 
				
			||||||
 | 
					# By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false.
 | 
				
			||||||
 | 
					gha_enabled: True
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					description: 'TeamPiped/Piped is a Vue 3 project that uses UnoCSS (similar to Tailwind).'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default Values: https://github.com/sweepai/sweep/blob/main/sweep.yaml
 | 
				
			||||||
| 
						 | 
					@ -73,5 +73,6 @@ export default defineConfig({
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    build: {
 | 
					    build: {
 | 
				
			||||||
        sourcemap: true,
 | 
					        sourcemap: true,
 | 
				
			||||||
 | 
					        cssMinify: "lightningcss",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue