[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-env\"],\n  \"plugins\": [\"rewire\"]\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: returnyoutubedislike\ncustom: \"https://returnyoutubedislike.com/donate\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: Bug Report\ndescription: File a bug report!\n# title: \"(Bug): \"\nlabels: [\"bug\"]\nbody:\n  - type: checkboxes\n    attributes:\n      label: Have you tried to find similar issues (open or recently closed)?\n      options:\n        - label: \"Yes, this issue is not a duplicate\"\n          required: true\n  - type: input\n    attributes:\n      label: Browser\n      description: Which browser are you using?\n      placeholder: \"Example: Google Chrome\"\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Browser Version\n      description: Which browser version are you using?\n      placeholder: \"Example: Chromium v95\"\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: \"Extension or Userscript?\"\n      options:\n        - Extension\n        - Userscript\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Extension/Userscript Version\n      description: Which extension/userscript version are you using?\n      placeholder: \"Example: Version 0.0.0.1\"\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Video link where you see the problem\n      description: Video link where you see the problem\n      placeholder: \"Example: https://www.youtube.com/watch?v=s4-gMgdsnHQ\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: What happened?\n      description: Also tell us, what did you expect to happen?\n      placeholder: Tell us what you see!\n      value: \"A bug happened!\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: How to reproduce/recreate?\n      description: Detailed steps to reproduce/recreate it.\n      placeholder: \"We need to be able to reproduce/recreate that bug/event in order to fix it. Please write the steps in detail.\"\n      value: \"Tell us how it happened with detailed steps for us.\"\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: \"Will you be available for follow-up questions to help developers diagnose & fix the issue?\"\n      options:\n        - \"Yes\"\n        - \"No\"\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "content": "name: Feature Request\ndescription: Request or suggest a new feature!\n# title: \"(Feature Request): \"\nlabels: [\"enhancement\"]\nbody:\n  - type: dropdown\n    attributes:\n      label: Extension or Userscript?\n      options:\n        - Extension\n        - Userscript\n        - Both\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Request or suggest a new feature!\n      placeholder: I want to suggest...\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Ways to implement this!\n      placeholder: We can implement it by...\n  - type: checkboxes\n    attributes:\n      label: Can you work on this?\n      options:\n        - label: \"Yes\"\n        - label: \"No\"\n  - type: dropdown\n    attributes:\n      label: \"Will you be available for follow-up questions to help developers implement this?\"\n      options:\n        - \"Yes\"\n        - \"No\"\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n  - package-ecosystem: \"npm\"\n    directory: \"/Website\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: Test Build Extension\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - Extensions/combined/**\n  pull_request:\n    branches:\n      - main\n    paths:\n      - Extensions/combined/**\n\njobs:\n  build:\n    runs-on: ubuntu-24.04\n\n    defaults:\n      run:\n        working-directory: ./Extensions/combined\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20.x'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Build extension\n        run: npm run build\n\n      - name: Upload Chrome artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ryd-chrome\n          path: ./Extensions/combined/dist/chrome\n\n      - name: Upload Firefox artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ryd-firefox\n          path: ./Extensions/combined/dist/firefox\n\n      - name: Upload Safari artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ryd-safari\n          path: ./Extensions/combined/dist/safari\n"
  },
  {
    "path": ".github/workflows/commentcommands.yml",
    "content": "name: Comment Commands\n\non:\n  issue_comment:\n    types: [created]\n\njobs:\n  assign-commenter:\n    runs-on: ubuntu-24.04\n    if: |\n      contains(github.event.comment.body, '/assignme') ||\n      contains(github.event.comment.body, '/assign me')\n    steps:\n      - name: Assigning commenter\n        run: |\n          curl \\\n            -X POST \\\n            -H \"Accept: application/vnd.github+json\" \\\n            -H \"Authorization: token ${{ secrets.GITHUB_TOKEN }}\" \\\n            https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees \\\n            -d '{\"assignees\":[\"${{ github.event.comment.user.login }}\"]}'\n\n  request-issue-framing-improvement:\n    runs-on: ubuntu-24.04\n    if: |\n      contains(github.event.comment.body, '/improve') && (\n        github.event.comment.author_association == 'OWNER' ||\n        github.event.comment.author_association == 'COLLABORATOR' ||\n        github.event.comment.author_association == 'CONTRIBUTOR'\n      )\n    steps:\n      - name: Request issue framing improvement\n        run: |\n          curl \\\n            -X POST \\\n            -H \"Accept: application/vnd.github+json\" \\\n            -H \"Authorization: token ${{ secrets.GITHUB_TOKEN }}\" \\\n            https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments \\\n            -d '{\"body\":\"This issue is put on hold due to low quality. No reviews or fixes will be performed at this time. Eventually, it will be closed. While we appreciate your effort writing, we are not able to further investigate it. Please improve it by writing a better title or providing more details, and you may re-open it.\"}'\n\n  add-duplicate-label:\n    runs-on: ubuntu-24.04\n    if: |\n      contains(github.event.comment.body, '/duplicate') && (\n        github.event.comment.author_association == 'OWNER' ||\n        github.event.comment.author_association == 'COLLABORATOR' ||\n        github.event.comment.author_association != 'CONTRIBUTOR'\n      )\n    steps:\n      - name: Add duplicate label\n        run: |\n          curl \\\n            -X POST \\\n            -H \"Accept: application/vnd.github+json\" \\\n            -H \"Authorization: token ${{ secrets.GITHUB_TOKEN }}\" \\\n            https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels \\\n            -d '{\"labels\":[\"duplicate\"]}'\n"
  },
  {
    "path": ".github/workflows/weblint.yml",
    "content": "# This workflow lints the code in /Website, if any was committed\n\nname: Website Lint\n\non:\n  push:\n    branches: \n      - main\n    paths:\n      - Website/**\n  pull_request:\n    branches: \n      - main\n    paths:\n      - Website/**\n\njobs:\n  weblint:\n    runs-on: ubuntu-24.04\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install dependencies\n        run: npm install\n        working-directory: Website\n\n      - name: Run lint\n        run: npm run lint\n        working-directory: Website\n"
  },
  {
    "path": ".gitignore",
    "content": "*cert\n*Backend\n.DS_Store\npackage-lock.json\n\n# Website node modules and build output\nWebsite/package-lock.json\n\n# AI assistant artifacts\n.claude/\nCLAUDE.md\nWebsite/.editorconfig\nWebsite/node_modules\nWebsite/.nuxt\nWebsite/dist/\nWebsite/_nuxt\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw*\n\nnode_modules\n\n# Build files\nExtensions/combined/bundled-content-script.js\n\n# Dist Files\nExtensions/combined/dist/*\npackage-lock.json\nWebsite/package-lock.json\n"
  },
  {
    "path": ".nvmrc",
    "content": "v12.18.4"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Welcome To Return YouTube Dislikes contributing guide\n\nThank you for investing your time in contributing to our project! All your changes will be reflected in the next version of the extension (or the [website](https://www.returnyoutubedislike.com/)).\n\n## Getting Started\n\nPlease use Prettier with default settings for formatting.\n\n#### Prerequisites\n\nYou need to have node and npm installed to create the bundled version of the source.\n\nVersions used when setting up:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nTo create the `bundled-content-script.js` that contains most of the business logic of this extension you have to install all dependencies first.\n\n1. Go to the root of the repo and run:\n\n```\nnpm install\n```\n\n2. Run the following command to create `bundled-content-script.js` which is used in `manifest.json`\n\n```\nnpm start // to create the build file(s) and start a file watcher that hot-reloads on save\n\n// or\n\nnpm run build // to create the build file(s) once\n```\n\nCongratulations, You are now ready to develop!\n\nIf you are new to developing Chrome extensions, or need extra help, please see [this YouTube tutorial](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Issues\n\n#### Opening a new issue\n\nIf you have any issues with the extension, please search to make sure the issue isn't already reported. If it isn't, open an issue, using the issue form is highly recommended but not mandatory.\n\n#### Solving an issue\n\nIf you found an issue that you feel you might be able to solve, don't be shy. Open a PR with the fix and make sure to mention the issue you are fixing.\n\n### Feature Request\n\n#### Opening a new feature request\n\nIf you have an idea for the extension, feel free to open a feature request, but please search it before to make sure the feature isn't already suggested. Using the feature form is highly recommended but not mandatory\n\n#### Implementing a feature request\n\nIf you found a feature that you feel you might be able to implement, don't be shy. Open a PR with the fix and make sure to mention the feature you are implementing.\n\n### What PRs do we accept?\n\n- Issue fixes.\n- Feature implementation.\n- Typos or better and easier words to use.\n- Website contributions.\n"
  },
  {
    "path": "CONTRIBUTINGar.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# مرحبًا بكم في دليل المساهمة في Return YouTube Dislikes\n\nشكرًا لك على استثمار وقتك في المساهمة في مشروعنا! سيتم عكس جميع تغييراتك في الإصدار التالي من الامتداد (أو [الموقع](https://www.returnyoutubedislike.com/)).\n\n## البدء\n\nيرجى استخدام Prettier بالإعدادات الافتراضية للتنسيق.\n\n#### المتطلبات الأساسية\n\nتحتاج إلى تثبيت node و npm لإنشاء النسخة المجمعة من المصدر.\n\nالإصدارات المستخدمة عند الإعداد:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nلإنشاء `bundled-content-script.js` الذي يحتوي على معظم منطق العمل لهذا الامتداد، يجب عليك تثبيت جميع التبعيات أولاً.\n\n1. انتقل إلى جذر المستودع وقم بتشغيل:\n\n```\nnpm install\n```\n\n2. قم بتشغيل الأمر التالي لإنشاء `bundled-content-script.js` الذي يستخدم في `manifest.json`\n\n```\nnpm start // لإنشاء ملف(ات) البناء وبدء مراقب الملفات الذي يعيد التحميل تلقائيًا عند الحفظ\n\n// أو\n\nnpm run build // لإنشاء ملف(ات) البناء مرة واحدة\n```\n\nتهانينا، أنت الآن جاهز للتطوير!\n\nإذا كنت جديدًا في تطوير ملحقات Chrome، أو تحتاج إلى مساعدة إضافية، يرجى مشاهدة [هذا الفيديو التعليمي على YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### المشاكل\n\n#### فتح مشكلة جديدة\n\nإذا كانت لديك أي مشاكل مع الامتداد، يرجى البحث للتأكد من أن المشكلة لم يتم الإبلاغ عنها بالفعل. إذا لم تكن كذلك، افتح مشكلة، يوصى بشدة باستخدام نموذج المشكلة ولكنه ليس إلزاميًا.\n\n#### حل مشكلة\n\nإذا وجدت مشكلة تشعر أنك قد تكون قادرًا على حلها، لا تخجل. افتح PR مع الإصلاح وتأكد من ذكر المشكلة التي تقوم بإصلاحها.\n\n### طلب ميزة\n\n#### فتح طلب ميزة جديدة\n\nإذا كانت لديك فكرة للامتداد، لا تتردد في فتح طلب ميزة، ولكن يرجى البحث أولاً للتأكد من أن الميزة لم يتم اقتراحها بالفعل. يوصى بشدة باستخدام نموذج الميزة ولكنه ليس إلزاميًا.\n\n#### تنفيذ طلب ميزة\n\nإذا وجدت ميزة تشعر أنك قد تكون قادرًا على تنفيذها، لا تخجل. افتح PR مع الإصلاح وتأكد من ذكر الميزة التي تقوم بتنفيذها.\n\n### ما PRs التي نقبلها؟\n\n- إصلاحات المشاكل.\n- تنفيذ الميزات.\n- الأخطاء المطبعية أو الكلمات الأفضل والأسهل للاستخدام.\n- مساهمات الموقع.\n"
  },
  {
    "path": "CONTRIBUTINGaz.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# \"YouTube Dislike Sayını Geri Qaytar\" layihəsinə qatqı qaydalarına Xoş Gəlmisiniz\n\nLayihəmizə qatqı etmək üçün vaxt ayırdığınız üçün təşəkkür edirik! Bütün dəyişiklikləriniz uzantının növbəti versiyasına (və ya [internet saytına](https://www.returnyoutubedislike.com/)) əlavə ediləcəkdir.\n\n## Başlamaq\n\nFormatlama üçün xahiş olunur ki, Prettier-i standart parametrlərdə istifadə edin.\n\n#### Tələblər\n\nLayihənin paketlənmiş versiyasını yaratmaq üçün node və npm quraşdırılmalıdır.\n\nQuraşdırma zamanı istifadə olunan versiyalar:\n\n- node: 12.18.4  \n- npm: 6.14.6  \n\nBu uzantının əsas məntiqini ehtiva edən `bundled-content-script.js` faylını yaratmaq üçün əvvəlcə bütün asılılıqları quraşdırmalısınız.\n\n1. Deponun əsas qovluğuna keçin və aşağıdakı əmri icra edin:\n\n```\nnpm install\n```\n\n2. `manifest.json` içərisində istifadə olunan `bundled-content-script.js` faylını yaratmaq üçün aşağıdakı əmri çalışdırın.\n\n```\nnpm start // fayl yaradılarkən və saxlanarkən avtomatik yenilənən fayl izləyicisini başlatmaq üçün\n\n// ya da\n\nnpm run build // faylları yalnız bir dəfə yaratmaq üçün\n```\n\nTəbriklər, artıq inkişaf etməyə hazırsınız!\n\nChrome uzantılarının inkişafına yeni başlamısınızsa və ya əlavə yardıma ehtiyacınız varsa, lütfən [bu YouTube dərsinə](https://www.youtube.com/watch?v=mdOj6HYE3_0) baxın.\n\n### Issue'lar\n\n#### Yeni bir issue açmaq\n\nUzantı ilə bağlı hər hansı bir probleminiz varsa, problemin əvvəllər bildirilmədiyindən əmin olmaq üçün axtarış aparın. Əgər daha əvvəl bildirilməyibsə, bir mövzu açın. Problem formunun istifadəsi şiddətlə tövsiyə olunur, lakin məcburi deyil.\n\n#### Bir issue'yu həll etmək\n\nHəll edə biləcəyinizi düşündüyünüz bir problem tapsanız, çəkinmədən çalışın. Problemi həll edən bir PR açın və həll etdiyiniz problemi müəyyənləşdirdiyinizdən əmin olun.\n\n### Xüsusiyyət İstəkləri\n\n#### Yeni bir xüsusiyyət istəyi açmaq\n\nUzantı haqqında bir ideyanız varsa, yeni bir xüsusiyyət istəyi açmaqdan çəkinməyin, lakin xüsusiyyətin daha əvvəl təklif olunmadığından əmin olmaq üçün axtarış edin. Xüsusiyyət formasının istifadəsi şiddətlə tövsiyə olunur, lakin məcburi deyil.\n\n#### Bir xüsusiyyət istəyini tətbiq etmək\n\nTətbiq edə biləcəyinizi düşündüyünüz bir xüsusiyyət tapsanız, çəkinmədən çalışın. Xüsusiyyəti tətbiq edən bir PR açın və əlavə etdiyiniz xüsusiyyəti qeyd etdiyinizdən əmin olun.\n\n### Hansı PR növlərini qəbul edirik?\n\n- Problem həlləri.  \n- Xüsusiyyət tətbiqləri.  \n- Yazı səhvləri və ya daha aydın ifadələr.  \n- Sayt qatqıları.\n```\n"
  },
  {
    "path": "CONTRIBUTINGbg.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Добре дошли в ръководството за сътрудничество на Return YouTube Dislikes\n\nБлагодарим ви, че отделяте време за принос към нашия проект! Всички вашите промени ще се отразят в следващата версия на разширението (или на [уебсайта](https://www.returnyoutubedislike.com/)).\n\n## Започване\n\nМоля, използвайте Prettier с настройките по подразбиране за форматиране.\n\n#### Изисквания\n\nТрябва да имате инсталирани node и npm, за да създадете обединената версия на изходния код.\n\nИзползвани версии при настройване:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nЗа да създадете `bundled-content-script.js`, който съдържа повечето от бизнес логиката на това разширение, трябва първо да инсталирате всички зависимости.\n\n1. Отидете в коренната папка на хранилището и изпълнете:\n\n```\nnpm install\n```\n\n2. Изпълнете следната команда, за да създадете `bundled-content-script.js`, който се използва в `manifest.json`\n\n```\nnpm start // за създаване на файл(ове) за сборка и стартиране на файлов наблюдател, който рестартира автоматично след запазване\n\n// или\n\nnpm run build // за създаване на файл(ове) за сборка веднъж\n```\n\nПоздравления, вече сте готови за разработка!\n\nАко сте нов в разработката на разширения за Chrome или ви трябва допълнителна помощ, моля вижте [този урок на YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Проблеми\n\n#### Отваряне на нов проблем\n\nАко имате проблеми с разширението, моля, направете търсене, за да се уверите, че проблемът вече не е сигнализиран. Ако не е, отворете проблема (issue), използването на формуляра за проблем е настоятелно препоръчително, но не задължително.\n\n#### Решаване на проблем\n\nАко сте открили проблем, който чувствате, че можете да решите, не бъдете срамежливи. Направете PR с решението и се уверете, че споменавате проблема, който решавате.\n\n### Заявки за функционалности\n\n#### Отваряне на нова заявка за функционалност\n\nАко имате идея за разширението, не се колебайте да отворите заявка за функционалност, но моля, направете търсене, за да се уверите, че функционалността вече не е предложена. Използването на формуляра за функционалности е настоятелно препоръчително, но не задължително.\n\n#### Изпълнение на заявка за функционалност\n\nАко сте открили функционалност, която чувствате, че можете да изпълните, не бъдете срамежливи. Отворете PR с решението и се уверете, че споменавате функционалността, която изпълнявате.\n\n### Какви PR-ове приемаме?\n\n- Коригиране на проблеми.\n- Изпълнение на функционалности.\n- Правописни грешки или по-добри и по-лесни думи за употреба.\n- Приноси към уебсайта.\n"
  },
  {
    "path": "CONTRIBUTINGcn.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# 欢迎来到 Return YouTube Dislikes 贡献指南\n\n感谢您花时间为我们的项目做出贡献！您的所有更改都将反映在扩展的下一个版本中（或 [网站](https://www.returnyoutubedislike.com/)）。\n\n## 入门\n\n请使用 Prettier 的默认设置进行格式化。\n\n#### 先决条件\n\n您需要安装 node 和 npm 才能创建源代码的捆绑版本。\n\n设置时使用的版本：\n\n- node：12.18.4\n- npm：6.14.6\n\n要创建包含此扩展的大部分业务逻辑的 `bundled-content-script.js`，您必须先安装所有依赖项。\n\n1. 转到 repo 的根目录并运行：\n\n```\nnpm install\n```\n\n2. 运行以下命令创建 `manifest.json` 中使用的 `bundled-content-script.js`\n\n```\nnpm start // 创建构建文件并启动在保存时热重载的文件观察器\n\n// 或\n\nnpm run build // 创建构建文件一次\n```\n\n恭喜，您现在可以开始开发了！\n\n如果您是 Chrome 扩展程序开发新手，或者需要额外帮助，请参阅[此 YouTube 教程](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### 问题\n\n#### 打开新问题\n\n如果您对扩展程序有任何问题，请搜索以确保该问题尚未被报告。如果没有，请打开一个问题，强烈建议使用问题表单，但这不是强制性的。\n\n#### 解决问题\n\n如果您发现一个您认为可以解决的问题，请随时提出来，不必客气。打开带有修复程序的 PR，并确保提及您正在修复的问题。\n\n### 功能请求\n\n#### 打开新的功能请求\n\n如果您对扩展程序有想法，请随时打开功能请求，但请先搜索以确保该功能尚未被建议。强烈建议使用功能表单，但这不是强制性的\n\n#### 实现功能请求\n\n如果您发现您认为可以实现的功能，请随时提出来，不必客气。打开带有修复的 PR，并确保提及您正在实现的功能。\n\n### 我们接受哪些 PR？\n\n- 问题修复。\n- 功能实现。\n- 拼写错误或更好、更易用的词语。\n- 网站贡献。\n"
  },
  {
    "path": "CONTRIBUTINGda.md",
    "content": "Read this in other languages: [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Velkommen til Return YouTube Dislike bidragsvejledning\n\nTak fordi du bruger din tid på at bidrage til vores projekt! Alle dine ændringer vil blive afspejlet i den næste version af udvidelsen (eller på [websitet](https://www.returnyoutubedislike.com/)).\n\n## Kom godt i gang\n\nBrug venligst Prettier med standardindstillinger til formatering.\n\n#### Forudsætninger\n\nDu skal have Node og npm installeret for at kunne opbygge den bundtede version af koden.\n\nVersioner brugt ved opsætning:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nFor at oprette `bundled-content-script.js`, som indeholder størstedelen af udvidelsens forretningslogik, skal du først installere alle afhængigheder.\n\n1. Gå til rodmappen af repoet og kør:\n\n```\nnpm install\n```\n\n2. Kør følgende kommando for at oprette `bundled-content-script.js`, som bruges i `manifest.json`\n\n```\nnpm start // opret build-fil(er) og start en watcher, der genindlæser ved gem\n\n// eller\n\nnpm run build // opret build-fil(er) én gang\n```\n\nTillykke, du er nu klar til at udvikle!\n\nHvis du er ny i udvikling af Chrome‑udvidelser eller har brug for ekstra hjælp, se [denne YouTube‑vejledning](https://www.youtube.com/watch?v=mdOj6HYE3_0).\n\n### Issues\n\n#### Opret en ny issue\n\nHvis du har problemer med udvidelsen, så søg først for at sikre, at problemet ikke allerede er rapporteret. Hvis ikke, opret en issue. Det anbefales stærkt at bruge issue‑skabelonen, men det er ikke påkrævet.\n\n#### Løs en issue\n\nHvis du har fundet et problem, du mener, du kan løse, så tøv ikke. Opret en PR med rettelsen, og husk at nævne den issue, du løser.\n\n### Feature‑ønsker\n\n#### Opret et nyt feature‑ønske\n\nHvis du har en idé til udvidelsen, er du velkommen til at oprette et feature‑ønske, men søg venligst først for at sikre, at det ikke allerede er foreslået. Brug af feature‑skabelonen anbefales, men er ikke påkrævet.\n\n#### Implementér et feature‑ønske\n\nHvis du har fundet et feature, som du kan implementere, så tøv ikke. Opret en PR med implementeringen, og husk at nævne det feature, du implementerer.\n\n### Hvilke PR’er accepterer vi?\n\n- Fejlrettelser.\n- Implementering af funktioner.\n- Ret stavefejl eller brug bedre og enklere formuleringer.\n- Bidrag til websitet.\n"
  },
  {
    "path": "CONTRIBUTINGde.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Willkommen beim Leitfaden für Beitragende von Return YouTube Dislikes\n\nVielen Dank, dass du deine Zeit investierst, um zu unserem Projekt beizutragen! Alle deine Änderungen werden in der nächsten Version der Erweiterung (oder der [Website](https://www.returnyoutubedislike.com/)) reflektiert.\n\n## Erste Schritte\n\nBitte verwende Prettier mit den Standardeinstellungen für die Formatierung.\n\n#### Voraussetzungen\n\nDu musst Node und npm installiert haben, um die gebündelte Version der Quelle zu erstellen.\n\nVerwendete Versionen beim Einrichten:\n\n- Node: 12.18.4\n- npm: 6.14.6\n\nUm die `bundled-content-script.js` zu erstellen, die den Großteil der Geschäftslogik dieser Erweiterung enthält, musst du zuerst alle Abhängigkeiten installieren.\n\n1. Gehe zum Root-Verzeichnis des Repos und führe aus:\n\n```\nnpm install\n```\n\n2. Führe den folgenden Befehl aus, um `bundled-content-script.js` zu erstellen, das in `manifest.json` verwendet wird:\n\n```\nnpm start // um die Build-Datei(en) zu erstellen und einen Datei-Watcher zu starten, der bei jedem Speichern neu lädt\n\n// oder\n\nnpm run build // um die Build-Datei(en) einmal zu erstellen\n```\n\nHerzlichen Glückwunsch, du bist jetzt bereit zum Entwickeln!\n\nWenn du neu in der Entwicklung von Chrome-Erweiterungen bist oder zusätzliche Hilfe benötigst, sieh dir bitte [dieses YouTube-Tutorial](https://www.youtube.com/watch?v=mdOj6HYE3_0) an.\n\n### Probleme\n\n#### Öffnen eines neuen Problems\n\nWenn du Probleme mit der Erweiterung hast, suche bitte, um sicherzustellen, dass das Problem noch nicht gemeldet wurde. Wenn nicht, öffne ein Problem. Die Verwendung des Problemformulars wird dringend empfohlen, ist aber nicht zwingend erforderlich.\n\n#### Lösung eines Problems\n\nWenn du ein Problem gefunden hast, das du lösen könntest, sei nicht schüchtern. Öffne einen PR mit der Lösung und vergewissere dich, dass du das behobene Problem erwähnst.\n\n### Feature-Anfrage\n\n#### Öffnen einer neuen Feature-Anfrage\n\nWenn du eine Idee für die Erweiterung hast, kannst du gerne eine Feature-Anfrage öffnen, aber suche bitte zuerst, um sicherzustellen, dass das Feature noch nicht vorgeschlagen wurde. Die Verwendung des Feature-Formulars wird dringend empfohlen, ist aber nicht zwingend erforderlich.\n\n#### Implementierung einer Feature-Anfrage\n\nWenn du ein Feature gefunden hast, das du implementieren könntest, sei nicht schüchtern. Öffne einen PR mit der Implementierung und vergewissere dich, dass du das implementierte Feature erwähnst.\n\n### Welche PRs akzeptieren wir?\n\n- Fehlerbehebungen.\n- Implementierung von Funktionen.\n- Rechtschreibfehler oder bessere und einfachere Wörter zur Verwendung.\n- Beiträge zur Website.\n"
  },
  {
    "path": "CONTRIBUTINGes.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Bienvenido(a) a la guía de contribución de Return YouTube Dislikes\n\n¡Gracias por invertir tiempo en contribuir al proyecto! Todos sus cambios serán mostrados en la siguiente versión de la extensión (o en la [pagina web](https://www.returnyoutubedislike.com/))\n\n## Empecemos\n\nPor favor, utiliza Prettier con la configuración predeterminada para el formateo\n\n#### Requisitos previos\n\nNecesita tener instalados node y npm para crear la versión empaquetada del código fuente\n\nVersiones utilizadas durante la configuración:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nPara crear el archivo `bundled-content-script.js` que contiene la mayor parte de la lógica de negocio de esta extensión, primero debe instalar todas las dependencias\n\n1. Ve a la raíz del repositorio y ejecute:\n\n```\nnpm install\n```\n\n2. Ejecuta el siguiente comando para crear `bundled-content-script.js`, el cual se utiliza en `manifest.json`\n\n```\nnpm start // para crear el/los archivo(s) de compilación y comenzar un observador de archivos que se recargue automáticamente al guardar cambios\n\n// o\n\nnpm run build // para crear el/los archivo(s) de compilación una vez\n```\n\n¡Felicitaciones! ¡Ahora estás listo(a) para desarrollar!\n\nSi eres nuevo/a en el desarrollo de extensiones de Chrome o necesitas ayuda adicional, por favor consulta [este tutorial en YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Problemas\n\n#### Abrir un nuevo problema\n\nSi tiene algún problema con la extensión, por favor verifique si el problema ya ha sido reportado. Si no lo ha sido, abre un problema. Se recomienda encarecidamente utilizar el formulario de problema, pero no es obligatorio.\n\n#### Resolver un problema\n\nSi encontró un problema que crea que pueda resolver, no dude en hacerlo. Abra una solicitud de extracción (PR) con la solución y asegúrese de mencionar el problema que está solucionando\n\n### Solicitud de función\n\n#### Abrir una nueva solicitud de función\n\nIf you have an idea for the extension, feel free to open a feature request, but please search it before to make sure the feature isn't already suggested. Using the feature form is highly recommended but not mandatory\n\n#### Implementando una solicitud de funcionalidad\n\nSi encontró una función que pueda implementar, no dude en hacerlo. Abra una solicitud de extracción (PR) con la solución y asegúrese de mencionar la función que está implementando\n\n### ¿Qué PRs aceptamos?\n\n- Corrección de problemas.\n- Implementación de funciones.\n- Corrección de errores tipográficos o uso de palabras mejores y más sencillas.\n- Contribuciones al sitio web.\n"
  },
  {
    "path": "CONTRIBUTINGfr.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Bienvenue dans le guide de contribution à Return YouTube Dislikes\n\nMerci d'investir votre temps pour contribuer à notre projet ! Toutes vos modifications seront prises en compte dans la prochaine version de l'extension (ou du site web).\n\n## Pour commencer\n\nVeuillez utiliser [Prettier](https://prettier.io/) avec les paramètres par défaut pour le formatage du code.\n\n#### Prérequis\n\nVous devez avoir installé node et npm pour créer la version bundle depuis le code source.\n\nVersions utilisées lors de la mise en place :\n\n- node: 12.18.4\n- npm: 6.14.6\n\nPour créer le fichier `bundled-content-script.js` qui contient la plupart de la logique de cette extension, vous devez d'abord installer toutes les dépendances.\n\n1. Allez à la racine du repo et exécutez :\n\n```\nnpm install\n```\n\n2. Exécutez la commande suivante pour créer le fichier `bundled-content-script.js` qui est utilisé dans le fichier `manifest.json`.\n\n```\nnpm start // pour créer le(s) fichier(s) de construction et lancer un observateur de fichiers qui recharge à chaud lors de la sauvegarde.\n\n// ou\n\nnpm run build // pour créer le(s) fichier(s) de construction une seule fois\n```\n\nFélicitations, vous êtes maintenant prêt·e à développer !\n\nSi vous n'avez jamais développé d'extensions pour Chrome ou si vous avez besoin d'une aide supplémentaire, consultez [ce tutoriel YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0) (en anglais).\n\n### Problèmes (aussi appelé issues en anglais)\n\n#### Signaler un problème\n\nSi vous rencontrez des problèmes avec l'extension, vérifiez que le problème n'a pas déjà été signalé. Si ce n'est pas le cas, [signalez le problème](https://github.com/Anarios/return-youtube-dislike/issues/new?assignees=&labels=bug&template=bug.yml&title=%28Bug%29%3A+), en utilisant le formulaire qui est fortement recommandé mais pas obligatoire.\n\n#### Résoudre un problème\n\nSi vous avez trouvé un problème que vous pensez pouvoir résoudre, ne soyez pas timide. Ouvrez une [PR](https://github.com/Anarios/return-youtube-dislike/pulls) [(C'est quoi ?)](https://blog.zenika.com/2017/01/24/pull-request-demystifie/) avec la solution et assurez-vous de mentionner le problème que vous résolvez (écrivez # puis le numéro de l'issue).\n\n### Demande de fonctionnalité (aussi appelé feature request en anglais)\n\n#### Ouverture d'une nouvelle demande de fonctionnalité\n\nSi vous avez une idée pour l'extension, n'hésitez pas à [ouvrir une demande de fonctionnalité](https://github.com/Anarios/return-youtube-dislike/issues/new?assignees=&labels=enhancement&template=feature-request.yml&title=%28Feature+Request%29%3A+), mais veuillez effectuer une recherche préalable pour vous assurer que la fonctionnalité n'est pas déjà proposée. L'utilisation du formulaire de demande de fonctionnalité est fortement recommandée mais pas obligatoire.\n\n#### Implémenter une demande de fonctionnalité\n\nSi vous avez trouvé une fonctionnalité que vous pensez pouvoir mettre en œuvre, ne soyez pas timide. Ouvrez une [PR](https://github.com/Anarios/return-youtube-dislike/pulls) [(C'est quoi ?)](https://blog.zenika.com/2017/01/24/pull-request-demystifie/) avec le correctif et assurez-vous de mentionner la fonctionnalité que vous implémentez (écrivez # puis le numéro de l'issue).\n\n### Quels PR acceptons-nous ?\n\n- Correction de problèmes.\n- Implémentation de fonctionnalités.\n- Fautes de frappe ou utilisation de mots plus simples et plus efficaces.\n- Contributions au site web.\n"
  },
  {
    "path": "CONTRIBUTINGgr.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Welcome To Return YouTube Dislikes contributing guide\n\nΣας ευχαριστούμε που αφιερώσατε χρόνο για να συνεισφέρετε στο έργο μας! Όλες οι αλλαγές σας θα εμφανιστούν στην επόμενη έκδοση της επέκτασης (ή στην [ιστοσελίδα](https://www.returnyoutubedislike.com/)).\n\n## Ξεκινώντας\n\nΠαρακαλώ χρησιμοποιήστε το Prettier με τις προκαθορισμένες ρυθμίσεις για τη μορφοποίηση.\n\n#### Προαπαιτούμενα\n\nΠρέπει να έχετε το node και το npm εγκαταστημένο για να δημιουργήσετε την ομαδοποιημένη έκδοση της πηγής.\n\nΕκδόσεις για την εγκατάσταση:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nΓια να δημιουργήσετε το `bundled-content-script.js` που περιέχει το περισσότερο business logic αυτής της επέκτασης πρέπει να εγκαταστήσετε όλα τα  dependencies πρώτα.\n\n1. Πηγαίντε στη ρίζα (root) του repo και εκτελέστε:\n\n```\nnpm install\n```\n\n2. Εκτελέστε την ακόλουθη εντολή για να δημιουργήσετε το `bundled-content-script.js` το οποίο χρησιμοποιείται στο `manifest.json`\n\n```\nnpm start // για να δημιουργήσετε τα build file(s) και να ξεκινήσετε ένα πρόγραμμα παρακολούθυησης αρχείων που τα φορτώνει ξανά κατά την αποθήκευση\n\n// ή\n\nnpm run build // για να δημιουργήσετε τα build file(s) μία φορά\n```\n\nΣυγχαρητήρια, τώρα είστε έτοιμος να δημιουργήσετε!\n\nΕάν είστε νέος στη δημιουργία επεκτάσεων στο Chrome, ή χρειάζεστε επιπλέον βοήθεια, παρακαλώ δείτε [αυτόν τον οδηγό στο YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Ζητήματα\n\n#### Δημιουργώντας ένα νέο ζήτημα\n\nΕάν αντιμετωπίζετε κάποιο πρόβλημα με την επέκταση, παρακαλώ κάντε μια αναζήτηση για να σιγουρευτείτε ότι το ζήτημα δεν έχει αναφερθεί ήδη. Αν δεν είναι, δημιουργήστε ένα νέο ζήτημα χρησιμοποιώντας την φόρμα ζητημάτων η οποία συστήνεται αλλά δεν είναι απαραίτητη.\n\n#### Λύνοντας ένα ζήτημα\n\nΕάν βρήκατε ένα ζήτημα που πιστεύετε ότι ίσως μπορέσετε να λύσετε, μην ντρέπεστε. Ανοίξτε ένα PR με την επιδιόρθωση και φροντίστε να αναφέρετε το πρόβλημα που επιδιορθώνετε.\n\n### Αίτηση για νέα λειτουργίας\n\n#### Άνοιγμα αιτήματος νέας λειτουργίας\n\nΕάν έχετε μια ιδέα για την επέκταση, μη διστάσετε να ανοίξετε ένα αίτημα λειτουργίας, αλλά ψάξτε το προηγουμένως για να βεβαιωθείτε ότι η λειτουργία δεν έχει ήδη προταθεί. Η χρήση της φόρμας για νέες λειτουργίες συστήνεται αλλά δεν είναι απαραίτητη.\n\n#### Υλοποιώντας ένα αίτημα νέας λειτουργίας\n\nΑν βρήκατε μια δυνατότητα που πιστεύετε ότι μπορεί να μπορέσετε να εφαρμόσετε, μην ντρέπεστε. Ανοίξτε ένα PR με την επιδιόρθωση και φροντίστε να αναφέρετε τη δυνατότητα που εφαρμόζετε.\n\n### Τι PR δεχόμαστε;\n\n- Διορθώσεις ζητημάτων.\n- Υλοποίηση νέων λειτουργιών.\n- Τυπογραφικά λάθη ή καλύτερες και πιο εύκολες λέξεις στη χρήση.\n- Συνεισφορές στην ιστοσελίδα.\n"
  },
  {
    "path": "CONTRIBUTINGhu.md",
    "content": "Read this in other languages: [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Üdvözlünk a Return YouTube Dislike közreműködői útmutatójában\n\nKöszönjük, hogy időt szánsz a projekthez való hozzájárulásra! Minden módosításod meg fog jelenni a bővítmény következő verziójában (vagy a [weboldalon](https://www.returnyoutubedislike.com/)).\n\n## Kezdő lépések\n\nKérjük, a formázáshoz használd a Prettier eszközt alapértelmezett beállításokkal.\n\n#### Előkövetelmények\n\nA forrás kimenetének elkészítéséhez szükséged lesz a Node és az npm telepítésére.\n\nAz előkészítéskor használt verziók:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nA `bundled-content-script.js` létrehozásához (ami a bővítmény üzleti logikájának nagy részét tartalmazza) először telepíteni kell az összes függőséget.\n\n1. Lépj a repó gyökerébe és futtasd:\n\n```\nnpm install\n```\n\n2. Futtasd az alábbi parancsot a `bundled-content-script.js` elkészítéséhez, amelyet a `manifest.json` használ:\n\n```\nnpm start // a build fájl(ok) létrehozása és egy watcher indítása, amely mentéskor újratölt\n\n// vagy\n\nnpm run build // a build fájl(ok) egyszeri létrehozása\n```\n\nGratulálunk, készen állsz a fejlesztésre!\n\nHa új vagy a Chrome‑bővítmények fejlesztésében, vagy extra segítségre van szükséged, nézd meg [ezt a YouTube‑oktatóanyagot](https://www.youtube.com/watch?v=mdOj6HYE3_0).\n\n### Hibák és problémák\n\n#### Új issue megnyitása\n\nHa problémád van a bővítménnyel, először keress rá, hogy nem jelentették‑e már. Ha nem, nyiss egy issue‑t. Erősen ajánlott az issue űrlap használata, de nem kötelező.\n\n#### Egy issue megoldása\n\nHa találtál egy problémát, amit meg tudsz oldani, ne habozz. Nyiss egy PR‑t a javítással, és mindenképp említsd meg, melyik issue‑t javítod.\n\n### Funkciókérések\n\n#### Új funkciókérés megnyitása\n\nHa van egy ötleted a bővítményhez, nyugodtan nyiss funkciókérést, de előtte keresd meg, hogy nem javasolták‑e már. A funkció űrlap használata ajánlott, de nem kötelező.\n\n#### Funkciókérés megvalósítása\n\nHa találtál egy funkciót, amit meg tudsz valósítani, ne habozz. Nyiss egy PR‑t a megvalósítással, és említsd meg a funkciót, amit implementálsz.\n\n### Milyen PR‑okat fogadunk el?\n\n- Hibajavítások.\n- Funkciók implementálása.\n- Elírások javítása, jobb és egyszerűbb megfogalmazások.\n- Közreműködések a weboldalon.\n"
  },
  {
    "path": "CONTRIBUTINGid.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Selamat datang di petunjuk kontribusi Return Youtube Dislikes\n\nTerima kasih telah menggunakan waktumu untuk berkontribusi pada proyek kami! Semua perubahan akan diaplikasikan pada versi extension selanjutnya (atau [website](https://www.returnyoutubedislike.com/)).\n\n## Mula-mula\n\nTolong gunakan Prettier dengan setting bawaan untuk melakukan formatting.\n\n#### Prerequisites\n\nKamu harus menginstall node dan npm untuk membuat versi bundled dari source code.\n\nVersi yang digunakan:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nUntuk membuat `bundled-content-script.js` yang memiliki semua logic dari extension ini, kamu harus menginstall semua dependency terlebih dahulu.\n\n1. Masuk ke root dari repo dan jalankan:\n\n```\nnpm install\n```\n\n2. Jalankan command berikut untuk membuat `bundled-content-script.js` yang akan digunakan di `manifest.json`\n\n```\nnpm start // untuk membuat build file dan menjalankan file watcher yang akan melakukan hot-reloads ketika menyimpan.\n\n// atau\n\nnpm run build // untuk membuat build file satu kali.\n```\n\nSelamat, kamu sekarang sudah siap untuk melakukan develop!\n\nJika kamu baru dalam melakukan develop extension Chrome, atau membutuhkan bantuan tambahan, tolong kunjungi [tutorial Youtube ini](https://www.youtube.com/watch?v=mdOj6HYE3_0).\n\n### Issues\n\n#### Membuat issue baru\n\nJika kamu menemuakan issue mengenai extension, tolong cari terlebih dahulu untuk memastikan issue tersebuat telah di laporkan. Jika belum, buat issue baru, direkomendasikan menggunakan form issue tapi tidak harus.\n\n#### Menyelesaikan issue\n\nJika kamu menemukan issue yang dapat kamu selesaikan, jangan malu. Buat PR dan pastikan untuk mention issue yang sedang diperbaiki.\n\n### Rqeust Fitur\n\n#### Membuat request fitur\n\nJika kamu memiliki ide mengenai extension, silakan untuk membuat request fitur, tapi tolong cari terlebih dahulu untuk memastikan fitur tersebut belum direquest oleh orang lain. Direkomendasikan menggunakan form request fitur tapi tidak harus.\n\n#### Implementasi request fitur\n\nJika kamu menemukan request fitur yang dapat kamu kerjakan, jangan malu. Buat PR dan pastikan untuk mention request fitur yang sedang dikerjakan.\n\n### PR apa saja yang kami terima?\n\n- Memperbaiki issue.\n- Implementasi fitur.\n- Typo atau improvisasi kata dan kalimat.\n- Kontribusi website.\n"
  },
  {
    "path": "CONTRIBUTINGja.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Return YouTube Dislike へのコントリビュートガイドへようこそ\n\n本プロジェクトへのご協力に時間を割いていただきありがとうございます！ 皆さまの変更は、拡張機能の次のリリース（または[ウェブサイト](https://www.returnyoutubedislike.com/)）に反映されます。\n\n## はじめに\n\nフォーマットには Prettier をデフォルト設定のまま使用してください。\n\n#### 前提条件\n\nソースのバンドル版を作成するには、Node と npm をインストールする必要があります。\n\nセットアップ時のバージョン:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nこの拡張機能の大部分のロジックを含む `bundled-content-script.js` を作成するには、まず依存関係をインストールします。\n\n1. リポジトリのルートに移動して、以下を実行します:\n\n```\nnpm install\n```\n\n2. `manifest.json` で使用される `bundled-content-script.js` を作成するには、次のコマンドを実行します。\n\n```\nnpm start // ビルドファイルを作成し、保存時に自動リロードするウォッチャーを開始\n\n// または\n\nnpm run build // 一度だけビルドファイルを作成\n```\n\nおめでとうございます。これで開発を始める準備ができました！\n\nChrome 拡張の開発が初めて、または追加のヘルプが必要な場合は、[こちらの YouTube チュートリアル](https://www.youtube.com/watch?v=mdOj6HYE3_0)をご覧ください。\n\n### Issue について\n\n#### 新しい Issue を作成する\n\n拡張機能に問題がある場合は、既に報告されていないか検索してください。未報告であれば Issue を作成してください。Issue フォームの使用は強く推奨しますが必須ではありません。\n\n#### Issue を解決する\n\n解決できそうな Issue を見つけた場合は、遠慮なく修正の PR を作成し、修正した Issue を必ず明記してください。\n\n### 機能リクエスト\n\n#### 新しい機能リクエストを作成する\n\n拡張機能に関するアイデアがある場合は、自由に機能リクエストを作成してください。ただし、既に提案されていないか事前に検索してください。機能フォームの使用は強く推奨しますが必須ではありません。\n\n#### 機能リクエストを実装する\n\n実装できそうな機能を見つけた場合は、遠慮なく実装の PR を作成し、実装する機能を必ず明記してください。\n\n### 受け付ける PR\n\n- 不具合修正\n- 機能の実装\n- 誤字修正や、より良く簡潔な表現への改善\n- ウェブサイトへの貢献\n"
  },
  {
    "path": "CONTRIBUTINGkr.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Return YouTube Dislike 기여 가이드에 오신 것을 환영합니다\n\n프로젝트에 기여해 주셔서 감사합니다! 여러분의 변경 사항은 확장 프로그램의 다음 버전(또는 [웹사이트](https://www.returnyoutubedislike.com/))에 반영됩니다.\n\n## 시작하기\n\n코드 포매팅에는 기본 설정의 Prettier를 사용해 주세요.\n\n#### 사전 준비물\n\n소스의 번들 버전을 만들려면 Node와 npm이 설치되어 있어야 합니다.\n\n설정 시 사용한 버전:\n\n- node: 12.18.4\n- npm: 6.14.6\n\n이 확장 프로그램의 대부분의 비즈니스 로직을 담은 `bundled-content-script.js`를 생성하려면 먼저 모든 의존성을 설치해야 합니다.\n\n1. 저장소 루트로 이동하여 다음을 실행하세요:\n\n```\nnpm install\n```\n\n2. `manifest.json`에서 사용하는 `bundled-content-script.js`를 생성하려면 다음 명령을 실행하세요:\n\n```\nnpm start // 빌드 파일을 생성하고 저장 시 자동 새로고침하는 감시자 시작\n\n// 또는\n\nnpm run build // 빌드 파일을 한 번만 생성\n```\n\n축하합니다. 이제 개발할 준비가 되었습니다!\n\nChrome 확장 프로그램 개발이 처음이거나 추가 도움이 필요하다면 [이 YouTube 튜토리얼](https://www.youtube.com/watch?v=mdOj6HYE3_0)을 참고하세요.\n\n### 이슈\n\n#### 새 이슈 만들기\n\n확장 프로그램에 문제가 있다면, 먼저 동일한 이슈가 이미 보고되지 않았는지 검색해 주세요. 없다면 이슈를 생성해 주세요. 이슈 템플릿 사용을 권장하지만 필수는 아닙니다.\n\n#### 이슈 해결하기\n\n해결할 수 있을 것 같은 이슈를 찾았다면 주저하지 마세요. 수정 사항으로 PR을 열고, 해결 중인 이슈를 꼭 언급해 주세요.\n\n### 기능 요청\n\n#### 새 기능 요청 만들기\n\n확장 프로그램에 대한 아이디어가 있다면 기능 요청을 자유롭게 열어 주세요. 다만, 이미 제안된 내용이 아닌지 먼저 검색해 주세요. 기능 템플릿 사용을 권장하지만 필수는 아닙니다.\n\n#### 기능 요청 구현하기\n\n구현할 수 있을 것 같은 기능을 찾았다면 주저하지 마세요. 구현 내용으로 PR을 열고, 구현하는 기능을 꼭 언급해 주세요.\n\n### 어떤 PR을 수락하나요?\n\n- 버그 수정\n- 기능 구현\n- 오타 수정 또는 더 좋고 간결한 표현으로 개선\n- 웹사이트 관련 기여\n"
  },
  {
    "path": "CONTRIBUTINGnl.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Welkom bij de YouTube Dislikes bijdragengids\n\nBedankt voor het investeren van uw tijd in het bijdragen aan ons project! Al uw wijzigingen worden weergegeven in de volgende versie van de extensie (of de [website](https://www.returnyoutubedislike.com/)).\n\n## Aan de slag\n\nGebruik Prettier met standaardinstellingen voor opmaak.\n\n#### Vereisten\n\nU moet node en npm hebben geïnstalleerd om de gebundelde versie van de broncode te maken.\n\nVersies gebruikt bij het instellen:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nOm de `bundled-content-script.js` te maken die de meeste bedrijfslogica van deze extensie bevat, moet u eerst alle afhankelijkheden installeren.\n\n1. Ga naar de hoofdmap van de repo en voer het volgende uit:\n\n```\nnpm install\n```\n\n2. Voer de volgende opdracht uit om `bundled-content-script.js` aan te maken dat wordt gebruikt in `manifest.json`\n\n```\nnpm start // om het (de) buildbestand(en) te maken en een bestandswachter te starten die bij het opslaan opnieuw wordt geladen\n\n// of\n\nnpm run build // om het (de) buildbestand(en) eenmaal te maken\n```\n\nGefeliciteerd, je bent nu klaar om te ontwikkelen!\n\nAls je nieuw bent in het ontwikkelen van Chrome-extensies of extra hulp nodig hebt, bekijk dan [deze YouTube-tutorial](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Problemen\n\n#### Een nieuw probleem openen\n\nAls je problemen hebt met de extensie, zoek dan eerst of het probleem nog niet is gemeld. Als dit niet het geval is, open dan een probleem, het gebruik van het probleemformulier wordt sterk aanbevolen, maar is niet verplicht.\n\n#### Een probleem oplossen\n\nAls je een probleem hebt gevonden waarvan je denkt dat je het zou kunnen oplossen, wees dan niet verlegen. Open een PR met de oplossing en vermeld het probleem dat u aan het oplossen bent.\n\n### Functieverzoek:\n\n#### Een nieuw functieverzoek openen\n\nAls je een idee hebt voor de extensie, kun je een functieverzoek openen, maar zoek het eerst op om er zeker van te zijn dat de functie niet al is voorgesteld. Het gebruik van het functieformulier wordt sterk aanbevolen, maar is niet verplicht.\n\n#### Een functieverzoek implementeren\n\nAls je een functie hebt gevonden waarvan je denkt dat je die zou kunnen implementeren, wees dan niet verlegen. Open een PR met de oplossing en zorg ervoor dat u de functie vermeldt die u implementeert.\n\n### Welke PR's accepteren we?\n\n- Probleemoplossingen.\n- Implementatie van functies.\n- Typefouten of betere en gemakkelijkere woorden om te gebruiken.\n- Website bijdragen.\n"
  },
  {
    "path": "CONTRIBUTINGpl.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Witamy w przewodniku współtworzenia Return YouTube Dislike\n\nDziękujemy za zainwestowanie czasu w rozwój naszego projektu! Wszystkie Twoje zmiany znajdą się w następnej wersji rozszerzenia ([bądź strony](https://www.returnyoutubedislike.com/)).\n\n## Początek\n\nProsimy używać Prettier z domyślnymi ustawieniami do formatowania.\n\n#### Wymagania wstępne\n\nMusisz mieć zainstalowane node i npm, aby utworzyć dołączoną wersję źródła.\n\nWersje używane przy ustawianiu:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nAby utworzyć `bundled-content-script.js`, które zawiera większość logiki tego rozszerzenia, musisz najpierw zainstalować wszystkie zależności.\n\n1. Przejdź do korzenia tego repo i uruchom:\n\n```\nnpm install\n```\n\n2. Użyj polecenia poniżej aby stworzyć `bundled-content-script.js`, które jest używane w `manifest.json`\n\n```\nnpm start // aby utworzyć plik(i) build-u i uruchomić obserwatora pliku, który przeładowuje po zapisie\n\n// lub\n\nnpm run build // aby jednorazowo utworzyć plik(i) build-u\n```\n\nGratulacje, jesteś gotów pisać!\n\nJeśli jesteś nowy w pisaniu rozszerzeń do Chrome, lub potrzebujesz dodatkowej pomocy, obejrzyj [ten poradnik na YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Problemy\n\n#### Otwieranie nowego problemu\n\nJeśli masz jakiekolwiek problemy z rozszerzeniem, najpierw wyszukaj go aby upewnić się, że dany problem nie został już zgłoszony. Jeżeli nie, otwórz problem. Używanie formularza problemu jest zalecane, ale nie jest konieczne.\n\n#### Rozwiązywanie problemu\n\nJeżeli znalazłeś problem, który myślisz, że jesteś w stanie rozwiązać, nie wstydź się. Otwórz PR z fix-em i opisz problem, który naprawiasz.\n\n### Prośba o funkcjonalność\n\n#### Otwieranie nowej prośby o funkcjonalność\n\nJeżeli masz pomysł na rozszerzenie, śmiało otwórz nowe żądanie o funkcjonalność, ale prosimy o wyszukanie swojego pomysłu, aby upewnić się, że nie został on już zasugerowany. Używanie formularza jest zalecane, ale nie jest konieczne.\n\n#### Implementacja prośby o funkcjonalność\n\nJeżeli znalazłeś pomysł na funkcjonalność, którą myślisz, że jesteś w stanie zaimplementować, nie wstydź się. Otwórz PR z fix-em, i opisz funkcjonalność, którą implementujesz.\n\n### Jakie PR-y przyjmujemy?\n\n- Naprawy problemów.\n- Implementacja funkcjonalności.\n- Literówki lub lepsze i łatwiejsze w zrozumieniu słowa.\n- Współtworzenie strony.\n"
  },
  {
    "path": "CONTRIBUTINGpt_BR.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Bem-vindo ao guia de contribuição do Return YouTube Dislikes\n\nObrigado por investir seu tempo contribuindo para o nosso projeto! Todas as mudanças serão refletidas na próxima versão da extensão (ou do [website](https://www.returnyoutubedislike.com/)).\n\n## Começando\n\nPor favor use o Prettier com as configurações padrão de formatação.\n\n#### Pré requisitos\n\nVocê precisa ter o node e o npm instalados para criar o pacote a partir do código-fonte.\n\nVersões usadas na configuração:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nPara criar o `bundled-content-script.js` que contém muita da lógica de negócio dessa extensão você precisará instalar todas as dependências primeiro.\n\n1. Vá para a raiz do repositório e execute:\n\n```\nnpm install\n```\n\n2. Execute o comando a seguir para criar o `bundled-content-script.js` que será usado no `manifest.json`\n\n```\nnpm start // Para criar os arquivos de build e iniciar um file watcher que recarrega o conteúdo automaticamente ao salvar\n\n// ou\n\nnpm run build // Para criar os arquivos de build apenas uma vez\n```\n\nParabéns, agora você está pronto para desenvolver!\n\nSe você é novo no desenvolvimento de extensões para o Chrome, ou precisa de ajuda extra, por favor dê uma olhada [nesse tutorial do YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Problemas(Issues)\n\n#### Abrindo uma nova issue\n\nSe você tiver qualquer problema com a extensão, por favor pesquise nas issues do nosso repositório para ter certeza de que o problema já não foi reportado. Se não foi, abra uma issue, usar o formulário de issues é altamente recomendado mas não é obrigatório.\n\n#### Resolvendo issues\n\nSe você encontrou uma issue que você sente que é capaz de corrigir, não tenha vergonha. Abra uma PR com a correção e certifique-se de mencionar a issue que você está corrigindo.\n\n### Sugerir um nova funcionalidade(Feature request)\n\n#### Abrindo uma feature request\n\nSe você tem uma ideia de funcionalidade para a extensão, sinta-se livre para abrir uma feature request no nosso repositório, mas por favor certifique-se de que a funcionalidade já não foi sugerida. Usar o formulário de features é altamente recomendado mas não é obrigatório.\n\n#### Implementando uma feature request\n\nSe você encontrou uma feature request que você sente que é capaz de implementar, não tenha vergonha. Abra uma PR com a implementação e certifique-se de mencionar a feature request que você está implementando.\n\n### Quais PRs nós aceitamos?\n\n- Correção de problemas.\n- Implementação de funcionalidades.\n- Erros de digitação e palavras melhores ou mais fáceis de entender.\n- Contribuições para o Website.\n"
  },
  {
    "path": "CONTRIBUTINGru.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Добро пожаловать в руководство по внесению вклада Return YouTube Dislikes\n\nБлагодарим вас за то, что вы потратили своё время на участие в нашем проекте! Все ваши изменения будут отражены в следующей версии расширения (или на [сайте](https://www.returnyoutubedislike.com/)).\n\n## Приступая к работе\n\nПожалуйста, используйте Prettier с настройками по умолчанию для форматирования кода.\n\n#### Необходимое\n\nВам необходимо иметь установленные node и npm, чтобы создать bundled-версию источника.\n\nВерсии, используемые при настройке:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nДля создания `bundled-content-script.js` который содержит большую часть бизнес-логики этого расширения, вы должны сначала установить все зависимости.\n\n1. Перейдите в корень репозитория и выполните следующее:\n\n```\nnpm install\n```\n\n2. Выполните следующую команду, чтобы создать `bundled-content-script.js`, который используется в `manifest.json`\n\n```\nnpm start // для создания файла(ов) сборки и запуска наблюдателя за файлами, который выполняет hot-reload при сохранении\n\n// или\n\nnpm run build // для создания файла(ов) сборки один раз\n```\n\nПоздравляем, теперь вы готовы к разработке!\n\nЕсли вы новичок в разработке расширений Chrome или вам нужна дополнительная помощь, посмотрите [это руководство на YouTube (англ.)](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Вопросы\n\n#### Открытие нового вопроса/проблемы\n\nЕсли у вас возникли проблемы с расширением, пожалуйста, поищите, чтобы убедиться, что о проблеме ещё не сообщалось. Если это не так, откройте вопрос(issue), использование формы проблемы настоятельно рекомендуется, но не является обязательным.\n\n#### Решение вопроса\n\nЕсли вы нашли проблему, которую, как вам кажется, вы можете решить, не стесняйтесь. Откройте запрос на извлечение(pull request) с исправлением и обязательно укажите вопрос, который вы устраняете.\n\n### Запрос функции\n\n#### Открытие запроса на новую функцию\n\nЕсли у вас есть идея для расширения, не стесняйтесь открыть запрос на функцию, но, пожалуйста, прежде поищите другие запросы, чтобы убедиться, что функция уже не предложена. Использование формы запроса функции настоятельно рекомендуется, но не является обязательным\n\n#### Внедрение запроса на функцию\n\nЕсли вы нашли функцию, которую, как вам кажется, вы могли бы реализовать, не стесняйтесь. Откройте запрос на извлечение(pull request) с исправлением и обязательно укажите функцию, которую вы реализуете.\n\n### Какие запросы на извлечение(pull request) мы принимаем?\n\n- Исправления проблем.\n- Внедрения новых функций.\n- Исправления опечаток и упрощение текста.\n- Улучшения сайта.\n"
  },
  {
    "path": "CONTRIBUTINGsv.md",
    "content": "Read this in other languages: [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# Välkommen till Return YouTube Dislike – bidragsguide\n\nTack för att du lägger tid på att bidra till vårt projekt! Alla dina ändringar kommer att synas i nästa version av tillägget (eller på [webbplatsen](https://www.returnyoutubedislike.com/)).\n\n## Kom igång\n\nAnvänd gärna Prettier med standardinställningar för formatering.\n\n#### Förutsättningar\n\nDu behöver ha Node och npm installerat för att skapa den bundlade versionen av koden.\n\nVersioner som användes vid uppsättning:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nFör att skapa `bundled-content-script.js`, som innehåller större delen av tilläggets logik, behöver du först installera alla beroenden.\n\n1. Gå till repo‑ts roten och kör:\n\n```\nnpm install\n```\n\n2. Kör följande kommando för att skapa `bundled-content-script.js`, som används i `manifest.json`:\n\n```\nnpm start // skapar build‑fil(er) och startar en filbevakare som laddar om vid spara\n\n// eller\n\nnpm run build // skapar build‑fil(er) en gång\n```\n\nGrattis! Du är nu redo att utveckla.\n\nOm du är ny på att utveckla Chrome‑tillägg, eller behöver extra hjälp, se [den här YouTube‑guiden](https://www.youtube.com/watch?v=mdOj6HYE3_0).\n\n### Ärenden (Issues)\n\n#### Skapa ett nytt ärende\n\nOm du har problem med tillägget, sök först för att säkerställa att ärendet inte redan är rapporterat. Om inte, skapa ett ärende. Det rekommenderas starkt att använda ärendeformuläret, men det är inte obligatoriskt.\n\n#### Lösa ett ärende\n\nOm du hittat ett ärende du tror att du kan lösa, tveka inte. Öppna en PR med lösningen och nämn vilket ärende du åtgärdar.\n\n### Funktionsförslag\n\n#### Skapa ett nytt funktionsförslag\n\nHar du en idé för tillägget? Skapa gärna ett funktionsförslag, men sök först för att se till att förslaget inte redan finns. Att använda funktionsformuläret rekommenderas men är inte obligatoriskt.\n\n#### Implementera ett funktionsförslag\n\nOm du hittat en funktion du kan implementera, tveka inte. Öppna en PR med implementationen och nämn vilken funktion du implementerar.\n\n### Vilka PR:er accepterar vi?\n\n- Buggfixar.\n- Implementering av funktioner.\n- Rättning av stavfel eller förbättrade, enklare formuleringar.\n- Bidrag till webbplatsen.\n"
  },
  {
    "path": "CONTRIBUTINGtr.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n# \"YouTube Dislike Sayısını Geri Getir\"in katkı kılavuzuna Hoş Geldiniz\n\nProjemize katkıda bulunmak için zaman ayırdığınız için teşekkür ederiz! Tüm değişiklikleriniz, uzantının bir sonraki sürümüne (veya [internet sitesi](https://www.returnyoutubedislike.com/)ne) yansıtılacaktır.\n\n## Başlarken\n\nLütfen formatlama işlemi için, Prettier'i varsayılan ayarlardayken kullanın.\n\n#### Ön Şartlar\n\nKaynağın paketlenmiş sürümünü oluşturmak için node ve npm'nin kurulu olması gerekir.\n\nKurulum sırasında kullanılan sürümler:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nBu uzantının iş mantığının çoğunu içeren `bundled-content-script.js`yi oluşturmak için, önce tüm bağımlılıkları yüklemeniz gerekir.\n\n1. Deponun köküne gidin ve şu komutu çalıştırın:\n\n```\nnpm install\n```\n\n2. `manifest.json` içinde kullanılan `bundled-content-script.js` dosyasını oluşturmak için aşağıdaki komutu çalıştırın.\n\n```\nnpm start // derleme dosyasının/dosyalarının oluşturulması ve kaydedilmesi sırasında çalışırken yeniden yüklenen bir dosya izleyicisini başlatmak için\n\n// ya da\n\nnpm run build // derleme dosyasını/dosyalarını bir kez oluşturmak için\n```\n\nTebrikler, artık geliştirmeye hazırsınız!\n\nChrome uzantıları geliştirme konusunda yeniyseniz veya fazladan yardıma ihtiyacınız olursa lütfen [bu YouTube öğreticisi](https://www.youtube.com/watch?v=mdOj6HYE3_0)ne bakın.\n\n### Issue'lar\n\n#### Yeni bir issue başlatmak\n\nUzantıyla ilgili herhangi bir sorununuz varsa, sorunun önceden bildirilmediğinden emin olmak için lütfen arama yapın. Eğer daha önce bildirilmediyse, bir konu açın. Sorun formunu kullanmanız şiddetle tavsiye edilir ancak zorunlu değildir.\n\n#### Bir issue'yu çözmek\n\nÇözebileceğinizi düşündüğünüz bir sorun bulduysanız, çekinmeyin. Düzeltmeyi içeren bir PR açın ve düzelttiğiniz sorunu belirttiğinizden emin olun.\n\n### Özellik Talebi\n\n#### Yeni bir özellik talebi açmak\n\nUzantı hakkında bir fikriniz varsa, bir özellik isteği açmaktan çekinmeyin, ancak özelliğin daha önce önerilmediğinden emin olmak için lütfen önce arama yapın. Özellik formunun kullanılması şiddetle tavsiye edilir ancak zorunlu değildir.\n\n#### Bir özellik isteğini uygulamak\n\nUygulayabileceğinizi düşündüğünüz bir özellik bulduysanız, çekinmeyin. Düzeltmeyi içeren bir PR açın ve uyguladığınız özelliği belirttiğinizden emin olun.\n\n### Hangi tür PR'leri kabul ediyoruz?\n\n- Sorun düzeltmeleri.\n- Özellik uygulaması.\n- Yazım hataları veya daha anlaşılabilir ve kullanımı daha kolay kelimeler.\n- Site katkıları.\n"
  },
  {
    "path": "CONTRIBUTINGuk.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Вітаємо у посібнику внеску в Return YouTube Dislikes\n\nДякуємо, що вкладаєте свій час у розвиток нашого проєкту! Усі ваші зміни буде відображено в наступній версії розширення (або ж [вебсайту](https://www.returnyoutubedislike.com/)).\n\n## Почнімо працювати\n\nБудь ласка, використовуйте Prettier із налаштуваннями за замовчуванням для форматування коду.\n\n#### Заздалегідь\n\nВам потрібно встановити node і npm, щоб створити bundled версію джерела.\n\nВерсії, що використовувались при налаштуванні:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nДля створення `bundled-content-script.js`, який містить більшу частину бізнес-логіки цього розширення, спочатку потрібно встановити всі залежності.\n\n1. Перейдіть в корінь репозиторію та виконайте наступне:\n\n```\nnpm install\n```\n\n2. Виконайте наступну команду, щоб створити `bundled-content-script.js`, який використовується в `manifest.json`\n\n```\nnpm start // для створення файлу(ів) збірки та запуску спостерігача за файлами, який виконує hot-reload при збереженні\n\n// або ж\n\nnpm run build // для створення файлу(ів) збірки один раз\n```\n\nВітаємо, тепер ви готові до розробки!\n\nЯкщо ви новачок у розробці розширення Chrome або вам потрібна додаткова допомога, перегляньте [це керівництво на YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0) (англ.)\n\n### Проблеми\n\n#### Відкриття нової проблеми\n\nЯкщо у вас виникли проблеми з розширенням, здійсніть пошук і переконайтеся, що про цю проблему ще не повідомляли. Якщо ні, створіть Issue, використання форми проблеми наполегливо рекомендується, але не є обов’язковим.\n\n#### Вирішення проблеми\n\nЯкщо ви знайшли проблему, і гадаєте, що можете її вирішити, не соромтеся. Створіть Pull Request на вилучення з виправленням і обов'язково вкажіть проблему, яку ви усуваєте.\n\n### Запит функції\n\n#### Відкриття запиту на нову функцію\n\nЯкщо у вас є ідея щодо розширення, не соромтеся створіть Pull Request, але, будь ласка, здійсніть пошук і переконайтеся, що ця функція ще не запропонована. Використання форми функції наполегливо рекомендується, але не є обов’язковим.\n\n#### Реалізація запиту функції\n\nЯкщо ви знайшли функцію, і гадаєте, що можете її реалізувати, не соромтеся. Створіть Pull Request із виправленням і обов’язково вкажіть функцію, яку ви впроваджуєте.\n\n### Які Pull Request ми приймаємо?\n\n- Виправлення проблем.\n- Впровадження нових функцій.\n- Виправлення помилок та спрощення тексту.\n- Поліпшення сайту.\n"
  },
  {
    "path": "CONTRIBUTINGvi.md",
    "content": "Read this in other languages: [English](CONTRIBUTING.md), [العربية](CONTRIBUTINGar.md), [Azərbaycan dili](CONTRIBUTINGaz.md), [български](CONTRIBUTINGbg.md), [中文](CONTRIBUTINGcn.md), [Dansk](CONTRIBUTINGda.md), [Deutsch](CONTRIBUTINGde.md), [Español](CONTRIBUTINGes.md), [Français](CONTRIBUTINGfr.md), [Ελληνικά](CONTRIBUTINGgr.md), [Magyar](CONTRIBUTINGhu.md), [Bahasa Indonesia](CONTRIBUTINGid.md), [日本語](CONTRIBUTINGja.md), [한국어](CONTRIBUTINGkr.md), [Nederlands](CONTRIBUTINGnl.md), [Polski](CONTRIBUTINGpl.md), [Português do Brasil](CONTRIBUTINGpt_BR.md), [русский](CONTRIBUTINGru.md), [Svenska](CONTRIBUTINGsv.md), [Türkçe](CONTRIBUTINGtr.md), [українська](CONTRIBUTINGuk.md), [Tiếng Việt](CONTRIBUTINGvi.md)\n\n\n# Chào mừng tới Hướng dẫn Đóng góp của Return YouTube Dislike (Trả lại số lượt Không thích trên YouTube) <!-- # Welcome To Return YouTube Dislikes contributing guide -->\n\nCảm ơn bạn đã dành thời gian để đóng góp cho dự án của chúng tôi! Tất cả các thay đổi của bạn sẽ được hiển thị trong phiên bản tiếp theo của tiện ích mở rộng này (hoặc của [trang mạng](https://www.returnyoutubedislike.com/)).\n\n## Bắt đầu <!-- ## Getting Started -->\n\nHãy dùng [Prettier](https://prettier.io/) với thiết lập mặc định để định dạng mã.\n\n#### Yêu cầu sơ bộ <!-- ### Prerequisites -->\n\nBạn cần phải cài **node** và **npm** để tạo bản đóng gói của mã nguồn.\n\nCác phiên bản được dùng khi cài đặt:\n\n- node: 12.18.4\n- npm: 6.14.6\n\nDể có thể tạo tệp `bundled-content-script.js`, trong đó có chứa hầu hết các lôgic kinh doanh của tiện ích mở rộng này, trước tiên bạn phải cài các đối tượng phụ thuộc.\n\n1. Tới thư mục gốc của kho mã nguồn và chạy lệnh:\n\n```\nnpm install\n```\n\n2. Chạy lệnh dưới đây để tạo `bundled-content-script.js`, sẽ dùng tới trong `manifest.json`\n\n```\nnpm start // để tạo (các) tệp xây dựng và khởi chạy một trình quan sát tập tin, đảm nhiệm việc tự động tải lại dự án khi có thay đổi được lưu\n\n// hoặc\n\nnpm run build // để tạo (các) tệp xây dựng chỉ một lần\n```\n\nChúc mừng! Bạn đã sẵn sàng để phát triển chương trình!\n\nNếu bạn chưa bao giờ phát triển tiện ích mở rộng cho Chrome hoặc cần sự trợ giúp, hãy xem [hướng dẫn này trên YouTube](https://www.youtube.com/watch?v=mdOj6HYE3_0)\n\n### Vấn đề <!-- ### Issues -->\n\n#### Tạo một vấn đề mới <!-- #### Opening a new issue -->\n\nNếu bạn có bất kì vấn đề gì với tiện ích mở rộng này, trước tiên hãy đọc qua danh sách các vấn đề đang có. Nếu vấn đề của bạn không có trong danh sách các vấn đề, hãy [tạo một vấn đề](https://github.com/Anarios/return-youtube-dislike/issues/new?assignees=&labels=bug&template=bug.yml&title=%28Bug%29%3A+). Dùng mẫu đơn vấn đề nếu có thể, nhưng không bắt buộc.\n\n#### Giải quyết một vấn đề <!-- #### Solving an issue -->\n\nNếu bạn cảm thấy có thể giải quyết một vấn đề nào đó, đừng ngần ngại. Hãy tạo một [yêu cầu kéo](https://github.com/Anarios/return-youtube-dislike/pulls) cho sự thay đổi của bạn và nhớ hãy ghi tên lỗi mà bạn giải quyết.\n\n### Yêu cầu Tính năng <!-- ### Feature Request -->\n\n#### Tạo một yêu cầu tính năng mới <!-- #### Opening a new feature request -->\n\nNếu bạn có một ý tưởng dành cho tiện ích mở rộng này, hãy mạnh dạn [tạo một yêu cầu chức năng](https://github.com/Anarios/return-youtube-dislike/issues/new?assignees=&labels=enhancement&template=feature-request.yml&title=%28Feature+Request%29%3A+), nhưng hãy chắc rằng bạn đã tìm và không thấy yêu cầu tính năng y hệt trong danh sách yêu cầu tính năng. Dùng mẫu đơn yêu cầu tính năng nếu có thể, nhưng không bắt buộc.\n\n#### Thực hiện một yêu cầu tính năng <!-- #### Implementing a feature request -->\n\nNếu bạn cảm thấy có thể thực hiện một tính năng nào đó, đừng ngần ngại. Hãy tạo một [yêu cầu kéo](https://github.com/Anarios/return-youtube-dislike/pulls) cho sự thay đổi của bạn và nhớ hãy ghi tên tính năng mà bạn thực hiện.\n\n### Những yêu cầu kéo mà chúng tôi chấp nhận? <!-- ### What PRs do we accept? -->\n\n- Giải quyết vấn đề.\n- Thực hiện tính năng.\n- Sửa lỗi chính tả hoặc đề xuất cách dùng từ tốt hơn.\n- Đóng góp cho trang mạng.\n"
  },
  {
    "path": "Docs/FAQ.md",
    "content": "Read this in other languages: [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [Português do Brasil](FAQpt_BRmd), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n\n# Frequently Asked Questions\n\n## Before asking a question on GitHub or Discord, please refer to this.\n\n<br>\n\n### **1. Where does this extension get the data?**\n\nA Combination of Google APIs and scraped data.\n\nWe save all available data to our DB for it to be available after Google shuts down dislike counts in their API.\n\n<br>\n\n### **2. Video dislike count doesn't update**\n\nRight now video dislikes are cached, and aren't updated very frequently. Once every 2-3 days, not more often.\n\nYeah, it's not ideal, but it is what it is. Working on improving how often we can update them.\n\n<br>\n\n### **3. How does this work?**\n\nThe extension collects the video id of the video you are watching, fetches the dislike (and other fields like views, likes etc) using our API, if this is the first time the video was fetched by our API, it will use the YouTube API to get the data, then stores the data in a database for caching (cached for around 2-3 days) and archiving purposes and returns it to you. The extension then displays the dislikes to you.\n\n<br>\n\n### **4. What will happen after the YouTube API stops returning the dislike count?**\n\nThe backend will switch to using a combination of archived dislike stats, estimates extrapolated from extension user data and estimates based on view/like ratios for videos whose dislikes weren't archived and for outdated dislike archives.\n\n<br>\n\n### **5. How is the dislike count calculated?**\n\nRYD uses the votes from its users to extrapolate the dislike count.\n\n- If the video was uploaded after the API was shut down:\n\n  $$ \\textup{RYD Dislike Count} = \\left( \\frac{\\textup{RYD Users Dislike Count}}{\\textup{RYD Users Like Count}} \\right) \\times \\textup{Public Like Count} $$\n\n- If the RYD database somehow had the actual like and dislike count (provided by the uploader or from the archive), the dislike count will be calculated based on both - the users' votes and the archived value. The archived value will have less influence on the final count as it ages.\n\n<br>\n\n---\n\nThis in video form\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## I have security / privacy concerns\n\nSee [this page](SECURITY-FAQ.md) for more info.\n"
  },
  {
    "path": "Docs/FAQar.md",
    "content": "اقرأ هذا بلغات أخرى: [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [Português do Brasil](FAQpt_BRmd)\n\n# الأسئلة الشائعة\n\n## قبل طرح سؤال على GitHub أو Discord، يرجى الرجوع إلى هذا.\n\n<br>\n\n### **1. من أين تحصل هذه الإضافة على البيانات؟**\n\nمزيج من واجهات برمجة تطبيقات Google والبيانات المستخرجة.\n\nنحن نحفظ جميع البيانات المتاحة في قاعدة بياناتنا لتكون متاحة بعد أن تقوم Google بإيقاف عداد عدم الإعجاب في واجهة برمجة التطبيقات الخاصة بهم.\n\n<br>\n\n### **2. عداد عدم الإعجاب بالفيديو لا يتم تحديثه**\n\nحاليًا، يتم تخزين عدم الإعجاب بالفيديو مؤقتًا، ولا يتم تحديثه بشكل متكرر. مرة كل 2-3 أيام، وليس أكثر.\n\nنعم، إنه ليس مثاليًا، لكنه ما هو عليه. نعمل على تحسين عدد مرات التحديث.\n\n<br>\n\n### **3. كيف يعمل هذا؟**\n\nتجمع الإضافة معرف الفيديو الذي تشاهده، وتسترجع عدم الإعجاب (وحقول أخرى مثل المشاهدات، الإعجابات، إلخ) باستخدام واجهة برمجة التطبيقات الخاصة بنا، إذا كانت هذه هي المرة الأولى التي يتم فيها استرجاع الفيديو بواسطة واجهة برمجة التطبيقات الخاصة بنا، فستستخدم واجهة برمجة تطبيقات YouTube للحصول على البيانات، ثم تخزن البيانات في قاعدة بيانات للتخزين المؤقت (مخزنة مؤقتًا لمدة 2-3 أيام) ولأغراض الأرشفة وتعيدها إليك. ثم تعرض الإضافة عدم الإعجاب لك.\n\n<br>\n\n### **4. ماذا سيحدث بعد أن تتوقف واجهة برمجة تطبيقات YouTube عن إرجاع عداد عدم الإعجاب؟**\n\nسيتحول الخلفية إلى استخدام مزيج من إحصائيات عدم الإعجاب المؤرشفة، والتقديرات المستخلصة من بيانات مستخدمي الإضافة والتقديرات بناءً على نسب المشاهدة/الإعجاب للفيديوهات التي لم يتم أرشفة عدم الإعجاب بها ولأرشيفات عدم الإعجاب القديمة.\n\n<br>\n\n### **5. كيف يتم حساب عداد عدم الإعجاب؟**\n\nتستخدم RYD أصوات مستخدميها لاستخلاص عداد عدم الإعجاب.\n\n- إذا تم تحميل الفيديو بعد إيقاف واجهة برمجة التطبيقات:\n\n  $$ \\textup{عداد عدم الإعجاب في RYD} = \\left( \\frac{\\textup{عداد عدم الإعجاب لمستخدمي RYD}}{\\textup{عداد الإعجاب لمستخدمي RYD}} \\right) \\times \\textup{عداد الإعجاب العام} $$\n\n- إذا كانت قاعدة بيانات RYD تحتوي بطريقة ما على عداد الإعجاب وعدم الإعجاب الفعلي (مقدم من الناشر أو من الأرشيف)، فسيتم حساب عداد عدم الإعجاب بناءً على كل من - أصوات المستخدمين والقيمة المؤرشفة. سيكون للقيمة المؤرشفة تأثير أقل على العد النهائي مع مرور الوقت.\n\n<br>\n\n---\n\nهذا في شكل فيديو\n\n[![شرح IReturn YouTube Dislike](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## لدي مخاوف بشأن الأمان / الخصوصية\n\nانظر [هذه الصفحة](SECURITY-FAQ.md) لمزيد من المعلومات."
  },
  {
    "path": "Docs/FAQaz.md",
    "content": "Bunu digər dillərdə oxuyun: [English](FAQ.md), [русский](FAQru.md), [Nederlands](FAQnl.md), [Français](FAQfr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md)\n\n# Tez-tez verilən suallar\n\n## GitHub və ya Discord-da sual verməzdən əvvəl bura nəzər salın.\n\n<br>\n\n### **1.Bu uzantı məlumatları haradan əldə edir??**\n\nGoogle API-lərin və silinmiş məlumatların birləşməsi.Google API-lərin və silinmiş məlumatların birləşməsi.\n\nGoogle öz API-də bəyənməmə saylarını bağladıqdan sonra istifadə oluna bilməsi üçün bütün mövcud məlumatları verilənlər bazamızda saxlayırıq.\n<br>\n\n### **2. Videonun bəyənməmə sayı yenilənmir**\n\nHazırda videonun bəyənməmələri keşlənir və tez-tez yenilənmir. Hər 2-3 gündə, daha tez-tez deyil.\n\nBəli, bu ideal deyil, amma olduğu kimidir. Biz bunları daha tez-tez necə yeniləyə biləcəyimizi anlamaq üzərində işləyirik.\n<br>\n\n### **3. Bu uzantı necə işləyir?**\n\nArtırma bizim API-dən istifadə edərək baxdığınız videonun video identifikatorunu götürür, bəyənmədiklərini (və baxışlar, bəyənmələr və s. kimi digər sahələri) alır; video API tərəfindən ilk dəfə alınırsa, YouTube API-dən istifadə edir. Məlumatları əldə etmək üçün o, keşləmə (təxminən 2-3 gün ərzində yaddaşda saxlanılır) və arxivləşdirmə məqsədləri üçün verilənlər bazasında saxlanılır və sizə qaytarılır. Daha sonra uzantı sizə bəyənmədiyinizləri göstərir.\n\n<br>\n\n### **4. YouTube API bəyənməmə saylarını qaytarmağı dayandırdıqda nə baş verir?**\n\nBackend arxivləşdirilmiş bəyənməmə statistikası, genişləndirmə istifadəçi məlumatlarından ekstrapolyasiya edilmiş təxminlər və arxivləşdirilməmiş videolar və köhnə bəyənməmə arxivləri üçün baxış/bəyənmə nisbətlərinə əsaslanan təxminlərin birləşməsindən istifadə etməyə keçəcək.\n\n<br>\n\n### **5. Bəyənməmələrin sayı necə hesablanır?**\n\nYDS bəyənməmələrin sayını hesablamaq üçün istifadəçilərinin səslərindən istifadə edir.\n\n- Video API bağlandıqdan sonra yüklənibsə:\n\n  $$ \\textup{YDS Bəyənməmə Sayısı} = \\left( \\frac{\\textup{YDS İstifadəçiləri Saymağı Bəyənirlər}}{\\textup{YDS İstifadəçilərinin Bəyənmədiyi Say}} \\right) \\times \\textup{İctimai Bəyənmə Sayısı} $$\n\n- Əgər YDS verilənlər bazasında hansısa şəkildə real bəyənmə və bəyənməmə sayları varsa (yükləyən və ya arxivdən təqdim olunur), bəyənməmə sayı həm istifadəçilərin səsləri, həm də arxivləşdirilmiş dəyər əsasında hesablanacaq. Arxivləşdirilmiş dəyər yaşlandıqca son hesaba daha az təsir edəcək.\n<br>\n\n---\n\nBu video formada\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Məxfilik/təhlükəsizliklə bağlı narahatlıqlarım var\nƏtraflı məlumat üçün [bu səyfə](SECURITY-FAQtr.md)ya baxın.\n"
  },
  {
    "path": "Docs/FAQbg.md",
    "content": "Прочетете това на други езици:  [English](FAQ.md), [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md)\n\n\n# Често задавани въпроси\n\n## Преди да зададете въпрос в GitHub или Discord, моля, обърнете се към това.\n\n<br>\n\n### **1. Откъде взема разширението тези данни?**\n\nКомбинация от Google APIs и скрейпнати данни.\n\nЗапазваме всички налични данни в нашата база данни, за да бъдат налични след като Google затвори броя на дизлайковете в техния API.\n\n<br>\n\n### **2. Броят на дизлайковете на видеото не се обновява**\n\nВ момента дизлайковете на видеата се кешират и не се обновяват много често. Веднъж на всеки 2-3 дни, не по-често.\n\nДа, не е идеално, но каквото, такова. Работим по подобрение на това, колко често можем да ги актуализираме.\n\n<br>\n\n### **3. HКак работи това?**\n\nРазширението взима video id на видеото, което гледате, извлича дизлайковете (и други полета като гледания, харесвания и т.н.) чрез нашия API. Ако това е първият път, когато видеото беше извлечено от нашия API, той ще използва YouTube API, за да получи данните, след което ще ги запази в база данни за кеширане (кеширане за около 2-3 дни) и архивиране и ще ги върне. Разширението след това ви показва дизлайковете.\n\n<br>\n\n### **4. Какво ще стане, след като YouTube API спре да връща броя на дизлайковете?**\n\nБекендът ще премине към използване на комбинация от архивирани статистики за дизлайкове, оценки екстраполирани от данните на потребителите на разширението и оценки на базата на съотношението гледания/харесвания за видеа, чиито дизлайкове не са били архивирани и за стари архиви с дизлайкове.\n\n<br>\n\n### **5. Как се изчислява броят на дизлайковете?**\n\nRYD използва гласовете от своите потребители, за да екстраполира броя на дизлайковете.\n\n- Ако видеото е качено след като API е затворил:\n\n  $$ \\textup{RYD Брой на дизлайковете} = \\left( \\frac{\\textup{RYD Брой на дизлайковете от потребителите}}{\\textup{RYD Брой на харесвания от потребителите}} \\right) \\times \\textup{Общ брой харесвания} $$\n\n- Ако базата данни на RYD по някакъв начин има реалния брой на харесвания и дизлайкове (предоставени от качителя или от архива), броят на дизлайковете ще бъде изчислен на базата и на гласовете на потребителите, и на архивната стойност. Архивната стойност ще има по-малко влияние върху крайния брой, докато остарее.\n\n<br>\n\n---\n\nТова във видео форма\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Имам опасения относно сигурността / личните данни\n\nВижте [тази страница](SECURITY-FAQbg.md) за повече информация.\n"
  },
  {
    "path": "Docs/FAQcn.md",
    "content": "以其他语言阅读: [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [Português do Brasil](FAQpt_BRmd)\n\n\n# 常见问题\n\n## 在 GitHub 或 Discord 上提问之前，请先参阅此内容.\n\n<br>\n\n### **1. 此扩展程序从哪里获取数据?**\n\nGoogle API 和抓取数据的组合。\n\n我们将所有可用数据保存到我们的数据库中，以便 Google 关闭其 API 中的不喜欢计数后仍可用。\n\n<br>\n\n### **2. 视频不喜欢计数未更新**\n\n目前，视频的点赞信息会被缓存，更新频率不高。每 2-3 天更新一次，不会更频繁。\n\n这并不理想，但这就是现状。我们正在努力提高更新频率。\n\n<br>\n\n### **3. 这是如何运作的?**\n\n该扩展程序会收集您正在观看的视频的视频 ID，使用我们的 API 获取不喜欢的内容（以及其他字段，如观看次数、喜欢次数等），如果这是我们的 API 首次获取该视频，它将使用 YouTube API 获取数据，然后将数据存储在数据库中以供缓存（缓存约 2-3 天）和存档，并将其返回给您。然后，该扩展程序会向您显示不喜欢的内容。\n\n<br>\n\n### **4. YouTube API 停止返回不喜欢计数后会发生什么?**\n\n后端将切换到使用已存档的不喜欢统计数据、从扩展用户数据推断出的估计值以及基于未存档的不喜欢的视频和过时的不喜欢存档的观看/喜欢比率的估计值的组合。\n\n<br>\n\n### **5. 如何计算不喜欢数?**\n\nRYD 使用用户的投票来推断不喜欢的数量。\n\n- 如果视频是在 API 关闭后上传的:\n\n  $$ \\textup{RYD Dislike Count} = \\left( \\frac{\\textup{RYD Users Dislike Count}}{\\textup{RYD Users Like Count}} \\right) \\times \\textup{Public Like Count} $$\n\n- 如果 RYD 数据库以某种方式拥有实际的喜欢和不喜欢计数（由上传者提供或来自存档），则不喜欢计数将根据用户的投票和存档值计算。存档值随着时间的推移对最终计数的影响会越来越小。\n\n<br>\n\n---\n\n这是视频形式\n\n[![IReturn YouTube 不喜欢解释](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## I have security / privacy concerns\n\n有关详细信息，请参阅[此页面](SECURITY-FAQ.md)。"
  },
  {
    "path": "Docs/FAQde.md",
    "content": "Read this in other languages: [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md)\n\n# Häufig gestellte Fragen\n\n## Bevor Sie eine Frage auf GitHub oder Discord stellen, sehen Sie bitte hier nach.\n\n<br>\n\n### **1. Woher stammt die Datenquelle dieser Erweiterung?**\n\nEine Kombination aus Google-APIs und gescrapten Daten.\n\nWir speichern alle verfügbaren Daten in unserer Datenbank, damit sie verfügbar sind, nachdem Google die Anzahl der Dislikes in ihrer API abschaltet.\n\n<br>\n\n### **2. Die Anzahl der Dislikes bei Videos aktualisiert sich nicht**\n\nAktuell werden die Dislikes bei Videos zwischengespeichert und nicht sehr häufig aktualisiert. Etwa alle 2-3 Tage, nicht öfter.\n\nJa, das ist nicht ideal, aber es ist wie es ist. Wir arbeiten daran, wie oft wir sie aktualisieren können, zu verbessern.\n\n<br>\n\n### **3. Wie funktioniert das?**\n\nDie Erweiterung sammelt die Video-ID des von Ihnen angesehenen Videos, ruft die Dislikes (und andere Felder wie Aufrufe, Likes usw.) über unsere API ab. Wenn dies das erste Mal ist, dass das Video von unserer API abgerufen wurde, wird die YouTube-API verwendet, um die Daten zu erhalten. Anschließend werden die Daten für den Zwischenspeicher (ca. 2-3 Tage zwischengespeichert) und Archivierungszwecke in einer Datenbank gespeichert und an Sie zurückgegeben. Die Erweiterung zeigt Ihnen dann die Dislikes an.\n\n<br>\n\n### **4. Was passiert, wenn die YouTube-API aufhört, die Anzahl der Dislikes zurückzugeben?**\n\nDas Backend wird auf eine Kombination aus archivierten Dislike-Statistiken, Schätzungen, die aus den Daten der Erweiterungsnutzer extrapoliert werden, und Schätzungen basierend auf Ansichts-/Like-Verhältnissen für Videos, deren Dislikes nicht archiviert wurden, und veralteten Dislike-Archiven umstellen.\n\n<br>\n\n### **5. Wie wird die Anzahl der Dislikes berechnet?**\n\nRYD verwendet die Stimmen seiner Benutzer, um die Anzahl der Dislikes zu extrapolieren.\n\n- Wenn das Video nach dem Abschalten der API hochgeladen wurde:\n\n  $$ \\textup{RYD Dislike Count} = \\left( \\frac{\\textup{RYD Users Dislike Count}}{\\textup{RYD Users Like Count}} \\right) \\times \\textup{Public Like Count} $$\n\n- Wenn die RYD-Datenbank auf irgendeine Weise die tatsächliche Anzahl von Likes und Dislikes enthielt (vom Ersteller bereitgestellt oder aus dem Archiv), wird die Anzahl der Dislikes basierend auf beiden - den Stimmen der Benutzer und dem archivierten Wert - berechnet. Der archivierte Wert wird mit zunehmendem Alter weniger Einfluss auf die endgültige Zählung haben.\n\n<br>\n\n---\n\nDies in Videoform\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Ich habe Sicherheits-/Privatsphärebedenken\n\nSiehe [diese Seite](SECURITY-FAQde.md) für weitere Informationen.\n"
  },
  {
    "path": "Docs/FAQfr.md",
    "content": "Lisez ceci dans d'autres langues : [English](FAQ.md), [русский](FAQru.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n# Foire Aux Questions\n\n## Avant de poser une question sur GitHub ou Discord, veuillez vous référer à ceci.\n\n### **1. Où cette extension obtient-elle les données ?**\n\nUne combinaison d'API de Google et de données scrapées.\n\nNous sauvegardons toutes les données disponibles dans notre base de données pour qu'elles soient disponibles après que Google ait supprimé le compteur de dislikes dans son API.\n\n### **2. Le nombre de dislikes sur les vidéos n'est pas mis à jour**\n\nActuellement, les dislike sont mis en cache et ne sont pas mis à jour très fréquemment. Une fois tous les 2-3 jours, pas plus souvent.\n\nOui, ce n'est pas idéal, mais c'est ce que c'est. Nous travaillons à améliorer la fréquence des mises à jour.\n\n### **3. Comment cela fonctionne-t-il ?**\n\nL'extension collecte l'ID de la vidéo que vous regardez, récupère les dislikes (et d'autres champs comme les vues, les likes etc.) en utilisant notre API, si c'est la première fois que la vidéo a été récupérée par notre API, elle utilisera l'API YouTube pour obtenir les données, puis stocke les données dans une base de données pour la mise en cache (mise en cache pendant environ 2-3 jours) et à des fins d'archivage et vous les renvoie. L'extension vous affiche ensuite les dislikes.\n\n### **4. Que se passera-t-il lorsque l'API YouTube ne renverra plus le nombre de dislikes ?**\n\nLe backend utilisera une combinaison de statistiques du nombre de dislikes archivées, d'estimations extrapolées à partir des données d'extension des utilisateurs et d'estimations basées sur les ratios vues/likes pour les vidéos dont les dislikes n'ont pas été archivées et pour les archives dont le nombre de dislikes est obsolète.\n\n## Je suis préoccupé par la sécurité / la confidentialité\n\nVoir [cette page](SECURITY-FAQfr.md) pour plus d'informations.\n"
  },
  {
    "path": "Docs/FAQid.md",
    "content": "Baca ini dibahasa lain: [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [Português do Brasil](FAQpt_BRmd)\n\n\n# Pertayaan yang Sering Ditanyakan\n\n## Sebelum bertanya di GitHub atau Discord, tolong lihat halaman ini.\n\n<br>\n\n### **1. Darimana extension ini mendapatkan datanya?**\n\nKombinasi dari API Google dan scraping data.\n\nKami telah menyimpan semua data yang ada kedalam DB agar datanya tetap ada meskipun setelah Google menghapus jumlah dislike dari API mereka.\n\n<br>\n\n### **2. Jumlah dislike video tidak update**\n\n\nSekarang jumlah dislike video dicache, dan tidak update selalu.\nSekali setiap 2-3 hari, tidak lebih dari itu.\n\nBenar, itu tidak ideal, tapi inilah yang bisa kami lakukan dan kami sedang melakukan improvisasi agar dapat melakukan update sesering mungkin.\n\n<br>\n\n### **3. Bagaimana cara kerjanya?**\n\nExtension mengumpulkan id dari video yang sedang ditonton, mengambil data dislike (dan lainnya seperti jumlah views, likes, dll) menggunakan API kami, jika video ini pertama kali diambil datanya menggunakan API kami, API yang digunakan akan diubah ke API Youtube untuk mengambil datanya dan datanya akan disimpan kedalam database kami untuk dilakukan cache (untuk 2-3 hari) yang bertujuan untuk pengarsipan data dan datanya dikembalikan ke kamu. Lalu extension akan menampilkan jumlah dislike ke kamu.\n\n<br>\n\n### **4. Apa yang akan terjadi setelah API Youtube menghapus jumlah dislike?**\n\nBagian backend akan berganti dengan menggunakan kombinasi dari data arsip dislike, estimasi perkiraan dari pengguna, dan estimasi berdasarkan jumlah rasio view/like untuk video yang dislikenya tidak diarsip dan arsip dislike yang sudah tua.\n\n<br>\n\n### **5. Bagaimana cara menghitung jumlah dislike?**\n\nRYD mernggunakan sistem voting dari pengguna untuk memperkirakan jumlah dislike.\n\n- Jika video diupload setelah API dislike dihapus:\n\n  $$ \\textup{Jumlah RYD Dislike} = \\left( \\frac{\\textup{Jumlah RYD Dislike Pengguna}}{\\textup{Jumlah RYD Like Pengguna}} \\right) \\times \\textup{Jumlah Public Like Publik} $$\n\n- Jika entah bagaimana Database RYD memiliki data jumlah asli dari like dan dislike (diberikan dari pengupload atau arsip), jumlah dislike akan dihitung berdasarkan jumlah voting dari pengguna dan arsip. Semakin tua data arsip maka semakin sedikit pengaruhnya ke hasil akhir perhitungan dislike.\n\n<br>\n\n---\n\nBerikut adalah form video\n\n[![Mengenai IReturn YouTube Dislike](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Saya memiliki masalah keamanan/privasi\n\nLihat [halaman ini](SECURITY-FAQid.md) untuk informasi lebih lanjut.\n"
  },
  {
    "path": "Docs/FAQnl.md",
    "content": "Lees dit in andere talen: [Engels](FAQ.md), [русский](FAQru.md), [Français](FAQfr.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n# Veel Gestelde Vragen\n\n## Raadpleeg deze voordat u een vraag stelt op GitHub of Discord.\n\n<br>\n\n### **1. Waar haalt deze extensie de gegevens vandaan?**\n\nEen combinatie van Google API's en geschraapte gegevens.\n\nWe slaan alle beschikbare gegevens op in onze database, zodat deze beschikbaar is nadat Google het aantal dislikes in hun API heeft stopgezet.\n\n<br>\n\n### **2. Het aantal video-dislikes wordt niet bijgewerkt**\n\nOp dit moment worden video's die niet leuk zijn in het cachegeheugen opgeslagen en worden niet erg vaak bijgewerkt. Eens in de 2-3 dagen, niet vaker.\n\nJa, het is niet ideaal, maar het is wat het is. Werken aan het verbeteren van hoe vaak we ze kunnen bijwerken.\n\n<br>\n\n### **3. Hoe werkt dit?**\n\nDe extensie verzamelt de video-ID van de video die je aan het bekijken bent, haalt de dislike (en andere velden zoals views, likes etc) op met behulp van onze API. Als dit de eerste keer is dat de video wordt opgehaald door onze API, zal deze de YouTube API gebruiken om de gegevens op te halen, slaat de gegevens vervolgens op in een database voor caching (cache voor ongeveer 2-3 dagen) en archiveringsdoeleinden en stuurt ze naar u terug. De extensie geeft vervolgens de antipathieën aan u weer.\n\n<br>\n\n### **4. Wat gebeurt er nadat de YouTube API stopt met het teruggeven van het aantal dislikes?**\n\nDe backend zal overschakelen naar het gebruik van een combinatie van afkeer statistieken en afkeer archieven, schattingen geëxtrapoleerd uit gebruikersgegevens van extensies en schattingen op basis van weergave/vind-ik-leuk-verhoudingen voor video's waarvan de afkeuren niet zijn gearchiveerd en voor verouderde afkeer-archieven.\n\n<br>\n\n### **5. Hoe wordt het aantal dislikes berekend?**\n\nRYD gebruikt de stemmen van zijn gebruikers om het aantal dislikes te extrapoleren.\n\n- Als de video is geüpload nadat de API was afgesloten:\n\n  $$ \\textup{RYD Dislike Count} = \\left( \\frac{\\textup{RYD Users Like Count}}{\\textup{RYD Users Dislike Count}} \\right) \\times \\textup{Public Like Count} $$\n\n- Als de RYD-database op de een of andere manier het werkelijke aantal likes en dislikes had (geleverd door de uploader of uit het archief), wordt het aantal dislikes berekend op basis van zowel de stemmen van de gebruikers als de gearchiveerde waarde. De gearchiveerde waarde heeft minder invloed op de uiteindelijke telling naarmate deze ouder wordt.\n\n<br>\n\n---\n\nDit in videovorm\n\n[![IReturn YouTube Dislike Uitgelegd (Engels)](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Ik heb zorgen over de beveiliging/privacy\n\nBekijk [deze pagina](SECURITY-FAQnl.md) voor meer informatie.\n"
  },
  {
    "path": "Docs/FAQpl.md",
    "content": "Read this in other languages: [English](FAQ.md), [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Deutsch](FAQde.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n# Często zadawane pytania\n\n## Przeczytaj poniższe przed zadawaniem pytań na GitHubie lub Discordzie.\n\n<br>\n\n### **1.Skąd rozszerzenie otrzymuje swoje dane?**\n\nKombinacja API Google i danych scrape-owanych.\n\nZapisujemy wszystkie dostępne dane do naszej bazy danych, żeby były dostępne po tym jak Google wyłączy liczniki łapek w dół w swoim API.\n\n<br>\n\n### **2. Licznik łapek w dół się nie aktualizuje**\n\nNa chwile obecną łapki w dół są buforowane i nie są bardzo często aktualizowane. Raz na 2-3 dni, nie częściej.\n\nNo nie jest to idealne, ale tak już jest. Pracujemy nad tym jak częściej możemy to aktualizować.\n\n<br>\n\n### **3. Jak to działa?**\n\nRozszerzenie zbiera ID filmu, którego oglądasz, pobiera ilość łapek w dół (i inne pola, takie jak wyświetlenia, łapki w górę itd.) za pomocą naszego API. Jeżeli film jest pierwszy raz pobrany przez nasze API, zostanie użyte YouTube API do pobrania danych, a potem przechowania w bazie danych do buforu (przez jakieś 2-3 dni) i archiwizacji, po czym zwracane jest Tobie. Rozszerzenie potem wyświetla ilość łapek w dół.\n\n<br>\n\n### **4. Co się wydarzy gdy YouTube API przestanie zwracać liczbę łapek w dół?**\n\nBackend przełączy się na używanie kombinacji zarchiwizowanych statystyk łapek w dół, szacunków ekstrapolowanych z danych użytkowników rozszerzenia i szacowań opartych na stosunkach wyświetleń/łapek w górę dla filmów, których ilość łapek w dół nie została zarchiwizowana i dla przestarzałych archiwów.\n\n<br>\n\n### **5. Jak wyliczana jest liczba łapek w dół?**\n\nRYD używa głosów użytkowników, aby ekstrapolować liczbę łapek w dół.\n\n- Jeżeli film został wrzucony przed wyłączeniem API:\n\n  $$ \\textup{Liczba łapek w dół RYD} = \\left( \\frac{\\textup{Liczba łapek w dół użytkowników RYD}}{\\textup{Liczba łapek w górę użytkowników RYD}} \\right) \\times \\textup{Publiczna liczba łapek w górę} $$\n\n- Jeśli baza danych RYD jakimś cudem miałaby prawidłową liczbę łapek w górę i dół (zapewnione przez twórce lub z archiwum), liczba łapek w dół będzie obliczana w oparciu jednocześnie głosów użytkowników i wartości zarchiwizowanych. Zarchiwizowana wartość będzie miała mniejszy wpływ na ostateczną liczbę z biegiem czasu.\n\n<br>\n\n---\n\nTo samo w formie filmu.\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Mam zastrzeżenia co do bezpieczeństwa / prywatności\n\nPrzejdź [tutaj](SECURITY-FAQ.md) aby uzyskać więcej informacji.\n"
  },
  {
    "path": "Docs/FAQpt_BR.md",
    "content": "Leia isso em outros Idiomas: [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md)\n\n# Frequently Asked Questions\n\n## Before asking a question on GitHub or Discord, please refer to this.\n\n<br>\n\n### **1. Where does this extension get the data?**\n\nA Combination of Google APIs and scraped data.\n\nWe save all available data to our DB for it to be available after Google shuts down dislike counts in their API.\n\n<br>\n\n### **2. Video dislike count doesn't update**\n\nRight now video dislikes are cached, and aren't updated very frequenly. Once every 2-3 days, not more often.\n\nYeah, it's not ideal, but it is what it is. Working on improving how often we can update them.\n\n<br>\n\n### **3. How does this work?**\n\nThe extension collects the video id of the video you are watching, fetches the dislike (and other fields like views, likes etc) using our API, if this is the first time the video was fetched by our API, it will use the YouTube API to get the data, then stores the data in a database for caching (cached for around 2-3 days) and archiving purposes and returns it to you. The extension then displays the dislikes to you.\n\n<br>\n\n### **4. What will happen after the YouTube API stops returning the dislike count?**\n\nThe backend will switch to using a combination of archived dislike stats, estimates extrapolated from extension user data and estimates based on view/like ratios for videos whose dislikes weren't archived and for outdated dislike archives.\n\n<br>\n\n### **5. How is the dislike count calculated?**\n\nRYD uses the votes from its users to extrapolate the dislike count.\n\n- If the video was uploaded after the API was shut down:\n\n  $$ \\textup{RYD Dislike Count} = \\left( \\frac{\\textup{RYD Users Dislike Count}}{\\textup{RYD Users Like Count}} \\right) \\times \\textup{Public Like Count} $$\n\n- If the RYD database somehow had the actual like and dislike count (provided by the uploader or from the archive), the dislike count will be calculated based on both - the users' votes and the archived value. The archived value will have less influence on the final count as it ages.\n\n<br>\n\n---\n\nEssa a This in video form\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## I have security / privacy concerns\n\nSee [this page](SECURITY-FAQpt_BR.md) for more info.\n"
  },
  {
    "path": "Docs/FAQru.md",
    "content": "Read this in other languages: [English](FAQ.md), [Nederlands](FAQnl.md), [Français](FAQfr.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n# Часто задаваемые вопросы\n\n## Прежде чем задать вопрос на GitHub или в Discord, пожалуйста, ознакомьтесь с этим.\n\n### **1. Откуда это расширение получает данные?**\n\nКомбинация API Google и старых данных.\n\nМы сохраняем все имеющиеся данные в нашей базе данных, чтобы они были доступны после того, как Google прекратит подсчёт отметок «Не нравится» в своём API.\n\n### **2. Количество не понравившихся видео не обновляется**\n\nВ настоящее время видео с отметками «Не нравится» кэшируются и обновляются не очень часто. Раз в 2-3 дня, не чаще.\n\nДа, это не идеально, но это то, что есть. Мы работаем над тем, чтобы улучшить частоту их обновления.\n\n### **3. Как это работает?**\n\nРасширение собирает идентификатор видео, которое вы смотрите, извлекает данные об отметках «Не нравится» (и другие поля, такие как просмотры, отметки «Нравится» и т.д.) с помощью нашего API, если видео было извлечено нашим API впервые, оно использует YouTube API для получения данных, затем сохраняет данные в базе данных для кэширования (кэшируются около 2-3 дней) и архивирования и возвращает их вам. После этого расширение отображает отметки «Не нравится» вам.\n\n### **4. Что произойдёт после того, как API YouTube перестанет возвращать данные о количестве отметок «Не нравится»?**\n\nВнутренняя часть нашего сервера переключится на использование комбинации архивных статистик отметок «Не нравится», оценок, экстраполированных из данных о пользователях расширения, и оценок, основанных на соотношении просмотров и отметок «Нравится» для видео, чьи отметки «Не нравится» не были заархивированы, и для устаревших архивов с отметками «Не нравится».\n\n## Я беспокоюсь о безопасности / конфиденциальности\n\nБолее подробную информацию смотрите на [этой странице](SECURITY-FAQ.md).\n"
  },
  {
    "path": "Docs/FAQtr.md",
    "content": "Bunu diğer dillerde okuyun: [English](FAQ.md), [русский](FAQru.md), [Nederlands](FAQnl.md), [Français](FAQfr.md), [українська](FAQuk.md), [Polski](FAQpl.md), [Deutsch](FAQde.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n# Sıkça Sorulan Sorular\n\n## GitHub'da veya Discord'da bir soru sormadan önce, lütfen buraya göz atın.\n\n<br>\n\n### **1. Bu uzantı verileri nereden alıyor?**\n\nGoogle API'lerinin ve kazınmış verilerin bir kombinasyonu.\n\nGoogle, API'lerinde dislike sayılarını kapattıktan sonra kullanılabilir olması için mevcut tüm verileri DB'mize kaydederiz.\n\n<br>\n\n### **2. Video'nun dislike sayısı güncellenmiyor**\n\nŞu anda video dislike'ları önbelleğe alınır ve çok sık güncellenmez. Her 2-3 günde bir, daha sık değil.\n\nEvet, ideal değil, ama olan bu. Bunları nasıl daha sık güncelleyebileceğimizi öğrenmeye çalışıyoruz.\n\n<br>\n\n### **3. Bu uzantı nasıl çalışıyor?**\n\nUzantı, izlediğiniz videonun video kimliğini alır, dislike'larını (ve görüntülemeleri, like'ları vb. diğer alanları) API'mizi kullanarak getirir; video, API'miz tarafından ilk kez getiriliyorsa YouTube API'sini kullanır. Verileri almak için, verileri önbelleğe alma (yaklaşık 2-3 gün önbelleğe alınır) ve arşivleme amacıyla bir veritabanında saklanır ve size geri döndürülür. Uzantı daha sonra size dislike'ları gösterir.\n\n<br>\n\n### **4. YouTube API'si, dislike sayısını döndürmeyi durdurduğunda ne olacak?**\n\nBackend, arşivlenmiş dislike istatistikleri, uzantı kullanıcı verilerinden tahmin edilen tahminler ve like'ları arşivlenmemiş videolar ve eski dislike arşivleri için izlenme/like oranlarına dayalı tahminlerin bir kombinasyonunu kullanmaya geçecektir.\n\n<br>\n\n### **5. Dislike sayısı nasıl hesaplanıyor?**\n\nYDS, dislike sayısını tahmin etmek için kullanıcılarının oylarını kullanır.\n\n- Video, API kapatıldıktan sonra yüklendiyse:\n\n  $$ \\textup{YDS'nin Dislike Sayısı} = \\left( \\frac{\\textup{YDS Kullanıcılarının Like Sayısı}}{\\textup{YDS Kullanıcılarının Dislike Sayısı}} \\right) \\times \\textup{Halka Açık Like Sayısı} $$\n\n- YDS veritabanı bir şekilde gerçek like ve dislike sayısına sahipse (yükleyici tarafından veya arşivden sağlanır), dislike sayısı hem kullanıcıların oyları hem de arşivlenen değer temelinde hesaplanacaktır. Arşivlenen değer, eskidikçe son sayım üzerinde daha az etkiye sahip olacaktır.\n\n<br>\n\n---\n\nBu video şeklinde\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Gizlilik / güvenlik hakkında endişelerim var\n\nDaha fazla bilgi için [bu sayfa](SECURITY-FAQtr.md)ya göz atın.\n"
  },
  {
    "path": "Docs/FAQuk.md",
    "content": "Read this in other languages: [English](FAQ.md), [русский](FAQru.md), [Français](FAQfr.md), [Türkçe](FAQtr.md), [Deutsch](FAQde.md), [العربية](FAQar.md), [Bahasa Indonesia](FAQid.md), [中文](FAqcn.md), [български](FAQbg.md), [Tiếng Việt](FAQvi.md)\n\n# Часті питання\n\n## Перш ніж задавати питання на GitHub або у Discord, будь ласка, ознайомтеся з цим.\n\n<br>\n\n### **1. Звідки це розширення отримує дані?**\n\nКомбінація Google API та старих даних.\n\nМи зберігаємо всі доступні дані в нашій базі, аби вони були доступні після того, як Google вимкне лічильник відміток «Не подобається» у своєму API.\n\n<br>\n\n### **2. Лічильник «Не подобається» не оновлюється**\n\nНаразі відео з відмітками «Не подобається» кешуються і оновлюються не надто часто. Не частіше, ніж раз в 2-3 дні.\n\nТак, це не ідеально, але маємо те, що маємо. Ми працюємо над тим, щоб збільшити частоту їх оновлення.\n\n<br>\n\n### **3. Як це працює?**\n\nРозширення отримує ID відео, яке ви переглядаєте, та дізнається кількість відміток «Не подобається» (та інші дані: перегляди, відмітки «Подобається» тощо) за допомогою нашого API, якщо відео було витягнуте нашим API вперше, воно використовує YouTube API для отримання даних, потім зберігає дані в базі даних для кешування (кешуються близько 2-3 днів) та архівування й повертає їх вам. Після цього розширення відображає відмітки «Не подобається» вам.\n\n<br>\n\n### **4. Що станеться після того, як YouTube API перестане повертати кількість відміток «Не подобається»?**\n\nСервер перейде на використання комбінації заархівованих статистичних даних відміток «Не подобається» екстрапольованих із даними .\n\n<br>\n\n### **5. Як розраховується кількість відміток «Не подобається»?**\n\nRYD використовує відмітки своїх користувачів, щоб екстраполювати кількість відміток «Не подобається».\n\n- Якщо відео було завантажено після вимкнення API:\n\n  $$ \\textup{К-ть відміток «Не подобається» у RYD} = \\left( \\frac{\\textup{К-ть відміток «Подобається» серед користувачів RYD}}{\\textup{К-ть відміток «Не подобається» серед користувачів RYD}} \\right) \\times \\textup{Публічна к-ть відміток «Подобається»} $$\n\n- Якщо база даних RYD якимось чином мала фактичну кількість відміток «Подобається» та «Не подобається» (надану завантажувачем або з архіву), кількість відміток «Не подобається» буде розраховано на основі відміток користувачів і архівного значення. Архівне значення матиме менший вплив на остаточний підрахунок у міру актуальності.\n\n<br>\n\n---\n\nЦе все, але у відео форматі\n\n[![IReturn YouTube Dislike Explained](https://yt-embed.herokuapp.com/embed?v=GSmmtv-0yYQ)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Я турбуюся про безпеку/конфіденційність\n\nДетальніше про це дивіться [тут](SECURITY-FAQuk.md).\n"
  },
  {
    "path": "Docs/FAQvi.md",
    "content": "Đọc bằng các ngôn ngữ khác: [English](FAQ.md), [русский](FAQru.md), [Français](FAQfr.md), [Nederlands](FAQnl.md), [Türkçe](FAQtr.md), [українська](FAQuk.md), [Polski](FAQpl.md)\n\n\n# Câu Hỏi Thường Gặp <!-- # Frequently Asked Questions -->\n\n## Trước khi đưa ra bất kì câu hỏi nào trên GitHub hay trên Discord, vui lòng đọc hết trang này. <!-- ## Before asking a question on GitHub or Discord, please refer to this. -->\n\n<br>\n\n### **1. Tiện ích mở rộng này lấy dữ liệu từ đâu?** <!-- ### Where does this extension get the data? -->\n\nKết hợp dữ liệu lấy từ API của Google và dữ liệu thu thập được.\n\nChúng tôi lưu giữ tất cả dữ liệu mà chúng tôi có được vào cơ sở dữ liệu của chúng tôi, kể từ khi Google loại bỏ số lượt đánh giá \"không thích\" khỏi API của họ.\n\n<br>\n\n### **2. Số lượt \"không thích\" không được cập nhật** <!-- ### Video dislike count doesn't update -->\n\nHiện tại, các lượt đánh giá \"không thích\" sẽ được lưu lại và số lượt đánh giá tổng được hiển thị không được cập nhật thường xuyên. Thông thường, cứ mỗi 2 đến 3 ngày, chứ không ngắn hơn.\n\nĐồng ý rằng điều này không đáng mong chờ, nhưng hiện tại thì tiện ích hoạt động như vậy. Chúng tôi đang cố gắng cải thiện tần suất nhật số lượt đánh giá.\n\n<br>\n\n### **3. Cách thức hoạt động của tiện ích mở rộng này?** <!-- ### How does this work? -->\n\nTiện ích này thu thập ID của vi-đê-ô mà bạn đang xem, truy vấn đánh giá của vi-đê-ô (bao gồm số lượt \"không thích\", số lượt \"thích\", số lượt xem, v.v.) bằng API của chúng tôi. Nếu đây là đầu tiên diễn ra truy vấn tới API của chúng tôi đối với vi-đê-ô này, tiện ích sẽ sử dụng API của YouTube sẽ truy vấn các thông tin cần thiết, sau đó lưu trữ các thông tin này vào cơ sở dữ liệu cho mục đích truy vấn nhanh (trong vòng 2 đến 3 ngày) và cho mục đích lưu trữ và sẽ trả kết quả truy vấn về cho bạn. Lúc này, tiện ích sẽ hiện thị số lượt đánh giá \"không thích\" cho bạn xem.\n\n<br>\n\n### **4. Việc gì sẽ diễn ra sau khi API của YouTube ngừng cung cấp số lượt \"không thích\"?** <!-- ### What will happen after the YouTube API stops returning the dislike count? -->\n\nĐầu sau của tiện ích sẽ chuyển sang sử dụng kết hợp dữ liệu đã được lưu trữ về số lượt đánh giá \"không thích\", những ước tính ngoại suy từ dữ liệu người dùng của tiện ích và những ước tính dựa trên tỉ lệ lượt xem/lượt \"thích\" đối với những vi-đê-ô chưa lưu được số lượt \"không thích\" và đối với các kho lưu trữ số lượt \"không thích\" lỗi thời. \n\n<br>\n\n### **5. Số lượt đánh giá \"không thích\" được tính như thế nào?** <!-- ### How is the dislike count calculated? -->\n\nRYD sử dụng đánh giá từ người dùng để ngoại suy số lượt đánh giá \"không thích\".\n\n- Nếu vi-đê-ô được đăng tải sau khi API của YouTube loại bỏ trường `dislike`:\n\n  $$ \\textup{Số lượt \"Không thích\" của RYD} = \\left( \\frac{\\textup{Số lượt \"Không thích\" từ Người dùng của RYD}}{\\textup{Số lượt \"Thích\" từ Người dùng của RYD}} \\right) \\times \\textup{Số lượt \"Thích\" từ API của YouTube} $$\n\n- Nếu cơ sở dữ liệu của RYD có được số lượt đánh giá \"thích\" và \"không thích\" thực (được cung cấp bởi chủ vi-đê-ô hay từ kho lưu trữ), số lượt đánh giá \"không thích\" sẽ được tính dựa trên đồng thời (1) đánh giá từ người dùng của tiện ích và (2) số liệu được lưu trữ. Số liệu được lưu trữ sẽ càng ngày càng kém quan trọng trong việc tính toán số lượt đánh giá.\n\n<br>\n\n---\n\nVi-đê-ô thông tin về Return YouTube Dislike:\n\n[Giải thích về Return YouTube Dislike (Tiếng Anh)](https://www.youtube.com/watch?v=GSmmtv-0yYQ)\n\n---\n\n<br>\n\n## Tôi có thắc mắc về tính bảo mật / quyền riêng tư <!-- ## I have security / privacy concerns -->\n\nVui lòng tham khảo [trang này](SECURITY-FAQvi.md) để biết thêm thông tin.\n"
  },
  {
    "path": "Docs/Guide__Installing.md",
    "content": "**Contents**\n\n- [Downloading](#downloading)\n  - [Desktop (all OS supported by these browsers)](#desktop-all-os-supported-by-these-browsers)\n    - [Chromium Based Browsers](#chromium-based-browsers)\n    - [Firefox Based Browsers](#firefox-based-browsers)\n  - [Mobile](#mobile)\n    - [Android](#android)\n    - [iOS](#ios)\n    - [Userscript](#userscript)\n- [Installation](#installation)\n  - [Desktop](#desktop)\n    - [**Chromium based browsers**](#chromium-based-browsers-1)\n      - [From Chrome Webstore](#from-chrome-webstore)\n      - [From crx/zip file](#from-crxzip-file)\n      - [From unzipped folder](#from-unzipped-folder)\n    - [**Firefox Based Browsers**](#firefox-based-browsers-1)\n      - [From addon store](#from-addon-store)\n      - [From xpi/jar/zip file](#from-xpijarzip-file)\n  - [Mobile](#mobile-1)\n    - [Android](#android-1)\n      - [App from Play Store](#app-from-play-store)\n      - [On Firefox](#on-firefox)\n    - [iOS](#ios-1)\n- [Using](#using)\n- [Updating](#updating)\n  - [Extension / Addon](#extension--addon)\n- [Miscellaneous](#miscellaneous)\n  - [Using YouTube website as an app with an extension](#using-youtube-website-as-an-app-with-an-extension)\n    - [Desktop](#desktop-1)\n      - [Chromium Based Browsers](#chromium-based-browsers-2)\n      - [Firefox Based Browsers](#firefox-based-browsers-2)\n    - [Mobile](#mobile-2)\n      - [Firefox Based Browsers](#firefox-based-browsers-3)\n      - [Chromium Based Browsers](#chromium-based-browsers-3)\n\n<br>\n\n<br>\n\n## Downloading\n\n### Desktop (all OS supported by these browsers)\n\n<br>\n\n#### [Chromium Based Browsers][4]\n\nThis extension has been tested to work on these browsers.\n\n- [Google Chrome][1]\n- [Microsoft Edge][1]\n- [Brave][1]\n- [Opera][1]\n\nIt should be able to work on [all Chromium-based browsers (list here)][4]. But that isn't guaranteed.\n\n<br>\n\n#### [Firefox Based Browsers][5]\n\n- [Firefox][2]\n- This addon should be able to run on most of the [Firefox-based browsers][5]. But isn't guaranteed.\n\n<br>\n\n### Mobile\n\n#### Android\n\n1. **F-Droid Store**\n\n- [Show Youtube Dislikes](https://f-droid.org/en/packages/com.jesperh.showyoutubedislikes/)\n\n  [Download from here (Click here)](https://f-droid.org/en/packages/com.jesperh.showyoutubedislikes/)\n\n  The source code is available at [github.com/jesperbakhandskemager/view-youtube-dislike](https://github.com/jesperbakhandskemager/view-youtube-dislike)\n\n  You can download the apk file from\n  [https://github.com/jesperbakhandskemager/view-youtube-dislike/releases/](https://github.com/jesperbakhandskemager/view-youtube-dislike/releases/)\n\n  **Note: This app is NOT made by the original author of the extension**.\n\n2. [**Firefox Nightly**][2]\n\n- This addon should be able to run on most of the [Firefox-based browsers][5]. But isn't guaranteed.\n\n3. [**Chromium Based browsers**][4]\n\nMost [**Chromium Based browsers**][4] **don't support extensions on Android or iOS**\n\nHowever, [Kiwi Browser](https://kiwibrowser.com/) does. You can refer to this video - [Google Chrome Extensions on Android with Kiwi Browser!](https://youtu.be/T6J0T_-oim4)\n\n<br>\n\n#### iOS\n\nNo Support on Firefox\n\nYou can have a look at these pages for more information (the reason why it's not available on Firefox):\n\n- [https://support.mozilla.org/en-US/kb/add-ons-firefox-ios]\n- [https://support.mozilla.org/en-US/questions/1101350]\n\nFor now, you can try this\n\n- [For Jailbroken iOS - **WE TAKE NO RESPONSIBILITY. USE AT YOUR OWN RISK**](https://chariz.com/get/return-youtube-dislike)\n\n  This is an iOS port [**developed by a separate team**](https://github.com/PoomSmart/Return-YouTube-Dislikes) not related to the owner of github.com/Anarios/return-youtube-dislike\n\n#### Userscript\n\n[Download from here](https://returnyoutubedislike.com/install)\n\n<br>\n\n<br>\n\n## Installation\n\n### Desktop\n\n<br>\n\n#### [**Chromium based browsers**][4]\n\n<br>\n\n##### From Chrome Webstore\n\n1. [Go to website (click here)][1]\n2. Click install\n3. Wait for the extension to download and\n4. 🙂 Installed !!\n\n<br>\n\n##### From crx/zip file\n\n1. Download the crx/zip file.\n2. Type [`chrome://extensions`][6] in address bar\n3. Search for the \"Developer Mode\" switch and turn it on.\n4. Open the folder and the browser side by side.\n5. Drag and drop the crx/zip file in [chrome://extensions][6] tab\n6. Click on \"Add extension\"\n7. Installation Completed 🎉\n8. Remember to **turn off the \"developer mode\"** if not needed\n\n<br>\n\n##### From unzipped folder\n\n1. Download the crx/zip file.\n2. Go to [`chrome://extensions`][6]\n3. Search for the \"Developer Mode\" switch and turn it on.\n4. Click on \"Load Unpacked\"\n5. Navigate to the folder & select it\n6. The extension should be installed 🎉\n7. Remember to **turn off the \"developer mode\"** if not needed\n\n<br>\n\n#### [**Firefox Based Browsers**][5]\n\n##### From addon store\n\n1. Go to the addon store and find the extension or [click here][2].\n2. Click on `Add to Firefox`\n3. Done 🎉 The extension should be installed.\n\n<br>\n\n##### From xpi/jar/zip file\n\n1. Type `about:addons` in the address bar and press \"Enter\". Alternatively, You can use\n   `Ctrl` + `Shift` + `A` on Windows and\n   `Cmd` + `Shift` + `A` on Mac and\n2. Click on the Setting Gear icon `⚙`.\n3. Click on `Install Add-on From File...`\n4. Locate and click on the .xpi/.jar/.zip file.\n5. Select and click to open it.\n6. Done 🎉 The extension should be installed.\n\n<br>\n\n### Mobile\n\n#### Android\n\n##### App from Play Store\n\n- Although app(s) which use this API exist, they aren't official.\n\n##### On Firefox\n\n1. Install Firefox Nightly. Refer to this blog post for the procedure: [Expanded extension support in Firefox for Android Nightly](https://blog.mozilla.org/addons/2020/09/29/expanded-extension-support-in-firefox-for-android-nightly/)\n2. Install the extension in it from [addon store][2]\n3. If you want - install the site as an app. For that procedure [go here](#firefox-based-browsers-3).\n\n#### iOS\n\n- [For iOS click here](#ios)\n- Only available on Firefox-based browsers because Chromium-based browsers for the Android platform don't support installing extensions.\n\n<br>\n\n<br>\n\n## Using\n\nNo more steps are required, it should start working right away.\n\nIf required you can restart your browser.\n\n**🎉 🎊 Congratulations** 🥳 🎊\n\n<br>\n\n<br>\n\n## Updating\n\n### Extension / Addon\n\nOn [Chromium based browsers][4]\n\n- [from webstore][1]\n- sideloading from [crx/zip file](#from-crxzip-file) or [unzipped folder](#from-unzipped-folder)\n\nOn [Firefox based browsers][5]\n\n- [from addon store][2]\n- [sideloading from xpi/jar/zip file](#from-xpijarzip-file)\n\nOn [Firefox based browsers][5] for mobile\n\n- [from addon store][2]\n\n<br>\n\n<br>\n\n## Miscellaneous\n\n### Using YouTube website as an app with an extension\n\n#### Desktop\n\n##### Chromium Based Browsers\n\n0. Go to YouTube\n1. Tap on menu button (`☰` / `⋯` / `⋮`)\n2. Click on \"Install YouTube\"\n3. 🎉Done!\n\n<br>\n\n##### Firefox Based Browsers\n\n**⚠ Doesn't work on newer versions.**\n\n1. Type [`about:config`](about:config) in address bar & press `Enter` key.\n2. When warned `Proceed with Caution` click on `Accept the Risk and Continue`\n3. Locate and click on the search bar on the page.\n4. Type `browser.ssb.enable`.\n5. click on `⇋` and make sure the state is set to `true`.\n6. Restart the browser and Open YouTube.\n7. Click on `⋮` located in the upper right corner\n8. Click `Install this website as an app` or `Install this website as an app` which depends on the version you are using.\n9. Done! 🎉 You should be able to see the site's app in the start menu.\n\nYou can also refer here (for pictures):\n\n[How to Enable the Site-Specific Browser (SSB) in Firefox - Make Tech Easier](https://www.maketecheasier.com/enable-site-specific-browser-firefox)\n\n<br>\n\n#### Mobile\n\n##### Firefox Based Browsers\n\n0. Install the extension\n1. Go to YouTube\n2. Tap on the three dots `⋮` located at the bottom right corner of the screen.\n3. Turn on \"Desktop Site\".\n4. Refresh the page.\n5. Wait for it to completely load.\n6. Tap again on the three dots `⋮` located at the bottom right corner of the screen.\n7. You will see an option as `Install`. Tap on it.\n8. Tap - `Add` when asked `Add to Home screen?`\n9. Enjoy the YT site as a Firefox nightly app !!\n\n<br>\n\n##### [Chromium Based Browsers][4]\n\nBut still - here are the steps:\n\n1. Go to YouTube\n2. Tap on the three dots `⋮` located at the bottom right corner of the screen.\n3. Turn on \"Desktop Site\".\n4. Refresh the page and wait for it to completely load.\n5. Tap again on the three dots `⋮` located at the bottom right corner of the screen.\n6. You will see an option as `Install`. Tap on it.\n7. When asked `Install App` tap on `Yes`\n8. Tap - `Add` when asked `Add to Home screen?`\n9. Enjoy the YT site as an app !!\n\n<br>\n\n<br>\n\n<!-- links -->\n\n[1]: https://chrome.google.com/webstore/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi\n[2]: https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/\n[3]: https://github.com/Anarios/return-youtube-dislike/raw/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js\n[4]: https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium\n[5]: https://en.wikipedia.org/wiki/Category:Web_browsers_based_on_Firefox\n[6]: chrome://extensions\n"
  },
  {
    "path": "Docs/Guide__Installingvi.md",
    "content": "**Mục lục**\n\n- [Downloading](#downloading)\n  - [Desktop (all OS supported by these browsers)](#desktop-all-os-supported-by-these-browsers)\n    - [Chromium Based Browsers](#chromium-based-browsers)\n    - [Firefox Based Browsers](#firefox-based-browsers)\n  - [Mobile](#mobile)\n    - [Android](#android)\n    - [iOS](#ios)\n    - [Userscript](#userscript)\n- [Installation](#installation)\n  - [Desktop](#desktop)\n    - [**Chromium based browsers**](#chromium-based-browsers-1)\n      - [From Chrome Webstore](#from-chrome-webstore)\n      - [From crx/zip file](#from-crxzip-file)\n      - [From unzipped folder](#from-unzipped-folder)\n    - [**Firefox Based Browsers**](#firefox-based-browsers-1)\n      - [From addon store](#from-addon-store)\n      - [From xpi/jar/zip file](#from-xpijarzip-file)\n  - [Mobile](#mobile-1)\n    - [Android](#android-1)\n      - [App from Play Store](#app-from-play-store)\n      - [On Firefox](#on-firefox)\n    - [iOS](#ios-1)\n- [Using](#using)\n- [Updating](#updating)\n  - [Extension / Addon](#extension--addon)\n- [Miscellaneous](#miscellaneous)\n  - [Using YouTube website as an app with an extension](#using-youtube-website-as-an-app-with-an-extension)\n    - [Desktop](#desktop-1)\n      - [Chromium Based Browsers](#chromium-based-browsers-2)\n      - [Firefox Based Browsers](#firefox-based-browsers-2)\n    - [Mobile](#mobile-2)\n      - [Firefox Based Browsers](#firefox-based-browsers-3)\n      - [Chromium Based Browsers](#chromium-based-browsers-3)\n\n<br>\n\n<br>\n\n## Downloading\n\n### Desktop (all OS supported by these browsers)\n\n<br>\n\n#### [Chromium Based Browsers][4]\n\nThis extension has been tested to work on these browsers.\n\n- [Google Chrome][1]\n- [Microsoft Edge][1]\n- [Brave][1]\n- [Opera][1]\n\nIt should be able to work on [all Chromium-based browsers (list here)][4]. But that isn't guaranteed.\n\n<br>\n\n#### [Firefox Based Browsers][5]\n\n- [Firefox][2]\n- This addon should be able to run on most of the [Firefox-based browsers][5]. But isn't guaranteed.\n\n<br>\n\n### Mobile\n\n#### Android\n\n1. **F-Droid Store**\n\n- [Show Youtube Dislikes](https://f-droid.org/en/packages/com.jesperh.showyoutubedislikes/)\n\n  [Download from here (Click here)](https://f-droid.org/en/packages/com.jesperh.showyoutubedislikes/)\n\n  The source code is available at [github.com/jesperbakhandskemager/view-youtube-dislike](https://github.com/jesperbakhandskemager/view-youtube-dislike)\n\n  You can download the apk file from\n  [https://github.com/jesperbakhandskemager/view-youtube-dislike/releases/](https://github.com/jesperbakhandskemager/view-youtube-dislike/releases/)\n\n  **Note: This app is NOT made by the original author of the extension**.\n\n2. [**Firefox Nightly**][2]\n\n- This addon should be able to run on most of the [Firefox-based browsers][5]. But isn't guaranteed.\n\n3. [**Chromium Based browsers**][4]\n\nMost [**Chromium Based browsers**][4] **don't support extensions on Android or iOS**\n\nHowever, [Kiwi Browser](https://kiwibrowser.com/) does. You can refer to this video - [Google Chrome Extensions on Android with Kiwi Browser!](https://youtu.be/T6J0T_-oim4)\n\n<br>\n\n#### iOS\n\nNo Support on Firefox\n\nYou can have a look at these pages for more information (the reason why it's not available on Firefox):\n\n- [https://support.mozilla.org/en-US/kb/add-ons-firefox-ios]\n- [https://support.mozilla.org/en-US/questions/1101350]\n\nFor now, you can try this\n\n- [For Jailbroken iOS - **WE TAKE NO RESPONSIBILITY. USE AT YOUR OWN RISK**](https://chariz.com/get/return-youtube-dislike)\n\n  This is an iOS port [**developed by a separate team**](https://github.com/PoomSmart/Return-YouTube-Dislikes) not related to the owner of github.com/Anarios/return-youtube-dislike\n\n#### Userscript\n\n[Download from here](https://returnyoutubedislike.com/install)\n\n<br>\n\n<br>\n\n## Installation\n\n### Desktop\n\n<br>\n\n#### [**Chromium based browsers**][4]\n\n<br>\n\n##### From Chrome Webstore\n\n1. [Go to website (click here)][1]\n2. Click install\n3. Wait for the extension to download and\n4. 🙂 Installed !!\n\n<br>\n\n##### From crx/zip file\n\n1. Download the crx/zip file.\n2. Type [`chrome://extensions`][6] in address bar\n3. Search for the \"Developer Mode\" switch and turn it on.\n4. Open the folder and the browser side by side.\n5. Drag and drop the crx/zip file in [chrome://extensions][6] tab\n6. Click on \"Add extension\"\n7. Installation Completed 🎉\n8. Remember to **turn off the \"developer mode\"** if not needed\n\n<br>\n\n##### From unzipped folder\n\n1. Download the crx/zip file.\n2. Go to [`chrome://extensions`][6]\n3. Search for the \"Developer Mode\" switch and turn it on.\n4. Click on \"Load Unpacked\"\n5. Navigate to the folder & select it\n6. The extension should be installed 🎉\n7. Remember to **turn off the \"developer mode\"** if not needed\n\n<br>\n\n#### [**Firefox Based Browsers**][5]\n\n##### From addon store\n\n1. Go to the addon store and find the extension or [click here][2].\n2. Click on `Add to Firefox`\n3. Done 🎉 The extension should be installed.\n\n<br>\n\n##### From xpi/jar/zip file\n\n1. Type `about:addons` in the address bar and press \"Enter\". Alternatively, You can use\n   `Ctrl` + `Shift` + `A` on Windows and\n   `Cmd` + `Shift` + `A` on Mac and\n2. Click on the Setting Gear icon `⚙`.\n3. Click on `Install Add-on From File...`\n4. Locate and click on the .xpi/.jar/.zip file.\n5. Select and click to open it.\n6. Done 🎉 The extension should be installed.\n\n<br>\n\n### Mobile\n\n#### Android\n\n##### App from Play Store\n\n- Although app(s) which use this API exist, they aren't official.\n\n##### On Firefox\n\n1. Install Firefox Nightly. Refer to this blog post for the procedure: [Expanded extension support in Firefox for Android Nightly](https://blog.mozilla.org/addons/2020/09/29/expanded-extension-support-in-firefox-for-android-nightly/)\n2. Install the extension in it from [addon store][2]\n3. If you want - install the site as an app. For that procedure [go here](#firefox-based-browsers-3).\n\n#### iOS\n\n- [For iOS click here](#ios)\n- Only available on Firefox-based browsers because Chromium-based browsers for the Android platform don't support installing extensions.\n\n<br>\n\n<br>\n\n## Using\n\nNo more steps are required, it should start working right away.\n\nIf required you can restart your browser.\n\n**🎉 🎊 Congratulations** 🥳 🎊\n\n<br>\n\n<br>\n\n## Updating\n\n### Extension / Addon\n\nOn [Chromium based browsers][4]\n\n- [from webstore][1]\n- sideloading from [crx/zip file](#from-crxzip-file) or [unzipped folder](#from-unzipped-folder)\n\nOn [Firefox based browsers][5]\n\n- [from addon store][2]\n- [sideloading from xpi/jar/zip file](#from-xpijarzip-file)\n\nOn [Firefox based browsers][5] for mobile\n\n- [from addon store][2]\n\n<br>\n\n<br>\n\n## Miscellaneous\n\n### Using YouTube website as an app with an extension\n\n#### Desktop\n\n##### Chromium Based Browsers\n\n0. Go to YouTube\n1. Tap on menu button (`☰` / `⋯` / `⋮`)\n2. Click on \"Install YouTube\"\n3. 🎉Done!\n\n<br>\n\n##### Firefox Based Browsers\n\n**⚠ Doesn't work on newer versions.**\n\n1. Type [`about:config`](about:config) in address bar & press `Enter` key.\n2. When warned `Proceed with Caution` click on `Accept the Risk and Continue`\n3. Locate and click on the search bar on the page.\n4. Type `browser.ssb.enable`.\n5. click on `⇋` and make sure the state is set to `true`.\n6. Restart the browser and Open YouTube.\n7. Click on `⋮` located in the upper right corner\n8. Click `Install this website as an app` or `Install this website as an app` which depends on the version you are using.\n9. Done! 🎉 You should be able to see the site's app in the start menu.\n\nYou can also refer here (for pictures):\n\n[How to Enable the Site-Specific Browser (SSB) in Firefox - Make Tech Easier](https://www.maketecheasier.com/enable-site-specific-browser-firefox)\n\n<br>\n\n#### Mobile\n\n##### Firefox Based Browsers\n\n0. Install the extension\n1. Go to YouTube\n2. Tap on the three dots `⋮` located at the bottom right corner of the screen.\n3. Turn on \"Desktop Site\".\n4. Refresh the page.\n5. Wait for it to completely load.\n6. Tap again on the three dots `⋮` located at the bottom right corner of the screen.\n7. You will see an option as `Install`. Tap on it.\n8. Tap - `Add` when asked `Add to Home screen?`\n9. Enjoy the YT site as a Firefox nightly app !!\n\n<br>\n\n##### [Chromium Based Browsers][4]\n\nBut still - here are the steps:\n\n1. Go to YouTube\n2. Tap on the three dots `⋮` located at the bottom right corner of the screen.\n3. Turn on \"Desktop Site\".\n4. Refresh the page and wait for it to completely load.\n5. Tap again on the three dots `⋮` located at the bottom right corner of the screen.\n6. You will see an option as `Install`. Tap on it.\n7. When asked `Install App` tap on `Yes`\n8. Tap - `Add` when asked `Add to Home screen?`\n9. Enjoy the YT site as an app !!\n\n<br>\n\n<br>\n\n<!-- links -->\n\n[1]: https://chrome.google.com/webstore/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi\n[2]: https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/\n[3]: https://github.com/Anarios/return-youtube-dislike/raw/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js\n[4]: https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium\n[5]: https://en.wikipedia.org/wiki/Category:Web_browsers_based_on_Firefox\n[6]: chrome://extensions\n"
  },
  {
    "path": "Docs/Guide__Troubleshooting.md",
    "content": "Troubleshooting Guide\n\n**Index**\n\n- [Extension](#extension)\n  - [Basic checks](#basic-checks)\n  - [Check API status](#check-api-status)\n  - [Install certificates](#install-certificates)\n  - [Check for logs in the console](#check-for-logs-in-the-console)\n    - [In Chromium Based browsers](#in-chromium-based-browsers)\n    - [In Firefox Based Browsers](#in-firefox-based-browsers)\n  - [Check for conflicting extensions](#check-for-conflicting-extensions)\n  - [Known conflicts](#known-conflicts)\n- [iOS app](#ios-app)\n- [YouTube Vanced app](#youtube-vanced--app)\n- [Contact in Discord Server](#contact-in-discord-server)\n- [Useful Links](#useful-links)\n\n<br>\n\n## Extension\n\n(for Windows & Macs)\n\n### Basic checks\n\n1. Make sure you have the latest version of the extension installed. ([Click Here to check](https://chrome.google.com/webstore/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi#:~:text=Report%20abuse-,Version,-2.0.0.3))\n2. Close all the tabs & restart your browser\n3. Reinstall the extension.\n4. [Check API status](#check-api-status)\n5. [Check Cloudflare status](https://www.cloudflarestatus.com/)\n6. [If you are on Windows 7 read this](#install-certificates)\n\n<br>\n\n### Check API status\n\nIf the basic checks didn't resolve anything\n\n[See if you get any response from this link (click here)](https://returnyoutubedislikeapi.com/votes?videoId=QOFEgexls14)\n\n- If you **don't see something like** this, then the **API is down** and **everything is fine on your side**.\n  `{\"id\":\"QOFEgexls14\",\"dateCreated\":\"2021-12-28T02:53:20.995329Z\",\"likes\":2968,\"dislikes\":204,\"rating\":4.725047080979285,\"viewCount\":29157,\"deleted\":false}`\n- If you see some responses but not in the above format (with likes and dislikes) then probably you are being rate-limited. It is done to prevent bot attacks and database vandalization. It depends on IP (its hash - which is never stored in non-volatile storage) for its countermeasures. If many people are accessing the server from the same IP (as in the case of public/institutional Wi-Fi) then it's possible that the IP is being rate-limited. If that's the case, there's no way for us to differentiate you from a bot/attacker.\n\n**If you see \"Certificate error\" and [if you are on Windows 7 (or earlier) read this](#install-certificates)**\n\n<br><br>\n\n### Install certificates\n\n**Applies for Windows 7 (and earlier) only**\n\nand only for [Chromium Based Browsers][1]\n\n- [Chromium-based browsers][1] don't have their own certificate manager.\n- They use Windows' certificates manager.\n- Microsoft has officially dropped the support for Windows 7\n\nYou will have to install the latest certificates for that.\n\nYou can follow this guide:\n\n[Fix error NET::ERR CERT DATE INVALID - Your connection is not private - Windows 7 - 2021](https://youtu.be/JYZLxP2Z8G4)\n\nIf you don't want to install the certificate from Google Drive\n\n- Here is the official link to the certificate [**x1.i.lencr.org**](http://x1.i.lencr.org/).\n- **You will have to close all the tabs** before downloading this certificate.\n\n**The thumbprint of real certificate is `cabd2a79a1076a31f21d253635cb039d4329a5e8`**\n\n**To make sure that you have installed the correct certificate, you should consider checking if the thumbprints match.** To do this you can follow this guide: [How to check a certificate's thumbprint](https://knowledge.digicert.com/solution/SO9840.html)\n\n<br>\n\n### Check for logs in the console\n\n#### [In Chromium Based browsers][1]\n\n1. In Developer tools, go to [`console` panel](https://developer.chrome.com/docs/devtools/open/#console).\n   - For Windows press `Ctrl` + `Shift` + `J` all at once\n   - For Mac press `Cmd` + `Option` + `J` all at once\n2. Find `filter` box in the newly appeared window.\n3. Type `Return`.\n4. Check the [Check API Status](#check-api-status) and see if you get similar responses.\n5. If you see any errors in red [please contact us][4] and report them in our [discord server][3]\n\n<!-- If ever needed\n   - For Android refer to this article: [Remote debug Android devices](https://developer.chrome.com/docs/devtools/remote-debugging/) -->\n\n<br>\n\n#### [In Firefox Based Browsers][2]\n\n1. Open Browser Console\n   - For standard keyboard layout press `Ctrl` + `Shift` + `K` all at once\n   - For Mac keyboard layout press `Cmd` + `Option` + `K` all at once\n   - For Android refer to this article: [Remotely debugging Firefox <36 for Android](https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Firefox_for_Android)\n2. Find `Filter Output` box in the newly appeared window.\n3. Type `Return`.\n4. If you see any errors in red [please contact us][4] and report them in our [discord server][3]\n\n<br>\n\n### Check for conflicting extensions\n\nSome privacy and/or security-focused extensions such as ad- or script-blockers, as well as YouTube customization plugins might prevent the extension from working correctly.  \nTry to disable all other extensions and test whether the extension works.  \nIf it does, find the extension(s) preventing RYD from working correctly and re-configure them in a way that'd stop them from interfering.\n\n<br>\n\n### Known conflicts\n\n> ### scriptSafe\n>\n> **Solution:** Trust `returnyoutubedislikeapi.com` manually\n>\n> ![trust returnyoutubedislikeapi.com manually](https://cdn.discordapp.com/attachments/821116437720334397/929814357708247060/unknown.png)\n\n<br>\n\n> ### uMatrix\n>\n> **Solution:** Allow XHR for `returnyoutubedislikeapi.com` manually\n>\n> ![Allow XHR for `returnyoutubedislikeapi.com` manually](https://media.discordapp.net/attachments/821116437720334397/929813724238336141/unknown.png)\n\n<br>\n\n<br>\n\n## iOS app\n\nComing soon. Please have patience.\n\n<br>\n\n<br>\n\n## YouTube Vanced app\n\nComing soon. Please have patience.\n\n<br>\n\n<br>\n\n## Contact in Discord Server\n\n**Only if nothing mentioned above helped and you still have a problem.**\n\n[Discord server link: https://discord.gg/mYnESY4Md5][3]\n\n0. Join the discord server if you haven't already\n1. Go to the #Bugs-and-problems channel\n2. There, thoroughly describe:\n   - your problem\n   - what you have tried and what didn't work\n   - results of the troubleshooting steps\n\n<!-- {\n  \"update_frequency\" : \"low\"\n} -->\n\n<br>\n\n<br>\n\n## Useful Links\n\n[List of Chromium Based Browsers][1]\n\n[List of Firefox Based Browsers][2]\n\n[Return-YouTube-Dislike Discord Server][3]\n\n<!-- links -->\n\n[1]: https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium\n[2]: https://en.wikipedia.org/wiki/Category:Web_browsers_based_on_Firefox\n[3]: https://discord.gg/mYnESY4Md5\n[4]: #contact-in-discord-server\n"
  },
  {
    "path": "Docs/Guide__Troubleshootingvi.md",
    "content": "Hướng dẫn Khắc phục Sự cố\n\n**Mục lục**\n\n- [Extension](#extension)\n  - [Basic checks](#basic-checks)\n  - [Check API status](#check-api-status)\n  - [Install certificates](#install-certificates)\n  - [Check for logs in the console](#check-for-logs-in-the-console)\n    - [In Chromium Based browsers](#in-chromium-based-browsers)\n    - [In Firefox Based Browsers](#in-firefox-based-browsers)\n  - [Check for conflicting extensions](#check-for-conflicting-extensions)\n  - [Known conflicts](#known-conflicts)\n- [iOS app](#ios-app)\n- [YouTube Vanced app](#youtube-vanced--app)\n- [Contact in Discord Server](#contact-in-discord-server)\n- [Useful Links](#useful-links)\n\n<br>\n\n## Extension\n\n(for Windows & Macs)\n\n### Basic checks\n\n1. Make sure you have the latest version of the extension installed. ([Click Here to check](https://chrome.google.com/webstore/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi#:~:text=Report%20abuse-,Version,-2.0.0.3))\n2. Close all the tabs & restart your browser\n3. Reinstall the extension.\n4. [Check API status](#check-api-status)\n5. [Check Cloudflare status](https://www.cloudflarestatus.com/)\n6. [If you are on Windows 7 read this](#install-certificates)\n\n<br>\n\n### Check API status\n\nIf the basic checks didn't resolve anything\n\n[See if you get any response from this link (click here)](https://returnyoutubedislikeapi.com/votes?videoId=QOFEgexls14)\n\n- If you **don't see something like** this, then the **API is down** and **everything is fine on your side**.\n  `{\"id\":\"QOFEgexls14\",\"dateCreated\":\"2021-12-28T02:53:20.995329Z\",\"likes\":2968,\"dislikes\":204,\"rating\":4.725047080979285,\"viewCount\":29157,\"deleted\":false}`\n- If you see some responses but not in the above format (with likes and dislikes) then probably you are being rate-limited. It is done to prevent bot attacks and database vandalization. It depends on IP (its hash - which is never stored in non-volatile storage) for its countermeasures. If many people are accessing the server from the same IP (as in the case of public/institutional Wi-Fi) then it's possible that the IP is being rate-limited. If that's the case, there's no way for us to differentiate you from a bot/attacker.\n\n**If you see \"Certificate error\" and [if you are on Windows 7 (or earlier) read this](#install-certificates)**\n\n<br><br>\n\n### Install certificates\n\n**Applies for Windows 7 (and earlier) only**\n\nand only for [Chromium Based Browsers][1]\n\n- [Chromium-based browsers][1] don't have their own certificate manager.\n- They use Windows' certificates manager.\n- Microsoft has officially dropped the support for Windows 7\n\nYou will have to install the latest certificates for that.\n\nYou can follow this guide:\n\n[Fix error NET::ERR CERT DATE INVALID - Your connection is not private - Windows 7 - 2021](https://youtu.be/JYZLxP2Z8G4)\n\nIf you don't want to install the certificate from Google Drive\n\n- Here is the official link to the certificate [**x1.i.lencr.org**](http://x1.i.lencr.org/).\n- **You will have to close all the tabs** before downloading this certificate.\n\n**The thumbprint of real certificate is `cabd2a79a1076a31f21d253635cb039d4329a5e8`**\n\n**To make sure that you have installed the correct certificate, you should consider checking if the thumbprints match.** To do this you can follow this guide: [How to check a certificate's thumbprint](https://knowledge.digicert.com/solution/SO9840.html)\n\n<br>\n\n### Check for logs in the console\n\n#### [In Chromium Based browsers][1]\n\n1. In Developer tools, go to [`console` panel](https://developer.chrome.com/docs/devtools/open/#console).\n   - For Windows press `Ctrl` + `Shift` + `J` all at once\n   - For Mac press `Cmd` + `Option` + `J` all at once\n2. Find `filter` box in the newly appeared window.\n3. Type `Return`.\n4. Check the [Check API Status](#check-api-status) and see if you get similar responses.\n5. If you see any errors in red [please contact us][4] and report them in our [discord server][3]\n\n<!-- If ever needed\n   - For Android refer to this article: [Remote debug Android devices](https://developer.chrome.com/docs/devtools/remote-debugging/) -->\n\n<br>\n\n#### [In Firefox Based Browsers][2]\n\n1. Open Browser Console\n   - For standard keyboard layout press `Ctrl` + `Shift` + `K` all at once\n   - For Mac keyboard layout press `Cmd` + `Option` + `K` all at once\n   - For Android refer to this article: [Remotely debugging Firefox <36 for Android](https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Firefox_for_Android)\n2. Find `Filter Output` box in the newly appeared window.\n3. Type `Return`.\n4. If you see any errors in red [please contact us][4] and report them in our [discord server][3]\n\n<br>\n\n### Check for conflicting extensions\n\nSome privacy and/or security-focused extensions such as ad- or script-blockers, as well as YouTube customization plugins might prevent the extension from working correctly.  \nTry to disable all other extensions and test whether the extension works.  \nIf it does, find the extension(s) preventing RYD from working correctly and re-configure them in a way that'd stop them from interfering.\n\n<br>\n\n### Known conflicts\n\n> ### scriptSafe\n>\n> **Solution:** Trust `returnyoutubedislikeapi.com` manually\n>\n> ![trust returnyoutubedislikeapi.com manually](https://cdn.discordapp.com/attachments/821116437720334397/929814357708247060/unknown.png)\n\n<br>\n\n> ### uMatrix\n>\n> **Solution:** Allow XHR for `returnyoutubedislikeapi.com` manually\n>\n> ![Allow XHR for `returnyoutubedislikeapi.com` manually](https://media.discordapp.net/attachments/821116437720334397/929813724238336141/unknown.png)\n\n<br>\n\n<br>\n\n## iOS app\n\nComing soon. Please have patience.\n\n<br>\n\n<br>\n\n## YouTube Vanced app\n\nComing soon. Please have patience.\n\n<br>\n\n<br>\n\n## Contact in Discord Server\n\n**Only if nothing mentioned above helped and you still have a problem.**\n\n[Discord server link: https://discord.gg/mYnESY4Md5][3]\n\n0. Join the discord server if you haven't already\n1. Go to the #Bugs-and-problems channel\n2. There, thoroughly describe:\n   - your problem\n   - what you have tried and what didn't work\n   - results of the troubleshooting steps\n\n<!-- {\n  \"update_frequency\" : \"low\"\n} -->\n\n<br>\n\n<br>\n\n## Useful Links\n\n[List of Chromium Based Browsers][1]\n\n[List of Firefox Based Browsers][2]\n\n[Return-YouTube-Dislike Discord Server][3]\n\n<!-- links -->\n\n[1]: https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium\n[2]: https://en.wikipedia.org/wiki/Category:Web_browsers_based_on_Firefox\n[3]: https://discord.gg/mYnESY4Md5\n[4]: #contact-in-discord-server\n"
  },
  {
    "path": "Docs/Privacy Policy",
    "content": "The only data collected from users is their likes and dislikes made while the extension is installed.\n\nNo personal info, account name or watch history is collected or saved.\n\nUsers are identified by a random user ID, which is not directly linked to any of their accounts, the only purpose of this user ID is to make voting process possible.\n\nNone of the saved data is shared with any third parties.\n"
  },
  {
    "path": "Docs/Privacy Policyvi",
    "content": "Dữ liệu duy nhất được thu nhập: số lượt đánh giá \"thích\" và \"không thích\" trong thời gian tiện ích mở rộng này được cài.\n\nDữ liệu không được thu thập cũng như không lưu trữ: thông tin cá nhân, tên tài khoản, lịch sử xem vi-đê-ô.\n\nNgười dùng được gán cho một ID người dùng ngẫu nhiên. ID người dùng này không có bất kì mối liên hệ nào tới những tài khoản của người dùng. Mục đích duy nhất của việc dùng ID người dùng là để thực hiện chức năng đánh giá vi-đê-ô.\n\nDữ liệu được lưu trữ không được chia sẻ với bất kì bên thứ ba nào.\n"
  },
  {
    "path": "Docs/SECURITY-FAQ.md",
    "content": "Read this in other languages: [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Security\n\n### Are you tracking my viewing history?\n\nNo. The extension's code is public and you can see it for yourself. The only information being sent is the video ID, which is required to fetch the dislike count for the videos. There are no additional headers being sent. Over the communication layer, your public IP will be exposed to the server, as well as the time when the request was made. However, none of these are uniquely identifying you in any way. Assuming a zero-trust environment, the best we could get is a dynamic IP. Which, today is yours, tomorrow is your neighbor's. If you're really worried about your IP being traced, you probably already use a VPN.\n\n### Can you uniquely identify me if I dislike?\n\nYes. When you dislike a video, we create a randomly generated unique ID for you that is not tied to your Google account. This is done to prevent botting. But there is no way to tie this random Id to you or your personal YouTube account.\n\n### What information do you have, exactly?\n\nJust the video ID. Not your comments, not your username, not who you've shared the video with, not any additional metadata. Nothing. Just the video ID.\n\n### How is my IP stored?\n\nThe backend keeps unhashed IP addresses in volatile memory (RAM) only. These addresses aren't stored on a hard drive, and therefore aren't logged. We hash the IP addresses, and that's stored instead. This is done to prevent database vandalism.\n\n### I heard some discussion over OAuth, and access to my YouTube account!\n\nThis feature will be optional, and very much opt-in. If you are a YouTube creator, and would like to share your dislike stats with us, you can. The way [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) was structured, it's actually very secure. You can revoke access to your account at any time, and can give very specific permissions to us. We will not ask for any permissions that aren't required. We'll only ask for permissions to view your video stats.\n\n### How can I trust this dislike count?\n\nWe have implemented measures to prevent bot attacks and are gonna continue to work on improving the effectiveness of the bot prevention system: this will help us keep the dislike count as a good representative of the actual count. Of course it will never be 100% accurate so it's up to you to decide whether you trust the count or not.\n\n### Why don't you share the backend code?\n\nWe will share it at some point - but there's really no real reason to share it right now. It gives a false sense of security - because in a zero-trust system, we could just as well disclose one version but deploy another. There are plenty of reasons to keep the code hidden, specifically, how we battle spam. Hiding/Obfuscating the spam handling code is a fairly standard practice.\n"
  },
  {
    "path": "Docs/SECURITY-FAQaz.md",
    "content": "Read this in other languages: [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md)\n\n# Təhlükəsizlik\n\n### Baxış tarixçəmi izləyirsiniz?\n\nXeyr. Artırmanın kodu ictimaiyyətə açıqdır və siz bunu özünüz görə bilərsiniz. Göndərilən yeganə məlumat videolar üçün bəyənməmə sayını almaq üçün tələb olunan video ID-dir. Göndərilən başqa əlavə başlıq yoxdur. Rabitə təbəqəsi vasitəsilə ictimai IP-niz serverə və sorğunun edildiyi vaxta məruz qalacaq. Bununla belə, bunların heç biri sizi heç bir şəkildə unikal şəkildə müəyyənləşdirmir. Sıfır etibar mühitini fərz etsək, əldə edə biləcəyimiz ən yaxşısı dinamik IP-dir. Bu İP bu gün sənin, sabah qonşunun ola bilər. Əgər həqiqətən IP-nizin izlənilməsindən narahatsınızsa, yəqin ki, artıq VPN-dən istifadə edirsiniz.\n\n### Videonu bəyənməsəm, məni unikal şəkildə tanıya bilərsiniz??\n\nBəli. Videonu bəyənmədiyiniz zaman biz sizin üçün Google hesabınızla əlaqəli olmayan təsadüfi yaradılmış unikal ID yaradırıq. Bu, botlardan istifadənin qarşısını almaq üçün edilir. Bununla belə, bu təsadüfi identifikatoru sizə və ya şəxsi YouTube hesabınıza bağlamağın heç bir yolu yoxdur.\n\n### Sizdə dəqiq olaraq hansı məlumat var??\n\nSadəcə video identifikatoru. Şərhləriniz, istifadəçi adınız, videonu kiminlə paylaşdığınız, heç bir əlavə metadata deyil. heç nə. Sadəcə video identifikatoru.\n\n### Mənim IP ünvanım necə saxlanılır?\n\nBackend yalnız müvəqqəti yaddaşda (RAM) haşiyələnməmiş IP ünvanlarını saxlayır. Bu ünvanlar sabit diskdə saxlanmır və buna görə də qeyd olunmur. Biz IP ünvanlarını hash edirik və əvəzinə onlar saxlanılır. Bu, verilənlər bazası vandalizminin qarşısını almaq üçün edilir.\n\n### OAuth vasitəsilə YouTube hesabıma daxil olmaq haqqında bəzi mübahisələr eşitdim!\n\nBu xüsusiyyət istəyə bağlı olacaq və çox üstünlük təşkil edəcək. Əgər siz YouTube yaradıcısısınızsa və bəyənmədiyiniz statistikanı bizimlə bölüşmək istəyirsinizsə, bunu edə bilərsiniz. [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) Qurulma şəkli, əslində çox təhlükəsizdir. İstənilən vaxt hesabınıza girişi ləğv edə və bizə çox xüsusi icazələr verə bilərsiniz. Biz heç bir lazımsız icazə istəməyəcəyik. Biz yalnız video statistikanıza baxmaq üçün icazə istəyəcəyik.\n### Bu sayda bəyənməmələrə necə etibar edə bilərəm??\n\nBiz bot hücumlarının qarşısını almaq üçün tədbirlər gördük və anti-bot sisteminin effektivliyini artırmaq üçün işi davam etdirəcəyik: bu, bəyənməmələrin sayını real rəqəmin yaxşı nümayəndəsi kimi saxlamağa kömək edəcək. Əlbəttə ki, bu, heç vaxt 100% dəqiq olmayacaq, ona görə də hesaba etibar edib etməmək sizə bağlıdır.\n\n### Niyə backend kodunu paylaşmırsınız?\nBiz bunu nə vaxtsa paylaşacağıq - lakin hazırda paylaşmaq üçün həqiqətən heç bir səbəb yoxdur. Bu, yanlış təhlükəsizlik hissi verə bilər - çünki sıfır inamlı sistemdə biz bir versiyanı ifşa edə, digərini yerləşdirə bilərik. Kodu gizli saxlamağın bir çox səbəbi var, xüsusən də spamla necə mübarizə aparırıq. Spam emalı kodunu ört-basdır etmək/gizlətmək olduqca standart təcrübədir."
  },
  {
    "path": "Docs/SECURITY-FAQbg.md",
    "content": "Прочетете това на други езици:  [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md)\n\n\n# Сигурност\n\n### Проследявате ли историята на гледанията ми?\n\nНе. Кода на разширението е публичен и може да го видите сами. Единствената информация, която се изпраща, е идентификаторът на видеото, който е необходим, за да вземе броя на дизлайковете за видеата. Няма допълнителни заглавия, изпращани. Чрез комуникационния слой вашите обществени IP адреси ще бъдат изложени на сървъра, както и времето, в което беше направен заявката. Обаче нито едно от тези не ви идентифицира уникално по никакъв начин. Предполагайки среда с нулево доверие, най-доброто, което можем да получим, е динамичен IP адрес. Който днес е вашият, утре е на вашия съсед. Ако наистина се притеснявате, че вашият IP адрес бива проследен, вероятно вече използвате VPN.\n\n### Можете ли да ме идентифицирате уникално, ако дизлайкна?\n\nДа. Когато дизлайкнете видео, създаваме случайно генериран уникален идентификатор за вас, който не е свързан с вашия Google акаунт. Това се прави, за да предотвратим бот атаки. Но няма начин този случаен идентификатор да бъде свързан с вас или с вашия личен акаунт на YouTube.\n\n### Какви точно информация имате?\n\nСамо идентификатора на видеото. Нито вашите коментари, нито потребителското ви име, нито на кого сте споделили видеото, нито допълнителни метаданни. Нищо. Само идентификатора на видеото.\n\n### Как се съхранява моят IP адрес?\n\nБекендът държи незакодирани IP адреси само в изменяемата памет (RAM). Те не се съхраняват на твърд диск и следователно не се записват. Ние кодираме IP адресите, и това се съхранява. Това се прави, за да се предотврати вандализмът върху базата данни.\n\n### Чух за OAuth и достъп до акаунта ми на YouTube!\n\nТази функция ще бъде по избор и изключително от ваш избор. Ако сте създател на YouTube и искате да споделите статистиката за дизлайковете с нас, можете. Структурата на [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) е наистина много сигурна. Всеки път можете да отмените достъпа до акаунта си и да дадете много конкретни разрешения на нас. Няма да поискаме никакви разрешения, които не са необходими. Ще поискаме само разрешения за преглед на статистиката на вашите видеа.\n\n### Как мога да се доверя на този брой дизлайкове?\n\nВъведохме мерки за предотвратяване на атаки с ботове и ще продължим да работим по подобряване на ефективността на системата за предотвратяване на ботове: това ще ни помогне да поддържаме броя на дизлайковете като добър представител на реалния брой. Разбира се, той никога няма да бъде 100% точен, така че решението дали да вярвате на броя или не, зависи от вас.\n\n### Защо не споделяте кода на бекенда?\n\nЩе го споделим на някакъв етап - но наистина няма реална причина да го споделяме в момента. Това създава лъжливо чувство за сигурност - защото в среда с нулево доверие можем съвсем спокойно да разкрием една версия, но да развием друга. Има много причини да държим кода скрит, специално начина, по който се борим срещу спама. Скриването/затъмняването на кода за борба със спама е стандартна практика.\n"
  },
  {
    "path": "Docs/SECURITY-FAQcn.md",
    "content": "阅读其他语言版本：[русский](SECURITY-FAQru.md)、[Nederlands](SECURITY_FAQnl.md)、[Français](SECURITY-FAQfr.md)、[Türkçe](SECURITY-FAQtr.md)、[українська](SECURITY-FAQk.md)、[Polski](SECURITY-FAQpl.md)、[Deutsch](SECURITY-FAQde.md)\n\n# 安全\n\n### 您是否在跟踪我的观看历史记录？\n\n否。扩展程序的代码是公开的，您可以自己查看。发送的唯一信息是视频 ID，这是获取视频的不喜欢计数所必需的。没有发送其他标头。在通信层上，您的公共 IP 以及发出请求的时间将暴露给服务器。但是，这些都不能以任何方式唯一地识别您。假设一个零信任环境，我们能得到的最好东西就是动态 IP。今天是你的，明天是你的邻居的。如果你真的担心你的 IP 被追踪，你可能已经使用了 VPN。\n\n### 如果我不喜欢，你能唯一地识别我吗？\n\n是。当你不喜欢某个视频时，我们会为你创建一个随机生成的唯一 ID，该 ID 不与你的 Google 帐户绑定。这样做是为了防止机器人。但是没有办法将这个随机 ID 绑定到你或你的个人 YouTube 帐户。\n\n### 你到底有什么信息？\n\n只有视频 ID。不是你的评论，不是你的用户名，不是你与谁分享了视频，也不是任何其他元数据。什么都没有。只有视频 ID。\n\n### 我的 IP 如何存储？\n\n后端仅将未散列的 IP 地址保存在易失性存储器 (RAM) 中。这些地址不存储在硬盘上，因此不会被记录。我们对 IP 地址进行散列，然后将其存储起来。这样做是为了防止数据库遭到破坏。\n\n### 我听到一些关于 OAuth 和访问我的 YouTube 帐户的讨论！\n\n此功能是可选的，并且非常可选择加入。如果您是 YouTube 创作者，并且想与我们分享您的不喜欢统计数据，您可以这样做。 [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) 的结构方式实际上非常安全。您可以随时撤销对您帐户的访问权限，并可以向我们授予非常具体的权限。我们不会要求任何不必要的权限。我们只会要求查看您的视频统计数据的权限。\n\n### 我如何信任这个不喜欢计数？\n\n我们已经实施了防止机器人攻击的措施，并将继续努力提高机器人预防系统的有效性：这将帮助我们将不喜欢计数保持为实际计数的良好代表。当然，它永远不会 100% 准确，因此由您决定是否信任计数。\n\n### 您为什么不共享后端代码？\n\n我们会在某个时候共享它 - 但现在真的没有真正的理由共享它。它给人一种虚假的安全感 - 因为在零信任系统中，我们也可以披露一个版本但部署另一个版本。隐藏代码的原因有很多，特别是我们如何对抗垃圾邮件。隐藏/混淆垃圾邮件处理代码是一种相当标准的做法。"
  },
  {
    "path": "Docs/SECURITY-FAQde.md",
    "content": "Read this in other languages: [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md)\n\n\n# Sicherheit\n\n### Verfolgen Sie meine Anzeigehistorie?\n\nNein. Der Code der Erweiterung ist öffentlich und Sie können ihn selbst einsehen. Die einzige übermittelte Information ist die Video-ID, die benötigt wird, um die Anzahl der Dislikes für die Videos abzurufen. Es werden keine zusätzlichen Header gesendet. Über die Kommunikationsschicht wird Ihre öffentliche IP-Adresse an den Server übermittelt, sowie die Zeit, zu der die Anfrage gestellt wurde. Keine dieser Informationen identifiziert Sie auf eindeutige Weise. Unter der Annahme einer Zero-Trust-Umgebung ist das Beste, was wir bekommen können, eine dynamische IP. Die heute Ihnen gehört, gehört morgen Ihrem Nachbarn. Wenn Sie sich wirklich Sorgen machen, dass Ihre IP zurückverfolgt wird, verwenden Sie wahrscheinlich bereits ein VPN.\n\n### Können Sie mich eindeutig identifizieren, wenn ich etwas dislike?\n\nJa. Wenn Sie ein Video nicht mögen, erstellen wir eine zufällig generierte eindeutige ID für Sie, die nicht mit Ihrem Google-Konto verknüpft ist. Dies geschieht, um das Botting zu verhindern. Es gibt jedoch keine Möglichkeit, diese zufällige ID mit Ihnen oder Ihrem persönlichen YouTube-Konto zu verknüpfen.\n\n### Welche Informationen haben Sie genau?\n\nNur die Video-ID. Nicht Ihre Kommentare, nicht Ihren Benutzernamen, nicht mit wem Sie das Video geteilt haben, keine zusätzlichen Metadaten. Nichts. Nur die Video-ID.\n\n### Wie wird meine IP gespeichert?\n\nDer Backend speichert nicht gehashte IP-Adressen nur im flüchtigen Speicher (RAM). Diese Adressen werden nicht auf einer Festplatte gespeichert und daher nicht protokolliert. Wir hashen die IP-Adressen und speichern diese stattdessen. Dies geschieht, um Datenbankvandalismus zu verhindern.\n\n### Ich habe etwas über OAuth gehört und den Zugriff auf mein YouTube-Konto!\n\nDiese Funktion wird optional sein und sehr stark opt-in. Wenn Sie ein YouTube-Ersteller sind und Ihre Dislike-Statistiken mit uns teilen möchten, können Sie dies tun. Die Art und Weise, wie [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) strukturiert wurde, ist tatsächlich sehr sicher. Sie können den Zugriff auf Ihr Konto jederzeit widerrufen und uns sehr spezifische Berechtigungen erteilen. Wir werden nicht nach Berechtigungen fragen, die nicht erforderlich sind. Wir werden nur um Berechtigungen bitten, um Ihre Video-Statistiken anzeigen zu können.\n\n### Wie kann ich diesem Dislike-Zähler vertrauen?\n\nWir haben Maßnahmen implementiert, um Bot-Angriffe zu verhindern, und werden weiterhin daran arbeiten, die Effektivität des Bot-Präventionssystems zu verbessern: Dies wird uns helfen, den Dislike-Zähler als gute Repräsentation der tatsächlichen Anzahl zu halten. Natürlich wird es nie zu 100% genau sein, also liegt es an Ihnen zu entscheiden, ob Sie dem Zähler vertrauen oder nicht.\n\n### Warum teilen Sie den Backend-Code nicht?\n\nWir werden ihn irgendwann teilen - aber es gibt wirklich keinen echten Grund, ihn jetzt zu teilen. Es vermittelt ein falsches Sicherheitsgefühl - denn in einem Zero-Trust-System könnten wir genauso gut eine Version offenlegen, aber eine andere bereitstellen. Es gibt viele Gründe, den Code verborgen zu halten, insbesondere wie wir gegen Spam kämpfen. Das Verbergen/Verfremden des Spam-Behandlungscodes ist eine ziemlich standardmäßige Praxis.\n"
  },
  {
    "path": "Docs/SECURITY-FAQfr.md",
    "content": "Lisez ceci dans d'autres langues : [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Sécurité\n\n### Est-ce que vous traquez l'historique des vidéos que je visionne ?\n\nNon. Le code de l'extension est public et vous pouvez le voir par vous-même. La seule information envoyée est l'ID de la vidéo, qui est nécessaire pour récupérer le nombre de dislikes des vidéos. Aucun en-tête (headers) supplémentaire n'est envoyé. Sur la [couche de communication](https://fr.wikipedia.org/wiki/Mod%C3%A8le_OSI#Caract%C3%A9risation_r%C3%A9sum%C3%A9e_des_couches), votre adresse IP publique sera exposée au serveur, ainsi que l'heure à laquelle la demande a été faite. Toutefois, aucun de ces éléments ne permet de vous identifier de manière unique. Dans un environnement où vous ne pouvez avoir confiance en personne (zero-trust environment), le mieux que l'on puisse obtenir est une IP dynamique. Qui, aujourd'hui est la vôtre, demain est celle de votre voisin. Si vous êtes vraiment inquiet que votre IP soit tracée, vous utilisez probablement déjà un VPN.\n\n### Pouvez-vous m'identifier de manière unique si je dislike ?\n\nOui. Lorsque vous dislikez une vidéo, nous créons pour vous un identifiant unique généré de manière aléatoire et non lié à votre compte Google. Cette mesure vise à empêcher les robots. Mais il n'y a aucun moyen de lier cet identifiant aléatoire à vous ou à votre compte YouTube personnel.\n\n### Quelles sont les informations dont vous disposez, exactement ?\n\nJuste l'ID de la vidéo. Pas vos commentaires, pas votre nom d'utilisateur, pas les personnes avec qui vous avez partagé la vidéo, pas de métadonnées supplémentaires. Rien. Juste l'ID de la vidéo.\n\n### Comment mon IP est-elle stockée ?\n\nLe backend conserve les adresses IP non hachées uniquement dans la mémoire volatile (RAM). Ces adresses ne sont pas stockées sur un disque dur et ne sont donc pas enregistrées. Nous hachons les adresses IP et stockons ça à la place. Ceci est fait pour empêcher le vandalisme de la base de données.\n\n### J'ai entendu des discussions sur OAuth, et l'accès à mon compte YouTube !\n\nCette fonctionnalité sera facultative et fera l'objet d'une demande d'adhésion. Si vous êtes un créateur YouTube et que vous souhaitez partager vos statistiques de dislikes avec nous, vous pouvez le faire. La façon dont [OAuth](https://fr.wikipedia.org/wiki/OAuth) a été structuré, c'est en fait très sûr. Vous pouvez révoquer l'accès à votre compte à tout moment et nous donner des autorisations très spécifiques. Nous ne demanderons pas d'autorisations qui ne sont pas nécessaires. Nous ne vous demanderons que l'autorisation de consulter les statistiques de vos vidéos.\n\n### Comment puis-je faire confiance à ce compte de dislikes ?\n\nNous avons mis en place des mesures pour prévenir les attaques de robots et nous allons continuer à travailler pour améliorer l'efficacité du système de prévention des robots : cela nous aidera à faire en sorte que le nombre de dislikes soit une bonne représentation du nombre réel. Bien entendu, il ne sera jamais précis à 100 %, c'est donc à vous de décider si vous lui faites confiance ou non.\n\n### Pourquoi ne pas partager le code du backend ?\n\nNous la partagerons à un moment donné - mais il n'y a pas vraiment de raison de la partager pour le moment. Cela donne un faux sentiment de sécurité, car dans un système zero-trust, nous pourrions tout aussi bien publier une version et en déployer une autre. Il y a de nombreuses raisons de garder le code caché, notamment pour lutter contre le spam. Cacher/dissimuler le code de traitement du spam est une pratique assez standard.\n"
  },
  {
    "path": "Docs/SECURITY-FAQid.md",
    "content": "Baca ini dibahasa lain: [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md)\n\n# Keamanan\n\n### Apakah kalian melacak riwayat video yang saya tonton?\n\nTidak, Kode extension bersifat publik dan kamu dapat melihatnya sendiri. Informasi yang kami ambil hanya id dari video, yang diperlukan untuk pengambilan jumlah dislike. Tidak ada tambahan data headers lainnya. Pada communication layer, IP Publik kamu akan terekspos ke server, bersamaan dengan waktu ketika requestnya dibuat. Meskipun begitu, data ini tidaklah bersifat unik atau mengidentifikasikan kamu dalam bentuk apapun. Asumsikan environment yang tidak terpercaya, hal terbaik yang dapat kami ambil adalah IP dinamis. Yang bisa saja hari ini adalah milikmu, besok milik tetanggamu. Jika kamu merasa khawati jika IP-mu dilacak, kamu mungkin telah menggunakan VPN.\n\n### Bisakah kalian mengidentifikasi saya ketika saya dislike suatu video?\n\nIya. Ketika kamu dislike suatu video, kami membuat id yang digenerate secara acak untukmu yang tidak ada hubungannya dengan akun Google. Ini dilakukan untuk menghindari bot. Tapi tidak mungkin untuk mencari kamu atau akun Youtube-mu menggunakan id acak ini.\n\n### Informasi apa saja yang kalian punya?\n\nHanya id dari video. Bukan komentarmu, bukan usernamemu, bukan siapa saja orang yang pernah kamu kirimkan video, bukan metadata tambahan. Tidak ada. Hanya id dari video.\n\n### Bagaimana IP saya disimpan?\n\nBackend menyimpan alamat IP yang tidak di hash hanya di dalam RAM. Alamat ini tidak disimpan di hard drive, sehingga tidak di log. Kami melakukan hash terhadap alamat IP dan menyimpan yang itu. Ini dilakukan untuk mencegah perusakan database.\n\n### Saya pernah mendengar diskusi mengenai OAuth, dan akses ke akun Youtube saya!\n\nFitur ini bersifat opsional, dan banyak yang berpartisipasi. Jika kamu merupakan seorang Youtuber, dan mau membagikan data dislikenya kepada kami, kamu bisa melakukannya. Dengan cara [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) yang terstruktur, dan sangat aman. Kamu dapat menghapus akses ke akunmu kapanpun, dan bisa menentukan data mana yang mau dibagikan dan tidak dibagikan secara spesifik. Kami tidak akan meminta izin apapun yang bersifat tidak wajib. Kami hanya akan meminta izin untuk melihat data video kamu.\n\n### Bagaimana bisa saya percaya jumlah dislike ini?\n\nKami telah melakukan tidakan pencegahan dari penyerangan bot dan akan terus mengimprovisasi keefektifan sistem dari pecegahan bot tersebut: ini akan membantu kami menjaga jumlah dislike agar tetap bisa menjadi representasi dari jumlah yang asli. Tentu jumlahnya tidak akan akurat 100% jadi pada akhirnya itu semua terserah padamu untuk percaya atau tidak dengan jumlah dislikenya.\n\n### Mengapa kamu tidak memberikan kode backend?\n\nKami akan memberikannya nanti pada satu waktu - tapi tidak ada alasan khusus untuk memberikanny sekarang. Itu memberikan rasa keamanan yang salah - karena pada sistem yang tidak terpercaya, kami bisa saja memberikan versi yang berdeda dengan yang kami deploy. Ada beberapa alsan lain untuk tetap menyembunyikan kodenya, terutama untuk memerangi spam. Menyembunyikan/menyamarkan kode penanganan spam adalah praktik yang cukup standar dilakukan.\n"
  },
  {
    "path": "Docs/SECURITY-FAQnl.md",
    "content": "Lees dit in andere talen: [English](SECURITY_FAQ.md), [русский](SECURITY-FAQru.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Veiligheid\n\n### Houd je mijn kijkgeschiedenis bij?\n\nNee. De code van de extensie is openbaar en u kunt deze zelf zien. De enige informatie die wordt verzonden, is de video-ID, die\nnodig is om het aantal dislikes voor de video's op te halen. Er worden geen extra headers verzonden. Via de communicatielaag\nwordt uw openbare IP-adres zichtbaar voor de server, evenals het tijdstip waarop het verzoek is gedaan. Geen van deze\nidentificeert u echter op enigerlei wijze uniek. Uitgaande van een zero-trust-omgeving, is het beste wat we kunnen krijgen een\ndynamisch IP-adres. Wat vandaag van jou is, morgen van je buurman. Als u zich echt zorgen maakt dat uw IP-adres wordt\ngetraceerd, gebruikt u waarschijnlijk al een VPN.\n\n### Kun je me uniek identificeren als ik een hekel heb?\n\nJa. Als je een video niet leuk vindt, maken we een willekeurig gegenereerde unieke ID voor je die niet is gekoppeld aan je\nGoogle-account. Dit wordt gedaan om botting te voorkomen. Maar er is geen manier om deze willekeurige id aan jou of je\npersoonlijke YouTube-account te koppelen.\n\n### Welke informatie heb je precies?\n\nAlleen de video-ID. Niet je opmerkingen, niet je gebruikersnaam, niet met wie je de video hebt gedeeld, geen aanvullende\nmetadata. Niks. Alleen de video-ID.\n\n### Hoe wordt mijn IP opgeslagen?\n\nDe backend bewaart niet-gehashte IP-adressen alleen in vluchtig geheugen (RAM). Deze adressen worden niet opgeslagen op een\nharde schijf en worden daarom niet gelogd. We hashen de IP-adressen en dat wordt in plaats daarvan opgeslagen. Dit wordt gedaan\nom databasevandalisme te voorkomen.\n\n### Ik hoorde wat discussie over OAuth en toegang tot mijn YouTube-account!\n\nDeze functie zal optioneel zijn, en zeer veel opt-in. Als je een YouTube-creator bent en je wilt je afkeerstatistieken met ons delen, dan kan dat. De weg [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) gestructureerd was, is het eigenlijk heel veilig. U kunt op elk moment de toegang tot uw account intrekken en ons zeer specifieke machtigingen geven. We zullen geen toestemming vragen die niet vereist zijn. We vragen alleen toestemming om je videostatistieken te bekijken.\n\n### Hoe kan ik deze afkeertelling vertrouwen?\n\nWe hebben maatregelen genomen om botaanvallen te voorkomen en zullen blijven werken aan het verbeteren van de effectiviteit van het botpreventiesysteem: dit zal ons helpen het aantal afkeer als een goede vertegenwoordiger van het werkelijke aantal te houden. Natuurlijk zal het nooit 100% nauwkeurig zijn, dus het is aan jou om te beslissen of je de telling vertrouwt of niet.\n\n### Waarom deel je de backend-code niet?\n\nWe zullen het op een gegeven moment delen - maar er is echt geen echte reden om het nu te delen. Het geeft een vals gevoel van veiligheid - omdat we in een zero-trust-systeem net zo goed de ene versie kunnen onthullen, maar een andere kunnen implementeren. Er zijn tal van redenen om de code verborgen te houden, met name hoe we spam bestrijden. Het verbergen/verduisteren van de spamverwerkingscode is een vrij standaardpraktijk.\n"
  },
  {
    "path": "Docs/SECURITY-FAQpl.md",
    "content": "Read this in other languages: [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Security\n\n### Czy śledzicie moją historię wyświetleń?\n\nNie. Kod rozszerzenia jest publiczny i można samemu to zobaczyć. Jedyne informacje, które są wysyłane, to ID filmu, które jest wymagane do pobrania liczby łapek w dół dla filmów. Nie są wysyłane żadne dodatkowe nagłówki. W warstwie sieciowej, Twój adres IP będzie jawny dla serwera wraz z czasem wykonania żądania. Jednakże, żadne z tych danych nie identyfikują Ciebie jednoznacznie w żaden sposób. Zakładając środowisko zerowego zaufania, najczulsze dane, jakie możemy otrzymać, jest dynamiczny adres IP, który dzisiaj jest Twój, a jutro Twojego sąsiada. Jeżeli boisz się śledzenia poprzez adres IP, pewnie już korzystasz z VPN.\n\n### Czy możecie mnie jednoznacznie zidentyfikować, jeżeli zostawię łapkę w dół?\n\nTak. Kiedy zostawiasz łapkę w dół, tworzymy losowo generowane ID dla Ciebie, które nie jest związane z Twoim kontem Google. Powodem takiego rozwiązania jest zapobieganie botowaniu. Mimo to, nie ma sposobu powiązania tego losowego ID z Tobą lub Twoim osobistym kontem Google.\n\n### Jakie informacje wy macie, konkretnie?\n\nTylko ID filmu. Komentarze - nie. Nazwa użytkownika - nie. Osoby, którym udostępniłeś film - nie. Jakiekolwiek dodatkowe metadane - nie. Nic. Tylko ID filmu.\n\n### Jak mój adres IP jest przechowywany?\n\nBackend trzyma niehashowane adresy IP tylko w pamięci zmiennej (RAM). Te adresy nie są przechowywane na dysku twardym, przez co nie są rejestrowane. Zamiast tego przechowujemy zhashowany adres IP. Jest to zrobione po to aby zapobiec wandalizmom.\n\n### Słyszałem jakąś dyskusję o OAuth, i dostępie do mojego konta YouTube.\n\nTa funkcjonalność będzie opcjonalna i z całą pewnością wymagała ręcznego dołączenia. Jeżeli jesteś twórcą na YouTube i chcesz podzielić się z nami swoimi statystykami, to możesz. Sposób w jaki [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) został ustrukturyzowany jest właściwie bardzo bezpieczny. Możesz wycofać dostęp do konta w każdej chwili i dać nam specyficzne uprawnienia. Nie będziemy prosić o żadne uprawnienia, które nie są wymagane. Poprosimy tylko o możliwość wyświetlenia statystyk filmów.\n\n### Jak bardzo mogę ufać licznikowi łapek w dół?\n\nZaimplementowaliśmy środki zapobiegające atakom botów i będziemy kontynuować pracę nad systemem zapobiegającym botom: to pomoże nam utrzymać licznik łapek w dół jako dobrą reprezentację prawdziwej wartości. Oczywiście, wartość ta nigdy nie będzie w 100% dokładna, więc to czy zaufasz tej liczbie zależy tylko od Ciebie.\n\n### Dlaczego nie udostępnicie kodu backendu?\n\nKiedyś go udostępnimy - ale nie ma za bardzo sensu robić to teraz. Wprowadziłoby to fałszywe poczucie bezpieczeństwa - bo w systemie zerowego zaufania równie dobrze moglibyśmy przedstawić jeden system, a uruchomić inny. Jest dużo powodów do ukrywania kodu, specyficznie do walki ze spamem. Ukrywanie/Obfuskacja kodu jest dość standardową praktyką.\n"
  },
  {
    "path": "Docs/SECURITY-FAQpt_BR.md",
    "content": "Leia isso em outros idiomas: [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md)\n\n# Segurança\n\n### Are you tracking my viewing history?\n\nNo. The extension's code is public and you can see it for yourself. The only information being sent is the video ID, which is required to fetch the dislike count for the videos. There are no additional headers being sent. Over the communication layer, your public IP will be exposed to the server, as well as the time when the request was made. However, none of these are uniquely identifying you in any way. Assuming a zero-trust environment, the best we could get is a dynamic IP. Which, today is yours, tomorrow is your neighbor's. If you're really worried about your IP being traced, you probably already use a VPN.\n\n### Can you uniquely identify me if I dislike?\n\nYes. When you dislike a video, we create a randomly generated unique ID for you that is not tied to your Google account. This is done to prevent botting. But there is no way to tie this random Id to you or your personal YouTube account.\n\n### Quais as informações que você tem, exatamente?\n\nSomente seu video ID. Não temos seus comentarios, seu nome de usuario, ou qualquer outro Not your comments, not your username, not who you've shared the video with, not any additional metadata. NADA. Apenas seu Video ID.\n\n### How is my IP stored?\n\nThe backend keeps unhashed IP addresses in volatile memory (RAM) only. These addresses aren't stored on a hard drive, and therefore aren't logged. We hash the IP addresses, and that's stored instead. This is done to prevent database vandalism.\n\n### I heard some discussion over OAuth, and access to my YouTube account!\n\nThis feature will be optional, and very much opt-in. If you are a YouTube creator, and would like to share your dislike stats with us, you can. The way [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) was structured, it's actually very secure. You can revoke access to your account at any time, and can give very specific permissions to us. We will not ask for any permissions that aren't required. We'll only ask for permissions to view your video stats.\n\n### How can I trust this dislike count?\n\nWe have implemented measures to prevent bot attacks and are gonna continue to work on improving the effectiveness of the bot prevention system: this will help us keep the dislike count as a good representative of the actual count. Of course it will never be 100% accurate so it's up to you to decide whether you trust the count or not.\n\n### Why don't you share the backend code?\n\nWe will share it at some point - but there's really no real reason to share it right now. It gives a false sense of security - because in a zero-trust system, we could just as well disclose one version but deploy another. There are plenty of reasons to keep the code hidden, specifically, how we battle spam. Hiding/Obfuscating the spam handling code is a fairly standard practice.\n"
  },
  {
    "path": "Docs/SECURITY-FAQru.md",
    "content": "Прочитать на других языках: [English](SECURITY-FAQ.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Безопасность\n\n### Вы отслеживаете мою историю просмотров?\n\nНет. Код расширения находится в открытом доступе, и вы можете ознакомиться с ним самостоятельно. Единственная передаваемая информация - это идентификатор видео, который необходим для получения данных о количестве просмотров видео. Никаких дополнительных заголовков не передаётся. На коммуникационном уровне серверу будет передан ваш публичный IP-адрес, а также время, когда был сделан запрос. Однако ничто из этого не идентифицирует вас каким-либо образом. Предполагая среду с нулевым доверием, лучшее, что мы можем получить, это динамический IP. Который сегодня ваш, а завтра - вашего соседа. Если вы действительно беспокоитесь о том, что ваш IP может быть отслежен, вы, вероятно, уже используете какой-нибудь VPN.\n\n### Можете ли вы однозначно идентифицировать меня, если я оставил отметку «Не нравится»?\n\nДа. Когда вы оставляете видео отметку «Не нравится», мы создаём для вас случайно созданный уникальный идентификатор, который не привязан к вашему аккаунту Google. Это делается для избежания ботов. Но нет никакого способа привязать этот случайный идентификатор к вам или вашему личному аккаунту YouTube.\n\n### Какой именно информацией вы располагаете?\n\nТолько идентификатор видео. Ни ваших комментариев, ни вашего имени пользователя, ни того, с кем вы поделились видео, ни каких-либо дополнительных метаданных. Ничего. Только идентификатор видео.\n\n### Как хранится мой IP-адрес?\n\nВнутренняя часть нашего сервера хранит нехешированные IP-адреса только в энергозависимой памяти (ОЗУ). Эти адреса не хранятся на жёстком диске и поэтому не регистрируются. Мы хэшируем IP-адреса, и оно хранится вместо них. Это сделано для предотвращения вандализма в базе данных.\n\n### Я слышал обсуждение OAuth и доступа к моему аккаунту на YouTube!\n\nЭта функция будет необязательной и очень нужной. Если вы являетесь создателем YouTube и хотите поделиться с нами статистикой отметок «Не нравится», вы можете это сделать. По своей структуре [OAuth](<https://ru.wikipedia.org/wiki/OAuth#:~:text=%D0%B1%D0%B5%D0%B7%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D1%87%D0%B8%20%D0%B5%D0%B9%20(%D1%82%D1%80%D0%B5%D1%82%D1%8C%D0%B5%D0%B9%20%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%B5)%20%D0%BB%D0%BE%D0%B3%D0%B8%D0%BD%D0%B0%20%D0%B8%20%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8F>) очень безопасен. Вы можете отозвать доступ к своему аккаунту в любое время, и можете дать нам ограниченные разрешения. Мы не будем запрашивать никаких разрешений, которые не требуются. Мы будем запрашивать разрешения только для просмотра статистики ваших видео.\n\n### Как я могу доверять этому счётчику отметок «Не нравится»?\n\nМы приняли меры по предотвращению атак ботов и собираемся продолжать работать над повышением эффективности системы предотвращения ботов: это поможет нам сохранить подсчёт отметок «Не нравится» как хороший представитель фактического количества. Конечно, он никогда не будет точным на 100%, поэтому вы сами решаете, доверять ему или нет.\n\n### Почему бы вам не поделиться кодом внутренней части своего сервера?\n\nМы поделимся им в какой-то момент - но сейчас нет никаких реальных причин делиться ею. Это даёт ложное чувство безопасности - ведь в системе с нулевым доверием мы можем с таким же успехом раскрыть одну версию, но развернуть другую. Есть много причин держать код в тайне, в частности, как мы боремся со спамом. Скрытие и запутывание кода обработки спама - это довольно стандартная практика.\n"
  },
  {
    "path": "Docs/SECURITY-FAQtr.md",
    "content": "Read this in other languages: [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Güvenlik\n\n### İzleme geçmişimi takip ediyor musunuz?\n\nHayır. Uzantının kodu herkese açıktır ve kendiniz görebilirsiniz. Gönderilen tek bilgi, videolar için dislike sayısını almak için gereken video kimliğidir. Gönderilen başka bir ek header yoktur. İletişim katmanı üzerinden, genel IP'niz sunucuya ve isteğin yapıldığı zamana maruz kalacaktır. Ancak, bunların hiçbiri sizi hiçbir şekilde benzersiz bir şekilde tanımlamıyor. Sıfır güven ortamını varsayarsak, elde edebileceğimizin en iyisi dinamik bir IP'dir. Ki, bu IP bugün sizin, yarın komşunuzun olabilir. IP'nizin izlenmesinden gerçekten endişeleniyorsanız, muhtemelen zaten bir VPN kullanıyorsunuzdur.\n\n### Bir videoya dislike atarsam, beni benzersiz bir şekilde tanımlayabilir misiniz?\n\nEvet. Bir videoya dislike attığınızda, sizin için Google hesabınızla bağlantılı olmayan rastgele oluşturulmuş benzersiz bir kimlik oluştururuz. Bu, bot kullanılmasını önlemek için yapılır. Ancak bu rastgele kimliği, size veya kişisel YouTube hesabınıza bağlamanın bir yolu yoktur.\n\n### Tam olarak hangi bilgilere sahipsiniz, gerçekten?\n\nSadece video ID'si. Yorumlarınızı değil, kullanıcı adınızı değil, videoyu kiminle paylaştığınız değil, ek meta verilerinden hiçbiri değil. Hiç bir şey. Sadece video ID'si.\n\n### IP adresim nasıl saklanıyor?\n\nBackend, karma olmayan IP adreslerini yalnızca geçici bellekte (RAM'de) tutar. Bu adresler, bir sabit sürücüde depolanmaz ve bu nedenle günlüğe kaydedilmez. IP adreslerini hash ederiz ve bunun yerine depolanır. Bu, veri tabanı vandalizmini önlemek için yapılır.\n\n### OAuth üzerinden YouTube hesabıma erişmek konusunda bazı tartışmalar duydum!\n\nBu özellik isteğe bağlı olacak ve çok fazla tercih edilecek. Bir YouTube içerik üreticisiyseniz ve dislike istatistiklerinizi bizimle paylaşmak istiyorsanız, bunu yapabilirsiniz. [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) yapılandırılma şekli, aslında çok güvenlidir. Hesabınıza erişimi istediğiniz zaman iptal edebilir ve bize çok özel izinler verebilirsiniz. Gerekli olmayan herhangi bir izini istemeyeceğiz. Yalnızca video istatistiklerinizi görüntülemek için izin isteyeceğiz.\n\n### Bu dislike sayısına nasıl güvenebilirim?\n\nBot saldırılarını önlemek için önlemler aldık ve bot önleme sisteminin etkinliğini arttırmak için çalışmaya devam edeceğiz: bu, dislike sayısını gerçek sayının iyi bir temsilcisi olarak tutmamıza yardımcı olacaktır. Tabii ki hiçbir zaman %100 doğru olmayacaktır, bu yüzden sayıma güvenip güvenmemek size kalmıştır.\n\n### Neden backend kodunu paylaşmıyorsunuz?\n\nBir noktada paylaşacağız - ama şu anda paylaşmak için ortada gerçekten gerçek bir sebep yok. Yanlış bir güvenlik hissi verebilir - çünkü sıfır güvenli bir sistemde, bir sürümü ifşa edebilir, ancak bir başkasını devreye alabiliriz. Özellikle spam ile nasıl mücadele ettiğimiz gibi, kodu gizli tutmak için birçok neden vardır. İstenmeyen posta işleme kodunu örtmek/gizlemek oldukça standart bir uygulamadır.\n"
  },
  {
    "path": "Docs/SECURITY-FAQuk.md",
    "content": "Read this in other languages: [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md), [العربية](SECURITY-FAQar.md), [Bahasa Indonesia](SECURITY-FAQid.md), [中文](SECURITY-FAQcn.md), [български](SECURITY-FAQbg.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n# Безпека\n\n### Ви відстежуєте мою історію переглядів?\n\nНі. Код розширення знаходиться у відкритому доступі, і ви можете ознайомитись з ним самостійно. Єдина інформація, що передається - це ID відео, який необхідний для отримання даних про кількість відміток. Жодних додаткових заголовків не передається. На комунікаційному рівні серверу буде передано вашу публічну IP-адресу, а також час, коли було зроблено запит. Однак ніщо з цього ніяк не здатно вас ідентифіквати. Припускаючи середовище з нульовою довірою, найцінніше, що ми можемо отримати це динамічний IP. Який сьогодні ваш, а завтра – вашого сусіда. Якщо ви дійсно турбуєтеся про те, що ваш IP може бути відстежений, ви, ймовірно, вже використовуєте якийсь VPN.\n\n### Чи можете ви однозначно ідентифікувати мене, коли я залишаю відмітку «Не подобається»?\n\nТак. Коли ви залишаєте відео позначку «Не подобається», ми створюємо для вас випадковий унікальний ID, який не прив'язаний до вашого облікового запису Google. Це робиться для уникнення атаки ботів. Але немає жодного способу зв'язати цей випадковий ID до вас або вашого особистого облікового запису YouTube.\n\n### Якою саме інформацією ви володієте?\n\nЛише ID відео. Ні ваших коментарів, ні вашого імені користувача, ні того, з ким ви поділилися відео, ні будь-яких інших додаткових метаданих. Нічого. Лише ID відео.\n\n### Як зберігається моя IP-адреса?\n\nВнутрішня частина нашого сервера зберігає нехешовані IP-адреси лише в енергозалежній пам'яті (ОЗП). Ці адреси не зберігаються на жорсткому диску і тому ніде не записані. Ми хешуємо IP-адреси, і вони зберігаються замість інших. Це зроблено для запобігання вандалізму у базі даних.\n\n### Я чув дискусію щодо OAuth і доступу до мого облікового запису YouTube!\n\nЦя функція буде необов'язковою та дуже необхідною. Якщо ви творець YouTube і хочете поділитися з нами кількістю ваших відміток «Не подобається», ви зможете це зробити. За структурою [OAuth](https://uk.wikipedia.org/wiki/OAuth#:~:text=%D0%B1%D0%B5%D0%B7%20%D0%BD%D0%B5%D0%BE%D0%B1%D1%85%D1%96%D0%B4%D0%BD%D0%BE%D1%81%D1%82%D1%96%20%D0%B2%D0%B2%D0%BE%D0%B4%D1%83%20%D1%96%D0%BC%D0%B5%D0%BD%D1%96%20%D0%BA%D0%BE%D1%80%D0%B8%D1%81%D1%82%D1%83%D0%B2%D0%B0%D1%87%D0%B0%20%D1%82%D0%B0%20%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8E) надбезпечний. Ви зможете відкликати доступ до свого облікового запису в будь-яку мить, і зможете дати нам обмежені дозволи. Ми не будемо вимагати жодних дозволів, крім необжідних. Ми будемо вимагати лише дозволи перегляду статистики ваших відео.\n\n### Наскільки достовірна ця кількость відміток «Не подобається»?\n\nМи вжили заходів щодо запобігання атакам ботів і збираємося продовжувати працювати над підвищенням ефективності цієї сисеми надалі: це допоможе нам зберегти підрахунок відміток «Не подобається» як хороше представлення фактичної кількості. Звичайно, він ніколи не буде точним на всі 100%, тож ви самі вирішуєте, довіряти йому чи ні.\n\n### Чому б вам не поділитися внутрішнім кодом вашого сервера?\n\nМи поділимося ним у якийсь момент – але зараз немає жодних причин це робити. Це дасть хибне почуття безпеки - адже в системі з нульовою довірою ми можемо з таким самим успіхом поділитися однією версією, але розгорнути іншу. Є багато причин тримати код у таємниці, зокрема, як ми боремося зі спамом. Приховування та затьмарення коду обробки спаму є доволі стандартною практикою.\n"
  },
  {
    "path": "Docs/SECURITY-FAQvi.md",
    "content": "Đọc bằng các ngôn ngữ khác: [English](SECURITY-FAQ.md), [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Tiếng Việt](SECURITY-FAQvi.md)\n\n\n# Tính Bảo mật\n\n### Tiện ích mở rộng này có theo dõi lịch sử xem vi-đê-ô của tôi không?\n\nKhông. Mã nguồn của tiện ích mở rộng này được cung cấp công khai và bạn có thể kiểm tra mã nguồn nếu muốn. Thông tin duy nhất được tiện ích này gửi đi là ID của vi-đê-ô vì việc truy vấn số lượt đánh giá \"không thích\" cần ID của vi-đê-ô. Không thông tin nào khác được gửi đi. Ở lớp truyền tải, máy chủ sẽ biết được địa chỉ IP của bạn cũng như thời điểm mà truy vấn được tạo. Tuy nhiên, những thông tin này không thể định danh chính xác bạn. Giả sử với môi trường bất tín, chúng tôi cùng lắm chỉ có thể biết được địa chỉ IP động. Địa chỉ IP động là của bạn ngày hôm nay, nhưng sẽ là của người khác vào ngày mai. Nếu bạn lo lắng rằng địa chỉ IP của mình bị theo dõi, bạn có thể sử dụng mạng riêng ảo.\n\n### Nhóm phát triển có thể định danh chính xác tôi không nếu như tôi có đánh giá \"không thích\"?\n\nCó. Khi bạn đưa ra đánh giá \"không thích\" với một vi-đê-ô, chúng tội sẽ tạo ra một số định danh duy nhất ngẫu nhiên cho bạn, nhưng số định danh này không có bất kì mối liên hệ nào với tài khoản Google của bạn. Việc tạo số định danh là nhằm tránh người dùng bot. Không có bất kì cách nào để liên hệ số định danh này với bạn hay với tài khoản YouTube của bạn.\n\n### Chính xác thì thông tin nào được thu thập?\n\nChỉ có ID của vi-đê-ô sẽ được thu thập. Bình luận của bạn, không. Tên người dùng của bạn, không. Thông tin của người mà bạn chia sẻ vi-đê-ô, không. Siêu dữ liệu khác, không. Không có bất kì thông tin nào khác được thu thập. Chỉ duy nhất ID của vi-đê-ô.\n\n### Địa chỉ IP của tôi được lưu trữ ra sao?\n\nĐầu sau chỉ giữ địa chỉ IP không được băm trong bộ nhớ khả biến (RAM). Những địa chỉ này không được lưu vào bộ nhớ bất biến (như ổ đĩa cứng) và như vậy không bị ghi chép lại. Chúng tôi băm địa chỉ IP rồi mới lưu lại những địa chỉ đã băm. Việc lưu địa chỉ đã băm là nhằm tránh việc phá hoại cơ sở dữ liệu.\n\n### Tôi có bắt gặp một số cuộc thảo luận về OAuth cũng như về việc truy cập tài khoản YouTube của tôi!\n\nTính năng sắp có này là tùy chọn. Nếu bạn là nhà sáng tạo nội dung trên YouTube và bạn mong muốn chia sẽ số liệu đánh giá \"không thích\" với chúng tôi, chúng tôi rất hoan nghênh. Cách thức hoạt động của [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) giúp đảm bảo tính bảo mật. Bạn có thể hủy bỏ các truy cập vào tài khoản của bạn bất cứ lúc nào bạn muốn và cũng như có thể đưa ra bất kì sự cho phép cụ thể nào. Chúng tôi sẽ không đòi hỏi bất kì sự cho phép nào không cần thiết. Chúng tôi chỉ xin được cấp phép để xem số liệu về vi-đê-ô của bạn.\n\n### Số lượt đánh giá \"không thích\" có đáng tin cậy không?\n\nChúng tôi đã và đang thực hiện nhiều biện pháp phòng chống việc tấn công từ người dùng bot và sẽ tiếp duy trì việc tăng cường tính hiệu quả của hệ thống phòng chống bot: Điều này sẽ giúp chúng tôi đảm bảo rằng số lượt đánh giá \"không thích\" của tiện ích phản ánh đúng số lượt đánh giá thực. Tất nhiên là không thể đảm bảo chính xác hoàn toàn, cho nên tùy thuộc vào bạn có muốn tin hay không.\n\n### Tại sao mã nguồn của đầu sau không được chia sẻ?\n\nChúng tôi sẽ chia sẻ mã nguồn của đầu sau trong tương lại. Nhưng không có lí do gì thực sự cấp thiết để phải chia sẻ mã nguồn vào thời điểm hiện nay. Việc chia sẻ mã nguồn sẽ tạo ra sự an tâm hão huyền vì trong môi trường bất tín, chúng tôi có thể chia sẻ mã nguồn này nhưng lại dùng mã nguồn khác. Có nhiều lí do để giữ bí mật mã nguồn, trong đó có việc chống tương tác rác. Che giấu/Làm rối mã xử lý tương tác rác là biện pháp tiêu chuẩn.\n"
  },
  {
    "path": "Docs/SECURITY_FAQar.md",
    "content": "اقرأ هذا بلغات أخرى: [русский](SECURITY-FAQru.md), [Nederlands](SECURITY_FAQnl.md), [Français](SECURITY-FAQfr.md), [Türkçe](SECURITY-FAQtr.md), [українська](SECURITY-FAQuk.md), [Polski](SECURITY-FAQpl.md), [Deutsch](SECURITY-FAQde.md)\n\n# الأمان\n\n### هل تقومون بتتبع سجل المشاهدة الخاص بي؟\n\nلا. كود الإضافة عام ويمكنك رؤيته بنفسك. المعلومات الوحيدة التي يتم إرسالها هي معرف الفيديو، وهو مطلوب لجلب عدد عدم الإعجاب للفيديوهات. لا يتم إرسال أي رؤوس إضافية. عبر طبقة الاتصال، سيتم كشف عنوان IP العام الخاص بك للخادم، وكذلك الوقت الذي تم فيه الطلب. ومع ذلك، لا يمكن لأي من هذه المعلومات تحديد هويتك بشكل فريد بأي طريقة. بافتراض بيئة عدم الثقة، أفضل ما يمكننا الحصول عليه هو عنوان IP ديناميكي. والذي، اليوم هو لك، وغدًا هو لجارك. إذا كنت قلقًا حقًا بشأن تتبع عنوان IP الخاص بك، فمن المحتمل أنك تستخدم VPN بالفعل.\n\n### هل يمكنكم تحديد هويتي بشكل فريد إذا قمت بعدم الإعجاب؟\n\nنعم. عندما تقوم بعدم الإعجاب بفيديو، نقوم بإنشاء معرف فريد عشوائي لك غير مرتبط بحساب Google الخاص بك. يتم ذلك لمنع التلاعب. ولكن لا توجد طريقة لربط هذا المعرف العشوائي بك أو بحساب YouTube الشخصي الخاص بك.\n\n### ما هي المعلومات التي لديكم بالضبط؟\n\nفقط معرف الفيديو. ليس تعليقاتك، وليس اسم المستخدم الخاص بك، وليس من شاركت الفيديو معه، وليس أي بيانات إضافية. لا شيء. فقط معرف الفيديو.\n\n### كيف يتم تخزين عنوان IP الخاص بي؟\n\nيحتفظ الخلفية بعناوين IP غير المشفرة في الذاكرة المتطايرة (RAM) فقط. هذه العناوين لا يتم تخزينها على القرص الصلب، وبالتالي لا يتم تسجيلها. نقوم بتشفير عناوين IP، ويتم تخزين هذا التشفير بدلاً من ذلك. يتم ذلك لمنع تخريب قاعدة البيانات.\n\n### سمعت بعض النقاش حول OAuth والوصول إلى حساب YouTube الخاص بي!\n\nهذه الميزة ستكون اختيارية، وبشكل كبير تعتمد على الموافقة. إذا كنت منشئ محتوى على YouTube، وترغب في مشاركة إحصائيات عدم الإعجاب الخاصة بك معنا، يمكنك ذلك. الطريقة التي تم بها هيكلة [OAuth](https://en.wikipedia.org/wiki/OAuth#:~:text=but%20without%20giving%20them%20the%20passwords.) آمنة جدًا في الواقع. يمكنك إلغاء الوصول إلى حسابك في أي وقت، ويمكنك منحنا أذونات محددة جدًا. لن نطلب أي أذونات غير مطلوبة. سنطلب فقط الأذونات لعرض إحصائيات الفيديو الخاصة بك.\n\n### كيف يمكنني الوثوق بعدد عدم الإعجاب هذا؟\n\nلقد قمنا بتنفيذ تدابير لمنع هجمات الروبوتات وسنواصل العمل على تحسين فعالية نظام منع الروبوتات: سيساعدنا ذلك في الحفاظ على عدد عدم الإعجاب كممثل جيد للعدد الفعلي. بالطبع لن يكون دقيقًا بنسبة 100% لذا الأمر متروك لك لتقرر ما إذا كنت تثق في العدد أم لا.\n\n### لماذا لا تشاركون كود الخلفية؟\n\nسنشاركه في وقت ما - ولكن لا يوجد سبب حقيقي لمشاركته الآن. إنه يعطي إحساسًا زائفًا بالأمان - لأنه في نظام عدم الثقة، يمكننا الكشف عن إصدار واحد ولكن نشر آخر. هناك العديد من الأسباب للحفاظ على الكود مخفيًا، تحديدًا، كيفية مكافحة البريد العشوائي. إخفاء/تشويش كود معالجة البريد العشوائي هو ممارسة قياسية إلى حد ما."
  },
  {
    "path": "Docs/readme.md",
    "content": "Read this in other languages: [Nederlands](readmenl.md), [Français](readmefr.md), [Türkçe](readmetr.md), [Bahasa Indonesia](readmeid.md), [中文](readmecn.md), [Tiếng Việt](readmevi.md)\n\n**Contents**\n\n- [Guides](#guides)\n- [FAQs](#faqs)\n<!-- - [FAQs](#faqs)\n- [Other Lists](#other-lists) -->\n\n<br>\n\n## Guides\n\n- [Downloading, Installing & Using](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [Troubleshooting](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [FAQ](FAQ.md)\n- [When & How to Report Bugs](Guide__Bug_Reporting.md)\n- [Contributing](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md) -->\n<!-- - [How to update wiki](/) -->\n\n<br>\n\n## FAQs\n\n- [General](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n- [Security](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md)\n\n<!-- - [Privacy](FAQ_Privacy.md)\n- [Technical](FAQ_Technical.md)\n- [Creators](FAQ_Creators.md)\n\n<br>\n\n## Other Lists\n\n- [Common Problems](Common_Problems.md)\n- [Repeated Questions](Repeated_Questions.md)\n- [Repeated Feature requests](Repeated_Feature_requests.md)\n- [Repeated Issues](Repeated_Issues.md) -->\n"
  },
  {
    "path": "Docs/readmeaz.md",
    "content": "Read this in other languages: [English](readme.md), [Nederlands](readmenl.md), [Français](readmefr.md)\n\n**İçerikler**\n\n- [Bələdçilər](#rehberler)\n- [Tez-tez verilən suallar](#sss'ler)\n<!-- - [Tez-tez verilən suallar](#sss'ler)\n- [Digər Siyahılar](#diğer-listeler) -->\n\n<br>\n\n## Bələdçilər\n\n- [Yükləyin, quraşdırın və istifadə edin](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [Problemlərin aradan qaldırılması](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [Tez-tez verilən suallar](FAQaz.md)\n- [Bugları nə vaxt və necə bildirmək olar](Guide__Bug_Reporting.md)\n- [Töhfə](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGtr.md) -->\n<!-- - [Vikini necə yeniləmək olar](/) -->\n\n<br>\n\n## Tez-tez verilən suallar\n\n- [Ümumi](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQaz.md)\n- [Təhlükəsizlik](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQaz.md)\n\n<!-- - [Təhlükəsizlik](FAQ_Privacy.md)\n- [Texniki](FAQ_Technical.md)\n- [İstehsalçılar](FAQ_Creators.md)\n\n<br>\n\n## Digər Siyahılar\n\n- [Common Problems](Common_Problems.md)\n- [Repeated Questions](Repeated_Questions.md)\n- [Repeated Feature requests](Repeated_Feature_requests.md)\n- [Repeated Issues](Repeated_Issues.md) -->\n"
  },
  {
    "path": "Docs/readmecn.md",
    "content": "阅读其他语言版本：[English](readme.md), [Nederlands](readmenl.md), [Français](readmefr.md), [Türkçe](readmetr.md)\n\n**目录**\n\n- [指南](#guides)\n- [常见问题](#faqs)\n<!-- - [常见问题](#faqs)\n- [其他列表](#other-lists) -->\n\n<br>\n\n## 指南\n\n- [下载、安装和使用](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [故障排除](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [常见问题](FAQ.md)\n- [何时及如何报告错误](Guide__Bug_Reporting.md)\n- [贡献](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md) -->\n<!-- - [如何更新 wiki](/) -->\n\n<br>\n\n## 常见问题\n\n- [常规](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQcn.md)\n- [安全](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQcn.md)\n\n<!-- - [隐私](FAQ_Privacy.md)\n- [技术](FAQ_Technical.md)\n- [创作者](FAQ_Creators.md)\n\n<br>\n\n## 其他列表\n\n- [常见问题](Common_Problems.md)\n- [重复的问题](Repeated_Questions.md)\n- [重复的功能请求](Repeated_Feature_requests.md)\n- [重复的问题](Repeated_Issues.md) -->"
  },
  {
    "path": "Docs/readmefr.md",
    "content": "Lisez ceci dans d'autres langues : [English](readme.md), [Nederlands](readmenl.md), [Türkçe](readmetr.md), [Bahasa Indonesia](readmeid.md), [中文](readmecn.md), [Tiếng Việt](readmevi.md)\n\n**Contenu**\n\n- [Guides](#guides)\n- [FAQ](#faq)\n<!-- - [Autres listes](#autres-listes) -->\n\n<br>\n\n## Guides\n\n- [Téléchargement, installation et utilisation](https://github.com/Anarios/return-youtube-dislike/wiki/T%C3%A9l%C3%A9chargement,-installation-et-utilisation)\n- [Dépannage](https://github.com/Anarios/return-youtube-dislike/wiki/Guide-de-d%C3%A9pannage)\n<!-- - [FAQ](FAQfr.md)\n- [Quand et comment signaler les bugs](Guide__Bug_Reportingfr.md)\n- [Contribution](/CONTRIBUTINGfr.md) -->\n<!-- - [Comment mettre à jour le wiki](/) -->\n\n<br>\n\n## FAQ\n\n- [Général](FAQfr.md)\n- [Sécurité](SECURITY-FAQfr.md)\n\n<!-- - [Vie privée](FAQ_Privacyfr.md)\n- [Technique](FAQ_Technicalfr.md)\n- [Créateurs](FAQ_Creatorsfr.md)\n\n<br>\n\n## Autres listes\n\n- [Problèmes courants](Common_Problemsfr.md)\n- [Questions répétitives](Repeated_Questionsfr.md)\n- [Demandes répétitives de fonctionnalités](Repeated_Feature_requestsfr.md)\n- [Questions répétitives](Repeated_Issuesfr.md) -->\n"
  },
  {
    "path": "Docs/readmeid.md",
    "content": "Baca ini dibahasa lain: [Nederlands](readmenl.md), [Français](readmefr.md), [Türkçe](readmetr.md)\n\n**Isi**\n\n- [Petunjuk](#petunjuk)\n- [Pertanyaan yang Sering Ditanyakan](#pertanyaan-yang-sering-ditanyakan)\n<!-- - [FAQs](#faqs)\n- [Other Lists](#other-lists) -->\n\n<br>\n\n## Petunjuk\n\n- [Mengunduh, Menginstall, dan Menggunakan](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [Troubleshooting](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [FAQ](FAQ.md)\n- [When & How to Report Bugs](Guide__Bug_Reporting.md)\n- [Contributing](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md) -->\n<!-- - [How to update wiki](/) -->\n\n<br>\n\n## Pertanyaan yang Sering Ditanyakan\n\n- [Umum](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n- [Keamanan](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md)\n\n<!-- - [Privacy](FAQ_Privacy.md)\n- [Technical](FAQ_Technical.md)\n- [Creators](FAQ_Creators.md)\n\n<br>\n\n## Other Lists\n\n- [Common Problems](Common_Problems.md)\n- [Repeated Questions](Repeated_Questions.md)\n- [Repeated Feature requests](Repeated_Feature_requests.md)\n- [Repeated Issues](Repeated_Issues.md) -->\n"
  },
  {
    "path": "Docs/readmenl.md",
    "content": "Read this in other languages: [English](readme.md), [Français](readmefr.md), [Türkçe](readmetr.md), [Bahasa Indonesia](readmeid.md), [中文](readmecn.md), [Tiếng Việt](readmevi.md)\n\n**Inhoud**\n\n- [Gidsen](#gidsen)\n- [FAQs](#faqs)\n<!-- - [FAQs](#faqs)\n- [Andere lijsten](#andere-lijsten) -->\n\n<br>\n\n## Gidsen\n\n- [Downloaden, installeren en gebruiken](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [Probleemoplossen](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [FAQ](FAQ.md)\n- [Wanneer en hoe bugs te melden](Guide__Bug_Reporting.md)\n- [Bijdragend](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md) -->\n<!-- - [Hoe de wiki te updaten](/) -->\n\n<br>\n\n## FAQs\n\n- [Algemeen](https://github.com/Anarios/return-youtube-dislike/blob/main/Guides/FAQ.md)\n- [Beveiliging](https://github.com/Anarios/return-youtube-dislike/blob/main/Guides/SECURITY-FAQ.md)\n\n<!-- - [Privacy](FAQ_Privacy.md)\n- [Techisch](FAQ_Technical.md)\n- [Makers](FAQ_Creators.md)\n\n<br>\n\n## Andere lijsten\n\n- [Veel voorkomende problemen](Common_Problems.md)\n- [Herhalende vragen](Repeated_Questions.md)\n- [Herhaalde functieverzoeken](Repeated_Feature_requests.md)\n- [Herhaalde problemen](Repeated_Issues.md) -->\n"
  },
  {
    "path": "Docs/readmept_BR.md",
    "content": "Leia isso em outros idiomas: [Nederlands](readmenl.md), [Français](readmefr.md), [Türkçe](readmetr.md), [Bahasa Indonesia](readmeid.md), [中文](readmecn.md)\n\n**Contents**\n\n- [Guides](#guides)\n- [FAQs](#faqs)\n<!-- - [FAQs](#faqs)\n- [Other Lists](#other-lists) -->\n\n<br>\n\n## Guides\n\n- [Downloading, Installing & Using](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [Troubleshooting](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [FAQ](FAQ.md)\n- [When & How to Report Bugs](Guide__Bug_Reporting.md)\n- [Contributing](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md) -->\n<!-- - [How to update wiki](/) -->\n\n<br>\n\n## FAQs\n\n- [General](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n- [Security](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md)\n\n<!-- - [Privacy](FAQ_Privacy.md)\n- [Technical](FAQ_Technical.md)\n- [Creators](FAQ_Creators.md)\n\n<br>\n\n## Other Lists\n\n- [Common Problems](Common_Problems.md)\n- [Repeated Questions](Repeated_Questions.md)\n- [Repeated Feature requests](Repeated_Feature_requests.md)\n- [Repeated Issues](Repeated_Issues.md) -->\n"
  },
  {
    "path": "Docs/readmetr.md",
    "content": "Read this in other languages: [English](readme.md), [Nederlands](readmenl.md), [Français](readmefr.md), [Bahasa Indonesia](readmeid.md), [中文](readmecn.md), [Tiếng Việt](readmevi.md)\n\n**İçerikler**\n\n- [Rehberler](#rehberler)\n- [SSS'ler](#sss'ler)\n<!-- - [SSS'ler](#sss'ler)\n- [Diğer Listeler](#diğer-listeler) -->\n\n<br>\n\n## Rehberler\n\n- [İndirme, Yükleme ve Kullanma](https://github.com/Anarios/return-youtube-dislike/wiki/Downloading,-Installing-&-Using)\n- [Sorun Giderme](https://github.com/Anarios/return-youtube-dislike/wiki/Troubleshooting-Guide)\n<!-- - [SSS](FAQtr.md)\n- [Hata Raporları Ne Zaman ve Nasıl Bildirilir](Guide__Bug_Reporting.md)\n- [Katkı sağlama](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGtr.md) -->\n<!-- - [Wiki nasıl güncellenir](/) -->\n\n<br>\n\n## SSS'ler\n\n- [Genel](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQtr.md)\n- [Güvenlik](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQtr.md)\n\n<!-- - [Gizlilik](FAQ_Privacy.md)\n- [Teknik](FAQ_Technical.md)\n- [Üreticiler](FAQ_Creators.md)\n\n<br>\n\n## Diğer Listeler\n\n- [Common Problems](Common_Problems.md)\n- [Repeated Questions](Repeated_Questions.md)\n- [Repeated Feature requests](Repeated_Feature_requests.md)\n- [Repeated Issues](Repeated_Issues.md) -->\n"
  },
  {
    "path": "Docs/readmevi.md",
    "content": "Đọc bằng các ngôn ngữ khác: [English](readme.md), [Nederlands](readmenl.md), [Français](readmefr.md), [Türkçe](readmetr.md)\n\n**Mục lục** <!-- Contents -->\n\n- [Hướng dẫn](#guides)\n- [Hỏi-Đáp](#faqs)\n<!-- - [FAQs](#faqs)\n- [Nội dung Khác](#other-lists) -->\n\n<br>\n\n## Hướng dẫn <!-- ## Guides -->\n\n- [Tải, Cài đặt & Sử dụng](https://github.com/Anarios/return-youtube-dislike/wiki/Tải,-Cài-đặt-&-Sử-dụng)\n- [Khắc phục Sự cố](https://github.com/Anarios/return-youtube-dislike/wiki/Hướng-dẫn-Khắc-phục-Sự-cố)\n<!-- - [Hỏi-Đáp](FAQvi.md)\n- [Khi nào và Làm sao Báo cáo Lỗi](Guide__Bug_Reportingvi.md)\n- [Đóng góp](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGvi.md) -->\n<!-- - [Cách cập nhật wiki](/) -->\n\n<br>\n\n## Hỏi-Đáp <!-- ## FAQs -->\n\n- [Tổng quát](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQvi.md)\n- [Bảo mật](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQvi.md)\n\n<!-- - [Quyền Riêng tư](FAQ_Privacyvi.md)\n- [Kỹ thuật](FAQ_Technicalvi.md)\n- [Nhà Sáng tạo Nội dung](FAQ_Creatorsvi.md)\n\n<br>\n\n## Nội dung Khác [!-- ## Other Lists --]\n\n- [Vấn đề Thường gặp](Common_Problemsvi.md)\n- [Câu hỏi Lặp đi lặp lại](Repeated_Questionsvi.md)\n- [Yêu cầu tính năng Lặp đi lặp lại](Repeated_Feature_requestsvi.md)\n- [Vấn đề Lặp đi lặp lại](Repeated_Issuesvi.md) -->\n"
  },
  {
    "path": "Extensions/UserScript/Return Youtube Dislike.user.js",
    "content": "// ==UserScript==\n// @name         Return YouTube Dislike\n// @namespace    https://www.returnyoutubedislike.com/\n// @homepage     https://www.returnyoutubedislike.com/\n// @version      3.1.5\n// @encoding     utf-8\n// @description  Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/\n// @icon         https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png\n// @author       Anarios & JRWR\n// @match        *://*.youtube.com/*\n// @exclude      *://music.youtube.com/*\n// @exclude      *://*.music.youtube.com/*\n// @compatible   chrome\n// @compatible   firefox\n// @compatible   opera\n// @compatible   safari\n// @compatible   edge\n// @downloadURL  https://github.com/Anarios/return-youtube-dislike/raw/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js\n// @updateURL    https://github.com/Anarios/return-youtube-dislike/raw/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js\n// @grant        GM.xmlHttpRequest\n// @connect      youtube.com\n// @grant        GM_addStyle\n// @run-at       document-end\n// ==/UserScript==\n\nconst extConfig = {\n  // BEGIN USER OPTIONS\n  // You may change the following variables to allowed values listed in the corresponding brackets (* means default). Keep the style and keywords intact.\n  showUpdatePopup: false, // [true, false*] Show a popup tab after extension update (See what's new)\n  disableVoteSubmission: false, // [true, false*] Disable like/dislike submission (Stops counting your likes and dislikes)\n  disableLogging: true, // [true*, false] Disable Logging API Response in JavaScript Console.\n  coloredThumbs: false, // [true, false*] Colorize thumbs (Use custom colors for thumb icons)\n  coloredBar: false, // [true, false*] Colorize ratio bar (Use custom colors for ratio bar)\n  colorTheme: \"classic\", // [classic*, accessible, neon] Color theme (red/green, blue/yellow, pink/cyan)\n  numberDisplayFormat: \"compactShort\", // [compactShort*, compactLong, standard] Number format (For non-English locale users, you may be able to improve appearance with a different option. Please file a feature request if your locale is not covered)\n  numberDisplayRoundDown: true, // [true*, false] Round down numbers (Show rounded down numbers)\n  tooltipPercentageMode: \"none\", // [none*, dash_like, dash_dislike, both, only_like, only_dislike] Mode of showing percentage in like/dislike bar tooltip.\n  numberDisplayReformatLikes: false, // [true, false*] Re-format like numbers (Make likes and dislikes format consistent)\n  rateBarEnabled: false, // [true, false*] Enables ratio bar under like/dislike buttons\n  // END USER OPTIONS\n};\n\nconst LIKED_STATE = \"LIKED_STATE\";\nconst DISLIKED_STATE = \"DISLIKED_STATE\";\nconst NEUTRAL_STATE = \"NEUTRAL_STATE\";\nlet previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL\nlet likesvalue = 0;\nlet dislikesvalue = 0;\nlet preNavigateLikeButton = null;\n\nlet isMobile = location.hostname == \"m.youtube.com\";\nlet isShorts = () => location.pathname.startsWith(\"/shorts\");\nlet mobileDislikes = 0;\nfunction cLog(text, subtext = \"\") {\n  if (!extConfig.disableLogging) {\n    subtext = subtext.trim() === \"\" ? \"\" : `(${subtext})`; \n    console.log(`[Return YouTube Dislikes] ${text} ${subtext}`); \n  }\n}\n\nfunction isInViewport(element) {\n  const rect = element.getBoundingClientRect();\n  const height = innerHeight || document.documentElement.clientHeight;\n  const width = innerWidth || document.documentElement.clientWidth;\n  return (\n    // When short (channel) is ignored, the element (like/dislike AND short itself) is\n    // hidden with a 0 DOMRect. In this case, consider it outside of Viewport\n    !(rect.top == 0 && rect.left == 0 && rect.bottom == 0 && rect.right == 0) &&\n    rect.top >= 0 &&\n    rect.left >= 0 &&\n    rect.bottom <= height &&\n    rect.right <= width\n  );\n}\n\nfunction getButtons() {\n  if (isShorts()) {\n    let elements = document.querySelectorAll(\n      isMobile ? \"ytm-like-button-renderer\" : \"#like-button > ytd-like-button-renderer\",\n    );\n    for (let element of elements) {\n      if (isInViewport(element)) {\n        return element;\n      }\n    }\n  }\n  if (isMobile) {\n    return (\n      document.querySelector(\".slim-video-action-bar-actions .segmented-buttons\") ??\n      document.querySelector(\".slim-video-action-bar-actions\")\n    );\n  }\n  if (document.getElementById(\"menu-container\")?.offsetParent === null) {\n    return (\n      document.querySelector(\"ytd-menu-renderer.ytd-watch-metadata > div\") ??\n      document.querySelector(\"ytd-menu-renderer.ytd-video-primary-info-renderer > div\")\n    );\n  } else {\n    return document.getElementById(\"menu-container\")?.querySelector(\"#top-level-buttons-computed\");\n  }\n}\n\nfunction getDislikeButton() {\n  if (getButtons().children[0].tagName === \"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER\") {\n    if (getButtons().children[0].children[1] === undefined) {\n      return document.querySelector(\"#segmented-dislike-button\");\n    } else {\n      return getButtons().children[0].children[1];\n    }\n  } else {\n    if (getButtons().querySelector(\"segmented-like-dislike-button-view-model\")) {\n      const dislikeViewModel = getButtons().querySelector(\"dislike-button-view-model\");\n      if (!dislikeViewModel) cLog(\"Dislike button wasn't added to DOM yet...\");\n      return dislikeViewModel;\n    } else {\n      return getButtons().children[1];\n    }\n  }\n}\n\nfunction getLikeButton() {\n  return getButtons().children[0].tagName === \"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER\"\n    ? document.querySelector(\"#segmented-like-button\") !== null\n      ? document.querySelector(\"#segmented-like-button\")\n      : getButtons().children[0].children[0]\n    : getButtons().querySelector(\"like-button-view-model\") ?? getButtons().children[0];\n}\n\nfunction getLikeTextContainer() {\n  return (\n    getLikeButton().querySelector(\"#text\") ??\n    getLikeButton().getElementsByTagName(\"yt-formatted-string\")[0] ??\n    getLikeButton().querySelector(\"span[role='text']\")\n  );\n}\n\nfunction getDislikeTextContainer() {\n  const dislikeButton = getDislikeButton();\n  let result =\n    dislikeButton?.querySelector(\"#text\") ??\n    dislikeButton?.getElementsByTagName(\"yt-formatted-string\")[0] ??\n    dislikeButton?.querySelector(\"span[role='text']\");\n  if (result === null) {\n    let textSpan = document.createElement(\"span\");\n    textSpan.id = \"text\";\n    textSpan.style.marginLeft = \"6px\";\n    dislikeButton?.querySelector(\"button\").appendChild(textSpan);\n    if (dislikeButton) dislikeButton.querySelector(\"button\").style.width = \"auto\";\n    result = textSpan;\n  }\n  return result;\n}\n\nfunction createObserver(options, callback) {\n  const observerWrapper = new Object();\n  observerWrapper.options = options;\n  observerWrapper.observer = new MutationObserver(callback);\n  observerWrapper.observe = function (element) {\n    this.observer.observe(element, this.options);\n  };\n  observerWrapper.disconnect = function () {\n    this.observer.disconnect();\n  };\n  return observerWrapper;\n}\n\nlet shortsObserver = null;\n\nif (isShorts() && !shortsObserver) {\n  cLog(\"Initializing shorts mutation observer\");\n  shortsObserver = createObserver(\n    {\n      attributes: true,\n    },\n    (mutationList) => {\n      mutationList.forEach((mutation) => {\n        if (\n          mutation.type === \"attributes\" &&\n          mutation.target.nodeName === \"TP-YT-PAPER-BUTTON\" &&\n          mutation.target.id === \"button\"\n        ) {\n          cLog(\"Short thumb button status changed\");\n          if (mutation.target.getAttribute(\"aria-pressed\") === \"true\") {\n            mutation.target.style.color =\n              mutation.target.parentElement.parentElement.id === \"like-button\"\n                ? getColorFromTheme(true)\n                : getColorFromTheme(false);\n          } else {\n            mutation.target.style.color = \"unset\";\n          }\n          return;\n        }\n        cLog(\"Unexpected mutation observer event: \" + mutation.target + mutation.type);\n      });\n    },\n  );\n}\n\nfunction isVideoLiked() {\n  if (isMobile) {\n    return getLikeButton().querySelector(\"button\").getAttribute(\"aria-label\") == \"true\";\n  }\n  return getLikeButton().classList.contains(\"style-default-active\");\n}\n\nfunction isVideoDisliked() {\n  if (isMobile) {\n    return getDislikeButton()?.querySelector(\"button\").getAttribute(\"aria-label\") == \"true\";\n  }\n  return getDislikeButton()?.classList.contains(\"style-default-active\");\n}\n\nfunction isVideoNotLiked() {\n  if (isMobile) {\n    return !isVideoLiked();\n  }\n  return getLikeButton().classList.contains(\"style-text\");\n}\n\nfunction isVideoNotDisliked() {\n  if (isMobile) {\n    return !isVideoDisliked();\n  }\n  return getDislikeButton()?.classList.contains(\"style-text\");\n}\n\nfunction checkForUserAvatarButton() {\n  if (isMobile) {\n    return;\n  }\n  if (document.querySelector(\"#avatar-btn\")) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\nfunction getState() {\n  if (isVideoLiked()) {\n    return LIKED_STATE;\n  }\n  if (isVideoDisliked()) {\n    return DISLIKED_STATE;\n  }\n  return NEUTRAL_STATE;\n}\n\nfunction setLikes(likesCount) {\n  if (isMobile) {\n    getButtons().children[0].querySelector(\".button-renderer-text\").innerText = likesCount;\n    return;\n  }\n  getLikeTextContainer().innerText = likesCount;\n}\n\nfunction setDislikes(dislikesCount) {\n  if (isMobile) {\n    mobileDislikes = dislikesCount;\n    return;\n  }\n\n  const _container = getDislikeTextContainer();\n  _container?.removeAttribute(\"is-empty\");\n  if (_container?.innerText !== dislikesCount) {\n    _container.innerText = dislikesCount;\n  }\n}\n\nfunction getLikeCountFromButton() {\n  try {\n    if (isShorts()) {\n      //Youtube Shorts don't work with this query. It's not necessary; we can skip it and still see the results.\n      //It should be possible to fix this function, but it's not critical to showing the dislike count.\n      return false;\n    }\n    let likeButton =\n      getLikeButton().querySelector(\"yt-formatted-string#text\") ?? getLikeButton().querySelector(\"button\");\n\n    let likesStr = likeButton.getAttribute(\"aria-label\").replace(/\\D/g, \"\");\n    return likesStr.length > 0 ? parseInt(likesStr) : false;\n  } catch {\n    return false;\n  }\n}\n\n(typeof GM_addStyle != \"undefined\"\n  ? GM_addStyle\n  : (styles) => {\n      let styleNode = document.createElement(\"style\");\n      styleNode.type = \"text/css\";\n      styleNode.innerText = styles;\n      document.head.appendChild(styleNode);\n    })(`\n    #return-youtube-dislike-bar-container {\n      background: var(--yt-spec-icon-disabled);\n      border-radius: 2px;\n    }\n\n    #return-youtube-dislike-bar {\n      background: var(--yt-spec-text-primary);\n      border-radius: 2px;\n      transition: all 0.15s ease-in-out;\n    }\n\n    .ryd-tooltip {\n      position: absolute;\n      display: block;\n      height: 2px;\n      bottom: -10px;\n    }\n\n    .ryd-tooltip-bar-container {\n      width: 100%;\n      height: 2px;\n      position: absolute;\n      padding-top: 6px;\n      padding-bottom: 12px;\n      top: -6px;\n    }\n\n    ytd-menu-renderer.ytd-watch-metadata {\n      overflow-y: visible !important;\n    }\n    \n    #top-level-buttons-computed {\n      position: relative !important;\n    }\n  `);\n\nfunction createRateBar(likes, dislikes) {\n  if (isMobile || !extConfig.rateBarEnabled) {\n    return;\n  }\n  let rateBar = document.getElementById(\"return-youtube-dislike-bar-container\");\n\n  const widthPx = getLikeButton().clientWidth + (getDislikeButton()?.clientWidth ?? 52);\n\n  const widthPercent = likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;\n\n  var likePercentage = parseFloat(widthPercent.toFixed(1));\n  const dislikePercentage = (100 - likePercentage).toLocaleString();\n  likePercentage = likePercentage.toLocaleString();\n\n  var tooltipInnerHTML;\n  switch (extConfig.tooltipPercentageMode) {\n    case \"dash_like\":\n      tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}&nbsp;&nbsp;-&nbsp;&nbsp;${likePercentage}%`;\n      break;\n    case \"dash_dislike\":\n      tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}&nbsp;&nbsp;-&nbsp;&nbsp;${dislikePercentage}%`;\n      break;\n    case \"both\":\n      tooltipInnerHTML = `${likePercentage}%&nbsp;/&nbsp;${dislikePercentage}%`;\n      break;\n    case \"only_like\":\n      tooltipInnerHTML = `${likePercentage}%`;\n      break;\n    case \"only_dislike\":\n      tooltipInnerHTML = `${dislikePercentage}%`;\n      break;\n    default:\n      tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;\n  }\n\n  if (!rateBar && !isMobile) {\n    let colorLikeStyle = \"\";\n    let colorDislikeStyle = \"\";\n    if (extConfig.coloredBar) {\n      colorLikeStyle = \"; background-color: \" + getColorFromTheme(true);\n      colorDislikeStyle = \"; background-color: \" + getColorFromTheme(false);\n    }\n\n    getButtons().insertAdjacentHTML(\n      \"beforeend\",\n      `\n        <div class=\"ryd-tooltip\" style=\"width: ${widthPx}px\">\n        <div class=\"ryd-tooltip-bar-container\">\n           <div\n              id=\"return-youtube-dislike-bar-container\"\n              style=\"width: 100%; height: 2px;${colorDislikeStyle}\"\n              >\n              <div\n                 id=\"return-youtube-dislike-bar\"\n                 style=\"width: ${widthPercent}%; height: 100%${colorDislikeStyle}\"\n                 ></div>\n           </div>\n        </div>\n        <tp-yt-paper-tooltip position=\"top\" id=\"ryd-dislike-tooltip\" class=\"style-scope ytd-sentiment-bar-renderer\" role=\"tooltip\" tabindex=\"-1\">\n           <!--css-build:shady-->${tooltipInnerHTML}\n        </tp-yt-paper-tooltip>\n        </div>\n`,\n    );\n    let descriptionAndActionsElement = document.getElementById(\"top-row\");\n    descriptionAndActionsElement.style.borderBottom = \"1px solid var(--yt-spec-10-percent-layer)\";\n    descriptionAndActionsElement.style.paddingBottom = \"10px\";\n  } else {\n    document.querySelector(\".ryd-tooltip\").style.width = widthPx + \"px\";\n    document.getElementById(\"return-youtube-dislike-bar\").style.width = widthPercent + \"%\";\n\n    if (extConfig.coloredBar) {\n      document.getElementById(\"return-youtube-dislike-bar-container\").style.backgroundColor = getColorFromTheme(false);\n      document.getElementById(\"return-youtube-dislike-bar\").style.backgroundColor = getColorFromTheme(true);\n    }\n  }\n}\n\nfunction setState() {\n  cLog(\"Fetching votes...\");\n  let statsSet = false;\n\n  fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`).then((response) => {\n    response.json().then((json) => {\n      if (json && !(\"traceId\" in response) && !statsSet) {\n        const { dislikes, likes } = json;\n        cLog(`Received count: ${dislikes}`);\n        likesvalue = likes;\n        dislikesvalue = dislikes;\n        setDislikes(numberFormat(dislikes));\n        if (extConfig.numberDisplayReformatLikes === true) {\n          const nativeLikes = getLikeCountFromButton();\n          if (nativeLikes !== false) {\n            setLikes(numberFormat(nativeLikes));\n          }\n        }\n        createRateBar(likes, dislikes);\n        if (extConfig.coloredThumbs === true) {\n          const dislikeButton = getDislikeButton();\n          if (isShorts()) {\n            // for shorts, leave deactived buttons in default color\n            const shortLikeButton = getLikeButton().querySelector(\"tp-yt-paper-button#button\");\n            const shortDislikeButton = dislikeButton?.querySelector(\"tp-yt-paper-button#button\");\n            if (shortLikeButton.getAttribute(\"aria-pressed\") === \"true\") {\n              shortLikeButton.style.color = getColorFromTheme(true);\n            }\n            if (shortDislikeButton && shortDislikeButton.getAttribute(\"aria-pressed\") === \"true\") {\n              shortDislikeButton.style.color = getColorFromTheme(false);\n            }\n            shortsObserver.observe(shortLikeButton);\n            shortsObserver.observe(shortDislikeButton);\n          } else {\n            getLikeButton().style.color = getColorFromTheme(true);\n            if (dislikeButton) dislikeButton.style.color = getColorFromTheme(false);\n          }\n        }\n      }\n    });\n  });\n}\n\nfunction updateDOMDislikes() {\n  setDislikes(numberFormat(dislikesvalue));\n  createRateBar(likesvalue, dislikesvalue);\n}\n\nfunction likeClicked() {\n  if (checkForUserAvatarButton() == true) {\n    if (previousState == 1) {\n      likesvalue--;\n      updateDOMDislikes();\n      previousState = 3;\n    } else if (previousState == 2) {\n      likesvalue++;\n      dislikesvalue--;\n      updateDOMDislikes();\n      previousState = 1;\n    } else if (previousState == 3) {\n      likesvalue++;\n      updateDOMDislikes();\n      previousState = 1;\n    }\n    if (extConfig.numberDisplayReformatLikes === true) {\n      const nativeLikes = getLikeCountFromButton();\n      if (nativeLikes !== false) {\n        setLikes(numberFormat(nativeLikes));\n      }\n    }\n  }\n}\n\nfunction dislikeClicked() {\n  if (checkForUserAvatarButton() == true) {\n    if (previousState == 3) {\n      dislikesvalue++;\n      updateDOMDislikes();\n      previousState = 2;\n    } else if (previousState == 2) {\n      dislikesvalue--;\n      updateDOMDislikes();\n      previousState = 3;\n    } else if (previousState == 1) {\n      likesvalue--;\n      dislikesvalue++;\n      updateDOMDislikes();\n      previousState = 2;\n      if (extConfig.numberDisplayReformatLikes === true) {\n        const nativeLikes = getLikeCountFromButton();\n        if (nativeLikes !== false) {\n          setLikes(numberFormat(nativeLikes));\n        }\n      }\n    }\n  }\n}\n\nfunction setInitialState() {\n  setState();\n}\n\nfunction getVideoId() {\n  const urlObject = new URL(window.location.href);\n  const pathname = urlObject.pathname;\n  if (pathname.startsWith(\"/clip\")) {\n    return (document.querySelector(\"meta[itemprop='videoId']\") || document.querySelector(\"meta[itemprop='identifier']\")).content;\n  } else {\n    if (pathname.startsWith(\"/shorts\")) {\n      return pathname.slice(8);\n    }\n    return urlObject.searchParams.get(\"v\");\n  }\n}\n\nfunction isVideoLoaded() {\n  if (isMobile) {\n    return document.getElementById(\"player\").getAttribute(\"loading\") == \"false\";\n  }\n  const videoId = getVideoId();\n\n  return (\n    // desktop: spring 2024 UI\n    document.querySelector(`ytd-watch-grid[video-id='${videoId}']`) !== null ||\n    // desktop: older UI\n    document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null ||\n    // mobile: no video-id attribute\n    document.querySelector('#player[loading=\"false\"]:not([hidden])') !== null\n  );\n}\n\nfunction roundDown(num) {\n  if (num < 1000) return num;\n  const int = Math.floor(Math.log10(num) - 2);\n  const decimal = int + (int % 3 ? 1 : 0);\n  const value = Math.floor(num / 10 ** decimal);\n  return value * 10 ** decimal;\n}\n\nfunction numberFormat(numberState) {\n  let numberDisplay;\n  if (extConfig.numberDisplayRoundDown === false) {\n    numberDisplay = numberState;\n  } else {\n    numberDisplay = roundDown(numberState);\n  }\n  return getNumberFormatter(extConfig.numberDisplayFormat).format(numberDisplay);\n}\n\nfunction getNumberFormatter(optionSelect) {\n  let userLocales;\n  if (document.documentElement.lang) {\n    userLocales = document.documentElement.lang;\n  } else if (navigator.language) {\n    userLocales = navigator.language;\n  } else {\n    try {\n      userLocales = new URL(\n        Array.from(document.querySelectorAll(\"head > link[rel='search']\"))\n          ?.find((n) => n?.getAttribute(\"href\")?.includes(\"?locale=\"))\n          ?.getAttribute(\"href\"),\n      )?.searchParams?.get(\"locale\");\n    } catch {\n      cLog(\"Cannot find browser locale. Use en as default for number formatting.\");\n      userLocales = \"en\";\n    }\n  }\n\n  let formatterNotation;\n  let formatterCompactDisplay;\n  switch (optionSelect) {\n    case \"compactLong\":\n      formatterNotation = \"compact\";\n      formatterCompactDisplay = \"long\";\n      break;\n    case \"standard\":\n      formatterNotation = \"standard\";\n      formatterCompactDisplay = \"short\";\n      break;\n    case \"compactShort\":\n    default:\n      formatterNotation = \"compact\";\n      formatterCompactDisplay = \"short\";\n  }\n\n  const formatter = Intl.NumberFormat(userLocales, {\n    notation: formatterNotation,\n    compactDisplay: formatterCompactDisplay,\n  });\n  return formatter;\n}\n\nfunction getColorFromTheme(voteIsLike) {\n  let colorString;\n  switch (extConfig.colorTheme) {\n    case \"accessible\":\n      if (voteIsLike === true) {\n        colorString = \"dodgerblue\";\n      } else {\n        colorString = \"gold\";\n      }\n      break;\n    case \"neon\":\n      if (voteIsLike === true) {\n        colorString = \"aqua\";\n      } else {\n        colorString = \"magenta\";\n      }\n      break;\n    case \"classic\":\n    default:\n      if (voteIsLike === true) {\n        colorString = \"lime\";\n      } else {\n        colorString = \"red\";\n      }\n  }\n  return colorString;\n}\n\nlet smartimationObserver = null;\n\nfunction setEventListeners(evt) {\n  let jsInitChecktimer;\n\n  function checkForJS_Finish() {\n    //console.log();\n    if (isShorts() || (getButtons()?.offsetParent && isVideoLoaded())) {\n      const buttons = getButtons();\n      const dislikeButton = getDislikeButton();\n\n      if (preNavigateLikeButton !== getLikeButton() && dislikeButton) {\n        cLog(\"Registering button listeners...\");\n        try {\n          getLikeButton().addEventListener(\"click\", likeClicked);\n          dislikeButton?.addEventListener(\"click\", dislikeClicked);\n          getLikeButton().addEventListener(\"touchstart\", likeClicked);\n          dislikeButton?.addEventListener(\"touchstart\", dislikeClicked);\n          dislikeButton?.addEventListener(\"focusin\", updateDOMDislikes);\n          dislikeButton?.addEventListener(\"focusout\", updateDOMDislikes);\n          preNavigateLikeButton = getLikeButton();\n\n          if (!smartimationObserver) {\n            smartimationObserver = createObserver(\n              {\n                attributes: true,\n                subtree: true,\n                childList: true,\n              },\n              updateDOMDislikes,\n            );\n            smartimationObserver.container = null;\n          }\n\n          const smartimationContainer = buttons.querySelector(\"yt-smartimation\");\n          if (smartimationContainer && smartimationObserver.container != smartimationContainer) {\n            cLog(\"Initializing smartimation mutation observer\");\n            smartimationObserver.disconnect();\n            smartimationObserver.observe(smartimationContainer);\n            smartimationObserver.container = smartimationContainer;\n          }\n        } catch {\n          return;\n        } //Don't spam errors into the console\n      }\n      if (dislikeButton) {\n        setInitialState();\n        clearInterval(jsInitChecktimer);\n      }\n    }\n  }\n\n  cLog(\"Setting up...\");\n  jsInitChecktimer = setInterval(checkForJS_Finish, 111);\n}\n\n(function () {\n  \"use strict\";\n  window.addEventListener(\"yt-navigate-finish\", setEventListeners, true);\n  setEventListeners();\n})();\nif (isMobile) {\n  let originalPush = history.pushState;\n  history.pushState = function (...args) {\n    window.returnDislikeButtonlistenersSet = false;\n    setEventListeners(args[2]);\n    return originalPush.apply(history, args);\n  };\n  setInterval(() => {\n    const dislikeButton = getDislikeButton();\n    if (dislikeButton?.querySelector(\".button-renderer-text\") === null) {\n      getDislikeTextContainer().innerText = mobileDislikes;\n    } else {\n      if (dislikeButton) dislikeButton.querySelector(\".button-renderer-text\").innerText = mobileDislikes;\n    }\n  }, 1000);\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/az/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API statusu:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Oflayn\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Onlayn\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Rəng mövzusu:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Bəyənmə nisbəti çubuğunu rəngləndir\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Bəyənmə nisbəti çubuğu üçün xüsusi rənglər istifadə edin.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Düymələri rəngləndir\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Düymə simvolları üçün xüsusi rənglər istifadə edin.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Uzantının mövcud olmasını təmin edən tək şey ianələrinizdir, lütfən layihəni dəstəkləməyi düşünün.\"\n  },\n  \"customColors\": {\n    \"message\": \"Dislike çubuğu və düymələr üçün xüsusi rənglər\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Xüsusi say formatları\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"API cavablarının konsolda qeydə alınmasını deaktiv et.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Konsola yazmağı söndür\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Dislike-ları görmə qabiliyyəti verir\"\n  },\n  \"extensionName\": {\n    \"message\": \"YouTube Dislike Sayını Geri Qaytar\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"YouTube Dislike Sayını Geri Qaytar Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Parametrlər\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Dəyişiklik Jurnalı\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Bağış Et\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"Tez-tez verilən suallar\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Kömək\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Sayt\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Sayı formatı:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Premium xüsusiyyətlər Patreon dəstəkçiləri üçün mövcuddur\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Patreon ilə daxil ol\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Giriş tamamlanmadı. Yenidən cəhd edin.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Patreon girişini başlatmaq alınmadı. Yenidən cəhd edin.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Çıxış\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Giriş üçün 'identity' icazəsi tələb olunur. Davam etmək üçün icazə verin.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Əsas Səviyyə\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Üzvlük yoxlanılır…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Aktiv üzvlük yoxdur\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Premium Dəstəkçi\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Dəstəkçi\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon istifadəçisi\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Like sayılarının formatını yeniləyir\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Like və dislike formatını uyğunlaşdırır.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Premium tanıtım panelini gizlət\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"YouTube izləmə səhifələrində premium tanıtım panelini göstərmə.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Aşağı yuvarlanmış sayıları göstərir\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Sayıları aşağıya yuvarla (standart YouTube davranışı).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTube Shorts Dəstəyi\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Bəyən/bəyənməmə çubuğu ipucunda faiz.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Bəyən/bəyənməmə çubuğu ipucunda faizi göstər.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Rəng mövzusu:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klassik\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Aydın\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Bəyənmə nisbəti çubuğunu rəngləndir\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Nisbət çubuğu üçün xüsusi rənglər istifadə edin.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Düymələri rəngləndir\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Düymə simvolları üçün xüsusi rənglər istifadə edin.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Like və dislike formatını uyğunlaşdırır\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Like sayılarının formatını yeniləyir.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"Dmitry Selivanov və İcma tərəfindən\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Sahibi tərəfindən deaktiv edilib\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Sayı formatı:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Like/dislike statistikasını aşağı yuvarlayır (standart YouTube davranışı)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Yuvarlanmış statistikaları göstər.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Like/dislike göndərməsini deaktiv et\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Like və dislike-larınızı saymağı dayandırır.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Müvəqqəti olaraq istifadədən kənar\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Bu versiyaya yenilə\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Faiz rejimi:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Siçan üzərinə gətirərkən xüsusi faiz görünümünü istifadə et.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Versiya __RYD_VERSION__ yükləndi\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Yeniliklər\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premium video analitikası\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Zaman üzrə qarşılıqlı əlaqə\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Ən yaxşı ölkələr\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Ən çox bəyənən ölkələr\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Ən çox bəyənməyən ölkələr\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Coğrafiya\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Genişləndir\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Yığ\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Xəritə göstəricisi\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Dünya xəritəsinə qayıt\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Bəyənmələr\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Bəyənməmələr\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Nisbət\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Məlumat intervalı\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Son günlər\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"İlk günlər\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Seçilmiş intervalda ən son günləri göstər\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Seçilmiş intervalda ən erkən günləri göstər\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Fərdi seçim\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Bütün dövr\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Son $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"İlk $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 gün\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 gün\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Bütün dövr\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1g\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Seçilmiş period: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Uzantı istifadəçiləri\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 bəyənmə\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 bəyənməmə\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$2 ölkədən $3 unikal IP ilə $1 qarşılıqlı əlaqə qeydə alındı\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Saatda səslər\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Gündə səslər\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Həftədə səslər\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Hər $1 saatda səslər\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Hər $1 gündə səslər\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Hər $1 dəqiqədə səslər\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Dəqiqədə səslər\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Hər $1 saniyədə səslər\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Saniyədə səslər\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Hələ məlumat yoxdur\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Naməlum\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Analitika yüklənir…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Analitikanı açmaq üçün Patreonu yenidən icazələndirin.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premium analitika aktiv Patreon dəstəkçiləri üçün əlçatandır.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sessiya bitdi. Yenidən Patreon ilə daxil olun.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Analitika serveri hazırda əlçatan deyil.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Analitika xidmətinə qoşulmaq mümkün olmadı.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Premium analitikanı yükləmək mümkün olmadı.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Fəaliyyət\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Bəyənmə nisbəti\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Yüksək bəyənmə nisbəti\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Aşağı bəyənmə nisbəti\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Daha çox bəyənmə\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Daha az bəyənmə\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Daha çox bəyənməmə\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Daha az bəyənməmə\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Daha çox $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Daha az $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Bəyənmələr: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Bəyənməmələr: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Bəyənmə nisbəti: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Bəyənməmə analitikasının xülasəsi\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Emal olunmamış bəyənməmələr bu videoya baxan uzantı istifadəçilərinin birbaşa hesabatlarıdır.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Premiumu aç\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Tam analitikaya baxış\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Emal olunmamış bəyənməmə sayı\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Təxmini bəyənməmələr\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Təxmini bəyənmələr\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Ən son bəyənməmə cəmləri alınır…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Hazırda bəyənməmə məlumatı yüklənmədi. Səhifəni yenidən yükləyin.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Premium analitikanı necə açmalı\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Premium video analitikasını üç sürətli addımda açın:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Return YouTube Dislike uzantısının pəncərəsindən Patreon ilə daxil olun.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Fəaliyyət qrafikləri, ölkə reytinqləri və interaktiv xəritə üçün Premium səviyyəsini seçin.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Hər video üçün saatlıq trendləri, aparıcı ölkələri və ABŞ ştatlarının detallı xəritəsini görmək üçün YouTube-u yenidən yükləyin.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium analitika bloklanıb\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Patreon üzvlüyünüzdə Premium səviyyə yoxdur. Təkmil video analitikasını açmaq üçün dəstəyinizi yüksəldin.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Patreonda yüksəldin\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Premium Analytics dəyişiklik jurnalı · 20 oktyabr 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premium buraxılış\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Premium analitika yenidən quruldu\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Canlı fəaliyyət qrafiki, ən yaxşı ölkələrin lider lövhəsi və qayıtma YouTube-ni bəyənilməyən premium daxil olan interaktiv xəritəni araşdırın.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"20 oktyabr 2025 tarixində buraxıldı\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Patreonda yüksəlt\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Quraşdırma bələdçisini oxu\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Yeniliklər\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Canlı fəaliyyət qrafiki\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Gelimi real vaxtda bəyənmə və bəyənməyənləri bəyənməyi izləyin. Saat, gündəlik, gündəlik və ya hər zaman hafızası dəyişdirin, pəncərəni gündən-günə və ya ən son səsləri birləşdirin və hər sıçrayışın nə olduğunu görməyə başladın.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"İnteraktiv Dünya və ABŞ HeatMap\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Dünyada pan, və ya ABŞ-ın isti nöqtələrini yerüstü hissələrə daldırın. Yüksələn alətdə, bəyənilənləri və nisbətləri yüksəldilmiş alətdə və rəqəmləri ilə müqayisə edin və yükləmələrinizə ilk reaksiya verən bölgələrdə.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Ən yaxşı ölkələrin lider lövhəsi\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Hər ölkəni xam fəaliyyət, bəyənmə, bəyənmir və ya nisbətlə sıralayın. Kampaniyaların harada olduğunu öyrənmək və növbəti dərc etməyi planlaşdıran qrafiki kimi eyni hafızası ilə süzün.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Ekran görüntüsü tezliklə gələcək — hazır olduqda bu görünüşü DevTools ilə çəkin.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Yer tutucu: zaman cədvəli icmalı\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Yer tutucu: qlobal aktivlik xəritəsi\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Yerli yerləşdirən: Ölkələrin lider taxtası\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Premium analitikanı necə açmaq olar\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Hazırsınız? Bu addımları izləyin və YouTube vərəqənizi yeniləyin.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Return YouTube Dislike açılır pəncərəsini açın və Patreon ilə daxil olun.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Premium səviyyəsini seçin ki, analitika məlumatları hesabınıza axın etsin.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Hər hansı YouTube izləmə səhifəsini yeniləyin — zaman cədvəli, xəritə və lider lövhələri dərhal görünəcək.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Buraxılışın əsas məqamları\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Fəaliyyət qrafiki, ilk və son çömçə üçün aralığı lövbərləri olan canlı məlumatlar yayımlanır.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Ölkə liderliyi paneli dərhal paneli tərk etmədən yüksələn bölgələri yerləşdirə biləcəyiniz üçün dərhal təravətləndirir.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"İnteraktiv xəritə, daha aydın alət və ölçüləri olan qlobal və ABŞ dövlət qaynar nöqtələrini vurğulayır.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Kömək lazımdır?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Bir problem yaşasanız buradayıq — suallarınızı dəstək sənədlərinə və ya icmaya gətirin.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"İcmadan soruş\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike loqosu\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/cs/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Stav API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Barevný motiv:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Obarvit pruh poměru\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Použít vlastní barvy pro pruh poměru.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Obarvit ikony\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Použít vlastní barvy pro ikony.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Jediná věc, díky které rozšíření funguje, jsou vaše příspěvky. Zvažte prosím podpoření projektu.\"\n  },\n  \"customColors\": {\n    \"message\": \"Vlastní barvy pro lištu disliků a tlačítka\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Vlastní formáty čísel\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Vypnout zapisování odpovědí API do konzole.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Zakázat protokolování do konzole\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Navrátí možnost zobrazení počtu disliků\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Nastavení\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Seznam změn\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Přispějte\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Nápověda\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Web\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Formát čísel:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Prémiové funkce jsou dostupné pro podporovatele na Patreonu\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Přihlásit se pomocí Patreonu\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Přihlášení se nepodařilo dokončit. Zkuste to znovu.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Nepodařilo se zahájit přihlášení přes Patreon. Zkuste to znovu.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Odhlásit se\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Přihlášení vyžaduje oprávnění „identity“. Povolte ho, prosím.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Základní úroveň\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Kontrola členství…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Žádné aktivní členství\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Prémiový podporovatel\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Podporovatel\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Uživatel Patreonu\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Přeformátovat čísla palců nahoru\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Udělat formát palců nahoru/dolů konzistentní.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Skrýt panel prémiové upoutávky\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Nezobrazovat prémiovou upoutávku na stránkách přehrávání na YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Zobrazit čísla zaokrouhlená dolů\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Zaokrouhlit čísla dolů (výchozí chování YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Podpora YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Procenta v nápovědě u lišty like/dislike.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Zobrazit procenta v nápovědě u lišty like/dislike.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Barevný motiv:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klasický\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Přístupný\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Obarvit pruh poměru\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Použít vlastní barvy pro pruh poměru.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Obarvit ikony\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Použít vlastní barvy pro ikony.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Použít stejný formát čísel pro palce nahoru/dolů\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Přeformátovat počet palců nahoru.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"vytvořil Dmitry Selivanov & komunita\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"zakázáno majitelem\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Formát čísel:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Zaokrouhlit dolů statistiky palců nahoru/dolů (výchozí chování YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Zobrazit statistiky zaokrouhlené dolů.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Zakázat odesílání palců nahoru/dolů\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Zastaví počítání vašich palců nahoru/dolů.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"dočasně nedostupné\"\n  },\n  \"textUpdate\": {\n    \"message\": \"aktualizovat na\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Režim procent:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Použít vlastní zobrazení procent po najetí myší.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Verze __RYD_VERSION__ nainstalována\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Co je nového\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Prémiové statistiky videa\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Zapojení v čase\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Nejlepší země\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Země s nejvíce lajky\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Země s nejvíce dislajky\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografie\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Rozbalit\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Sbalit\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Metrika mapy\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Zpět na mapu světa\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Lajky\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Dislajky\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Poměr\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Časové okno dat\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Poslední dny\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"První dny\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Zobrazit nejnovější dny v rámci vybraného okna\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Zobrazit nejstarší dny v rámci vybraného okna\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Vlastní výběr\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Celé období\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Posledních $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Prvních $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 den\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 dní\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Celé období\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Vybrané období: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Uživatelé rozšíření\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 lajků\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 dislajků\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Zaznamenáno $1 interakcí z $2 zemí ($3 unikátních IP)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Hlasy za hodinu\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Hlasy za den\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Hlasy za týden\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Hlasy každých $1 hodin\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Hlasy každých $1 dní\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Hlasy každých $1 minut\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Hlasy za minutu\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Hlasy každých $1 sekund\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Hlasy za sekundu\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Zatím žádná data\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Neznámé\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Načítání statistik…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Pro odemčení statistik znovu autorizujte Patreon.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Prémiové statistiky jsou dostupné aktivním podporovatelům na Patreonu.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Relace vypršela. Přihlaste se znovu přes Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Služba statistik je nyní nedostupná.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Nelze se připojit ke službě statistik.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Nelze načíst prémiové statistiky.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktivita\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Poměr lajků\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Vyšší poměr lajků\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Nižší poměr lajků\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Více lajků\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Méně lajků\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Více dislajků\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Méně dislajků\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Více $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Méně $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Lajky: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Dislajky: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Poměr lajků: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Přehled statistik dislajků\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Nezpracované dislajky jsou přímá hlášení uživatelů rozšíření sledujících toto video.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Odemknout Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Zobrazit náhled kompletních statistik\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Počet nezpracovaných dislajků\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Odhadované dislajky\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Odhadované lajky\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Získávání nejnovějších součtů dislajků…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Data dislajků se nepodařilo načíst. Zkuste stránku znovu načíst.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Jak odemknout prémiové statistiky\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Odemkněte prémiové statistiky videa ve třech krocích:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Přihlaste se přes Patreon v okně rozšíření Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Vyberte úroveň Premium pro aktivaci časových os aktivity, žebříčků zemí a interaktivní mapy.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Znovu načtěte YouTube a uvidíte hodinové trendy, hlavní země a detailní přehled států USA pro každé video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Prémiová analytika je uzamčená\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Vaše členství na Patreonu nezahrnuje úroveň Premium. Povýšte svůj příspěvek a zpřístupněte si pokročilou video analytiku.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Povýšit na Patreonu\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Přehled novinek Premium Analytics · 20. října 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premium vydání\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Prémiové analýzy v novém\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Prozkoumejte časovou osu živých aktivit, žebříček nejlepších zemí a interaktivní mapu, které jsou součástí Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Vydáno 20. října 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Upgradovat na Patreonu\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Přečíst průvodce nastavením\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Nové funkce\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Časová osa živé aktivity\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Sledujte streamy hodnocení Líbí se a Nelíbí se v reálném čase. Přepínejte hodinové, denní nebo trvalé předvolby bez opětovného načítání, ukotvte okno k prvnímu dni nebo posledním hlasům a procházejte každý bodec, abyste viděli, co vyvolalo zapojení.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interaktivní mapa světa a USA\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Posouvejte se po celém světě nebo se ponořte do amerických států na povrch hotspotů. Porovnejte hodnocení Líbí se, Nelíbí se a poměry v upgradovaném popisku a zvýrazněte oblasti, které reagují na vaše nahraná videa jako první.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Žebříček nejlepších zemí\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Seřaďte každou zemi podle nezpracované aktivity, oblíbenosti, nelibosti nebo poměru. Filtrujte se stejnými předvolbami jako na časové ose, abyste zjistili, kde kampaně končí, a naplánujte si, co dále publikovat.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Screenshot doplníme – pořiďte tuto scénu v DevTools, až bude připravena.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Zástupný obrázek: přehled časové osy\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Zástupný obrázek: mapa globální aktivity\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Zástupný symbol: žebříček zemí\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Jak odemknout Premium Analytics\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Připraveni? Postupujte podle těchto kroků a obnovte kartu YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Otevřete popup Return YouTube Dislike a přihlaste se přes Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Zvolte úroveň Premium, aby se do vašeho účtu začala posílat analytická data.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Znovu načtěte libovolné video na YouTube – časová osa, mapa a žebříčky se zobrazí okamžitě.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Hlavní novinky vydání\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Předvolby časové osy aktivity streamují živá data s ukotvením rozsahu pro první a poslední segmenty.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Žebříček zemí se okamžitě aktualizuje, takže můžete vidět rostoucí oblasti, aniž byste opustili panel.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Interaktivní mapa zvýrazňuje globální a americké státní hotspoty s jasnějšími popisky a metrikami.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Potřebujete pomoct?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Jsme tu pro vás – využijte dokumentaci podpory nebo komunitu.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Zeptejte se komunity\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/de/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API‑Status:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Farbschema:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Verhältnisbalken einfärben\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Benutzerdefinierte Farben für die Verhältnisleiste verwenden.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Like/Dislike-Icons einfärben\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Benutzerdefinierte Farben für die Like/Dislike-Icons verwenden.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Das Einzige, was die Erweiterung am Laufen hält, sind Ihre Spenden, bitte erwägen Sie, das Projekt zu unterstützen.\"\n  },\n  \"customColors\": {\n    \"message\": \"Benutzerdefinierte Farben für die Dislike-Leiste und Knopf\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Benutzerdefinierte Zahlenformate\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Deaktiviert das Protokollieren von API‑Antworten in der Konsole.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Protokollierung in Konsole deaktivieren\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Macht Dislikes wieder sichtbar\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Einstellungen\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Änderungshistorie\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Spenden\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Hilfe\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Website\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Zahlenformat:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Premiumfunktionen sind für Patreon‑Unterstützer verfügbar\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Mit Patreon anmelden\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Anmeldung konnte nicht abgeschlossen werden. Bitte versuche es erneut.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Patreon‑Anmeldung konnte nicht gestartet werden. Bitte versuche es erneut.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Abmelden\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Für die Anmeldung ist die Berechtigung „identity“ erforderlich. Bitte erteilen Sie sie, um fortzufahren.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Basis‑Stufe\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Mitgliedschaft wird geprüft …\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Keine aktive Mitgliedschaft\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Premium‑Unterstützer\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Unterstützer\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon‑Nutzer\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Likes neu formatieren\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Das Format der Likes und Dislikes einheitlich machen.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Premium-Teaser-Panel ausblenden\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Premium-Teaser auf YouTube-Wiedergabeseiten nicht anzeigen.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Zeige abgerundete Zahlen\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Zahlen abrunden (Standardverhalten von YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTube Shorts Unterstützung\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Prozentangabe in der Like/Dislike‑Leiste (Tooltip).\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Prozentangabe im Tooltip der Like/Dislike‑Leiste anzeigen.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Farbschema:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klassisch\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Barrierefrei\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Verhältnisbalken einfärben\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Benutzerdefinierte Farben für die Verhältnisleiste verwenden.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Like/Dislike-Icons einfärben\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Benutzerdefinierte Farben für die Like/Dislike-Icons verwenden.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Das Format der Likes und Dislikes einheitlich machen.\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Likes neu formatieren\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"von Dmitry Selivanov & der Community\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Deaktiviert vom Ersteller\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Zahlenformat:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Runde Likes/Dislikes ab (Standardverhalten von YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Zeige abgerundete Zahlen.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Deaktiviere Like/Dislike-Übermittlung\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Stoppt deine Likes und Dislikes zu zählen.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Temporär nicht verfügbar\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Aktualisieren auf\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Prozentmodus:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Benutzerdefinierte Prozentanzeige beim Hover verwenden.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Version __RYD_VERSION__ installiert\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Was ist neu\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premium-Video-Analyse\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Interaktion im Zeitverlauf\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Top-Länder\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Länder mit den meisten Likes\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Länder mit den meisten Dislikes\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografie\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Erweitern\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Einklappen\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Kartenmetrik\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Zur Weltkarte zurück\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Likes\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Dislikes\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Verhältnis\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Datenzeitraum\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Neueste Tage\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Erste Tage\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Neueste Tage im ausgewählten Zeitraum anzeigen\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Früheste Tage im ausgewählten Zeitraum anzeigen\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Benutzerdefinierte Auswahl\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Gesamter Zeitraum\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Letzte $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Erste $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 Tag\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 Tage\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Gesamter Zeitraum\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1 Tg\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Ausgewählter Zeitraum: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Extension-Nutzer\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 Likes\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 Dislikes\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$1 Interaktionen aus $2 Ländern erfasst ($3 eindeutige IPs)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Stimmen pro Stunde\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Stimmen pro Tag\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Stimmen pro Woche\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Stimmen alle $1 Stunden\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Stimmen alle $1 Tage\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Stimmen alle $1 Minuten\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Stimmen pro Minute\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Stimmen alle $1 Sekunden\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Stimmen pro Sekunde\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Noch keine Daten\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Unbekannt\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Analysen werden geladen…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Bitte Patreon erneut autorisieren, um die Analysen freizuschalten.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premium-Analysen sind für aktive Patreon-Unterstützer verfügbar.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sitzung abgelaufen. Bitte erneut mit Patreon anmelden.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Analyse-Backend ist derzeit nicht verfügbar.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Analyse-Dienst kann nicht erreicht werden.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Premium-Analysen konnten nicht geladen werden.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktivität\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Like-Verhältnis\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Höheres Like-Verhältnis\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Niedrigeres Like-Verhältnis\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Mehr Likes\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Weniger Likes\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Mehr Dislikes\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Weniger Dislikes\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Mehr $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Weniger $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Likes: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Dislikes: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Like-Verhältnis: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Dislike-Insights auf einen Blick\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Roh-Dislikes sind direkte Meldungen von Erweiterungsnutzern, die dieses Video ansehen.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Premium freischalten\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Gesamte Analysen ansehen\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Rohzahl der Dislikes\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Geschätzte Dislikes\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Geschätzte Likes\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Neueste Dislike-Gesamtwerte werden abgerufen…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Dislike-Daten konnten gerade nicht geladen werden. Versuchen Sie, die Seite neu zu laden.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"So schalten Sie Premium-Analysen frei\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Schalten Sie Premium-Video-Insights in drei schnellen Schritten frei:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Melden Sie sich im Pop-up der Return YouTube Dislike-Erweiterung mit Patreon an.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Wählen Sie die Premium-Stufe, um Aktivitätsverläufe, Länderranglisten und die interaktive Karte zu aktivieren.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Laden Sie YouTube neu, um stündliche Trends, Top-Länder und Detailansichten der US-Bundesstaaten für jedes Video zu sehen.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium-Analysen gesperrt\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Deine Patreon-Mitgliedschaft enthält keine Premium-Stufe. Erhöhe deinen Beitrag, um die erweiterten Videoanalysen freizuschalten.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Auf Patreon upgraden\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Premium-Analytics-Changelog · 20. Oktober 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premium-Release\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Premium-Analysen, neu gedacht\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Entdecken Sie die Zeitleiste der Live-Aktivitäten, die Bestenliste der Top-Länder und die interaktive Karte, die in Return YouTube Dislike Premium enthalten sind.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Veröffentlicht am 20. Oktober 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Auf Patreon upgraden\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Setup-Anleitung lesen\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Das ist neu\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Zeitleiste der Live-Aktivität\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Sehen Sie sich den Stream von Vorlieben und Abneigungen in Echtzeit an. Schalten Sie ohne Neuladen zwischen stündlichen, täglichen oder Gesamtvoreinstellungen um, verankern Sie das Fenster auf dem ersten Tag oder den letzten Abstimmungen und scannen Sie jede Spitze, um zu sehen, was das Engagement ausgelöst hat.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interaktive Welt- und US-Heatmap\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Schwenken Sie um den Globus oder tauchen Sie in US-Bundesstaaten ein, um Hotspots zu entdecken. Vergleichen Sie Vorlieben, Abneigungen und Verhältnisse im aktualisierten Tooltip und heben Sie die Regionen hervor, die zuerst auf Ihre Uploads reagieren.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Bestenliste der Top-Länder\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Ordnen Sie jedes Land nach Rohaktivität, Vorlieben, Abneigungen oder Verhältnis. Filtern Sie mit denselben Voreinstellungen wie in der Zeitleiste, um zu erfahren, wo Kampagnen landen, und um zu planen, was als Nächstes veröffentlicht werden soll.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Screenshot folgt – erstelle diese Ansicht bei Bedarf in den DevTools.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Platzhalter: Timeline-Übersicht\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Platzhalter: Globale Aktivitätskarte\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Platzhalter: Länder-Rangliste\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"So schaltest du Premium-Analysen frei\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Bereit? Folge diesen Schritten und aktualisiere anschließend deinen YouTube-Tab.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Öffne das Return-YouTube-Dislike-Popup und melde dich mit Patreon an.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Wähle den Premium-Tarif, damit Analysedaten in dein Konto fließen.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Lade eine YouTube-Watch-Seite neu – Timeline, Karte und Bestenlisten erscheinen sofort.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Highlights des Releases\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Aktivitätszeitleistenvoreinstellungen streamen Live-Daten mit Bereichsankern für den ersten und neuesten Bucket.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Die Länder-Bestenliste wird sofort aktualisiert, sodass Sie aufstrebende Regionen erkennen können, ohne das Panel zu verlassen.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Die interaktive Karte hebt globale und US-amerikanische Hotspots mit klareren Tooltips und Messwerten hervor.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Brauchst du Hilfe?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Wir unterstützen dich gern – nutze die Support-Dokus oder unsere Community.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Frag die Community\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike-Logo\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/el/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Κατάσταση API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Εκτός λειτουργίας\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Σε λειτουργία\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Χρωματικό θέμα:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Χρωματισμός μπάρας σχέσης\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Προσαρμοζόμενο χρώμα για την μπάρα αναλογιών.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Χρωματισμός των thumbs\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Προσαρμοζόμενο χρώμα για τα thumb icons.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Το μόνο πράγμα που διατηρεί την επέκταση σε λειτουργία είναι οι δωρεές σας, σκεφτείτε να υποστηρίξετε το έργο.\"\n  },\n  \"customColors\": {\n    \"message\": \"Προσαρμοζόμενα χρώματα για τα κουμπιά και την μπάρα dislike\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Προσαρμοζόμενη μορφή αριθμών\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Απενεργοποιεί την καταγραφή των αποκρίσεων API στην κονσόλα.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Απενεργοποίηση καταγραφής στην κονσόλα\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Επαναφέρει τη δυνατότητα να βλέπετε τα dislikes\"\n  },\n  \"extensionName\": {\n    \"message\": \"Επαναφορά του YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Ρυθμίσεις\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Αρχείο αλλαγών\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Στηρίξτε\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"Συχνές ερωτήσεις\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Βοήθεια\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Ιστοσελίδα\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Μορφή αριθμών:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Οι premium λειτουργίες είναι διαθέσιμες για υποστηρικτές στο Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Σύνδεση με Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Η σύνδεση δεν ολοκληρώθηκε. Δοκιμάστε ξανά.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Αποτυχία έναρξης σύνδεσης μέσω Patreon. Δοκιμάστε ξανά.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Αποσύνδεση\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Η σύνδεση απαιτεί το δικαίωμα «identity». Παραχωρήστε το για να συνεχίσετε.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Βασικό επίπεδο\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Έλεγχος συνδρομής…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Καμία ενεργή συνδρομή\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Υποστηρικτής Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Υποστηρικτής\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Χρήστης Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Μορφοποίηση του αριθμού των likes\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Σταθερή μορφοποίηση των likes & dislikes.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Απόκρυψη πίνακα προωθητικού Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Να μην εμφανίζεται το προωθητικό Premium στις σελίδες αναπαραγωγής του YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Εμφάνιση στρογγυλεμένων αριθμών\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Στρογγυλοποιήστε την τιμή μέτρησης (προεπιλεγμένη επιλογή του YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Υποστήριξη για YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Ποσοστό στο tooltip της μπάρας like/dislike.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Εμφάνιση ποσοστού στο tooltip της μπάρας like/dislike.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Θέματα χρωμάτων:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Classic\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Accessible\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Χρωματισμός μπάρας αναλογιών.\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Προσαρμοζόμενο χρώμα για την μπάρα αναλογιών.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Χρωματισμός των thumbs\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Προσαρμοζόμενο χρώμα για τα thumb icons.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Κάνει σταθερή την μορφοποίηση των likes & dislikes.\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Μορφοποίηση του αριθμού των likes.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"από τον Dmitry Selivanov & την κοινότητα\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Απενεργοποιήθηκε από τον κάτοχο\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Μορφή αριθμών:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Στρογγυλοποιήστε την τιμή μέτρησης (προεπιλεγμένη επιλογή του YouTube).\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Εμφάνιση στρογγυλεμένων αριθμών.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Διακοπή υποβολής like/dislike\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Σταματάει να μετράει τα likes & dislikes που κάνεις.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Προσωρινά μη διαθέσιμο\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Ενημέρωση σε\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Λειτουργία ποσοστού:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Χρήση προσαρμοσμένης εμφάνισης ποσοστού κατά την αιώρηση.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Εκδοση __RYD_VERSION__ εγκαταστάθηκε\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Τί νέο υπάρχει\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Στατιστικά Premium για το βίντεο\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Συμμετοχή με την πάροδο του χρόνου\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Κορυφαίες χώρες\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Χώρες με τα περισσότερα likes\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Χώρες με τα περισσότερα dislikes\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Γεωγραφία\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Ανάπτυξη\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Σύμπτυξη\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Μετρική χάρτη\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Επιστροφή στον παγκόσμιο χάρτη\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Likes\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Dislikes\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Αναλογία\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Παράθυρο δεδομένων\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Τελευταίες ημέρες\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Πρώτες ημέρες\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Εμφάνιση των πιο πρόσφατων ημερών στο επιλεγμένο διάστημα\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Εμφάνιση των πρώτων ημερών στο επιλεγμένο διάστημα\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Προσαρμοσμένη επιλογή\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Όλο το διάστημα\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Τελευταίες $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Πρώτες $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 ημέρα\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 ημέρες\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Όλο το διάστημα\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1η\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Επιλεγμένη περίοδος: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Χρήστες της επέκτασης\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 likes\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 dislikes\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Καταγράφηκαν $1 αλληλεπιδράσεις από $2 χώρες ($3 μοναδικές IP)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Ψήφοι ανά ώρα\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Ψήφοι ανά ημέρα\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Ψήφοι ανά εβδομάδα\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Ψήφοι κάθε $1 ώρες\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Ψήφοι κάθε $1 ημέρες\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Ψήφοι κάθε $1 λεπτά\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Ψήφοι ανά λεπτό\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Ψήφοι κάθε $1 δευτερόλεπτα\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Ψήφοι ανά δευτερόλεπτο\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Δεν υπάρχουν δεδομένα ακόμη\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Άγνωστο\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Φόρτωση στατιστικών…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Εξουσιοδοτήστε ξανά το Patreon για να ξεκλειδώσετε τα στατιστικά.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Τα premium στατιστικά είναι διαθέσιμα για ενεργούς υποστηρικτές στο Patreon.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Η συνεδρία έληξε. Συνδεθείτε ξανά με το Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Η υπηρεσία στατιστικών δεν είναι διαθέσιμη αυτή τη στιγμή.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Δεν είναι δυνατή η σύνδεση με την υπηρεσία στατιστικών.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Δεν είναι δυνατή η φόρτωση των premium στατιστικών.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Δραστηριότητα\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Αναλογία likes\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Υψηλότερη αναλογία likes\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Χαμηλότερη αναλογία likes\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Περισσότερα likes\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Λιγότερα likes\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Περισσότερα dislikes\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Λιγότερα dislikes\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Περισσότερα $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Λιγότερα $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Likes: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Dislikes: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Αναλογία likes: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Στιγμιότυπο στατιστικών dislikes\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Τα ανεπεξέργαστα dislikes είναι άμεσες αναφορές χρηστών της επέκτασης που βλέπουν αυτό το βίντεο.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Ξεκλείδωμα Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Προεπισκόπηση πλήρων στατιστικών\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Ανεπεξέργαστος αριθμός dislikes\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Εκτιμώμενα dislikes\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Εκτιμώμενα likes\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Λήψη των πιο πρόσφατων συνολικών dislikes…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Δεν ήταν δυνατή η φόρτωση των δεδομένων dislikes. Δοκιμάστε να ανανεώσετε τη σελίδα.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Πώς να ξεκλειδώσετε τα premium στατιστικά\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Ξεκλειδώστε τα premium στατιστικά βίντεο σε τρία γρήγορα βήματα:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Συνδεθείτε με το Patreon από το αναδυόμενο παράθυρο της επέκτασης Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Επιλέξτε το επίπεδο Premium για να ενεργοποιήσετε χρονολόγια δραστηριότητας, πίνακες κατάταξης χωρών και τον διαδραστικό χάρτη.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Ανανεώστε το YouTube για να δείτε ωριαίες τάσεις, κορυφαίες χώρες και ανάλυση πολιτειών των ΗΠΑ για κάθε βίντεο.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Οι premium αναλύσεις είναι κλειδωμένες\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Η συνδρομή σου στο Patreon δεν περιλαμβάνει το επίπεδο Premium. Αναβάθμισε τη στήριξή σου για να ξεκλειδώσεις προηγμένα στατιστικά βίντεο.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Αναβάθμιση στο Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Ημερολόγιο αλλαγών Premium Analytics · 20 Οκτωβρίου 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Κυκλοφορία Premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Premium αναλύσεις, επανασχεδιασμένες\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Εξερευνήστε το χρονοδιάγραμμα της ζωντανής δραστηριότητας, τον πίνακα κορυφαίων χωρών και τον διαδραστικό χάρτη που περιλαμβάνονται στο Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Κυκλοφόρησε στις 20 Οκτωβρίου 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Αναβάθμιση στο Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Διαβάστε τον οδηγό ρύθμισης\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Τι νέο υπάρχει\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Χρονοδιάγραμμα ζωντανής δραστηριότητας\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Παρακολουθήστε τη ροή \\\"μου αρέσει\\\" και \\\"δεν μου αρέσει\\\" σε πραγματικό χρόνο. Εναλλάξτε τις ωριαίες, ημερήσιες ή προεπιλογές όλων των εποχών χωρίς επαναφόρτωση, αγκυρώστε το παράθυρο στην πρώτη ημέρα ή τις πιο πρόσφατες ψηφοφορίες και σκουπίστε κάθε αιχμή για να δείτε τι ενεργοποίησε την αφοσίωση.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Διαδραστικός χάρτης θερμότητας κόσμου και ΗΠΑ\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Μετακινηθείτε σε όλο τον κόσμο ή βουτήξτε στις πολιτείες των ΗΠΑ για να εμφανίσετε hotspot. Συγκρίνετε τις επισημάνσεις \\\"μου αρέσει\\\", \\\"δεν μου αρέσει\\\" και τις αναλογίες στην αναβαθμισμένη επεξήγηση εργαλείου και δώστε έμφαση στις περιοχές που αντιδρούν πρώτες στις μεταφορτώσεις σας.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Κορυφαίος πίνακας κορυφαίων χωρών\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Κατατάξτε κάθε χώρα κατά ακατέργαστη δραστηριότητα, \\\"μου αρέσει\\\", \\\"δεν μου αρέσει\\\" ή αναλογία. Φιλτράρετε με τις ίδιες προεπιλογές με το χρονοδιάγραμμα για να μάθετε πού προέρχονται οι καμπάνιες και να σχεδιάσετε τι θα δημοσιεύσετε στη συνέχεια.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Το στιγμιότυπο θα προστεθεί — αποθηκεύστε αυτή την προβολή μέσω DevTools όταν είστε έτοιμοι.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Placeholder: επισκόπηση χρονογραμμής\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Placeholder: χάρτης παγκόσμιας δραστηριότητας\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Placeholder: leaderboard χωρών\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Πώς να ξεκλειδώσετε τα Premium analytics\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Έτοιμοι; Ακολουθήστε αυτά τα βήματα και ανανεώστε την καρτέλα YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Ανοίξτε το αναδυόμενο Return YouTube Dislike και συνδεθείτε με Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Επιλέξτε τη βαθμίδα Premium ώστε τα δεδομένα analytics να ρέουν στον λογαριασμό σας.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Ανανεώστε οποιαδήποτε σελίδα παρακολούθησης στο YouTube — χρονογραμμή, χάρτης και κατατάξεις εμφανίζονται άμεσα.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Κύρια σημεία κυκλοφορίας\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Οι προκαθορισμένες ρυθμίσεις του χρονοδιαγράμματος δραστηριότητας μεταδίδουν ζωντανά δεδομένα με αγκυρώσεις εύρους για τον πρώτο και τον τελευταίο κάδο.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Ο πίνακας κορυφαίων χωρών ανανεώνεται αμέσως, ώστε να μπορείτε να εντοπίζετε ανερχόμενες περιοχές χωρίς να βγείτε από το πλαίσιο.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Ο διαδραστικός χάρτης υπογραμμίζει τα παγκόσμια και τις πολιτειακές εστίες των ΗΠΑ με σαφέστερες συμβουλές εργαλείων και μετρήσεις.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Χρειάζεστε βοήθεια;\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Είμαστε εδώ αν συναντήσετε εμπόδιο — βρείτε απαντήσεις στη τεκμηρίωση ή στην κοινότητα.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Ρωτήστε την κοινότητα\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Λογότυπο Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/en/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API Status:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Color theme:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Colorize ratio bar\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Use custom colors for the ratio bar.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Colorize thumbs\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Use custom colors for the thumb icons.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"The only thing that keeps the extension running is your donations, please consider supporting the project.\"\n  },\n  \"customColors\": {\n    \"message\": \"Custom colors for dislike bar and buttons\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Custom number formats\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Disable logging API responses in console.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Disable logging to console\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Returns ability to see dislikes\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Settings\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Change Log\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Donate\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Help\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Website\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Number format:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Premium features available for Patreon supporters\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Login with Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Failed to complete login. Please try again.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Failed to initiate Patreon login. Please try again.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Logout\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Login requires the 'identity' permission. Please allow it to continue.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Basic Tier\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Checking membership...\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"No Active Membership\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Premium Supporter\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Supporter\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon User\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Re-format like numbers\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Make likes and dislikes format consistent.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Hide premium teaser panel\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Don't show the premium teaser on YouTube watch pages.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Show rounded down numbers\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Round down numbers (default YouTube behavior).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTube Shorts Support\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Percentage in like/dislike bar tooltip.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Display percentage in like/dislike bar tooltip.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Color theme:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Classic\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Accessible\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Colorize ratio bar\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Use custom colors for ratio bar.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Colorize thumbs\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Use custom colors for thumb icons.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Make likes and dislikes format consistent\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Re-format like numbers.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"by Dmitry Selivanov & Community\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Disabled by Owner\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Number format:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Round down like/dislike stats (default YouTube behavior)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Show rounded down stats.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Disable like/dislike submission\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Stops counting your likes and dislikes.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Temporarily Unavailable\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Update to\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Percent mode:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Use custom percentage display on hover.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Version __RYD_VERSION__ installed\"\n  },\n  \"whatsnew\": {\n    \"message\": \"What's new\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premium Video Insights\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Engagement over time\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Top countries\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Top Like Countries\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Top Dislike Countries\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geography\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Expand\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Collapse\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Map metric\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Back to world map\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Likes\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Dislikes\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Ratio\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Data window\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Latest days\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"First days\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Show the most recent days within the selected window\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Show the earliest days within the selected window\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Custom selection\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"All time\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Last $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"First $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 day\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 days\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"All time\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Selected period: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Extension users\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 likes\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 dislikes\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Captured $1 interactions from $2 countries ($3 unique IPs)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Votes per hour\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Votes per day\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Votes per week\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Votes per $1 hours\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Votes per $1 days\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Votes per $1 minutes\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Votes per minute\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Votes per $1 seconds\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Votes per second\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"No data yet\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Unknown\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Loading insights…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Please re-authorize Patreon to unlock analytics.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premium analytics are available for active Patreon supporters.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Session expired. Sign in with Patreon again.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Analytics backend unavailable right now.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Unable to reach analytics service.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Unable to load premium analytics.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Activity\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Like ratio\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Higher like ratio\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Lower like ratio\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Higher Likes\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Lower Likes\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Higher Dislikes\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Lower Dislikes\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Higher $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Lower $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Likes: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Dislikes: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Like ratio: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Dislike insights snapshot\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Raw dislikes are direct reports from extension users watching this video.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Unlock Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Preview full analytics\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Raw dislike count\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Estimated dislikes\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Estimated likes\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Getting the latest dislike totals…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"We could not load dislike data just now. Try reloading the page.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"How to unlock Premium analytics\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Unlock Premium Video Insights in three quick steps:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Sign in with Patreon from the Return YouTube Dislike extension popup.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Select the Premium tier to enable activity timelines, country leaderboards, and the interactive map.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Reload YouTube to view hourly trends, top countries, and US state drill-downs for every video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium analytics locked\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Your Patreon membership doesn't include the Premium tier. Upgrade your pledge to unlock advanced video analytics.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Upgrade on Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Premium Analytics Changelog · October 20, 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premium Release\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Premium analytics, reimagined\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Explore the live activity timeline, top countries leaderboard, and interactive map included with Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Released on October 20, 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Upgrade on Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Read the setup guide\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"What’s new\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Live activity timeline\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Watch likes and dislikes stream in real time. Toggle hourly, daily, or all-time presets without reloading, anchor the window to day one or the latest votes, and scrub every spike to see what triggered engagement.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interactive world & US heatmap\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Pan across the globe or dive into US states to surface hotspots. Compare likes, dislikes, and ratios in the upgraded tooltip and spotlight the regions that react first to your uploads.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Top countries leaderboard\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Rank every country by raw activity, likes, dislikes, or ratio. Filter with the same presets as the timeline to learn where campaigns land and plan what to publish next.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Screenshot coming soon—capture this view in DevTools when ready.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Placeholder: timeline overview\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Placeholder: global activity map\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Placeholder: countries leaderboard\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"How to unlock Premium analytics\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Ready to dive in? Follow these quick steps and refresh your YouTube tab.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Open the Return YouTube Dislike popup and sign in with Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Choose the Premium tier to enable analytics data streaming to your account.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Reload any YouTube watch page—your timeline, map, and leaderboards appear instantly.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Release highlights\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Activity timeline presets stream live data with range anchors for first and latest buckets.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Country leaderboard refreshes instantly so you can spot rising regions without leaving the panel.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Interactive map highlights global and US state hotspots with clearer tooltips and metrics.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Need a hand?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"We’re here if you hit a snag—bring your questions to the support docs or community.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Ask the community\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike logo\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/es/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Estado de la API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Fuera de línea\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"En línea\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Combinación de colores:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Colorear barra de relación\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Usar colores personalizados para la barra de proporción.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Colorear los botones likes y dislike\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Usar colores personalizados para los íconos de los botones.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Lo único que mantiene la extensión en funcionamiento son sus donaciones, considere apoyar el proyecto.\"\n  },\n  \"customColors\": {\n    \"message\": \"Colores personalizados para la barra y los botones que no me gustan\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Formatos de números personalizados\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Desactiva el registro de respuestas de la API en la consola.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Desactivar registro en la consola\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Recupera la característica de los «dislikes» («No me gusta»)\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Ajustes\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Historial de cambios\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Donar\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"P+F\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Ayuda\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Página web\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Formato de número:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Funciones premium disponibles para patrocinadores en Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Iniciar sesión con Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"No se pudo completar el inicio de sesión. Inténtalo de nuevo.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"No se pudo iniciar el inicio de sesión de Patreon. Inténtalo de nuevo.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Cerrar sesión\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"El inicio de sesión requiere el permiso «identity». Permítelo para continuar.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Nivel básico\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Comprobando membresía…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Sin membresía activa\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Patrocinador Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Patrocinador\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Usuario de Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Reformatear como números\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Haz que el formato de like y dislike sea consistente.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Ocultar panel del teaser Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"No mostrar el teaser Premium en las páginas de reproducción de YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Mostrar números redondeados hacia abajo\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Redondear números hacia abajo (comportamiento predeterminado de YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Soporte de YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Porcentaje en el tooltip de la barra de like/dislike.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Mostrar porcentaje en el tooltip de la barra de like/dislike.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Esquema de colores:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Clásico\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Accesible\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neón\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Cambiar color de barra de votos\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Utiliza colores personalizados para la barra de votos.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Cambiar color de iconos de pulgares\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Utiliza colores personalizados para los iconos de los pulgares.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Unificar formato de votos\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Cambia el formato de las cifras de «Me gusta» y «No me gusta».\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"por Dmitry Selivanov y la comunidad\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"desactivado por el propietario\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Formato de cifras:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Redondear cifras (comportamiento original de YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Muestra las estadísticas redondeadas.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Desactivar envío de votos\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Deja de contar tus «Me gusta» y «No me gusta».\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"no disponible temporalmente\"\n  },\n  \"textUpdate\": {\n    \"message\": \"actualizar a\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Modo de porcentaje:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Usar visualización de porcentaje personalizada al pasar el puntero.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Versión __RYD_VERSION__ instalada\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Novedades\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Información premium del video\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Interacción a lo largo del tiempo\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Países principales\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Países con más Me gusta\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Países con más No me gusta\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografía\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Expandir\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Contraer\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Métrica del mapa\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Volver al mapa mundial\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Me gusta\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"No me gusta\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Proporción\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Ventana de datos\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Últimos días\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Primeros días\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Mostrar los días más recientes dentro de la ventana seleccionada\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Mostrar los primeros días dentro de la ventana seleccionada\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Selección personalizada\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Todo el tiempo\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Últimos $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Primeros $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 día\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 días\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Todo el tiempo\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Periodo seleccionado: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Usuarios de la extensión\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 me gusta\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 no me gusta\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Se capturaron $1 interacciones de $2 países ($3 IP únicas)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Votos por hora\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Votos por día\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Votos por semana\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Votos cada $1 horas\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Votos cada $1 días\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Votos cada $1 minutos\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Votos por minuto\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Votos cada $1 segundos\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Votos por segundo\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Aún no hay datos\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Desconocido\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Cargando información…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Vuelve a autorizar Patreon para desbloquear las analíticas.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Las analíticas premium están disponibles para los patrocinadores activos de Patreon.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"La sesión expiró. Inicia sesión con Patreon nuevamente.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"El servicio de analíticas no está disponible en este momento.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"No se puede conectar con el servicio de analíticas.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"No se pueden cargar las analíticas premium.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Actividad\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Proporción de me gusta\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Mayor proporción de me gusta\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Menor proporción de me gusta\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Más Me gusta\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Menos Me gusta\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Más No me gusta\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Menos No me gusta\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Más $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Menos $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Me gusta: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"No me gusta: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Proporción de me gusta: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Resumen de insights de dislikes\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Los dislikes sin procesar son reportes directos de usuarios de la extensión que ven este video.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Desbloquear Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Ver una vista previa de las analíticas completas\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Conteo de dislikes sin procesar\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Dislikes estimados\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Likes estimados\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Obteniendo los últimos totales de dislikes…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"No pudimos cargar los datos de dislikes en este momento. Intenta recargar la página.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Cómo desbloquear las analíticas Premium\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Desbloquea las insights premium del video en tres pasos rápidos:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Inicia sesión con Patreon desde la ventana emergente de la extensión Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Selecciona el nivel Premium para activar las líneas de tiempo de actividad, los rankings por país y el mapa interactivo.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Recarga YouTube para ver las tendencias por hora, los países principales y el desglose por estados de EE. UU. para cada video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Análisis premium bloqueados\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Tu membresía de Patreon no incluye el nivel Premium. Mejora tu aporte para desbloquear análisis avanzados de videos.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Mejorar en Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Registro de cambios de Analytics Premium · 20 de octubre de 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Lanzamiento Premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Analytics Premium, reinventadas\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Explora la cronología de actividades en vivo, la tabla de clasificación de los principales países y el mapa interactivo incluidos con Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Publicado el 20 de octubre de 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Actualízate en Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Leer la guía de configuración\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Novedades\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Cronograma de actividad en vivo\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Vea la transmisión de Me gusta y No me gusta en tiempo real. Cambie los ajustes preestablecidos por hora, por día o de todos los tiempos sin recargar, fije la ventana al día uno o a las últimas votaciones y elimine cada pico para ver qué desencadenó la participación.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Mundo interactivo y mapa de calor de EE. UU.\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Desplácese por todo el mundo o sumérjase en los estados de EE. UU. para descubrir puntos críticos. Compare los gustos, disgustos y proporciones en la información sobre herramientas actualizada y destaque las regiones que reaccionan primero a sus cargas.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Clasificación de los mejores países\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Clasifique cada país por actividad bruta, gustos, disgustos o proporción. Filtre con los mismos ajustes preestablecidos que la línea de tiempo para saber dónde aterrizan las campañas y planificar qué publicar a continuación.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Captura en breve: toma esta vista con DevTools cuando esté lista.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Marcador: vista general de la cronología\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Marcador: mapa de actividad global\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Marcador de posición: clasificación de países\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Cómo desbloquear Analytics Premium\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"¿Listo para explorar? Sigue estos pasos y actualiza tu pestaña de YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Abre el popup de Return YouTube Dislike e inicia sesión con Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Elige el nivel Premium para activar el flujo de datos analíticos en tu cuenta.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Recarga cualquier página de YouTube: la cronología, el mapa y los rankings aparecerán al instante.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Aspectos destacados del lanzamiento\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Los ajustes preestablecidos de la línea de tiempo de actividad transmiten datos en vivo con anclajes de rango para el primer y último segmento.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"La tabla de clasificación de países se actualiza instantáneamente para que puedas detectar regiones en ascenso sin salir del panel.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"El mapa interactivo destaca los puntos críticos globales y estatales de EE. UU. con información sobre herramientas y métricas más claras.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"¿Necesitas ayuda?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Estamos aquí si surge algún problema: lleva tus dudas a la documentación o a la comunidad.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Pregunta a la comunidad\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logotipo de Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/fr/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"État de l’API :\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Hors ligne\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"En ligne\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Schéma de couleur:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Coloriser la barre de ratio\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Utilisez des couleurs personnalisées pour la barre de ratio.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Mettre en couleur les boutons like et dislike\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Utiliser des couleurs personnalisées pour les boutons like et dislike.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"La seule chose qui permet à l'extension de fonctionner est vos dons, veuillez envisager de soutenir le projet.\"\n  },\n  \"customColors\": {\n    \"message\": \"Couleurs personnalisées pour la barre du dislike et les boutons\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Formats numériques personnalisés\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Désactive la journalisation des réponses de l’API dans la console.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Désactiver la journalisation dans la console\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Ré-affiche les pouces rouges/dislikes des vidéos\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Paramètres\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Journal des modifications\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Faire un don\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"Questions fréquentes\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Aide\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Site web\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Format numérique:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Fonctionnalités premium disponibles pour les soutiens sur Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Se connecter avec Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Échec de la connexion. Veuillez réessayer.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Échec du démarrage de la connexion Patreon. Veuillez réessayer.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Se déconnecter\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"La connexion nécessite l’autorisation «identity». Veuillez l’accorder pour continuer.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Niveau de base\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Vérification de l'abonnement…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Aucun abonnement actif\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Soutien Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Soutien\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Utilisateur Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Formater le nombre de likes\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Rend la valeur des statistiques plus cohérente.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Masquer le panneau d’aperçu Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Ne pas afficher l’aperçu Premium sur les pages de lecture YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Afficher les nombres arrondis\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Arrondir les nombres à la baisse (comportement par défaut de YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Prise en charge des YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Pourcentage dans l’infobulle de la barre J’aime/Je n’aime pas.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Afficher le pourcentage dans l’infobulle de la barre J’aime/Je n’aime pas.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Thème de couleur :\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Classique\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Accessible\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Néon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Coloriser la barre de ratio\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Utilisez des couleurs personnalisées pour la barre de ratio.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Coloriser les pouces\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Utiliser des couleurs personnalisées pour les icônes des pouces.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Rendre le format des likes et des dislikes cohérent\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Reformater le nombre de likes.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"par Dmitry Selivanov & Communauté\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Désactivé par le créateur\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Format numérique :\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Arrondir vers le bas les statistiques de like/dislike (comportement par défaut de YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Afficher les statistiques arrondies vers le bas.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Désactiver l'envoi des likes/dislikes\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Arrête de compter les likes et les dislikes mis sur les vidéos.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"temporairement indisponible\"\n  },\n  \"textUpdate\": {\n    \"message\": \"mettre à jour vers\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Mode de pourcentage :\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Utiliser un affichage de pourcentage personnalisé au survol.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Version __RYD_VERSION__ installée\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Quoi de neuf\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Analyses vidéo Premium\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Engagement dans le temps\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Pays principaux\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Pays avec le plus de mentions J’aime\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Pays avec le plus de mentions Je n’aime pas\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Géographie\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Développer\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Réduire\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Métrique de la carte\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Retour à la carte mondiale\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"J’aime\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Je n’aime pas\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Ratio\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Fenêtre de données\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Derniers jours\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Premiers jours\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Afficher les jours les plus récents dans la fenêtre sélectionnée\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Afficher les premiers jours dans la fenêtre sélectionnée\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Sélection personnalisée\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Depuis le début\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Derniers $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Premiers $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 jour\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 jours\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Depuis le début\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1j\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Période sélectionnée : $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Utilisateurs de l’extension\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 mentions J’aime\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 mentions Je n’aime pas\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$1 interactions enregistrées depuis $2 pays ($3 adresses IP uniques)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Votes par heure\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Votes par jour\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Votes par semaine\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Votes toutes les $1 heures\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Votes tous les $1 jours\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Votes toutes les $1 minutes\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Votes par minute\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Votes toutes les $1 secondes\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Votes par seconde\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Pas encore de données\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Inconnu\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Chargement des analyses…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Veuillez réautoriser Patreon pour débloquer les analyses.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Les analyses premium sont disponibles pour les soutiens Patreon actifs.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Session expirée. Connectez-vous de nouveau avec Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Le service d’analyses est actuellement indisponible.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Impossible de se connecter au service d’analyses.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Impossible de charger les analyses premium.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Activité\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Ratio de J’aime\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Ratio de J’aime plus élevé\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Ratio de J’aime plus faible\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Plus de J’aime\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Moins de J’aime\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Plus de Je n’aime pas\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Moins de Je n’aime pas\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Plus de $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Moins de $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"J’aime : $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Je n’aime pas : $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Ratio de J’aime : $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Aperçu des insights des Je n’aime pas\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Les Je n’aime pas bruts sont les signalements directs des utilisateurs de l’extension qui regardent cette vidéo.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Débloquer Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Prévisualiser toutes les analyses\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Nombre de Je n’aime pas bruts\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Je n’aime pas estimés\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"J’aime estimés\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Récupération des derniers totaux de Je n’aime pas…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Impossible de charger les données de Je n’aime pas pour le moment. Veuillez recharger la page.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Comment débloquer les analyses Premium\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Débloquez les analyses vidéo Premium en trois étapes rapides :\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Connectez-vous avec Patreon depuis la fenêtre de l’extension Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Choisissez la formule Premium pour activer les chronologies d’activité, les classements par pays et la carte interactive.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Rechargez YouTube pour voir les tendances horaires, les pays principaux et le détail par États américains pour chaque vidéo.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Analyses Premium bloquées\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Votre adhésion Patreon n’inclut pas le niveau Premium. Améliorez votre soutien pour débloquer les analyses vidéo avancées.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Passer au niveau supérieur sur Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Journal des nouveautés Analytics Premium · 20 octobre 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Version Premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Des analyses Premium réinventées\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Explorez la chronologie des activités en direct, le classement des principaux pays et la carte interactive inclus avec Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Publié le 20 octobre 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Passer à Patreon Premium\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Lire le guide d’installation\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Nouveautés\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Chronologie des activités en direct\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Regardez les likes et les dégoûts en temps réel. Basculez entre les préréglages horaires, quotidiens ou permanents sans recharger, ancrez la fenêtre au premier jour ou aux derniers votes et éliminez chaque pic pour voir ce qui a déclenché l'engagement.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Carte thermique interactive du monde et des États-Unis\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Parcourez le monde ou plongez dans les États américains pour faire surface des points chauds. Comparez les goûts, les aversions et les ratios dans l'info-bulle mise à jour et mettez en évidence les régions qui réagissent en premier à vos téléchargements.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Classement des meilleurs pays\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Classez chaque pays par activité brute, goûts, dégoûts ou ratio. Filtrez avec les mêmes préréglages que la chronologie pour savoir où atterrissent les campagnes et planifier ce qu'il faut publier ensuite.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Capture à venir — enregistrez cette vue dans DevTools lorsque vous serez prêt.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Espace réservé : vue d’ensemble de la chronologie\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Espace réservé : carte d’activité mondiale\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Espace réservé : classement des pays\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Comment débloquer les analyses Premium\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Envie de vous lancer ? Suivez ces étapes et actualisez ensuite votre onglet YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Ouvrez le pop-up Return YouTube Dislike et connectez-vous avec Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Choisissez le palier Premium pour activer l’acheminement des données d’analyse vers votre compte.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Rechargez n’importe quelle page YouTube : la chronologie, la carte et les classements apparaissent instantanément.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Points forts de la version\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Les préréglages de la chronologie des activités diffusent des données en direct avec des ancres de plage pour le premier et le dernier compartiment.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Le classement des pays s'actualise instantanément afin que vous puissiez repérer les régions en hausse sans quitter le panneau.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"La carte interactive met en évidence les points chauds du monde et des États américains avec des info-bulles et des mesures plus claires.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Besoin d’aide ?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Nous sommes là en cas de blocage — consultez la documentation ou la communauté.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Demander à la communauté\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo de Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/hu/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API állapot:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Színtéma:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Aránysáv színezése\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Egyéni színek használata az aránysávhoz.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Tetszik/Nem tetszik gombok színezése\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Egyéni színek használata a gomb ikonokhoz.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Ez a bővítmény kizárólag a támogatásoknak köszönhetően működik. Kérjük, támogasd a projektet!\"\n  },\n  \"customColors\": {\n    \"message\": \"Egyéni színek a dislike csíkon és gombon\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Egyéni számformátum\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Letiltja az API‑válaszok konzolos naplózását.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Konzolnaplózás letiltása\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Visszaállítja a dislike számlálót\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Beállítások\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Változásnapló\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Támogatás\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"GYIK\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Segítség\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Weboldal\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Számformátum:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Prémium funkciók Patreon‑támogatóknak érhetők el\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Bejelentkezés Patreonnal\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"A bejelentkezés nem sikerült. Próbáld újra.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"A Patreon bejelentkezés indítása sikertelen. Próbáld újra.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Kijelentkezés\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"A bejelentkezéshez „identity” engedély szükséges. Kérjük, engedélyezd.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Alap szint\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Tagság ellenőrzése…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Nincs aktív tagság\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Prémium támogató\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Támogató\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon felhasználó\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Kedvelésszám újraformázása\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Tedd egységessé a kedvelés/nem tetszik formátumát.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Prémium előnézeti panel elrejtése\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Ne jelenjen meg a prémium előnézet a YouTube lejátszó oldalain.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Lekerekített számok megjelenítése\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Számok lefelé kerekítése (YouTube alapértelmezett).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTube Shorts támogatás\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Százalék a like/dislike sáv buborékjában.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Százalék megjelenítése a tetszik/nem tetszik sáv buborékában.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Színtéma:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klasszikus\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Akadálymentes\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Aránysáv színezése\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Egyéni színek használata az aránysávhoz.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Hüvelykujjas ikonok színezése\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Egyéni színek használata a hüvelykujjas ikonokhoz.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"A kedvelések és nem tetszések formátumának egységesítése\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"A kedvelésszám újraformázása.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"Készítő: Dmitry Selivanov & Community\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"tulajdonos által kikapcsolva\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Számformátum:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Lekerekített kedvelés/nem tetszik statisztika (YouTube alapértelmezés)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Lekerekített statisztikák megjelenítése.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Like/Dislike beküldés kikapcsolása\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Nem számolja tovább a like és dislike adataidat.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"temp n/a\"\n  },\n  \"textUpdate\": {\n    \"message\": \"frissítés erre:\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Százalék mód:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Egyéni százalékos megjelenítés használata rámutatáskor.\"\n  },\n  \"version30installed\": {\n    \"message\": \"3.0.0.1 verzió telepítve\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Újdonságok\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Prémium videóelemzések\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Elköteleződés időben\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Legjobb országok\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Legtöbb kedveléssel rendelkező országok\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Legtöbb nem tetszéssel rendelkező országok\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Földrajz\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Kibontás\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Összecsukás\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Térképmutató\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Vissza a világtérképre\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Kedvelések\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Nem tetszések\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Arány\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Adat időablak\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Legutóbbi napok\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Első napok\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"A kiválasztott időablak legfrissebb napjainak megjelenítése\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"A kiválasztott időablak legkorábbi napjainak megjelenítése\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Egyéni választás\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Teljes időszak\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Utolsó $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Első $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 nap\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 nap\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Teljes időszak\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1n\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Kiválasztott időszak: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Bővítmény felhasználói\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 kedvelés\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 nem tetszés\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$2 országból $3 egyedi IP-vel $1 interakció rögzítve\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Szavazatok óránként\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Szavazatok naponta\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Szavazatok hetente\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Szavazatok $1 óránként\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Szavazatok $1 naponként\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Szavazatok $1 percenként\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Szavazatok percenként\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Szavazatok $1 másodpercenként\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Szavazatok másodpercenként\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Még nincs adat\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Ismeretlen\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Elemzések betöltése…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Az elemzések feloldásához engedélyezze újra a Patreont.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"A prémium elemzések az aktív Patreon-támogatók számára érhetők el.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"A munkamenet lejárt. Jelentkezzen be újra a Patreonnal.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Az elemzőszolgáltatás jelenleg nem elérhető.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Nem sikerült elérni az elemzőszolgáltatást.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Nem sikerült betölteni a prémium elemzéseket.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktivitás\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Kedvelési arány\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Magasabb kedvelési arány\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Alacsonyabb kedvelési arány\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Több kedvelés\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Kevesebb kedvelés\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Több nem tetszés\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Kevesebb nem tetszés\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Több $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Kevesebb $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Kedvelések: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Nem tetszések: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Kedvelési arány: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Nem tetszések összefoglalója\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"A nyers nem tetszések az ezt a videót néző bővítményfelhasználók közvetlen jelentései.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Prémium feloldása\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Teljes elemzés megtekintése\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Nyers nem tetszések száma\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Becsült nem tetszések\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Becsült kedvelések\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Legfrissebb nemtetszés-összegek lekérése…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"A nemtetszés-adatok betöltése most nem sikerült. Próbálja meg újratölteni az oldalt.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Hogyan oldhatók fel a prémium elemzések\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Három gyors lépésben oldja fel a prémium videóelemzéseket:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Jelentkezzen be Patreon-fiókkal a Return YouTube Dislike bővítmény felugró ablakából.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Válassza a Premium szintet az aktivitási idővonalak, az országos ranglisták és az interaktív térkép bekapcsolásához.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Töltse újra a YouTube-ot, hogy minden videónál lássa az óránkénti trendeket, a vezető országokat és az amerikai államok lebontását.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"A prémium analitika zárolva\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"A Patreon tagságod nem tartalmazza a Premium szintet. Frissítsd a támogatásodat, hogy elérd a fejlett videóanalitikát.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Frissítés a Patreonon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Premium Analytics változásnapló · 2025. október 20\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Prémium kiadás\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Prémium elemzés, újragondolva\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Fedezze fel az élő tevékenységek idővonalát, a legjobb országok ranglistáját és az interaktív térképet, amely a Return YouTube Dislike Premiumhoz tartozik.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Megjelenés: 2025. október 20\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Frissítés a Patreonon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Olvassa el a beállítási útmutatót\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Újdonságok\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Élő tevékenység idővonala\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Nézze meg a tetszésnyilvánításokat és a nemtetszéseket valós időben. Változtassa meg az óránkénti, napi vagy minden idők előre beállított értékeit újratöltés nélkül, rögzítse az ablakot az első naphoz vagy a legutóbbi szavazatokhoz, és súrolja meg az összes tüskét, hogy megtudja, mi váltotta ki az elköteleződést.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interaktív világ és amerikai hőtérkép\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Pásztázzon szerte a világon, vagy merüljön el az Egyesült Államok államaiba, hogy felszínre kerüljön a hotspotok. Hasonlítsa össze a kedveléseket, nemtetszéseket és arányokat a frissített elemleírásban, és emelje ki azokat a régiókat, amelyek először reagálnak a feltöltéseire.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"A legjobb országok ranglistája\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Rangsoroljon minden országot nyers tevékenység, tetszésnyilvánítás, nemtetszés vagy arány szerint. Szűrjön ugyanazokkal az előre beállított értékekkel, mint az idővonalon, hogy megtudja, hol érnek el a kampányok, és megtervezheti, mit tegyen közzé legközelebb.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Képernyőkép hamarosan – rögzítse ezt a nézetet a DevToolsban, ha készen áll.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Helyőrző: idővonal áttekintése\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Helyőrző: globális tevékenységtérkép\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Helyőrző: országok ranglista\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"A Premium Analytics feloldása\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Készen állsz a merülésre? Kövesse ezeket a gyors lépéseket, és frissítse a YouTube lapot.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Nyissa meg a YouTube Nem tetszik visszaküldése előugró ablakot, és jelentkezzen be a Patreonnal.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Válassza a prémium szintet, hogy engedélyezze az analitikai adatok streamelését a fiókjába.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Töltse be újra bármelyik YouTube-videóoldalt – az idővonal, a térkép és a ranglisták azonnal megjelennek.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Kiemelések kiadása\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"A tevékenység idővonala előre beállítja az élő adatok közvetítését az első és a legújabb gyűjtőcsoportokhoz tartozó tartományhorgonyokkal.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Az ország ranglistája azonnal frissül, így a panel elhagyása nélkül is észreveheti az emelkedő régiókat.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Az interaktív térkép világosabb eszközleírásokkal és mérőszámokkal kiemeli a globális és az Egyesült Államok államainak hotspotjait.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Kézre van szüksége?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Itt vagyunk, ha akadozik – forduljon kérdéseit a támogatási dokumentumokhoz vagy a közösséghez.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Kérdezd meg a közösséget\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike logója\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/id/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Status API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Warnai tema:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Warnai bar rasio\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Gunakan warna kustom untuk rasio bar.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Warnai jempol\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Gunakan warna kustom untuk ikon jempol.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Donasimulah yang hanya bisa membuat extension ini terus berjalan, tolong pertimbangkan untuk mendukung proyek ini.\"\n  },\n  \"customColors\": {\n    \"message\": \"Warna kustom untuk bar dislike dan tombol\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Format angka kustom\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Menonaktifkan pencatatan respons API di konsol.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Nonaktifkan log ke konsol\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Mengembalikan kemampuan untuk melihat jumlah dislike\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Pengaturan\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Log Perubahan\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Donasi\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"Pertanyaan\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Bantuan\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Website\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Format angka:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Fitur premium tersedia bagi pendukung di Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Masuk dengan Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Gagal menyelesaikan login. Coba lagi.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Gagal memulai login Patreon. Coba lagi.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Keluar\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Masuk memerlukan izin \\\"identity\\\". Mohon izinkan untuk melanjutkan.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Tingkat dasar\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Memeriksa keanggotaan…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Tidak ada keanggotaan aktif\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Pendukung Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Pendukung\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Pengguna Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Format ulang jumlah like\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Membuat format like dan dislike konsisten.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Sembunyikan panel teaser premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Jangan tampilkan teaser premium di halaman tonton YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Munculkan angka yang dibulatkan kebawah\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Bulatkan angka kebawah (bawaan YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Mendukung YouTube Short\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Persentase di tooltip bilah like/dislike.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Tampilkan persentase di tooltip bilah like/dislike.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Warnai tema:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klasik\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Mudah diakses\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Warnai bar rasio\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Gunakan warna kustom bar rasio.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Warnai jempol\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Gunakan warna kustom untuk ikon jempol.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Membuat format like dan dislike konsisten\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Format ulang jumlah like.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"oleh Dmitry Selivanov & Komunitas\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Dimatikan oleh Pemilik\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Format angka:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Membulatkan data like/dislike kebawah (bawaan YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Munculkan data pembulatan kebawah.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Matikan pengiriman like/dislike\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Memberhentikan penghitungan like dan dislikemu\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Tidak Ada Untuk Sementara Waktu\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Update ke\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Mode persentase:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Gunakan tampilan persentase kustom saat diarahkan.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Versi __RYD_VERSION__ terinstall\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Apa yang baru\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Insight video premium\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Keterlibatan dari waktu ke waktu\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Negara teratas\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Negara dengan suka terbanyak\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Negara dengan tidak suka terbanyak\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografi\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Perluas\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Ciutkan\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Metrik peta\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Kembali ke peta dunia\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Suka\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Tidak suka\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Rasio\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Rentang data\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Hari terbaru\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Hari pertama\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Tampilkan hari terbaru dalam rentang yang dipilih\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Tampilkan hari paling awal dalam rentang yang dipilih\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Pilihan kustom\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Sepanjang waktu\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"$1 terakhir\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"$1 pertama\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 hari\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 hari\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Sepanjang waktu\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1h\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Periode yang dipilih: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Pengguna ekstensi\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 suka\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 tidak suka\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Tercatat $1 interaksi dari $2 negara ($3 IP unik)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Suara per jam\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Suara per hari\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Suara per minggu\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Suara setiap $1 jam\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Suara setiap $1 hari\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Suara setiap $1 menit\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Suara per menit\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Suara setiap $1 detik\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Suara per detik\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Belum ada data\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Tidak diketahui\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Memuat insight…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Otorisasi kembali Patreon untuk membuka analitik.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Analitik premium tersedia untuk pendukung Patreon aktif.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sesi berakhir. Masuk lagi dengan Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Layanan analitik sedang tidak tersedia.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Tidak dapat terhubung ke layanan analitik.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Tidak dapat memuat analitik premium.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktivitas\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Rasio suka\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Rasio suka lebih tinggi\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Rasio suka lebih rendah\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Lebih banyak suka\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Lebih sedikit suka\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Lebih banyak tidak suka\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Lebih sedikit tidak suka\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Lebih banyak $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Lebih sedikit $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Suka: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Tidak suka: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Rasio suka: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Ringkasan insight tidak suka\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Tidak suka mentah adalah laporan langsung dari pengguna ekstensi yang menonton video ini.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Buka Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Pratinjau analitik lengkap\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Jumlah tidak suka mentah\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Perkiraan tidak suka\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Perkiraan suka\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Mengambil total tidak suka terbaru…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Data tidak suka tidak dapat dimuat saat ini. Coba muat ulang halaman.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Cara membuka analitik Premium\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Buka insight video Premium dalam tiga langkah cepat:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Masuk dengan Patreon dari pop-up ekstensi Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Pilih tingkat Premium untuk mengaktifkan linimasa aktivitas, peringkat negara, dan peta interaktif.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Muat ulang YouTube untuk melihat tren per jam, negara teratas, dan detail per negara bagian AS untuk setiap video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Analitik premium terkunci\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Keanggotaan Patreon Anda belum mencakup tingkat Premium. Tingkatkan dukungan Anda untuk membuka analitik video lanjutan.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Tingkatkan di Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Log Perubahan Analisis Premium · 20 Oktober 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Rilis Premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Analisis premium, dirancang ulang\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Jelajahi garis waktu aktivitas langsung, papan peringkat negara teratas, dan peta interaktif yang disertakan dengan Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Dirilis pada 20 Oktober 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Tingkatkan di Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Baca panduan pengaturan\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Apa yang baru\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Garis waktu aktivitas langsung\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Tonton streaming suka dan tidak suka secara real-time. Alihkan preset per jam, harian, atau sepanjang waktu tanpa memuat ulang, jangkar jendela ke hari pertama atau pemungutan suara terbaru, dan bersihkan setiap lonjakan untuk melihat apa yang memicu keterlibatan.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Dunia interaktif & peta panas AS\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Jelajahi dunia atau jelajahi negara bagian AS untuk menemukan hotspot. Bandingkan suka, tidak suka, dan rasio di tooltip yang ditingkatkan dan soroti wilayah yang bereaksi pertama kali terhadap upload Anda.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Papan peringkat negara teratas\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Beri peringkat setiap negara berdasarkan aktivitas mentah, kesukaan, ketidaksukaan, atau rasio. Filter dengan preset yang sama dengan timeline untuk mempelajari lokasi kampanye dan merencanakan apa yang akan dipublikasikan selanjutnya.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Tangkapan layar segera hadir—ambil gambar ini di DevTools jika sudah siap.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Placeholder: ikhtisar garis waktu\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Placeholder: peta aktivitas global\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Placeholder: papan peringkat negara\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Cara membuka kunci analitik Premium\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Siap untuk terjun? Ikuti langkah cepat ini dan segarkan tab YouTube Anda.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Buka popup Kembalikan YouTube Dislike dan masuk dengan Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Pilih tingkat Premium untuk mengaktifkan streaming data analitik ke akun Anda.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Muat ulang laman tontonan YouTube mana pun—garis waktu, peta, dan papan peringkat Anda langsung muncul.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Sorotan rilis\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Preset garis waktu aktivitas mengalirkan data langsung dengan jangkar rentang untuk keranjang pertama dan terbaru.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Papan peringkat negara langsung diperbarui sehingga Anda dapat melihat wilayah yang meningkat tanpa perlu keluar dari panel.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Peta interaktif menyoroti hotspot global dan negara bagian AS dengan tooltip dan metrik yang lebih jelas.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Butuh bantuan?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Kami siap membantu jika Anda mengalami kesulitan—sampaikan pertanyaan Anda ke dokumen atau komunitas dukungan.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Tanyakan pada komunitas\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/it/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Stato API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Colore tema:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Colora barra di rapporto\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Usa colori personalizzati per la barra di rapporto.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Colora pollici\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Usa colori personalizzati per le icone dei pollici.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"L'unica cosa che manda avanti questa estensione sono le tue donazioni, valuta se sostenere il progetto.\"\n  },\n  \"customColors\": {\n    \"message\": \"Colori personalizzati per barra dei Non mi piace e bottoni\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Formato numeri personalizzato\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Disattiva la registrazione delle risposte API nella console.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Disattiva log sulla console\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Riporta il numero di Non mi piace\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Impostazioni\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Note di versione\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Dona\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Aiuto\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Sito web\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Formato numeri:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Funzionalità premium disponibili per i sostenitori su Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Accedi con Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Impossibile completare l’accesso. Riprova.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Impossibile avviare l’accesso con Patreon. Riprova.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Esci\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"L’accesso richiede l’autorizzazione \\\"identity\\\". Concedila per continuare.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Livello base\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Verifica dell'abbonamento…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Nessuna iscrizione attiva\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Sostenitore Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Sostenitore\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Utente Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Ri-formatta numero Mi piace\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Rendi uniforme il formato di Mi piace e Non mi piace.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Nascondi il pannello teaser Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Non mostrare il teaser Premium nelle pagine di riproduzione di YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Mostra numeri arrotondati per difetto\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Arrotonda numeri per difetto (comportamento standard di YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Supporto per YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Percentuale nel suggerimento della barra mi piace/non mi piace.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Mostra la percentuale nel suggerimento della barra mi piace/non mi piace.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Colore tema:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Classico\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Accessibile\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Colora barra di rapporto\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Usa colori personalizzati per la barra di rapporto.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Colora pollici\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Usa colori personalizzati per le icone dei pollici.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Rendi uniforme il formato di Mi piace e Non mi piace.\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Ri-formatta numero di Mi piace.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"di Dmitry Selivanov e della comunità\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Disattivati dal proprietario\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Formato numeri:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Arrotonda per difetto il numero di Mi Piace/Non mi piace (comportamento standard di YouTube).\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Mostra stastistiche arrotondate per difetto.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Disattiva invio di Mi piace/Non mi piace\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Smette di contare i tuoi Mi piace e Non mi piace.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Temporaneamente non disponibile\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Aggiorna a\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Modalità percentuale:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Usa una visualizzazione personalizzata della percentuale al passaggio del mouse.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Versione __RYD_VERSION__ installata\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Novità\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Analisi video Premium\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Coinvolgimento nel tempo\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Paesi principali\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Paesi con più Mi piace\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Paesi con più Non mi piace\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografia\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Espandi\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Comprimi\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Metrica della mappa\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Torna alla mappa mondiale\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Mi piace\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Non mi piace\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Rapporto\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Finestra dati\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Ultimi giorni\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Primi giorni\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Mostra i giorni più recenti nella finestra selezionata\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Mostra i primi giorni nella finestra selezionata\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Selezione personalizzata\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Tutto il periodo\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Ultimi $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Primi $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 giorno\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 giorni\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Tutto il periodo\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1g\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Periodo selezionato: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Utenti dell’estensione\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 Mi piace\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 Non mi piace\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Registrate $1 interazioni da $2 paesi ($3 IP univoche)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Voti per ora\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Voti per giorno\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Voti per settimana\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Voti ogni $1 ore\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Voti ogni $1 giorni\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Voti ogni $1 minuti\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Voti al minuto\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Voti ogni $1 secondi\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Voti al secondo\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Nessun dato ancora\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Sconosciuto\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Caricamento delle analisi…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Autorizza nuovamente Patreon per sbloccare le analisi.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Le analisi premium sono disponibili per i sostenitori attivi su Patreon.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sessione scaduta. Accedi di nuovo con Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Il servizio di analisi non è al momento disponibile.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Impossibile contattare il servizio di analisi.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Impossibile caricare le analisi premium.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Attività\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Rapporto Mi piace\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Rapporto Mi piace più alto\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Rapporto Mi piace più basso\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Più Mi piace\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Meno Mi piace\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Più Non mi piace\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Meno Non mi piace\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Più $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Meno $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Mi piace: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Non mi piace: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Rapporto Mi piace: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Panoramica delle analisi dei Non mi piace\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"I Non mi piace grezzi sono segnalazioni dirette degli utenti dell’estensione che guardano questo video.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Sblocca Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Anteprima delle analisi complete\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Conteggio Non mi piace grezzi\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Non mi piace stimati\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Mi piace stimati\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Recupero degli ultimi totali di Non mi piace…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Non è stato possibile caricare i dati dei Non mi piace. Prova a ricaricare la pagina.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Come sbloccare le analisi Premium\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Sblocca le analisi video Premium in tre semplici passaggi:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Accedi con Patreon dal popup dell’estensione Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Seleziona il livello Premium per attivare le cronologie di attività, le classifiche per paese e la mappa interattiva.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Ricarica YouTube per vedere le tendenze orarie, i principali paesi e i dettagli sugli stati USA per ogni video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Analitiche Premium bloccate\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Il tuo abbonamento Patreon non include il livello Premium. Effettua un upgrade per sbloccare le analitiche video avanzate.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Esegui l’upgrade su Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Registro delle novità di Analytics Premium · 20 ottobre 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Versione Premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Analytics Premium reinventate\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Esplora la sequenza temporale delle attività dal vivo, la classifica dei principali paesi e la mappa interattiva inclusa in Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Rilasciato il 20 ottobre 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Esegui l’upgrade su Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Leggi la guida di configurazione\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Novità\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Cronologia delle attività dal vivo\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Guarda lo streaming di Mi piace e Non mi piace in tempo reale. Alterna le preimpostazioni orarie, giornaliere o di tutti i tempi senza ricaricare, ancora la finestra al primo giorno o agli ultimi voti e scorri ogni picco per vedere cosa ha attivato il coinvolgimento.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Mondo interattivo e mappa termica degli Stati Uniti\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Effettua una panoramica in tutto il mondo o tuffati negli stati degli Stati Uniti per individuare gli hotspot. Confronta Mi piace, Non mi piace e rapporti nella descrizione comando aggiornata e metti in evidenza le regioni che reagiscono per prime ai tuoi caricamenti.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Classifica dei principali paesi\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Classifica ogni paese in base all'attività grezza, alle preferenze, alle antipatie o al rapporto. Filtra con le stesse preimpostazioni della sequenza temporale per scoprire dove arrivano le campagne e pianificare cosa pubblicare successivamente.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Screenshot in arrivo: acquisisci questa vista con DevTools quando sarà pronta.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Segnaposto: panoramica della timeline\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Segnaposto: mappa dell’attività globale\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Segnaposto: classifica dei paesi\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Come sbloccare le Analytics Premium\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Pronto a iniziare? Segui questi passaggi e poi aggiorna la scheda di YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Apri il popup di Return YouTube Dislike e accedi con Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Scegli il livello Premium per attivare il flusso dei dati di analisi sul tuo account.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Ricarica una pagina video di YouTube: timeline, mappa e classifiche compaiono all’istante.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Punti salienti della release\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Le preimpostazioni della sequenza temporale delle attività trasmettono dati in tempo reale con ancoraggi di intervallo per il primo e l'ultimo bucket.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"La classifica dei paesi si aggiorna istantaneamente in modo da poter individuare le regioni in crescita senza uscire dal pannello.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"La mappa interattiva evidenzia gli hotspot statali globali e statunitensi con descrizioni comandi e metriche più chiare.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Serve aiuto?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Siamo qui per te: consulta la documentazione di supporto o la community.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Chiedi alla community\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/ja/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API の状態:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"オフライン\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"オンライン\"\n  },\n  \"colorTheme\": {\n    \"message\": \"配色：\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"嫌いなバーに色を付ける\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"嫌いなバーをカスタムカラーで色付けします。\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"ボタンに色を付ける\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"カスタムカラーのボタンの色が好きで嫌いです。\"\n  },\n  \"considerDonating\": {\n    \"message\": \"この拡張機能を存続させることができるのは,皆様からの寄付だけです。\"\n  },\n  \"customColors\": {\n    \"message\": \"低評価ボタンとバーの色のカスタマイズ\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"評価数の表示形式のカスタマイズ\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"コンソールでの API 応答のログ記録を無効にします。\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"コンソールへのログ出力を無効化\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"低評価を表示する機能を復元\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"設定\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"変更履歴\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"寄付\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"ヘルプ\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Webサイト\"\n  },\n  \"numberFormat\": {\n    \"message\": \"数値形式\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"プレミアム機能はPatreonサポーター向けに提供されています。\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Patreonでログイン\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"ログインを完了できませんでした。もう一度お試しください。\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Patreonログインの開始に失敗しました。もう一度お試しください。\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"ログアウト\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"ログインには「identity」権限が必要です。続行するには許可してください。\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"ベーシックプラン\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"メンバーシップを確認中…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"有効なメンバーシップはありません\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"プレミアム支援者\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"支援者\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreonユーザー\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"いいねの数をフォーマットする\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"統計値の一貫性を高めます。\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"プレミアムティーザーパネルを非表示にする\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"YouTube の再生ページにプレミアムティーザーを表示しません。\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"四捨五入された数値を表示\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"カウント値を四捨五入します（これはYouTubeのデフォルトオプションです）。\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTubeショートのサポート\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"高評価/低評価バーのツールチップにパーセントを表示。\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"高評価/低評価バーのツールチップにパーセントを表示します。\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"カラーテーマ:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"クラシック\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"アクセシブル\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"ネオン\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"比率バーを色付け\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"比率バーにカスタムカラーを使用します。\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"サムズアイコンを色付け\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"サムズアイコンにカスタムカラーを使用します。\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"高評価/低評価の表示形式を統一\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"高評価数の表記を再フォーマットします。\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"by Dmitry Selivanov & コミュニティ\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"投稿者により無効化\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"数値の形式:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"高評価/低評価の統計を切り下げ表示（YouTubeの既定）\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"切り下げた統計を表示します。\"\n  },\n  \"textSettings\": {\n    \"message\": \"高評価/低評価を表示しない\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"評価数のカウントを停止します。\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"一時的に利用不可\"\n  },\n  \"textUpdate\": {\n    \"message\": \"アップデート：\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"パーセント表示モード:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"ホバー時にカスタムのパーセント表示を使用する。\"\n  },\n  \"version30installed\": {\n    \"message\": \"バージョン __RYD_VERSION__ がインストールされました。\"\n  },\n  \"whatsnew\": {\n    \"message\": \"新機能\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"プレミアム動画インサイト\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"時間別エンゲージメント\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"上位の国\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"高評価が最も多い国\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"低評価が最も多い国\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"地域\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"展開\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"折りたたむ\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"マップ指標\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"世界地図に戻る\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"高評価\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"低評価\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"比率\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"データ期間\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"直近の日数\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"初期の日数\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"選択した期間内の直近の日を表示\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"選択した期間内の最初の日を表示\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"カスタム選択\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"全期間\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"直近$1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"最初の$1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1日\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1日\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"全期間\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1日\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"選択期間: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"拡張機能ユーザー\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1件の高評価\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1件の低評価\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$2か国から$3個の一意IPで$1件のインタラクションを取得\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"1時間あたりの投票数\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"1日あたりの投票数\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"1週間あたりの投票数\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"$1時間ごとの投票数\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"$1日ごとの投票数\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"$1分ごとの投票数\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"1分あたりの投票数\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"$1秒ごとの投票数\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"1秒あたりの投票数\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"データはまだありません\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"不明\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"インサイトを読み込み中…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"分析を有効にするにはPatreonの認証を再実行してください。\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"プレミアム分析はアクティブなPatreon支援者が利用できます。\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"セッションの有効期限が切れました。Patreonで再ログインしてください。\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"分析バックエンドは現在利用できません。\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"分析サービスに接続できません。\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"プレミアム分析を読み込めませんでした。\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"アクティビティ\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"高評価率\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"高評価率が高い\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"高評価率が低い\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"高評価が多い\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"高評価が少ない\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"低評価が多い\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"低評価が少ない\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"$1が多い\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"$1が少ない\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"高評価: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"低評価: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"高評価率: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"低評価インサイトの概要\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"未加工の低評価は、この動画を視聴している拡張機能ユーザーからの直接報告です。\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Premiumを有効化\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"すべての分析をプレビュー\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"未加工の低評価数\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"推定低評価\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"推定高評価\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"最新の低評価合計を取得中…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"低評価データを読み込めませんでした。ページを更新してください。\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Premium分析の有効化方法\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"3つのステップですぐにPremium動画インサイトを有効化:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Return YouTube Dislike拡張機能のポップアップからPatreonにサインインします。\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Premiumティアを選択して、アクティビティタイムライン、国別ランキング、インタラクティブマップを利用します。\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"YouTubeを再読み込みして、各動画の時間帯トレンド、上位国、米国州別の詳細を確認してください。\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"プレミアム分析はロックされています\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"ご利用中のPatreonメンバーシップにはプレミアム階層が含まれていません。上位プランにアップグレードして高度な動画分析を開放してください。\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Patreonでアップグレード\"\n  },\n  \"changelog_title\": {\n    \"message\": \"プレミアム分析変更ログ · 2025 年 10 月 20 日\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"プレミアムリリース\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"再考されたプレミアム分析\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Return YouTube Dislike Premium に含まれるライブ アクティビティ タイムライン、上位国のリーダーボード、インタラクティブ マップを探索してください。\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"2025 年 10 月 20 日発売\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Patreonでアップグレードする\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"セットアップガイドを読む\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"新着情報\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"ライブアクティビティのタイムライン\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"リアルタイムで好き嫌いのストリーミングを視聴します。リロードせずに時間ごと、毎日、または常時のプリセットを切り替え、ウィンドウを初日または最新の投票に固定し、すべてのスパイクをスクラブしてエンゲージメントのきっかけとなったものを確認します。\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"インタラクティブな世界と米国のヒートマップ\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"世界中を移動したり、米国の州に飛び込み、ホットスポットを表面化します。アップグレードされたツールチップでいいね、嫌い、比率を比較し、アップロードに最初に反応した地域にスポットライトを当てます。\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"上位国のリーダーボード\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"生のアクティビティ、好き嫌い、比率によってすべての国をランク付けします。タイムラインと同じプリセットでフィルタリングして、キャンペーンがどこに到達するかを確認し、次に何を公開するかを計画します。\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"スクリーンショットは近日公開予定です。準備ができたら、DevTools でこのビューをキャプチャしてください。\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"プレースホルダー: タイムラインの概要\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"プレースホルダー: グローバル アクティビティ マップ\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"プレースホルダー: 各国のリーダーボード\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"プレミアム分析のロックを解除する方法\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"飛び込む準備はできていますか?次の簡単な手順に従って、YouTube タブを更新します。\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"「Return YouTube Dislike」ポップアップを開き、Patreon にサインインします。\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"アカウントへの分析データのストリーミングを有効にするには、プレミアム レベルを選択します。\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"YouTube 再生ページをリロードすると、タイムライン、地図、リーダーボードが即座に表示されます。\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"リリースのハイライト\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"アクティビティ タイムライン プリセットは、最初と最新のバケットの範囲アンカーを使用してライブ データをストリーミングします。\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"国のリーダーボードは即座に更新されるため、パネルから離れることなく上昇している地域を見つけることができます。\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"インタラクティブなマップは、より明確なツールチップと指標を使用して、世界および米国の州のホットスポットを強調表示します。\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"手が必要ですか?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"問題が発生した場合は、サポート ドキュメントまたはコミュニティに質問してください。\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"コミュニティに質問する\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike のロゴ\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/ko/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API 상태:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"오프라인\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"온라인\"\n  },\n  \"colorTheme\": {\n    \"message\": \"컬러 테마:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"비율 막대 컬러화\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"비율 막대에 커스텀 색상을 사용합니다.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"엄지 컬러화\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"엄지 아이콘에 커스텀 색상을 사용합니다.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"확장 프로그램을 계속 돌아가게 할 수 있는 유일한 방법은 당신의 기부이며, 프로젝트에 지원하는 것을 고려해 주세요.\"\n  },\n  \"customColors\": {\n    \"message\": \"싫어요 바와 버튼의 색을 커스텀\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"숫자 표시 형식 커스텀\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"콘솔에서 API 응답 로깅을 비활성화합니다.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"콘솔 로그 비활성화\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"대략적인 싫어요 갯수를 볼 수 있게 합니다\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike 베타\"\n  },\n  \"legendSettings\": {\n    \"message\": \"설정\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"변경사항\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"기부\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"도움말\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"웹사이트\"\n  },\n  \"numberFormat\": {\n    \"message\": \"숫자 표시 형식:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"프리미엄 기능은 Patreon 후원자에게 제공됩니다\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Patreon으로 로그인\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"로그인을 완료하지 못했습니다. 다시 시도해 주세요.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Patreon 로그인 시작에 실패했습니다. 다시 시도해 주세요.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"로그아웃\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"로그인에는 'identity' 권한이 필요합니다. 계속하려면 허용하세요.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"기본 등급\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"멤버십 확인 중…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"활성 멤버십 없음\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"프리미엄 후원자\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"후원자\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon 사용자\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"좋아요 갯수 표시 형식 재정의\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"좋아요와 싫어요 형식을 일관되게 만듭니다.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"프리미엄 티저 패널 숨기기\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"YouTube 시청 페이지에서 프리미엄 티저를 표시하지 않습니다.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"반올림된 숫자 표시\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"반올림된 숫자(기본 유튜브 동작).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"유튜브 쇼츠 지원\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"좋아요/싫어요 막대 툴팁에 백분율 표시.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"좋아요/싫어요 막대 툴팁에 백분율을 표시합니다.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"컬러 테마:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"클래식\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Accessible\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"네온\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"비율 막대 컬러화\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"비율 막대에 커스텀 색상을 사용합니다.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"엄지 컬러화\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"엄지 아이콘에 커스텀 색상을 사용합니다.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"좋아요와 싫어요 형식을 일관되게 만듭니다\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"좋아요 갯수 표시 형식 재정의\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"Dmitry Selivanov와 커뮤니티 제작\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"업로더가 비활성화함\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"숫자 표시 형식:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"좋아요/싫어요 통계 비활성화(기본 YouTube 동작)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"반올림된 통계 보이기\"\n  },\n  \"textSettings\": {\n    \"message\": \"좋아요/싫어요 제출 비활성화\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"당신의 좋아요와 싫어요 갯수 계산을 중지합니다.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"일시적으로 사용할 수 없음\"\n  },\n  \"textUpdate\": {\n    \"message\": \"업데이트\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"백분율 모드:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"호버 시 사용자 지정 백분율 표시 사용.\"\n  },\n  \"version30installed\": {\n    \"message\": \"__RYD_VERSION__ 버전이 설치됨\"\n  },\n  \"whatsnew\": {\n    \"message\": \"새로운 점\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"프리미엄 동영상 인사이트\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"시간에 따른 참여도\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"상위 국가\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"좋아요가 가장 많은 국가\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"싫어요가 가장 많은 국가\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"지역\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"펼치기\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"접기\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"지도 지표\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"세계 지도 돌아가기\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"좋아요\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"싫어요\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"비율\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"데이터 기간\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"최근 일수\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"처음 일수\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"선택한 기간의 최신 일수를 표시\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"선택한 기간의 초기 일수를 표시\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"사용자 지정 선택\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"전체 기간\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"최근 $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"처음 $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1일\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1일\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"전체 기간\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1일\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"선택한 기간: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"확장 프로그램 사용자\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"좋아요 $1개\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"싫어요 $1개\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$2개 국가에서 $3개의 고유 IP로 $1건의 상호작용 수집\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"시간당 투표 수\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"일일 투표 수\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"주간 투표 수\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"$1시간마다 투표 수\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"$1일마다 투표 수\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"$1분마다 투표 수\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"분당 투표 수\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"$1초마다 투표 수\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"초당 투표 수\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"아직 데이터가 없습니다\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"알 수 없음\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"인사이트 불러오는 중…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"분석을 사용하려면 Patreon을 다시 인증하세요.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"프리미엄 분석은 Patreon 활성 후원자에게 제공됩니다.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"세션이 만료되었습니다. Patreon으로 다시 로그인하세요.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"분석 서비스가 현재 이용 불가입니다.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"분석 서비스에 연결할 수 없습니다.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"프리미엄 분석을 불러올 수 없습니다.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"활동\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"좋아요 비율\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"높은 좋아요 비율\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"낮은 좋아요 비율\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"좋아요 더 많음\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"좋아요 더 적음\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"싫어요 더 많음\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"싫어요 더 적음\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"$1 더 많음\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"$1 더 적음\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"좋아요: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"싫어요: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"좋아요 비율: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"싫어요 인사이트 한눈에 보기\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"원본 싫어요 수는 이 영상을 시청하는 확장 프로그램 사용자가 직접 보고한 값입니다.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"프리미엄 해제\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"전체 분석 미리보기\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"원본 싫어요 수\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"예상 싫어요\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"예상 좋아요\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"최신 싫어요 합계를 가져오는 중…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"지금은 싫어요 데이터를 불러올 수 없습니다. 페이지를 새로고침해 보세요.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"프리미엄 분석을 여는 방법\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"세 단계로 프리미엄 동영상 인사이트를 해제하세요:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Return YouTube Dislike 확장 프로그램 팝업에서 Patreon으로 로그인하세요.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"활동 타임라인, 국가 순위 및 인터랙티브 지도를 사용하려면 프리미엄 등급을 선택하세요.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"YouTube를 다시 로드하여 각 영상의 시간대별 추세, 주요 국가 및 미국 주별 세부 정보를 확인하세요.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"프리미엄 분석이 잠겨 있습니다\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"현재 Patreon 멤버십에는 프리미엄 등급이 포함되어 있지 않습니다. 후원을 업그레이드하여 고급 동영상 분석을 이용하세요.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Patreon에서 업그레이드\"\n  },\n  \"changelog_title\": {\n    \"message\": \"프리미엄 분석 변경 로그 · 2025년 10월 20일\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"프리미엄 릴리스\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"새롭게 재해석된 프리미엄 분석\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Return YouTube Dislike Premium에 포함된 실시간 활동 타임라인, 상위 국가 리더보드, 대화형 지도를 살펴보세요.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"2025년 10월 20일 출시\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Patreon에서 업그레이드하세요\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"설정 가이드 읽기\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"새로운 소식\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"실시간 활동 타임라인\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"좋아요 및 싫어요 스트리밍을 실시간으로 시청하세요. 다시 로드하지 않고 시간별, 일일 또는 전체 사전 설정을 전환하고, 창을 첫날 또는 최신 투표에 고정하고, 모든 스파이크를 스크러빙하여 무엇이 참여를 유발했는지 확인하세요.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"대화형 세계 및 미국 히트맵\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"전 세계를 탐색하거나 미국 주를 탐험하여 핫스팟을 찾아보세요. 업그레이드된 툴팁에서 좋아요, 싫어요, 비율을 비교하고 업로드에 가장 먼저 반응하는 지역을 집중 조명하세요.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"상위 국가 리더보드\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"원시 활동, 좋아요, 싫어요 또는 비율을 기준으로 모든 국가의 순위를 매깁니다. 타임라인과 동일한 사전 설정으로 필터링하여 캠페인이 시작되는 위치를 알아보고 다음에 게시할 내용을 계획하세요.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"스크린샷이 곧 공개됩니다. 준비되면 DevTools에서 이 보기를 캡처하세요.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"자리 표시자: 타임라인 개요\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"자리 표시자: 글로벌 활동 지도\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"자리 표시자: 국가 리더보드\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"프리미엄 분석을 잠금 해제하는 방법\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"다이빙할 준비가 되셨나요? 다음의 빠른 단계를 따르고 YouTube 탭을 새로고침하세요.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Return YouTube Dislike 팝업을 열고 Patreon에 로그인하세요.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"귀하의 계정으로 분석 데이터 스트리밍을 활성화하려면 프리미엄 등급을 선택하세요.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"YouTube 보기 페이지를 새로고침하세요. 타임라인, 지도, 리더보드가 즉시 표시됩니다.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"릴리스 하이라이트\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"활동 타임라인 사전 설정은 첫 번째 및 최신 버킷에 대한 범위 앵커를 사용하여 실시간 데이터를 스트리밍합니다.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"국가 리더보드는 즉시 새로 고쳐지므로 패널을 떠나지 않고도 상승 지역을 확인할 수 있습니다.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"대화형 지도는 보다 명확한 도구 설명과 측정항목을 통해 전 세계 및 미국 주의 핫스팟을 강조합니다.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"도움이 필요하세요?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"문제가 발생하면 저희가 도와드리겠습니다. 지원 문서나 커뮤니티에 질문을 가져오세요.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"커뮤니티에 물어보세요\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike 로고\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/nl/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API‑status:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Kleurenthema:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Verhoudingsbalk inkleuren\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Gebruik aangepaste kleuren voor de verhoudingsbalk.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Duimpjes inkleuren\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Gebruik aangepaste kleuren voor de duimpictogrammen.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Het enige dat de extensie draaiende houdt, zijn uw donaties. Overweeg alstublieft om het project te steunen.\"\n  },\n  \"customColors\": {\n    \"message\": \"Aangepaste kleuren voor afkeerbalk en knoppen\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Aangepaste getalnotaties\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Schakelt het loggen van API‑antwoorden in de console uit.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Loggen naar de console uitschakelen\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Herstelt de functie om dislikes te kunnen zien\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Instellingen\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Wijzigingslog\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Doneer\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Help\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Website\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Getalnotatie:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Premiumfuncties zijn beschikbaar voor supporters op Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Inloggen met Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Aanmelden kon niet worden voltooid. Probeer het opnieuw.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Patreon‑aanmelding kon niet worden gestart. Probeer het opnieuw.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Afmelden\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Voor inloggen is de machtiging 'identity' vereist. Sta dit toe om door te gaan.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Basisniveau\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Lidmaatschap controleren…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Geen actief lidmaatschap\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Premium‑supporter\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Supporter\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon‑gebruiker\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Opnieuw formatteren zoals getallen\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Maak het formaat voor leuk en niet leuk consistent.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Premium-teaserpaneel verbergen\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Toon de premiumteaser niet op YouTube-kijkpagina’s.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Afgeronde getallen weergeven\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Rond getallen naar beneden af (standaard YouTube-gedrag).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Ondersteuning voor YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Percentage in de tooltip van de like/dislike‑balk.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Percentage in de tooltip van de like/dislike‑balk weergeven.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Kleurenthema:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klassiek\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Beschikbaar\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Verhoudingsbalk inkleuren\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Gebruik aangepaste kleuren voor de verhoudingsbalk.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Duimpjes inkleuren\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Gebruik aangepaste kleuren voor duimpictogrammen.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Maak het formaat voor likes en dislikes consistent\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Herformatteer als getallen.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"door Dmitry Selivanov & Gemeenschap\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Uitgeschakeld door eigenaar\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Getalnotatie:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Statistieken voor leuk/niet leuk naar beneden afronden (standaard YouTube-gedrag)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Afgeronde statistieken weergeven.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Inzending voor leuk/niet leuk uitschakelen\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Stopt met het tellen van je leuks en niet leuks\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Tijdelijk niet beschikbaar\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Actualiseer naar\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Percentage‑modus:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Aangepaste procentweergave bij hover gebruiken.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Versie 3.0.0.13 geïnstalleerd\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Wat is er nieuw\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premium video-inzichten\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Betrokkenheid in de tijd\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Toplanden\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Landen met de meeste likes\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Landen met de meeste dislikes\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografie\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Uitklappen\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Inklappen\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Kaartmetriek\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Terug naar wereldkaart\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Likes\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Dislikes\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Verhouding\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Gegevensvenster\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Laatste dagen\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Eerste dagen\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Toon de meest recente dagen binnen het geselecteerde venster\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Toon de eerste dagen binnen het geselecteerde venster\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Aangepaste selectie\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Gehele periode\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Laatste $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Eerste $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 dag\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 dagen\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Gehele periode\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Geselecteerde periode: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Extensiegebruikers\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 likes\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 dislikes\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$1 interacties vastgelegd uit $2 landen ($3 unieke IP's)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Stemmen per uur\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Stemmen per dag\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Stemmen per week\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Stemmen elke $1 uur\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Stemmen elke $1 dagen\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Stemmen elke $1 minuten\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Stemmen per minuut\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Stemmen elke $1 seconden\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Stemmen per seconde\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Nog geen gegevens\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Onbekend\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Inzichten laden…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Autoriseer Patreon opnieuw om de analyses te ontgrendelen.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premium-analyses zijn beschikbaar voor actieve Patreon-supporters.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sessie verlopen. Meld je opnieuw aan met Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"De analysedienst is nu niet beschikbaar.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Kan geen verbinding maken met de analysedienst.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Premium-analyses kunnen niet worden geladen.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Activiteit\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Like-verhouding\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Hogere like-verhouding\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Lagere like-verhouding\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Meer likes\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Minder likes\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Meer dislikes\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Minder dislikes\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Meer $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Minder $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Likes: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Dislikes: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Like-verhouding: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Momentopname van dislike-inzichten\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Ruwe dislikes zijn directe meldingen van extensiegebruikers die deze video bekijken.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Premium ontgrendelen\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Volledige analyses bekijken\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Aantal ruwe dislikes\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Geschatte dislikes\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Geschatte likes\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Nieuwste totalen van dislikes ophalen…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"De dislikegegevens konden nu niet worden geladen. Probeer de pagina te vernieuwen.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Premium-analyses ontgrendelen\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Ontgrendel Premium-video-inzichten in drie snelle stappen:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Meld je aan met Patreon vanuit het pop-upvenster van de Return YouTube Dislike-extensie.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Selecteer het Premium-abonnement om activiteitstijdlijnen, ranglijsten per land en de interactieve kaart te activeren.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Laad YouTube opnieuw om uurlijkse trends, toplanden en uitsplitsingen per Amerikaanse staat te bekijken voor elke video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium-analyse vergrendeld\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Je Patreon-lidmaatschap bevat geen Premium-niveau. Upgrade je bijdrage om uitgebreide video-analyses te ontgrendelen.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Upgraden op Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Wijzigingenlogboek voor Premium Analytics · 20 oktober 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premium-release\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Premium analyses, opnieuw uitgevonden\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Ontdek de tijdlijn van live-activiteiten, het klassement van de beste landen en de interactieve kaart die bij Return YouTube Dislike Premium zijn inbegrepen.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Uitgebracht op 20 oktober 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Upgraden op Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Lees de installatiehandleiding\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Wat is er nieuw\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Tijdlijn voor live activiteiten\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Bekijk de likes en dislikes in realtime. Schakel tussen voorinstellingen per uur, per dag of altijd zonder opnieuw te laden, veranker het venster op dag één of de laatste stemmingen en scrub elke piek om te zien wat de betrokkenheid heeft veroorzaakt.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interactieve wereld- en Amerikaanse heatmap\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Kijk over de hele wereld of duik in Amerikaanse staten om hotspots te ontdekken. Vergelijk de likes, dislikes en ratio's in de geüpgradede tooltip en breng de regio's onder de aandacht die het eerst op uw uploads reageren.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Leiderbord van toplanden\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Rangschik elk land op ruwe activiteit, voorkeuren, antipathieën of verhoudingen. Filter met dezelfde voorinstellingen als de tijdlijn om te zien waar campagnes terechtkomen en plan wat u vervolgens gaat publiceren.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Screenshot binnenkort beschikbaar: leg deze weergave vast in DevTools als u klaar bent.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Placeholder: tijdlijnoverzicht\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Tijdelijke aanduiding: mondiale activiteitenkaart\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Tijdelijke aanduiding: landenklassement\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Hoe Premium-analyses te ontgrendelen\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Klaar om erin te duiken? Volg deze snelle stappen en vernieuw uw YouTube-tabblad.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Open de pop-up Return YouTube Dislike en log in met Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Kies de Premium-laag om het streamen van analysegegevens naar uw account in te schakelen.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Laad elke YouTube-weergavepagina opnieuw: uw tijdlijn, kaart en klassementen verschijnen onmiddellijk.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Hoogtepunten vrijgeven\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Voorinstellingen voor de tijdlijn van activiteiten streamen live gegevens met bereikankers voor de eerste en laatste buckets.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Het landenklassement wordt onmiddellijk vernieuwd, zodat u stijgende regio's kunt zien zonder het paneel te verlaten.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Interactieve kaart belicht hotspots in de wereld en de VS met duidelijkere tooltips en statistieken.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Handje nodig?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"We zijn er als u een probleem tegenkomt: stel uw vragen aan de ondersteuningsdocumenten of community.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Vraag het aan de gemeenschap\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike-logo\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/pl/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Status API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Motyw kolorystyczny:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Koloruj pasek ocen\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Użyj własnych kolorów paska.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Koloruj łapki\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Użyj własnych kolorów łapek.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Jedyne co napędza ten projekt to wasze dotacje, prosimy o rozważenie wsparcia tego projektu.\"\n  },\n  \"customColors\": {\n    \"message\": \"Własne kolory paska łapek i guzików\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Własne formaty liczbowe\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Wyłącza logowanie odpowiedzi API w konsoli.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Wyłącz logowanie do konsoli\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Przywraca licznik łapek w dół\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Ustawienia\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Zmiany\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Wesprzyj\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Pomoc\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Strona\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Format liczbowy:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Funkcje premium są dostępne dla wspierających na Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Zaloguj się przez Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Nie udało się dokończyć logowania. Spróbuj ponownie.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Nie udało się rozpocząć logowania przez Patreon. Spróbuj ponownie.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Wyloguj się\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Logowanie wymaga uprawnienia „identity”. Zezwól, aby kontynuować.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Poziom podstawowy\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Sprawdzanie członkostwa…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Brak aktywnego członkostwa\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Wspierający Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Wspierający\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Użytkownik Patreona\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Zmień formatowanie liczby łapek.\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Ujednolica format liczbowy łapek.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Ukryj panel zajawki Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Nie pokazuj zajawki Premium na stronach odtwarzania YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Pokaż zaokrąglone w dół liczby\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Zaokrąglaj w dół liczby (domyślne zachowanie YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Wsparcie dla YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Procent w podpowiedzi paska polubień/niepolubień.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Wyświetl procent w podpowiedzi paska polubień/niepolubień.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Motyw kolorystyczny:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klasyczny\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Dostępny\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neonowy\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Koloruj pasek ocen\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Użyj własnych kolorów paska.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Koloruj łapki\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Użyj własnych kolorów łapek.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Ujednolica format liczbowy łapek.\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Zmień formatowanie liczby łapek.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"przez Dmitry Selivanov & Społeczność\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Wyłączone przez twórcę\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Format liczbowy:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Zaokrąglaj łapki w górę/dół (domyślne zachowanie YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Pokaż zaokrąglone w dół statystyki.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Wyłącz przesyłanie łapek\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Przestaje liczyć Twoje łapki.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Tymczasowo niedostępny\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Zaktualizuj do\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Tryb procentów:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Użyj niestandardowego wyświetlania procentów po najechaniu.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Zainstalowana wersja __RYD_VERSION__\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Co nowego\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premiumowe statystyki wideo\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Zaangażowanie w czasie\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Najlepsze kraje\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Kraje z największą liczbą polubień\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Kraje z największą liczbą negatywnych ocen\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografia\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Rozwiń\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Zwiń\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Metryka mapy\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Powrót do mapy świata\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Polubienia\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Niepolubienia\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Współczynnik\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Zakres danych\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Ostatnie dni\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Pierwsze dni\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Pokaż najnowsze dni w wybranym zakresie\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Pokaż najwcześniejsze dni w wybranym zakresie\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Własny wybór\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Cały okres\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Ostatnie $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Pierwsze $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 dzień\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 dni\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Cały okres\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Wybrany okres: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Użytkownicy rozszerzenia\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 polubień\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 niepolubień\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Zarejestrowano $1 interakcji z $2 krajów ($3 unikalnych adresów IP)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Głosy na godzinę\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Głosy na dzień\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Głosy na tydzień\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Głosy co $1 godzin\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Głosy co $1 dni\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Głosy co $1 minut\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Głosy na minutę\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Głosy co $1 sekund\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Głosy na sekundę\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Brak danych\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Nieznane\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Ładowanie statystyk…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Autoryzuj ponownie Patreon, aby odblokować statystyki.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premiumowe statystyki są dostępne dla aktywnych wspierających na Patreonie.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sesja wygasła. Zaloguj się ponownie przez Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Usługa statystyk jest teraz niedostępna.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Nie można połączyć się z usługą statystyk.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Nie można wczytać premiumowych statystyk.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktywność\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Współczynnik polubień\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Wyższy współczynnik polubień\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Niższy współczynnik polubień\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Więcej polubień\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Mniej polubień\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Więcej niepolubień\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Mniej niepolubień\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Więcej $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Mniej $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Polubienia: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Niepolubienia: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Współczynnik polubień: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Podgląd statystyk niepolubień\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Surowe niepolubienia to bezpośrednie zgłoszenia użytkowników rozszerzenia oglądających ten film.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Odblokuj Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Podgląd pełnych statystyk\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Liczba surowych niepolubień\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Szacowane niepolubienia\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Szacowane polubienia\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Pobieranie najnowszych sum niepolubień…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Nie udało się wczytać danych o niepolubieniach. Spróbuj odświeżyć stronę.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Jak odblokować premiumowe statystyki\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Odblokuj premiumowe statystyki wideo w trzech prostych krokach:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Zaloguj się przez Patreon w oknie rozszerzenia Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Wybierz poziom Premium, aby włączyć wykresy aktywności, rankingi krajów i interaktywną mapę.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Odśwież YouTube, aby zobaczyć trendy godzinowe, najlepsze kraje i szczegóły według stanów USA dla każdego filmu.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Analityka premium zablokowana\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Twoje członkostwo w Patreonie nie obejmuje poziomu Premium. Zwiększ wsparcie, aby odblokować zaawansowane analizy wideo.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Ulepsz na Patreonie\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Dziennik zmian Premium Analytics · 20 października 2025 r\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Wydanie premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Analityka premium w nowym wydaniu\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Przeglądaj oś czasu aktywności na żywo, tabelę wyników najlepszych krajów i interaktywną mapę dołączoną do usługi Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Wydany 20 października 2025 r\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Uaktualnij na Patreonie\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Przeczytaj przewodnik konfiguracji\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Co nowego\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Oś czasu aktywności na żywo\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Oglądaj transmisję polubień i antypatii w czasie rzeczywistym. Przełączaj wstępne ustawienia godzinowe, dzienne lub wszechczasowe bez ponownego ładowania, zakotwicz okno na pierwszym dniu lub najnowszych głosach i przeglądaj każdy skok, aby zobaczyć, co wywołało zaangażowanie.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interaktywna mapa cieplna świata i USA\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Podróżuj po całym świecie lub zanurz się w stanach USA, aby wydobyć na powierzchnię gorące punkty. Porównaj polubienia, antypatie i współczynniki w ulepszonej podpowiedzi i zwróć uwagę na regiony, które jako pierwsze reagują na przesłane przez Ciebie treści.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Tabela liderów najlepszych krajów\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Uszereguj każdy kraj według surowej aktywności, upodobań, antypatii lub współczynnika. Filtruj przy użyciu tych samych ustawień co na osi czasu, aby dowiedzieć się, dokąd dotrą kampanie i zaplanować, co opublikować w następnej kolejności.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Zrzut ekranu wkrótce — przechwyć ten widok w DevTools, gdy będzie gotowy.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Symbol zastępczy: przegląd osi czasu\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Symbol zastępczy: globalna mapa aktywności\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Symbol zastępczy: tabela wyników krajów\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Jak odblokować analitykę Premium\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Gotowy do nurkowania? Wykonaj te krótkie kroki i odśwież kartę YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Otwórz wyskakujące okienko „Zwróć niechęć do YouTube” i zaloguj się na Patreonie.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Wybierz warstwę Premium, aby umożliwić przesyłanie strumieniowe danych analitycznych na swoje konto.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Załaduj ponownie dowolną stronę odtwarzania w YouTube — Twoja oś czasu, mapa i tabele wyników pojawią się natychmiast.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Wydaj najważniejsze informacje\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Gotowe ustawienia osi czasu aktywności przesyłają strumieniowo dane na żywo z kotwicami zakresu dla pierwszego i najnowszego segmentu.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Tablica liderów krajów odświeża się natychmiast, dzięki czemu możesz wykryć regiony o wzroście popularności bez opuszczania panelu.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Interaktywna mapa wyróżnia hotspoty na całym świecie i w stanach USA za pomocą wyraźniejszych podpowiedzi i wskaźników.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Potrzebujesz pomocy?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Jesteśmy tu, jeśli napotkasz przeszkodę — zadaj swoje pytania pracownikom pomocy technicznej lub społeczności.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Zapytaj społeczność\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/pt_BR/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Status da API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Tema de Cores:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Colorir barra de proporção\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Usar cores personalizadas para a barra de proporção.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Colorir botões\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Usar cores personalizadas para os botões de curtir e descurtir.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"A única coisa que mantém a extensão funcionando são suas doações, considere apoiar o projeto.\"\n  },\n  \"customColors\": {\n    \"message\": \"Cores personalizadas para barra de proporção e botões\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Formato numérico personalizado\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Desativa o registro de respostas da API no console.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Desativar logs no console\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Retorna a capacidade de ver descurtidas\"\n  },\n  \"extensionName\": {\n    \"message\": \"Volta Descurtidas do YouTube\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Volta Descurtidas do YouTube Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Configurações\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Registro de alterações\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Doar\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"Perguntas Frequentes\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Ajuda\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Website\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Formato numérico:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Recursos premium disponíveis para apoiadores no Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Entrar com o Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Não foi possível concluir o login. Tente novamente.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Falha ao iniciar o login do Patreon. Tente novamente.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Sair\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"O login requer a permissão \\\"identity\\\". Permita para continuar.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Nível básico\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Verificando assinatura…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Nenhuma assinatura ativa\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Apoiador Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Apoiador\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Usuário do Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Re-formatar número de curtidas\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Tornar o formato de curtidas e descurtidas consistente.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Ocultar painel do teaser Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Não mostrar o teaser Premium nas páginas de exibição do YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Mostrar números arredondados\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Arredondar os números para baixo (comportamento padrão do YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Suporte a curtas do YouTube\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Percentual na dica (tooltip) da barra de like/dislike.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Mostrar o percentual na dica (tooltip) da barra de like/dislike.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Tema de cores:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Clássico\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Acessível\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Néon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Colorir barra de proporção\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Usar cores personalizadas para a barra de proporção.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Colorir botões\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Usar cores personalizadas para os botões de curtir e descurtir.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Tornar o formato de curtidas e descurtidas consistente\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Re-formatar número de curtidas.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"por Dmitry Selivanov & Comunidade\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Desativado pelo proprietário\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Formato numérico:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Arredondar as estatísticas de curtidas/descurtidas para baixo (comportamento padrão do YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Mostrar estatísticas arredondadas\"\n  },\n  \"textSettings\": {\n    \"message\": \"Desativar envio de curtidas/descurtidas\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Para de contar suas curtidas e descurtidas.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Temporariamente Indisponível\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Atualizar para\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Modo de porcentagem:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Usar exibição personalizada de porcentagem ao passar o mouse.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Versão __RYD_VERSION__ instalada\"\n  },\n  \"whatsnew\": {\n    \"message\": \"O que há de novo?:\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Insights premium do vídeo\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Engajamento ao longo do tempo\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Países em destaque\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Países com mais curtidas\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Países com mais dislikes\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografia\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Expandir\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Recolher\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Métrica do mapa\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Voltar ao mapa mundial\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Curtidas\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Dislikes\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Proporção\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Janela de dados\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Últimos dias\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Primeiros dias\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Mostrar os dias mais recentes dentro da janela selecionada\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Mostrar os primeiros dias dentro da janela selecionada\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Seleção personalizada\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Todo o período\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Últimos $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Primeiros $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 dia\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 dias\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Todo o período\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Período selecionado: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Usuários da extensão\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 curtidas\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 dislikes\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Capturamos $1 interações de $2 países ($3 IPs únicos)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Votos por hora\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Votos por dia\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Votos por semana\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Votos a cada $1 horas\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Votos a cada $1 dias\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Votos a cada $1 minutos\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Votos por minuto\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Votos a cada $1 segundos\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Votos por segundo\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Ainda não há dados\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Desconhecido\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Carregando insights…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Autorize o Patreon novamente para desbloquear as análises.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"As análises premium estão disponíveis para apoiadores ativos no Patreon.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sessão expirada. Entre novamente com o Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"O serviço de análises não está disponível no momento.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Não foi possível acessar o serviço de análises.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Não foi possível carregar as análises premium.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Atividade\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Proporção de curtidas\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Proporção de curtidas mais alta\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Proporção de curtidas mais baixa\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Mais curtidas\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Menos curtidas\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Mais dislikes\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Menos dislikes\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Mais $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Menos $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Curtidas: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Dislikes: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Proporção de curtidas: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Resumo de insights de dislikes\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Os dislikes brutos são relatos diretos dos usuários da extensão que assistem a este vídeo.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Desbloquear Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Pré-visualizar todas as análises\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Contagem de dislikes brutos\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Dislikes estimados\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Curtidas estimadas\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Obtendo os últimos totais de dislikes…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Não conseguimos carregar os dados de dislikes agora. Tente recarregar a página.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Como desbloquear as análises Premium\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Desbloqueie os insights Premium do vídeo em três passos rápidos:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Entre com o Patreon a partir do pop-up da extensão Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Selecione o plano Premium para habilitar linhas do tempo de atividade, rankings por país e o mapa interativo.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Recarregue o YouTube para ver tendências por hora, países em destaque e o detalhamento por estado dos EUA para cada vídeo.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Analytics Premium bloqueados\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Sua assinatura no Patreon não inclui o nível Premium. Faça um upgrade do apoio para desbloquear análises avançadas de vídeos.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Fazer upgrade no Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Registro de novidades do Analytics Premium · 20 de outubro de 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Lançamento Premium\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Analytics Premium repensado\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Explore o cronograma de atividades ao vivo, a tabela de classificação dos principais países e o mapa interativo incluídos no Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Lançado em 20 de outubro de 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Fazer upgrade no Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Ler o guia de configuração\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Novidades\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Cronograma de atividades ao vivo\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Assista ao fluxo de gostos e desgostos em tempo real. Alterne as predefinições de hora em hora, diariamente ou de todos os tempos sem recarregar, fixe a janela no primeiro dia ou nas votações mais recentes e limpe cada pico para ver o que desencadeou o engajamento.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Mundo interativo e mapa de calor dos EUA\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Percorra o mundo ou mergulhe nos estados dos EUA para descobrir pontos críticos. Compare gostos, desgostos e proporções na dica atualizada e destaque as regiões que reagem primeiro aos seus uploads.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Tabela de classificação dos principais países\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Classifique cada país por atividade bruta, gostos, desgostos ou proporção. Filtre com as mesmas predefinições da linha do tempo para saber onde as campanhas chegam e planejar o que publicar em seguida.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Captura em breve — registre esta visualização no DevTools quando estiver pronto.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Marcador: visão geral da linha do tempo\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Marcador: mapa de atividade global\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Espaço reservado: tabela de classificação dos países\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Como desbloquear o Analytics Premium\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Pronto para começar? Siga estes passos e atualize sua guia do YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Abra o pop-up do Return YouTube Dislike e entre com o Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Escolha o nível Premium para ativar o envio dos dados de análise para a sua conta.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Recarregue qualquer página do YouTube — linha do tempo, mapa e rankings aparecem na hora.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Destaques do lançamento\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"As predefinições da linha do tempo de atividades transmitem dados ao vivo com âncoras de intervalo para os primeiros e mais recentes intervalos.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"A tabela de classificação do país é atualizada instantaneamente para que você possa identificar regiões em ascensão sem sair do painel.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"O mapa interativo destaca pontos de acesso globais e estaduais dos EUA com dicas e métricas mais claras.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Precisa de ajuda?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Conte conosco se algo der errado — consulte a documentação de suporte ou a comunidade.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Pergunte à comunidade\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo do Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/ru/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Состояние API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Офлайн\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Онлайн\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Цветовая схема:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Раскрасьте полосу неприязни\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Раскрасьте полосу неприязни собственными цветами.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Раскрасьте кнопки\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Раскрасьте кнопки нравится и не нравится с помощью пользовательских цветов.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Единственный источник доходов позволяющий расширению продолжать работу - ваши пожертвования. Пожалуйста, поддержите нас и помогите нам развиваться.\"\n  },\n  \"customColors\": {\n    \"message\": \"Выбор цветов панели дизлайков и кнопок\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Выбор формата чисел\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Отключает логирование ответов API в консоль.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Отключить логирование в консоль\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Возвращает способность видеть дизлайки\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Настройки\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Журнал изменений\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Пожертвовать\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"ЧАВО\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Помощь\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Сайт\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Числовой формат\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Премиум‑функции доступны подписчикам на Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Войти через Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Не удалось завершить вход. Попробуйте еще раз.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Не удалось начать вход через Patreon. Попробуйте еще раз.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Выйти\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Для входа требуется разрешение «identity». Разрешите, чтобы продолжить.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Базовый уровень\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Проверка подписки…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Нет активной подписки\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Премиум‑поддерживающий\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Поддерживающий\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Пользователь Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Форматировать количество лайков\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Делает значение статистики более последовательным.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Скрывать панель премиум-тизера\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Не показывать премиум-тизер на страницах просмотра YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Показывать округленные числа\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Округлить значение счетчика (это параметр YouTube по умолчанию).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Поддержка YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Процент в подсказке полосы лайков/дизлайков.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Показывать процент в подсказке полосы лайков/дизлайков.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Цветовая тема:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Классическая\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Доступная\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Неоновая\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Раскрасьте полосу неприязни\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Используйте пользовательские цвета для полосы неприязни.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Раскрасьте кнопки\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Раскрасьте кнопки нравится и не нравится с помощью пользовательских цветов.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Привести формат лайков и дизлайков к единому виду\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Переформатировать число лайков.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"Дмитрий Селиванов и сообщество\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Отключено владельцем\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Числовой формат:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Округлить статистику лайков/дизлайков (стандартное поведение YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Показывать округленные данные.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Отключить отправку лайков/дизлайков\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Отключает отправку ваших отметок нравится/не нравится\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Временно недоступно\"\n  },\n  \"textUpdate\": {\n    \"message\": \"обновление до\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Режим процентов:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Использовать кастомный показ процентов при наведении.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Версия __RYD_VERSION__ установлена\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Что нового\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Премиальная аналитика по видео\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Вовлечённость во времени\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Топ стран\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Страны с наибольшим числом лайков\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Страны с наибольшим числом дизлайков\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"География\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Развернуть\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Свернуть\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Метрика карты\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Назад к карте мира\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Лайки\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Дизлайки\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Соотношение\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Диапазон данных\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Последние дни\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Первые дни\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Показать самые последние дни в выбранном диапазоне\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Показать самые ранние дни в выбранном диапазоне\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Пользовательский выбор\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"За всё время\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Последние $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Первые $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 день\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 дней\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"За всё время\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1д\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Выбранный период: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Пользователи расширения\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 лайков\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 дизлайков\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Зафиксировано $1 взаимодействий из $2 стран ($3 уникальных IP)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Голоса в час\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Голоса в день\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Голоса в неделю\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Голоса каждые $1 часов\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Голоса каждые $1 дней\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Голоса каждые $1 минут\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Голоса в минуту\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Голоса каждые $1 секунд\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Голоса в секунду\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Данных пока нет\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Неизвестно\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Загрузка аналитики…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Повторно авторизуйте Patreon, чтобы открыть аналитику.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Премиальная аналитика доступна активным подписчикам Patreon.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Сессия истекла. Войдите через Patreon ещё раз.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Сервис аналитики сейчас недоступен.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Не удалось подключиться к сервису аналитики.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Не удалось загрузить премиальную аналитику.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Активность\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Соотношение лайков\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Большее соотношение лайков\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Меньшее соотношение лайков\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Больше лайков\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Меньше лайков\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Больше дизлайков\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Меньше дизлайков\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Больше $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Меньше $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Лайки: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Дизлайки: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Соотношение лайков: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Краткий обзор дизлайков\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Необработанные дизлайки — это прямые сообщения пользователей расширения, смотрящих это видео.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Открыть Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Просмотреть полную аналитику\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Количество необработанных дизлайков\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Оценочные дизлайки\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Оценочные лайки\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Получаем последние суммы дизлайков…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Не удалось загрузить данные по дизлайкам. Попробуйте обновить страницу.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Как открыть премиальную аналитику\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Откройте премиальную аналитику видео в три быстрых шага:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Войдите через Patreon во всплывающем окне расширения Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Выберите уровень Premium, чтобы включить временные шкалы активности, рейтинги стран и интерактивную карту.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Перезагрузите YouTube, чтобы увидеть почасовые тренды, топ стран и детализацию по штатам США для каждого видео.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Премиальная аналитика заблокирована\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"В вашей подписке на Patreon нет уровня Premium. Повышайте поддержку, чтобы получить доступ к расширенной видеоаналитике.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Обновить на Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Журнал изменений Premium Analytics · 20 октября 2025 года\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Премиум-релиз\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Премиальная аналитика по-новому\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Изучите хронологию событий в прямом эфире, таблицу лидеров лучших стран и интерактивную карту, включенную в состав Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Выпущено 20 октября 2025 года\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Повысить уровень на Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Прочитать инструкцию по настройке\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Что нового\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"График активности в прямом эфире\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Смотрите трансляцию лайков и антипатий в режиме реального времени. Переключайте ежечасные, ежедневные или постоянные настройки без перезагрузки, привязывайте окно к первому или последнему дню голосования и просматривайте каждый всплеск, чтобы увидеть, что вызвало взаимодействие.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Интерактивная тепловая карта мира и США\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Путешествуйте по всему земному шару или погрузитесь в штаты США, чтобы обнаружить горячие точки. Сравните соотношение лайков, антипатий и их соотношение в обновленной подсказке и выделите регионы, которые первыми реагируют на ваши загрузки.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Таблица лидеров лучших стран\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Проранжируйте каждую страну по показателям активности, симпатий, антипатий или соотношения. Фильтруйте с теми же настройками, что и на временной шкале, чтобы узнать, где будут проводиться кампании, и спланировать, что публиковать дальше.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Скриншот появится позже — сохраните этот вид через DevTools, когда будете готовы.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Заполнитель: обзор временной шкалы\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Заполнитель: карта глобальной активности\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Заполнитель: таблица лидеров стран\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Как открыть Premium Analytics\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Готовы погрузиться? Выполните эти шаги и обновите вкладку YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Откройте всплывающее окно Return YouTube Dislike и войдите через Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Выберите уровень Premium, чтобы аналитические данные поступали в ваш аккаунт.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Перезагрузите любую страницу просмотра YouTube — временная шкала, карта и рейтинги появятся сразу.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Основные моменты релиза\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Предварительные настройки временной шкалы активности передают данные в реальном времени с привязкой к диапазону для первого и последнего сегментов.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Таблица лидеров стран обновляется мгновенно, поэтому вы можете видеть растущие регионы, не покидая панели.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"На интерактивной карте обозначены горячие точки мира и штатов США с более четкими подсказками и показателями.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Нужна помощь?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Если возникли вопросы, загляните в справочные материалы или сообщество — мы рядом.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Спросить сообщество\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Логотип Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/sv_SE/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API‑status:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Offline\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Online\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Färgtema:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Färgsätt förhållandefältet\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Använd anpassade färger för förhållandefältet.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Färglägg tummarna\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Använd anpassade färger för tumikoner.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Det enda som håller tillägget i gång är dina donationer, överväg att stöda projektet.\"\n  },\n  \"customColors\": {\n    \"message\": \"Anpassade färger för knappar och fältet ogilla\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Anpassade sifforformat\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Inaktiverar loggning av API‑svar i konsolen.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Inaktivera loggning till konsolen\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Återställer förmågan att se ogilla\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Inställningar\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Ändringslogg\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Donera\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"FAQ\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Hjälp\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Hemsida\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Sifforformat:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Premiumfunktioner är tillgängliga för Patreon‑stödjare\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Logga in med Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Det gick inte att slutföra inloggningen. Försök igen.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Det gick inte att starta Patreon‑inloggning. Försök igen.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Logga ut\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Inloggning kräver behörigheten ”identity”. Tillåt för att fortsätta.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Grundnivå\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Kontrollerar medlemskap…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Inget aktivt medlemskap\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Premium‑stödjare\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Stödjare\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon‑användare\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Omformatera som siffror\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Gör formatet på gilla och ogilla konsekvent.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Dölj premiumteaser-panelen\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Visa inte premiumteasern på YouTubes tittarsidor.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Visa avrundade siffror\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Avrunda siffrorna neråt (standard YouTube-beteende).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTube Shorts Support\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Procent i verktygstips för gilla/ogilla‑fältet.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Visa procent i verktygstipset för gilla/ogilla‑fältet.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Färgtema:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klassisk\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Tillgänglig\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Färgsätt förhållandefältet\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Använd anpassade färger för förhållandefältet.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Färglägg tummarna\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Använd anpassade färger för tumikoner.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Gör formatet på gilla och ogilla konsekvent\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Omformatera som siffror.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"av Dmitry Selivanov & Community\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Inaktiverad av ägaren\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Sifforformat:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Avrunda statistiken neråt för gilla/ogilla (standard YouTube-beteende)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Visa avrundad statistik.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Inaktivera gilla-/ogilla-inskickningar\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Slutar räkna dina gilla och ogilla.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Tillfälligt otillgänglig\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Uppdatera till\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Procentläge:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Använd anpassad procentvisning vid hovring.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Version __RYD_VERSION__ installerad\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Vad är nytt\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premium-videoinblickar\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Engagemang över tid\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Toppländer\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Länder med flest gilla-markeringar\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Länder med flest ogilla-markeringar\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Geografi\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Expandera\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Fäll ihop\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Kartmetrik\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Tillbaka till världskartan\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Gilla-markeringar\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Ogilla-markeringar\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Förhållande\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Datafönster\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Senaste dagarna\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Första dagarna\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Visa de senaste dagarna inom det valda intervallet\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Visa de första dagarna inom det valda intervallet\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Anpassat urval\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Hela perioden\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Senaste $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Första $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 dag\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 dagar\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Hela perioden\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1d\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Vald period: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Tilläggsanvändare\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 gilla-markeringar\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 ogilla-markeringar\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Registrerade $1 interaktioner från $2 länder ($3 unika IP-adresser)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Röster per timme\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Röster per dag\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Röster per vecka\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Röster var $1 timme\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Röster var $1 dag\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Röster var $1 minut\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Röster per minut\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Röster var $1 sekund\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Röster per sekund\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Inga data ännu\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Okänt\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Läser in insikter…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Auktorisera Patreon igen för att låsa upp analyserna.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premiumanalyser är tillgängliga för aktiva Patreon-supportrar.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Sessionen har gått ut. Logga in igen med Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Analyssystemet är inte tillgängligt just nu.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Det går inte att nå analysetjänsten.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Det går inte att läsa in premiumanalyser.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktivitet\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Gilla-förhållande\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Högre gilla-förhållande\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Lägre gilla-förhållande\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Fler gilla-markeringar\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Färre gilla-markeringar\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Fler ogilla-markeringar\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Färre ogilla-markeringar\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Mer $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Mindre $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Gilla-markeringar: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Ogilla-markeringar: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Gilla-förhållande: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Översikt över ogilla-insikter\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Råa ogilla-markeringar är direkta rapporter från tilläggsanvändare som tittar på videon.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Lås upp Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Förhandsvisa alla analyser\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Antal råa ogilla-markeringar\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Beräknade ogilla-markeringar\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Beräknade gilla-markeringar\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Hämtar de senaste ogilla-summorna…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Ogilla-data kunde inte läsas in. Försök att ladda om sidan.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Så låser du upp Premiumanalyser\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Lås upp Premium-videoinblickar i tre snabba steg:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Logga in med Patreon via popup-fönstret i Return YouTube Dislike-tillägget.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Välj Premium-nivån för att aktivera aktivitetsgrafer, landstopplistor och den interaktiva kartan.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Ladda om YouTube för att se timvisa trender, toppländer och detaljer per delstat i USA för varje video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premiumanalys låst\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Ditt Patreon-medlemskap innehåller inte Premium-nivån. Uppgradera ditt stöd för att låsa upp avancerad videoanalys.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Uppgradera på Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Ändringslogg för Premium Analytics · 20 oktober 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premiumversion\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Premiumanalys, omarbetad\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Utforska liveaktivitetens tidslinje, topplistan över länder och den interaktiva kartan som ingår i Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Släppt den 20 oktober 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Uppgradera på Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Läs installationsguiden\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Vad är nytt\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Tidslinje för liveaktivitet\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Se gilla- och ogilla-strömmar i realtid. Växla förinställningar per timme, dagligen eller genom tiderna utan att ladda om, förankra fönstret till dag ett eller de senaste rösterna och skrubba varje spik för att se vad som utlöste engagemang.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Interaktiv värld och USA värmekarta\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Panorera över hela världen eller dyk in i amerikanska delstater för att få ytan på hotspots. Jämför gillande, ogillar och förhållanden i det uppgraderade verktygstipset och lyft fram de regioner som reagerar först på dina uppladdningar.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Topplistan för länder\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Rangordna varje land efter rå aktivitet, gillar, ogillar eller förhållande. Filtrera med samma förinställningar som tidslinjen för att ta reda på var kampanjer landar och planera vad som ska publiceras härnäst.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Skärmdump kommer snart – ta den här vyn i DevTools när du är klar.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Platshållare: tidslinjeöversikt\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Platshållare: global aktivitetskarta\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Platshållare: länders topplista\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Hur man låser upp Premium Analytics\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Redo att dyka i? Följ dessa snabba steg och uppdatera din YouTube-flik.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Öppna popup-fönstret Returnera YouTube ogillar och logga in med Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Välj Premium-nivån för att aktivera analysdataströmning till ditt konto.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Ladda om vilken YouTube-visningssida som helst – din tidslinje, karta och topplistor visas direkt.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Släpp höjdpunkter\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Förinställningar för aktivitetstidslinje streamar livedata med räckviddsankare för första och senaste hinkar.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Country leaderboard uppdateras omedelbart så att du kan upptäcka stigande regioner utan att lämna panelen.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Interaktiv karta framhäver globala och amerikanska delstaters hotspots med tydligare verktygstips och mätvärden.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Behöver du en hand?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Vi finns här om du har problem – ta med dina frågor till supportdokumenten eller communityn.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Fråga samhället\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike-logotyp\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/tr/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API durumu:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Çevrimdışı\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Çevrimiçi\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Renk teması:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Beğeni oranı çubuğunu renklendir\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Beğeni oranı çubuğu için özel renkler kullanın.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Butonları renklendir\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Buton simgeleri için özel renkler kullan.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Uzantının var olmasını sağlayan tek şey bağışlarınızdır, lütfen projeyi desteklemeyi düşünün.\"\n  },\n  \"customColors\": {\n    \"message\": \"Dislike çubuğu ve düğmeler için özel renkler\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Özel sayı biçimleri\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Konsolda API yanıtlarının günlüğe kaydını kapatır.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Konsola günlüklemeyi kapat\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Dislike'ları görebilme yeteneği verir\"\n  },\n  \"extensionName\": {\n    \"message\": \"YouTube Dislike Sayısını Geri Getir\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"YouTube Dislike Sayısını Geri Getir Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Ayarlar\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Değişim Günlüğü\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Bağış Yap\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"SSS\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Yardım\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Site\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Sayı tarzı:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Premium özellikler Patreon destekçileri için kullanılabilir\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Patreon ile giriş yap\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Giriş tamamlanamadı. Lütfen tekrar deneyin.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Patreon oturum açma başlatılamadı. Lütfen tekrar deneyin.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Çıkış yap\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Oturum açmak için 'identity' izni gerekir. Devam etmek için lütfen izin verin.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Temel Seviye\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Üyelik kontrol ediliyor…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Etkin üyelik yok\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Premium Destekçi\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Destekçi\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon kullanıcısı\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Like sayılarının tarzını yenileştir\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Like ve dislike tarzını tutarlı hâle getirir.\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Premium tanıtım panelini gizle\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"YouTube izleme sayfalarında premium tanıtımını gösterme.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Aşağı yuvarlanmış sayıları gösterir\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Sayıları aşağıya yuvarla (varsayılan YouTube davranışı).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"YouTube Shorts Desteği\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Beğeni/beğenmeme çubuğu araç ipucunda yüzde.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Beğeni/beğenmeme çubuğu araç ipucunda yüzdeyi göster.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Renk teması:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Klasik\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Anlaşılır\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Neon\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Beğeni oranı çubuğunu renklendir\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Oran çubuğu için özel renkler kullanın.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Butonları renklendir\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Buton simgeleri için özel renkler kullanın.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Like ve dislike tarzını tutarlı hâle getirir\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Like sayılarının tarzını yenileştir.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"Dmitry Selivanov ve Topluluk tarafından\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Sahibi tarafından devre dışı bırakıldı\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Sayı tarzı:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Like/dislike istatisliklerini aşağıya yuvarlar (varsayılan YouTube davranışı)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Yuvarlatılmış istatistikleri göster.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Like/dislike gönderimini devre dışı bırak\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Like ve dislike'larınızı saymayı bırakır.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Geçici Olarak Kullanım Dışı\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Şu sürüme güncelle\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Yüzde modu:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Üzerine gelindiğinde özel yüzde gösterimini kullan.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Sürüm __RYD_VERSION__ yüklendi\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Yeni Ne Var\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Premium video içgörüleri\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Zaman içindeki etkileşim\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Öne çıkan ülkeler\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"En çok beğeni alan ülkeler\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"En çok beğenilmeyen ülkeler\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Coğrafya\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Genişlet\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Daralt\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Harita metriği\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Dünya haritasına dön\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Beğeniler\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Beğenilmeyenler\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Oran\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Veri aralığı\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Son günler\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"İlk günler\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Seçilen aralıktaki en yeni günleri göster\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Seçilen aralıktaki en eski günleri göster\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Özel seçim\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Tüm zamanlar\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Son $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"İlk $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 gün\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 gün\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Tüm zamanlar\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1g\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Seçilen dönem: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Uzantı kullanıcıları\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 beğeni\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 beğenilmeme\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"$2 ülkeden $3 benzersiz IP ile $1 etkileşim kaydedildi\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Saat başına oy\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Gün başına oy\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Hafta başına oy\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Her $1 saatte oy\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Her $1 günde oy\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Her $1 dakikada oy\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Dakika başına oy\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Her $1 saniyede oy\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Saniye başına oy\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Henüz veri yok\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Bilinmiyor\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"İçgörüler yükleniyor…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Analitiği açmak için lütfen Patreon’u yeniden yetkilendirin.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Premium analitik, aktif Patreon destekçileri için kullanılabilir.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Oturumun süresi doldu. Patreon ile yeniden giriş yapın.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Analitik hizmeti şu anda kullanılamıyor.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Analitik hizmetine ulaşılamıyor.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Premium analitik yüklenemedi.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Aktivite\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Beğeni oranı\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Daha yüksek beğeni oranı\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Daha düşük beğeni oranı\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Daha fazla beğeni\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Daha az beğeni\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Daha fazla beğenilmeme\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Daha az beğenilmeme\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Daha fazla $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Daha az $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Beğeniler: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Beğenilmeyenler: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Beğeni oranı: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Beğenilmeme içgörüleri özeti\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Ham beğenilmeme sayıları, bu videoyu izleyen uzantı kullanıcılarının doğrudan bildirimleridir.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Premium’u aç\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Tüm analitiği önizle\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Ham beğenilmeme sayısı\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Tahmini beğenilmeme\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Tahmini beğeni\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"En son beğenilmeme toplamları alınıyor…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Şu anda beğenilmeme verileri yüklenemedi. Sayfayı yeniden yüklemeyi deneyin.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Premium analitik nasıl açılır\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Premium video içgörülerini üç hızlı adımda açın:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Return YouTube Dislike eklentisinin açılır penceresinden Patreon ile giriş yapın.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Aktivite zaman çizelgelerini, ülke sıralamalarını ve interaktif haritayı açmak için Premium seviyesini seçin.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Her video için saatlik trendleri, öne çıkan ülkeleri ve ABD eyalet detaylarını görmek için YouTube’u yeniden yükleyin.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium analiz kilitli\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Patreon üyeliğinizde Premium seviye bulunmuyor. Gelişmiş video analizlerini açmak için desteğinizi yükseltin.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Patreon’da yükselt\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Premium Analytics Değişiklik Günlüğü · 20 Ekim 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Premium Sürüm\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Yeniden tasarlanan premium analizler\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Return YouTube Beğenmeme Premium'a dahil olan canlı etkinlik zaman çizelgesini, en iyi ülkeler sıralama tablosunu ve etkileşimli haritayı keşfedin.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"20 Ekim 2025'te yayınlandı\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Patreon'da yükseltme\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Kurulum kılavuzunu okuyun\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Ne var ne yok\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Canlı etkinlik zaman çizelgesi\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Beğenilenler ve beğenilmeyenler akışını gerçek zamanlı olarak izleyin. Yeniden yüklemeye gerek kalmadan saatlik, günlük veya tüm zamanların ön ayarlarını değiştirin, pencereyi birinci güne veya en son oylara sabitleyin ve etkileşimi neyin tetiklediğini görmek için her artışı temizleyin.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"İnteraktif dünya ve ABD ısı haritası\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Sıcak noktaları ortaya çıkarmak için dünya çapında gezinin veya ABD eyaletlerine dalın. Yükseltilmiş araç ipucundaki beğenme, beğenmeme ve oranları karşılaştırın ve yüklemelerinize ilk tepki veren bölgeleri öne çıkarın.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"En iyi ülkeler sıralama tablosu\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Her ülkeyi ham aktiviteye, beğenilenlere, beğenilmeyenlere veya orana göre sıralayın. Kampanyaların nereye varacağını öğrenmek ve bir sonraki adımda ne yayınlayacağınızı planlamak için zaman çizelgesiyle aynı ön ayarlarla filtreleyin.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Ekran görüntüsü yakında gelecek; hazır olduğunuzda bu görünümü DevTools'ta yakalayın.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Yer tutucu: zaman çizelgesine genel bakış\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Yer tutucu: küresel etkinlik haritası\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Yer tutucu: ülkeler sıralama tablosu\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Premium analizlerin kilidini açma\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Dalmaya hazır mısınız? Bu hızlı adımları izleyin ve YouTube sekmenizi yenileyin.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"YouTube Beğenmemeye Geri Dön açılır penceresini açın ve Patreon'da oturum açın.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Hesabınıza analiz verileri akışını etkinleştirmek için Premium katmanını seçin.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Herhangi bir YouTube izleme sayfasını yeniden yükleyin; zaman çizelgeniz, haritanız ve skor tablolarınız anında görünür.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Yayının öne çıkanları\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Etkinlik zaman çizelgesi ön ayarları, ilk ve en son bölümler için aralık bağlantılarıyla canlı verileri yayınlar.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Ülke skor tablosu anında yenilenir, böylece yükselen bölgeleri panelden ayrılmadan tespit edebilirsiniz.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Etkileşimli harita, daha net araç ipuçları ve ölçümlerle küresel ve ABD eyaletinin sıcak noktalarını vurgular.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Yardıma mı ihtiyacınız var?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Bir sorunla karşılaşırsanız buradayız; sorularınızı destek belgelerine veya topluluğa iletin.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Topluluğa sorun\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike logosu\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/uk/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Стан API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Офлайн\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Онлайн\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Кольорова схема:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Обрати кольори стрічки співвідношення\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Змінює кольори стрічки співвідношення на обрані вами.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Обрати кольори відміток\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Змінює кольори піктограм відміток на обрані вами.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Розширення досі існує лише за допомогою ваших пожертв, будь ласка, підтримайте проєкт.\"\n  },\n  \"customColors\": {\n    \"message\": \"Користувальницькі кольори стрічки співвідношення та кнопок\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Користувацькі формати значень\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Вимикає журналювання відповідей API в консолі.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Вимкнути журнал у консоль\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Повертає здатність бачити відмітки «Не подобається»\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Налаштування\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Журнал змін\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Підтримати\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"ЧаПи\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Допомога\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Вебсайт\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Формат значень:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Преміум‑функції доступні прихильникам на Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Увійти через Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Не вдалося завершити вхід. Спробуйте ще раз.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Не вдалося розпочати вхід через Patreon. Спробуйте ще раз.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Вийти\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Для входу потрібен дозвіл «identity». Дозвольте, щоб продовжити.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Базовий рівень\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Перевірка підписки…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Немає активної підписки\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Преміум‑підтримувач\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Підтримувач\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Користувач Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Форматувати як числа\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Зробить формат «Подобається» та «Не подобається» однаковим\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Приховати панель преміум-тизеру\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Не показувати преміум-тизер на сторінках перегляду YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Показувати заокруглені значення\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Округлює значення відміток (стандартний параметр YouTube)\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Підтримка YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Відсоток у підказці панелі вподобань/невподобань.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Показувати відсоток у підказці панелі вподобань/невподобань.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Кольорова схема:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Класика\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Доступність\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Неон\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Обрати кольори стрічки співвідношення\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Змінює кольори стрічки співвідношення на обрані вами.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Обрати кольори відміток\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Змінює кольори піктограм відміток на обрані вами.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Зробить формат «Подобається» та «Не подобається» однаковим\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Форматувати як числа.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"від Дмитра Селіванова та спільноти\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Вимкнуто власником\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Формат значень:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Округлює значення відміток (стандартний параметр YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Показувати заокруглені значення.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Вимкнути надсилання відміток\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Вимикає надсилання відміток «Подобається» та «Не подобається».\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Тимчасово недоступно\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Оновлення до\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Режим відсотків:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Використовувати власне відображення відсотків при наведенні.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Версію __RYD_VERSION__ встановлено\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Що нового\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Преміальна аналітика відео\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Залучення з часом\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Топ країн\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Країни з найбільшою кількістю лайків\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Країни з найбільшою кількістю дизлайків\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Географія\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Розгорнути\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Згорнути\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Показник карти\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Назад до карти світу\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Лайки\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Дизлайки\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Співвідношення\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Інтервал даних\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Останні дні\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Перші дні\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Показати найновіші дні у вибраному інтервалі\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Показати найперші дні у вибраному інтервалі\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Власний вибір\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"За весь час\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"Останні $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"Перші $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 день\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 днів\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"За весь час\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1д\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Обраний період: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Користувачі розширення\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 лайків\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 дизлайків\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Зафіксовано $1 взаємодій із $2 країн ($3 унікальних IP)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Голоси за годину\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Голоси за день\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Голоси за тиждень\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Голоси кожні $1 годин\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Голоси кожні $1 днів\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Голоси кожні $1 хвилин\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Голоси за хвилину\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Голоси кожні $1 секунд\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Голоси за секунду\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Даних ще немає\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Невідомо\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Завантаження аналітики…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Повторно авторизуйте Patreon, щоб розблокувати аналітику.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Преміальна аналітика доступна активним підтримувачам на Patreon.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Сесія завершилася. Увійдіть з Patreon ще раз.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Сервіс аналітики зараз недоступний.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Не вдалося підключитися до сервісу аналітики.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Не вдалося завантажити преміальну аналітику.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Активність\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Співвідношення лайків\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Вище співвідношення лайків\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Нижче співвідношення лайків\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Більше лайків\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Менше лайків\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Більше дизлайків\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Менше дизлайків\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Більше $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Менше $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Лайки: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Дизлайки: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Співвідношення лайків: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Короткий огляд дизлайків\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Необроблені дизлайки — це прямі звіти користувачів розширення, які дивляться це відео.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Відкрити Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Попередній перегляд повної аналітики\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Кількість необроблених дизлайків\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Орієнтовні дизлайки\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Орієнтовні лайки\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Отримуємо найсвіжіші суми дизлайків…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Наразі не вдалося завантажити дані про дизлайки. Спробуйте оновити сторінку.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Як розблокувати преміальну аналітику\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Розблокуйте преміальну відеоаналітику у три швидкі кроки:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Увійдіть через Patreon у спливаючому вікні розширення Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Виберіть рівень Premium, щоб увімкнути часові шкали активності, рейтинги країн та інтерактивну карту.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Перезавантажте YouTube, щоб переглянути погодинні тренди, провідні країни та деталізацію за штатами США для кожного відео.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Преміум-аналітика заблокована\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"У вашому членстві Patreon немає рівня Premium. Оновіть свій внесок, щоб відкрити розширену відеоаналітику.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Оновити на Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Журнал змін Premium Analytics · 20 жовтня 2025 р\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Преміум випуск\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Преміальна аналітика, переосмислена\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Ознайомтеся з хронологією активності в реальному часі, таблицею лідерів із найпопулярнішими країнами та інтерактивною картою, включеною в Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Дата виходу 20 жовтня 2025 року\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Оновлення на Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Прочитайте посібник із налаштування\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Що нового\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Графік живої активності\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Переглядайте потоки лайків і нелайків у режимі реального часу. Перемикайте погодинні, щоденні або постійні налаштування без перезавантаження, прив’язуйте вікно до першого дня або останніх голосувань і прочищайте кожен сплеск, щоб побачити, що викликало залучення.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Інтерактивна теплова карта світу та США\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Панорамуйте земну кулю або занурюйтеся в штати США, щоб виявити гарячі точки. Порівняйте оцінки «подобається», «не подобається» та співвідношення в оновленій підказці та виділіть регіони, які першими реагують на ваші завантаження.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Таблиця лідерів найкращих країн\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Розташуйте кожну країну за чистою активністю, уподобаннями, невподобаннями або співвідношенням. Фільтруйте за допомогою тих самих попередніх налаштувань, що й хронологія, щоб дізнатися, куди потрапляють кампанії, і спланувати, що публікувати далі.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Знімок екрана незабаром — зафіксуйте це зображення в DevTools, коли будете готові.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Заповнювач: огляд часової шкали\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Заповнювач: глобальна карта діяльності\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Заповнювач: таблиця лідерів країн\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Як розблокувати преміум аналітику\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Готові зануритися? Виконайте ці швидкі дії та оновіть вкладку YouTube.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Відкрийте спливаюче вікно Return YouTube Dislike і ввійдіть за допомогою Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Виберіть рівень Premium, щоб увімкнути передачу аналітичних даних у свій обліковий запис.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Перезавантажте будь-яку сторінку перегляду YouTube — ваша хронологія, карта та таблиця лідерів з’являться миттєво.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Основні моменти випуску\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Попередні налаштування шкали часу транслюють дані в реальному часі з прив’язками діапазону для першого та останнього сегментів.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Таблиця лідерів країни миттєво оновлюється, тож ви можете помітити регіони, що розвиваються, не залишаючи панелі.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Інтерактивна карта висвітлює гарячі точки світу та штатів США з більш чіткими підказками та показниками.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Потрібна допомога?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Ми готові, якщо ви зіткнетеся з труднощами — надішліть свої запитання до служби підтримки або до спільноти.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Запитайте у спільноти\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Логотип Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/vi/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"Trạng thái API:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"Ngoại tuyến\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"Trực tuyến\"\n  },\n  \"colorTheme\": {\n    \"message\": \"Chủ đề màu:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"Tô màu thanh tỉ lệ đánh giá\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"Dùng màu tùy chọn cho thanh tỉ lệ đánh giá.\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"Tô màu nút đánh giá\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"Dùng màu tùy chọn cho nút đánh giá.\"\n  },\n  \"considerDonating\": {\n    \"message\": \"Sự quyên góp của bạn giúp tiện ích mở rộng này duy trì hoạt động. Mong bạn hỗ trợ dự án này.\"\n  },\n  \"customColors\": {\n    \"message\": \"Màu tùy chọn cho nút đánh giá và thanh tỉ lệ đánh giá\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"Định dạng số tùy chỉnh\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"Tắt ghi log phản hồi API trong bảng điều khiển.\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"Tắt ghi log ra bảng điều khiển\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"Phục hồi chức năng hiển thị số lượt \\\"không thích\\\"\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"Cài đặt\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"Ghi chép Thay đổi\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"Quyên góp\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"Hỏi-Đáp\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"Trợ giúp\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"Trang mạng\"\n  },\n  \"numberFormat\": {\n    \"message\": \"Định dạng số:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Tính năng cao cấp dành cho người ủng hộ trên Patreon\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"Đăng nhập bằng Patreon\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"Không thể hoàn tất đăng nhập. Vui lòng thử lại.\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"Không thể bắt đầu đăng nhập Patreon. Vui lòng thử lại.\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"Đăng xuất\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"Đăng nhập cần quyền \\\"identity\\\". Vui lòng cho phép để tiếp tục.\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"Cấp cơ bản\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"Đang kiểm tra tư cách thành viên…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"Không có gói thành viên đang hoạt động\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"Người ủng hộ Premium\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"Người ủng hộ\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Người dùng Patreon\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"Định dạng lại số lượt đánh giá\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"Đồng bộ định dạng đánh giá \\\"thích\\\" và \\\"không thích\\\".\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"Ẩn bảng giới thiệu Premium\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"Không hiển thị bảng giới thiệu Premium trên trang xem YouTube.\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"Hiện số lượt đánh giá đã làm tròn\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"Làm tròn số lượt đánh giá (hành vi mặc định của YouTube).\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"Có hỗ trợ YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"Hiển thị phần trăm trong tooltip của thanh thích/không thích.\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"Hiển thị phần trăm trong tooltip của thanh thích/không thích.\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"Chủ đề màu:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"Cổ điển\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"Mù màu\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"Sáng chói\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"Tô màu thanh tỉ lệ đánh giá\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"Dùng màu tùy chọn cho thanh tỉ lệ đánh giá.\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"Tô màu nút đánh giá\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"Dùng màu tùy chọn cho nút đánh giá.\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"Đồng bộ định dạng đánh giá \\\"thích\\\" và \\\"không thích\\\"\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"Định dạng lại số lượt đánh giá.\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"bởi Dmitry Selivanov và Cộng đồng\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"Chủ kênh đã Tắt Đánh giá\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"Định dạng số:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"Làm tròn số lượt đánh giá (hành vi mặc định của YouTube)\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"Hiện số lượt đánh giá đã làm tròn.\"\n  },\n  \"textSettings\": {\n    \"message\": \"Tắt chức năng gửi đánh giá\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"Ngừng đếm đánh giá của tôi.\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"Tạm thời Không sử dụng được\"\n  },\n  \"textUpdate\": {\n    \"message\": \"Cập nhật\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"Chế độ phần trăm:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"Dùng hiển thị phần trăm tùy chỉnh khi di chuột.\"\n  },\n  \"version30installed\": {\n    \"message\": \"Đã cài Phiên bản 3.0.0.13\"\n  },\n  \"whatsnew\": {\n    \"message\": \"Điểm Mới\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"Phân tích video cao cấp\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"Tương tác theo thời gian\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"Quốc gia hàng đầu\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"Quốc gia có nhiều lượt thích nhất\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"Quốc gia có nhiều lượt không thích nhất\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"Địa lý\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"Mở rộng\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"Thu gọn\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"Chỉ số bản đồ\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"Quay lại bản đồ thế giới\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"Lượt thích\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"Lượt không thích\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"Tỷ lệ\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"Khoảng dữ liệu\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"Những ngày gần nhất\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"Những ngày đầu\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"Hiển thị những ngày mới nhất trong khoảng đã chọn\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"Hiển thị những ngày sớm nhất trong khoảng đã chọn\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"Tùy chọn riêng\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"Toàn thời gian\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"$1 gần nhất\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"$1 đầu tiên\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 ngày\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 ngày\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"Toàn thời gian\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1n\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"Khoảng thời gian đã chọn: $1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"Người dùng tiện ích\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 lượt thích\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 lượt không thích\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"Đã ghi nhận $1 tương tác từ $2 quốc gia ($3 địa chỉ IP duy nhất)\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"Lượt đánh giá mỗi giờ\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"Lượt đánh giá mỗi ngày\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"Lượt đánh giá mỗi tuần\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"Lượt đánh giá mỗi $1 giờ\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"Lượt đánh giá mỗi $1 ngày\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"Lượt đánh giá mỗi $1 phút\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"Lượt đánh giá mỗi phút\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"Lượt đánh giá mỗi $1 giây\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"Lượt đánh giá mỗi giây\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"Chưa có dữ liệu\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"Không rõ\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"Đang tải phân tích…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"Vui lòng cấp quyền Patreon lại để mở khóa phân tích.\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"Phân tích cao cấp chỉ dành cho người ủng hộ Patreon đang hoạt động.\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"Phiên đã hết hạn. Hãy đăng nhập lại bằng Patreon.\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"Dịch vụ phân tích hiện không khả dụng.\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"Không thể kết nối với dịch vụ phân tích.\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"Không thể tải phân tích cao cấp.\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"Hoạt động\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"Tỷ lệ lượt thích\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"Tỷ lệ lượt thích cao hơn\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"Tỷ lệ lượt thích thấp hơn\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"Nhiều lượt thích hơn\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"Ít lượt thích hơn\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"Nhiều lượt không thích hơn\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"Ít lượt không thích hơn\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"Nhiều $1 hơn\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"Ít $1 hơn\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"Lượt thích: $1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"Lượt không thích: $1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"Tỷ lệ lượt thích: $1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"Tổng quan nhanh về lượt không thích\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"Lượt không thích thô là báo cáo trực tiếp từ người dùng tiện ích đang xem video này.\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"Mở khóa Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"Xem thử toàn bộ phân tích\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"Số lượt không thích thô\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"Ước tính lượt không thích\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"Ước tính lượt thích\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"Đang lấy tổng lượt không thích mới nhất…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"Không thể tải dữ liệu lượt không thích lúc này. Thử tải lại trang.\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"Cách mở khóa phân tích Premium\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"Mở khóa phân tích video Premium trong ba bước nhanh:\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"Đăng nhập Patreon từ cửa sổ bật lên của tiện ích Return YouTube Dislike.\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"Chọn gói Premium để bật dòng thời gian hoạt động, bảng xếp hạng quốc gia và bản đồ tương tác.\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"Tải lại YouTube để xem xu hướng theo giờ, quốc gia hàng đầu và chi tiết từng bang của Hoa Kỳ cho mỗi video.\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Phân tích Premium đã bị khóa\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"Tư cách thành viên Patreon của bạn chưa bao gồm hạng Premium. Nâng cấp gói ủng hộ để mở khóa các số liệu phân tích video nâng cao.\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"Nâng cấp trên Patreon\"\n  },\n  \"changelog_title\": {\n    \"message\": \"Nhật ký thay đổi của Analytics cao cấp · Ngày 20 tháng 10 năm 2025\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"Bản phát hành cao cấp\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"Phân tích cao cấp, được mô phỏng lại\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"Khám phá dòng thời gian hoạt động trực tiếp, bảng xếp hạng các quốc gia hàng đầu và bản đồ tương tác đi kèm với Return YouTube Dislike Premium.\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"Phát hành vào ngày 20 tháng 10 năm 2025\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"Nâng cấp trên Patreon\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"Đọc hướng dẫn thiết lập\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"Có gì mới\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"Dòng thời gian hoạt động trực tiếp\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"Xem luồng lượt thích và không thích trong thời gian thực. Chuyển đổi các cài đặt trước hàng giờ, hàng ngày hoặc mọi thời điểm mà không cần tải lại, neo cửa sổ vào ngày đầu tiên hoặc phiếu bầu mới nhất và xem xét mọi mức tăng đột biến để xem điều gì đã kích hoạt sự tương tác.\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"Thế giới tương tác & bản đồ nhiệt của Hoa Kỳ\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"Xoay khắp thế giới hoặc đi sâu vào các tiểu bang của Hoa Kỳ để tìm ra các điểm nóng. So sánh lượt thích, lượt không thích và tỷ lệ trong chú giải công cụ được nâng cấp và đánh dấu các khu vực phản ứng đầu tiên với video tải lên của bạn.\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"Bảng xếp hạng các quốc gia hàng đầu\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"Xếp hạng mọi quốc gia theo hoạt động thô, lượt thích, lượt không thích hoặc tỷ lệ. Lọc với các giá trị đặt trước tương tự như dòng thời gian để tìm hiểu vị trí chiến dịch diễn ra và lên kế hoạch xuất bản nội dung tiếp theo.\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"Sắp có ảnh chụp màn hình—chụp chế độ xem này trong DevTools khi sẵn sàng.\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"Trình giữ chỗ: tổng quan về dòng thời gian\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"Giữ chỗ: bản đồ hoạt động toàn cầu\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"Trình giữ chỗ: bảng xếp hạng các quốc gia\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"Cách mở khóa Phân tích cao cấp\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"Sẵn sàng để đi sâu vào? Hãy làm theo các bước nhanh sau đây và làm mới tab YouTube của bạn.\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"Mở cửa sổ bật lên Trả lại lượt không thích trên YouTube và đăng nhập bằng Patreon.\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"Chọn cấp Premium để bật truyền dữ liệu phân tích tới tài khoản của bạn.\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"Tải lại bất kỳ trang xem YouTube nào—dòng thời gian, bản đồ và bảng xếp hạng của bạn sẽ xuất hiện ngay lập tức.\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"Phát hành điểm nổi bật\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"Giá trị đặt trước của dòng thời gian hoạt động truyền phát dữ liệu trực tiếp với các điểm cố định phạm vi cho các nhóm đầu tiên và mới nhất.\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"Bảng xếp hạng quốc gia làm mới ngay lập tức để bạn có thể phát hiện các khu vực đang phát triển mà không cần rời khỏi bảng điều khiển.\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"Bản đồ tương tác làm nổi bật các điểm nóng trên toàn cầu và tiểu bang Hoa Kỳ bằng các chú giải công cụ và số liệu rõ ràng hơn.\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"Cần một bàn tay?\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"Chúng tôi luôn sẵn sàng hỗ trợ nếu bạn gặp khó khăn—hãy đưa câu hỏi của bạn đến tài liệu hỗ trợ hoặc cộng đồng.\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"Hỏi cộng đồng\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Logo Return YouTube Dislike\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/zh_CN/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API 状态:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"离线\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"在线\"\n  },\n  \"colorTheme\": {\n    \"message\": \"配色主题:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"为比例条着色\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"为比例条使用自定义颜色。\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"为点赞/点踩图标着色\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"为点赞/点踩图标使用自定义颜色。\"\n  },\n  \"considerDonating\": {\n    \"message\": \"您的捐助是扩展得以持续运行的唯一动力，请考虑支持本项目。\"\n  },\n  \"customColors\": {\n    \"message\": \"为点踩进度条与按钮自定义颜色\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"自定义数字格式\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"在控制台中禁用 API 响应日志。\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"禁用控制台日志\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"恢复查看点踩数的能力\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"设置\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"更新日志\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"捐赠\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"常见问题\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"帮助\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"网站\"\n  },\n  \"numberFormat\": {\n    \"message\": \"数字格式:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Patreon 赞助者可使用高级功能\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"使用 Patreon 登录\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"登录未完成，请重试。\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"无法开始 Patreon 登录，请重试。\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"退出登录\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"登录需要 'identity' 权限。请允许以继续。\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"基础等级\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"正在检查会员身份…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"无有效会员\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"高级赞助者\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"赞助者\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon 用户\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"重新格式化点赞数字\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"使点赞/点踩的显示格式保持一致。\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"隐藏 Premium 推广面板\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"不要在 YouTube 播放页面显示 Premium 推广。\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"显示取整后的数字\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"对数字向下取整（YouTube 默认行为）。\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"支持 YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"在点赞/点踩条的工具提示中显示百分比。\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"在点赞/点踩条的工具提示中显示百分比。\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"配色主题:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"经典\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"无障碍\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"霓虹\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"为比例条着色\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"为比例条使用自定义颜色。\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"为点赞/点踩图标着色\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"为点赞/点踩图标使用自定义颜色。\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"使点赞/点踩的显示格式保持一致\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"重新格式化点赞数字。\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"由 Dmitry Selivanov 与社区开发\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"已被作者禁用\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"数字格式:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"对点赞/点踩统计取整（YouTube 默认行为）\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"显示取整后的统计。\"\n  },\n  \"textSettings\": {\n    \"message\": \"禁用点赞/点踩提交\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"停止统计您提交的点赞和点踩。\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"暂时不可用\"\n  },\n  \"textUpdate\": {\n    \"message\": \"更新到\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"百分比模式:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"悬停时使用自定义百分比显示。\"\n  },\n  \"version30installed\": {\n    \"message\": \"已安装版本 __RYD_VERSION__\"\n  },\n  \"whatsnew\": {\n    \"message\": \"新变化\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"高级视频洞察\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"随时间变化的互动\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"排名靠前的国家\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"点赞最多的国家\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"点踩最多的国家\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"地理分布\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"展开\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"收起\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"地图指标\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"返回世界地图\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"点赞\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"点踩\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"比率\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"数据窗口\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"最新天数\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"最早天数\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"显示所选时间范围内最新的天数\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"显示所选时间范围内最早的天数\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"自定义选择\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"全部时间\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"最近 $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"最初 $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 天\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 天\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"全部时间\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1天\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"已选时间段：$1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"扩展用户\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 次点赞\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 次点踩\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"记录了来自 $2 个国家的 $1 次互动（$3 个唯一 IP）\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"每小时投票数\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"每天投票数\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"每周投票数\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"每 $1 小时投票数\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"每 $1 天投票数\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"每 $1 分钟投票数\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"每分钟投票数\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"每 $1 秒投票数\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"每秒投票数\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"暂无数据\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"未知\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"正在加载洞察…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"请重新授权 Patreon 以解锁分析功能。\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"高级分析仅向活跃的 Patreon 支持者开放。\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"会话已过期，请重新使用 Patreon 登录。\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"分析后端当前不可用。\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"无法连接到分析服务。\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"无法加载高级分析。\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"活动\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"点赞比例\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"更高的点赞比例\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"更低的点赞比例\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"更多点赞\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"更少点赞\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"更多点踩\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"更少点踩\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"更多 $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"更少 $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"点赞：$1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"点踩：$1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"点赞比例：$1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"点踩洞察速览\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"原始点踩来自正在观看此视频的扩展用户的直接反馈。\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"解锁 Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"预览完整分析\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"原始点踩数\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"预计点踩数\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"预计点赞数\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"正在获取最新的点踩总数…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"暂时无法加载点踩数据，请尝试刷新页面。\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"如何解锁高级分析\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"三步快速解锁高级视频洞察：\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"在 Return YouTube Dislike 扩展弹窗中使用 Patreon 登录。\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"选择 Premium 等级以启用活动时间线、国家排行榜和交互式地图。\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"重新加载 YouTube 以查看每个视频的小时趋势、热门国家和美国各州的详细数据。\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium 高级分析已锁定\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"您的 Patreon 会员资格不包含 Premium 等级。升级支持即可解锁高级视频分析功能。\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"前往 Patreon 升级\"\n  },\n  \"changelog_title\": {\n    \"message\": \"高级分析变更日志 · 2025 年 10 月 20 日\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"高级版\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"重新构想的高级分析\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"探索 Return YouTube Dislike Premium 中包含的实时活动时间表、热门国家/地区排行榜和交互式地图。\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"发布于 2025 年 10 月 20 日\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"在 Patreon 上升级\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"阅读设置指南\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"什么是新的\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"现场活动时间线\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"实时观看喜欢和不喜欢的内容。切换每小时、每天或所有时间预设，无需重新加载，将窗口锚定到第一天或最新投票，并擦洗每个峰值以查看触发参与度的因素。\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"互动世界和美国热图\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"平移全球或潜入美国各州以发现热点。在升级后的工具提示中比较喜欢、不喜欢和比率，并突出显示对您上传的内容首先做出反应的区域。\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"热门国家排行榜\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"根据原始活动、喜欢、不喜欢或比率对每个国家/地区进行排名。使用与时间线相同的预设进行过滤，以了解营销活动的落地位置并计划下一步要发布的内容。\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"屏幕截图即将推出 - 准备好后在 DevTools 中捕获此视图。\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"占位符：时间线概述\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"占位符：全球活动地图\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"占位符：国家排行榜\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"如何解锁高级分析\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"准备好潜入了吗？请按照以下快速步骤操作并刷新您的 YouTube 标签。\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"打开“返回 YouTube 不喜欢”弹出窗口并使用 Patreon 登录。\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"选择高级层以启用分析数据流式传输到您的帐户。\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"重新加载任何 YouTube 观看页面 - 您的时间线、地图和排行榜会立即显示。\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"发布亮点\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"活动时间线预设使用第一个和最新存储桶的范围锚点来流式传输实时数据。\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"国家/地区排行榜会立即刷新，因此您无需离开面板即可发现正在上升的地区。\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"交互式地图通过更清晰的工具提示和指标突出显示全球和美国各州的热点。\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"需要帮忙吗？\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"如果您遇到困难，我们会随时为您服务 - 将您的问题提交给支持文档或社区。\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"询问社区\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike 标志\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/_locales/zh_TW/messages.json",
    "content": "{\n  \"apiStatusLabel\": {\n    \"message\": \"API 狀態:\"\n  },\n  \"apiStatusOffline\": {\n    \"message\": \"離線\"\n  },\n  \"apiStatusOnline\": {\n    \"message\": \"線上\"\n  },\n  \"colorTheme\": {\n    \"message\": \"配色主題:\"\n  },\n  \"colorizeRatio\": {\n    \"message\": \"為比例條著色\"\n  },\n  \"colorizeRatioHover\": {\n    \"message\": \"為比例條使用自訂顏色。\"\n  },\n  \"colorizeThumbs\": {\n    \"message\": \"為按讚/不喜歡圖示著色\"\n  },\n  \"colorizeThumbsHover\": {\n    \"message\": \"為按讚/不喜歡圖示使用自訂顏色。\"\n  },\n  \"considerDonating\": {\n    \"message\": \"您的贊助是此擴充功能持續運作的唯一動力，請考慮支持本專案。\"\n  },\n  \"customColors\": {\n    \"message\": \"自訂不喜歡進度條與按鈕顏色\"\n  },\n  \"customNumberFormats\": {\n    \"message\": \"自訂數字格式\"\n  },\n  \"disableLoggingHover\": {\n    \"message\": \"在主控台中停用 API 回應記錄。\"\n  },\n  \"disableLoggingLabel\": {\n    \"message\": \"停用主控台記錄\"\n  },\n  \"extensionDesc\": {\n    \"message\": \"恢復顯示不喜歡數\"\n  },\n  \"extensionName\": {\n    \"message\": \"Return YouTube Dislike\"\n  },\n  \"extensionNameBeta\": {\n    \"message\": \"Return YouTube Dislike Beta\"\n  },\n  \"legendSettings\": {\n    \"message\": \"設定\"\n  },\n  \"linkChangelog\": {\n    \"message\": \"變更記錄\"\n  },\n  \"linkDiscord\": {\n    \"message\": \"Discord\"\n  },\n  \"linkDonate\": {\n    \"message\": \"贊助\"\n  },\n  \"linkFAQ\": {\n    \"message\": \"常見問題\"\n  },\n  \"linkGithub\": {\n    \"message\": \"GitHub\"\n  },\n  \"linkHelp\": {\n    \"message\": \"說明\"\n  },\n  \"linkWebsite\": {\n    \"message\": \"網站\"\n  },\n  \"numberFormat\": {\n    \"message\": \"數字格式:\"\n  },\n  \"patreonBadgePrefix\": {\n    \"message\": \"Patreon\"\n  },\n  \"patreonLoggedOutMessage\": {\n    \"message\": \"Patreon 贊助者可使用進階功能\"\n  },\n  \"patreonLogin\": {\n    \"message\": \"使用 Patreon 登入\"\n  },\n  \"patreonLoginCompleteFailed\": {\n    \"message\": \"無法完成登入，請再試一次。\"\n  },\n  \"patreonLoginStartFailed\": {\n    \"message\": \"無法啟動 Patreon 登入，請再試一次。\"\n  },\n  \"patreonLogout\": {\n    \"message\": \"登出\"\n  },\n  \"patreonPermissionRequired\": {\n    \"message\": \"登入需要 'identity' 權限。請允許以繼續。\"\n  },\n  \"patreonTierBasic\": {\n    \"message\": \"基礎等級\"\n  },\n  \"patreonTierChecking\": {\n    \"message\": \"正在檢查會員身分…\"\n  },\n  \"patreonTierNone\": {\n    \"message\": \"沒有有效會員\"\n  },\n  \"patreonTierPremium\": {\n    \"message\": \"進階贊助者\"\n  },\n  \"patreonTierSupporter\": {\n    \"message\": \"贊助者\"\n  },\n  \"patreonUserFallback\": {\n    \"message\": \"Patreon 使用者\"\n  },\n  \"reformatLikes\": {\n    \"message\": \"重新格式化按讚數\"\n  },\n  \"reformatLikesHover\": {\n    \"message\": \"使按讚/不喜歡的顯示格式一致。\"\n  },\n  \"hidePremiumTeaser\": {\n    \"message\": \"隱藏 Premium 宣傳面板\"\n  },\n  \"hidePremiumTeaserHover\": {\n    \"message\": \"不要在 YouTube 觀看頁面顯示 Premium 宣傳。\"\n  },\n  \"roundNumbers\": {\n    \"message\": \"顯示取整後的數字\"\n  },\n  \"roundNumbersHover\": {\n    \"message\": \"對數字向下取整（YouTube 預設行為）。\"\n  },\n  \"shortsSupport\": {\n    \"message\": \"支援 YouTube Shorts\"\n  },\n  \"showTooltipPercentage\": {\n    \"message\": \"在按讚/不喜歡列的工具提示中顯示百分比。\"\n  },\n  \"showTooltipPercentageHover\": {\n    \"message\": \"在按讚/不喜歡列的工具提示中顯示百分比。\"\n  },\n  \"textColorTheme\": {\n    \"message\": \"配色主題:\"\n  },\n  \"textColorTheme1\": {\n    \"message\": \"經典\"\n  },\n  \"textColorTheme2\": {\n    \"message\": \"無障礙\"\n  },\n  \"textColorTheme3\": {\n    \"message\": \"霓虹\"\n  },\n  \"textColorizeRatioBar\": {\n    \"message\": \"為比例條著色\"\n  },\n  \"textColorizeRatioBarHover\": {\n    \"message\": \"為比例條使用自訂顏色。\"\n  },\n  \"textColorizeThumbs\": {\n    \"message\": \"為按讚/不喜歡圖示著色\"\n  },\n  \"textColorizeThumbsHover\": {\n    \"message\": \"為按讚/不喜歡圖示使用自訂顏色。\"\n  },\n  \"textConsistentFormat\": {\n    \"message\": \"使按讚/不喜歡的顯示格式一致\"\n  },\n  \"textConsistentFormatHover\": {\n    \"message\": \"重新格式化按讚數。\"\n  },\n  \"textDeveloper\": {\n    \"message\": \"由 Dmitry Selivanov 與社群開發\"\n  },\n  \"textLikesDisabled\": {\n    \"message\": \"由擁有者停用\"\n  },\n  \"textNumberFormat\": {\n    \"message\": \"數字格式:\"\n  },\n  \"textRoundingNumbers\": {\n    \"message\": \"對按讚/不喜歡統計取整（YouTube 預設行為）\"\n  },\n  \"textRoundingNumbersHover\": {\n    \"message\": \"顯示取整後的統計。\"\n  },\n  \"textSettings\": {\n    \"message\": \"停用送出按讚/不喜歡\"\n  },\n  \"textSettingsHover\": {\n    \"message\": \"停止計入您送出的按讚與不喜歡。\"\n  },\n  \"textTempUnavailable\": {\n    \"message\": \"暫時無法使用\"\n  },\n  \"textUpdate\": {\n    \"message\": \"更新至\"\n  },\n  \"tooltipPercentageMode\": {\n    \"message\": \"百分比模式:\"\n  },\n  \"tooltipPercentageModeHover\": {\n    \"message\": \"游標懸停時使用自訂百分比顯示。\"\n  },\n  \"version30installed\": {\n    \"message\": \"已安裝版本 __RYD_VERSION__\"\n  },\n  \"whatsnew\": {\n    \"message\": \"最新變更\"\n  },\n  \"premiumAnalytics_title\": {\n    \"message\": \"進階影片洞察\"\n  },\n  \"premiumAnalytics_activityTitle\": {\n    \"message\": \"隨時間變化的互動\"\n  },\n  \"premiumAnalytics_listsTitle\": {\n    \"message\": \"前幾大國家\"\n  },\n  \"premiumAnalytics_listLikesTitle\": {\n    \"message\": \"最多按讚的國家\"\n  },\n  \"premiumAnalytics_listDislikesTitle\": {\n    \"message\": \"最多不喜歡的國家\"\n  },\n  \"premiumAnalytics_mapTitle\": {\n    \"message\": \"地理分佈\"\n  },\n  \"premiumAnalytics_expand\": {\n    \"message\": \"展開\"\n  },\n  \"premiumAnalytics_collapse\": {\n    \"message\": \"收合\"\n  },\n  \"premiumAnalytics_mapMetricLabel\": {\n    \"message\": \"地圖指標\"\n  },\n  \"premiumAnalytics_mapReset\": {\n    \"message\": \"返回世界地圖\"\n  },\n  \"premiumAnalytics_modeLikes\": {\n    \"message\": \"按讚\"\n  },\n  \"premiumAnalytics_modeDislikes\": {\n    \"message\": \"不喜歡\"\n  },\n  \"premiumAnalytics_modeRatio\": {\n    \"message\": \"比例\"\n  },\n  \"premiumAnalytics_windowGroupLabel\": {\n    \"message\": \"資料區間\"\n  },\n  \"premiumAnalytics_windowLatest\": {\n    \"message\": \"最新天數\"\n  },\n  \"premiumAnalytics_windowFirst\": {\n    \"message\": \"最早天數\"\n  },\n  \"premiumAnalytics_windowLatestTitle\": {\n    \"message\": \"顯示所選區間內最新的天數\"\n  },\n  \"premiumAnalytics_windowFirstTitle\": {\n    \"message\": \"顯示所選區間內最早的天數\"\n  },\n  \"premiumAnalytics_windowSummaryCustom\": {\n    \"message\": \"自訂選取\"\n  },\n  \"premiumAnalytics_windowSummaryAllTime\": {\n    \"message\": \"全部期間\"\n  },\n  \"premiumAnalytics_windowSummaryLast\": {\n    \"message\": \"最近 $1\"\n  },\n  \"premiumAnalytics_windowSummaryFirst\": {\n    \"message\": \"最初 $1\"\n  },\n  \"premiumAnalytics_daysSingular\": {\n    \"message\": \"$1 天\"\n  },\n  \"premiumAnalytics_daysPlural\": {\n    \"message\": \"$1 天\"\n  },\n  \"premiumAnalytics_rangeLabelAllTime\": {\n    \"message\": \"全部期間\"\n  },\n  \"premiumAnalytics_rangeLabelDays\": {\n    \"message\": \"$1天\"\n  },\n  \"premiumAnalytics_selectedPeriod\": {\n    \"message\": \"選取期間：$1\"\n  },\n  \"premiumAnalytics_totalsHeading\": {\n    \"message\": \"擴充功能使用者\"\n  },\n  \"premiumAnalytics_totalsLikes\": {\n    \"message\": \"$1 次按讚\"\n  },\n  \"premiumAnalytics_totalsDislikes\": {\n    \"message\": \"$1 次不喜歡\"\n  },\n  \"premiumAnalytics_summaryMeta\": {\n    \"message\": \"已記錄 $1 次互動，來自 $2 個國家（$3 個唯一 IP）\"\n  },\n  \"premiumAnalytics_bucketHour\": {\n    \"message\": \"每小時投票數\"\n  },\n  \"premiumAnalytics_bucketDay\": {\n    \"message\": \"每天投票數\"\n  },\n  \"premiumAnalytics_bucketWeek\": {\n    \"message\": \"每週投票數\"\n  },\n  \"premiumAnalytics_bucketHours\": {\n    \"message\": \"每 $1 小時投票數\"\n  },\n  \"premiumAnalytics_bucketDays\": {\n    \"message\": \"每 $1 天投票數\"\n  },\n  \"premiumAnalytics_bucketMinutes\": {\n    \"message\": \"每 $1 分鐘投票數\"\n  },\n  \"premiumAnalytics_bucketMinute\": {\n    \"message\": \"每分鐘投票數\"\n  },\n  \"premiumAnalytics_bucketSeconds\": {\n    \"message\": \"每 $1 秒投票數\"\n  },\n  \"premiumAnalytics_bucketSecond\": {\n    \"message\": \"每秒投票數\"\n  },\n  \"premiumAnalytics_noData\": {\n    \"message\": \"尚無資料\"\n  },\n  \"premiumAnalytics_unknownRegion\": {\n    \"message\": \"未知\"\n  },\n  \"premiumAnalytics_loading\": {\n    \"message\": \"正在載入洞察…\"\n  },\n  \"premiumAnalytics_errorReauth\": {\n    \"message\": \"請重新授權 Patreon 以解鎖分析功能。\"\n  },\n  \"premiumAnalytics_errorInactive\": {\n    \"message\": \"進階分析僅提供給活躍的 Patreon 贊助者。\"\n  },\n  \"premiumAnalytics_errorSession\": {\n    \"message\": \"工作階段已過期，請再次使用 Patreon 登入。\"\n  },\n  \"premiumAnalytics_errorBackend\": {\n    \"message\": \"分析後端目前無法使用。\"\n  },\n  \"premiumAnalytics_errorNetwork\": {\n    \"message\": \"無法連線到分析服務。\"\n  },\n  \"premiumAnalytics_errorGeneric\": {\n    \"message\": \"無法載入進階分析。\"\n  },\n  \"premiumAnalytics_mapSeriesLabel\": {\n    \"message\": \"活動\"\n  },\n  \"premiumAnalytics_likeRatio\": {\n    \"message\": \"按讚比例\"\n  },\n  \"premiumAnalytics_visualHighRatio\": {\n    \"message\": \"較高的按讚比例\"\n  },\n  \"premiumAnalytics_visualLowRatio\": {\n    \"message\": \"較低的按讚比例\"\n  },\n  \"premiumAnalytics_visualHighLikes\": {\n    \"message\": \"更多按讚\"\n  },\n  \"premiumAnalytics_visualLowLikes\": {\n    \"message\": \"更少按讚\"\n  },\n  \"premiumAnalytics_visualHighDislikes\": {\n    \"message\": \"更多不喜歡\"\n  },\n  \"premiumAnalytics_visualLowDislikes\": {\n    \"message\": \"更少不喜歡\"\n  },\n  \"premiumAnalytics_visualHighGeneric\": {\n    \"message\": \"更多 $1\"\n  },\n  \"premiumAnalytics_visualLowGeneric\": {\n    \"message\": \"更少 $1\"\n  },\n  \"premiumAnalytics_tooltipLikes\": {\n    \"message\": \"按讚：$1\"\n  },\n  \"premiumAnalytics_tooltipDislikes\": {\n    \"message\": \"不喜歡：$1\"\n  },\n  \"premiumAnalytics_tooltipRatio\": {\n    \"message\": \"按讚比例：$1\"\n  },\n  \"premiumTeaser_title\": {\n    \"message\": \"不喜歡洞察快覽\"\n  },\n  \"premiumTeaser_subtitle\": {\n    \"message\": \"原始不喜歡數來自觀看此影片的擴充功能使用者的直接回報。\"\n  },\n  \"premiumTeaser_cta\": {\n    \"message\": \"解鎖 Premium\"\n  },\n  \"premiumTeaser_learn\": {\n    \"message\": \"預覽完整分析\"\n  },\n  \"premiumTeaser_statRaw\": {\n    \"message\": \"原始不喜歡數量\"\n  },\n  \"premiumTeaser_statDislikes\": {\n    \"message\": \"預估不喜歡數\"\n  },\n  \"premiumTeaser_statLikes\": {\n    \"message\": \"預估按讚數\"\n  },\n  \"premiumTeaser_statusLoading\": {\n    \"message\": \"正在取得最新的不喜歡總數…\"\n  },\n  \"premiumTeaser_statusError\": {\n    \"message\": \"目前無法載入不喜歡資料，請嘗試重新整理頁面。\"\n  },\n  \"premiumTeaser_breadcrumbsAria\": {\n    \"message\": \"如何解鎖進階分析\"\n  },\n  \"premiumTeaser_breadcrumbsTitle\": {\n    \"message\": \"三個步驟快速解鎖進階影片洞察：\"\n  },\n  \"premiumTeaser_breadcrumbStep1\": {\n    \"message\": \"在 Return YouTube Dislike 擴充功能的彈出視窗中使用 Patreon 登入。\"\n  },\n  \"premiumTeaser_breadcrumbStep2\": {\n    \"message\": \"選擇 Premium 方案以啟用活動時間軸、國家排行榜和互動地圖。\"\n  },\n  \"premiumTeaser_breadcrumbStep3\": {\n    \"message\": \"重新載入 YouTube，以查看每部影片的每小時趨勢、熱門國家與美國各州詳細資料。\"\n  },\n  \"premiumTierNotice_title\": {\n    \"message\": \"Premium 進階分析已鎖定\"\n  },\n  \"premiumTierNotice_message\": {\n    \"message\": \"您的 Patreon 會員資格未包含 Premium 等級。升級贊助即可解鎖進階影片分析功能。\"\n  },\n  \"premiumTierNotice_cta\": {\n    \"message\": \"前往 Patreon 升級\"\n  },\n  \"changelog_title\": {\n    \"message\": \"高級分析變更日誌 · 2025 年 10 月 20 日\"\n  },\n  \"changelog_badge\": {\n    \"message\": \"高級版\"\n  },\n  \"changelog_heading\": {\n    \"message\": \"重新構想的高級分析\"\n  },\n  \"changelog_subheading\": {\n    \"message\": \"探索 Return YouTube Dislike Premium 中包含的實時活動時間表、熱門國家/地區排行榜和交互式地圖。\"\n  },\n  \"changelog_releaseDate\": {\n    \"message\": \"發佈於 2025 年 10 月 20 日\"\n  },\n  \"changelog_cta_primary\": {\n    \"message\": \"在 Patreon 上升級\"\n  },\n  \"changelog_cta_secondary\": {\n    \"message\": \"閱讀設置指南\"\n  },\n  \"changelog_section_highlights\": {\n    \"message\": \"什麼是新的\"\n  },\n  \"changelog_feature_timeline_title\": {\n    \"message\": \"現場活動時間線\"\n  },\n  \"changelog_feature_timeline_body\": {\n    \"message\": \"實時觀看喜歡和不喜歡的內容。切換每小時、每天或所有時間預設，無需重新加載，將窗口錨定到第一天或最新投票，並擦洗每個峰值以查看觸發參與度的因素。\"\n  },\n  \"changelog_feature_map_title\": {\n    \"message\": \"互動世界和美國熱圖\"\n  },\n  \"changelog_feature_map_body\": {\n    \"message\": \"平移全球或潛入美國各州以發現熱點。在升級後的工具提示中比較喜歡、不喜歡和比率，並突出顯示對您上傳的內容首先做出反應的區域。\"\n  },\n  \"changelog_feature_quality_title\": {\n    \"message\": \"熱門國家排行榜\"\n  },\n  \"changelog_feature_quality_body\": {\n    \"message\": \"根據原始活動、喜歡、不喜歡或比率對每個國家/地區進行排名。使用與時間線相同的預設進行過濾，以了解營銷活動的落地位置併計劃下一步要發布的內容。\"\n  },\n  \"changelog_screenshot_placeholder\": {\n    \"message\": \"屏幕截圖即將推出 - 準備好後在 DevTools 中捕獲此視圖。\"\n  },\n  \"changelog_screenshot_label_timeline\": {\n    \"message\": \"佔位符：時間線概述\"\n  },\n  \"changelog_screenshot_label_map\": {\n    \"message\": \"佔位符：全球活動地圖\"\n  },\n  \"changelog_screenshot_label_teaser\": {\n    \"message\": \"佔位符：國家排行榜\"\n  },\n  \"changelog_section_upgrade\": {\n    \"message\": \"如何解鎖高級分析\"\n  },\n  \"changelog_upgrade_body\": {\n    \"message\": \"準備好潛入了嗎？請按照以下快速步驟操作並刷新您的 YouTube 標籤。\"\n  },\n  \"changelog_upgrade_step1\": {\n    \"message\": \"打開“返回 YouTube 不喜歡”彈出窗口並使用 Patreon 登錄。\"\n  },\n  \"changelog_upgrade_step2\": {\n    \"message\": \"選擇高級層以啟用分析數據流式傳輸到您的帳戶。\"\n  },\n  \"changelog_upgrade_step3\": {\n    \"message\": \"重新加載任何 YouTube 觀看頁面 - 您的時間線、地圖和排行榜會立即顯示。\"\n  },\n  \"changelog_section_release\": {\n    \"message\": \"發布亮點\"\n  },\n  \"changelog_release_item1\": {\n    \"message\": \"活動時間線預設使用第一個和最新存儲桶的範圍錨點來流式傳輸實時數據。\"\n  },\n  \"changelog_release_item2\": {\n    \"message\": \"國家/地區排行榜會立即刷新，因此您無需離開面板即可發現正在上升的地區。\"\n  },\n  \"changelog_release_item3\": {\n    \"message\": \"交互式地圖通過更清晰的工具提示和指標突出顯示全球和美國各州的熱點。\"\n  },\n  \"changelog_support_heading\": {\n    \"message\": \"需要幫忙嗎？\"\n  },\n  \"changelog_support_description\": {\n    \"message\": \"如果您遇到困難，我們會隨時為您服務 - 將您的問題提交給支持文檔或社區。\"\n  },\n  \"changelog_support_contact\": {\n    \"message\": \"詢問社區\"\n  },\n  \"changelog_brand_logo_alt\": {\n    \"message\": \"Return YouTube Dislike 標誌\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/changelog/3/changelog_3.0.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta content=\"text/html; charset=utf-8\" />\n    <title title=\"__MSG_extensionName__\">__MSG_extensionName__</title>\n    <link rel=\"stylesheet\" href=\"/changelog/changelog.css\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body>\n    <div class=\"container\">\n      <!-- Header -->\n      <div\n        style=\"\n          display: flex;\n          align-items: start;\n          position: sticky;\n          top: 4rem;\n          align-self: flex-start;\n        \"\n      >\n        <!-- Logo -->\n        <svg width=\"69\" viewBox=\"0 0 24 24\">\n          <path\n            d=\"M14.9 3H6c-.9 0-1.6.5-1.9 1.2l-3 7c-.1.3-.1.5-.1.7v2c0 1.1.9 2 2 2h6.3l-.9 4.5c-.1.5 0 1 .4 1.4l1.1 1.1 6.5-6.6c.4-.4.6-.9.6-1.4V5c-.1-1.1-1-2-2.1-2zm7.4 12.8h-2.9c-.4 0-.7-.3-.7-.7V3.9c0-.4.3-.7.7-.7h2.9c.4 0 .7.3.7.7V15c0 .4-.3.8-.7.8z\"\n            fill=\"#ff2222\"\n          />\n          <path d=\"m8 12.5 5.1-2.9L8 6.7v5.8z\" fill=\"#fff\" />\n        </svg>\n        <!-- Text -->\n        <div style=\"display: flex; flex-direction: column; margin-left: 2rem\">\n          <h1 style=\"margin: 0\" title=\"__MSG_extensionName__\">\n            __MSG_extensionName__\n          </h1>\n          <p\n            style=\"color: var(--lightGrey); margin-top: 0.5rem\"\n            title=\"__MSG_textDeveloper__\"\n          >\n            __MSG_textDeveloper__\n          </p>\n          <!-- Donation -->\n          <p\n            style=\"\n              max-width: 42ch;\n              margin-top: 1rem;\n              line-height: 1.75rem;\n              color: var(--lightGrey);\n            \"\n          >\n            __MSG_considerDonating__\n          </p>\n          <div>\n            <button id=\"link_website\" title=\"__MSG_linkWebsite__\">\n              __MSG_linkWebsite__\n            </button>\n            <button\n              style=\"margin-top: 0.3em; background: #ff0000\"\n              id=\"link_donate\"\n              title=\"__MSG_linkDonate__\"\n            >\n              __MSG_linkDonate__\n            </button>\n            <button id=\"link_discord\" title=\"__MSG_linkDiscord__\">__MSG_linkDiscord__</button>\n            <br />\n            <button\n              style=\"margin-top: 0.3rem\"\n              id=\"link_faq\"\n              title=\"__MSG_linkFAQ__\"\n            >\n              __MSG_linkFAQ__\n            </button>\n            <button id=\"link_github\" title=\"__MSG_linkGithub__\">__MSG_linkGithub__</button>\n            <button\n              style=\"margin-top: 0.3em\"\n              id=\"link_help\"\n              title=\"__MSG_linkHelp__\"\n            >\n              __MSG_linkHelp__\n            </button>\n          </div>\n        </div>\n      </div>\n\n      <!-- Changelog -->\n      <div style=\"padding: 0 2rem; line-height: 2rem; color: var(--lightGrey)\">\n        <h1 style=\"margin: 0; color: var(--tertiary)\">__MSG_whatsnew__:</h1>\n        <h2 style=\"margin: 0.5rem 0\">\n          <i style=\"color: var(--primary)\">* __MSG_version30installed__ *</i>\n        </h2>\n        <ul style=\"padding: 0 1rem\">\n          <li>__MSG_shortsSupport__</li>\n          <li>__MSG_customNumberFormats__</li>\n          <img src=\"/changelog/images/number_format.jpg\" />\n          <li>__MSG_customColors__</li>\n          <img\n            style=\"display: block\"\n            src=\"/changelog/images/colorize_example.jpg\"\n          />\n          <img src=\"/changelog/images/colorize.jpg\" />\n        </ul>\n      </div>\n    </div>\n  </body>\n  <script src=\"/changelog/changelog.js\"></script>\n</html>\n"
  },
  {
    "path": "Extensions/combined/changelog/4/changelog.css",
    "content": ":root {\n  color-scheme: light dark;\n  --ryd-bg: linear-gradient(135deg, #050505 0%, #141414 45%, #3c0a0f 100%);\n  --ryd-surface: rgba(8, 8, 8, 0.9);\n  --ryd-surface-light: rgba(248, 248, 248, 0.9);\n  --ryd-card: rgba(12, 12, 12, 0.85);\n  --ryd-card-border: rgba(255, 86, 94, 0.18);\n  --ryd-text-primary: #f9f9f9;\n  --ryd-text-secondary: rgba(251, 251, 251, 0.7);\n  --ryd-accent: #ff424d;\n  --ryd-accent-strong: #ff172e;\n  --ryd-muted: rgba(204, 204, 204, 0.55);\n  --ryd-shadow: 0 35px 80px rgba(125, 14, 24, 0.32);\n  --ryd-radius-lg: 32px;\n  --ryd-radius-md: 24px;\n  --ryd-radius-sm: 16px;\n  --ryd-gap: clamp(1.75rem, 4vw, 2.5rem);\n  font-family: \"Inter\", \"Segoe UI\", system-ui, -apple-system, sans-serif;\n}\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  background: var(--ryd-bg);\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding: clamp(1.5rem, 4vw, 3rem);\n  color: var(--ryd-text-primary);\n}\n\n.ryd-changelog {\n  width: min(100%, 1040px);\n  background: linear-gradient(180deg, rgba(15, 23, 42, 0.9) 0%, rgba(15, 23, 42, 0.92) 70%, rgba(15, 23, 42, 0.98) 100%);\n  border-radius: var(--ryd-radius-lg);\n  border: 1px solid rgba(148, 163, 184, 0.2);\n  box-shadow: var(--ryd-shadow);\n  padding: clamp(2.5rem, 5vw, 4rem);\n  position: relative;\n  overflow: hidden;\n}\n\n.ryd-changelog::after {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  background: radial-gradient(circle at top right, rgba(255, 66, 77, 0.32), transparent 45%),\n    radial-gradient(circle at 20% 80%, rgba(139, 0, 0, 0.28), transparent 55%);\n  pointer-events: none;\n  mix-blend-mode: lighten;\n}\n\n.ryd-changelog__hero {\n  position: relative;\n  z-index: 1;\n  text-align: left;\n  display: flex;\n  flex-direction: column;\n  gap: 1.25rem;\n}\n\n.ryd-changelog__brand {\n  display: flex;\n  align-items: center;\n  gap: 0.85rem;\n  background: rgba(15, 23, 42, 0.6);\n  border-radius: 999px;\n  padding: 0.35rem 0.85rem 0.35rem 0.35rem;\n  align-self: flex-start;\n  border: 1px solid rgba(255, 66, 77, 0.2);\n  box-shadow: 0 12px 30px rgba(255, 66, 77, 0.22);\n}\n\n.ryd-changelog__brand-logo {\n  display: block;\n  width: 56px;\n  height: 56px;\n  border-radius: 50%;\n  background: radial-gradient(circle at 35% 35%, rgba(255, 255, 255, 0.4), transparent 55%);\n  padding: 0.35rem;\n  box-sizing: border-box;\n  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.35));\n}\n\n.ryd-changelog__brand-text {\n  display: flex;\n  flex-direction: column;\n  gap: 0.1rem;\n}\n\n.ryd-changelog__brand-name {\n  font-size: 1.05rem;\n  font-weight: 700;\n  letter-spacing: 0.01em;\n}\n\n.ryd-changelog__brand-label {\n  font-size: 0.85rem;\n  color: var(--ryd-text-secondary);\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  font-weight: 600;\n}\n\n.ryd-changelog__badge {\n  display: inline-flex;\n  align-self: flex-start;\n  padding: 0.35rem 0.85rem;\n  border-radius: 100px;\n  background: rgba(255, 66, 77, 0.18);\n  color: var(--ryd-accent);\n  font-weight: 600;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  font-size: 0.8rem;\n}\n\n.ryd-changelog__title {\n  margin: 0;\n  font-size: clamp(2.25rem, 5vw, 3.25rem);\n  line-height: 1.05;\n  font-weight: 700;\n}\n\n.ryd-changelog__subtitle {\n  margin: 0;\n  max-width: 52ch;\n  color: var(--ryd-text-secondary);\n  font-size: clamp(1.05rem, 2.2vw, 1.25rem);\n  line-height: 1.6;\n}\n\n.ryd-changelog__actions {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n}\n\n.ryd-changelog__meta {\n  margin: 0;\n  color: var(--ryd-muted);\n  font-size: 0.95rem;\n}\n\n.ryd-button {\n  border: none;\n  border-radius: 999px;\n  padding: 0.85rem 1.75rem;\n  font-size: 0.95rem;\n  font-weight: 600;\n  letter-spacing: 0.01em;\n  cursor: pointer;\n  transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;\n}\n\n.ryd-button:focus-visible {\n  outline: 3px solid rgba(56, 189, 248, 0.55);\n  outline-offset: 3px;\n}\n\n.ryd-button--primary {\n  background: linear-gradient(135deg, var(--ryd-accent-strong), #ff5c6b);\n  color: #0b0b0b;\n  box-shadow: 0 20px 35px rgba(255, 41, 67, 0.35);\n}\n\n.ryd-button--primary:hover {\n  transform: translateY(-2px);\n  box-shadow: 0 25px 40px rgba(255, 41, 67, 0.45);\n}\n\n.ryd-button--ghost {\n  background: rgba(255, 255, 255, 0.08);\n  color: var(--ryd-text-primary);\n  border: 1px solid rgba(255, 66, 77, 0.25);\n}\n\n.ryd-button--ghost:hover {\n  background: rgba(255, 66, 77, 0.16);\n}\n\n.ryd-button--outline {\n  background: transparent;\n  color: var(--ryd-text-primary);\n  border: 1px solid rgba(255, 66, 77, 0.4);\n}\n\n.ryd-button--outline:hover {\n  background: rgba(255, 66, 77, 0.18);\n}\n\n.ryd-changelog__main {\n  position: relative;\n  z-index: 1;\n  margin-top: clamp(2.5rem, 5vw, 3.5rem);\n  display: flex;\n  flex-direction: column;\n  gap: var(--ryd-gap);\n}\n\n.ryd-section {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  background: var(--ryd-card);\n  border-radius: var(--ryd-radius-md);\n  border: 1px solid var(--ryd-card-border);\n  padding: clamp(1.75rem, 4vw, 2.5rem);\n  backdrop-filter: blur(14px);\n}\n\n.ryd-section__heading {\n  margin: 0;\n  font-size: clamp(1.45rem, 3vw, 1.85rem);\n}\n\n.ryd-section__intro {\n  margin: 0;\n  color: var(--ryd-text-secondary);\n  line-height: 1.6;\n  font-size: 1rem;\n}\n\n.ryd-feature-grid {\n  display: grid;\n  gap: clamp(1.75rem, 4vw, 2.5rem);\n  grid-template-columns: minmax(0, 1fr);\n}\n\n@media (max-width: 720px) {\n  .ryd-feature-grid {\n    gap: clamp(1.5rem, 6vw, 2rem);\n  }\n\n  .ryd-changelog__brand {\n    gap: 0.65rem;\n    padding: 0.3rem 0.75rem 0.3rem 0.3rem;\n  }\n\n  .ryd-changelog__brand-logo {\n    width: 48px;\n    height: 48px;\n  }\n}\n\n@media (max-width: 480px) {\n  .ryd-changelog__brand-name {\n    font-size: 0.95rem;\n  }\n\n  .ryd-changelog__brand-label {\n    font-size: 0.75rem;\n    letter-spacing: 0.06em;\n  }\n}\n\n.ryd-feature-card {\n  display: flex;\n  flex-direction: column;\n  gap: 1.25rem;\n  background: rgba(10, 10, 10, 0.8);\n  border-radius: var(--ryd-radius-sm);\n  border: 1px solid rgba(255, 66, 77, 0.18);\n  padding: 1.5rem;\n  position: relative;\n  overflow: hidden;\n}\n\n.ryd-feature-card::before {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  background: radial-gradient(circle at top right, rgba(255, 45, 73, 0.26), transparent 55%);\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n.ryd-feature-card__text {\n  position: relative;\n  z-index: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n}\n\n.ryd-feature-card__text h3 {\n  margin: 0;\n  font-size: 1.35rem;\n}\n\n.ryd-feature-card__text p {\n  margin: 0;\n  color: var(--ryd-text-secondary);\n  line-height: 1.55;\n  font-size: clamp(1rem, 2.2vw, 1.125rem);\n}\n\n.ryd-feature-card__visual {\n  position: relative;\n  z-index: 1;\n  border-radius: 14px;\n  border: 1px dashed rgba(255, 86, 94, 0.45);\n  background: rgba(32, 8, 12, 0.65);\n  overflow: hidden;\n  padding: clamp(0.5rem, 2vw, 0.85rem);\n}\n\n.ryd-feature-card__placeholder {\n  text-align: center;\n  display: flex;\n  flex-direction: column;\n  gap: 0.45rem;\n  padding: 1.25rem;\n}\n\n.ryd-feature-card__placeholder-label {\n  font-weight: 600;\n  letter-spacing: 0.04em;\n  text-transform: uppercase;\n  color: rgba(255, 106, 112, 0.78);\n}\n\n.ryd-feature-card__placeholder-copy {\n  color: var(--ryd-text-secondary);\n  font-size: 0.95rem;\n}\n\n.ryd-feature-card__visual img {\n  width: 100%;\n  height: auto;\n  object-fit: contain;\n  border-radius: inherit;\n  display: block;\n}\n\n.ryd-list {\n  margin: 0;\n  padding-left: 1.5rem;\n  display: flex;\n  flex-direction: column;\n  gap: 0.6rem;\n  color: var(--ryd-text-secondary);\n  line-height: 1.6;\n}\n\n.ryd-list--bulleted {\n  list-style: disc;\n}\n\n.ryd-list li::marker {\n  color: var(--ryd-accent);\n}\n\n.ryd-section--support {\n  align-items: flex-start;\n}\n\n.ryd-support {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n}\n\n.ryd-changelog__footer {\n  position: relative;\n  z-index: 1;\n  margin-top: clamp(2rem, 5vw, 3rem);\n  display: flex;\n  justify-content: space-between;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n  color: var(--ryd-muted);\n  font-size: 0.9rem;\n}\n\n@media (max-width: 720px) {\n  .ryd-changelog {\n    padding: clamp(1.75rem, 6vw, 2.5rem);\n    border-radius: 24px;\n  }\n\n  .ryd-section {\n    padding: clamp(1.5rem, 6vw, 2rem);\n  }\n\n  .ryd-feature-card__visual {\n    min-height: 160px;\n  }\n}\n\n@media (prefers-color-scheme: light) {\n  :root {\n    --ryd-bg: linear-gradient(135deg, #ffffff 0%, #f7f7f7 40%, #ffe5e7 100%);\n    --ryd-surface: rgba(255, 255, 255, 0.92);\n    --ryd-card: rgba(255, 255, 255, 0.92);\n    --ryd-card-border: rgba(255, 66, 77, 0.15);\n    --ryd-text-primary: #181818;\n    --ryd-text-secondary: rgba(24, 24, 24, 0.72);\n    --ryd-muted: rgba(120, 120, 120, 0.8);\n    --ryd-shadow: 0 30px 70px rgba(136, 8, 20, 0.16);\n  }\n\n  body {\n    color: var(--ryd-text-primary);\n  }\n\n  .ryd-changelog {\n    background: rgba(255, 255, 255, 0.94);\n  }\n\n  .ryd-feature-card {\n    background: rgba(255, 255, 255, 0.88);\n  }\n\n  .ryd-feature-card__visual {\n    background: rgba(255, 235, 237, 0.75);\n  }\n\n  .ryd-feature-card__placeholder-label {\n    color: rgba(184, 36, 46, 0.85);\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/changelog/4/changelog_4.0.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>__MSG_changelog_title__</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link rel=\"stylesheet\" href=\"changelog.css\" />\n  </head>\n  <body>\n    <div class=\"ryd-changelog\">\n      <header class=\"ryd-changelog__hero\">\n        <div class=\"ryd-changelog__brand\">\n          <img\n            class=\"ryd-changelog__brand-logo\"\n            src=\"../../icons/logo.svg\"\n            alt=\"__MSG_changelog_brand_logo_alt__\"\n            width=\"56\"\n            height=\"56\"\n          />\n          <div class=\"ryd-changelog__brand-text\">\n            <span class=\"ryd-changelog__brand-name\">__MSG_extensionName__</span>\n            <span class=\"ryd-changelog__brand-label\">__MSG_changelog_title__</span>\n          </div>\n        </div>\n        <div class=\"ryd-changelog__badge\">__MSG_changelog_badge__</div>\n        <h1 class=\"ryd-changelog__title\">__MSG_changelog_heading__</h1>\n        <p class=\"ryd-changelog__subtitle\">__MSG_changelog_subheading__</p>\n        <div class=\"ryd-changelog__actions\">\n          <button class=\"ryd-button ryd-button--primary\" id=\"ryd-changelog-upgrade\">\n            __MSG_changelog_cta_primary__\n          </button>\n          <button class=\"ryd-button ryd-button--ghost\" id=\"ryd-changelog-support\">\n            __MSG_changelog_cta_secondary__\n          </button>\n        </div>\n        <p class=\"ryd-changelog__meta\">__MSG_changelog_releaseDate__</p>\n      </header>\n\n      <main class=\"ryd-changelog__main\">\n        <section class=\"ryd-section\">\n          <h2 class=\"ryd-section__heading\">__MSG_changelog_section_highlights__</h2>\n          <div class=\"ryd-feature-grid\">\n            <article class=\"ryd-feature-card\">\n              <div class=\"ryd-feature-card__text\">\n                <h3>__MSG_changelog_feature_timeline_title__</h3>\n                <p>__MSG_changelog_feature_timeline_body__</p>\n              </div>\n              <figure class=\"ryd-feature-card__visual\">\n                <img\n                  src=\"../images/activity.jpg\"\n                  alt=\"Screenshot of the premium activity timeline showing hourly likes and dislikes.\"\n                  loading=\"lazy\"\n                />\n              </figure>\n            </article>\n\n            <article class=\"ryd-feature-card\">\n              <div class=\"ryd-feature-card__text\">\n                <h3>__MSG_changelog_feature_map_title__</h3>\n                <p>__MSG_changelog_feature_map_body__</p>\n              </div>\n              <figure class=\"ryd-feature-card__visual\">\n                <img\n                  src=\"../images/map1.jpg\"\n                  alt=\"Screenshot of the premium world heatmap with country level dislike insights.\"\n                  loading=\"lazy\"\n                />\n              </figure>\n              <figure class=\"ryd-feature-card__visual\">\n                <img\n                  src=\"../images/map2.jpg\"\n                  alt=\"Screenshot of the premium United States heatmap with state level dislike insights.\"\n                  loading=\"lazy\"\n                />\n              </figure>\n            </article>\n\n            <article class=\"ryd-feature-card\">\n              <div class=\"ryd-feature-card__text\">\n                <h3>__MSG_changelog_feature_quality_title__</h3>\n                <p>__MSG_changelog_feature_quality_body__</p>\n              </div>\n              <figure class=\"ryd-feature-card__visual\">\n                <img\n                  src=\"../images/list.jpg\"\n                  alt=\"Screenshot of the premium top countries leaderboard sorted by dislikes.\"\n                  loading=\"lazy\"\n                />\n              </figure>\n            </article>\n          </div>\n        </section>\n\n        <section class=\"ryd-section\">\n          <h2 class=\"ryd-section__heading\">__MSG_changelog_section_upgrade__</h2>\n          <p class=\"ryd-section__intro\">__MSG_changelog_upgrade_body__</p>\n          <ol class=\"ryd-list\">\n            <li>__MSG_changelog_upgrade_step1__</li>\n            <li>__MSG_changelog_upgrade_step2__</li>\n            <li>__MSG_changelog_upgrade_step3__</li>\n          </ol>\n        </section>\n\n        <section class=\"ryd-section\">\n          <h2 class=\"ryd-section__heading\">__MSG_changelog_section_release__</h2>\n          <ul class=\"ryd-list ryd-list--bulleted\">\n            <li>__MSG_changelog_release_item1__</li>\n            <li>__MSG_changelog_release_item2__</li>\n            <li>__MSG_changelog_release_item3__</li>\n          </ul>\n        </section>\n\n        <section class=\"ryd-section ryd-section--support\">\n          <h2 class=\"ryd-section__heading\">__MSG_changelog_support_heading__</h2>\n          <p class=\"ryd-section__intro\">__MSG_changelog_support_description__</p>\n          <div class=\"ryd-support\">\n            <button class=\"ryd-button ryd-button--outline\" id=\"ryd-changelog-contact\">\n              __MSG_changelog_support_contact__\n            </button>\n          </div>\n        </section>\n      </main>\n\n      <footer class=\"ryd-changelog__footer\">\n        <span>&copy; __MSG_extensionName__</span>\n        <span>__MSG_changelog_releaseDate__</span>\n      </footer>\n    </div>\n\n    <script type=\"module\" src=\"../../ryd.changelog.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "Extensions/combined/changelog/changelog.css",
    "content": "/* Variables */\n:root {\n  --primary: #ff4444;\n  --accent: #581111;\n\n  --background: #111;\n  --secondary: #272727;\n  --tertiary: #424242;\n  --lightGrey: #999;\n  --white: #fff;\n}\n\n/* Window Styling */\nhtml,\nbody {\n  background-color: var(--background);\n  color: var(--white);\n  min-width: 300px;\n  margin: 0;\n  font-family: \"Roboto\", Arial, Helvetica, sans-serif;\n  font-size: 16px;\n}\n\nimg {\n  border-radius: 1rem;\n  box-shadow: 0 0 2rem 0 black;\n  margin: 1rem -1rem;\n}\n\nbutton {\n  color: var(--white);\n  background: var(--secondary);\n  cursor: pointer;\n  padding: 5px 16px;\n  border: none;\n  border-radius: 4px;\n  font-weight: 500;\n  /* box-shadow: 0 2px 4px -1px rgb(0 0 0 / 20%), 0 4px 5px 0 rgb(0 0 0 / 14%),\n    0 1px 10px 0 rgb(0 0 0 / 12%); */\n  transition: 0.4s;\n  font-size: 16px;\n}\n\nbutton:hover {\n  background: #444;\n}\n.container {\n  display: flex;\n  flex-direction: row;\n  justify-content: center;\n  padding: 4rem 0;\n}\n"
  },
  {
    "path": "Extensions/combined/changelog/changelog.js",
    "content": "/*   Config   */\nconst config = {\n  advanced: false,\n  disableVoteSubmission: false,\n  coloredThumbs: false,\n  coloredBar: false,\n  colorTheme: \"classic\",\n  numberDisplayFormat: \"compactShort\",\n  showAdvancedMessage:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" enable-background=\"new 0 0 24 24\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"currentColor\"><rect fill=\"none\" height=\"24\" width=\"24\"/><path d=\"M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z\"/></svg>',\n  hideAdvancedMessage:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"currentColor\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\" opacity=\".87\"/><path d=\"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm4.3 14.3c-.39.39-1.02.39-1.41 0L12 13.41 9.11 16.3c-.39.39-1.02.39-1.41 0-.39-.39-.39-1.02 0-1.41L10.59 12 7.7 9.11c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0L12 10.59l2.89-2.89c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41L13.41 12l2.89 2.89c.38.38.38 1.02 0 1.41z\"/></svg>',\n\n  links: {\n    website: \"https://returnyoutubedislike.com\",\n    github: \"https://github.com/Anarios/return-youtube-dislike\",\n    discord: \"https://discord.gg/mYnESY4Md5\",\n    donate: \"https://returnyoutubedislike.com/donate\",\n    faq: \"https://returnyoutubedislike.com/faq\",\n    help: \"https://returnyoutubedislike.com/help\",\n  },\n};\n\n/*   Change language   */\nfunction localizeHtmlPage() {\n  //Localize by replacing __MSG_***__ meta tags\n  var objects = document.getElementsByTagName(\"html\");\n  for (var j = 0; j < objects.length; j++) {\n    var obj = objects[j];\n\n    var valStrH = obj.innerHTML.toString();\n    var valNewH = valStrH.replace(/__MSG_(\\w+)__/g, function (match, v1) {\n      return v1 ? chrome.i18n.getMessage(v1) : \"\";\n    });\n\n    if (valNewH != valStrH) {\n      obj.innerHTML = valNewH;\n    }\n  }\n}\n\nlocalizeHtmlPage();\n\n/*   Links   */\ncreateLink(config.links.website, \"link_website\");\ncreateLink(config.links.github, \"link_github\");\ncreateLink(config.links.discord, \"link_discord\");\ncreateLink(config.links.faq, \"link_faq\");\ncreateLink(config.links.donate, \"link_donate\");\ncreateLink(config.links.help, \"link_help\");\n\nfunction createLink(url, id) {\n  document.getElementById(id).addEventListener(\"click\", () => {\n    chrome.tabs.create({ url: url });\n  });\n}\n"
  },
  {
    "path": "Extensions/combined/content-style.css",
    "content": ":root {\n  /* --yt-spec-icon-disabled: #f44 !important;\n  --yt-spec-text-primary: #4f4 !important; */\n  /* --yt-spec-general-background-a: #000 !important;\n  --yt-spec-general-background-b: #000 !important;\n  --yt-spec-general-background-c: #000 !important;\n  --yt-spec-brand-background-solid: #000 !important;\n  --yt-spec-brand-background-primary: #000 !important;\n  --yt-spec-brand-background-secondary: #000 !important; */\n}\n\n/* html:not(.style-scope)[dark], :not(.style-scope)[dark] {\n  --yt-spec-general-background-a: #000 !important;\n  --yt-spec-general-background-b: #000 !important;\n  --yt-spec-general-background-c: #000 !important;\n  --yt-spec-brand-background-solid: #000 !important;\n  --yt-spec-brand-background-primary: #000 !important;\n  --yt-spec-brand-background-secondary: #000 !important;\n} */\n\n#ryd-bar-container {\n  background: var(--yt-spec-icon-disabled);\n  border-radius: 2px;\n}\n\n#ryd-bar {\n  background: var(--yt-spec-text-primary);\n  border-radius: 2px;\n  transition: all 0.15s ease-in-out;\n}\n\n.ryd-tooltip {\n  display: block;\n  height: 2px;\n}\n\n.ryd-tooltip-old-design {\n  position: relative;\n  top: 9px;\n}\n\n.ryd-tooltip-new-design {\n  position: absolute;\n  bottom: -10px;\n}\n\n.ryd-tooltip-bar-container {\n  width: 100%;\n  height: 2px;\n  position: absolute;\n  padding-top: 6px;\n  padding-bottom: 12px;\n  top: -6px;\n}\n\n/* required to make the ratio bar visible in the new design */\nytd-menu-renderer.ytd-watch-metadata {\n  overflow-y: visible !important;\n}\n\n#top-level-buttons-computed {\n  position: relative !important;\n}\n\n.ryd-premium-analytics {\n  background: var(--yt-spec-base-background, #202020);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 16px;\n  padding: 16px;\n  margin-bottom: 16px;\n  color: var(--yt-spec-text-primary, #ffffff);\n  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.22);\n  font-family: Roboto, Arial, sans-serif;\n  position: relative;\n  transition: box-shadow 0.2s ease, transform 0.2s ease;\n}\n\n.ryd-premium-analytics::before {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  pointer-events: none;\n  opacity: 0;\n  background: rgba(148, 163, 184, 0.22);\n  transition: opacity 0.2s ease;\n}\n\n.ryd-premium-analytics.is-loading::before {\n  opacity: 1;\n}\n\n.ryd-premium-analytics.is-expanded {\n  position: fixed;\n  top: 72px;\n  left: 50%;\n  transform: translateX(-50%);\n  width: min(960px, calc(100vw - 48px));\n  max-height: calc(100vh - 120px);\n  overflow-y: auto;\n  z-index: 2147483646;\n  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.45);\n}\n\n.ryd-premium-teaser {\n  background: var(--yt-spec-base-background, #202020);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 16px;\n  padding: 16px;\n  margin-bottom: 16px;\n  color: var(--yt-spec-text-primary, #ffffff);\n  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);\n  font-family: Roboto, Arial, sans-serif;\n  transition: box-shadow 0.2s ease, transform 0.2s ease, opacity 0.2s ease;\n  position: relative;\n}\n\n.ryd-premium-teaser.is-loading {\n  opacity: 0.65;\n}\n\n.ryd-premium-tier-notice {\n  background: var(--yt-spec-base-background, #202020);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 16px;\n  padding: 20px;\n  margin-bottom: 16px;\n  color: var(--yt-spec-text-primary, #ffffff);\n  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);\n  font-family: Roboto, Arial, sans-serif;\n  display: flex;\n  justify-content: space-between;\n  gap: 16px;\n  flex-wrap: wrap;\n}\n\n.ryd-premium-tier-notice__content {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  flex: 1 1 240px;\n}\n\n.ryd-premium-tier-notice__title {\n  margin: 0;\n  font-size: 18px;\n  font-weight: 600;\n}\n\n.ryd-premium-tier-notice__message {\n  margin: 0;\n  font-size: 13px;\n  color: var(--yt-spec-text-secondary, #bcccdc);\n  line-height: 1.5;\n}\n\n.ryd-premium-tier-notice__actions {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 10px;\n}\n\n.ryd-premium-tier-notice__cta {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  padding: 10px 18px;\n  border-radius: 999px;\n  background: #2563eb;\n  color: #ffffff;\n  font-weight: 600;\n  font-size: 13px;\n  text-decoration: none;\n  transition: background 0.2s ease, transform 0.2s ease;\n}\n\n.ryd-premium-tier-notice__cta:hover {\n  background: #1d4ed8;\n  transform: translateY(-1px);\n}\n\n.ryd-premium-teaser__header {\n  display: flex;\n  justify-content: space-between;\n  gap: 16px;\n  flex-wrap: wrap;\n  position: relative;\n  padding-right: 48px;\n}\n\n.ryd-premium-teaser__badge {\n  display: inline-flex;\n  align-items: center;\n  padding: 4px 10px;\n  border-radius: 999px;\n  background: rgba(59, 130, 246, 0.18);\n  color: #0f172a;\n  font-size: 11px;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  font-weight: 600;\n  margin-bottom: 4px;\n}\n\n.ryd-premium-teaser__title {\n  font-size: 18px;\n  margin: 4px 0;\n  font-weight: 600;\n}\n\n.ryd-premium-teaser__subtitle {\n  margin: 0;\n  font-size: 13px;\n  color: var(--yt-spec-text-secondary, #bcccdc);\n}\n\n.ryd-premium-teaser__actions {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 8px;\n  text-align: left;\n}\n\n.ryd-premium-teaser__close {\n  position: absolute;\n  top: 8px;\n  right: 8px;\n  width: 32px;\n  height: 32px;\n  border: none;\n  border-radius: 999px;\n  background: transparent;\n  color: var(--yt-spec-text-secondary, #94a3b8);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  transition: background 0.2s ease, color 0.2s ease;\n}\n\n.ryd-premium-teaser__close:hover,\n.ryd-premium-teaser__close:focus-visible {\n  background: rgba(148, 163, 184, 0.16);\n  color: var(--yt-spec-text-primary, #f8fafc);\n  outline: none;\n}\n\n.ryd-premium-teaser__close-icon {\n  font-size: 18px;\n  line-height: 1;\n  font-weight: 600;\n}\n\n.ryd-premium-teaser__cta {\n  border: none;\n  background: rgba(59, 130, 246, 0.95);\n  color: #ffffff;\n  text-decoration: none;\n  font-size: 13px;\n  font-weight: 600;\n  border-radius: 999px;\n  padding: 8px 18px;\n  cursor: pointer;\n  transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.ryd-premium-teaser__cta:hover {\n  background: rgba(59, 130, 246, 1);\n  box-shadow: 0 8px 18px rgba(59, 130, 246, 0.25);\n}\n\n.ryd-premium-teaser__secondary {\n  color: var(--yt-spec-text-secondary, #8ea4c8);\n  font-size: 12px;\n  text-decoration: none;\n}\n\n.ryd-premium-teaser__secondary:hover {\n  text-decoration: underline;\n}\n\n.ryd-premium-teaser__body {\n  margin-top: 16px;\n  display: flex;\n  flex-direction: column;\n  gap: 16px;\n}\n\n.ryd-premium-teaser__stats {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 16px;\n}\n\n.ryd-premium-teaser__stat {\n  flex: 1 1 140px;\n  background: rgba(148, 163, 184, 0.08);\n  border-radius: 12px;\n  padding: 12px;\n}\n\n.ryd-premium-teaser__stat-label {\n  display: block;\n  font-size: 12px;\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n  color: var(--yt-spec-text-secondary, #9ca3af);\n  margin-bottom: 6px;\n}\n\n.ryd-premium-teaser__stat-value {\n  font-size: 20px;\n  font-weight: 600;\n}\n\n.ryd-premium-teaser__status {\n  margin: 0;\n  font-size: 12px;\n  color: var(--yt-spec-text-secondary, #9ca3af);\n}\n\n.ryd-premium-teaser__breadcrumbs {\n  background: rgba(59, 130, 246, 0.08);\n  border-radius: 12px;\n  padding: 12px 16px;\n}\n\n.ryd-premium-teaser__breadcrumbs-title {\n  margin: 0 0 8px 0;\n  font-size: 13px;\n  font-weight: 600;\n}\n\n.ryd-premium-teaser__breadcrumbs-list {\n  margin: 0;\n  padding-left: 18px;\n  display: grid;\n  gap: 6px;\n  font-size: 13px;\n  color: var(--yt-spec-text-secondary, #93adc8);\n}\n\n.ryd-premium-teaser__breadcrumbs-list li {\n  padding-left: 4px;\n}\n\nhtml[dark] .ryd-premium-teaser__badge {\n  background: rgba(59, 130, 246, 0.28);\n  color: #dbeafe;\n}\n\nhtml[dark] .ryd-premium-teaser__stat {\n  background: rgba(148, 163, 184, 0.12);\n}\n\nhtml[dark] .ryd-premium-teaser__breadcrumbs {\n  background: rgba(59, 130, 246, 0.14);\n}\n\n.ryd-analytics__header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  flex-wrap: wrap;\n  margin-bottom: 12px;\n}\n\n.ryd-analytics__title {\n  font-size: 16px;\n  font-weight: 600;\n}\n\n.ryd-analytics__controls {\n  display: flex;\n  gap: 12px;\n  align-items: center;\n  flex: 1 1 auto;\n}\n\n.ryd-analytics__ranges,\n.ryd-analytics__modes,\n.ryd-analytics__window-toggle {\n  display: inline-flex;\n  gap: 6px;\n  background: rgba(59, 130, 246, 0.12);\n  border: 1px solid rgba(59, 130, 246, 0.2);\n  border-radius: 999px;\n  padding: 2px;\n}\n\n.ryd-analytics__ranges button,\n.ryd-analytics__modes button,\n.ryd-analytics__window-toggle button {\n  border: none;\n  background: transparent;\n  color: #1f2937;\n  font-size: 12px;\n  padding: 6px 12px;\n  border-radius: 999px;\n  cursor: pointer;\n  transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;\n}\n\n.ryd-analytics__ranges button.is-active,\n.ryd-analytics__modes button.is-active,\n.ryd-analytics__window-toggle button.is-active {\n  background: rgba(59, 130, 246, 0.28);\n  color: #0f172a;\n  box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.35);\n}\n\n.ryd-analytics__ranges button:hover,\n.ryd-analytics__modes button:hover,\n.ryd-analytics__window-toggle button:hover {\n  background: rgba(59, 130, 246, 0.18);\n  color: #0f172a;\n}\n\nhtml[dark] .ryd-analytics__ranges,\nhtml[dark] .ryd-analytics__modes,\nhtml[dark] .ryd-analytics__window-toggle {\n  background: rgba(255, 255, 255, 0.05);\n  border: none;\n}\n\nhtml[dark] .ryd-analytics__ranges button,\nhtml[dark] .ryd-analytics__modes button,\nhtml[dark] .ryd-analytics__window-toggle button {\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\nhtml[dark] .ryd-analytics__ranges button.is-active,\nhtml[dark] .ryd-analytics__modes button.is-active,\nhtml[dark] .ryd-analytics__window-toggle button.is-active {\n  background: rgba(255, 255, 255, 0.15);\n  color: var(--yt-spec-text-primary, #ffffff);\n  box-shadow: none;\n}\n\nhtml[dark] .ryd-analytics__ranges button:hover,\nhtml[dark] .ryd-analytics__modes button:hover,\nhtml[dark] .ryd-analytics__window-toggle button:hover {\n  background: rgba(255, 255, 255, 0.1);\n  color: var(--yt-spec-text-primary, #ffffff);\n}\n\n.ryd-analytics__window {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n.ryd-analytics__window-label {\n  font-size: 12px;\n  font-weight: 600;\n  color: #1f2937;\n}\n\n.ryd-analytics__window-hint {\n  font-size: 12px;\n  color: #4b5563;\n}\n\nhtml[dark] .ryd-analytics__window-label {\n  color: var(--yt-spec-text-primary, #ffffff);\n}\n\nhtml[dark] .ryd-analytics__window-hint {\n  color: var(--yt-spec-text-secondary, #a3a3a3);\n}\n\n.ryd-analytics__body {\n  display: flex;\n  flex-direction: column;\n  gap: 16px;\n}\n\n.ryd-analytics__section {\n  background: rgba(255, 255, 255, 0.02);\n  border-radius: 16px;\n  padding: 12px;\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  position: relative;\n  transition: box-shadow 0.2s ease, transform 0.2s ease;\n}\n\n.ryd-analytics__section-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n}\n\n.ryd-analytics__section-title {\n  margin: 0;\n  font-size: 14px;\n  font-weight: 600;\n}\n\n.ryd-analytics__section-expand {\n  border: 1px solid rgba(59, 130, 246, 0.3);\n  background: rgba(59, 130, 246, 0.14);\n  color: #1f2937;\n  font-size: 12px;\n  font-weight: 600;\n  padding: 4px 12px;\n  border-radius: 999px;\n  cursor: pointer;\n  transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.ryd-analytics__section-expand.is-active,\n.ryd-analytics__section-expand:hover {\n  background: rgba(59, 130, 246, 0.24);\n  color: #0f172a;\n  border-color: rgba(59, 130, 246, 0.45);\n  box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.4);\n}\n\nhtml[dark] .ryd-analytics__section-expand {\n  border: none;\n  background: rgba(255, 255, 255, 0.08);\n  color: var(--yt-spec-text-secondary, #cbd5f5);\n}\n\nhtml[dark] .ryd-analytics__section-expand.is-active,\nhtml[dark] .ryd-analytics__section-expand:hover {\n  background: rgba(99, 102, 241, 0.25);\n  color: #f8fafc;\n  box-shadow: none;\n  border: none;\n}\n\n.ryd-analytics__section.is-expanded {\n  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.45);\n  padding: 16px;\n}\n\n.ryd-analytics__section.is-expanded .ryd-analytics__chart,\n.ryd-analytics__section.is-expanded .ryd-analytics__map {\n  height: min(75vh, 560px);\n}\n\n.ryd-analytics__chart-meta {\n  display: flex;\n  justify-content: flex-end;\n  margin: -4px 0 4px;\n}\n\n.ryd-analytics__bucket-label {\n  font-size: 12px;\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\n.ryd-analytics__chart {\n  width: 100%;\n  height: 220px;\n}\n\n.ryd-analytics__map-block {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n}\n\n.ryd-analytics__map-controls {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n}\n\n.ryd-analytics__map-label {\n  font-size: 12px;\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\n.ryd-analytics__map-reset {\n  border: none;\n  background: rgba(59, 130, 246, 0.18);\n  color: #0f172a;\n  font-size: 12px;\n  padding: 6px 14px;\n  border-radius: 999px;\n  cursor: pointer;\n  transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;\n}\n\n.ryd-analytics__map-reset:hover {\n  background: rgba(59, 130, 246, 0.28);\n  color: #0b1120;\n  box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.35);\n}\n\nhtml[dark] .ryd-analytics__map-reset {\n  background: rgba(255, 255, 255, 0.08);\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\nhtml[dark] .ryd-analytics__map-reset:hover {\n  background: rgba(255, 255, 255, 0.18);\n  color: var(--yt-spec-text-primary, #ffffff);\n}\n\n.ryd-analytics__map {\n  width: 100%;\n  height: 260px;\n  border-radius: 12px;\n  overflow: hidden;\n}\n\n.ryd-analytics__lists {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n  gap: 12px;\n}\n\n.ryd-analytics__list h4 {\n  margin: 0 0 6px;\n  font-size: 13px;\n  font-weight: 600;\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\n.ryd-analytics__list-items {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.ryd-analytics__list-items li {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 12px;\n  color: var(--yt-spec-text-primary, #ffffff);\n  background: rgba(255, 255, 255, 0.03);\n  border-radius: 6px;\n  padding: 6px 8px;\n}\n\n.ryd-analytics__country {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  max-width: 140px;\n}\n\n.ryd-analytics__value {\n  font-variant-numeric: tabular-nums;\n  font-weight: 600;\n}\n\n.ryd-analytics__placeholder {\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n  font-style: italic;\n  background: transparent !important;\n  justify-content: flex-start !important;\n}\n\n.ryd-analytics__footer {\n  font-size: 12px;\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\n.ryd-analytics__totals {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  margin-bottom: 6px;\n}\n\n.ryd-analytics__totals-header {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: baseline;\n  gap: 8px;\n}\n\n.ryd-analytics__totals-values {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 8px;\n}\n\n.ryd-analytics__totals-label {\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  font-weight: 600;\n  font-size: 10px;\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\n.ryd-analytics__totals-value {\n  font-weight: 600;\n  font-variant-numeric: tabular-nums;\n  color: var(--yt-spec-text-primary, #f1f1f1);\n}\n\n.ryd-analytics__period-label {\n  font-size: 11px;\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n}\n\n.ryd-analytics__summary-meta {\n  color: var(--yt-spec-text-secondary, #bbbbbb);\n  line-height: 1.4;\n}\n"
  },
  {
    "path": "Extensions/combined/manifest-chrome.json",
    "content": "{\n  \"name\": \"__MSG_extensionName__\",\n  \"description\": \"__MSG_extensionDesc__\",\n  \"default_locale\": \"en\",\n  \"version\": \"__RYD_VERSION__\",\n  \"manifest_version\": 3,\n  \"background\": {\n    \"service_worker\": \"ryd.background.js\"\n  },\n  \"icons\": {\n    \"48\": \"icons/icon48.png\",\n    \"128\": \"icons/icon128.png\"\n  },\n  \"host_permissions\": [\n    \"*://*.youtube.com/*\"\n  ],\n  \"permissions\": [\n    \"storage\"\n  ],\n  \"optional_permissions\": [\n    \"identity\"\n  ],\n  \"action\": {\n    \"default_popup\": \"popup.html\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\n        \"*://youtube.com/*\",\n        \"*://www.youtube.com/*\",\n        \"*://m.youtube.com/*\"\n      ],\n      \"exclude_matches\": [\n        \"*://*.music.youtube.com/*\"\n      ],\n      \"js\": [\n        \"ryd.content-script.js\"\n      ],\n      \"css\": [\n        \"content-style.css\"\n      ]\n    }\n  ],\n  \"externally_connectable\": {\n    \"matches\": [\n      \"*://*.youtube.com/*\"\n    ]\n  },\n  \"web_accessible_resources\": [\n    {\n      \"resources\": [\n        \"ryd.script.js\",\n        \"menu-fixer.js\"\n      ],\n      \"matches\": [\n        \"*://*.youtube.com/*\"\n      ]\n    }\n  ],\n  \"options_ui\": {\n    \"page\": \"popup.html\",\n    \"open_in_tab\": false\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/manifest-firefox.json",
    "content": "{\n  \"name\": \"__MSG_extensionName__\",\n  \"description\": \"__MSG_extensionDesc__\",\n  \"default_locale\": \"en\",\n  \"version\": \"__RYD_VERSION__\",\n  \"manifest_version\": 2,\n  \"background\": {\n    \"scripts\": [\n      \"ryd.background.js\"\n    ]\n  },\n  \"icons\": {\n    \"48\": \"icons/icon48.png\",\n    \"128\": \"icons/icon128.png\"\n  },\n  \"permissions\": [\n    \"activeTab\",\n    \"*://*.youtube.com/*\",\n    \"storage\",\n    \"*://returnyoutubedislikeapi.com/*\",\n    \"https://localhost:7258/*\",\n    \"http://localhost:7258/*\",\n    \"identity\"\n  ],\n  \n  \"browser_action\": {\n    \"default_popup\": \"popup.html\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\n        \"*://*.youtube.com/*\"\n      ],\n      \"exclude_matches\": [\n        \"*://*.music.youtube.com/*\"\n      ],\n      \"run_at\": \"document_idle\",\n      \"css\": [\n        \"content-style.css\"\n      ],\n      \"js\": [\n        \"ryd.content-script.js\"\n      ]\n    }\n  ],\n  \"options_ui\": {\n    \"page\": \"popup.html\",\n    \"open_in_tab\": false\n  },\n  \"web_accessible_resources\": [\n    \"menu-fixer.js\"\n  ]\n\n    // uncomment this section for local storage to work in firefox locally,\n//    ,\"browser_specific_settings\": {\n//      \"gecko\": {\n//        \"id\": \"addon@example.com\",\n//        \"strict_min_version\": \"42.0\"\n//      }\n//    }\n}\n"
  },
  {
    "path": "Extensions/combined/manifest-safari.json",
    "content": "{\n  \"name\": \"__MSG_extensionName__\",\n  \"description\": \"__MSG_extensionDesc__\",\n  \"default_locale\": \"en\",\n  \"version\": \"__RYD_VERSION__\",\n  \"manifest_version\": 2,\n  \"background\": {\n    \"scripts\": [\"ryd.background.js\"],\n    \"persistent\": false\n  },\n  \"icons\": {\n    \"48\": \"icons/icon48.png\",\n    \"128\": \"icons/icon128.png\"\n  },\n  \"permissions\": [\n    \"activeTab\",\n    \"*://*.youtube.com/*\",\n    \"storage\",\n    \"*://returnyoutubedislikeapi.com/*\"\n  ],\n  \"browser_action\": {\n    \"default_popup\": \"popup.html\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"*://*.youtube.com/*\"],\n      \"exclude_matches\": [\"*://*.music.youtube.com/*\"],\n      \"run_at\": \"document_idle\",\n      \"css\": [\"content-style.css\"],\n      \"js\": [\"ryd.content-script.js\"]\n    }\n  ],\n  \"options_ui\": {\n    \"page\": \"popup.html\"\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/menu-fixer.js",
    "content": "function debounceAsync(func, wait, renderer) {\n  let timeout;\n  let lastCallTime = 0;\n\n  return async function (...args) {\n    const context = this;\n    const now = Date.now();\n\n    if (!lastCallTime || now - lastCallTime > wait) {\n      // If the last call was long enough ago or this is the first call, execute immediately\n      lastCallTime = now;\n      return await func.apply(context, args);\n    } else {\n      // Hide all optional menu items - prevents endless loop\n      if (renderer?.polymerController?.flexAsTopLevelButtons) {\n        renderer.polymerController.flexAsTopLevelButtons = [];\n      }\n\n      // Otherwise, delay the call\n      return new Promise((resolve) => {\n        clearTimeout(timeout);\n        timeout = setTimeout(async () => {\n          lastCallTime = Date.now();\n          resolve(await func.apply(context, args));\n        }, wait);\n      });\n    }\n  };\n}\n\nconst fixYtdMenuRenderer = (ytdMenuRenderer) => {\n  if (!ytdMenuRenderer?.polymerController?.maybeUpdateFlexibleMenuImpl) {\n    return;\n  }\n  const originalMaybeUpdateFlexibleMenuImpl = ytdMenuRenderer.polymerController.maybeUpdateFlexibleMenuImpl.bind(\n    ytdMenuRenderer.polymerController,\n  );\n\n  ytdMenuRenderer.polymerController.maybeUpdateFlexibleMenuImpl = debounceAsync(\n    originalMaybeUpdateFlexibleMenuImpl,\n    100,\n    ytdMenuRenderer,\n  );\n};\n\nconst fixedYtdMenuRenderers = [];\nconst observer = new MutationObserver(() => {\n  const ytdMenuRenderers = [...document.querySelectorAll(\"ytd-menu-renderer\")].filter(\n    (el) => !fixedYtdMenuRenderers.includes(el),\n  );\n  if (!ytdMenuRenderers.length) return;\n\n  for (const el of ytdMenuRenderers) {\n    fixYtdMenuRenderer(el);\n    fixedYtdMenuRenderers.push(el);\n  }\n});\nobserver.observe(document.documentElement, {\n  subtree: true,\n  childList: true,\n});\n"
  },
  {
    "path": "Extensions/combined/popup.css",
    "content": "/* Variables */\n:root {\n  color-scheme: dark;\n\n  --primary: #cc2929;\n  --primary: #f39090;\n  --secondary: #502e2e;\n  --tertiary: #221818;\n  --background: #352929;\n\n  --accent: #581111;\n  --lightGrey: #999;\n}\n\n/***   MATERIAL 3   ***/\n.m3-primary {\n  background: var(--primary) !important;\n  color: var(--background) !important;\n}\n.m3-btn {\n  background: var(--tertiary);\n  color: var(--primary);\n  transition: 0.4s;\n  border-radius: 4px;\n  font-weight: 500;\n  border-radius: 100em;\n  box-shadow: 0 5px 5px rgba(0,0,0,0.1);\n  margin: 0.25em;\n}\n.m3-btn:hover {\n  background: var(--secondary) !important;\n  color: var(--primary) !important;\n}\n\nselect {\n  border-radius: 1em;\n  background-color: var(--secondary);\n  color: var(--primary);\n  border: none;\n  outline: none;\n  padding: 3px 2px;\n}\n/***   END MATERIAL 3   ***/\n\n/* Window Styling */\nhtml,\nbody {\n  background-color: var(--background);\n  color: var(--primary);\n  min-width: 310px;\n  min-height: 350px;\n  padding: 0.5em;\n  font-family: \"Roboto\", Arial, Helvetica, sans-serif;\n  font-size: 14px;\n}\n\nh1 {\n  font-size: 26px;\n}\n\n#ext-version {\n  padding: 0.25rem 0.5rem;\n}\n\nbutton {\n  cursor: pointer;\n  padding: 5px 16px;\n  border: none;\n}\n\n#ext-update {\n  cursor: pointer;\n  color: var(--primary);\n  text-decoration: none;\n  background: var(--primary);\n  border-radius: 0.25rem;\n}\n#ext-update:hover {\n  text-decoration: underline;\n}\n\n#ext {\n  padding: 0.25rem 0;\n  z-index: 69;\n  position: fixed;\n  background: var(--secondary);\n  margin: 0;\n  bottom: 1.15rem;\n  right: 1.15rem;\n  border-radius: 0.25rem;\n}\n\n.switch::before,\n.label-with-hover-tip::before {\n  content: attr(data-hover);\n  visibility: hidden;\n  opacity: 0;\n  transition:\n    visibility 0.1s linear,\n    opacity 0.1s linear;\n  width: 250px;\n  background-color: var(--secondary);\n  border-radius: 0.5rem;\n  padding: 0.5rem;\n\n  position: absolute;\n  z-index: 1;\n  left: 0;\n  top: 160%;\n}\n\n.switch:hover::before,\n.label-with-hover-tip:hover::before {\n  visibility: visible;\n  opacity: 1;\n}\n\n.fade-in {\n  opacity: 1;\n  animation-name: fadeInOpacity;\n  animation-iteration-count: 1;\n  animation-timing-function: ease-in;\n  animation-duration: 2s;\n}\n\n@keyframes fadeInOpacity {\n  0% {\n    opacity: 0;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n\n#advancedToggle {\n  position: fixed;\n  background: none;\n  box-shadow: none;\n  color: var(--primary);\n  top: 26px;\n  right: 26px;\n  padding: 2px;\n  z-index: 69;\n  height: 2rem;\n  width: 2rem;\n  transition-duration: .25s;\n}\n\n#advancedToggle:hover {\n  transform: rotate(-90deg);\n}\n\n#advancedToggle:active {\n  transform: scale(1.5);\n}\n\n#advancedSettings {\n  opacity: 0;\n  pointer-events: none;\n  transition-duration: 0.15s;\n  transition-timing-function: ease-in-out;\n  transform: scale(1.1);\n  position: fixed;\n  background: var(--background);\n  top: 10px;\n  right: 14px;\n  width: calc(100% - 65px);\n  height: calc(100% - 58px);\n  padding: 1rem;\n  overflow-y: auto;\n  overflow-x: hidden;\n  border: none;\n}\n\n::-webkit-scrollbar {\n  width: 1rem;\n}\n\n::-webkit-scrollbar-track {\n  background: #111; /* color of the tracking area */\n}\n\n::-webkit-scrollbar-thumb {\n  background-color: var(--primary); /* color of the scroll thumb */\n  border-radius: 1rem 0 0 1rem; /* roundness of the scroll thumb */\n  border-bottom: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-left: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-top: 0.25rem solid #111; /* creates padding around scroll thumb */\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background-color: #f22; /* color of the scroll thumb */\n  border-radius: 1rem 0 0 1rem; /* roundness of the scroll thumb */\n  border-bottom: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-left: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-top: 0.25rem solid #111; /* creates padding around scroll thumb */\n}\n\n#advancedLegend {\n  color: var(--secondary) !important;\n  /* margin: auto; */ /* Center the label */\n  /* padding: .25rem .5rem; */\n  /* border-radius: .25rem; */\n  /* border: .25rem solid var(--secondary); */\n}\n\n/*   Switches   */\n.switch {\n  position: relative;\n  display: inline-block;\n  width: 30px;\n  height: 17px;\n  margin-bottom: 1rem;\n}\n\n.label-with-hover-tip {\n  position: relative;\n  display: inline-block;\n  width: 80px;\n  height: 17px;\n  margin-bottom: 1rem;\n}\n\n.switch:last-of-type {\n  margin-bottom: 0;\n}\n\n.switch input {\n  display: none;\n}\n\n.slider {\n  position: absolute;\n  cursor: pointer;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: var(--secondary);\n  transition: 0.4s;\n  border-radius: 34px;\n}\n\n.slider::before {\n  position: absolute;\n  content: \"\";\n  height: 13px;\n  width: 13px;\n  left: 2px;\n  bottom: 2px;\n  background: var(--primary);\n  transition: 0.4s;\n  border-radius: 50%;\n}\n\ninput:checked + .slider {\n  background: var(--accent);\n}\n\ninput:checked + .slider::before {\n  transform: translateX(13px);\n  background: var(--primary);\n}\n\n.switchLabel {\n  margin-left: 0.5rem;\n  width: 250px !important;\n  transform: translateX(35px);\n  display: inline-block;\n}\n\n#server-status {\n  height: 72px;\n  width: 90px;\n  /* filter: invert(21%) sepia(100%) saturate(3618%) hue-rotate(102deg)\n    brightness(96%) contrast(108%); */\n}\n\n.container {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n"
  },
  {
    "path": "Extensions/combined/popup.html",
    "content": "<!doctype html>\n<html>\n\n  <!---   HEADER   -->\n  <head>\n    <meta content=\"text/html; charset=utf-8\" />\n    <title title=\"__MSG_extensionName__\">__MSG_extensionName__</title>\n    <link rel=\"stylesheet\" href=\"popup.css\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <!--   END HEADER -->\n  <body>\n    <center>\n      <img src=\"icons/logo.svg\" />\n      <h1 style=\"margin-bottom: 0.75rem\" title=\"__MSG_extensionName__\">\n        __MSG_extensionName__\n      </h1>\n      <p style=\"color: var(--lightGrey)\" title=\"__MSG_textDeveloper__\">\n        __MSG_textDeveloper__\n      </p>\n\n      <button class=\"m3-btn\" id=\"link_website\" title=\"__MSG_linkWebsite__\">\n        __MSG_linkWebsite__\n      </button>\n      <button\n        class=\"m3-btn m3-primary\"\n        style=\"margin-top: 0.3em;\"\n        id=\"link_donate\"\n        title=\"__MSG_linkDonate__\"\n      >\n        __MSG_linkDonate__\n      </button>\n      <button class=\"m3-btn\" id=\"link_discord\" title=\"__MSG_linkDiscord__\">__MSG_linkDiscord__</button>\n      <br />\n      \n      <!-- Patreon Login Section -->\n      <div id=\"patreon-section\" style=\"margin-top: 1rem; padding: 0.5rem; border-top: 1px solid var(--lightGrey);\">\n        <div id=\"patreon-logged-out\" style=\"display: none;\">\n          <p style=\"color: var(--lightGrey); font-size: 0.9rem; margin-bottom: 0.5rem;\" title=\"__MSG_patreonLoggedOutMessage__\">__MSG_patreonLoggedOutMessage__</p>\n          <button class=\"m3-btn m3-primary\" id=\"patreon-login-btn\" title=\"__MSG_patreonLogin__\">\n            <svg style=\"width: 16px; height: 16px; margin-right: 6px; vertical-align: middle;\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n              <path d=\"M14.82 2.41c3.96 0 7.18 3.24 7.18 7.21c0 3.96-3.22 7.18-7.18 7.18c-3.97 0-7.21-3.22-7.21-7.18c0-3.97 3.24-7.21 7.21-7.21M2 21.6h3.5V2.41H2V21.6z\"/>\n            </svg>\n            __MSG_patreonLogin__\n          </button>\n        </div>\n        <div id=\"patreon-logged-in\" style=\"display: none;\">\n          <div style=\"display: flex; align-items: center; margin-bottom: 0.5rem;\">\n            <img id=\"patreon-user-avatar\" src=\"\" alt=\"\" style=\"width: 32px; height: 32px; border-radius: 50%; margin-right: 8px; display: none;\">\n            <div>\n              <p id=\"patreon-user-name\" style=\"font-weight: 500; margin: 0;\"></p>\n              <p id=\"patreon-tier\" style=\"color: var(--lightGrey); font-size: 0.8rem; margin: 0;\"></p>\n            </div>\n          </div>\n          <button class=\"m3-btn\" id=\"patreon-logout-btn\" style=\"font-size: 0.9rem;\" title=\"__MSG_patreonLogout__\">__MSG_patreonLogout__</button>\n        </div>\n      </div>\n      \n      <button class=\"m3-btn\" style=\"margin-top: 0.3rem\" id=\"link_faq\" title=\"__MSG_linkFAQ__\">\n        __MSG_linkFAQ__\n      </button>\n      <button class=\"m3-btn\" id=\"link_github\" title=\"__MSG_linkGithub__\">__MSG_linkGithub__</button>\n      <button class=\"m3-btn\" style=\"margin-top: 0.3em\" id=\"link_help\" title=\"__MSG_linkHelp__\">\n        __MSG_linkHelp__\n      </button>\n      <br />\n      <button\n        class=\"m3-btn\"\n        style=\"margin-top: 0.3em\"\n        id=\"link_changelog\"\n        title=\"__MSG_linkChangelog__\"\n      >\n        __MSG_linkChangelog__\n      </button>\n\n      <br />\n      <br />\n      <p style=\"display: none\">__MSG_apiStatusLabel__ <b id=\"status\"></b></p>\n      <img\n        id=\"server-status\"\n        style=\"display: none; width: 0.75rem; height: 0.75rem\"\n        src=\"./icons/server.svg\"\n        alt=\"\"\n      />\n\n      <br />\n      <br />\n    </center>\n\n    <!-- top-right -->\n    <button id=\"advancedToggle\">\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        enable-background=\"new 0 0 24 24\"\n        height=\"24px\"\n        viewBox=\"0 0 24 24\"\n        width=\"24px\"\n        fill=\"currentColor\"\n      >\n        <rect fill=\"none\" height=\"24\" width=\"24\" />\n        <path\n          d=\"M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z\"\n        />\n      </svg>\n    </button>\n    <!-- bottom-right -->\n    <div id=\"ext\">\n      <a\n        href=\"https://returnyoutubedislike.com/install\"\n        target=\"_blank\"\n        id=\"ext-update\"\n      ></a>\n      <span id=\"ext-version\"></span>\n    </div>\n\n    <!-- dialog box -->\n    <fieldset id=\"advancedSettings\">\n      <label class=\"switch\" data-hover=\"__MSG_disableLoggingHover__\">\n        <input type=\"checkbox\" id=\"disable_logging\"/>\n        <span class=\"slider\"/>\n        <span class=\"switchLabel\">__MSG_disableLoggingLabel__</span>\n      </label>\n      <br />\n      <label class=\"switch\" data-hover=\"__MSG_textSettingsHover__\">\n        <input type=\"checkbox\" id=\"disable_vote_submission\" />\n        <span class=\"slider\"></span>\n        <span class=\"switchLabel\" title=\"__MSG_textSettings__\">\n          __MSG_textSettings__\n        </span>\n      </label>\n      <br />\n      <label class=\"switch\" data-hover=\"__MSG_reformatLikesHover__\">\n        <input type=\"checkbox\" id=\"number_reformat_likes\" />\n        <span class=\"slider\"></span>\n        <span class=\"switchLabel\">__MSG_reformatLikes__</span>\n      </label>\n      <br />\n      <label class=\"switch\" data-hover=\"__MSG_hidePremiumTeaserHover__\">\n        <input type=\"checkbox\" id=\"hide_premium_teaser\" />\n        <span class=\"slider\"></span>\n        <span class=\"switchLabel\">__MSG_hidePremiumTeaser__</span>\n      </label>\n      <br />\n      <div class=\"custom-select\">\n        <label for=\"number_format\">__MSG_numberFormat__</label>\n        <select name=\"number_format\" id=\"number_format\">\n          <option value=\"compactShort\" id=\"number_format_compactShort\"></option>\n          <option value=\"compactLong\" id=\"number_format_compactLong\"></option>\n          <option value=\"standard\" id=\"number_format_standard\"></option>\n        </select>\n      </div>\n      <br />\n      <div class=\"custom-select\">\n        <label class=\"switch\" data-hover=\"__MSG_colorizeRatioHover__\">\n          <input type=\"checkbox\" id=\"colored_bar\" />\n          <span class=\"slider\"></span>\n          <span class=\"switchLabel\">__MSG_colorizeRatio__</span>\n        </label>\n      </div>\n      <br />\n      <div class=\"custom-select\">\n        <label class=\"switch\" data-hover=\"__MSG_colorizeThumbsHover__\">\n          <input type=\"checkbox\" id=\"colored_thumbs\" />\n          <span class=\"slider\"></span>\n          <span class=\"switchLabel\">__MSG_colorizeThumbs__</span>\n        </label>\n      </div>\n      <br />\n      <div class=\"custom-select\">\n        <label for=\"color_theme\">__MSG_colorTheme__</label>\n        <select name=\"color_theme\" id=\"color_theme\">\n          <option value=\"classic\" id=\"color_theme_classic\">\n            __MSG_textColorTheme1__\n          </option>\n          <option value=\"accessible\" id=\"color_theme_accessible\">\n            __MSG_textColorTheme2__\n          </option>\n          <option value=\"neon\" id=\"color_theme_neon\">\n            __MSG_textColorTheme3__\n          </option>\n        </select>\n        <span\n          id=\"color_theme_example_like\"\n          style=\"\n            display: inline-block;\n            vertical-align: text-top;\n            width: 1em;\n            height: 1em;\n          \"\n          >&nbsp;</span\n        >\n        <span\n          id=\"color_theme_example_dislike\"\n          style=\"\n            display: inline-block;\n            vertical-align: text-top;\n            width: 1em;\n            height: 1em;\n          \"\n          >&nbsp;</span\n        >\n      </div>\n      <br />\n      <label\n        class=\"switch\"\n        data-hover=\"__MSG_showTooltipPercentageHover__\"\n      >\n        <input type=\"checkbox\" id=\"show_tooltip_percentage\" />\n        <span class=\"slider\"></span>\n        <span class=\"switchLabel\">__MSG_showTooltipPercentage__</span>\n      </label>\n      <div class=\"custom-select\">\n        <label\n          for=\"tooltip_percentage_mode\"\n          data-hover=\"__MSG_tooltipPercentageModeHover__\"\n          >__MSG_tooltipPercentageMode__</label\n        >\n        <select name=\"tooltip_percentage_mode\" id=\"tooltip_percentage_mode\">\n          <option value=\"dash_like\" id=\"tooltip_percentage_mode_dash_like\">\n            190&nbsp;/&nbsp;10&nbsp;&nbsp;-&nbsp;&nbsp;95%\n          </option>\n          <option\n            value=\"dash_dislike\"\n            id=\"tooltip_percentage_mode_dash_dislike\"\n          >\n            190&nbsp;/&nbsp;10&nbsp;&nbsp;-&nbsp;&nbsp;5%\n          </option>\n          <option value=\"both\" id=\"tooltip_percentage_mode_both\">\n            95%&nbsp;/&nbsp;5%\n          </option>\n          <option value=\"only_like\" id=\"tooltip_percentage_mode_only_like\">\n            95%\n          </option>\n          <option\n            value=\"only_dislike\"\n            id=\"tooltip_percentage_mode_only_dislike\"\n          >\n            5%\n          </option>\n        </select>\n      </div>\n    </fieldset>\n  </body>\n  <script src=\"popup.js\"></script>\n</html>\n"
  },
  {
    "path": "Extensions/combined/popup.js",
    "content": "import { config as appConfig, getApiUrl, getApiEndpoint } from \"./src/config.js\";\n\n/*   Config   */\nconst config = {\n  advanced: false,\n  disableVoteSubmission: false,\n  disableLogging: true,\n  coloredThumbs: false,\n  coloredBar: false,\n  colorTheme: \"classic\",\n  numberDisplayFormat: \"compactShort\",\n  showTooltipPercentage: false,\n  tooltipPercentageMode: \"dash_like\",\n  numberDisplayReformatLikes: false,\n  hidePremiumTeaser: false,\n  showAdvancedMessage:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" enable-background=\"new 0 0 24 24\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"currentColor\"><rect fill=\"none\" height=\"24\" width=\"24\"/><path d=\"M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z\"/></svg>',\n  hideAdvancedMessage:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"currentColor\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\" opacity=\".87\"/><path d=\"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm4.3 14.3c-.39.39-1.02.39-1.41 0L12 13.41 9.11 16.3c-.39.39-1.02.39-1.41 0-.39-.39-.39-1.02 0-1.41L10.59 12 7.7 9.11c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0L12 10.59l2.89-2.89c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41L13.41 12l2.89 2.89c.38.38.38 1.02 0 1.41z\"/></svg>',\n  links: appConfig.links,\n};\n\n/*   Change language   */\nfunction localizeHtmlPage() {\n  //Localize by replacing __MSG_***__ meta tags\n  var objects = document.getElementsByTagName(\"html\");\n  for (var j = 0; j < objects.length; j++) {\n    var obj = objects[j];\n\n    var valStrH = obj.innerHTML.toString();\n    var valNewH = valStrH.replace(/__MSG_(\\w+)__/g, function (match, v1) {\n      return v1 ? chrome.i18n.getMessage(v1) : \"\";\n    });\n\n    if (valNewH != valStrH) {\n      obj.innerHTML = valNewH;\n    }\n  }\n}\n\nlocalizeHtmlPage();\n\n/*   Links   */\ncreateLink(config.links.website, \"link_website\");\ncreateLink(config.links.github, \"link_github\");\ncreateLink(config.links.discord, \"link_discord\");\ncreateLink(config.links.faq, \"link_faq\");\ncreateLink(config.links.donate, \"link_donate\");\ncreateLink(config.links.help, \"link_help\");\ncreateLink(config.links.changelog, \"link_changelog\");\n\nfunction createLink(url, id) {\n  document.getElementById(id).addEventListener(\"click\", () => {\n    chrome.tabs.create({ url: url });\n  });\n}\n\ndocument.getElementById(\"disable_vote_submission\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ disableVoteSubmission: ev.target.checked });\n});\n\ndocument.getElementById(\"disable_logging\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ disableLogging: ev.target.checked });\n});\n\ndocument.getElementById(\"colored_thumbs\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ coloredThumbs: ev.target.checked });\n});\n\ndocument.getElementById(\"colored_bar\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ coloredBar: ev.target.checked });\n});\n\ndocument.getElementById(\"color_theme\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ colorTheme: ev.target.value });\n});\n\ndocument.getElementById(\"number_format\").addEventListener(\"change\", (ev) => {\n  chrome.storage.sync.set({ numberDisplayFormat: ev.target.value });\n});\n\ndocument.getElementById(\"show_tooltip_percentage\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ showTooltipPercentage: ev.target.checked });\n});\n\ndocument.getElementById(\"tooltip_percentage_mode\").addEventListener(\"change\", (ev) => {\n  chrome.storage.sync.set({ tooltipPercentageMode: ev.target.value });\n});\n\ndocument.getElementById(\"number_reformat_likes\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ numberDisplayReformatLikes: ev.target.checked });\n});\n\ndocument.getElementById(\"hide_premium_teaser\").addEventListener(\"click\", (ev) => {\n  chrome.storage.sync.set({ hidePremiumTeaser: ev.target.checked });\n});\n\nfunction initPatreonAuth() {\n  const loggedOutView = document.getElementById(\"patreon-logged-out\");\n  const loggedInView = document.getElementById(\"patreon-logged-in\");\n  const loginBtn = document.getElementById(\"patreon-login-btn\");\n  const logoutBtn = document.getElementById(\"patreon-logout-btn\");\n  const userAvatar = document.getElementById(\"patreon-user-avatar\");\n  const userName = document.getElementById(\"patreon-user-name\");\n  const userTier = document.getElementById(\"patreon-tier\");\n\n  chrome.storage.sync.get([\"patreonUser\", \"patreonSessionToken\"], (data) => {\n    const cachedUser = data.patreonUser;\n    const sessionToken = data.patreonSessionToken;\n\n    if (sessionToken && cachedUser) {\n      // Show cached state immediately to avoid flicker on popup reopen.\n      showLoggedInView(cachedUser);\n\n      verifySession(sessionToken).then((result) => {\n        if (result.status === \"valid\") {\n          if (result.membershipTier && result.membershipTier !== cachedUser.membershipTier) {\n            const updatedUser = { ...cachedUser, membershipTier: result.membershipTier, hasActiveMembership: true };\n            showLoggedInView(updatedUser);\n            chrome.storage.sync.set({ patreonUser: updatedUser });\n          } else if (cachedUser.hasActiveMembership !== true) {\n            const updatedUser = { ...cachedUser, hasActiveMembership: true };\n            showLoggedInView(updatedUser);\n            chrome.storage.sync.set({ patreonUser: updatedUser });\n          }\n        } else if (result.status === \"inactive\") {\n          const updatedUser = {\n            ...cachedUser,\n            hasActiveMembership: false,\n            membershipTier: result.membershipTier || cachedUser.membershipTier || \"none\",\n          };\n          showLoggedInView(updatedUser);\n          chrome.storage.sync.set({ patreonUser: updatedUser });\n        } else if (result.status === \"error\") {\n          console.warn(\"Patreon session verification skipped:\", result.reason || \"network_error\");\n        } else {\n          showLoggedOutView();\n          chrome.storage.sync.remove([\"patreonUser\", \"patreonSessionToken\"]);\n        }\n      });\n    } else {\n      showLoggedOutView();\n    }\n  });\n\n  function showLoggedOutView() {\n    loggedOutView.style.display = \"block\";\n    loggedInView.style.display = \"none\";\n  }\n\n  function showLoggedInView(user) {\n    loggedOutView.style.display = \"none\";\n    loggedInView.style.display = \"block\";\n\n    userName.textContent = user.fullName || user.email || chrome.i18n.getMessage(\"patreonUserFallback\");\n\n    if (user.imageUrl) {\n      userAvatar.src = user.imageUrl;\n      userAvatar.style.display = \"block\";\n    } else {\n      userAvatar.src = \"\";\n      userAvatar.style.display = \"none\";\n    }\n\n    const tierLabels = {\n      premium: chrome.i18n.getMessage(\"patreonTierPremium\"),\n      supporter: chrome.i18n.getMessage(\"patreonTierSupporter\"),\n      basic: chrome.i18n.getMessage(\"patreonTierBasic\"),\n      none: chrome.i18n.getMessage(\"patreonTierNone\"),\n    };\n\n    userTier.textContent = tierLabels[user.membershipTier] || chrome.i18n.getMessage(\"patreonTierChecking\");\n\n    if (user.hasActiveMembership) {\n      userTier.style.color = \"#f96854\";\n    } else {\n      userTier.style.color = \"var(--lightGrey)\";\n    }\n  }\n\n  async function verifySession(token) {\n    try {\n      const response = await fetch(getApiEndpoint(\"/api/auth/verify\"), {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify({ sessionToken: token }),\n      });\n\n      const data = await response.json().catch(() => null);\n      if (!data) {\n        return { status: \"error\", reason: \"invalid_response\" };\n      }\n\n      const failureReason = typeof data.failureReason === \"string\" ? data.failureReason : null;\n      const membershipTier = typeof data.membershipTier === \"string\" ? data.membershipTier : null;\n\n      if (data.valid === true) {\n        return { status: \"valid\", membershipTier };\n      }\n\n      if (failureReason === \"membershipinactive\") {\n        return { status: \"inactive\", membershipTier };\n      }\n\n      if (failureReason === \"expired\" || failureReason === \"legacyformat\") {\n        return { status: \"expired\", membershipTier };\n      }\n\n      if (failureReason === \"invalid\") {\n        return { status: \"invalid\", membershipTier, failureReason };\n      }\n\n      if (!failureReason) {\n        return { status: \"error\", reason: \"unknown_failure\" };\n      }\n\n      return { status: \"error\", reason: failureReason };\n    } catch (error) {\n      console.error(\"Session verification failed:\", error);\n      return { status: \"error\", reason: \"network_error\" };\n    }\n  }\n\n  // Wrapper for cross-browser identity API\n  function getIdentityApi() {\n    if (typeof browser !== \"undefined\" && browser.identity) return browser.identity; // prefer Firefox promise API\n    if (typeof chrome !== \"undefined\" && chrome.identity) return chrome.identity;\n    return null;\n  }\n\n  function launchWebAuthFlow(url) {\n    try {\n      if (\n        typeof browser !== \"undefined\" &&\n        browser.identity &&\n        typeof browser.identity.launchWebAuthFlow === \"function\"\n      ) {\n        // Promise-based API (Firefox)\n        return browser.identity.launchWebAuthFlow({ url, interactive: true });\n      }\n    } catch (_) {}\n\n    const chromeId = (typeof chrome !== \"undefined\" && chrome.identity) || null;\n    if (!chromeId || typeof chromeId.launchWebAuthFlow !== \"function\") {\n      return Promise.reject(new Error(\"identity API not available\"));\n    }\n    // Callback-based API (Chrome)\n    return new Promise((resolve, reject) => {\n      chromeId.launchWebAuthFlow({ url, interactive: true }, (responseUrl) => {\n        const err = chrome.runtime && chrome.runtime.lastError;\n        if (err) reject(err);\n        else resolve(responseUrl);\n      });\n    });\n  }\n\n  function extractOAuthParams(responseUrl) {\n    try {\n      const u = new URL(responseUrl);\n      let code = u.searchParams.get(\"code\");\n      let state = u.searchParams.get(\"state\");\n      if (!code && u.hash) {\n        const hashParams = new URLSearchParams(u.hash.startsWith(\"#\") ? u.hash.substring(1) : u.hash);\n        code = hashParams.get(\"code\");\n        state = state || hashParams.get(\"state\");\n      }\n      return { code, state };\n    } catch (_) {\n      return { code: null, state: null };\n    }\n  }\n\n  // Request identity permission immediately within the user click (no awaits prior)\n  function ensureIdentityPermission(onResult) {\n    try {\n      const mf = chrome && chrome.runtime && chrome.runtime.getManifest ? chrome.runtime.getManifest() : null;\n      // If manifest cannot request identity dynamically (e.g., Firefox), proceed if API is available\n      if (mf && (!Array.isArray(mf.optional_permissions) || !mf.optional_permissions.includes(\"identity\"))) {\n        const id = getIdentityApi();\n        onResult(Boolean(id && typeof id.getRedirectURL === \"function\"));\n        return;\n      }\n\n      const perms =\n        (typeof chrome !== \"undefined\" && chrome.permissions) ||\n        (typeof browser !== \"undefined\" && browser.permissions);\n      if (!perms || !perms.contains) return onResult(false);\n      const afterContains = (has) => {\n        if (typeof chrome !== \"undefined\" && chrome.runtime && chrome.runtime.lastError) has = false;\n        if (has) return onResult(true);\n        if (!perms.request) return onResult(false);\n        const reqResult = perms.request({ permissions: [\"identity\"] }, (granted) => {\n          if (typeof chrome !== \"undefined\" && chrome.runtime && chrome.runtime.lastError) return onResult(false);\n          onResult(Boolean(granted));\n        });\n        // Firefox may return a promise\n        if (reqResult && typeof reqResult.then === \"function\") {\n          reqResult.then((granted) => onResult(Boolean(granted))).catch(() => onResult(false));\n        }\n      };\n      const result = perms.contains({ permissions: [\"identity\"] }, afterContains);\n      if (result && typeof result.then === \"function\") {\n        result.then(afterContains).catch(() => onResult(false));\n      }\n    } catch (_) {\n      onResult(false);\n    }\n  }\n\n  loginBtn.addEventListener(\"click\", () => {\n    // Request permission immediately within this user gesture\n    ensureIdentityPermission(async (granted) => {\n      const identityNow = getIdentityApi();\n      if (!granted || !identityNow || typeof identityNow.getRedirectURL !== \"function\") {\n        alert(chrome.i18n.getMessage(\"patreonPermissionRequired\"));\n        return;\n      }\n      // Delegate OAuth to background so it persists if the popup closes\n      chrome.runtime.sendMessage({ message: \"patreon_oauth_login\" }, (resp) => {\n        if (chrome.runtime && chrome.runtime.lastError) {\n          console.error(\"Login failed:\", chrome.runtime.lastError.message);\n          alert(chrome.i18n.getMessage(\"patreonLoginStartFailed\"));\n          return;\n        }\n        if (resp && resp.success) {\n          const user = resp.user;\n          showLoggedInView(user);\n        } else {\n          console.error(\"Login failed:\", resp && resp.error);\n          alert(chrome.i18n.getMessage(\"patreonLoginCompleteFailed\"));\n        }\n      });\n    });\n  });\n\n  logoutBtn.addEventListener(\"click\", () => {\n    chrome.storage.sync.remove([\"patreonUser\", \"patreonSessionToken\"], () => {\n      showLoggedOutView();\n      chrome.runtime.sendMessage({ message: \"patreon_logout\" });\n    });\n  });\n}\n\ninitPatreonAuth();\n\n/*   Advanced Toggle   */\nconst advancedToggle = document.getElementById(\"advancedToggle\");\nadvancedToggle.addEventListener(\"click\", () => {\n  const adv = document.getElementById(\"advancedSettings\");\n  if (config.advanced) {\n    adv.style.transform = \"scale(1.1)\";\n    adv.style.pointerEvents = \"none\";\n    adv.style.opacity = \"0\";\n    advancedToggle.innerHTML = config.showAdvancedMessage;\n  } else {\n    adv.style.transform = \"scale(1)\";\n    adv.style.pointerEvents = \"auto\";\n    adv.style.opacity = \"1\";\n    advancedToggle.innerHTML = config.hideAdvancedMessage;\n  }\n  config.advanced = !config.advanced;\n});\n\ninitConfig();\n\nfunction initConfig() {\n  initializeDisableVoteSubmission();\n  initializeDisableLogging();\n  initializeVersionNumber();\n  initializeColoredThumbs();\n  initializeColoredBar();\n  initializeColorTheme();\n  initializeNumberDisplayFormat();\n  initializeTooltipPercentage();\n  initializeTooltipPercentageMode();\n  initializeNumberDisplayReformatLikes();\n  initializeHidePremiumTeaser();\n}\n\nfunction initializeVersionNumber() {\n  const version = chrome.runtime.getManifest().version;\n  document.getElementById(\"ext-version\").innerHTML = \"v\" + version;\n\n  fetch(\n    \"https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/combined/manifest-chrome.json\",\n  )\n    .then((response) => response.json())\n    .then((json) => {\n      if (compareVersions(json.version, version)) {\n        document.getElementById(\"ext-update\").innerHTML = chrome.i18n.getMessage(\"textUpdate\") + \" v\" + json.version;\n        document.getElementById(\"ext-update\").style.padding = \".25rem .5rem\";\n      }\n    });\n  // .catch(console.error);\n}\n\n// returns whether current < latest\nfunction compareVersions(latestStr, currentStr) {\n  let latestarr = latestStr.split(\".\");\n  let currentarr = currentStr.split(\".\");\n  let outdated = false;\n  // goes through version numbers from left to right from greatest to least significant\n  for (let i = 0; i < Math.max(latestarr.length, currentarr.length); i++) {\n    let latest = i < latestarr.length ? parseInt(latestarr[i]) : 0;\n    let current = i < currentarr.length ? parseInt(currentarr[i]) : 0;\n    if (latest > current) {\n      outdated = true;\n      break;\n    } else if (latest < current) {\n      outdated = false;\n      break;\n    }\n  }\n  return outdated;\n}\n\nfunction initializeDisableVoteSubmission() {\n  chrome.storage.sync.get([\"disableVoteSubmission\"], (res) => {\n    handleDisableVoteSubmissionChangeEvent(res.disableVoteSubmission);\n  });\n}\n\nfunction initializeDisableLogging() {\n  chrome.storage.sync.get([\"disableLogging\"], (res) => {\n    handleDisableLoggingChangeEvent(res.disableLogging);\n  });\n}\n\nfunction initializeColoredThumbs() {\n  chrome.storage.sync.get([\"coloredThumbs\"], (res) => {\n    handleColoredThumbsChangeEvent(res.coloredThumbs);\n  });\n}\n\nfunction initializeColoredBar() {\n  chrome.storage.sync.get([\"coloredBar\"], (res) => {\n    handleColoredBarChangeEvent(res.coloredBar);\n  });\n}\n\nfunction initializeColorTheme() {\n  chrome.storage.sync.get([\"colorTheme\"], (res) => {\n    handleColorThemeChangeEvent(res.colorTheme);\n  });\n}\n\nfunction initializeTooltipPercentage() {\n  chrome.storage.sync.get([\"showTooltipPercentage\"], (res) => {\n    handleShowTooltipPercentageChangeEvent(res.showTooltipPercentage);\n  });\n}\n\nfunction initializeTooltipPercentageMode() {\n  chrome.storage.sync.get([\"tooltipPercentageMode\"], (res) => {\n    handleTooltipPercentageModeChangeEvent(res.tooltipPercentageMode);\n  });\n}\n\nfunction initializeNumberDisplayFormat() {\n  chrome.storage.sync.get([\"numberDisplayFormat\"], (res) => {\n    handleNumberDisplayFormatChangeEvent(res.numberDisplayFormat);\n  });\n  updateNumberDisplayFormatContent();\n}\n\nfunction updateNumberDisplayFormatContent() {\n  let testValue = 123456;\n  document.getElementById(\"number_format_compactShort\").innerHTML =\n    getNumberFormatter(\"compactShort\").format(testValue);\n  document.getElementById(\"number_format_compactLong\").innerHTML = getNumberFormatter(\"compactLong\").format(testValue);\n  document.getElementById(\"number_format_standard\").innerHTML = getNumberFormatter(\"standard\").format(testValue);\n}\n\nfunction initializeNumberDisplayReformatLikes() {\n  chrome.storage.sync.get([\"numberDisplayReformatLikes\"], (res) => {\n    handleNumberDisplayReformatLikesChangeEvent(res.numberDisplayReformatLikes);\n  });\n}\n\nfunction initializeHidePremiumTeaser() {\n  chrome.storage.sync.get([\"hidePremiumTeaser\"], (res) => {\n    handleHidePremiumTeaserChangeEvent(res.hidePremiumTeaser);\n  });\n}\n\nchrome.storage.onChanged.addListener(storageChangeHandler);\n\nfunction storageChangeHandler(changes, area) {\n  if (changes.disableVoteSubmission !== undefined) {\n    handleDisableVoteSubmissionChangeEvent(changes.disableVoteSubmission.newValue);\n  }\n  if (changes.disableLogging !== undefined) {\n    handleDisableLoggingChangeEvent(changes.disableLogging.newValue);\n  }\n  if (changes.coloredThumbs !== undefined) {\n    handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);\n  }\n  if (changes.coloredBar !== undefined) {\n    handleColoredBarChangeEvent(changes.coloredBar.newValue);\n  }\n  if (changes.colorTheme !== undefined) {\n    handleColorThemeChangeEvent(changes.colorTheme.newValue);\n  }\n  if (changes.numberDisplayFormat !== undefined) {\n    handleNumberDisplayFormatChangeEvent(changes.numberDisplayFormat.newValue);\n  }\n  if (changes.showTooltipPercentage !== undefined) {\n    handleShowTooltipPercentageChangeEvent(changes.showTooltipPercentage.newValue);\n  }\n  if (changes.numberDisplayReformatLikes !== undefined) {\n    handleNumberDisplayReformatLikesChangeEvent(changes.numberDisplayReformatLikes.newValue);\n  }\n  if (changes.hidePremiumTeaser !== undefined) {\n    handleHidePremiumTeaserChangeEvent(changes.hidePremiumTeaser.newValue);\n  }\n}\n\nfunction handleDisableVoteSubmissionChangeEvent(value) {\n  config.disableVoteSubmission = value;\n  document.getElementById(\"disable_vote_submission\").checked = value;\n}\n\nfunction handleDisableLoggingChangeEvent(value) {\n  config.disableLogging = value;\n  document.getElementById(\"disable_logging\").checked = value;\n}\n\nfunction handleColoredThumbsChangeEvent(value) {\n  config.coloredThumbs = value;\n  document.getElementById(\"colored_thumbs\").checked = value;\n}\n\nfunction handleColoredBarChangeEvent(value) {\n  config.coloredBar = value;\n  document.getElementById(\"colored_bar\").checked = value;\n}\n\nfunction handleColorThemeChangeEvent(value) {\n  if (!value) {\n    value = \"classic\";\n  }\n  config.colorTheme = value;\n  document.getElementById(\"color_theme\").querySelector('option[value=\"' + value + '\"]').selected = true;\n  updateColorThemePreviewContent(value);\n}\n\nfunction updateColorThemePreviewContent(themeName) {\n  document.getElementById(\"color_theme_example_like\").style.backgroundColor = getColorFromTheme(themeName, true);\n  document.getElementById(\"color_theme_example_dislike\").style.backgroundColor = getColorFromTheme(themeName, false);\n}\n\nfunction handleNumberDisplayFormatChangeEvent(value) {\n  config.numberDisplayFormat = value;\n  document.getElementById(\"number_format\").querySelector('option[value=\"' + value + '\"]').selected = true;\n}\n\nfunction handleShowTooltipPercentageChangeEvent(value) {\n  config.showTooltipPercentage = value;\n  document.getElementById(\"show_tooltip_percentage\").checked = value;\n}\n\nfunction handleTooltipPercentageModeChangeEvent(value) {\n  if (!value) {\n    value = \"dash_like\";\n  }\n  config.tooltipPercentageMode = value;\n\n  document.getElementById(\"tooltip_percentage_mode\").querySelector('option[value=\"' + value + '\"]').selected = true;\n}\n\nfunction handleNumberDisplayReformatLikesChangeEvent(value) {\n  config.numberDisplayReformatLikes = value;\n  document.getElementById(\"number_reformat_likes\").checked = value;\n}\n\nfunction handleHidePremiumTeaserChangeEvent(value) {\n  const normalized = value === true;\n  config.hidePremiumTeaser = normalized;\n  document.getElementById(\"hide_premium_teaser\").checked = normalized;\n}\n\nfunction getNumberFormatter(optionSelect) {\n  let formatterNotation;\n  let formatterCompactDisplay;\n  let userLocales;\n  try {\n    userLocales = new URL(\n      Array.from(document.querySelectorAll(\"head > link[rel='search']\"))\n        ?.find((n) => n?.getAttribute(\"href\")?.includes(\"?locale=\"))\n        ?.getAttribute(\"href\"),\n    )?.searchParams?.get(\"locale\");\n  } catch {}\n\n  switch (optionSelect) {\n    case \"compactLong\":\n      formatterNotation = \"compact\";\n      formatterCompactDisplay = \"long\";\n      break;\n    case \"standard\":\n      formatterNotation = \"standard\";\n      formatterCompactDisplay = \"short\";\n      break;\n    case \"compactShort\":\n    default:\n      formatterNotation = \"compact\";\n      formatterCompactDisplay = \"short\";\n  }\n  const formatter = Intl.NumberFormat(document.documentElement.lang || userLocales || navigator.language, {\n    notation: formatterNotation,\n    compactDisplay: formatterCompactDisplay,\n  });\n  return formatter;\n}\n\n(async function getStatus() {\n  let status = document.getElementById(\"status\");\n  let serverStatus = document.getElementById(\"server-status\");\n  let resp = await fetch(\"https://returnyoutubedislikeapi.com/votes?videoId=YbJOTdZBX1g\");\n  let result = await resp.status;\n  if (result === 200) {\n    status.innerText = chrome.i18n.getMessage(\"apiStatusOnline\");\n    status.style.color = \"green\";\n    serverStatus.style.filter =\n      \"invert(58%) sepia(81%) saturate(2618%) hue-rotate(81deg) brightness(119%) contrast(129%)\";\n  } else {\n    status.innerText = chrome.i18n.getMessage(\"apiStatusOffline\");\n    status.style.color = \"red\";\n    serverStatus.style.filter =\n      \"invert(11%) sepia(100%) saturate(6449%) hue-rotate(3deg) brightness(116%) contrast(115%)\";\n  }\n})();\n\nfunction getColorFromTheme(colorTheme, voteIsLike) {\n  let colorString;\n  switch (colorTheme) {\n    case \"accessible\":\n      if (voteIsLike === true) {\n        colorString = \"dodgerblue\";\n      } else {\n        colorString = \"gold\";\n      }\n      break;\n    case \"neon\":\n      if (voteIsLike === true) {\n        colorString = \"aqua\";\n      } else {\n        colorString = \"magenta\";\n      }\n      break;\n    case \"classic\":\n    default:\n      if (voteIsLike === true) {\n        colorString = \"lime\";\n      } else {\n        colorString = \"red\";\n      }\n  }\n  return colorString;\n}\n\n/* popup-script.js\ndocument.querySelector('#login')\n.addEventListener('click', function () {\n  chrome.runtime.sendMessage({ message: 'get_auth_token' });\n});\n\n\ndocument.querySelector(\"#log_off\").addEventListener(\"click\", function () {\n  chrome.runtime.sendMessage({ message: \"log_off\" });\n});\n*/\n"
  },
  {
    "path": "Extensions/combined/readme.md",
    "content": "# Extension Source\n\n## Guide to Compiling\n\n## Compiling to Development (Testing)\n\n<ol>\n    <li>Go to the root directory of the project</li>\n    <li>Run <code>npm i</code> to install all project dependencies (if not done so already)</li>\n    <li>run <code>npm run dev</code> to compile the extension to the <code>~/Extensions/combined/dist/</code> folder.</li>\n</ol>\n\n## Compiling to Production (Final Release)\n\n<ol>\n    <li>Go to the root directory of the project</li>\n    <li>Run <code>npm i</code> to install all project dependancies (if not done so already)</li>\n    <li>run <code>npm run build</code> to compile the extension to the <code>~/Extensions/combined/dist/</code> folder.</li>\n</ol>\n"
  },
  {
    "path": "Extensions/combined/ryd.background.js",
    "content": "import { config, getApiUrl, getApiEndpoint, getChangelogUrl } from \"./src/config\";\n\nconst apiUrl = getApiUrl();\nconst voteDisabledIconName = config.voteDisabledIconName;\nconst defaultIconName = config.defaultIconName;\nlet api;\nconst CHANGELOG_STORAGE_KEY = \"lastShownChangelogVersion\";\nconst PENDING_CHANGELOG_STORAGE_KEY = \"pendingChangelogVersion\";\n\n/** stores extension's global config */\nlet extConfig = { ...config.defaultExtConfig };\n\nif (isChrome()) api = chrome;\nelse if (isFirefox()) api = browser;\n\ninitExtConfig();\n\nfunction broadcastPatreonStatus(authenticated, user, sessionToken) {\n  chrome.tabs.query({}, (tabs) => {\n    tabs\n      .filter((tab) => tab.url && tab.url.includes(\"youtube.com\"))\n      .forEach((tab) => {\n        const maybePromise = chrome.tabs.sendMessage(\n          tab.id,\n          {\n            message: \"patreon_status_changed\",\n            authenticated,\n            user: authenticated ? user : null,\n            sessionToken: authenticated ? sessionToken : null,\n          },\n          () => {\n            if (chrome.runtime.lastError) {\n              console.debug(\"Patreon status broadcast skipped:\", chrome.runtime.lastError.message);\n            }\n          },\n        );\n\n        if (maybePromise && typeof maybePromise.catch === \"function\") {\n          maybePromise.catch((error) => {\n            console.debug(\"Patreon status broadcast skipped:\", error?.message ?? error);\n          });\n        }\n      });\n  });\n}\n\nfunction handlePatreonAuthComplete(user, sessionToken, done) {\n  if (!user) {\n    done?.();\n    return;\n  }\n\n  chrome.storage.sync.set(\n    {\n      patreonAuthenticated: true,\n      patreonUser: user,\n      patreonSessionToken: sessionToken,\n    },\n    () => {\n      broadcastPatreonStatus(true, user, sessionToken);\n      done?.();\n    },\n  );\n}\n\nfunction getIdentityApi() {\n  if (isFirefox() && browser.identity) return browser.identity;\n  if (isChrome() && chrome.identity) return chrome.identity;\n  return null;\n}\n\nfunction launchWebAuthFlow(url) {\n  try {\n    if (isFirefox() && browser.identity && typeof browser.identity.launchWebAuthFlow === \"function\") {\n      return browser.identity.launchWebAuthFlow({ url, interactive: true });\n    }\n  } catch (_) {}\n  return new Promise((resolve, reject) => {\n    if (!isChrome() || !chrome.identity || typeof chrome.identity.launchWebAuthFlow !== \"function\") {\n      reject(new Error(\"identity API not available\"));\n      return;\n    }\n    chrome.identity.launchWebAuthFlow({ url, interactive: true }, (responseUrl) => {\n      const err = chrome.runtime && chrome.runtime.lastError;\n      if (err) reject(err);\n      else resolve(responseUrl);\n    });\n  });\n}\n\nfunction extractOAuthParams(responseUrl) {\n  try {\n    const u = new URL(responseUrl);\n    let code = u.searchParams.get(\"code\");\n    let state = u.searchParams.get(\"state\");\n    if (!code && u.hash) {\n      const hashParams = new URLSearchParams(u.hash.startsWith(\"#\") ? u.hash.substring(1) : u.hash);\n      code = hashParams.get(\"code\");\n      state = state || hashParams.get(\"state\");\n    }\n    return { code, state };\n  } catch (_) {\n    return { code: null, state: null };\n  }\n}\n\napi.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  if (request.message === \"get_auth_token\") {\n    chrome.identity.getAuthToken({ interactive: true }, function (token) {\n      console.log(token);\n      chrome.identity.getProfileUserInfo(function (userInfo) {\n        console.log(JSON.stringify(userInfo));\n      });\n    });\n  } else if (request.message === \"log_off\") {\n    // chrome.identity.clearAllCachedAuthTokens(() => console.log(\"logged off\"));\n  } else if (request.message === \"patreon_auth_complete\") {\n    handlePatreonAuthComplete(request.user, request.sessionToken);\n  } else if (request.message === \"patreon_logout\") {\n    // Clear Patreon authentication\n    chrome.storage.sync.remove([\"patreonAuthenticated\", \"patreonUser\", \"patreonSessionToken\"], () => {\n      broadcastPatreonStatus(false, null, null);\n    });\n  } else if (request.message === \"ryd_open_tab\") {\n    const targetUrl = typeof request?.url === \"string\" ? request.url : null;\n    if (!targetUrl) {\n      sendResponse?.({ success: false, error: \"invalid_url\" });\n      return;\n    }\n\n    try {\n      if (api?.tabs?.create) {\n        api.tabs.create({ url: targetUrl }, () => {\n          if (api.runtime?.lastError) {\n            console.debug(\"Tab open failed:\", api.runtime.lastError.message);\n          }\n        });\n        sendResponse?.({ success: true });\n        return;\n      }\n    } catch (error) {\n      console.debug(\"Tab open threw:\", error?.message ?? error);\n    }\n\n    sendResponse?.({ success: false, error: \"tabs_api_unavailable\" });\n    return;\n  } else if (request.message == \"set_state\") {\n    // chrome.identity.getAuthToken({ interactive: true }, function (token) {\n    let token = \"\";\n    fetch(getApiEndpoint(`/votes?videoId=${request.videoId}&likeCount=${request.likeCount || \"\"}`), {\n      method: \"GET\",\n      headers: {\n        Accept: \"application/json\",\n      },\n    })\n      .then((response) => response.json())\n      .then((response) => {\n        sendResponse(response);\n      })\n      .catch();\n    return true;\n  } else if (request.message == \"send_links\") {\n    toSend = toSend.concat(request.videoIds.filter((x) => !sentIds.has(x)));\n    if (toSend.length >= 20) {\n      fetch(getApiEndpoint(\"/votes\"), {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify(toSend),\n      });\n      for (const toSendUrl of toSend) {\n        sentIds.add(toSendUrl);\n      }\n      toSend = [];\n    }\n  } else if (request.message == \"register\") {\n    register();\n    return true;\n  } else if (request.message == \"send_vote\") {\n    sendVote(request.videoId, request.vote);\n    return true;\n  } else if (request.message === \"patreon_oauth_login\") {\n    (async () => {\n      try {\n        const idApi = getIdentityApi();\n        if (\n          !idApi ||\n          typeof (idApi.getRedirectURL || (isChrome() && chrome.identity && chrome.identity.getRedirectURL)) !==\n            \"function\"\n        ) {\n          sendResponse({ success: false, error: \"identity API not available\" });\n          return;\n        }\n        const redirectUri =\n          isFirefox() && browser.identity.getRedirectURL\n            ? browser.identity.getRedirectURL()\n            : isChrome() && chrome.identity.getRedirectURL\n              ? chrome.identity.getRedirectURL()\n              : \"\";\n\n        const startRes = await fetch(\n          getApiEndpoint(`/api/auth/oauth/login?redirectUri=${encodeURIComponent(redirectUri)}`),\n        );\n        const startData = await startRes.json();\n\n        const responseUrl = await launchWebAuthFlow(startData.authUrl);\n        const { code, state } = extractOAuthParams(responseUrl);\n        if (!code) {\n          sendResponse({ success: false, error: \"No authorization code received\" });\n          return;\n        }\n\n        const exchangeRes = await fetch(getApiEndpoint(\"/api/auth/oauth/exchange\"), {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify({\n            code,\n            state,\n            expectedState: startData.state,\n            redirectUri: startData.redirectUri || redirectUri,\n          }),\n        });\n        const authData = await exchangeRes.json();\n        if (authData && authData.success) {\n          handlePatreonAuthComplete(authData.user, authData.sessionToken, () => {\n            sendResponse({ success: true, user: authData.user });\n          });\n        } else {\n          sendResponse({ success: false, error: (authData && authData.error) || \"OAuth exchange failed\" });\n        }\n      } catch (e) {\n        console.error(\"patreon_oauth_login error\", e);\n        sendResponse({ success: false, error: String((e && e.message) || e) });\n      }\n    })();\n    return true;\n  }\n});\n\nfunction openChangelogTab(version) {\n  try {\n    const url = getChangelogUrl();\n    api.tabs.create({ url }, () => {\n      if (api.runtime.lastError) {\n        console.debug(\"Changelog tab could not open:\", api.runtime.lastError.message);\n      }\n      persistChangelogVersion(version);\n    });\n  } catch (error) {\n    console.debug(\"Failed to open changelog tab\", error);\n  }\n}\n\nfunction scheduleChangelogVersion(version) {\n  const storage = api?.storage?.local;\n  if (!storage || typeof storage.set !== \"function\") {\n    return false;\n  }\n  try {\n    const valueToStore = version || true;\n    storage.set({ [PENDING_CHANGELOG_STORAGE_KEY]: valueToStore }, () => {\n      if (api.runtime.lastError) {\n        console.debug(\"Failed to persist pending changelog version:\", api.runtime.lastError.message);\n      }\n    });\n    return true;\n  } catch (error) {\n    console.debug(\"Storage set failed for pending changelog version\", error);\n    return false;\n  }\n}\n\nfunction clearPendingChangelogVersion() {\n  const storage = api?.storage?.local;\n  if (!storage || typeof storage.remove !== \"function\") {\n    return;\n  }\n  try {\n    storage.remove(PENDING_CHANGELOG_STORAGE_KEY, () => {\n      if (api.runtime.lastError) {\n        console.debug(\"Failed to clear pending changelog version:\", api.runtime.lastError.message);\n      }\n    });\n  } catch (error) {\n    console.debug(\"Storage remove failed for pending changelog version\", error);\n  }\n}\n\nfunction showPendingChangelogIfNeeded() {\n  const storage = api?.storage?.local;\n  if (!storage || typeof storage.get !== \"function\") {\n    return;\n  }\n\n  try {\n    storage.get([PENDING_CHANGELOG_STORAGE_KEY, CHANGELOG_STORAGE_KEY], (result) => {\n      if (api.runtime.lastError) {\n        console.debug(\"Changelog storage read failed:\", api.runtime.lastError.message);\n        return;\n      }\n\n      const pendingValue = result?.[PENDING_CHANGELOG_STORAGE_KEY];\n      if (pendingValue === undefined || pendingValue === null || pendingValue === \"\") {\n        return;\n      }\n\n      const lastShownValue = result?.[CHANGELOG_STORAGE_KEY];\n      if (lastShownValue !== undefined && lastShownValue !== null && lastShownValue !== \"\") {\n        clearPendingChangelogVersion();\n        return;\n      }\n\n      openChangelogTab(typeof pendingValue === \"string\" ? pendingValue : null);\n    });\n  } catch (error) {\n    console.debug(\"Storage get failed for pending changelog version\", error);\n  }\n}\n\nfunction persistChangelogVersion(version) {\n  const storage = api?.storage?.local;\n  if (!storage || typeof storage.set !== \"function\") {\n    clearPendingChangelogVersion();\n    return;\n  }\n  try {\n    const valueToStore = version || true;\n    storage.set({ [CHANGELOG_STORAGE_KEY]: valueToStore }, () => {\n      if (api.runtime.lastError) {\n        console.debug(\"Failed to persist changelog version:\", api.runtime.lastError.message);\n        return;\n      }\n      clearPendingChangelogVersion();\n    });\n  } catch (error) {\n    console.debug(\"Storage set failed for changelog version\", error);\n  }\n}\n\nfunction maybeShowChangelog(details) {\n  const reason = details?.reason;\n  if (!reason) {\n    return;\n  }\n\n  if (reason === \"browser_update\" || reason === \"chrome_update\") {\n    return;\n  }\n\n  if (reason !== \"install\" && reason !== \"update\") {\n    return;\n  }\n\n  const manifest = api.runtime.getManifest();\n  const currentVersion = manifest?.version;\n  const storage = api?.storage?.local;\n  const isInstall = reason === \"install\";\n\n  const showChangelog = () => {\n    openChangelogTab(currentVersion || null);\n  };\n\n  if (!storage || typeof storage.get !== \"function\") {\n    showChangelog();\n    return;\n  }\n\n  try {\n    storage.get([CHANGELOG_STORAGE_KEY, PENDING_CHANGELOG_STORAGE_KEY], (result) => {\n      if (api.runtime.lastError) {\n        console.debug(\"Changelog storage read failed:\", api.runtime.lastError.message);\n        showChangelog();\n        return;\n      }\n\n      const hasStoredValue = (value) => value !== undefined && value !== null && value !== \"\";\n      const lastShownValue = result?.[CHANGELOG_STORAGE_KEY];\n      const pendingValue = result?.[PENDING_CHANGELOG_STORAGE_KEY];\n\n      if (isInstall) {\n        if (hasStoredValue(pendingValue)) {\n          clearPendingChangelogVersion();\n        }\n        if (!hasStoredValue(lastShownValue)) {\n          showChangelog();\n        }\n        return;\n      }\n\n      if (hasStoredValue(lastShownValue)) {\n        return;\n      }\n\n      if (hasStoredValue(pendingValue)) {\n        if (currentVersion && pendingValue !== currentVersion) {\n          scheduleChangelogVersion(currentVersion);\n        }\n        return;\n      }\n\n      if (!scheduleChangelogVersion(currentVersion || null)) {\n        showChangelog();\n      }\n    });\n  } catch (error) {\n    console.debug(\"Storage get failed for changelog version\", error);\n    showChangelog();\n  }\n}\n\napi.runtime.onInstalled.addListener((details) => {\n  maybeShowChangelog(details);\n});\n\nif (api?.runtime?.onStartup && typeof api.runtime.onStartup.addListener === \"function\") {\n  api.runtime.onStartup.addListener(() => {\n    showPendingChangelogIfNeeded();\n  });\n}\n\n// api.storage.sync.get(['lastShowChangelogVersion'], (details) => {\n//   if (extConfig.showUpdatePopup === true &&\n//     details.lastShowChangelogVersion !== chrome.runtime.getManifest().version\n//     ) {\n//     // keep it inside get to avoid race condition\n//     api.storage.sync.set({'lastShowChangelogVersion ': chrome.runtime.getManifest().version});\n//     // wait until async get runs & don't steal tab focus\n//     api.tabs.create({url: api.runtime.getURL(\"/changelog/4/changelog_4.0.html\"), active: false});\n//   }\n// });\n\nasync function sendVote(videoId, vote, depth = 1) {\n  api.storage.sync.get(null, async (storageResult) => {\n    if (!storageResult.userId || !storageResult.registrationConfirmed) {\n      await register();\n    }\n    let voteResponse = await fetch(getApiEndpoint(\"/interact/vote\"), {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({\n        userId: storageResult.userId,\n        videoId,\n        value: vote,\n      }),\n    });\n\n    if (voteResponse.status == 401 && depth > 0) {\n      await register();\n      await sendVote(videoId, vote, depth - 1);\n      return;\n    } else if (voteResponse.status == 401) {\n      // We have already tried registering\n      return;\n    }\n\n    const voteResponseJson = await voteResponse.json();\n    const solvedPuzzle = await solvePuzzle(voteResponseJson);\n    if (!solvedPuzzle.solution) {\n      await sendVote(videoId, vote);\n      return;\n    }\n\n    await fetch(getApiEndpoint(\"/interact/confirmVote\"), {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({\n        ...solvedPuzzle,\n        userId: storageResult.userId,\n        videoId,\n      }),\n    });\n  });\n}\n\nasync function register() {\n  const userId = generateUserID();\n  api.storage.sync.set({ userId });\n  const registrationResponse = await fetch(getApiEndpoint(`/puzzle/registration?userId=${userId}`), {\n    method: \"GET\",\n    headers: {\n      Accept: \"application/json\",\n    },\n  }).then((response) => response.json());\n  const solvedPuzzle = await solvePuzzle(registrationResponse);\n  if (!solvedPuzzle.solution) {\n    await register();\n    return;\n  }\n  const result = await fetch(getApiEndpoint(`/puzzle/registration?userId=${userId}`), {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n    },\n    body: JSON.stringify(solvedPuzzle),\n  }).then((response) => response.json());\n  if (result === true) {\n    return api.storage.sync.set({ registrationConfirmed: true });\n  }\n}\n\napi.storage.sync.get(null, async (res) => {\n  if (!res || !res.userId || !res.registrationConfirmed) {\n    await register();\n  }\n});\n\nconst sentIds = new Set();\nlet toSend = [];\n\nfunction countLeadingZeroes(uInt8View, limit) {\n  let zeroes = 0;\n  let value = 0;\n  for (let i = 0; i < uInt8View.length; i++) {\n    value = uInt8View[i];\n    if (value === 0) {\n      zeroes += 8;\n    } else {\n      let count = 1;\n      if (value >>> 4 === 0) {\n        count += 4;\n        value <<= 4;\n      }\n      if (value >>> 6 === 0) {\n        count += 2;\n        value <<= 2;\n      }\n      zeroes += count - (value >>> 7);\n      break;\n    }\n    if (zeroes >= limit) {\n      break;\n    }\n  }\n  return zeroes;\n}\n\nasync function solvePuzzle(puzzle) {\n  let challenge = Uint8Array.from(atob(puzzle.challenge), (c) => c.charCodeAt(0));\n  let buffer = new ArrayBuffer(20);\n  let uInt8View = new Uint8Array(buffer);\n  let uInt32View = new Uint32Array(buffer);\n  let maxCount = Math.pow(2, puzzle.difficulty) * 3;\n  for (let i = 4; i < 20; i++) {\n    uInt8View[i] = challenge[i - 4];\n  }\n\n  for (let i = 0; i < maxCount; i++) {\n    uInt32View[0] = i;\n    let hash = await crypto.subtle.digest(\"SHA-512\", buffer);\n    let hashUint8 = new Uint8Array(hash);\n    if (countLeadingZeroes(hashUint8) >= puzzle.difficulty) {\n      return {\n        solution: btoa(String.fromCharCode.apply(null, uInt8View.slice(0, 4))),\n      };\n    }\n  }\n  return {};\n}\n\nfunction generateUserID(length = 36) {\n  const charset = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n  let result = \"\";\n  if (crypto && crypto.getRandomValues) {\n    const values = new Uint32Array(length);\n    crypto.getRandomValues(values);\n    for (let i = 0; i < length; i++) {\n      result += charset[values[i] % charset.length];\n    }\n    return result;\n  } else {\n    for (let i = 0; i < length; i++) {\n      result += charset[Math.floor(Math.random() * charset.length)];\n    }\n    return result;\n  }\n}\n\nfunction storageChangeHandler(changes, area) {\n  if (changes.disableVoteSubmission !== undefined) {\n    handleDisableVoteSubmissionChangeEvent(changes.disableVoteSubmission.newValue);\n  }\n  if (changes.coloredThumbs !== undefined) {\n    handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);\n  }\n  if (changes.coloredBar !== undefined) {\n    handleColoredBarChangeEvent(changes.coloredBar.newValue);\n  }\n  if (changes.colorTheme !== undefined) {\n    handleColorThemeChangeEvent(changes.colorTheme.newValue);\n  }\n  if (changes.numberDisplayFormat !== undefined) {\n    handleNumberDisplayFormatChangeEvent(changes.numberDisplayFormat.newValue);\n  }\n  if (changes.numberDisplayReformatLikes !== undefined) {\n    handleNumberDisplayReformatLikesChangeEvent(changes.numberDisplayReformatLikes.newValue);\n  }\n  if (changes.disableLogging !== undefined) {\n    handleDisableLoggingChangeEvent(changes.disableLogging.newValue);\n  }\n  if (changes.showTooltipPercentage !== undefined) {\n    handleShowTooltipPercentageChangeEvent(changes.showTooltipPercentage.newValue);\n  }\n  if (changes.numberDisplayReformatLikes !== undefined) {\n    handleNumberDisplayReformatLikesChangeEvent(changes.numberDisplayReformatLikes.newValue);\n  }\n  if (changes.hidePremiumTeaser !== undefined) {\n    handleHidePremiumTeaserChangeEvent(changes.hidePremiumTeaser.newValue);\n  }\n}\n\nfunction handleDisableVoteSubmissionChangeEvent(value) {\n  extConfig.disableVoteSubmission = value;\n  if (value === true) {\n    changeIcon(voteDisabledIconName);\n  } else {\n    changeIcon(defaultIconName);\n  }\n}\n\nfunction handleDisableLoggingChangeEvent(value) {\n  extConfig.disableLogging = value;\n}\n\nfunction handleNumberDisplayFormatChangeEvent(value) {\n  extConfig.numberDisplayFormat = value;\n}\n\nfunction handleShowTooltipPercentageChangeEvent(value) {\n  extConfig.showTooltipPercentage = value;\n}\n\nfunction handleTooltipPercentageModeChangeEvent(value) {\n  if (!value) {\n    value = \"dash_like\";\n  }\n  extConfig.tooltipPercentageMode = value;\n}\n\nfunction changeIcon(iconName) {\n  if (api.action !== undefined) api.action.setIcon({ path: \"/icons/\" + iconName });\n  else if (api.browserAction !== undefined) api.browserAction.setIcon({ path: \"/icons/\" + iconName });\n  else console.log(\"changing icon is not supported\");\n}\n\nfunction handleColoredThumbsChangeEvent(value) {\n  extConfig.coloredThumbs = value;\n}\n\nfunction handleColoredBarChangeEvent(value) {\n  extConfig.coloredBar = value;\n}\n\nfunction handleColorThemeChangeEvent(value) {\n  if (!value) {\n    value = \"classic\";\n  }\n  extConfig.colorTheme = value;\n}\n\nfunction handleNumberDisplayReformatLikesChangeEvent(value) {\n  extConfig.numberDisplayReformatLikes = value;\n}\n\nfunction handleHidePremiumTeaserChangeEvent(value) {\n  extConfig.hidePremiumTeaser = value === true;\n}\n\napi.storage.onChanged.addListener(storageChangeHandler);\n\nfunction initExtConfig() {\n  initializeDisableVoteSubmission();\n  initializeDisableLogging();\n  initializeColoredThumbs();\n  initializeColoredBar();\n  initializeColorTheme();\n  initializeNumberDisplayFormat();\n  initializeNumberDisplayReformatLikes();\n  initializeTooltipPercentage();\n  initializeTooltipPercentageMode();\n  initializeHidePremiumTeaser();\n}\n\nfunction initializeDisableVoteSubmission() {\n  api.storage.sync.get([\"disableVoteSubmission\"], (res) => {\n    if (res.disableVoteSubmission === undefined) {\n      api.storage.sync.set({ disableVoteSubmission: false });\n    } else {\n      extConfig.disableVoteSubmission = res.disableVoteSubmission;\n      if (res.disableVoteSubmission) changeIcon(voteDisabledIconName);\n    }\n  });\n}\n\nfunction initializeDisableLogging() {\n  api.storage.sync.get([\"disableLogging\"], (res) => {\n    if (res.disableLogging === undefined) {\n      api.storage.sync.set({ disableLogging: true });\n    } else {\n      extConfig.disableLogging = res.disableLogging;\n    }\n  });\n}\n\nfunction initializeColoredThumbs() {\n  api.storage.sync.get([\"coloredThumbs\"], (res) => {\n    if (res.coloredThumbs === undefined) {\n      api.storage.sync.set({ coloredThumbs: false });\n    } else {\n      extConfig.coloredThumbs = res.coloredThumbs;\n    }\n  });\n}\n\nfunction initializeColoredBar() {\n  api.storage.sync.get([\"coloredBar\"], (res) => {\n    if (res.coloredBar === undefined) {\n      api.storage.sync.set({ coloredBar: false });\n    } else {\n      extConfig.coloredBar = res.coloredBar;\n    }\n  });\n}\n\nfunction initializeColorTheme() {\n  api.storage.sync.get([\"colorTheme\"], (res) => {\n    if (res.colorTheme === undefined) {\n      api.storage.sync.set({ colorTheme: false });\n    } else {\n      extConfig.colorTheme = res.colorTheme;\n    }\n  });\n}\n\nfunction initializeNumberDisplayFormat() {\n  api.storage.sync.get([\"numberDisplayFormat\"], (res) => {\n    if (res.numberDisplayFormat === undefined) {\n      api.storage.sync.set({ numberDisplayFormat: \"compactShort\" });\n    } else {\n      extConfig.numberDisplayFormat = res.numberDisplayFormat;\n    }\n  });\n}\n\nfunction initializeTooltipPercentage() {\n  api.storage.sync.get([\"showTooltipPercentage\"], (res) => {\n    if (res.showTooltipPercentage === undefined) {\n      api.storage.sync.set({ showTooltipPercentage: false });\n    } else {\n      extConfig.showTooltipPercentage = res.showTooltipPercentage;\n    }\n  });\n}\n\nfunction initializeTooltipPercentageMode() {\n  api.storage.sync.get([\"tooltipPercentageMode\"], (res) => {\n    if (res.tooltipPercentageMode === undefined) {\n      api.storage.sync.set({ tooltipPercentageMode: \"dash_like\" });\n    } else {\n      extConfig.tooltipPercentageMode = res.tooltipPercentageMode;\n    }\n  });\n}\n\nfunction initializeNumberDisplayReformatLikes() {\n  api.storage.sync.get([\"numberDisplayReformatLikes\"], (res) => {\n    if (res.numberDisplayReformatLikes === undefined) {\n      api.storage.sync.set({ numberDisplayReformatLikes: false });\n    } else {\n      extConfig.numberDisplayReformatLikes = res.numberDisplayReformatLikes;\n    }\n  });\n}\n\nfunction initializeHidePremiumTeaser() {\n  api.storage.sync.get([\"hidePremiumTeaser\"], (res) => {\n    if (res.hidePremiumTeaser === undefined) {\n      api.storage.sync.set({ hidePremiumTeaser: false });\n      extConfig.hidePremiumTeaser = false;\n    } else {\n      extConfig.hidePremiumTeaser = res.hidePremiumTeaser === true;\n    }\n  });\n}\n\nfunction isChrome() {\n  return typeof chrome !== \"undefined\" && typeof chrome.runtime !== \"undefined\";\n}\n\nfunction isFirefox() {\n  return typeof browser !== \"undefined\" && typeof browser.runtime !== \"undefined\";\n}\n"
  },
  {
    "path": "Extensions/combined/ryd.changelog.js",
    "content": "import { initChangelogPage } from \"./src/changelog\";\n\ninitChangelogPage();\n"
  },
  {
    "path": "Extensions/combined/ryd.content-script.js",
    "content": "import { getButtons } from \"./src/buttons\";\nimport { isShorts, setInitialState, initExtConfig } from \"./src/state\";\nimport { getBrowser, isVideoLoaded } from \"./src/utils\";\nimport { addLikeDislikeEventListener, createSmartimationObserver, storageChangeHandler } from \"./src/events\";\nimport { initPatreonFeatures } from \"./src/patreon\";\n\nawait initExtConfig();\ninitPatreonFeatures();\n\nlet jsInitChecktimer = null;\nlet isSetInitialStateDone = false;\nlet isStorageListenerRegistered = false;\nlet shortsNavigationObserver = null;\nlet shortsNavigationObserverTarget = null;\n\nfunction ensureShortsNavigationObserver() {\n  if (!isShorts()) {\n    return;\n  }\n\n  const shortsRoot = document.querySelector(\"ytd-shorts\");\n  if (!shortsRoot) {\n    return;\n  }\n\n  if (!shortsNavigationObserver) {\n    shortsNavigationObserver = new MutationObserver((mutations) => {\n      for (const mutation of mutations) {\n        if (\n          mutation.type === \"attributes\" &&\n          mutation.attributeName === \"is-active\" &&\n          mutation.target.tagName === \"YTD-REEL-VIDEO-RENDERER\" &&\n          mutation.target.hasAttribute(\"is-active\")\n        ) {\n          triggerInitializationCycle();\n          break;\n        }\n      }\n    });\n  }\n\n  if (shortsNavigationObserverTarget !== shortsRoot) {\n    shortsNavigationObserver.disconnect();\n    shortsNavigationObserver.observe(shortsRoot, {\n      attributes: true,\n      subtree: true,\n      attributeFilter: [\"is-active\"],\n    });\n    shortsNavigationObserverTarget = shortsRoot;\n  }\n}\n\nasync function checkForInitialization() {\n  try {\n    if (isShorts()) {\n      ensureShortsNavigationObserver();\n    }\n\n    if ((isShorts() && isVideoLoaded()) || (getButtons()?.offsetParent && isVideoLoaded())) {\n      if (jsInitChecktimer !== null) {\n        clearInterval(jsInitChecktimer);\n        jsInitChecktimer = null;\n      }\n      createSmartimationObserver();\n      addLikeDislikeEventListener();\n      await setInitialState();\n      isSetInitialStateDone = true;\n      if (!isStorageListenerRegistered) {\n        getBrowser().storage.onChanged.addListener(storageChangeHandler);\n        isStorageListenerRegistered = true;\n      }\n    }\n  } catch (exception) {\n    if (!isSetInitialStateDone) {\n      console.log(\"error\");\n      await setInitialState();\n    }\n  }\n}\n\nasync function triggerInitializationCycle() {\n  isSetInitialStateDone = false;\n\n  if (jsInitChecktimer !== null) {\n    clearInterval(jsInitChecktimer);\n    jsInitChecktimer = null;\n  }\n\n  await checkForInitialization();\n\n  if (!isSetInitialStateDone) {\n    jsInitChecktimer = setInterval(() => {\n      checkForInitialization();\n    }, 111);\n\n    setTimeout(() => {\n      if (!isSetInitialStateDone) {\n        checkForInitialization();\n      }\n    }, 2000);\n  }\n}\n\nasync function setEventListeners() {\n  await triggerInitializationCycle();\n}\n\nawait setEventListeners();\n\ndocument.addEventListener(\"yt-navigate-finish\", async function (event) {\n  await setEventListeners();\n});\n\nconst s = document.createElement(\"script\");\ns.src = chrome.runtime.getURL(\"menu-fixer.js\");\ns.onload = function () {\n  this.remove();\n};\n\n(document.head || document.documentElement).appendChild(s);\n"
  },
  {
    "path": "Extensions/combined/src/bar.js",
    "content": "import { getButtons, getDislikeButton, getLikeButton } from \"./buttons\";\nimport {\n  extConfig,\n  isMobile,\n  isLikesDisabled,\n  isNewDesign,\n  isRoundedDesign,\n  isShorts,\n} from \"./state\";\nimport { getColorFromTheme, isInViewport } from \"./utils\";\n\nfunction createRateBar(likes, dislikes) {\n  let rateBar = document.getElementById(\"ryd-bar-container\");\n  if (!isLikesDisabled()) {\n    // sometimes rate bar is hidden\n    if (rateBar && !isInViewport(rateBar)) {\n      rateBar.remove();\n      rateBar = null;\n    }\n\n    const widthPx =\n      parseFloat(window.getComputedStyle(getLikeButton()).width) +\n      parseFloat(window.getComputedStyle(getDislikeButton()).width) +\n      (isRoundedDesign() ? 0 : 8);\n\n    const widthPercent =\n      likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;\n\n    var likePercentage = parseFloat(widthPercent.toFixed(1));\n    const dislikePercentage = (100 - likePercentage).toLocaleString();\n    likePercentage = likePercentage.toLocaleString();\n\n    if (extConfig.showTooltipPercentage) {\n      var tooltipInnerHTML;\n      switch (extConfig.tooltipPercentageMode) {\n        case \"dash_dislike\":\n          tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}&nbsp;&nbsp;-&nbsp;&nbsp;${dislikePercentage}%`;\n          break;\n        case \"both\":\n          tooltipInnerHTML = `${likePercentage}%&nbsp;/&nbsp;${dislikePercentage}%`;\n          break;\n        case \"only_like\":\n          tooltipInnerHTML = `${likePercentage}%`;\n          break;\n        case \"only_dislike\":\n          tooltipInnerHTML = `${dislikePercentage}%`;\n          break;\n        default: // dash_like\n          tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}&nbsp;&nbsp;-&nbsp;&nbsp;${likePercentage}%`;\n      }\n    } else {\n      tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;\n    }\n\n    if (!isShorts()) {\n      if (!rateBar && !isMobile()) {\n        let colorLikeStyle = \"\";\n        let colorDislikeStyle = \"\";\n        if (extConfig.coloredBar) {\n          colorLikeStyle = \"; background-color: \" + getColorFromTheme(true);\n          colorDislikeStyle = \"; background-color: \" + getColorFromTheme(false);\n        }\n        let actions =\n          isNewDesign() && getButtons().id === \"top-level-buttons-computed\"\n            ? getButtons()\n            : document.getElementById(\"menu-container\");\n        (\n          actions ||\n          document.querySelector(\"ytm-slim-video-action-bar-renderer\")\n        ).insertAdjacentHTML(\n          \"beforeend\",\n          `\n              <div class=\"ryd-tooltip ryd-tooltip-${isNewDesign() ? \"new\" : \"old\"}-design\" style=\"width: ${widthPx}px\">\n              <div class=\"ryd-tooltip-bar-container\">\n                <div\n                    id=\"ryd-bar-container\"\n                    style=\"width: 100%; height: 2px;${colorDislikeStyle}\"\n                    >\n                    <div\n                      id=\"ryd-bar\"\n                      style=\"width: ${widthPercent}%; height: 100%${colorLikeStyle}\"\n                      ></div>\n                </div>\n              </div>\n              <tp-yt-paper-tooltip position=\"top\" id=\"ryd-dislike-tooltip\" class=\"style-scope ytd-sentiment-bar-renderer\" role=\"tooltip\" tabindex=\"-1\">\n                <!--css-build:shady-->${tooltipInnerHTML}\n              </tp-yt-paper-tooltip>\n              </div>\n      \t\t`,\n        );\n\n        if (isNewDesign()) {\n          // Add border between info and comments\n          let descriptionAndActionsElement = document.getElementById(\"top-row\");\n          descriptionAndActionsElement.style.borderBottom =\n            \"1px solid var(--yt-spec-10-percent-layer)\";\n          descriptionAndActionsElement.style.paddingBottom = \"10px\";\n\n          // Fix like/dislike ratio bar offset in new UI\n          document.getElementById(\"actions-inner\").style.width = \"revert\";\n          if (isRoundedDesign()) {\n            document.getElementById(\"actions\").style.flexDirection =\n              \"row-reverse\";\n          }\n        }\n      } else {\n        document.querySelector(`.ryd-tooltip`).style.width = widthPx + \"px\";\n        document.getElementById(\"ryd-bar\").style.width = widthPercent + \"%\";\n        document.querySelector(\"#ryd-dislike-tooltip > #tooltip\").innerHTML =\n          tooltipInnerHTML;\n        if (extConfig.coloredBar) {\n          document.getElementById(\"ryd-bar-container\").style.backgroundColor =\n            getColorFromTheme(false);\n          document.getElementById(\"ryd-bar\").style.backgroundColor =\n            getColorFromTheme(true);\n        }\n      }\n    }\n  } else {\n    console.log(\"removing bar\");\n    if (rateBar) {\n      rateBar.parentNode.removeChild(rateBar);\n    }\n  }\n}\n\nexport { createRateBar };\n"
  },
  {
    "path": "Extensions/combined/src/buttons.js",
    "content": "import { isMobile, isShorts, extConfig } from \"./state\";\nimport { isInViewport, querySelector, querySelectorAll } from \"./utils\";\n\nfunction getButtons() {\n  //---   If Watching Youtube Shorts:   ---//\n  if (isShorts()) {\n    let elements = isMobile()\n      ? querySelectorAll(extConfig.selectors.buttons.shorts.mobile)\n      : querySelectorAll(extConfig.selectors.buttons.shorts.desktop);\n\n    for (let element of elements) {\n      //YouTube Shorts can have multiple like/dislike buttons when scrolling through videos\n      //However, only one of them should be visible (no matter how you zoom)\n      if (isInViewport(element)) {\n        return element;\n      }\n    }\n\n    if (elements.length > 0) {\n      return elements[0];\n    }\n  }\n  //---   If Watching On Mobile:   ---//\n  if (isMobile()) {\n    return document.querySelector(extConfig.selectors.buttons.regular.mobile);\n  }\n  //---   If Menu Element Is Displayed:   ---//\n  if (querySelector(extConfig.selectors.menuContainer)?.offsetParent === null) {\n    return querySelector(extConfig.selectors.buttons.regular.desktopMenu);\n    //---   If Menu Element Isn't Displayed:   ---//\n  } else {\n    return querySelector(extConfig.selectors.buttons.regular.desktopNoMenu);\n  }\n}\n\nfunction getLikeButton() {\n  return getButtons().children[0].tagName ===\n    \"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER\"\n    ? querySelector(extConfig.selectors.buttons.likeButton.segmented) ??\n        querySelector(\n          extConfig.selectors.buttons.likeButton.segmentedGetButtons,\n          getButtons(),\n        )\n    : querySelector(\n        extConfig.selectors.buttons.likeButton.notSegmented,\n        getButtons(),\n      );\n}\n\nfunction getLikeTextContainer() {\n  return querySelector(extConfig.selectors.likeTextContainer, getLikeButton());\n}\n\nfunction getDislikeButton() {\n  if (\n    getButtons().children[0].tagName ===\n    \"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER\"\n  ) {\n    return (\n      querySelector(extConfig.selectors.buttons.dislikeButton.segmented) ??\n      querySelector(\n        extConfig.selectors.buttons.dislikeButton.segmentedGetButtons,\n        getButtons(),\n      )\n    );\n  }\n\n  const notSegmentedMatch = querySelector(\n    extConfig.selectors.buttons.dislikeButton.notSegmented,\n    getButtons(),\n  );\n\n  if (notSegmentedMatch != null) {\n    return notSegmentedMatch;\n  }\n\n  if (isShorts()) {\n    return querySelector([\"#dislike-button\"], getButtons());\n  }\n\n  return null;\n}\n\nfunction createDislikeTextContainer() {\n  const textNodeClone = (\n    getLikeButton().querySelector(\n      \".yt-spec-button-shape-next__button-text-content\",\n    ) ||\n    getLikeButton().querySelector(\"button > div[class*='cbox']\") ||\n    (\n      getLikeButton().querySelector('div > span[role=\"text\"]') ||\n      document.querySelector(\n        'button > div.yt-spec-button-shape-next__button-text-content > span[role=\"text\"]',\n      )\n    ).parentNode\n  ).cloneNode(true);\n  const insertPreChild = getDislikeButton().querySelector(\"button\");\n  insertPreChild.insertBefore(textNodeClone, null);\n  getDislikeButton()\n    .querySelector(\"button\")\n    .classList.remove(\"yt-spec-button-shape-next--icon-button\");\n  getDislikeButton()\n    .querySelector(\"button\")\n    .classList.add(\"yt-spec-button-shape-next--icon-leading\");\n  if (textNodeClone.querySelector(\"span[role='text']\") === null) {\n    const span = document.createElement(\"span\");\n    span.setAttribute(\"role\", \"text\");\n    while (textNodeClone.firstChild) {\n      textNodeClone.removeChild(textNodeClone.firstChild);\n    }\n    textNodeClone.appendChild(span);\n  }\n  textNodeClone.innerText = \"\";\n  return textNodeClone;\n}\n\nfunction getDislikeTextContainer() {\n  let result;\n  for (const selector of extConfig.selectors.dislikeTextContainer) {\n    result = getDislikeButton().querySelector(selector);\n    if (result !== null) {\n      break;\n    }\n  }\n  if (result == null) {\n    result = createDislikeTextContainer();\n  }\n  return result;\n}\n\nfunction checkForSignInButton() {\n  if (\n    document.querySelector(\n      \"a[href^='https://accounts.google.com/ServiceLogin']\",\n    )\n  ) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\nexport {\n  getButtons,\n  getLikeButton,\n  getDislikeButton,\n  getLikeTextContainer,\n  getDislikeTextContainer,\n  checkForSignInButton,\n};\n"
  },
  {
    "path": "Extensions/combined/src/changelog/index.js",
    "content": "import { config } from \"../config\";\nimport { getBrowser, localize } from \"../utils\";\n\nconst PATREON_JOIN_URL = \"https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008649\";\nconst SUPPORT_DOC_URL = config.links?.help ?? \"https://returnyoutubedislike.com/help\";\nconst COMMUNITY_URL = config.links?.discord ?? \"https://discord.gg/mYnESY4Md5\";\n\nexport function initChangelogPage() {\n  if (document.readyState === \"loading\") {\n    document.addEventListener(\"DOMContentLoaded\", setup);\n  } else {\n    setup();\n  }\n}\n\nfunction setup() {\n  applyLocaleMetadata();\n  localizeHtmlPage();\n  decorateScreenshotPlaceholders();\n  bindActions();\n}\n\nfunction applyLocaleMetadata() {\n  try {\n    const browserLocale = chrome?.i18n?.getMessage?.(\"@@ui_locale\");\n    if (browserLocale) {\n      document.documentElement.lang = browserLocale;\n    }\n  } catch (error) {\n    console.debug(\"Unable to resolve UI locale\", error);\n  }\n}\n\nfunction localizeHtmlPage() {\n  const elements = document.getElementsByTagName(\"html\");\n  for (let index = 0; index < elements.length; index += 1) {\n    const element = elements[index];\n    const original = element.innerHTML.toString();\n    const localized = original.replace(/__MSG_(\\w+)__/g, (match, key) => {\n      return key ? localize(key) : \"\";\n    });\n\n    if (localized !== original) {\n      element.innerHTML = localized;\n    }\n  }\n}\n\nfunction decorateScreenshotPlaceholders() {\n  document.querySelectorAll(\"[data-screenshot]\").forEach((wrapper) => {\n    const type = wrapper.getAttribute(\"data-screenshot\");\n    const labelKey = getPlaceholderLabelKey(type);\n    if (!labelKey) return;\n\n    const placeholder = wrapper.querySelector(\".ryd-feature-card__placeholder\");\n    if (!placeholder) return;\n\n    const label = localize(labelKey);\n    placeholder.setAttribute(\"role\", \"img\");\n    placeholder.setAttribute(\"aria-label\", label);\n    placeholder.title = label;\n  });\n}\n\nfunction getPlaceholderLabelKey(type) {\n  switch (type) {\n    case \"timeline\":\n      return \"changelog_screenshot_label_timeline\";\n    case \"map\":\n      return \"changelog_screenshot_label_map\";\n    case \"teaser\":\n      return \"changelog_screenshot_label_teaser\";\n    default:\n      return null;\n  }\n}\n\nfunction bindActions() {\n  const browser = getBrowser();\n\n  const upgradeButton = document.getElementById(\"ryd-changelog-upgrade\");\n  if (upgradeButton) {\n    upgradeButton.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      openExternal(PATREON_JOIN_URL, browser);\n    });\n  }\n\n  const supportButton = document.getElementById(\"ryd-changelog-support\");\n  if (supportButton) {\n    supportButton.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      openExternal(SUPPORT_DOC_URL, browser);\n    });\n  }\n\n  const contactButton = document.getElementById(\"ryd-changelog-contact\");\n  if (contactButton) {\n    contactButton.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      openExternal(COMMUNITY_URL, browser);\n    });\n  }\n}\n\nfunction openExternal(url, browser) {\n  if (!url) return;\n\n  try {\n    if (browser && browser.tabs && typeof browser.tabs.create === \"function\") {\n      browser.tabs.create({ url });\n      return;\n    }\n  } catch (error) {\n    console.debug(\"tabs.create unavailable, falling back\", error);\n  }\n\n  try {\n    window.open(url, \"_blank\", \"noopener\");\n  } catch (error) {\n    console.warn(\"Failed to open external url\", url, error);\n  }\n}\n"
  },
  {
    "path": "Extensions/combined/src/config.js",
    "content": "const DEV_API_URL = \"https://localhost:7258\";\nconst PROD_API_URL = \"https://returnyoutubedislikeapi.com\";\n\nconst runtime = typeof chrome !== \"undefined\" ? chrome.runtime : null;\nconst manifest = typeof runtime?.getManifest === \"function\" ? runtime.getManifest() : null;\nconst isDevelopment = !manifest || !(\"update_url\" in manifest);\n\nconst extensionChangelogUrl =\n  runtime && typeof runtime.getURL === \"function\"\n    ? runtime.getURL(\"changelog/4/changelog_4.0.html\")\n    : \"https://returnyoutubedislike.com/changelog/4/changelog_4.0.html\";\n\nconst config = {\n  apiUrl: isDevelopment ? DEV_API_URL : PROD_API_URL,\n\n  voteDisabledIconName: \"icon_hold128.png\",\n  defaultIconName: \"icon128.png\",\n\n  links: {\n    website: \"https://returnyoutubedislike.com\",\n    github: \"https://github.com/Anarios/return-youtube-dislike\",\n    discord: \"https://discord.gg/mYnESY4Md5\",\n    donate: \"https://returnyoutubedislike.com/donate\",\n    faq: \"https://returnyoutubedislike.com/faq\",\n    help: \"https://returnyoutubedislike.com/help\",\n    changelog: extensionChangelogUrl,\n  },\n\n  defaultExtConfig: {\n    disableVoteSubmission: false,\n    disableLogging: true,\n    coloredThumbs: false,\n    coloredBar: false,\n    colorTheme: \"classic\",\n    numberDisplayFormat: \"compactShort\",\n    numberDisplayReformatLikes: false,\n    hidePremiumTeaser: false,\n  },\n};\n\nfunction getApiUrl() {\n  return config.apiUrl;\n}\n\nfunction getApiEndpoint(endpoint) {\n  return `${config.apiUrl}${endpoint.startsWith(\"/\") ? \"\" : \"/\"}${endpoint}`;\n}\n\nfunction getChangelogUrl() {\n  return config.links?.changelog ?? extensionChangelogUrl;\n}\n\nexport { config, getApiUrl, getApiEndpoint, getChangelogUrl };\n"
  },
  {
    "path": "Extensions/combined/src/events.js",
    "content": "import {\n  getBrowser,\n  getVideoId,\n  numberFormat,\n  createObserver,\n} from \"./utils\";\nimport {\n  checkForSignInButton,\n  getButtons,\n  getDislikeButton,\n  getLikeButton,\n} from \"./buttons\";\nimport {\n  NEUTRAL_STATE,\n  LIKED_STATE,\n  DISLIKED_STATE,\n  setDislikes,\n  extConfig,\n  storedData,\n  setLikes,\n  getLikeCountFromButton,\n} from \"./state\";\nimport { createRateBar } from \"./bar\";\n\nfunction sendVote(vote) {\n  if (extConfig.disableVoteSubmission !== true) {\n    getBrowser().runtime.sendMessage({\n      message: \"send_vote\",\n      vote: vote,\n      videoId: getVideoId(window.location.href),\n    });\n  }\n}\n\nfunction updateDOMDislikes() {\n  setDislikes(numberFormat(storedData.dislikes));\n  createRateBar(storedData.likes, storedData.dislikes);\n}\n\nfunction likeClicked() {\n  if (checkForSignInButton() === false) {\n    if (storedData.previousState === DISLIKED_STATE) {\n      sendVote(1);\n      if (storedData.dislikes > 0) storedData.dislikes--;\n      storedData.likes++;\n      updateDOMDislikes();\n      storedData.previousState = LIKED_STATE;\n    } else if (storedData.previousState === NEUTRAL_STATE) {\n      sendVote(1);\n      storedData.likes++;\n      updateDOMDislikes();\n      storedData.previousState = LIKED_STATE;\n    } else if ((storedData.previousState = LIKED_STATE)) {\n      sendVote(0);\n      if (storedData.likes > 0) storedData.likes--;\n      updateDOMDislikes();\n      storedData.previousState = NEUTRAL_STATE;\n    }\n    if (extConfig.numberDisplayReformatLikes === true) {\n      const nativeLikes = getLikeCountFromButton();\n      if (nativeLikes !== false) {\n        setLikes(numberFormat(nativeLikes));\n      }\n    }\n  }\n}\n\nfunction dislikeClicked() {\n  if (checkForSignInButton() == false) {\n    if (storedData.previousState === NEUTRAL_STATE) {\n      sendVote(-1);\n      storedData.dislikes++;\n      updateDOMDislikes();\n      storedData.previousState = DISLIKED_STATE;\n    } else if (storedData.previousState === DISLIKED_STATE) {\n      sendVote(0);\n      if (storedData.dislikes > 0) storedData.dislikes--;\n      updateDOMDislikes();\n      storedData.previousState = NEUTRAL_STATE;\n    } else if (storedData.previousState === LIKED_STATE) {\n      sendVote(-1);\n      if (storedData.likes > 0) storedData.likes--;\n      storedData.dislikes++;\n      updateDOMDislikes();\n      storedData.previousState = DISLIKED_STATE;\n      if (extConfig.numberDisplayReformatLikes === true) {\n        const nativeLikes = getLikeCountFromButton();\n        if (nativeLikes !== false) {\n          setLikes(numberFormat(nativeLikes));\n        }\n      }\n    }\n  }\n}\n\nfunction addLikeDislikeEventListener() {\n  if (window.rydPreNavigateLikeButton !== getLikeButton()) {\n    getLikeButton().addEventListener(\"click\", likeClicked);\n    getLikeButton().addEventListener(\"touchstart\", likeClicked);\n    if (getDislikeButton()) {\n      getDislikeButton().addEventListener(\"click\", dislikeClicked);\n      getDislikeButton().addEventListener(\"touchstart\", dislikeClicked);\n      getDislikeButton().addEventListener(\"focusin\", updateDOMDislikes);\n      getDislikeButton().addEventListener(\"focusout\", updateDOMDislikes);\n    }\n    window.rydPreNavigateLikeButton = getLikeButton();\n  }\n}\n\nlet smartimationObserver = null;\n\nfunction createSmartimationObserver() {\n  if (!smartimationObserver) {\n    smartimationObserver = createObserver(\n      {\n        attributes: true,\n        subtree: true,\n        childList: true,\n      },\n      updateDOMDislikes,\n    );\n    smartimationObserver.container = null;\n  }\n\n  const smartimationContainer = getButtons().querySelector(\"yt-smartimation\");\n  if (\n    smartimationContainer &&\n    smartimationObserver.container != smartimationContainer\n  ) {\n    console.log(\"Initializing smartimation mutation observer\");\n    smartimationObserver.disconnect();\n    smartimationObserver.observe(smartimationContainer);\n    smartimationObserver.container = smartimationContainer;\n  }\n}\n\nfunction storageChangeHandler(changes, area) {\n  if (changes.disableVoteSubmission !== undefined) {\n    handleDisableVoteSubmissionChangeEvent(\n      changes.disableVoteSubmission.newValue,\n    );\n  }\n  if (changes.coloredThumbs !== undefined) {\n    handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);\n  }\n  if (changes.coloredBar !== undefined) {\n    handleColoredBarChangeEvent(changes.coloredBar.newValue);\n  }\n  if (changes.colorTheme !== undefined) {\n    handleColorThemeChangeEvent(changes.colorTheme.newValue);\n  }\n  if (changes.numberDisplayFormat !== undefined) {\n    handleNumberDisplayFormatChangeEvent(changes.numberDisplayFormat.newValue);\n  }\n  if (changes.numberDisplayReformatLikes !== undefined) {\n    handleNumberDisplayReformatLikesChangeEvent(\n      changes.numberDisplayReformatLikes.newValue,\n    );\n  }\n  if (changes.hidePremiumTeaser !== undefined) {\n    handleHidePremiumTeaserChangeEvent(\n      changes.hidePremiumTeaser.newValue,\n    );\n  }\n}\n\nfunction handleDisableVoteSubmissionChangeEvent(value) {\n  extConfig.disableVoteSubmission = value;\n}\n\nfunction handleColoredThumbsChangeEvent(value) {\n  extConfig.coloredThumbs = value;\n}\n\nfunction handleColoredBarChangeEvent(value) {\n  extConfig.coloredBar = value;\n}\n\nfunction handleColorThemeChangeEvent(value) {\n  if (!value) value = \"classic\";\n  extConfig.colorTheme = value;\n}\n\nfunction handleNumberDisplayFormatChangeEvent(value) {\n  extConfig.numberDisplayFormat = value;\n}\n\nfunction handleNumberDisplayReformatLikesChangeEvent(value) {\n  extConfig.numberDisplayReformatLikes = value;\n}\n\nfunction handleHidePremiumTeaserChangeEvent(value) {\n  extConfig.hidePremiumTeaser = value === true;\n}\n\nexport {\n  sendVote,\n  likeClicked,\n  dislikeClicked,\n  addLikeDislikeEventListener,\n  createSmartimationObserver,\n  storageChangeHandler,\n};\n"
  },
  {
    "path": "Extensions/combined/src/patreon.js",
    "content": "import { initPremiumAnalytics, teardownPremiumAnalytics, updatePremiumSession } from \"./premiumAnalytics\";\nimport {\n  initPremiumTeaser,\n  setTeaserSuppressed,\n  TEASER_SUPPRESSION_REASON_PREMIUM,\n} from \"./premiumAnalytics/teaser\";\n\nlet patreonState = {\n  authenticated: false,\n  user: null,\n  sessionToken: null,\n};\n\nfunction initPatreonFeatures() {\n  initPremiumTeaser();\n\n  chrome.storage.sync.get([\"patreonAuthenticated\", \"patreonUser\", \"patreonSessionToken\"], (data) => {\n    if (data.patreonAuthenticated && data.patreonUser) {\n      patreonState.authenticated = true;\n      patreonState.user = data.patreonUser;\n      patreonState.sessionToken = data.patreonSessionToken;\n      updatePremiumSession({\n        token: patreonState.sessionToken,\n        active: patreonState.user?.hasActiveMembership,\n        membershipTier: patreonState.user?.membershipTier,\n      });\n      enablePremiumFeatures();\n    }\n  });\n\n  chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n    if (request.message === \"patreon_status_changed\") {\n      patreonState.authenticated = request.authenticated;\n      patreonState.user = request.user || null;\n\n      if (request.authenticated) {\n        patreonState.sessionToken = request.sessionToken ?? patreonState.sessionToken;\n        updatePremiumSession({\n          token: patreonState.sessionToken,\n          active: patreonState.user?.hasActiveMembership,\n          membershipTier: patreonState.user?.membershipTier,\n        });\n        enablePremiumFeatures();\n      } else {\n        patreonState.sessionToken = null;\n        updatePremiumSession({ token: null, active: false });\n        disablePremiumFeatures();\n      }\n    }\n  });\n}\n\nfunction enablePremiumFeatures() {\n  const tier = patreonState.user?.membershipTier;\n  const hasActiveMembership = patreonState.user?.hasActiveMembership;\n\n  if (hasActiveMembership && tier === \"premium\") {\n    setTeaserSuppressed(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n    initPremiumAnalytics();\n  }\n}\n\nfunction disablePremiumFeatures() {\n  const premiumElements = document.querySelectorAll(\".ryd-premium-feature\");\n  premiumElements.forEach((el) => el.remove());\n  teardownPremiumAnalytics();\n  setTeaserSuppressed(false, TEASER_SUPPRESSION_REASON_PREMIUM);\n}\n\nfunction isPatreonUser() {\n  return patreonState.authenticated && patreonState.user?.hasActiveMembership;\n}\n\nfunction getPatreonTier() {\n  return patreonState.user?.membershipTier || \"none\";\n}\n\nexport { initPatreonFeatures, isPatreonUser, getPatreonTier, patreonState };\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/activity/index.js",
    "content": "import * as echarts from \"echarts\";\n\nimport { analyticsState } from \"../state\";\nimport { setActivityBucketLabel } from \"../panel\";\nimport { clampRangeToBounds, combineBounds, computeChartBounds, updateGlobalBounds } from \"./time\";\nimport { toEpoch, sanitizeCount } from \"../utils\";\nimport { localize } from \"../../utils\";\nimport { getTextColor, getMutedTextColor, getBorderColor } from \"../theme\";\nimport { logRangeSelection, logTimeBounds } from \"../logging\";\n\nlet activityTranslator = (key, substitutions) => localize(key, substitutions);\n\nexport function setActivityTranslator(translator) {\n  if (typeof translator === \"function\") {\n    activityTranslator = translator;\n  } else {\n    activityTranslator = (key, subs) => localize(key, subs);\n  }\n}\n\nexport function resetActivityTranslator() {\n  activityTranslator = (key, subs) => localize(key, subs);\n}\n\nfunction translateActivity(key, substitutions) {\n  try {\n    return activityTranslator(key, substitutions);\n  } catch (error) {\n    console.warn(\"Activity translation failed for\", key, error);\n    return localize(key, substitutions);\n  }\n}\n\nconst zoomListeners = new Set();\nconst MS_PER_MINUTE = 60 * 1000;\nconst MS_PER_HOUR = 60 * 60 * 1000;\nconst MS_PER_DAY = 24 * MS_PER_HOUR;\nconst MS_PER_WEEK = 7 * MS_PER_DAY;\n\nfunction pickFirstFinite(...values) {\n  for (const value of values) {\n    if (Number.isFinite(value)) {\n      return value;\n    }\n  }\n  return null;\n}\n\nexport function ensureActivityChart() {\n  const state = analyticsState;\n  if (!state.panelElement) return null;\n  if (!state.activityChart) {\n    const container = state.panelElement.querySelector(\"#ryd-analytics-activity\");\n    if (!container) return null;\n    state.activityChart = echarts.init(container);\n    state.activityChart.on(\"dataZoom\", handleDataZoom);\n  }\n  return state.activityChart;\n}\n\nexport function renderActivityChart(timeSeries) {\n  const state = analyticsState;\n  const activityChart = ensureActivityChart();\n  if (!activityChart) return;\n\n  const bucketLabel = timeSeries?.bucket;\n  const bucketMs = resolveBucketSize(bucketLabel) ?? state.latestBucketMs;\n  const bucketDescription = formatBucketDescription(bucketLabel, bucketMs);\n  const likesLabel = translateActivity(\"premiumAnalytics_modeLikes\");\n  const dislikesLabel = translateActivity(\"premiumAnalytics_modeDislikes\");\n\n  if (bucketDescription) {\n    state.latestBucketLabel = bucketDescription;\n    setActivityBucketLabel(bucketDescription);\n  } else {\n    state.latestBucketLabel = null;\n    setActivityBucketLabel(\"\");\n  }\n\n  const seriesPoints = normalizeSeriesPoints(timeSeries?.points ?? [], bucketMs);\n  const isSinglePoint = seriesPoints.length === 1;\n  const shouldShowSymbols = !isSinglePoint && seriesPoints.length <= 2;\n  const seriesType = isSinglePoint ? \"scatter\" : \"line\";\n  const symbol = isSinglePoint || shouldShowSymbols ? \"circle\" : \"none\";\n  const symbolSize = isSinglePoint ? 14 : shouldShowSymbols ? 8 : 0;\n  const likesSeries = seriesPoints.map((p) => [p.timestampUtc, p.likes]);\n  const dislikesSeries = seriesPoints.map((p) => [p.timestampUtc, p.dislikes]);\n\n  state.latestSeriesPoints = seriesPoints;\n  state.latestTimeAxis = seriesPoints.map((p) => toEpoch(p.timestampUtc)).filter((ms) => ms != null);\n  if (Number.isFinite(bucketMs) && bucketMs > 0) {\n    state.latestBucketMs = bucketMs;\n  } else if (state.latestTimeAxis.length > 1) {\n    state.latestBucketMs = Math.max(1, state.latestTimeAxis[1] - state.latestTimeAxis[0]);\n  }\n\n  const computedBounds = computeChartBounds(seriesPoints, state.latestTimeAxis, state.latestBucketMs);\n\n  const availableBounds = {\n    min: state.availableRange.min ?? toEpoch(timeSeries?.totalRangeStartUtc),\n    max: state.availableRange.max ?? toEpoch(timeSeries?.totalRangeEndUtc),\n  };\n\n  let sliderBounds = combineBounds(availableBounds, computedBounds);\n\n  if (Number.isFinite(computedBounds.min)) {\n    sliderBounds.min = Number.isFinite(sliderBounds.min)\n      ? Math.min(sliderBounds.min, computedBounds.min)\n      : computedBounds.min;\n  }\n\n  if (Number.isFinite(computedBounds.max)) {\n    sliderBounds.max = Number.isFinite(sliderBounds.max)\n      ? Math.max(sliderBounds.max, computedBounds.max)\n      : computedBounds.max;\n  }\n\n  if (Number.isFinite(sliderBounds.min) && Number.isFinite(sliderBounds.max) && sliderBounds.max <= sliderBounds.min) {\n    const fallbackMin = Number.isFinite(computedBounds.min) ? computedBounds.min : sliderBounds.min;\n    const fallbackMax = Number.isFinite(computedBounds.max) ? computedBounds.max : null;\n    if (Number.isFinite(fallbackMin) && Number.isFinite(fallbackMax) && fallbackMax > fallbackMin) {\n      sliderBounds = { min: fallbackMin, max: fallbackMax };\n    } else if (Number.isFinite(sliderBounds.min)) {\n      const span = Math.max(Number.isFinite(state.latestBucketMs) ? state.latestBucketMs : 0, MS_PER_MINUTE);\n      sliderBounds = {\n        min: sliderBounds.min,\n        max: sliderBounds.min + (span > 0 ? span : MS_PER_MINUTE),\n      };\n    }\n  }\n\n  let globalBounds = {\n    min: pickFirstFinite(availableBounds.min, sliderBounds.min, state.globalTimeBounds.min),\n    max: pickFirstFinite(availableBounds.max, sliderBounds.max, state.globalTimeBounds.max),\n  };\n\n  if (Number.isFinite(globalBounds.min) && Number.isFinite(globalBounds.max) && globalBounds.max <= globalBounds.min) {\n    if (Number.isFinite(sliderBounds.min) && Number.isFinite(sliderBounds.max) && sliderBounds.max > sliderBounds.min) {\n      globalBounds = { ...sliderBounds };\n    } else if (Number.isFinite(globalBounds.min)) {\n      const span = Math.max(Number.isFinite(state.latestBucketMs) ? state.latestBucketMs : 0, MS_PER_MINUTE);\n      globalBounds = {\n        min: globalBounds.min,\n        max: globalBounds.min + (span > 0 ? span : MS_PER_MINUTE),\n      };\n    }\n  }\n\n  state.globalTimeBounds = globalBounds;\n  updateGlobalBounds(state.globalTimeBounds);\n\n  state.chartTimeBounds = sliderBounds;\n  logTimeBounds(\"chart\", state.chartTimeBounds);\n  logTimeBounds(\"global\", state.globalTimeBounds);\n\n  const requestedSelection = {\n    from: state.selectionRange.from ?? toEpoch(timeSeries?.selectedRangeStartUtc),\n    to: state.selectionRange.to ?? toEpoch(timeSeries?.selectedRangeEndUtc),\n  };\n\n  let selectionBounds =\n    clampRangeToBounds(requestedSelection, sliderBounds) ??\n    (Number.isFinite(sliderBounds.min) && Number.isFinite(sliderBounds.max)\n      ? { from: sliderBounds.min, to: sliderBounds.max }\n      : null);\n\n  if (\n    selectionBounds &&\n    Number.isFinite(computedBounds.min) &&\n    selectionBounds.from != null &&\n    selectionBounds.from > computedBounds.min\n  ) {\n    selectionBounds = { ...selectionBounds, from: computedBounds.min };\n  }\n\n  if (selectionBounds) {\n    state.selectionRange = selectionBounds;\n    logRangeSelection(\"selection\", selectionBounds);\n  }\n\n  const axisBounds = {\n    min: pickFirstFinite(\n      sliderBounds.min,\n      computedBounds.min,\n      selectionBounds?.from,\n      state.latestTimeAxis[0],\n      Date.now() - state.latestBucketMs,\n    ),\n    max: pickFirstFinite(\n      sliderBounds.max,\n      computedBounds.max,\n      selectionBounds?.to,\n      state.latestTimeAxis[state.latestTimeAxis.length - 1],\n      Date.now(),\n    ),\n  };\n\n  const dataZoom = createDataZoom(sliderBounds, selectionBounds);\n\n  analyticsState.suppressZoomEvents = true;\n  activityChart.setOption({\n    backgroundColor: \"transparent\",\n    tooltip: { trigger: \"axis\" },\n    legend: { data: [likesLabel, dislikesLabel], textStyle: { color: getTextColor() } },\n    grid: { left: 40, right: 20, top: 30, bottom: 70 },\n    xAxis: {\n      type: \"time\",\n      min: axisBounds.min,\n      max: axisBounds.max,\n      axisLabel: { color: getMutedTextColor() },\n      axisLine: { lineStyle: { color: getBorderColor() } },\n    },\n    yAxis: {\n      type: \"value\",\n      axisLabel: { color: getMutedTextColor() },\n      splitLine: { lineStyle: { color: getBorderColor(0.35) } },\n    },\n    series: [\n      {\n        name: likesLabel,\n        type: seriesType,\n        smooth: seriesType === \"line\",\n        symbol,\n        symbolSize,\n        showSymbol: isSinglePoint || shouldShowSymbols,\n        itemStyle: { color: \"#55c759\" },\n        data: likesSeries,\n      },\n      {\n        name: dislikesLabel,\n        type: seriesType,\n        smooth: seriesType === \"line\",\n        symbol,\n        symbolSize,\n        showSymbol: isSinglePoint || shouldShowSymbols,\n        itemStyle: { color: \"#f87171\" },\n        data: dislikesSeries,\n      },\n    ],\n    dataZoom,\n  });\n  analyticsState.suppressZoomEvents = false;\n}\n\nexport function clearActivityChart() {\n  const chart = analyticsState.activityChart;\n  chart?.clear();\n}\n\nexport function resetChartZoom() {\n  const state = analyticsState;\n  const chart = state.activityChart;\n  if (!chart) return;\n  const bounds = {\n    min: state.globalTimeBounds.min ?? state.chartTimeBounds.min,\n    max: state.globalTimeBounds.max ?? state.chartTimeBounds.max,\n  };\n  const { min, max } = bounds;\n  if (min != null && max != null) {\n    analyticsState.suppressZoomEvents = true;\n    chart.dispatchAction({\n      type: \"dataZoom\",\n      startValue: min,\n      endValue: max,\n    });\n  } else {\n    analyticsState.suppressZoomEvents = true;\n    chart.dispatchAction({ type: \"dataZoom\", start: 0, end: 100 });\n  }\n}\n\nexport function resizeActivityChart() {\n  analyticsState.activityChart?.resize();\n}\n\nexport function disposeActivityChart() {\n  if (analyticsState.activityChart) {\n    analyticsState.activityChart.off?.(\"dataZoom\", handleDataZoom);\n    analyticsState.activityChart.dispose();\n    analyticsState.activityChart = null;\n  }\n}\n\nexport function registerZoomSelectionListener(listener) {\n  if (typeof listener === \"function\") {\n    zoomListeners.add(listener);\n  }\n}\n\nexport function unregisterZoomSelectionListener(listener) {\n  zoomListeners.delete(listener);\n}\n\nfunction createDataZoom(bounds, selection) {\n  const slider = {\n    type: \"slider\",\n    xAxisIndex: 0,\n    height: 18,\n    bottom: 20,\n    backgroundColor: \"rgba(255,255,255,0.05)\",\n    borderColor: \"rgba(255,255,255,0.1)\",\n    textStyle: { color: getMutedTextColor() },\n    handleStyle: { color: getTextColor(), borderColor: getBorderColor(0.6) },\n    realtime: true,\n  };\n\n  const inside = {\n    type: \"inside\",\n    xAxisIndex: 0,\n  };\n\n  const { min, max } = bounds;\n\n  if (min != null && max != null) {\n    slider.rangeMode = \"value\";\n    inside.rangeMode = \"value\";\n  }\n\n  if (selection && selection.from != null && selection.to != null) {\n    const clamped = clampRangeToBounds(selection, bounds);\n    if (clamped) {\n      slider.startValue = clamped.from;\n      slider.endValue = clamped.to;\n      inside.startValue = clamped.from;\n      inside.endValue = clamped.to;\n    }\n  } else if (min != null && max != null) {\n    slider.startValue = min;\n    slider.endValue = max;\n    inside.startValue = min;\n    inside.endValue = max;\n  }\n\n  return [slider, inside];\n}\n\nfunction resolveBucketSize(label) {\n  switch (label) {\n    case \"hour\":\n      return 60 * 60 * 1000;\n    case \"day\":\n      return 24 * 60 * 60 * 1000;\n    case \"week\":\n      return 7 * 24 * 60 * 60 * 1000;\n    default:\n      return null;\n  }\n}\n\nfunction formatBucketDescription(label, bucketMs) {\n  const normalizedLabel = typeof label === \"string\" ? label.toLowerCase() : null;\n\n  switch (normalizedLabel) {\n    case \"hour\":\n      return translateActivity(\"premiumAnalytics_bucketHour\");\n    case \"day\":\n      return translateActivity(\"premiumAnalytics_bucketDay\");\n    case \"week\":\n      return translateActivity(\"premiumAnalytics_bucketWeek\");\n    default:\n      break;\n  }\n\n  if (!Number.isFinite(bucketMs) || bucketMs <= 0) {\n    return null;\n  }\n\n  if (approximately(bucketMs, MS_PER_WEEK)) {\n    return translateActivity(\"premiumAnalytics_bucketWeek\");\n  }\n\n  if (bucketMs % MS_PER_DAY === 0) {\n    const days = bucketMs / MS_PER_DAY;\n    if (days === 1) return translateActivity(\"premiumAnalytics_bucketDay\");\n    return translateActivity(\"premiumAnalytics_bucketDays\", [formatBucketNumber(days)]);\n  }\n\n  if (bucketMs % MS_PER_HOUR === 0) {\n    const hours = bucketMs / MS_PER_HOUR;\n    if (hours === 1) return translateActivity(\"premiumAnalytics_bucketHour\");\n    return translateActivity(\"premiumAnalytics_bucketHours\", [formatBucketNumber(hours)]);\n  }\n\n  if (bucketMs % MS_PER_MINUTE === 0) {\n    const minutes = bucketMs / MS_PER_MINUTE;\n    if (minutes === 1) return translateActivity(\"premiumAnalytics_bucketMinute\");\n    return translateActivity(\"premiumAnalytics_bucketMinutes\", [formatBucketNumber(minutes)]);\n  }\n\n  const seconds = Math.round(bucketMs / 1000);\n  if (seconds === 1) return translateActivity(\"premiumAnalytics_bucketSecond\");\n  return translateActivity(\"premiumAnalytics_bucketSeconds\", [formatBucketNumber(seconds)]);\n}\n\nfunction formatBucketNumber(value) {\n  const numeric = Number(value);\n  return Number.isFinite(numeric) ? numeric.toLocaleString() : `${value}`;\n}\n\nfunction approximately(value, target) {\n  return Math.abs(value - target) < MS_PER_MINUTE;\n}\n\nfunction normalizeSeriesPoints(points, bucketMs) {\n  if (!Array.isArray(points) || !points.length) {\n    return [];\n  }\n\n  if (!Number.isFinite(bucketMs) || bucketMs <= 0) {\n    return [...points].sort((a, b) => toEpoch(a.timestampUtc) - toEpoch(b.timestampUtc));\n  }\n\n  const sorted = [...points]\n    .map((p) => ({\n      timestampUtc: p.timestampUtc,\n      likes: sanitizeCount(p.likes),\n      dislikes: sanitizeCount(p.dislikes),\n    }))\n    .filter((p) => toEpoch(p.timestampUtc) != null)\n    .sort((a, b) => toEpoch(a.timestampUtc) - toEpoch(b.timestampUtc));\n\n  if (!sorted.length) {\n    return [];\n  }\n\n  const filled = [];\n  for (let i = 0; i < sorted.length; i += 1) {\n    const current = sorted[i];\n    filled.push(current);\n    const currentMs = toEpoch(current.timestampUtc);\n    const next = sorted[i + 1];\n    if (!next) {\n      continue;\n    }\n    const nextMs = toEpoch(next.timestampUtc);\n    if (!Number.isFinite(currentMs) || !Number.isFinite(nextMs)) {\n      continue;\n    }\n\n    let cursor = currentMs + bucketMs;\n    while (cursor < nextMs) {\n      filled.push({\n        timestampUtc: new Date(cursor).toISOString(),\n        likes: 0,\n        dislikes: 0,\n      });\n      cursor += bucketMs;\n    }\n  }\n\n  return filled;\n}\n\nfunction handleDataZoom(event) {\n  if (analyticsState.suppressZoomEvents) {\n    analyticsState.suppressZoomEvents = false;\n    return;\n  }\n\n  const payload = Array.isArray(event?.batch) && event.batch.length ? event.batch[0] : event;\n  const bounds = analyticsState.chartTimeBounds;\n  const minBound = bounds?.min ?? analyticsState.availableRange.min;\n  const maxBound = bounds?.max ?? analyticsState.availableRange.max;\n\n  const startValue = resolveZoomValue(payload, \"startValue\", \"start\", minBound, maxBound);\n  const endValue = resolveZoomValue(payload, \"endValue\", \"end\", minBound, maxBound);\n\n  if (!Number.isFinite(startValue) || !Number.isFinite(endValue) || startValue === endValue) {\n    return;\n  }\n\n  const range = {\n    from: startValue,\n    to: endValue,\n    source: payload?.type ?? event?.type ?? \"dataZoom\",\n  };\n\n  zoomListeners.forEach((listener) => {\n    try {\n      listener(range);\n    } catch (error) {\n      console.error(\"premium analytics zoom listener failed\", error);\n    }\n  });\n}\n\nfunction resolveZoomValue(payload, valueKey, percentKey, minBound, maxBound) {\n  const direct = Number(payload?.[valueKey]);\n  if (Number.isFinite(direct)) {\n    return direct;\n  }\n\n  const percent = Number(payload?.[percentKey]);\n  if (!Number.isFinite(percent) || minBound == null || maxBound == null) {\n    return null;\n  }\n\n  const clampedPercent = Math.min(Math.max(percent, 0), 100);\n  const span = maxBound - minBound;\n  if (!Number.isFinite(span) || span <= 0) {\n    return null;\n  }\n\n  return minBound + (clampedPercent / 100) * span;\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/activity/index.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\njest.mock(\"echarts\", () => {\n  return {\n    init: jest.fn(() => {\n      const zrHandlers = {};\n      const chart = {\n        setOption: jest.fn(),\n        clear: jest.fn(),\n        resize: jest.fn(),\n        dispose: jest.fn(),\n        on: jest.fn(),\n        dispatchAction: jest.fn(),\n        getZr: () => ({\n          on: (event, handler) => {\n            zrHandlers[event] = handler;\n          },\n          trigger: (event) => zrHandlers[event]?.(),\n        }),\n      };\n      chart.__zrHandlers = zrHandlers;\n      return chart;\n    }),\n  };\n});\n\njest.mock(\"../theme\", () => ({\n  getTextColor: jest.fn(() => \"#111111\"),\n  getMutedTextColor: jest.fn(() => \"#222222\"),\n  getBorderColor: jest.fn(() => \"#333333\"),\n}));\n\njest.mock(\"../logging\", () => ({\n  logRangeSelection: jest.fn(),\n  logTimeBounds: jest.fn(),\n  logZoomPreview: jest.fn(),\n}));\n\nimport echarts from \"echarts\";\nimport { analyticsState, resetStateForVideo } from \"../state\";\nimport {\n  ensureActivityChart,\n  renderActivityChart,\n  clearActivityChart,\n  resetChartZoom,\n  resizeActivityChart,\n  disposeActivityChart,\n  registerZoomSelectionListener,\n  setActivityTranslator,\n  resetActivityTranslator,\n} from \"./index\";\nimport * as timeModule from \"./time\";\nimport * as logging from \"../logging\";\n\nconst enMessages = require(\"../../../_locales/en/messages.json\");\n\nfunction getMessage(key, substitutions) {\n  const entry = enMessages[key];\n  if (!entry) {\n    return key;\n  }\n  let message = entry.message ?? \"\";\n  if (substitutions == null) {\n    return message;\n  }\n  const values = Array.isArray(substitutions) ? substitutions : [substitutions];\n  values.forEach((value, index) => {\n    const replacement = value != null ? `${value}` : \"\";\n    message = message.replace(new RegExp(`\\\\$${index + 1}`, \"g\"), replacement);\n  });\n  return message;\n}\n\nfunction createPanel() {\n  const panel = document.createElement(\"section\");\n  const meta = document.createElement(\"div\");\n  meta.id = \"ryd-analytics-activity-meta\";\n  meta.className = \"ryd-analytics__chart-meta\";\n  meta.hidden = true;\n  const bucketLabel = document.createElement(\"span\");\n  bucketLabel.id = \"ryd-analytics-bucket-label\";\n  meta.appendChild(bucketLabel);\n  const chartHost = document.createElement(\"div\");\n  chartHost.id = \"ryd-analytics-activity\";\n  panel.appendChild(meta);\n  panel.appendChild(chartHost);\n  analyticsState.panelElement = panel;\n  document.body.appendChild(panel);\n  return panel;\n}\n\ndescribe(\"premiumAnalytics.activity\", () => {\n  let chartInstance;\n\n  beforeEach(() => {\n    global.chrome = {\n      i18n: {\n        getMessage,\n      },\n    };\n    setActivityTranslator((key, substitutions) => getMessage(key, substitutions));\n    document.body.innerHTML = \"\";\n    resetStateForVideo();\n    analyticsState.panelElement = null;\n    analyticsState.activityChart = null;\n    analyticsState.globalTimeBounds = { min: null, max: null };\n    echarts.init.mockImplementation(() => {\n      const zrHandlers = {};\n      chartInstance = {\n        setOption: jest.fn(),\n        clear: jest.fn(),\n        resize: jest.fn(),\n        dispose: jest.fn(),\n        on: jest.fn(),\n        dispatchAction: jest.fn(),\n        getZr: () => ({\n          on: (event, handler) => {\n            zrHandlers[event] = handler;\n          },\n          trigger: (event) => zrHandlers[event]?.(),\n        }),\n        __zrHandlers: zrHandlers,\n      };\n      return chartInstance;\n    });\n    createPanel();\n  });\n\n  afterEach(() => {\n    delete global.chrome;\n    resetActivityTranslator();\n    jest.restoreAllMocks();\n  });\n\n  it(\"initializes and caches the chart\", () => {\n    const chart = ensureActivityChart();\n\n    expect(echarts.init).toHaveBeenCalled();\n    expect(chart).toBe(chartInstance);\n    expect(ensureActivityChart()).toBe(chartInstance);\n    expect(chartInstance.on).toHaveBeenCalledWith(\"dataZoom\", expect.any(Function));\n  });\n\n  it(\"renders series data and updates state\", () => {\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 0, max: 1000 });\n\n    renderActivityChart({\n      bucket: \"hour\",\n      totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      totalRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      points: [\n        { timestampUtc: \"2025-01-01T12:00:00Z\", likes: 10, dislikes: 2 },\n        { timestampUtc: \"2025-01-01T13:00:00Z\", likes: 20, dislikes: 4 },\n      ],\n    });\n\n    expect(chartInstance.setOption).toHaveBeenCalled();\n    expect(logging.logRangeSelection).toHaveBeenCalled();\n    expect(analyticsState.latestSeriesPoints).toHaveLength(2);\n    expect(analyticsState.selectionRange.from).toBeLessThanOrEqual(analyticsState.selectionRange.to);\n\n  });\n\n  it(\"updates the bucket label when rendering\", () => {\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 0, max: 0 });\n    const meta = document.getElementById(\"ryd-analytics-activity-meta\");\n    const label = document.getElementById(\"ryd-analytics-bucket-label\");\n    expect(meta.hidden).toBe(true);\n    expect(label.textContent).toBe(\"\");\n\n    renderActivityChart({\n      bucket: \"day\",\n      points: [],\n    });\n\n    expect(meta.hidden).toBe(false);\n    expect(label.textContent).toBe(getMessage(\"premiumAnalytics_bucketDay\"));\n  });\n\n  it(\"fills missing buckets with zero counts\", () => {\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 0, max: 3 * 60 * 60 * 1000 });\n\n    renderActivityChart({\n      bucket: \"hour\",\n      points: [\n        { timestampUtc: \"2025-01-01T00:00:00Z\", likes: 5, dislikes: 1 },\n        { timestampUtc: \"2025-01-01T02:00:00Z\", likes: 2, dislikes: 0 },\n      ],\n    });\n\n    const options = chartInstance.setOption.mock.calls.at(-1)[0];\n    const likesSeries = options.series[0].data;\n    const dislikesSeries = options.series[1].data;\n\n    const hour0 = new Date(\"2025-01-01T00:00:00Z\").toISOString();\n    const hour1 = new Date(\"2025-01-01T01:00:00Z\").toISOString();\n    const hour2 = new Date(\"2025-01-01T02:00:00Z\").toISOString();\n\n    expect(likesSeries.map(([timestamp]) => new Date(timestamp).toISOString())).toEqual([hour0, hour1, hour2]);\n    expect(likesSeries.map(([, value]) => value)).toEqual([5, 0, 2]);\n\n    expect(dislikesSeries.map(([timestamp]) => new Date(timestamp).toISOString())).toEqual([hour0, hour1, hour2]);\n    expect(dislikesSeries.map(([, value]) => value)).toEqual([1, 0, 0]);\n\n    expect(analyticsState.latestSeriesPoints).toHaveLength(3);\n    expect(analyticsState.latestBucketMs).toBe(60 * 60 * 1000);\n    const latestOptions = chartInstance.setOption.mock.calls.at(-1)[0];\n    expect(latestOptions.series[0].type).toBe(\"line\");\n    expect(latestOptions.series[0].showSymbol).toBe(false);\n    expect(latestOptions.series[0].symbol).toBe(\"none\");\n  });\n\n  it(\"configures slider bounds from declared range\", () => {\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 100, max: 200 });\n    renderActivityChart({\n      totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      totalRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-01T12:00:00Z\",\n      points: [],\n    });\n\n    const chartOptions = chartInstance.setOption.mock.calls.at(-1)[0];\n    const slider = chartOptions.dataZoom[0];\n    expect(slider.startValue).toBeLessThan(slider.endValue);\n    expect(slider.startValue).toBe(new Date(\"2025-01-01T00:00:00Z\").getTime());\n    expect(slider.rangeMode).toBe(\"value\");\n  });\n\n  it(\"extends slider bounds to include bucket-aligned points before declared range\", () => {\n    jest.spyOn(timeModule, \"computeChartBounds\").mockRestore();\n    renderActivityChart({\n      bucket: \"day\",\n      totalRangeStartUtc: \"2025-01-01T06:00:00Z\",\n      totalRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T06:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      points: [{ timestampUtc: \"2025-01-01T00:00:00Z\", likes: 5, dislikes: 1 }],\n    });\n\n    const chartOptions = chartInstance.setOption.mock.calls.at(-1)[0];\n    const pointMs = new Date(\"2025-01-01T00:00:00Z\").getTime();\n    expect(chartOptions.xAxis.min).toBeLessThanOrEqual(pointMs);\n    expect(chartOptions.dataZoom[0].startValue).toBe(pointMs);\n    expect(analyticsState.chartTimeBounds.min).toBeLessThanOrEqual(pointMs);\n    expect(analyticsState.selectionRange.from).toBe(pointMs);\n  });\n\n  it(\"expands collapsed ranges for single-bucket day series\", () => {\n    renderActivityChart({\n      bucket: \"day\",\n      totalRangeStartUtc: \"2025-01-01T12:00:00Z\",\n      totalRangeEndUtc: \"2025-01-01T12:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T12:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-01T12:00:00Z\",\n      points: [{ timestampUtc: \"2025-01-01T12:00:00Z\", likes: 3, dislikes: 1 }],\n    });\n\n    const chartOptions = chartInstance.setOption.mock.calls.at(-1)[0];\n    const slider = chartOptions.dataZoom[0];\n    const inside = chartOptions.dataZoom[1];\n\n    expect(slider.startValue).toBeLessThan(slider.endValue);\n    expect(inside.startValue).toBeLessThan(inside.endValue);\n    expect(analyticsState.selectionRange.from).toBeLessThan(analyticsState.selectionRange.to);\n    expect(analyticsState.chartTimeBounds.min).toBeLessThan(analyticsState.chartTimeBounds.max);\n    expect(analyticsState.globalTimeBounds.min).toBeLessThan(analyticsState.globalTimeBounds.max);\n    expect(chartOptions.series[0].type).toBe(\"scatter\");\n    expect(chartOptions.series[0].showSymbol).toBe(true);\n    expect(chartOptions.series[0].symbol).toBe(\"circle\");\n    expect(chartOptions.series[0].symbolSize).toBe(14);\n    expect(chartOptions.series[1].type).toBe(\"scatter\");\n    expect(chartOptions.series[1].showSymbol).toBe(true);\n    expect(chartOptions.series[1].symbol).toBe(\"circle\");\n    expect(chartOptions.series[1].symbolSize).toBe(14);\n  });\n\n  it(\"derives bounds from data when range metadata is missing\", () => {\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 1_000, max: 2_000 });\n    renderActivityChart({\n      points: [],\n    });\n\n    const chartOptions = chartInstance.setOption.mock.calls.at(-1)[0];\n    const slider = chartOptions.dataZoom[0];\n    expect(slider.startValue).toBe(chartOptions.xAxis.min);\n    expect(slider.endValue).toBe(chartOptions.xAxis.max);\n    expect(slider.startValue).toBe(analyticsState.chartTimeBounds.min);\n    expect(slider.endValue).toBe(analyticsState.chartTimeBounds.max);\n    expect(Number.isFinite(slider.startValue)).toBe(true);\n  });\n\n  it(\"clamps selection to available bounds\", () => {\n    const availableMin = new Date(\"2025-01-01T00:00:00Z\").getTime();\n    const availableMax = new Date(\"2025-03-01T00:00:00Z\").getTime();\n    analyticsState.availableRange = { min: availableMin, max: availableMax };\n    renderActivityChart({\n      totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      totalRangeEndUtc: \"2025-03-01T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      selectedRangeEndUtc: \"2025-02-01T00:00:00Z\",\n      points: [],\n    });\n\n    expect(analyticsState.selectionRange.from).toBeGreaterThanOrEqual(availableMin);\n    expect(analyticsState.selectionRange.to).toBeLessThanOrEqual(availableMax);\n  });\n\n  it(\"keeps the slider bounds anchored to the total range after render\", () => {\n    const totalRangeStart = \"2025-01-01T00:00:00Z\";\n    const totalRangeEnd = \"2025-03-01T00:00:00Z\";\n    const selectedRangeStart = \"2025-02-01T00:00:00Z\";\n    const selectedRangeEnd = \"2025-02-15T00:00:00Z\";\n\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({\n      min: new Date(selectedRangeStart).getTime(),\n      max: new Date(selectedRangeEnd).getTime(),\n    });\n\n    renderActivityChart({\n      totalRangeStartUtc: totalRangeStart,\n      totalRangeEndUtc: totalRangeEnd,\n      selectedRangeStartUtc: selectedRangeStart,\n      selectedRangeEndUtc: selectedRangeEnd,\n      points: [\n        { timestampUtc: selectedRangeStart, likes: 1, dislikes: 0 },\n        { timestampUtc: selectedRangeEnd, likes: 2, dislikes: 1 },\n      ],\n    });\n\n    const chartOptions = chartInstance.setOption.mock.calls.at(-1)[0];\n    const slider = chartOptions.dataZoom[0];\n\n    expect(chartOptions.xAxis.min).toBe(new Date(totalRangeStart).getTime());\n    expect(chartOptions.xAxis.max).toBe(new Date(totalRangeEnd).getTime());\n    expect(slider.startValue).toBe(new Date(selectedRangeStart).getTime());\n    expect(slider.endValue).toBe(new Date(selectedRangeEnd).getTime());\n  });\n\n  it(\"clears the chart\", () => {\n    ensureActivityChart();\n    clearActivityChart();\n    expect(chartInstance.clear).toHaveBeenCalled();\n  });\n\n  it(\"resets zoom to global bounds\", () => {\n    ensureActivityChart();\n    analyticsState.globalTimeBounds = { min: 10, max: 20 };\n    analyticsState.chartTimeBounds = { min: 10, max: 20 };\n\n    resetChartZoom();\n    expect(chartInstance.dispatchAction).toHaveBeenCalledWith({ type: \"dataZoom\", startValue: 10, endValue: 20 });\n  });\n\n  it(\"resizes and disposes the chart\", () => {\n    ensureActivityChart();\n\n    resizeActivityChart();\n    expect(chartInstance.resize).toHaveBeenCalled();\n\n    disposeActivityChart();\n    expect(chartInstance.dispose).toHaveBeenCalled();\n    expect(analyticsState.activityChart).toBeNull();\n  });\n\n  it(\"notifies zoom listeners when selection changes\", () => {\n    const listener = jest.fn();\n    registerZoomSelectionListener(listener);\n    ensureActivityChart();\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 0, max: 1_000_000 });\n    renderActivityChart({\n      totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      totalRangeEndUtc: \"2025-02-01T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-05T00:00:00Z\",\n      points: [],\n    });\n\n    const handler = chartInstance.on.mock.calls.find(([event]) => event === \"dataZoom\")[1];\n    analyticsState.suppressZoomEvents = false;\n    handler({ startValue: 1000, endValue: 2000 });\n\n    expect(listener).toHaveBeenCalledWith(expect.objectContaining({ from: 1000, to: 2000 }));\n  });\n\n  it(\"ignores zoom events when suppression flag is set\", () => {\n    const listener = jest.fn();\n    registerZoomSelectionListener(listener);\n    ensureActivityChart();\n    renderActivityChart({\n      totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      totalRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-01T12:00:00Z\",\n      points: [],\n    });\n\n    const handler = chartInstance.on.mock.calls.find(([event]) => event === \"dataZoom\")[1];\n    analyticsState.suppressZoomEvents = true;\n    handler({ startValue: 100, endValue: 200 });\n\n    expect(listener).not.toHaveBeenCalled();\n    expect(analyticsState.suppressZoomEvents).toBe(false);\n  });\n\n  it(\"derives zoom values from percentage payloads\", () => {\n    const listener = jest.fn();\n    registerZoomSelectionListener(listener);\n    ensureActivityChart();\n    jest.spyOn(timeModule, \"computeChartBounds\").mockReturnValue({ min: 0, max: 1000 });\n    renderActivityChart({\n      totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      totalRangeEndUtc: \"2025-01-02T00:00:00Z\",\n      selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n      selectedRangeEndUtc: \"2025-01-01T12:00:00Z\",\n      points: [],\n    });\n\n    const handler = chartInstance.on.mock.calls.find(([event]) => event === \"dataZoom\")[1];\n    analyticsState.suppressZoomEvents = false;\n    handler({ start: 10, end: 60 });\n\n    expect(listener).toHaveBeenCalled();\n    const call = listener.mock.calls.at(-1)[0];\n    const bounds = analyticsState.chartTimeBounds;\n    const min = bounds.min;\n    const max = bounds.max;\n    const expectedFrom = min + 0.1 * (max - min);\n    const expectedTo = min + 0.6 * (max - min);\n    expect(call.from).toBeCloseTo(expectedFrom, 0);\n    expect(call.to).toBeCloseTo(expectedTo, 0);\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/activity/time.js",
    "content": "import { sanitizeCount, toEpoch } from \"../utils\";\nimport { analyticsState } from \"../state\";\n\nexport function computeChartBounds(points, timeline, bucketSizeMs) {\n  if (!timeline.length) {\n    const now = Date.now();\n    const fallbackBucket = Math.max(bucketSizeMs, 60 * 1000);\n    return { min: now - fallbackBucket, max: now };\n  }\n\n  const nonZeroTimes = points\n    .filter((p) => p && (sanitizeCount(p.likes) > 0 || sanitizeCount(p.dislikes) > 0))\n    .map((p) => toEpoch(p.timestampUtc))\n    .filter((ms) => ms != null);\n\n  const allTimes = timeline.filter((ms) => ms != null);\n\n  let min = nonZeroTimes.length ? Math.min(...nonZeroTimes) : Math.min(...allTimes);\n  let max = nonZeroTimes.length ? Math.max(...nonZeroTimes) : Math.max(...allTimes);\n\n  if (min === max) {\n    max = min + Math.max(bucketSizeMs, 60 * 1000);\n  }\n\n  return { min, max };\n}\n\nexport function updateGlobalBounds(bounds) {\n  const state = analyticsState;\n  if (bounds.min != null) {\n    state.globalTimeBounds.min = state.globalTimeBounds.min == null\n      ? bounds.min\n      : Math.min(state.globalTimeBounds.min, bounds.min);\n  }\n  if (bounds.max != null) {\n    state.globalTimeBounds.max = state.globalTimeBounds.max == null\n      ? bounds.max\n      : Math.max(state.globalTimeBounds.max, bounds.max);\n  }\n  if (state.globalTimeBounds.min == null && bounds.min != null) {\n    state.globalTimeBounds.min = bounds.min;\n  }\n  if (state.globalTimeBounds.max == null && bounds.max != null) {\n    state.globalTimeBounds.max = bounds.max;\n  }\n}\n\nexport function clampRangeToBounds(range, bounds) {\n  const { min, max } = bounds;\n  if (min == null || max == null) return null;\n\n  const startValue = Number.isFinite(range.from) ? range.from : min;\n  const endValue = Number.isFinite(range.to) ? range.to : max;\n\n  const clampedStart = Math.max(min, Math.min(max, startValue));\n  const clampedEnd = Math.max(min, Math.min(max, endValue));\n  if (clampedEnd <= clampedStart) {\n    return null;\n  }\n\n  return { from: clampedStart, to: clampedEnd };\n}\n\nexport function combineBounds(primary = {}, fallback = {}) {\n  const min = resolvePreferredLower(primary?.min, fallback?.min);\n  const max = resolvePreferredUpper(primary?.max, fallback?.max);\n  return { min, max };\n}\n\nfunction resolvePreferredLower(primary, fallback) {\n  if (Number.isFinite(primary)) {\n    return primary;\n  }\n  if (Number.isFinite(fallback)) {\n    return fallback;\n  }\n  return null;\n}\n\nfunction resolvePreferredUpper(primary, fallback) {\n  if (Number.isFinite(primary)) {\n    return primary;\n  }\n  if (Number.isFinite(fallback)) {\n    return fallback;\n  }\n  return null;\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/activity/time.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\nimport { analyticsState, resetStateForVideo } from \"../state\";\nimport {\n  computeChartBounds,\n  updateGlobalBounds,\n  clampRangeToBounds,\n  combineBounds,\n} from \"./time\";\n\nfunction initializeState() {\n  resetStateForVideo();\n  analyticsState.globalTimeBounds = { min: null, max: null };\n}\n\ndescribe(\"premiumAnalytics.time\", () => {\n  beforeEach(() => {\n    initializeState();\n  });\n\n  describe(\"computeChartBounds\", () => {\n    it(\"returns fallback when timeline empty\", () => {\n      const bounds = computeChartBounds([], [], 60 * 1000);\n      expect(bounds.max - bounds.min).toBeGreaterThan(0);\n    });\n\n    it(\"derives bounds from non-zero series\", () => {\n      const now = Date.now();\n      const points = [\n        { timestampUtc: new Date(now - 10_000).toISOString(), likes: 0, dislikes: 0 },\n        { timestampUtc: new Date(now - 5_000).toISOString(), likes: 3, dislikes: 1 },\n        { timestampUtc: new Date(now).toISOString(), likes: 4, dislikes: 0 },\n      ];\n      const timeline = [now - 10_000, now - 5_000, now];\n\n      const bounds = computeChartBounds(points, timeline, 1000);\n\n      expect(bounds.min).toBe(timeline[1]);\n      expect(bounds.max).toBe(timeline[2]);\n    });\n\n    it(\"expands identical bounds by bucket size\", () => {\n      const now = Date.now();\n      const points = [{ timestampUtc: new Date(now).toISOString(), likes: 5, dislikes: 0 }];\n      const bounds = computeChartBounds(points, [now], 2000);\n\n      expect(bounds.max - bounds.min).toBe(60 * 1000);\n    });\n  });\n\n  describe(\"updateGlobalBounds\", () => {\n    it(\"captures minimum and maximum values\", () => {\n      updateGlobalBounds({ min: 100, max: 200 });\n      expect(analyticsState.globalTimeBounds).toEqual({ min: 100, max: 200 });\n\n      updateGlobalBounds({ min: 50, max: 250 });\n      expect(analyticsState.globalTimeBounds).toEqual({ min: 50, max: 250 });\n    });\n  });\n\n  describe(\"clampRangeToBounds\", () => {\n    it(\"returns null when bounds incomplete\", () => {\n      expect(clampRangeToBounds({ from: 0, to: 1 }, { min: null, max: 10 })).toBeNull();\n    });\n\n    it(\"clamps values inside bounds\", () => {\n      const result = clampRangeToBounds({ from: -5, to: 15 }, { min: 0, max: 10 });\n      expect(result).toEqual({ from: 0, to: 10 });\n    });\n\n    it(\"rejects invalid ranges\", () => {\n      expect(clampRangeToBounds({ from: 5, to: 1 }, { min: 0, max: 10 })).toBeNull();\n    });\n  });\n\n  describe(\"combineBounds\", () => {\n    it(\"prefers primary values when finite\", () => {\n      expect(combineBounds({ min: 1, max: 5 }, { min: 2, max: 6 })).toEqual({ min: 1, max: 5 });\n    });\n\n    it(\"falls back to secondary when primary missing\", () => {\n      expect(combineBounds({ min: null, max: null }, { min: 4, max: 8 })).toEqual({ min: 4, max: 8 });\n    });\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/constants.js",
    "content": "const MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst EXPANDABLE_SECTIONS = new Set([\"activity\", \"map\", \"lists\"]);\n\nexport { MS_PER_DAY, EXPANDABLE_SECTIONS };\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/index.js",
    "content": "export { initPremiumAnalytics, teardownPremiumAnalytics, updatePremiumSession } from \"./lifecycle\";\nexport { requestAnalytics } from \"./requests\";\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/index.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\njest.mock(\"../config\", () => ({\n  __esModule: true,\n  getApiEndpoint: jest.fn((path) => `https://api.example${path}`),\n}));\n\njest.mock(\"./panel\", () => ({\n  configurePanelCallbacks: jest.fn(),\n  ensurePanel: jest.fn(() => ({})),\n  updateRangeButtons: jest.fn(),\n  updateRangeAnchorButtons: jest.fn(),\n  updateModeButtons: jest.fn(),\n  setListsLoading: jest.fn(),\n  renderSummary: jest.fn(),\n  setFooterMessage: jest.fn(),\n  setLoadingState: jest.fn(),\n  showPanel: jest.fn(),\n  hidePanel: jest.fn(),\n  togglePanel: jest.fn(),\n  applyChartExpansionState: jest.fn(),\n}));\n\njest.mock(\"./activity\", () => ({\n  renderActivityChart: jest.fn(),\n  clearActivityChart: jest.fn(),\n  resetChartZoom: jest.fn(),\n  resizeActivityChart: jest.fn(),\n  disposeActivityChart: jest.fn(),\n  registerZoomSelectionListener: jest.fn(),\n}));\n\njest.mock(\"./map\", () => ({\n  ensureMapChart: jest.fn(),\n  renderMap: jest.fn(),\n  clearMapChart: jest.fn(),\n  resizeMapChart: jest.fn(),\n  disposeMapChart: jest.fn(),\n}));\n\njest.mock(\"./lists\", () => ({\n  updateCountryList: jest.fn(),\n}));\n\njest.mock(\"./teaser\", () => ({\n  setTeaserSuppressed: jest.fn(),\n  TEASER_SUPPRESSION_REASON_PREMIUM: \"premium\",\n}));\n\njest.mock(\"./tierNotice\", () => ({\n  showTierNotice: jest.fn(),\n  hideTierNotice: jest.fn(),\n}));\n\njest.mock(\"./utils\", () => {\n  const actual = jest.requireActual(\"./utils\");\n  return {\n    __esModule: true,\n    ...actual,\n    debounce: jest.fn((fn) => fn),\n    safeJson: jest.fn(async () => ({ error: \"membership_inactive\" })),\n    toEpoch: jest.fn(actual.toEpoch),\n  };\n});\n\njest.mock(\"./logging\", () => ({\n  logFetchRequest: jest.fn(),\n}));\n\nconst panelMocks = jest.requireMock(\"./panel\");\nconst activityMocks = jest.requireMock(\"./activity\");\nconst mapMocks = jest.requireMock(\"./map\");\nconst listsMocks = jest.requireMock(\"./lists\");\nconst utilsMocks = jest.requireMock(\"./utils\");\nconst loggingMocks = jest.requireMock(\"./logging\");\nconst configMocks = jest.requireMock(\"../config\");\n\nconst {\n  configurePanelCallbacks: mockConfigurePanelCallbacks,\n  ensurePanel: mockEnsurePanel,\n  updateRangeButtons: mockUpdateRangeButtons,\n  updateModeButtons: mockUpdateModeButtons,\n  renderSummary: mockRenderSummary,\n  setFooterMessage: mockSetFooterMessage,\n  setLoadingState: mockSetLoadingState,\n  applyChartExpansionState: mockApplyChartExpansionState,\n} = panelMocks;\n\nconst { updateCountryList: mockUpdateCountryList } = listsMocks;\n\nconst {\n  renderActivityChart: mockRenderActivityChart,\n  resetChartZoom: mockResetChartZoom,\n  resizeActivityChart: mockResizeActivityChart,\n  disposeActivityChart: mockDisposeActivityChart,\n  registerZoomSelectionListener: mockRegisterZoomSelectionListener,\n} = activityMocks;\n\nconst {\n  ensureMapChart: mockEnsureMapChart,\n  renderMap: mockRenderMap,\n  resizeMapChart: mockResizeMapChart,\n  disposeMapChart: mockDisposeMapChart,\n} = mapMocks;\n\nconst { debounce: mockDebounce, safeJson: mockSafeJson, toEpoch: mockToEpoch } = utilsMocks;\n\nconst { logFetchRequest: mockLogFetchRequest } = loggingMocks;\nconst { getApiEndpoint: mockGetApiEndpoint } = configMocks;\nconst {\n  setTeaserSuppressed: mockSetTeaserSuppressed,\n  TEASER_SUPPRESSION_REASON_PREMIUM,\n} = jest.requireMock(\"./teaser\");\nconst { showTierNotice: mockShowTierNotice, hideTierNotice: mockHideTierNotice } = jest.requireMock(\"./tierNotice\");\n\njest.mock(\"../utils\", () => {\n  const actual = jest.requireActual(\"../utils\");\n  return {\n    ...actual,\n    getVideoId: jest.fn(() => \"video1234567\"),\n  };\n});\n\nimport { analyticsState, resetSessionState, resetStateForVideo } from \"./state\";\nimport {\n  initPremiumAnalytics,\n  requestAnalytics,\n  teardownPremiumAnalytics,\n  updatePremiumSession,\n} from \"./index\";\nconst premiumAnalyticsModule = require(\"./index\");\n\nconst flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));\n\nconst enMessages = require(\"../../_locales/en/messages.json\");\n\nfunction getMessage(key, substitutions) {\n  const entry = enMessages[key];\n  if (!entry) {\n    return key;\n  }\n  let message = entry.message ?? \"\";\n  if (substitutions == null) {\n    return message;\n  }\n  const values = Array.isArray(substitutions) ? substitutions : [substitutions];\n  values.forEach((value, index) => {\n    const replacement = value != null ? `${value}` : \"\";\n    message = message.replace(new RegExp(`\\\\$${index + 1}`, \"g\"), replacement);\n  });\n  return message;\n}\n\ndescribe(\"premiumAnalytics\", () => {\n  beforeEach(() => {\n    jest.clearAllMocks();\n    resetStateForVideo();\n    resetSessionState();\n    analyticsState.sessionToken = \"token\";\n    analyticsState.sessionActive = true;\n    analyticsState.membershipTier = \"premium\";\n    analyticsState.currentRange = 7;\n    analyticsState.rangeAnchor = \"first\";\n    analyticsState.currentVideoId = \"video1234567\";\n    analyticsState.zoomListenerRegistered = false;\n    analyticsState.initialized = false;\n    global.chrome = {\n      i18n: {\n        getMessage,\n      },\n    };\n    global.fetch = jest.fn(() =>\n      Promise.resolve({\n        ok: true,\n        json: () =>\n          Promise.resolve({\n            timeSeries: {\n              points: [],\n              totalRangeStartUtc: \"2025-01-01T00:00:00Z\",\n              totalRangeEndUtc: \"2025-01-10T00:00:00Z\",\n              selectedRangeStartUtc: \"2025-01-01T00:00:00Z\",\n              selectedRangeEndUtc: \"2025-01-07T00:00:00Z\",\n            },\n            geo: { topLikes: [], topDislikes: [], countries: [] },\n            summary: {\n              totalLikes: 10,\n              totalDislikes: 5,\n              uniqueIps: 3,\n              countriesRepresented: 2,\n              peakLikes: { timestampUtc: \"2025-01-01T00:00:00Z\", count: 5 },\n              peakDislikes: { timestampUtc: \"2025-01-01T01:00:00Z\", count: 2 },\n            },\n          }),\n      }),\n    );\n    mockEnsurePanel.mockReturnValue({\n      querySelector: jest.fn(() => ({\n        innerHTML: \"\",\n        querySelector: jest.fn(),\n      })),\n    });\n  });\n\n  afterEach(() => {\n    delete global.fetch;\n    delete global.chrome;\n  });\n\n  it(\"initializes only once and wires listeners\", () => {\n    initPremiumAnalytics();\n    expect(mockConfigurePanelCallbacks).toHaveBeenCalled();\n    expect(mockRegisterZoomSelectionListener).toHaveBeenCalledTimes(1);\n    const listener = mockRegisterZoomSelectionListener.mock.calls.at(-1)?.[0];\n    expect(typeof listener).toBe(\"function\");\n\n    const callbacks = mockConfigurePanelCallbacks.mock.calls.at(-1)?.[0];\n    expect(typeof callbacks?.onChartExpand).toBe(\"function\");\n\n    initPremiumAnalytics();\n    expect(mockConfigurePanelCallbacks).toHaveBeenCalledTimes(1);\n    expect(mockRegisterZoomSelectionListener).toHaveBeenCalledTimes(1);\n  });\n\n  it(\"requests analytics and renders results\", async () => {\n    requestAnalytics();\n\n    await flushPromises();\n\n    expect(fetch).toHaveBeenCalled();\n    expect(mockRenderActivityChart).toHaveBeenCalled();\n    expect(mockUpdateCountryList).toHaveBeenCalled();\n    expect(mockRenderSummary).toHaveBeenCalled();\n    expect(mockSetLoadingState).toHaveBeenCalledWith(true);\n    expect(mockSetLoadingState).toHaveBeenCalledWith(false);\n    expect(mockToEpoch).toHaveBeenCalledWith(\"2025-01-01T00:00:00Z\");\n    expect(mockToEpoch).toHaveBeenCalledWith(\"2025-01-07T00:00:00Z\");\n  });\n\n  it(\"handles error responses gracefully\", async () => {\n    global.fetch = jest.fn(() =>\n      Promise.resolve({\n        ok: false,\n        status: 403,\n      }),\n    );\n\n    requestAnalytics();\n    await flushPromises();\n\n    expect(mockSetFooterMessage).toHaveBeenCalledWith(getMessage(\"premiumAnalytics_errorInactive\"));\n    expect(mockSetLoadingState).toHaveBeenCalledWith(true);\n    expect(mockSetLoadingState).toHaveBeenCalledWith(false);\n  });\n\n  it(\"tears down listeners and disposes charts\", () => {\n    initPremiumAnalytics();\n    teardownPremiumAnalytics();\n\n    expect(mockDisposeActivityChart).toHaveBeenCalled();\n    expect(mockDisposeMapChart).toHaveBeenCalled();\n    expect(analyticsState.initialized).toBe(false);\n  });\n\n  it(\"updates session state and triggers refresh\", async () => {\n    const initialFetchCalls = global.fetch.mock.calls.length;\n\n    updatePremiumSession({ token: \"new\", active: true, membershipTier: \"premium\" });\n    await flushPromises();\n\n    expect(analyticsState.sessionToken).toBe(\"new\");\n    expect(global.fetch.mock.calls.length).toBeGreaterThan(initialFetchCalls);\n    expect(mockSetLoadingState).toHaveBeenCalledWith(true);\n    expect(mockSetTeaserSuppressed).toHaveBeenCalledWith(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n    expect(mockHideTierNotice).toHaveBeenCalled();\n\n    updatePremiumSession({ token: null, active: false });\n    expect(mockDisposeActivityChart).toHaveBeenCalled();\n    expect(mockSetLoadingState).toHaveBeenCalledWith(false);\n    expect(mockSetTeaserSuppressed).toHaveBeenCalledWith(false, TEASER_SUPPRESSION_REASON_PREMIUM);\n    expect(mockHideTierNotice).toHaveBeenCalled();\n  });\n\n  it(\"avoids duplicate fetch when session details unchanged\", async () => {\n    const initialFetchCalls = global.fetch.mock.calls.length;\n\n    updatePremiumSession({ token: analyticsState.sessionToken, active: true, membershipTier: \"premium\" });\n    await flushPromises();\n\n    expect(global.fetch.mock.calls.length).toBe(initialFetchCalls);\n  });\n\n  it(\"shows tier notice when membership is not premium\", () => {\n    updatePremiumSession({ token: \"token\", active: true, membershipTier: \"supporter\" });\n\n    expect(mockShowTierNotice).toHaveBeenCalled();\n    expect(mockEnsurePanel).not.toHaveBeenCalled();\n    expect(mockSetTeaserSuppressed).toHaveBeenCalledWith(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n  });\n\n  it(\"sends the selected range in days without explicit timestamps\", async () => {\n    analyticsState.currentRange = 30;\n\n    requestAnalytics();\n\n    await flushPromises();\n\n    expect(mockLogFetchRequest).toHaveBeenCalled();\n    const [, params] = mockLogFetchRequest.mock.calls.at(-1);\n    expect(params.get(\"rangeDays\")).toBe(\"30\");\n    expect(params.get(\"rangeAnchor\")).toBe(\"first\");\n    expect(params.has(\"selectedRangeStartUtc\")).toBe(false);\n    expect(params.has(\"selectedRangeEndUtc\")).toBe(false);\n  });\n\n  it(\"supports all-time range by passing zero days\", async () => {\n    analyticsState.currentRange = 0;\n\n    requestAnalytics();\n\n    await flushPromises();\n\n    const [, params] = mockLogFetchRequest.mock.calls.at(-1);\n    expect(params.get(\"rangeDays\")).toBe(\"0\");\n    expect(params.get(\"rangeAnchor\")).toBe(\"first\");\n  });\n\n  it(\"includes the selected range anchor when requesting presets\", async () => {\n    analyticsState.currentRange = 90;\n    analyticsState.rangeAnchor = \"last\";\n\n    requestAnalytics();\n\n    await flushPromises();\n\n    const [, params] = mockLogFetchRequest.mock.calls.at(-1);\n    expect(params.get(\"rangeDays\")).toBe(\"90\");\n    expect(params.get(\"rangeAnchor\")).toBe(\"last\");\n  });\n\n  it(\"keeps the all-time preset anchored to the full history\", async () => {\n    analyticsState.currentRange = 0;\n    analyticsState.rangeAnchor = \"first\";\n    mockLogFetchRequest.mockClear();\n\n    const longRangeResponse = {\n      timeSeries: {\n        points: [],\n        totalRangeStartUtc: \"2024-01-01T00:00:00Z\",\n        totalRangeEndUtc: \"2024-12-31T00:00:00Z\",\n        selectedRangeStartUtc: \"2024-01-01T00:00:00Z\",\n        selectedRangeEndUtc: \"2024-12-31T00:00:00Z\",\n      },\n      geo: { topLikes: [], topDislikes: [], countries: [], subdivisions: [] },\n      summary: {\n        totalLikes: 0,\n        totalDislikes: 0,\n        uniqueIps: 0,\n        countriesRepresented: 0,\n        peakLikes: { timestampUtc: \"2024-01-01T00:00:00Z\", count: 0 },\n        peakDislikes: { timestampUtc: \"2024-01-01T00:00:00Z\", count: 0 },\n      },\n    };\n\n    global.fetch.mockImplementationOnce(() =>\n      Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(longRangeResponse),\n      }),\n    );\n\n    requestAnalytics();\n    await flushPromises();\n\n    expect(analyticsState.currentRange).toBe(0);\n\n    mockLogFetchRequest.mockClear();\n    analyticsState.rangeAnchor = \"last\";\n\n    requestAnalytics();\n    await flushPromises();\n\n    expect(analyticsState.currentRange).toBe(0);\n    expect(mockLogFetchRequest).toHaveBeenCalled();\n    const [, params] = mockLogFetchRequest.mock.calls.at(-1);\n    expect(params.get(\"rangeDays\")).toBe(\"0\");\n    expect(params.get(\"rangeAnchor\")).toBe(\"last\");\n  });\n\n  it(\"reuses existing session data when refreshing analytics\", async () => {\n    requestAnalytics();\n    await flushPromises();\n    const firstUrl = fetch.mock.calls[0][0];\n\n    requestAnalytics();\n    await flushPromises();\n\n    const secondUrl = fetch.mock.calls[fetch.mock.calls.length - 1][0];\n    expect(firstUrl).toBe(secondUrl);\n  });\n\n  it(\"defers chart zoom resets until preset data arrives\", () => {\n    initPremiumAnalytics();\n    const callbacks = mockConfigurePanelCallbacks.mock.calls.at(-1)?.[0];\n    expect(typeof callbacks?.onRangePreset).toBe(\"function\");\n\n    mockResetChartZoom.mockClear();\n    analyticsState.currentRange = 30;\n\n    callbacks.onRangePreset(7);\n\n    expect(mockResetChartZoom).not.toHaveBeenCalled();\n  });\n\n  it(\"toggles chart expansion via callback\", () => {\n    initPremiumAnalytics();\n    const callbacks = mockConfigurePanelCallbacks.mock.calls.at(-1)?.[0];\n    const expand = callbacks.onChartExpand;\n\n    analyticsState.latestCountries = [{ countryCode: \"US\", likes: 10, dislikes: 4 }];\n\n    mockApplyChartExpansionState.mockClear();\n    mockResizeActivityChart.mockClear();\n    mockResizeMapChart.mockClear();\n    mockRenderMap.mockClear();\n\n    expand(\"activity\");\n    expect(analyticsState.expandedChart).toBe(\"activity\");\n    expect(mockApplyChartExpansionState).toHaveBeenCalled();\n    expect(mockResizeActivityChart).toHaveBeenCalledTimes(1);\n    expect(mockResizeMapChart).not.toHaveBeenCalled();\n    expect(mockRenderMap).not.toHaveBeenCalled();\n\n    mockApplyChartExpansionState.mockClear();\n    expand(\"map\");\n    expect(analyticsState.expandedChart).toBe(\"map\");\n    expect(mockResizeMapChart).toHaveBeenCalledTimes(1);\n    expect(mockResizeActivityChart).toHaveBeenCalledTimes(2);\n    expect(mockRenderMap).toHaveBeenCalledTimes(1);\n\n    mockRenderMap.mockClear();\n    expand(\"map\");\n    expect(analyticsState.expandedChart).toBeNull();\n    expect(mockRenderMap).toHaveBeenCalledTimes(1);\n\n    mockRenderMap.mockClear();\n    expand(\"lists\");\n    expect(analyticsState.expandedChart).toBe(\"lists\");\n    expect(mockRenderMap).not.toHaveBeenCalled();\n  });\n\n  it(\"re-renders map when switching modes\", () => {\n    initPremiumAnalytics();\n    const callbacks = mockConfigurePanelCallbacks.mock.calls.at(-1)?.[0];\n    const expand = callbacks.onChartExpand;\n    const changeMode = callbacks.onModeChange;\n\n    analyticsState.latestCountries = [{ countryCode: \"US\", likes: 10, dislikes: 4 }];\n    analyticsState.currentMode = \"ratio\";\n\n    expand(\"map\");\n    mockRenderMap.mockClear();\n\n    [\"likes\", \"dislikes\", \"ratio\"].forEach((mode) => {\n      expect(() => changeMode(mode)).not.toThrow();\n      expect(analyticsState.currentMode).toBe(mode);\n      expect(mockRenderMap).toHaveBeenCalledTimes(1);\n      mockRenderMap.mockClear();\n    });\n  });\n\n  it(\"refetches analytics when zoom selection changes\", async () => {\n    initPremiumAnalytics();\n    requestAnalytics();\n    await flushPromises();\n\n    const previousKey = analyticsState.activeRequestKey;\n\n    const from = Date.UTC(2025, 0, 2);\n    const to = Date.UTC(2025, 0, 5);\n    analyticsState.sessionActive = true;\n    analyticsState.sessionToken = \"token\";\n    analyticsState.currentVideoId = \"video1234567\";\n    const initialApiCalls = mockGetApiEndpoint.mock.calls.length;\n\n    requestAnalytics({ selection: { from, to } });\n    await flushPromises();\n\n    expect(mockGetApiEndpoint.mock.calls.length).toBeGreaterThan(initialApiCalls);\n    const lastCall = mockGetApiEndpoint.mock.calls.at(-1)?.[0];\n    expect(lastCall).toContain(encodeURIComponent(new Date(from).toISOString()));\n    expect(lastCall).toContain(encodeURIComponent(new Date(to).toISOString()));\n    expect(analyticsState.activeRequestKey).not.toBe(previousKey);\n    expect(analyticsState.activeRequestKey).toContain(new Date(from).toISOString());\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/lifecycle.js",
    "content": "import { analyticsState, resetStateForVideo, setSession } from \"./state\";\nimport {\n  configurePanelCallbacks,\n  ensurePanel,\n  updateRangeButtons,\n  updateRangeAnchorButtons,\n  updateModeButtons,\n  applyChartExpansionState,\n} from \"./panel\";\nimport { resizeActivityChart, disposeActivityChart, registerZoomSelectionListener } from \"./activity\";\nimport { renderMap, resizeMapChart, disposeMapChart } from \"./map\";\nimport { debounce } from \"./utils\";\nimport { getVideoId } from \"../utils\";\n\nimport { MS_PER_DAY, EXPANDABLE_SECTIONS } from \"./constants\";\nimport { requestAnalytics, scheduleSelectionFetch, normalizeSelection } from \"./requests\";\nimport { setTeaserSuppressed, TEASER_SUPPRESSION_REASON_PREMIUM } from \"./teaser\";\nimport { showTierNotice, hideTierNotice } from \"./tierNotice\";\n\nlet resizeListener = null;\n\nfunction initPremiumAnalytics() {\n  if (analyticsState.initialized) return;\n  analyticsState.initialized = true;\n\n  if (!analyticsState.zoomListenerRegistered) {\n    registerZoomSelectionListener(handleChartSelection);\n    analyticsState.zoomListenerRegistered = true;\n  }\n\n  configurePanelCallbacks({\n    onRangePreset: handleRangePreset,\n    onRangeAnchorChange: handleRangeAnchorChange,\n    onModeChange: handleModeChange,\n    onChartExpand: handleChartExpand,\n  });\n\n  document.addEventListener(\"yt-navigate-finish\", handleNavigation, { passive: true });\n  resizeListener = resizeListener || debounce(resizeCharts, 200);\n  window.addEventListener(\"resize\", resizeListener);\n\n  handleNavigation();\n}\n\nfunction handleNavigation() {\n  const videoId = resolveVideoId();\n  if (!videoId) {\n    teardownPanel();\n    return;\n  }\n\n  if (videoId === analyticsState.currentVideoId) {\n    ensurePanel();\n    return;\n  }\n\n  analyticsState.currentVideoId = videoId;\n  resetStateForVideo();\n  applyChartExpansionState();\n  if (analyticsState.initialized) {\n    ensurePanel();\n  }\n  requestAnalytics();\n}\n\nfunction handleRangePreset(rangeDays) {\n  const state = analyticsState;\n  const samePreset = rangeDays === state.currentRange;\n  if (samePreset) {\n    return;\n  }\n  state.usingCustomRange = false;\n  state.customSelection = null;\n  state.selectionRange = { from: null, to: null };\n  state.currentRange = rangeDays;\n  updateRangeButtons();\n  updateRangeAnchorButtons();\n  requestAnalytics();\n}\n\nfunction handleRangeAnchorChange(anchor) {\n  const normalized = anchor === \"last\" ? \"last\" : \"first\";\n  if (analyticsState.rangeAnchor === normalized) {\n    return;\n  }\n  analyticsState.rangeAnchor = normalized;\n  analyticsState.usingCustomRange = false;\n  analyticsState.customSelection = null;\n  analyticsState.selectionRange = { from: null, to: null };\n  updateRangeButtons();\n  updateRangeAnchorButtons();\n  requestAnalytics();\n}\n\nfunction handleModeChange(mode) {\n  if (!mode || mode === analyticsState.currentMode) return;\n  analyticsState.currentMode = mode;\n  updateModeButtons();\n  renderMap();\n}\n\nfunction handleChartExpand(chartKey) {\n  if (!EXPANDABLE_SECTIONS.has(chartKey)) {\n    return;\n  }\n\n  const state = analyticsState;\n  const previous = state.expandedChart;\n  const next = state.expandedChart === chartKey ? null : chartKey;\n  state.expandedChart = next;\n  applyChartExpansionState();\n\n  if (next === \"activity\") {\n    resizeActivityChart();\n  } else if (next === \"map\") {\n    resizeMapChart();\n    resizeActivityChart();\n  } else if (next === \"lists\") {\n    resizeActivityChart();\n    resizeMapChart();\n  } else {\n    resizeActivityChart();\n    resizeMapChart();\n  }\n\n  if (previous === \"map\" || next === \"map\") {\n    renderMap();\n  }\n}\n\nfunction handleChartSelection(range) {\n  const normalized = normalizeSelection(range);\n  if (!normalized) {\n    return;\n  }\n\n  const sameAsCurrent =\n    analyticsState.selectionRange.from === normalized.from &&\n    analyticsState.selectionRange.to === normalized.to;\n\n  analyticsState.selectionRange = normalized;\n  analyticsState.customSelection = { ...normalized };\n  analyticsState.usingCustomRange = true;\n  analyticsState.currentRange = Math.max(0, Math.round((normalized.to - normalized.from) / MS_PER_DAY));\n  updateRangeButtons();\n  updateRangeAnchorButtons();\n\n  if (!sameAsCurrent) {\n    scheduleSelectionFetch(normalized);\n  }\n}\n\nfunction teardownPanel() {\n  const state = analyticsState;\n  state.currentVideoId = null;\n  state.activeRequestKey = null;\n  state.latestCountries = [];\n  resetStateForVideo();\n  state.panelExpanded = false;\n  state.isLoading = false;\n\n  if (state.panelElement) {\n    state.panelElement.remove();\n    state.panelElement = null;\n  }\n\n  disposeActivityChart();\n  disposeMapChart();\n}\n\nfunction teardownPremiumAnalytics() {\n  teardownPanel();\n  if (resizeListener) {\n    window.removeEventListener(\"resize\", resizeListener);\n  }\n  document.removeEventListener(\"yt-navigate-finish\", handleNavigation);\n  hideTierNotice();\n  analyticsState.initialized = false;\n}\n\nfunction updatePremiumSession({ token, active, membershipTier }) {\n  const previousToken = analyticsState.sessionToken;\n  const wasActive = analyticsState.sessionActive;\n  const previousTier = analyticsState.membershipTier;\n\n  setSession(token || null, active, membershipTier);\n\n  if (!analyticsState.sessionActive) {\n    setTeaserSuppressed(false, TEASER_SUPPRESSION_REASON_PREMIUM);\n    hideTierNotice();\n    teardownPanel();\n    return;\n  }\n\n  const hasPremiumTier = analyticsState.membershipTier === \"premium\";\n\n  if (!hasPremiumTier) {\n    setTeaserSuppressed(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n    showTierNotice();\n    teardownPanel();\n    return;\n  }\n\n  hideTierNotice();\n  setTeaserSuppressed(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n  ensurePanel();\n\n  const tokenChanged = analyticsState.sessionToken && analyticsState.sessionToken !== previousToken;\n  const tierUpgraded = previousTier !== \"premium\" && analyticsState.membershipTier === \"premium\";\n  const activated = (!wasActive && analyticsState.sessionActive) || tierUpgraded;\n\n  if ((tokenChanged || activated) && analyticsState.currentVideoId) {\n    requestAnalytics();\n  }\n}\n\nfunction resolveVideoId() {\n  const videoId = getVideoId(window.location.href);\n  if (!videoId || videoId.length !== 11) return null;\n  return videoId;\n}\n\nfunction resizeCharts() {\n  applyChartExpansionState();\n  resizeActivityChart();\n  resizeMapChart();\n}\n\nexport { initPremiumAnalytics, teardownPremiumAnalytics, updatePremiumSession };\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/lists/index.js",
    "content": "import { sanitizeCount } from \"../utils\";\nimport { localize } from \"../../utils\";\n\nfunction createPlaceholder() {\n  return `<li class=\"ryd-analytics__placeholder\">${localize(\"premiumAnalytics_noData\")}</li>`;\n}\n\nfunction renderEntry({ countryCode, countryName, likes, dislikes }, type) {\n  const value = type === \"likes\" ? likes : dislikes;\n  const safeValue = sanitizeCount(value);\n  const name = countryName || countryCode || localize(\"premiumAnalytics_unknownRegion\");\n  const codeSuffix = countryCode ? ` (${countryCode})` : \"\";\n  return `<li><span class=\"ryd-analytics__country\">${name}${codeSuffix}</span><span class=\"ryd-analytics__value\">${safeValue.toLocaleString()}</span></li>`;\n}\n\nfunction updateCountryList(container, entries, type) {\n  if (!container) return;\n  if (!entries?.length) {\n    container.innerHTML = createPlaceholder();\n    return;\n  }\n\n  container.innerHTML = entries.map((entry) => renderEntry(entry, type)).join(\"\");\n}\n\nexport { updateCountryList };\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/logging.js",
    "content": "const LOG_PREFIX = '[PremiumAnalytics]';\n\nexport function logTimeBounds(label, bounds) {\n  if (!bounds) return;\n  console.log(\n    `${LOG_PREFIX} ${label} bounds:`,\n    bounds.min != null ? new Date(bounds.min).toISOString() : 'null',\n    '->',\n    bounds.max != null ? new Date(bounds.max).toISOString() : 'null',\n  );\n}\n\nexport function logRangeSelection(label, range) {\n  if (!range) return;\n  console.log(\n    `${LOG_PREFIX} ${label} selection:`,\n    range.from != null ? new Date(range.from).toISOString() : 'null',\n    '->',\n    range.to != null ? new Date(range.to).toISOString() : 'null',\n  );\n}\n\nexport function logFetchRequest(videoId, params) {\n  console.log(`${LOG_PREFIX} fetch video=${videoId} params=`, params.toString());\n}\n\nexport function logZoomPreview(label, bounds) {\n  if (!bounds) return;\n  console.log(\n    `${LOG_PREFIX} ${label} preview:`,\n    new Date(bounds.from).toISOString(),\n    '->',\n    new Date(bounds.to).toISOString(),\n  );\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/logging.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\nimport {\n  logTimeBounds,\n  logRangeSelection,\n  logFetchRequest,\n  logZoomPreview,\n} from \"./logging\";\n\ndescribe(\"premiumAnalytics.logging\", () => {\n  let consoleSpy;\n\n  beforeEach(() => {\n    consoleSpy = jest.spyOn(console, \"log\").mockImplementation(() => {});\n  });\n\n  afterEach(() => {\n    consoleSpy.mockRestore();\n  });\n\n  it(\"logs time bounds when values are provided\", () => {\n    logTimeBounds(\"chart\", { min: 0, max: 1 });\n\n    expect(consoleSpy).toHaveBeenCalledWith(\"[PremiumAnalytics] chart bounds:\", new Date(0).toISOString(), \"->\", new Date(1).toISOString());\n  });\n\n  it(\"skips logging time bounds when null\", () => {\n    logTimeBounds(\"chart\", null);\n    expect(consoleSpy).not.toHaveBeenCalled();\n  });\n\n  it(\"logs range selections\", () => {\n    logRangeSelection(\"selection\", { from: 0, to: 1 });\n\n    expect(consoleSpy).toHaveBeenCalledWith(\n      \"[PremiumAnalytics] selection selection:\",\n      new Date(0).toISOString(),\n      \"->\",\n      new Date(1).toISOString(),\n    );\n  });\n\n  it(\"logs fetch parameters\", () => {\n    const params = new URLSearchParams({ a: \"1\", b: \"two\" });\n    logFetchRequest(\"video-id\", params);\n\n    expect(consoleSpy).toHaveBeenCalledWith(\"[PremiumAnalytics] fetch video=video-id params=\", \"a=1&b=two\");\n  });\n\n  it(\"logs zoom preview bounds\", () => {\n    logZoomPreview(\"preview\", { from: 3, to: 4 });\n\n    expect(consoleSpy).toHaveBeenCalledWith(\n      \"[PremiumAnalytics] preview preview:\",\n      new Date(3).toISOString(),\n      \"->\",\n      new Date(4).toISOString(),\n    );\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/map/index.js",
    "content": "import * as echarts from \"echarts\";\nimport { feature } from \"topojson-client\";\nimport countryCodeLookup from \"country-code-lookup\";\nimport worldData from \"world-atlas/countries-110m.json\";\nimport usStatesData from \"us-atlas/states-10m.json\";\n\nimport { analyticsState } from \"../state\";\nimport { sanitizeCount } from \"../utils\";\nimport { localize } from \"../../utils\";\nimport { getMutedTextColor, getBorderColor, getSurfaceColor, getHoverFillColor } from \"../theme\";\n\nlet mapTranslator = (key, substitutions) => localize(key, substitutions);\n\nexport function setMapTranslator(translator) {\n  if (typeof translator === \"function\") {\n    mapTranslator = translator;\n  } else {\n    mapTranslator = (key, substitutions) => localize(key, substitutions);\n  }\n}\n\nexport function resetMapTranslator() {\n  mapTranslator = (key, substitutions) => localize(key, substitutions);\n}\n\nfunction translateMessage(key, substitutions) {\n  try {\n    return mapTranslator(key, substitutions);\n  } catch (error) {\n    console.warn(\"Map translation failed for\", key, error);\n    return localize(key, substitutions);\n  }\n}\n\nconst worldFeatures = feature(worldData, worldData.objects.countries).features ?? [];\nconst NORMALIZED_WORLD_FEATURES = normalizeWorldFeatures(worldFeatures);\nconst WORLD_FEATURE_COLLECTION = { type: \"FeatureCollection\", features: NORMALIZED_WORLD_FEATURES };\necharts.registerMap(\"world\", WORLD_FEATURE_COLLECTION);\nconst usStateFeatures = feature(usStatesData, usStatesData.objects.states).features ?? [];\nconst NORMALIZED_US_STATE_FEATURES = normalizeWorldFeatures(usStateFeatures);\nconst US_STATE_FEATURE_COLLECTION = { type: \"FeatureCollection\", features: NORMALIZED_US_STATE_FEATURES };\necharts.registerMap(\"usa-states\", US_STATE_FEATURE_COLLECTION);\nconst US_STATE_NAME_BY_CODE = {\n  AL: \"Alabama\",\n  AK: \"Alaska\",\n  AZ: \"Arizona\",\n  AR: \"Arkansas\",\n  CA: \"California\",\n  CO: \"Colorado\",\n  CT: \"Connecticut\",\n  DE: \"Delaware\",\n  FL: \"Florida\",\n  GA: \"Georgia\",\n  HI: \"Hawaii\",\n  ID: \"Idaho\",\n  IL: \"Illinois\",\n  IN: \"Indiana\",\n  IA: \"Iowa\",\n  KS: \"Kansas\",\n  KY: \"Kentucky\",\n  LA: \"Louisiana\",\n  ME: \"Maine\",\n  MD: \"Maryland\",\n  MA: \"Massachusetts\",\n  MI: \"Michigan\",\n  MN: \"Minnesota\",\n  MS: \"Mississippi\",\n  MO: \"Missouri\",\n  MT: \"Montana\",\n  NE: \"Nebraska\",\n  NV: \"Nevada\",\n  NH: \"New Hampshire\",\n  NJ: \"New Jersey\",\n  NM: \"New Mexico\",\n  NY: \"New York\",\n  NC: \"North Carolina\",\n  ND: \"North Dakota\",\n  OH: \"Ohio\",\n  OK: \"Oklahoma\",\n  OR: \"Oregon\",\n  PA: \"Pennsylvania\",\n  RI: \"Rhode Island\",\n  SC: \"South Carolina\",\n  SD: \"South Dakota\",\n  TN: \"Tennessee\",\n  TX: \"Texas\",\n  UT: \"Utah\",\n  VT: \"Vermont\",\n  VA: \"Virginia\",\n  WA: \"Washington\",\n  WV: \"West Virginia\",\n  WI: \"Wisconsin\",\n  WY: \"Wyoming\",\n  DC: \"District of Columbia\",\n  PR: \"Puerto Rico\",\n  GU: \"Guam\",\n  VI: \"U.S. Virgin Islands\",\n  AS: \"American Samoa\",\n  MP: \"Northern Mariana Islands\",\n};\nconst CANONICAL_SYNONYM_GROUPS = [\n  [\n    \"unitedstatesofamerica\",\n    \"unitedstates\",\n    \"unitedstatesus\",\n    \"unitedstatesusa\",\n    \"usa\",\n    \"us\",\n    \"unitedstatesunitedstates\",\n  ],\n  [\"ssudan\", \"southsudan\", \"southsudanrepublic\"],\n  [\"wsahara\", \"westernsahara\"],\n  [\"democraticrepubliccongo\", \"democraticrepublicofthecongo\", \"drcongo\", \"drc\", \"congokinshasa\"],\n  [\"cotedivoire\", \"ivorycoast\"],\n  [\"czechia\", \"czechrepublic\"],\n  [\"eswatini\", \"swaziland\", \"swazil\"],\n  [\"eqguinea\", \"equatorialguinea\"],\n  [\"timorleste\", \"easttimor\"],\n  [\"solomonisls\", \"solomonislands\", \"solomonis\"],\n];\nconst CANONICAL_SYNONYM_LOOKUP = buildSynonymLookup(CANONICAL_SYNONYM_GROUPS);\nconst FEATURE_NAME_BY_CANONICAL = buildFeatureCanonicalMap(NORMALIZED_WORLD_FEATURES);\nconst ISO_TO_FEATURE_NAME = buildIsoToFeatureNameMap(FEATURE_NAME_BY_CANONICAL);\nconst US_STATE_FEATURE_CANONICAL_MAP = buildStateCanonicalMap(NORMALIZED_US_STATE_FEATURES);\n\nexport function ensureMapChart() {\n  const state = analyticsState;\n  if (!state.panelElement) return null;\n  if (!state.mapChart) {\n    const container = state.panelElement.querySelector(\"#ryd-analytics-map\");\n    if (!container) return null;\n    state.mapChart = echarts.init(container);\n  }\n  initializeMapInteractions(state.mapChart);\n  return state.mapChart;\n}\n\nexport function renderMap(countries) {\n  const state = analyticsState;\n  if (Array.isArray(countries)) {\n    state.latestCountries = countries;\n  }\n\n  const mapChart = ensureMapChart();\n  if (!mapChart) return;\n\n  const context = resolveMapContext(state);\n  const mode = state.currentMode;\n  const showLegend = state.expandedChart === \"map\";\n\n  const mapData = context.entries.map((entry) => {\n    let value;\n    if (mode === \"ratio\") {\n      value = entry.ratio ?? null;\n    } else if (mode === \"likes\") {\n      value = entry.likes;\n    } else {\n      value = entry.dislikes;\n    }\n\n    if (value !== null && !Number.isFinite(value)) {\n      value = null;\n    }\n\n    return {\n      name: entry.mapName,\n      code: entry.code,\n      displayName: entry.displayName,\n      value,\n      likes: entry.likes,\n      dislikes: entry.dislikes,\n      ratio: entry.ratio,\n    };\n  });\n\n  const numericValues = mapData\n    .map((d) => (typeof d.value === \"number\" && Number.isFinite(d.value) ? d.value : null))\n    .filter((v) => v !== null);\n\n  const visualConfig = resolveVisualMapConfig(numericValues, mode);\n  const mapSeriesLabel = translateMessage(\"premiumAnalytics_mapSeriesLabel\");\n\n  mapChart.setOption(\n    {\n      backgroundColor: \"transparent\",\n      tooltip: {\n        trigger: \"item\",\n        formatter: (params) => formatMapTooltip(params),\n      },\n      visualMap: {\n        min: visualConfig.min,\n        max: visualConfig.max,\n        text: [visualConfig.highLabel, visualConfig.lowLabel],\n        realtime: true,\n        calculable: false,\n        inRange: {\n          color: visualConfig.colors,\n        },\n        textStyle: { color: getMutedTextColor() },\n        formatter: mode === \"ratio\" ? (value) => `${Math.round((value ?? 0) * 100)}%` : undefined,\n        precision: mode === \"ratio\" ? 2 : 0,\n        orient: \"vertical\",\n        left: \"left\",\n        bottom: 20,\n        itemWidth: 14,\n        itemHeight: 120,\n        align: \"left\",\n        show: showLegend,\n      },\n      geo: {\n        map: context.mapKey,\n        roam: true,\n        nameProperty: \"name\",\n        itemStyle: {\n          borderColor: getBorderColor(0.4),\n          borderWidth: 0.6,\n          areaColor: getSurfaceColor(0.06, 0.02),\n        },\n        emphasis: {\n          itemStyle: {\n            areaColor: getHoverFillColor(0.18, 0.18),\n          },\n        },\n        ...context.geoOverrides,\n      },\n      series: [\n        {\n          name: mapSeriesLabel,\n          type: \"map\",\n          geoIndex: 0,\n          data: mapData,\n        },\n      ],\n    },\n    true,\n  );\n\n  updateMapResetButtonVisibility();\n}\n\nfunction resolveMapContext(state) {\n  const focusCode = typeof state.mapFocusCountry === \"string\" ? normalizeIsoCode(state.mapFocusCountry) : \"\";\n  const subdivisions = Array.isArray(state.latestSubdivisions) ? state.latestSubdivisions : [];\n  const focusSubdivisions = focusCode ? filterSubdivisionsForCountry(subdivisions, focusCode) : [];\n\n  if (state.mapView === \"subdivision\" && focusCode === \"US\" && focusSubdivisions.length > 0) {\n    return {\n      mapKey: \"usa-states\",\n      entries: buildSubdivisionEntries(focusSubdivisions, focusCode),\n      geoOverrides: {\n        zoom: 2.35,\n        center: [-98.5795, 39.8283],\n        layoutCenter: [\"50%\", \"48%\"],\n        layoutSize: \"120%\",\n        scaleLimit: { min: 1, max: 12 },\n      },\n    };\n  }\n\n  if (state.mapView === \"subdivision\") {\n    state.mapView = \"world\";\n    state.mapFocusCountry = null;\n  }\n\n  return {\n    mapKey: \"world\",\n    entries: buildCountryEntries(Array.isArray(state.latestCountries) ? state.latestCountries : []),\n    geoOverrides: {\n      scaleLimit: { min: 1, max: 10 },\n    },\n  };\n}\n\nfunction buildCountryEntries(entries) {\n  return entries.map((entry) => {\n    const likes = sanitizeCount(entry.likes);\n    const dislikes = sanitizeCount(entry.dislikes);\n    const total = likes + dislikes;\n    const ratio = total > 0 ? likes / total : null;\n    const countryCode = typeof entry.countryCode === \"string\" ? entry.countryCode.toUpperCase() : \"\";\n    const fallbackName = translateMessage(\"premiumAnalytics_unknownRegion\");\n    const displayName = entry.countryName || countryCode || fallbackName;\n    const mapName = resolveMapRegionName(countryCode, displayName);\n\n    return {\n      displayName,\n      mapName,\n      code: countryCode,\n      likes,\n      dislikes,\n      ratio,\n      total,\n    };\n  });\n}\n\nfunction buildSubdivisionEntries(entries, countryCode) {\n  const isoCountry = normalizeIsoCode(countryCode);\n  if (!isoCountry) {\n    return [];\n  }\n\n  return entries\n    .filter((entry) => normalizeIsoCode(entry?.countryCode) === isoCountry)\n    .map((entry) => {\n      const likes = sanitizeCount(entry.likes);\n      const dislikes = sanitizeCount(entry.dislikes);\n      const total = likes + dislikes;\n      const ratio = total > 0 ? likes / total : null;\n      const subdivisionCode = typeof entry.subdivisionCode === \"string\" ? entry.subdivisionCode.toUpperCase() : \"\";\n      const fallbackName = translateMessage(\"premiumAnalytics_unknownRegion\");\n      const displayName = entry.subdivisionName || subdivisionCode || fallbackName;\n      const mapName = resolveSubdivisionFeatureName(isoCountry, subdivisionCode, displayName);\n\n      return {\n        displayName,\n        mapName,\n        code: subdivisionCode || `${isoCountry}:${displayName}`,\n        likes,\n        dislikes,\n        ratio,\n        total,\n      };\n    })\n    .filter((entry) => !!entry.mapName);\n}\n\nfunction resolveSubdivisionFeatureName(countryCode, subdivisionCode, fallbackName) {\n  const normalizedCountry = normalizeIsoCode(countryCode);\n  const normalizedCode = typeof subdivisionCode === \"string\" ? subdivisionCode.trim().toUpperCase() : \"\";\n\n  if (normalizedCountry === \"US\") {\n    if (normalizedCode && US_STATE_NAME_BY_CODE[normalizedCode]) {\n      return US_STATE_NAME_BY_CODE[normalizedCode];\n    }\n\n    const resolved = findFeatureNameByCanonical(fallbackName, US_STATE_FEATURE_CANONICAL_MAP);\n    if (resolved) {\n      return resolved;\n    }\n\n    if (normalizedCode && US_STATE_NAME_BY_CODE[normalizedCode]) {\n      return US_STATE_NAME_BY_CODE[normalizedCode];\n    }\n  }\n\n  const fallbackResolved = findFeatureNameByCanonical(fallbackName, US_STATE_FEATURE_CANONICAL_MAP);\n  if (fallbackResolved) {\n    return fallbackResolved;\n  }\n\n  if (normalizedCode && US_STATE_NAME_BY_CODE[normalizedCode]) {\n    return US_STATE_NAME_BY_CODE[normalizedCode];\n  }\n\n  return fallbackName || normalizedCode || translateMessage(\"premiumAnalytics_unknownRegion\");\n}\n\nfunction filterSubdivisionsForCountry(subdivisions, countryCode) {\n  const normalizedCountry = normalizeIsoCode(countryCode);\n  if (!normalizedCountry) {\n    return [];\n  }\n\n  return subdivisions.filter((entry) => normalizeIsoCode(entry?.countryCode) === normalizedCountry);\n}\n\nfunction initializeMapInteractions(mapChart) {\n  if (!mapChart) return;\n\n  if (!mapChart.__rydClickBound) {\n    mapChart.on(\"click\", handleMapClick);\n    mapChart.__rydClickBound = true;\n  }\n\n  bindMapResetButton();\n}\n\nfunction handleMapClick(params) {\n  if (!params) return;\n  const state = analyticsState;\n\n  if (state.mapView === \"subdivision\") {\n    return;\n  }\n\n  const dataCode = typeof params.data?.code === \"string\" ? params.data.code.toUpperCase() : \"\";\n  const canonicalName = canonicalizeName(params.name);\n\n  let resolvedCode = dataCode;\n  if (!resolvedCode && canonicalName === canonicalizeName(\"United States of America\")) {\n    resolvedCode = \"US\";\n  }\n\n  if (resolvedCode !== \"US\") {\n    return;\n  }\n\n  const subdivisions = filterSubdivisionsForCountry(state.latestSubdivisions ?? [], resolvedCode);\n  if (subdivisions.length === 0) {\n    return;\n  }\n\n  state.mapFocusCountry = resolvedCode;\n  state.mapView = \"subdivision\";\n  renderMap();\n}\n\nfunction bindMapResetButton() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  const button = panel.querySelector(\"#ryd-analytics-map-reset\");\n  if (!button || button.__rydBound) return;\n\n  button.addEventListener(\"click\", () => {\n    analyticsState.mapView = \"world\";\n    analyticsState.mapFocusCountry = null;\n    renderMap();\n  });\n  button.__rydBound = true;\n}\n\nfunction updateMapResetButtonVisibility() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  const button = panel.querySelector(\"#ryd-analytics-map-reset\");\n  if (!button) return;\n\n  button.hidden = analyticsState.mapView !== \"subdivision\";\n}\n\nexport function disposeMapChart() {\n  const state = analyticsState;\n  if (state.mapChart) {\n    state.mapChart.dispose();\n    state.mapChart = null;\n  }\n  updateMapResetButtonVisibility();\n}\n\nexport function clearMapChart() {\n  analyticsState.mapChart?.clear();\n}\n\nexport function resizeMapChart() {\n  analyticsState.mapChart?.resize();\n}\n\nfunction resolveVisualMapConfig(values, mode) {\n  const fallbackMax = values.length ? Math.max(...values, 0) : 0;\n  const max = fallbackMax <= 0 ? 1 : fallbackMax;\n  const likesLabel = translateMessage(\"premiumAnalytics_modeLikes\");\n  const dislikesLabel = translateMessage(\"premiumAnalytics_modeDislikes\");\n  const ratioLabel = translateMessage(\"premiumAnalytics_likeRatio\");\n\n  if (mode === \"ratio\") {\n    return {\n      min: 0,\n      max: 1,\n      colors: [\"#ef4444\", \"#fde047\", \"#22c55e\"],\n      highLabel: translateMessage(\"premiumAnalytics_visualHighRatio\"),\n      lowLabel: translateMessage(\"premiumAnalytics_visualLowRatio\"),\n    };\n  }\n\n  if (mode === \"likes\") {\n    return {\n      min: 0,\n      max,\n      colors: [\"#bbf7d0\", \"#15803d\"],\n      highLabel: translateMessage(\"premiumAnalytics_visualHighLikes\"),\n      lowLabel: translateMessage(\"premiumAnalytics_visualLowLikes\"),\n    };\n  }\n\n  if (mode === \"dislikes\") {\n    return {\n      min: 0,\n      max,\n      colors: [\"#fecaca\", \"#b91c1c\"],\n      highLabel: translateMessage(\"premiumAnalytics_visualHighDislikes\"),\n      lowLabel: translateMessage(\"premiumAnalytics_visualLowDislikes\"),\n    };\n  }\n\n  const label = mode === \"likes\" ? likesLabel : mode === \"dislikes\" ? dislikesLabel : ratioLabel;\n\n  return {\n    min: 0,\n    max,\n    colors: [\"#e2e8f0\", \"#3b82f6\"],\n    highLabel: translateMessage(\"premiumAnalytics_visualHighGeneric\", [label]),\n    lowLabel: translateMessage(\"premiumAnalytics_visualLowGeneric\", [label]),\n  };\n}\n\nfunction formatMapTooltip(params) {\n  const data = params.data;\n  if (!data) {\n    return `${params.name}`;\n  }\n\n  const name = data.displayName || params.name;\n  const code = data.code ? ` (${data.code})` : \"\";\n  const likes = Number.isFinite(data.likes) ? data.likes : 0;\n  const dislikes = Number.isFinite(data.dislikes) ? data.dislikes : 0;\n  const percent = data.ratio != null ? (data.ratio * 100).toFixed(1) : \"0.0\";\n  const likesText = translateMessage(\"premiumAnalytics_tooltipLikes\", [likes.toLocaleString()]);\n  const dislikesText = translateMessage(\"premiumAnalytics_tooltipDislikes\", [dislikes.toLocaleString()]);\n  const ratioText = translateMessage(\"premiumAnalytics_tooltipRatio\", [`${percent}%`]);\n\n  return `${name}${code}<br/>${likesText}<br/>${dislikesText}<br/>${ratioText}`;\n}\n\nfunction resolveMapRegionName(countryCode, fallbackName) {\n  const trimmedName = (fallbackName || \"\").trim();\n  const isoCode = normalizeIsoCode(countryCode);\n\n  if (isoCode && ISO_TO_FEATURE_NAME.has(isoCode)) {\n    return ISO_TO_FEATURE_NAME.get(isoCode);\n  }\n\n  const canonicalMatch = findFeatureNameByCanonical(trimmedName, FEATURE_NAME_BY_CANONICAL);\n  if (canonicalMatch) {\n    return canonicalMatch;\n  }\n\n  if (isoCode) {\n    const lookupMatch = countryCodeLookup.byIso(isoCode);\n    const lookupName = lookupMatch?.country ?? lookupMatch?.officialName;\n    const resolved = findFeatureNameByCanonical(lookupName, FEATURE_NAME_BY_CANONICAL);\n    if (resolved) {\n      return resolved;\n    }\n  }\n\n  if (trimmedName) {\n    const lookupMatch = countryCodeLookup.byCountry(trimmedName);\n    if (lookupMatch) {\n      const candidates = [lookupMatch.country, lookupMatch.officialName].filter(Boolean);\n      for (const candidate of candidates) {\n        const resolved = findFeatureNameByCanonical(candidate, FEATURE_NAME_BY_CANONICAL);\n        if (resolved) {\n          return resolved;\n        }\n      }\n    }\n  }\n\n  return trimmedName || translateMessage(\"premiumAnalytics_unknownRegion\");\n}\n\nfunction buildStateCanonicalMap(features) {\n  const map = new Map();\n  features.forEach((featureItem) => {\n    if (!featureItem?.properties?.name) return;\n    const canonical = canonicalizeName(featureItem.properties.name);\n    if (!canonical || map.has(canonical)) return;\n    map.set(canonical, featureItem.properties.name);\n  });\n  return map;\n}\n\nfunction buildFeatureCanonicalMap(features) {\n  const map = new Map();\n  features.forEach((featureItem) => {\n    if (!featureItem?.properties?.name) return;\n    const canonical = canonicalizeName(featureItem.properties.name);\n    if (!canonical) return;\n    if (!map.has(canonical)) {\n      map.set(canonical, featureItem.properties.name);\n    }\n  });\n  CANONICAL_SYNONYM_GROUPS.forEach((group) => {\n    if (!Array.isArray(group) || group.length === 0) {\n      return;\n    }\n    const [primary] = group;\n    const canonicalPrimary = primary ?? \"\";\n    const primaryName = map.get(canonicalPrimary);\n    if (!primaryName) {\n      return;\n    }\n    group.forEach((alias) => {\n      if (alias && !map.has(alias)) {\n        map.set(alias, primaryName);\n      }\n    });\n  });\n  return map;\n}\n\nfunction buildSynonymLookup(groups) {\n  const lookup = new Map();\n  for (const group of groups) {\n    if (!Array.isArray(group) || group.length === 0) {\n      continue;\n    }\n    const [primary] = group;\n    for (const alias of group) {\n      if (alias) {\n        lookup.set(alias, primary);\n      }\n    }\n  }\n  return lookup;\n}\n\nfunction buildIsoToFeatureNameMap(featureCanonicalMap) {\n  const map = new Map();\n  countryCodeLookup.countries.forEach((country) => {\n    const iso2 = (country.iso2 || \"\").toUpperCase();\n    if (!iso2) return;\n    const candidates = [country.country, country.officialName].filter(Boolean);\n    for (const candidate of candidates) {\n      const featureName = findFeatureNameByCanonical(candidate, featureCanonicalMap);\n      if (featureName) {\n        map.set(iso2, featureName);\n        return;\n      }\n    }\n  });\n  return map;\n}\n\nfunction findFeatureNameByCanonical(name, featureCanonicalMap) {\n  const canonical = canonicalizeName(name);\n  if (!canonical) return null;\n  return featureCanonicalMap.get(canonical) ?? null;\n}\n\nfunction canonicalizeName(name) {\n  if (!name) return \"\";\n  const canonical = expandAbbreviations(name)\n    .normalize(\"NFD\")\n    .replace(/[\\u0300-\\u036f]/g, \"\")\n    .replace(/[^A-Za-z0-9]+/g, \"\")\n    .replace(/ofthe|the|and/gi, \"\")\n    .toLowerCase();\n\n  return CANONICAL_SYNONYM_LOOKUP.get(canonical) ?? canonical;\n}\n\nfunction expandAbbreviations(name) {\n  return name\n    .replace(/\\(([^)]+)\\)/g, \" $1 \")\n    .replace(/\\bDem\\.(?=\\s|$)/gi, \"Democratic \")\n    .replace(/\\bRep\\.(?=\\s|$)/gi, \"Republic \")\n    .replace(/\\bUAE\\b/gi, \"United Arab Emirates\")\n    .replace(/\\bUK\\b/gi, \"United Kingdom\")\n    .replace(/\\bUSA\\b/gi, \"United States\")\n    .replace(/\\bU\\.S\\.?\\b/gi, \"United States\")\n    .replace(/\\bSt\\.?\\b/gi, \"Saint \")\n    .replace(/\\bNat\\.?\\b/gi, \"National \")\n    .replace(/\\bIs\\.?\\b/gi, \"Islands \")\n    .replace(/DemocraticRepublic/gi, \"Democratic Republic\")\n    .replace(/Republicof/gi, \"Republic of \")\n    .replace(/FederalRepublic/gi, \"Federal Republic \");\n}\n\nfunction normalizeIsoCode(code) {\n  if (!code) return \"\";\n  const trimmed = code.trim().toUpperCase();\n  if (trimmed.length === 2) {\n    return trimmed;\n  }\n  if (trimmed.length === 3) {\n    const match = countryCodeLookup.byIso(trimmed);\n    if (match?.iso2) {\n      return match.iso2.toUpperCase();\n    }\n  }\n  return trimmed;\n}\n\nfunction normalizeWorldFeatures(features) {\n  return features.map((featureItem) => {\n    if (!featureItem || !featureItem.geometry) {\n      return featureItem;\n    }\n\n    const normalizedGeometry = normalizeGeometry(featureItem.geometry);\n    return { ...featureItem, geometry: normalizedGeometry };\n  });\n}\n\nfunction normalizeGeometry(geometry) {\n  if (geometry.type === \"Polygon\") {\n    return {\n      ...geometry,\n      coordinates: geometry.coordinates.map((ring) => normalizeRing(ring)),\n    };\n  }\n  if (geometry.type === \"MultiPolygon\") {\n    return {\n      ...geometry,\n      coordinates: geometry.coordinates.map((polygon) => polygon.map((ring) => normalizeRing(ring))),\n    };\n  }\n\n  return geometry;\n}\n\nfunction normalizeRing(ring) {\n  if (!Array.isArray(ring) || ring.length === 0) {\n    return ring;\n  }\n\n  const normalized = [];\n  let prevLon = null;\n\n  for (let i = 0; i < ring.length; i += 1) {\n    const point = ring[i];\n    if (!Array.isArray(point) || point.length < 2) {\n      normalized.push(point);\n      continue;\n    }\n    let [lon, lat] = point;\n    if (prevLon != null) {\n      let delta = lon - prevLon;\n      if (delta > 180) {\n        lon -= 360;\n      } else if (delta < -180) {\n        lon += 360;\n      }\n    }\n    normalized.push([lon, lat]);\n    prevLon = lon;\n  }\n\n  if (normalized.length > 1) {\n    const [firstLon, firstLat] = normalized[0];\n    normalized[normalized.length - 1] = [firstLon, firstLat];\n  }\n\n  return normalized;\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/map/index.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\njest.mock(\"echarts\", () => {\n  const registerMap = jest.fn();\n  const init = jest.fn(() => {\n    const listeners = {};\n    return {\n      setOption: jest.fn(),\n      clear: jest.fn(),\n      resize: jest.fn(),\n      dispose: jest.fn(),\n      on: jest.fn((event, handler) => {\n        listeners[event] = handler;\n      }),\n      __listeners: listeners,\n    };\n  });\n  return { registerMap, init };\n});\n\njest.mock(\"topojson-client\", () => {\n  const polygon = {\n    type: \"Polygon\",\n    coordinates: [\n      [\n        [0, 0],\n        [1, 0],\n        [1, 1],\n        [0, 1],\n        [0, 0],\n      ],\n    ],\n  };\n  const worldFeatures = [\n    { properties: { name: \"United States of America\" }, geometry: polygon },\n    { properties: { name: \"Canada\" }, geometry: polygon },\n    { properties: { name: \"S. Sudan\" }, geometry: polygon },\n    { properties: { name: \"Côte d'Ivoire\" }, geometry: polygon },\n    { properties: { name: \"Czechia\" }, geometry: polygon },\n    { properties: { name: \"eSwatini\" }, geometry: polygon },\n    { properties: { name: \"Dem. Rep. Congo\" }, geometry: polygon },\n    { properties: { name: \"Eq. Guinea\" }, geometry: polygon },\n    { properties: { name: \"Solomon Is.\" }, geometry: polygon },\n    { properties: { name: \"Timor-Leste\" }, geometry: polygon },\n    { properties: { name: \"W. Sahara\" }, geometry: polygon },\n  ];\n  const stateFeatures = [\n    { properties: { name: \"California\" }, geometry: polygon },\n    { properties: { name: \"Texas\" }, geometry: polygon },\n    { properties: { name: \"New York\" }, geometry: polygon },\n  ];\n  let callIndex = 0;\n  return {\n    feature: jest.fn(() => {\n      const result = callIndex === 0 ? worldFeatures : stateFeatures;\n      callIndex += 1;\n      return { features: result };\n    }),\n  };\n});\n\njest.mock(\"country-code-lookup\", () => {\n  const countries = [\n    { iso2: \"US\", iso3: \"USA\", country: \"United States\", officialName: \"United States of America\" },\n    { iso2: \"CA\", iso3: \"CAN\", country: \"Canada\", officialName: \"Canada\" },\n    { iso2: \"SS\", iso3: \"SSD\", country: \"South Sudan\", officialName: \"Republic of South Sudan\" },\n    { iso2: \"CI\", iso3: \"CIV\", country: \"Ivory Coast\", officialName: \"Côte d'Ivoire\" },\n    { iso2: \"CZ\", iso3: \"CZE\", country: \"Czech Republic\", officialName: \"Czechia\" },\n    { iso2: \"SZ\", iso3: \"SWZ\", country: \"Swaziland\", officialName: \"Eswatini\" },\n    {\n      iso2: \"CD\",\n      iso3: \"COD\",\n      country: \"Democratic Republic of the Congo\",\n      officialName: \"Democratic Republic of the Congo\",\n    },\n    { iso2: \"GQ\", iso3: \"GNQ\", country: \"Equatorial Guinea\", officialName: \"Republic of Equatorial Guinea\" },\n    { iso2: \"SB\", iso3: \"SLB\", country: \"Solomon Islands\", officialName: \"Solomon Islands\" },\n    { iso2: \"TL\", iso3: \"TLS\", country: \"East Timor\", officialName: \"Democratic Republic of Timor-Leste\" },\n    { iso2: \"EH\", iso3: \"ESH\", country: \"Western Sahara\", officialName: \"Sahrawi Arab Democratic Republic\" },\n  ];\n  const byIso = jest.fn((code) => countries.find((c) => c.iso2 === code || c.iso3 === code));\n  const byCountry = jest.fn((name) => countries.find((c) => c.country === name || c.officialName === name));\n  return {\n    countries,\n    byIso,\n    byCountry,\n  };\n});\n\njest.mock(\"../theme\", () => ({\n  getMutedTextColor: jest.fn(() => \"#aaaaaa\"),\n  getBorderColor: jest.fn(() => \"#bbbbbb\"),\n  getSurfaceColor: jest.fn(() => \"rgba(0,0,0,0.04)\"),\n  getHoverFillColor: jest.fn(() => \"rgba(0,0,0,0.18)\"),\n}));\n\nimport echarts from \"echarts\";\nimport { analyticsState } from \"../state\";\nimport {\n  ensureMapChart,\n  renderMap,\n  disposeMapChart,\n  clearMapChart,\n  resizeMapChart,\n  setMapTranslator,\n  resetMapTranslator,\n} from \"./index\";\n\nconst enMessages = require(\"../../../_locales/en/messages.json\");\n\nfunction getMessage(key, substitutions) {\n  const entry = enMessages[key];\n  if (!entry) return key;\n  let message = entry.message ?? \"\";\n  if (substitutions != null) {\n    const values = Array.isArray(substitutions) ? substitutions : [substitutions];\n    values.forEach((value, index) => {\n      const replacement = value != null ? `${value}` : \"\";\n      message = message.replace(new RegExp(`\\\\$${index + 1}`, \"g\"), replacement);\n    });\n  }\n  return message;\n}\n\nfunction setupI18nMock() {\n  global.chrome = {\n    i18n: {\n      getMessage,\n    },\n  };\n}\n\nfunction createChartStub() {\n  const listeners = {};\n  return {\n    setOption: jest.fn(),\n    clear: jest.fn(),\n    resize: jest.fn(),\n    dispose: jest.fn(),\n    on: jest.fn((event, handler) => {\n      listeners[event] = handler;\n    }),\n    __listeners: listeners,\n  };\n}\n\nfunction setupPanel() {\n  const panel = document.createElement(\"section\");\n  const controls = document.createElement(\"div\");\n  const resetButton = document.createElement(\"button\");\n  resetButton.id = \"ryd-analytics-map-reset\";\n  resetButton.type = \"button\";\n  resetButton.hidden = true;\n  controls.appendChild(resetButton);\n  const mapHost = document.createElement(\"div\");\n  mapHost.id = \"ryd-analytics-map\";\n  panel.appendChild(controls);\n  panel.appendChild(mapHost);\n  analyticsState.panelElement = panel;\n  document.body.appendChild(panel);\n  return panel;\n}\n\ndescribe(\"premiumAnalytics.map\", () => {\n  beforeEach(() => {\n    setupI18nMock();\n    jest.clearAllMocks();\n    document.body.innerHTML = \"\";\n    analyticsState.panelElement = null;\n    analyticsState.mapChart = null;\n    analyticsState.currentMode = \"ratio\";\n    analyticsState.latestCountries = [];\n    analyticsState.latestSubdivisions = [];\n    analyticsState.mapView = \"world\";\n    analyticsState.mapFocusCountry = null;\n    analyticsState.expandedChart = null;\n    setupPanel();\n    setMapTranslator((key, substitutions) => getMessage(key, substitutions));\n  });\n\n  afterEach(() => {\n    delete global.chrome;\n    resetMapTranslator();\n  });\n\n  it(\"initializes the map chart when required\", () => {\n    const chart = ensureMapChart();\n    expect(echarts.init).toHaveBeenCalled();\n    expect(chart).toBe(analyticsState.mapChart);\n  });\n\n  it(\"renders processed map data for ratio mode\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    renderMap([\n      { countryCode: \"US\", countryName: \"United States\", likes: 10, dislikes: 5 },\n      { countryCode: \"CA\", countryName: \"Canada\", likes: 0, dislikes: 4 },\n    ]);\n\n    expect(chart.setOption).toHaveBeenCalled();\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.series[0].data).toHaveLength(2);\n    expect(option.visualMap.min).toBe(0);\n    expect(option.visualMap.max).toBe(1);\n  });\n\n  it(\"formats tooltip with likes, dislikes, and ratio regardless of mode\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.currentMode = \"likes\";\n\n    renderMap([{ countryCode: \"US\", countryName: \"United States\", likes: 75, dislikes: 25 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    const formatter = option.tooltip.formatter;\n    const formatted = formatter({\n      name: \"United States\",\n      data: option.series[0].data[0],\n    });\n\n    expect(formatted).toContain(getMessage(\"premiumAnalytics_tooltipLikes\", [\"75\"]));\n    expect(formatted).toContain(getMessage(\"premiumAnalytics_tooltipDislikes\", [\"25\"]));\n    expect(formatted).toContain(getMessage(\"premiumAnalytics_tooltipRatio\", [\"75.0%\"]));\n  });\n\n  it(\"normalizes United States naming differences\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    renderMap([{ countryCode: \"US\", countryName: \"United States\", likes: 10, dislikes: 5 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.series[0].data[0].name).toBe(\"United States of America\");\n  });\n\n  it(\"falls back to synonyms when only country name present\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n\n    renderMap([{ countryCode: \"\", countryName: \"United States\", likes: 4, dislikes: 1 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.series[0].data[0].name).toBe(\"United States of America\");\n  });\n\n  it(\"handles grouped synonyms including parenthetical abbreviations\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n\n    renderMap([{ countryCode: null, countryName: \"United States (USA)\", likes: 2, dislikes: 0 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.series[0].data[0].name).toBe(\"United States of America\");\n  });\n\n  it(\"maps additional synonyms to their feature names\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n\n    renderMap([\n      { countryCode: \"CI\", countryName: \"Ivory Coast\", likes: 2, dislikes: 1 },\n      { countryCode: \"SS\", countryName: \"South Sudan\", likes: 3, dislikes: 0 },\n    ]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    const names = option.series[0].data.map((entry) => entry.name);\n    expect(names).toContain(\"Côte d'Ivoire\");\n    expect(names).toContain(\"S. Sudan\");\n  });\n\n  it(\"switches metric based on mode\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.currentMode = \"likes\";\n\n    renderMap([{ countryCode: \"US\", countryName: \"United States\", likes: 10, dislikes: 5 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.series[0].data[0].value).toBe(10);\n\n    analyticsState.currentMode = \"ratio\";\n    chart.setOption.mockClear();\n\n    renderMap([{ countryCode: \"US\", countryName: \"United States\", likes: 10, dislikes: 5 }]);\n\n    const ratioOption = chart.setOption.mock.calls[0][0];\n    expect(ratioOption.series[0].data[0].value).toBeCloseTo(10 / 15);\n  });\n\n  it(\"uses red palette for dislikes mode\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.currentMode = \"dislikes\";\n\n    renderMap([{ countryCode: \"US\", countryName: \"United States\", likes: 10, dislikes: 5 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.series[0].data[0].value).toBe(5);\n    expect(option.visualMap.inRange.color).toEqual([\"#fecaca\", \"#b91c1c\"]);\n  });\n\n  it(\"supports internal state dataset fallback\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.latestCountries = [{ countryCode: \"US\", countryName: \"United States\", likes: 5, dislikes: 1 }];\n\n    renderMap();\n\n    expect(chart.setOption).toHaveBeenCalled();\n  });\n\n  it(\"hides the visual map legend when the section is collapsed\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.expandedChart = null;\n\n    renderMap([{ countryCode: \"US\", likes: 1, dislikes: 0 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.visualMap.show).toBe(false);\n  });\n\n  it(\"shows the visual map legend when the section is expanded\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.expandedChart = \"map\";\n\n    renderMap([{ countryCode: \"US\", likes: 1, dislikes: 0 }]);\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.visualMap.show).toBe(true);\n  });\n\n  it(\"renders the US state map when the United States is selected\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.latestCountries = [{ countryCode: \"US\", countryName: \"United States\", likes: 5, dislikes: 2 }];\n    analyticsState.latestSubdivisions = [\n      { countryCode: \"US\", subdivisionCode: \"CA\", subdivisionName: \"California\", likes: 2, dislikes: 1 },\n    ];\n\n    renderMap();\n\n    const initialOption = chart.setOption.mock.calls[0][0];\n    expect(initialOption.geo.map).toBe(\"world\");\n\n    const clickHandler = chart.__listeners.click;\n    clickHandler({ data: { code: \"US\" }, name: \"United States\" });\n\n    const subdivisionOption = chart.setOption.mock.calls.at(-1)[0];\n    expect(subdivisionOption.geo.map).toBe(\"usa-states\");\n    expect(subdivisionOption.geo.zoom).toBeCloseTo(2.35);\n  });\n\n  it(\"returns to the world map when the reset button is clicked\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.latestCountries = [{ countryCode: \"US\", countryName: \"United States\", likes: 5, dislikes: 2 }];\n    analyticsState.latestSubdivisions = [\n      { countryCode: \"US\", subdivisionCode: \"CA\", subdivisionName: \"California\", likes: 2, dislikes: 1 },\n    ];\n\n    renderMap();\n\n    const clickHandler = chart.__listeners.click;\n    clickHandler({ data: { code: \"US\" }, name: \"United States\" });\n    const resetButton = document.getElementById(\"ryd-analytics-map-reset\");\n    expect(resetButton.hidden).toBe(false);\n\n    resetButton.click();\n    const resetOption = chart.setOption.mock.calls.at(-1)[0];\n    expect(resetOption.geo.map).toBe(\"world\");\n    expect(resetButton.hidden).toBe(true);\n  });\n\n  it(\"falls back to the world map when no subdivisions are available\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n    analyticsState.latestCountries = [{ countryCode: \"US\", countryName: \"United States\", likes: 5, dislikes: 2 }];\n    analyticsState.latestSubdivisions = [];\n\n    renderMap();\n\n    const option = chart.setOption.mock.calls[0][0];\n    expect(option.geo.map).toBe(\"world\");\n    const resetButton = document.getElementById(\"ryd-analytics-map-reset\");\n    expect(resetButton.hidden).toBe(true);\n  });\n\n  it(\"clears, resizes, and disposes the chart\", () => {\n    const chart = createChartStub();\n    analyticsState.mapChart = chart;\n\n    clearMapChart();\n    expect(chart.clear).toHaveBeenCalled();\n\n    resizeMapChart();\n    expect(chart.resize).toHaveBeenCalled();\n\n    disposeMapChart();\n    expect(chart.dispose).toHaveBeenCalled();\n    expect(analyticsState.mapChart).toBeNull();\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/panel.js",
    "content": "import { analyticsState, RANGE_OPTIONS, RANGE_ANCHORS } from \"./state\";\nimport { sanitizeCount } from \"./utils\";\nimport { localize } from \"../utils\";\n\nlet callbacks = {\n  onRangePreset: () => {},\n  onRangeAnchorChange: () => {},\n  onModeChange: () => {},\n  onChartExpand: () => {},\n};\n\nexport function configurePanelCallbacks(newCallbacks) {\n  callbacks = { ...callbacks, ...newCallbacks };\n}\n\nexport function ensurePanel() {\n  if (analyticsState.panelElement && analyticsState.panelElement.isConnected) {\n    applyExpansionState();\n    applyLoadingState();\n    updateRangeAnchorButtons();\n    return analyticsState.panelElement;\n  }\n\n  const secondaryInner =\n    document.querySelector(\"#secondary #secondary-inner\") || document.querySelector(\"#secondary-inner\");\n  if (!secondaryInner) {\n    setTimeout(ensurePanel, 500);\n    return null;\n  }\n\n  const panel = document.createElement(\"section\");\n  panel.className = \"ryd-premium-analytics ryd-premium-feature\";\n  panel.innerHTML = createPanelMarkup();\n  secondaryInner.insertBefore(panel, secondaryInner.firstChild);\n  analyticsState.panelElement = panel;\n\n  bindUiControls(panel);\n  updateRangeButtons();\n  updateRangeAnchorButtons();\n  updateModeButtons();\n  applyExpansionState();\n  applyLoadingState();\n  applyChartExpansionState();\n\n  return panel;\n}\n\nexport function updateRangeButtons() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  panel.querySelectorAll(\".ryd-range\").forEach((btn) => {\n    const value = btn.dataset.range;\n    const numeric = parseInt(value ?? \"\", 10);\n    const shouldHighlight = !analyticsState.usingCustomRange;\n    btn.classList.toggle(\"is-active\", shouldHighlight && numeric === analyticsState.currentRange);\n  });\n}\n\nexport function updateRangeAnchorButtons() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  const resolvedAnchor = resolveRangeAnchor();\n  panel.querySelectorAll(\".ryd-range-anchor\").forEach((btn) => {\n    btn.classList.toggle(\"is-active\", btn.dataset.anchor === resolvedAnchor);\n  });\n\n  const descriptor = panel.querySelector(\"#ryd-analytics-range-window\");\n  if (descriptor) {\n    descriptor.textContent = formatRangeWindowLabel(resolvedAnchor);\n  }\n}\n\nexport function updateModeButtons() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  panel.querySelectorAll(\".ryd-mode\").forEach((btn) => {\n    btn.classList.toggle(\"is-active\", btn.dataset.mode === analyticsState.currentMode);\n  });\n}\n\nexport function setListsLoading() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n  panel.classList.add(\"is-loading\");\n}\n\nexport function setFooterMessage(text) {\n  const footer = analyticsState.panelElement?.querySelector(\"#ryd-analytics-footer\");\n  if (footer) {\n    footer.textContent = text || \"\";\n  }\n}\n\nexport function setPanelExpanded(expanded) {\n  analyticsState.panelExpanded = expanded;\n  applyExpansionState();\n}\n\nexport function togglePanelExpanded() {\n  setPanelExpanded(!analyticsState.panelExpanded);\n}\n\nexport function setLoadingState(isLoading) {\n  analyticsState.isLoading = isLoading;\n  applyLoadingState();\n}\n\nexport function setActivityBucketLabel(text) {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n  const container = panel.querySelector(\"#ryd-analytics-activity-meta\");\n  const label = panel.querySelector(\"#ryd-analytics-bucket-label\");\n  if (!container || !label) return;\n  const content = typeof text === \"string\" ? text.trim() : \"\";\n  label.textContent = content;\n  container.hidden = content.length === 0;\n}\n\nexport function renderSummary(summary) {\n  if (!summary) return;\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n  const footer = panel.querySelector(\"#ryd-analytics-footer\");\n  if (!footer) return;\n\n  const likes = sanitizeCount(summary.totalLikes);\n  const dislikes = sanitizeCount(summary.totalDislikes);\n  const totalInteractions = likes + dislikes;\n  const countries = sanitizeCount(summary.countriesRepresented);\n  const uniqueIps = sanitizeCount(summary.uniqueIps);\n  const periodLabel = formatSelectedPeriod();\n  const formattedLikes = likes.toLocaleString();\n  const formattedDislikes = dislikes.toLocaleString();\n  const formattedInteractions = totalInteractions.toLocaleString();\n  const formattedCountries = countries.toLocaleString();\n  const formattedIps = uniqueIps.toLocaleString();\n\n  const periodMarkup = periodLabel\n    ? `<span class=\"ryd-analytics__period-label\">${localize(\"premiumAnalytics_selectedPeriod\", [periodLabel])}</span>`\n    : \"\";\n\n  footer.innerHTML = `\n    <div class=\"ryd-analytics__totals\" role=\"status\" aria-live=\"polite\">\n      <div class=\"ryd-analytics__totals-header\">\n        <span class=\"ryd-analytics__totals-label\">${localize(\"premiumAnalytics_totalsHeading\")}</span>\n        ${periodMarkup}\n      </div>\n      <div class=\"ryd-analytics__totals-values\">\n        <span class=\"ryd-analytics__totals-value ryd-analytics__totals-likes\">${localize(\"premiumAnalytics_totalsLikes\", [formattedLikes])}</span>\n        <span class=\"ryd-analytics__totals-divider\" aria-hidden=\"true\">•</span>\n        <span class=\"ryd-analytics__totals-value ryd-analytics__totals-dislikes\">${localize(\"premiumAnalytics_totalsDislikes\", [formattedDislikes])}</span>\n      </div>\n    </div>\n    <div class=\"ryd-analytics__summary-meta\">\n      ${localize(\"premiumAnalytics_summaryMeta\", [\n        formattedInteractions,\n        formattedCountries,\n        formattedIps,\n      ])}\n    </div>\n  `;\n}\n\nfunction bindUiControls(container) {\n  container.querySelectorAll(\".ryd-range\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const value = btn.dataset.range;\n      if (!value || value === \"custom\") return;\n      const numeric = parseInt(value, 10);\n      if (Number.isNaN(numeric)) return;\n      callbacks.onRangePreset(numeric);\n    });\n  });\n\n  container.querySelectorAll(\".ryd-mode\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const mode = btn.dataset.mode;\n      if (!mode) return;\n      callbacks.onModeChange(mode);\n    });\n  });\n\n  container.querySelectorAll(\".ryd-range-anchor\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const anchor = btn.dataset.anchor;\n      if (!anchor) return;\n      callbacks.onRangeAnchorChange(anchor);\n    });\n  });\n\n  container.querySelectorAll(\".ryd-analytics__section-expand\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const chart = btn.dataset.chart;\n      if (!chart) return;\n      callbacks.onChartExpand(chart);\n    });\n  });\n}\n\nfunction createPanelMarkup() {\n  const rangeControls = RANGE_OPTIONS.map((days) => {\n    const label = formatRangeLabel(days);\n    const isActive = days === analyticsState.currentRange;\n    return `<button class=\"ryd-range${isActive ? \" is-active\" : \"\"}\" data-range=\"${days}\">${label}</button>`;\n  }).join(\"\");\n  const anchorControls = createRangeAnchorControls();\n  const modeControls = createModeControls();\n  const expandLabel = localize(\"premiumAnalytics_expand\");\n\n  return `\n    <header class=\"ryd-analytics__header\">\n      <div class=\"ryd-analytics__title\">${localize(\"premiumAnalytics_title\")}</div>\n      <div class=\"ryd-analytics__controls\">\n        <div class=\"ryd-analytics__ranges\">${rangeControls}</div>\n        ${anchorControls}\n      </div>\n    </header>\n    <div class=\"ryd-analytics__body\">\n      <section class=\"ryd-analytics__section\" data-chart=\"activity\">\n        <div class=\"ryd-analytics__section-header\">\n          <h3 class=\"ryd-analytics__section-title\">${localize(\"premiumAnalytics_activityTitle\")}</h3>\n          <button class=\"ryd-analytics__section-expand\" type=\"button\" data-chart=\"activity\" aria-expanded=\"false\">${expandLabel}</button>\n        </div>\n        <div class=\"ryd-analytics__section-content\">\n          <div class=\"ryd-analytics__chart-meta\" id=\"ryd-analytics-activity-meta\" hidden>\n            <span class=\"ryd-analytics__bucket-label\" id=\"ryd-analytics-bucket-label\"></span>\n          </div>\n          <div class=\"ryd-analytics__chart\" id=\"ryd-analytics-activity\"></div>  \n        </div>\n      </section>\n      <section class=\"ryd-analytics__section\" data-chart=\"lists\">\n        <div class=\"ryd-analytics__section-header\">\n          <h3 class=\"ryd-analytics__section-title\">${localize(\"premiumAnalytics_listsTitle\")}</h3>\n          <button class=\"ryd-analytics__section-expand\" type=\"button\" data-chart=\"lists\" aria-expanded=\"false\">${expandLabel}</button> \n        </div>\n        <div class=\"ryd-analytics__section-content\">\n          <div class=\"ryd-analytics__lists\">\n            <div class=\"ryd-analytics__list\" data-type=\"likes\">\n              <h4>${localize(\"premiumAnalytics_listLikesTitle\")}</h4>\n              <ul class=\"ryd-analytics__list-items\" id=\"ryd-analytics-top-likes\"></ul>\n            </div>\n            <div class=\"ryd-analytics__list\" data-type=\"dislikes\">\n              <h4>${localize(\"premiumAnalytics_listDislikesTitle\")}</h4>\n              <ul class=\"ryd-analytics__list-items\" id=\"ryd-analytics-top-dislikes\"></ul>\n            </div>\n          </div>\n        </div>\n      </section>\n      <section class=\"ryd-analytics__section\" data-chart=\"map\">\n        <div class=\"ryd-analytics__section-header\">\n          <h3 class=\"ryd-analytics__section-title\">${localize(\"premiumAnalytics_mapTitle\")}</h3>\n          <button class=\"ryd-analytics__section-expand\" type=\"button\" data-chart=\"map\" aria-expanded=\"false\">${expandLabel}</button>\n        </div>\n        <div class=\"ryd-analytics__section-content\">\n          <div class=\"ryd-analytics__map-block\">\n            <div class=\"ryd-analytics__map-controls\">\n              <span class=\"ryd-analytics__map-label\">${localize(\"premiumAnalytics_mapMetricLabel\")}</span>\n              <div class=\"ryd-analytics__modes\" role=\"tablist\">${modeControls}</div>\n              <button class=\"ryd-analytics__map-reset\" type=\"button\" id=\"ryd-analytics-map-reset\" hidden>${localize(\n                \"premiumAnalytics_mapReset\",\n              )}</button>\n            </div>\n            <div class=\"ryd-analytics__map\" id=\"ryd-analytics-map\"></div>\n          </div>\n        </div>\n      </section>\n      <div class=\"ryd-analytics__footer\" id=\"ryd-analytics-footer\"></div>\n    </div>\n  `;\n}\n\nfunction createModeControls() {\n  const modes = [\n    { key: \"likes\", label: localize(\"premiumAnalytics_modeLikes\") },\n    { key: \"dislikes\", label: localize(\"premiumAnalytics_modeDislikes\") },\n    { key: \"ratio\", label: localize(\"premiumAnalytics_modeRatio\") },\n  ];\n\n  return modes\n    .map(({ key, label }) => {\n      const isActive = analyticsState.currentMode === key;\n      return `<button class=\"ryd-mode${isActive ? \" is-active\" : \"\"}\" data-mode=\"${key}\">${label}</button>`;\n    })\n    .join(\"\");\n}\n\nfunction createRangeAnchorControls() {\n  const resolvedAnchor = resolveRangeAnchor();\n  const buttons = RANGE_ANCHORS.map((anchor) => {\n    const label =\n      anchor === \"last\" ? localize(\"premiumAnalytics_windowLatest\") : localize(\"premiumAnalytics_windowFirst\");\n    const isActive = anchor === resolvedAnchor;\n    const title =\n      anchor === \"last\"\n        ? localize(\"premiumAnalytics_windowLatestTitle\")\n        : localize(\"premiumAnalytics_windowFirstTitle\");\n    return `<button class=\"ryd-range-anchor${isActive ? \" is-active\" : \"\"}\" type=\"button\" data-anchor=\"${anchor}\" title=\"${title}\">${label}</button>`;\n  }).join(\"\");\n\n  return `\n    <div class=\"ryd-analytics__window\" role=\"group\" aria-label=\"${localize(\"premiumAnalytics_windowGroupLabel\")}\">\n      <span class=\"ryd-analytics__window-label\">${localize(\"premiumAnalytics_windowGroupLabel\")}</span>\n      <div class=\"ryd-analytics__window-toggle\">${buttons}</div>\n      <span class=\"ryd-analytics__window-hint\" id=\"ryd-analytics-range-window\">${formatRangeWindowLabel(resolvedAnchor)}</span>\n    </div>\n  `;\n}\n\nfunction formatRangeLabel(days) {\n  return days === 0\n    ? localize(\"premiumAnalytics_rangeLabelAllTime\")\n    : localize(\"premiumAnalytics_rangeLabelDays\", [`${days}`]);\n}\n\nfunction resolveRangeAnchor() {\n  const anchor = typeof analyticsState.rangeAnchor === \"string\" ? analyticsState.rangeAnchor.toLowerCase() : \"\";\n  return anchor === \"last\" ? \"last\" : \"first\";\n}\n\nfunction formatRangeWindowLabel(anchor) {\n  if (\n    analyticsState.usingCustomRange &&\n    analyticsState.selectionRange?.from != null &&\n    analyticsState.selectionRange?.to != null\n  ) {\n    return localize(\"premiumAnalytics_windowSummaryCustom\");\n  }\n\n  const range = analyticsState.currentRange;\n  if (!Number.isFinite(range) || range <= 0) {\n    return localize(\"premiumAnalytics_windowSummaryAllTime\");\n  }\n  const absoluteRange = Math.max(0, Math.round(range));\n  if (absoluteRange <= 0) {\n    return localize(\"premiumAnalytics_windowSummaryAllTime\");\n  }\n  const dayLabel = formatDayCount(absoluteRange);\n  return anchor === \"last\"\n    ? localize(\"premiumAnalytics_windowSummaryLast\", [dayLabel])\n    : localize(\"premiumAnalytics_windowSummaryFirst\", [dayLabel]);\n}\n\nfunction formatDayCount(count) {\n  const numeric = Number(count);\n  const formatted = Number.isFinite(numeric) ? numeric.toLocaleString() : `${count}`;\n  if (numeric === 1) {\n    return localize(\"premiumAnalytics_daysSingular\", [formatted]);\n  }\n  return localize(\"premiumAnalytics_daysPlural\", [formatted]);\n}\n\nfunction applyExpansionState() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  panel.classList.toggle(\"is-expanded\", analyticsState.panelExpanded);\n  const expandButton = panel.querySelector(\".ryd-analytics__expand\");\n  if (analyticsState.panelExpanded) {\n    positionExpandedPanel(panel, expandButton);\n  } else {\n    resetPanelPosition(panel, expandButton);\n  }\n}\n\nexport function applyChartExpansionState() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n\n  const expandedChart = analyticsState.expandedChart;\n  const sections = panel.querySelectorAll(\".ryd-analytics__section\");\n\n  sections.forEach((section) => {\n    const chartKey = section.dataset.chart;\n    const button = section.querySelector(\".ryd-analytics__section-expand\");\n    const isMatch = expandedChart && chartKey === expandedChart;\n\n    section.classList.toggle(\"is-expanded\", isMatch);\n\n    if (isMatch) {\n      positionExpandedSection(section, button);\n    } else {\n      resetSectionPosition(section, button);\n    }\n  });\n}\n\nfunction positionExpandedPanel(panel, button) {\n  const anchorRect = resolveAnchorRect();\n  panel.style.position = \"fixed\";\n  panel.style.top = \"72px\";\n  applyAnchorPosition(panel, anchorRect, { maxWidthFallback: \"min(960px, calc(100vw - 48px))\" });\n  panel.style.maxHeight = \"calc(100vh - 96px)\";\n  panel.style.overflowY = \"auto\";\n  panel.style.zIndex = \"2147483646\";\n  if (button) {\n    button.textContent = localize(\"premiumAnalytics_collapse\");\n    button.setAttribute(\"aria-expanded\", \"true\");\n    button.classList.add(\"is-active\");\n  }\n}\n\nfunction resetPanelPosition(panel, button) {\n  panel.removeAttribute(\"style\");\n  if (button) {\n    button.textContent = localize(\"premiumAnalytics_expand\");\n    button.setAttribute(\"aria-expanded\", \"false\");\n    button.classList.remove(\"is-active\");\n  }\n}\n\nfunction positionExpandedSection(section, button) {\n  const anchorRect = resolveAnchorRect();\n  section.style.position = \"fixed\";\n  section.style.top = \"72px\";\n  applyAnchorPosition(section, anchorRect, { maxWidthFallback: \"min(960px, calc(100vw - 48px))\" });\n  section.style.maxHeight = \"calc(100vh - 120px)\";\n  section.style.zIndex = \"2147483647\";\n  section.style.background = \"var(--yt-spec-base-background, #202020)\";\n  section.style.boxShadow = \"0 30px 60px rgba(0, 0, 0, 0.45)\";\n  section.style.padding = \"16px\";\n  section.style.overflowY = \"auto\";\n\n  const viewportHeight = window.innerHeight || 0;\n  const computed = viewportHeight > 0 ? Math.min(viewportHeight * 0.8, viewportHeight - 160) : NaN;\n  const targetHeight = Math.max(360, Number.isFinite(computed) && computed > 0 ? computed : 560);\n  section.style.height = `${Math.round(targetHeight)}px`;\n\n  section.querySelectorAll(\".ryd-analytics__chart, .ryd-analytics__map\").forEach((element) => {\n    const chartHeight = Math.max(320, Math.round(targetHeight - 120));\n    element.style.height = `${chartHeight}px`;\n  });\n  if (button) {\n    button.textContent = localize(\"premiumAnalytics_collapse\");\n    button.setAttribute(\"aria-expanded\", \"true\");\n    button.classList.add(\"is-active\");\n  }\n}\n\nfunction resetSectionPosition(section, button) {\n  section.removeAttribute(\"style\");\n  section\n    .querySelectorAll(\".ryd-analytics__chart, .ryd-analytics__map\")\n    .forEach((element) => element.removeAttribute(\"style\"));\n  if (button) {\n    button.textContent = localize(\"premiumAnalytics_expand\");\n    button.setAttribute(\"aria-expanded\", \"false\");\n    button.classList.remove(\"is-active\");\n  }\n}\n\nfunction applyLoadingState() {\n  const panel = analyticsState.panelElement;\n  if (!panel) return;\n  panel.classList.toggle(\"is-loading\", analyticsState.isLoading);\n  panel.setAttribute(\"aria-busy\", analyticsState.isLoading ? \"true\" : \"false\");\n}\n\nfunction formatSelectedPeriod() {\n  const { usingCustomRange, selectionRange, currentRange } = analyticsState;\n\n  if (usingCustomRange && selectionRange?.from && selectionRange?.to) {\n    const from = formatDate(selectionRange.from);\n    const to = formatDate(selectionRange.to);\n    if (from && to) {\n      return from === to ? from : `${from} – ${to}`;\n    }\n  }\n\n  return formatRangeWindowLabel(resolveRangeAnchor());\n}\n\nfunction formatDate(ms) {\n  if (!Number.isFinite(ms)) return null;\n  const date = new Date(ms);\n  if (Number.isNaN(date.getTime())) return null;\n  return date.toLocaleDateString(undefined, { month: \"short\", day: \"numeric\", year: \"numeric\" });\n}\n\nfunction resolveAnchorRect() {\n  const selectors = [\n    \"#primary-inner\",\n    \"#columns #primary-inner\",\n    \"#primary\",\n    \"#columns #primary\",\n    \"ytd-watch-flexy #primary\",\n    \"#columns\",\n    \"ytd-watch-flexy\",\n  ];\n\n  for (const selector of selectors) {\n    const element = document.querySelector(selector);\n    if (!element || typeof element.getBoundingClientRect !== \"function\") continue;\n    const rect = element.getBoundingClientRect();\n    if (rect && typeof rect.width === \"number\" && rect.width > 1) {\n      return rect;\n    }\n  }\n\n  return null;\n}\n\nfunction applyAnchorPosition(target, rect, { maxWidthFallback }) {\n  if (rect) {\n    target.style.left = `${Math.round(rect.left)}px`;\n    target.style.width = `${Math.round(rect.width)}px`;\n    target.style.transform = \"\";\n    return;\n  }\n\n  target.style.left = \"50%\";\n  target.style.width = maxWidthFallback;\n  target.style.transform = \"translateX(-50%)\";\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/panel.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\nimport { analyticsState, resetStateForVideo } from \"./state\";\nimport {\n  configurePanelCallbacks,\n  ensurePanel,\n  updateRangeButtons,\n  updateRangeAnchorButtons,\n  updateModeButtons,\n  setListsLoading,\n  renderSummary,\n  setFooterMessage,\n  setPanelExpanded,\n  setLoadingState,\n  applyChartExpansionState,\n} from \"./panel\";\n\nconst enMessages = require(\"../../_locales/en/messages.json\");\n\nfunction getMessage(key, substitutions) {\n  const entry = enMessages[key];\n  if (!entry) {\n    return key;\n  }\n  let message = entry.message ?? \"\";\n  if (substitutions == null) {\n    return message;\n  }\n  const values = Array.isArray(substitutions) ? substitutions : [substitutions];\n  values.forEach((value, index) => {\n    const replacement = value != null ? `${value}` : \"\";\n    message = message.replace(new RegExp(`\\\\$${index + 1}`, \"g\"), replacement);\n  });\n  return message;\n}\n\ndescribe(\"premiumAnalytics.panel\", () => {\n  function mountSecondaryContainer() {\n    document.body.innerHTML = `\n      <div id=\"columns\">\n        <div id=\"primary\" style=\"width: 640px; position: relative;\"></div>\n        <div id=\"secondary\">\n          <div id=\"secondary-inner\"></div>\n        </div>\n      </div>\n    `;\n  }\n\n  beforeEach(() => {\n    global.chrome = {\n      i18n: {\n        getMessage,\n      },\n    };\n    resetStateForVideo();\n    analyticsState.panelElement = null;\n    analyticsState.currentRange = 30;\n    analyticsState.rangeAnchor = \"first\";\n    analyticsState.currentMode = \"ratio\";\n    mountSecondaryContainer();\n  });\n\n  afterEach(() => {\n    document.body.innerHTML = \"\";\n    delete global.chrome;\n  });\n\n  it(\"configures callbacks and triggers them via UI interactions\", () => {\n    const rangeSpy = jest.fn();\n    const anchorSpy = jest.fn();\n    const modeSpy = jest.fn();\n    const expandSpy = jest.fn();\n\n    configurePanelCallbacks({\n      onRangePreset: rangeSpy,\n      onRangeAnchorChange: anchorSpy,\n      onModeChange: modeSpy,\n      onChartExpand: expandSpy,\n    });\n    const panel = ensurePanel();\n\n    const rangeButton = panel.querySelector(\".ryd-range[data-range='7']\");\n    rangeButton.click();\n    expect(rangeSpy).toHaveBeenCalledWith(7);\n\n    const anchorButton = panel.querySelector(\".ryd-range-anchor[data-anchor='last']\");\n    anchorButton.click();\n    expect(anchorSpy).toHaveBeenCalledWith(\"last\");\n\n    const modeButton = panel.querySelector(\".ryd-mode[data-mode='likes']\");\n    modeButton.click();\n    expect(modeSpy).toHaveBeenCalledWith(\"likes\");\n\n    const activityExpand = panel.querySelector(\".ryd-analytics__section-expand[data-chart='activity']\");\n    activityExpand.click();\n    expect(expandSpy).toHaveBeenCalledWith(\"activity\");\n\n    const listsExpand = panel.querySelector(\".ryd-analytics__section-expand[data-chart='lists']\");\n    listsExpand.click();\n    expect(expandSpy).toHaveBeenCalledWith(\"lists\");\n  });\n\n  it(\"creates the analytics panel inside the secondary column\", () => {\n    const panel = ensurePanel();\n\n    expect(panel).not.toBeNull();\n    expect(document.querySelector(\"#secondary-inner\").firstChild).toBe(panel);\n    expect(panel.querySelector(\".ryd-analytics__expand\")).toBeNull();\n    expect(panel.querySelectorAll(\".ryd-analytics__section-expand\")).toHaveLength(3);\n  });\n\n  it(\"renders preset range buttons without custom toggle\", () => {\n    const panel = ensurePanel();\n    const rangeButtons = [...panel.querySelectorAll(\".ryd-range\")];\n    const values = rangeButtons.map((btn) => btn.dataset.range);\n    expect(values).toEqual([\"7\", \"30\", \"90\", \"0\"]);\n    expect(rangeButtons.every((btn) => btn.dataset.range !== \"custom\")).toBe(true);\n    const anchorButtons = [...panel.querySelectorAll(\".ryd-range-anchor\")];\n    const anchors = anchorButtons.map((btn) => btn.dataset.anchor);\n    expect(anchors).toEqual([\"first\", \"last\"]);\n  });\n\n  it(\"updates range button active states based on state\", () => {\n    const panel = ensurePanel();\n    analyticsState.currentRange = 7;\n\n    updateRangeButtons();\n\n    const activeRanges = [...panel.querySelectorAll(\".ryd-range.is-active\")].map((btn) => btn.dataset.range);\n    expect(activeRanges).toContain(\"7\");\n    expect(activeRanges).toHaveLength(1);\n  });\n\n  it(\"updates mode button active states\", () => {\n    const panel = ensurePanel();\n    analyticsState.currentMode = \"likes\";\n\n    updateModeButtons();\n\n    const likesBtn = panel.querySelector(\".ryd-mode[data-mode='likes']\");\n    const ratioBtn = panel.querySelector(\".ryd-mode[data-mode='ratio']\");\n    expect(likesBtn.classList.contains(\"is-active\")).toBe(true);\n    expect(ratioBtn.classList.contains(\"is-active\")).toBe(false);\n  });\n\n  it(\"applies loading styles\", () => {\n    const panel = ensurePanel();\n    setLoadingState(true);\n\n    expect(panel.classList.contains(\"is-loading\")).toBe(true);\n    expect(panel.getAttribute(\"aria-busy\")).toBe(\"true\");\n\n    setLoadingState(false);\n    expect(panel.classList.contains(\"is-loading\")).toBe(false);\n    expect(panel.getAttribute(\"aria-busy\")).toBe(\"false\");\n  });\n\n  it(\"shows loading state when requested\", () => {\n    const panel = ensurePanel();\n    setListsLoading();\n\n    expect(panel.classList.contains(\"is-loading\")).toBe(true);\n  });\n\n  it(\"renders summary footer with totals and selected period\", () => {\n    ensurePanel();\n    renderSummary({\n      totalLikes: 1200,\n      totalDislikes: 300,\n      uniqueIps: 45,\n      countriesRepresented: 12,\n    });\n\n    const footer = document.querySelector(\"#ryd-analytics-footer\");\n    const likes = footer.querySelector(\".ryd-analytics__totals-likes\");\n    const dislikes = footer.querySelector(\".ryd-analytics__totals-dislikes\");\n    const summaryMeta = footer.querySelector(\".ryd-analytics__summary-meta\");\n    const periodLabel = footer.querySelector(\".ryd-analytics__period-label\");\n\n    expect(likes?.textContent).toBe(getMessage(\"premiumAnalytics_totalsLikes\", [\"1,200\"]));\n    expect(dislikes?.textContent).toBe(getMessage(\"premiumAnalytics_totalsDislikes\", [\"300\"]));\n    expect(summaryMeta?.textContent?.trim()).toBe(getMessage(\"premiumAnalytics_summaryMeta\", [\"1,500\", \"12\", \"45\"]));\n    const dayLabel = getMessage(\"premiumAnalytics_daysPlural\", [\"30\"]);\n    const windowLabel = getMessage(\"premiumAnalytics_windowSummaryFirst\", [dayLabel]);\n    expect(periodLabel?.textContent).toBe(getMessage(\"premiumAnalytics_selectedPeriod\", [windowLabel]));\n  });\n\n  it(\"updates anchor toggle state and descriptor\", () => {\n    const panel = ensurePanel();\n    analyticsState.rangeAnchor = \"last\";\n    analyticsState.currentRange = 30;\n    analyticsState.usingCustomRange = false;\n\n    updateRangeAnchorButtons();\n\n    const activeAnchor = panel.querySelector(\".ryd-range-anchor.is-active\");\n    expect(activeAnchor?.dataset.anchor).toBe(\"last\");\n    const descriptor = panel.querySelector(\"#ryd-analytics-range-window\");\n    const lastLabel = getMessage(\"premiumAnalytics_daysPlural\", [\"30\"]);\n    expect(descriptor?.textContent).toBe(getMessage(\"premiumAnalytics_windowSummaryLast\", [lastLabel]));\n  });\n\n  it(\"sets footer message directly\", () => {\n    ensurePanel();\n    setFooterMessage(\"Custom message\");\n\n    expect(document.querySelector(\"#ryd-analytics-footer\").textContent).toBe(\"Custom message\");\n  });\n\n  it(\"setPanelExpanded respects provided state\", () => {\n    const panel = ensurePanel();\n    setPanelExpanded(true);\n    expect(panel.classList.contains(\"is-expanded\")).toBe(true);\n    setPanelExpanded(false);\n    expect(panel.classList.contains(\"is-expanded\")).toBe(false);\n  });\n\n  it(\"applies chart expansion state exclusively\", () => {\n    const panel = ensurePanel();\n    const activitySection = panel.querySelector(\".ryd-analytics__section[data-chart='activity']\");\n    const listsSection = panel.querySelector(\".ryd-analytics__section[data-chart='lists']\");\n    const mapSection = panel.querySelector(\".ryd-analytics__section[data-chart='map']\");\n\n    analyticsState.expandedChart = \"activity\";\n    applyChartExpansionState();\n    expect(activitySection.classList.contains(\"is-expanded\")).toBe(true);\n    expect(listsSection.classList.contains(\"is-expanded\")).toBe(false);\n    expect(mapSection.classList.contains(\"is-expanded\")).toBe(false);\n\n    analyticsState.expandedChart = \"lists\";\n    applyChartExpansionState();\n    expect(activitySection.classList.contains(\"is-expanded\")).toBe(false);\n    expect(listsSection.classList.contains(\"is-expanded\")).toBe(true);\n    expect(mapSection.classList.contains(\"is-expanded\")).toBe(false);\n\n    analyticsState.expandedChart = \"map\";\n    applyChartExpansionState();\n    expect(activitySection.classList.contains(\"is-expanded\")).toBe(false);\n    expect(listsSection.classList.contains(\"is-expanded\")).toBe(false);\n    expect(mapSection.classList.contains(\"is-expanded\")).toBe(true);\n\n    analyticsState.expandedChart = null;\n    applyChartExpansionState();\n    expect(activitySection.classList.contains(\"is-expanded\")).toBe(false);\n    expect(listsSection.classList.contains(\"is-expanded\")).toBe(false);\n    expect(mapSection.classList.contains(\"is-expanded\")).toBe(false);\n  });\n\n  it(\"positions expanded sections using the first anchor with a valid width\", () => {\n    const columns = document.querySelector(\"#columns\");\n    const primary = document.querySelector(\"#primary\");\n    const primaryInner = document.createElement(\"div\");\n    primaryInner.id = \"primary-inner\";\n    columns.insertBefore(primaryInner, primary);\n\n    Object.defineProperty(primary, \"getBoundingClientRect\", {\n      value: jest.fn(() => ({ left: 0, top: 0, width: 0, height: 720 })),\n      configurable: true,\n    });\n\n    Object.defineProperty(primaryInner, \"getBoundingClientRect\", {\n      value: jest.fn(() => ({ left: 120, top: 72, width: 960, height: 720 })),\n      configurable: true,\n    });\n\n    const panel = ensurePanel();\n    analyticsState.expandedChart = \"activity\";\n    applyChartExpansionState();\n\n    const section = panel.querySelector(\".ryd-analytics__section[data-chart='activity']\");\n    expect(section.classList.contains(\"is-expanded\")).toBe(true);\n    expect(section.style.left).toBe(\"120px\");\n    expect(section.style.width).toBe(\"960px\");\n    expect(section.style.transform).toBe(\"\");\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/render.js",
    "content": "import { analyticsState } from \"./state\";\nimport {\n  ensurePanel,\n  renderSummary,\n  updateRangeButtons,\n  updateRangeAnchorButtons,\n  updateModeButtons,\n  applyChartExpansionState,\n} from \"./panel\";\nimport { renderActivityChart, resizeActivityChart } from \"./activity\";\nimport { ensureMapChart, renderMap, resizeMapChart } from \"./map\";\nimport { toEpoch } from \"./utils\";\nimport { updateCountryList } from \"./lists\";\n\nimport { MS_PER_DAY } from \"./constants\";\n\nfunction renderAnalytics(data) {\n  const panel = ensurePanel();\n  if (!panel) {\n    setTimeout(() => renderAnalytics(data), 200);\n    return;\n  }\n\n  const previousRangeDays = analyticsState.currentRange;\n  const wasUsingCustomRange = analyticsState.usingCustomRange;\n\n  analyticsState.availableRange = {\n    min: toEpoch(data?.timeSeries?.totalRangeStartUtc),\n    max: toEpoch(data?.timeSeries?.totalRangeEndUtc),\n  };\n\n  analyticsState.selectionRange = {\n    from: toEpoch(data?.timeSeries?.selectedRangeStartUtc),\n    to: toEpoch(data?.timeSeries?.selectedRangeEndUtc),\n  };\n\n  analyticsState.customSelection = wasUsingCustomRange ? { ...analyticsState.selectionRange } : null;\n  analyticsState.usingCustomRange = analyticsState.customSelection != null;\n  analyticsState.pendingSelection = null;\n\n  if (analyticsState.selectionRange.from != null && analyticsState.selectionRange.to != null) {\n    if (analyticsState.usingCustomRange) {\n      analyticsState.currentRange = Math.max(\n        0,\n        Math.round((analyticsState.selectionRange.to - analyticsState.selectionRange.from) / MS_PER_DAY),\n      );\n    } else {\n      analyticsState.currentRange = previousRangeDays;\n    }\n  }\n\n  analyticsState.suppressZoomEvents = true;\n\n  applyChartExpansionState();\n  renderActivityChart(data?.timeSeries);\n\n  analyticsState.latestCountries = data?.geo?.countries ?? [];\n  analyticsState.latestSubdivisions = data?.geo?.subdivisions ?? [];\n\n  if (analyticsState.mapView === \"subdivision\") {\n    const focusCode = typeof analyticsState.mapFocusCountry === \"string\" ? analyticsState.mapFocusCountry.toUpperCase() : \"\";\n    const hasFocusedSubdivisions =\n      focusCode &&\n      analyticsState.latestSubdivisions.some(\n        (entry) => typeof entry?.countryCode === \"string\" && entry.countryCode.toUpperCase() === focusCode,\n      );\n    if (!hasFocusedSubdivisions) {\n      analyticsState.mapView = \"world\";\n      analyticsState.mapFocusCountry = null;\n    }\n  }\n\n  updateCountryList(panel.querySelector(\"#ryd-analytics-top-likes\"), data?.geo?.topLikes ?? [], \"likes\");\n  updateCountryList(panel.querySelector(\"#ryd-analytics-top-dislikes\"), data?.geo?.topDislikes ?? [], \"dislikes\");\n\n  renderSummary(data?.summary);\n  updateRangeButtons();\n  updateRangeAnchorButtons();\n  updateModeButtons();\n\n  ensureMapChart();\n  renderMap();\n\n  if (analyticsState.expandedChart === \"activity\") {\n    resizeActivityChart();\n  } else if (analyticsState.expandedChart === \"map\") {\n    resizeMapChart();\n    resizeActivityChart();\n  } else if (analyticsState.expandedChart === \"lists\") {\n    resizeActivityChart();\n    resizeMapChart();\n  }\n}\n\nexport { renderAnalytics };\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/requests.js",
    "content": "import { getApiEndpoint } from \"../config\";\nimport { analyticsState, COUNTRY_LIMIT } from \"./state\";\nimport { ensurePanel, updateRangeButtons, updateRangeAnchorButtons, setFooterMessage, setLoadingState } from \"./panel\";\nimport { debounce, safeJson } from \"./utils\";\nimport { logFetchRequest } from \"./logging\";\nimport { localize } from \"../utils\";\n\nimport { MS_PER_DAY } from \"./constants\";\nimport { renderAnalytics } from \"./render\";\n\nconst HOURLY_THRESHOLD_DAYS = 7;\nconst HOURLY_THRESHOLD_MS = HOURLY_THRESHOLD_DAYS * MS_PER_DAY;\nconst MS_PER_HOUR = 60 * 60 * 1000;\n\nfunction requestAnalytics({ selection } = {}) {\n  const state = analyticsState;\n\n  if (!state.currentVideoId || !state.sessionToken || !state.sessionActive) {\n    return;\n  }\n\n  if (state.membershipTier !== \"premium\") {\n    return;\n  }\n\n  ensurePanel();\n  setFooterMessage(\"Loading insights…\");\n  setLoadingState(true);\n  updateRangeButtons();\n  updateRangeAnchorButtons();\n\n  const effectiveSelection = normalizeSelection(selection ?? state.customSelection);\n  const bucket = resolveBucket(effectiveSelection, state.currentRange);\n  const rangeAnchor = resolveAnchor();\n  state.rangeAnchor = rangeAnchor;\n\n  const params = new URLSearchParams();\n  params.set(\"bucket\", bucket);\n  params.set(\"countryLimit\", `${COUNTRY_LIMIT}`);\n\n  let requestKey;\n  if (effectiveSelection) {\n    const startIso = msToIso(effectiveSelection.from);\n    const endIso = msToIso(effectiveSelection.to);\n    if (startIso && endIso) {\n      params.set(\"selectedRangeStartUtc\", startIso);\n      params.set(\"selectedRangeEndUtc\", endIso);\n      requestKey = `${state.currentVideoId}:${startIso}:${endIso}`;\n      state.usingCustomRange = true;\n      state.currentRange = Math.max(0, Math.round((effectiveSelection.to - effectiveSelection.from) / MS_PER_DAY));\n      state.customSelection = { ...effectiveSelection };\n      state.selectionRange = { ...effectiveSelection };\n    } else {\n      params.set(\"rangeDays\", `${state.currentRange}`);\n      params.set(\"rangeAnchor\", rangeAnchor);\n      requestKey = `${state.currentVideoId}:${state.currentRange}:${rangeAnchor}`;\n      state.usingCustomRange = false;\n      state.customSelection = null;\n    }\n  } else {\n    params.set(\"rangeDays\", `${state.currentRange}`);\n    params.set(\"rangeAnchor\", rangeAnchor);\n    requestKey = `${state.currentVideoId}:${state.currentRange}:${rangeAnchor}`;\n    state.usingCustomRange = false;\n    state.customSelection = null;\n  }\n\n  state.pendingSelection = effectiveSelection || null;\n  state.activeRequestKey = requestKey;\n  state.latestBucketMs = bucket === \"hour\" ? MS_PER_HOUR : MS_PER_DAY;\n\n  logFetchRequest(state.currentVideoId, params);\n\n  const url = getApiEndpoint(`/api/patreon/analytics/video/${state.currentVideoId}?${params.toString()}`);\n\n  fetch(url, {\n    headers: {\n      Authorization: `Bearer ${state.sessionToken}`,\n      \"Content-Type\": \"application/json\",\n    },\n    credentials: \"omit\",\n  })\n    .then(async (response) => {\n      if (!response.ok) {\n        const payload = await safeJson(response);\n        handleError(response.status, payload?.error);\n        return null;\n      }\n      return response.json();\n    })\n    .then((data) => {\n      if (!data || analyticsState.activeRequestKey !== requestKey) return;\n      renderAnalytics(data);\n    })\n    .catch((error) => {\n      console.error(\"Premium analytics failed\", error);\n      if (analyticsState.activeRequestKey === requestKey) {\n        handleError(0, \"network_error\");\n      }\n    })\n    .finally(() => {\n      if (analyticsState.activeRequestKey === requestKey) {\n        setLoadingState(false);\n      }\n    });\n}\n\nfunction commitSelectionFetch(selection) {\n  requestAnalytics({ selection });\n}\n\nconst debounceSelectionFetch = debounce(commitSelectionFetch, 400);\n\nfunction scheduleSelectionFetch(selection) {\n  debounceSelectionFetch(selection);\n}\n\nfunction normalizeSelection(selection) {\n  if (!selection) return null;\n  const from = Number(selection.from);\n  const to = Number(selection.to);\n  if (!Number.isFinite(from) || !Number.isFinite(to) || to <= from) {\n    return null;\n  }\n  return { from, to };\n}\n\nfunction msToIso(ms) {\n  if (!Number.isFinite(ms)) return null;\n  const date = new Date(ms);\n  if (Number.isNaN(date.getTime())) return null;\n  return date.toISOString();\n}\n\nfunction handleError(status, code) {\n  let message;\n  if (status === 409 || code === \"session_upgrade_required\") {\n    message = localize(\"premiumAnalytics_errorReauth\");\n  } else if (status === 403 && code === \"membership_tier_insufficient\") {\n    message = localize(\"premiumTierNotice_message\");\n  } else if (status === 403 || code === \"membership_inactive\") {\n    message = localize(\"premiumAnalytics_errorInactive\");\n  } else if (status === 401 || code === \"invalid_session\") {\n    message = localize(\"premiumAnalytics_errorSession\");\n  } else if (code === \"analytics_failure\") {\n    message = localize(\"premiumAnalytics_errorBackend\");\n  } else if (code === \"network_error\") {\n    message = localize(\"premiumAnalytics_errorNetwork\");\n  } else {\n    message = localize(\"premiumAnalytics_errorGeneric\");\n  }\n\n  setFooterMessage(message);\n  setLoadingState(false);\n}\n\nexport { requestAnalytics, scheduleSelectionFetch, normalizeSelection };\n\nfunction resolveAnchor() {\n  const anchor = typeof analyticsState.rangeAnchor === \"string\" ? analyticsState.rangeAnchor.toLowerCase() : \"\";\n  return anchor === \"last\" ? \"last\" : \"first\";\n}\n\nfunction resolveBucket(selection, rangeDays) {\n  if (selection) {\n    const durationMs = Number(selection.to) - Number(selection.from);\n    if (Number.isFinite(durationMs) && durationMs >= 0 && durationMs <= HOURLY_THRESHOLD_MS) {\n      return \"hour\";\n    }\n    return \"day\";\n  }\n\n  if (Number.isFinite(rangeDays) && rangeDays > 0 && rangeDays <= HOURLY_THRESHOLD_DAYS) {\n    return \"hour\";\n  }\n\n  return \"day\";\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/state.js",
    "content": "export const RANGE_OPTIONS = [7, 30, 90, 0];\nexport const RANGE_ANCHORS = [\"first\", \"last\"];\nexport const COUNTRY_LIMIT = 12;\n\nexport const analyticsState = {\n  initialized: false,\n  panelElement: null,\n  panelExpanded: false,\n  isLoading: false,\n  activityChart: null,\n  mapChart: null,\n  currentVideoId: null,\n  currentRange: 30,\n  rangeAnchor: \"first\",\n  currentMode: \"ratio\",\n  activeRequestKey: null,\n  latestCountries: [],\n  latestSubdivisions: [],\n  sessionToken: null,\n  sessionActive: false,\n  membershipTier: \"none\",\n  latestSeriesPoints: [],\n  latestTimeAxis: [],\n  latestBucketMs: 60 * 60 * 1000,\n  latestBucketLabel: null,\n  chartTimeBounds: { min: null, max: null },\n  globalTimeBounds: { min: null, max: null },\n  availableRange: { min: null, max: null },\n  selectionRange: { from: null, to: null },\n  customSelection: null,\n  suppressZoomEvents: false,\n  zoomListenerRegistered: false,\n  usingCustomRange: false,\n  pendingSelection: null,\n  expandedChart: null,\n  mapView: \"world\",\n  mapFocusCountry: null,\n};\n\nexport function resetStateForVideo() {\n  analyticsState.latestSeriesPoints = [];\n  analyticsState.latestTimeAxis = [];\n  analyticsState.latestBucketMs = 60 * 60 * 1000;\n  analyticsState.latestBucketLabel = null;\n  analyticsState.chartTimeBounds = { min: null, max: null };\n  analyticsState.globalTimeBounds = { min: null, max: null };\n  analyticsState.availableRange = { min: null, max: null };\n  analyticsState.selectionRange = { from: null, to: null };\n  analyticsState.customSelection = null;\n  analyticsState.suppressZoomEvents = false;\n  analyticsState.pendingSelection = null;\n  analyticsState.usingCustomRange = false;\n  analyticsState.expandedChart = null;\n  analyticsState.latestCountries = [];\n  analyticsState.latestSubdivisions = [];\n  analyticsState.mapView = \"world\";\n  analyticsState.mapFocusCountry = null;\n}\n\nexport function resetSessionState() {\n  analyticsState.sessionToken = null;\n  analyticsState.sessionActive = false;\n  analyticsState.membershipTier = \"none\";\n}\n\nexport function setSession(token, isActive, membershipTier = \"none\") {\n  analyticsState.sessionToken = token;\n  analyticsState.sessionActive = !!isActive;\n  analyticsState.membershipTier = typeof membershipTier === \"string\" ? membershipTier : \"none\";\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/state.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\nimport {\n  analyticsState,\n  resetStateForVideo,\n  resetSessionState,\n  setSession,\n  RANGE_OPTIONS,\n  RANGE_ANCHORS,\n  COUNTRY_LIMIT,\n} from \"./state\";\n\nfunction resetGlobals() {\n  analyticsState.initialized = false;\n  analyticsState.panelElement = null;\n  analyticsState.panelExpanded = false;\n  analyticsState.isLoading = false;\n  analyticsState.activityChart = null;\n  analyticsState.mapChart = null;\n  analyticsState.currentVideoId = null;\n  analyticsState.currentRange = 30;\n  analyticsState.rangeAnchor = \"first\";\n  analyticsState.currentMode = \"ratio\";\n  analyticsState.activeRequestKey = null;\n  analyticsState.latestCountries = [];\n  analyticsState.latestSubdivisions = [];\n  analyticsState.sessionToken = null;\n  analyticsState.sessionActive = false;\n  analyticsState.membershipTier = \"none\";\n  analyticsState.latestSeriesPoints = [];\n  analyticsState.latestTimeAxis = [];\n  analyticsState.latestBucketMs = 60 * 60 * 1000;\n  analyticsState.chartTimeBounds = { min: null, max: null };\n  analyticsState.globalTimeBounds = { min: null, max: null };\n  analyticsState.availableRange = { min: null, max: null };\n  analyticsState.selectionRange = { from: null, to: null };\n  analyticsState.customSelection = null;\n  analyticsState.suppressZoomEvents = false;\n  analyticsState.zoomListenerRegistered = false;\n  analyticsState.usingCustomRange = false;\n  analyticsState.pendingSelection = null;\n  analyticsState.expandedChart = null;\n  analyticsState.mapView = \"world\";\n  analyticsState.mapFocusCountry = null;\n}\n\ndescribe(\"premiumAnalytics.state\", () => {\n  beforeEach(resetGlobals);\n\n  it(\"exposes configuration constants\", () => {\n    expect(RANGE_OPTIONS).toEqual([7, 30, 90, 0]);\n    expect(RANGE_ANCHORS).toEqual([\"first\", \"last\"]);\n    expect(COUNTRY_LIMIT).toBe(12);\n  });\n\n  describe(\"resetStateForVideo\", () => {\n    it(\"clears video-specific state and timers\", () => {\n      analyticsState.latestSeriesPoints = [{ foo: \"bar\" }];\n      analyticsState.latestTimeAxis = [1, 2, 3];\n      analyticsState.latestBucketMs = 1234;\n      analyticsState.chartTimeBounds = { min: 1, max: 2 };\n      analyticsState.globalTimeBounds = { min: 3, max: 4 };\n      analyticsState.availableRange = { min: 5, max: 6 };\n      analyticsState.selectionRange = { from: 7, to: 8 };\n      analyticsState.customSelection = { from: 7, to: 8 };\n      analyticsState.suppressZoomEvents = true;\n      analyticsState.pendingSelection = { from: 1, to: 2 };\n      analyticsState.usingCustomRange = true;\n      analyticsState.expandedChart = \"map\";\n      analyticsState.latestCountries = [{ countryCode: \"US\" }];\n      analyticsState.latestSubdivisions = [{ subdivisionCode: \"CA\" }];\n      analyticsState.mapView = \"subdivision\";\n      analyticsState.mapFocusCountry = \"US\";\n\n      resetStateForVideo();\n\n      expect(analyticsState.latestSeriesPoints).toEqual([]);\n      expect(analyticsState.latestTimeAxis).toEqual([]);\n      expect(analyticsState.latestBucketMs).toBe(60 * 60 * 1000);\n      expect(analyticsState.chartTimeBounds).toEqual({ min: null, max: null });\n      expect(analyticsState.globalTimeBounds).toEqual({ min: null, max: null });\n      expect(analyticsState.availableRange).toEqual({ min: null, max: null });\n      expect(analyticsState.selectionRange).toEqual({ from: null, to: null });\n      expect(analyticsState.customSelection).toBeNull();\n      expect(analyticsState.suppressZoomEvents).toBe(false);\n      expect(analyticsState.pendingSelection).toBeNull();\n      expect(analyticsState.usingCustomRange).toBe(false);\n      expect(analyticsState.expandedChart).toBeNull();\n      expect(analyticsState.latestCountries).toEqual([]);\n      expect(analyticsState.latestSubdivisions).toEqual([]);\n      expect(analyticsState.mapView).toBe(\"world\");\n      expect(analyticsState.mapFocusCountry).toBeNull();\n    });\n  });\n\n  describe(\"resetSessionState\", () => {\n    it(\"clears session token and status\", () => {\n      analyticsState.sessionToken = \"token\";\n      analyticsState.sessionActive = true;\n      analyticsState.membershipTier = \"supporter\";\n\n      resetSessionState();\n\n      expect(analyticsState.sessionToken).toBeNull();\n      expect(analyticsState.sessionActive).toBe(false);\n      expect(analyticsState.membershipTier).toBe(\"none\");\n    });\n  });\n\n  describe(\"setSession\", () => {\n    it(\"assigns session token and coerces active flag\", () => {\n      setSession(\"token\", \"truthy\", \"premium\");\n\n      expect(analyticsState.sessionToken).toBe(\"token\");\n      expect(analyticsState.sessionActive).toBe(true);\n      expect(analyticsState.membershipTier).toBe(\"premium\");\n\n      setSession(null, 0, null);\n      expect(analyticsState.sessionToken).toBeNull();\n      expect(analyticsState.sessionActive).toBe(false);\n      expect(analyticsState.membershipTier).toBe(\"none\");\n    });\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/teaser/index.js",
    "content": "import { getApiEndpoint, getChangelogUrl } from \"../../config\";\nimport { getBrowser, getVideoId, localize } from \"../../utils\";\nimport { extConfig } from \"../../state\";\n\nconst PATREON_JOIN_URL = \"https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008649\";\nconst CHANGELOG_URL = getChangelogUrl();\n\nexport const TEASER_SUPPRESSION_REASON_LEGACY = \"legacy\";\nexport const TEASER_SUPPRESSION_REASON_PREMIUM = \"premium\";\nexport const TEASER_SUPPRESSION_REASON_SETTINGS = \"settings\";\n\nconst teaserState = {\n  initialized: false,\n  suppressed: false,\n  panelElement: null,\n  currentVideoId: null,\n  abortController: null,\n  suppressionReasons: new Set(),\n  storageListener: null,\n};\n\nexport async function initPremiumTeaser() {\n  if (teaserState.initialized) return;\n  teaserState.initialized = true;\n\n  document.addEventListener(\"yt-navigate-finish\", handleNavigation, { passive: true });\n\n  try {\n    await syncSuppressionWithSettings();\n  } catch {\n    // Ignore storage sync failures; teaser suppression will remain manual.\n  }\n\n  handleNavigation();\n}\n\nexport function setTeaserSuppressed(shouldSuppress, reason = TEASER_SUPPRESSION_REASON_LEGACY) {\n  const normalizedReason = reason || TEASER_SUPPRESSION_REASON_LEGACY;\n  const reasons = teaserState.suppressionReasons;\n\n  if (shouldSuppress) {\n    reasons.add(normalizedReason);\n  } else {\n    reasons.delete(normalizedReason);\n  }\n\n  const next = reasons.size > 0;\n\n  if (next === teaserState.suppressed) {\n    if (next) {\n      removePanel(true);\n      resetState();\n    }\n    return;\n  }\n\n  teaserState.suppressed = next;\n\n  if (next) {\n    removePanel(true);\n    resetState();\n  } else {\n    handleNavigation();\n  }\n}\n\nfunction applySettingsSuppression(shouldHide, persist = false) {\n  const normalized = shouldHide === true;\n  extConfig.hidePremiumTeaser = normalized;\n  setTeaserSuppressed(normalized, TEASER_SUPPRESSION_REASON_SETTINGS);\n\n  if (!persist) {\n    return;\n  }\n\n  try {\n    const browser = getBrowser();\n    browser?.storage?.sync?.set?.({ hidePremiumTeaser: normalized });\n  } catch {\n    // Ignore persistence failures; suppression state already applied locally.\n  }\n}\n\nasync function syncSuppressionWithSettings() {\n  try {\n    const browser = getBrowser();\n    if (!browser?.storage?.sync) {\n      return;\n    }\n\n    await new Promise((resolve) => {\n      browser.storage.sync.get([\"hidePremiumTeaser\"], (res) => {\n        try {\n          const shouldHide = res?.hidePremiumTeaser === true;\n          applySettingsSuppression(shouldHide);\n        } finally {\n          resolve();\n        }\n      });\n    });\n\n    if (!teaserState.storageListener) {\n      const listener = (changes, area) => {\n        if (area !== \"sync\" || !changes.hidePremiumTeaser) {\n          return;\n        }\n        const shouldHide = changes.hidePremiumTeaser.newValue === true;\n        applySettingsSuppression(shouldHide);\n      };\n      teaserState.storageListener = listener;\n      browser.storage.onChanged.addListener(listener);\n    }\n  } catch {\n    // Ignore storage sync failures; teaser suppression will remain manual.\n  }\n}\n\nfunction handleNavigation() {\n  if (teaserState.suppressed) {\n    return;\n  }\n\n  const videoId = resolveVideoId();\n  if (!videoId) {\n    resetState();\n    removePanel();\n    return;\n  }\n\n  if (teaserState.currentVideoId === videoId) {\n    ensurePanel();\n    return;\n  }\n\n  teaserState.currentVideoId = videoId;\n  fetchAndRender(videoId);\n}\n\nfunction resolveVideoId() {\n  try {\n    const id = getVideoId(window.location.href);\n    if (!id || id.length !== 11) {\n      return null;\n    }\n    return id;\n  } catch {\n    return null;\n  }\n}\n\nfunction fetchAndRender(videoId) {\n  if (teaserState.suppressed) {\n    return;\n  }\n\n  const panel = ensurePanel();\n  if (!panel) {\n    setTimeout(() => {\n      if (!teaserState.suppressed) {\n        fetchAndRender(videoId);\n      }\n    }, 200);\n    return;\n  }\n\n  setLoading(true);\n  updateCounts({ dislikes: null, rawDislikes: null, likes: null });\n\n  if (teaserState.abortController) {\n    teaserState.abortController.abort();\n  }\n\n  const controller = new AbortController();\n  teaserState.abortController = controller;\n\n  const url = getApiEndpoint(`/votes?videoId=${encodeURIComponent(videoId)}`);\n\n  fetch(url, {\n    method: \"GET\",\n    headers: { Accept: \"application/json\" },\n    signal: controller.signal,\n  })\n    .then((response) => {\n      if (!response.ok) {\n        throw new Error(`Unexpected response: ${response.status}`);\n      }\n      return response.json();\n    })\n    .then((payload) => {\n      if (teaserState.currentVideoId !== videoId || teaserState.suppressed) {\n        return;\n      }\n      updateCounts(payload);\n      setLoading(false);\n    })\n    .catch((error) => {\n      if (error?.name === \"AbortError\") {\n        return;\n      }\n      if (teaserState.suppressed) {\n        return;\n      }\n      showError();\n    });\n}\n\nfunction ensurePanel() {\n  const premiumPanel = document.querySelector(\".ryd-premium-analytics\");\n  if (premiumPanel) {\n    if (!teaserState.suppressed) {\n      setTeaserSuppressed(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n    } else {\n      removePanel(true);\n    }\n    return null;\n  }\n\n  if (teaserState.panelElement && teaserState.panelElement.isConnected) {\n    return teaserState.panelElement;\n  }\n\n  const container = document.querySelector(\"#secondary #secondary-inner\") || document.querySelector(\"#secondary-inner\");\n  if (!container) {\n    setTimeout(ensurePanel, 250);\n    return null;\n  }\n\n  const panel = document.createElement(\"section\");\n  panel.className = \"ryd-premium-teaser\";\n  panel.innerHTML = createPanelMarkup();\n  container.insertBefore(panel, container.firstChild);\n\n  const cta = panel.querySelector(\"#ryd-premium-teaser-cta\");\n  if (cta) {\n    cta.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      openTab(PATREON_JOIN_URL);\n    });\n  }\n\n  const infoLink = panel.querySelector(\"#ryd-premium-teaser-learn\");\n  if (infoLink) {\n    infoLink.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      openTab(CHANGELOG_URL);\n    });\n  }\n\n  const dismissButton = panel.querySelector(\"#ryd-premium-teaser-close\");\n  if (dismissButton) {\n    dismissButton.addEventListener(\"click\", handleManualDismiss);\n  }\n\n  teaserState.panelElement = panel;\n  return panel;\n}\n\nfunction removePanel(includeStrayNodes = false) {\n  if (teaserState.panelElement) {\n    teaserState.panelElement.remove();\n    teaserState.panelElement = null;\n  }\n  if (includeStrayNodes) {\n    document.querySelectorAll(\".ryd-premium-teaser\").forEach((node) => {\n      if (node !== teaserState.panelElement) {\n        node.remove();\n      }\n    });\n  }\n}\n\nfunction resetState() {\n  teaserState.currentVideoId = null;\n  if (teaserState.abortController) {\n    teaserState.abortController.abort();\n    teaserState.abortController = null;\n  }\n}\n\nfunction setLoading(isLoading) {\n  const panel = teaserState.panelElement;\n  if (!panel) return;\n  panel.classList.toggle(\"is-loading\", !!isLoading);\n  const status = panel.querySelector(\"#ryd-premium-teaser-status\");\n  if (status) {\n    status.textContent = isLoading ? localize(\"premiumTeaser_statusLoading\") : \"\";\n  }\n}\n\nfunction showError() {\n  setLoading(false);\n  const status = teaserState.panelElement?.querySelector(\"#ryd-premium-teaser-status\");\n  if (status) {\n    status.textContent = localize(\"premiumTeaser_statusError\");\n  }\n  updateCounts({ dislikes: null, rawDislikes: null, likes: null });\n}\n\nfunction updateCounts(payload) {\n  const panel = teaserState.panelElement;\n  if (!panel) return;\n\n  const dislikesValue = panel.querySelector(\"#ryd-premium-teaser-dislikes\");\n  const rawDislikesValue = panel.querySelector(\"#ryd-premium-teaser-raw\");\n  const likesValue = panel.querySelector(\"#ryd-premium-teaser-likes\");\n\n  const dislikes = normalizeNumber(payload?.dislikes);\n  const rawDislikes = normalizeNumber(payload?.rawDislikes ?? payload?.dislikes);\n  const likes = normalizeNumber(payload?.likes ?? payload?.rawLikes);\n\n  if (dislikesValue) dislikesValue.textContent = dislikes;\n  if (rawDislikesValue) rawDislikesValue.textContent = rawDislikes;\n  if (likesValue) likesValue.textContent = likes;\n}\n\nfunction normalizeNumber(value) {\n  if (!Number.isFinite(value)) {\n    return \"—\";\n  }\n  try {\n    return Number(value).toLocaleString();\n  } catch {\n    return `${value}`;\n  }\n}\n\nfunction createPanelMarkup() {\n  const extensionName = localize(\"extensionName\");\n  const title = localize(\"premiumTeaser_title\");\n  const subtitle = localize(\"premiumTeaser_subtitle\");\n  const ctaText = localize(\"premiumTeaser_cta\");\n  const secondaryText = localize(\"premiumTeaser_learn\");\n  const closeLabel = localize(\"hidePremiumTeaser\");\n  const statRaw = localize(\"premiumTeaser_statRaw\");\n  const statDislikes = localize(\"premiumTeaser_statDislikes\");\n  const statLikes = localize(\"premiumTeaser_statLikes\");\n  const breadcrumbsAria = localize(\"premiumTeaser_breadcrumbsAria\");\n  const breadcrumbsTitle = localize(\"premiumTeaser_breadcrumbsTitle\");\n  const step1 = localize(\"premiumTeaser_breadcrumbStep1\");\n  const step2 = localize(\"premiumTeaser_breadcrumbStep2\");\n  const step3 = localize(\"premiumTeaser_breadcrumbStep3\");\n\n  return `\n    <header class=\"ryd-premium-teaser__header\">\n      <div>\n        <span class=\"ryd-premium-teaser__badge\">${extensionName}</span>\n        <h2 class=\"ryd-premium-teaser__title\">${title}</h2>\n        <p class=\"ryd-premium-teaser__subtitle\">${subtitle}</p>\n      </div>\n      <div class=\"ryd-premium-teaser__actions\">\n        <a href=\"${PATREON_JOIN_URL}\" class=\"ryd-premium-teaser__cta\" id=\"ryd-premium-teaser-cta\">${ctaText}</a>\n        <a href=\"${CHANGELOG_URL}\" class=\"ryd-premium-teaser__secondary\" id=\"ryd-premium-teaser-learn\">${secondaryText}</a>\n      </div>\n      <button type=\"button\" class=\"ryd-premium-teaser__close\" id=\"ryd-premium-teaser-close\" aria-label=\"${closeLabel}\" title=\"${closeLabel}\">\n        <span class=\"ryd-premium-teaser__close-icon\" aria-hidden=\"true\">&times;</span>\n      </button>\n    </header>\n    <div class=\"ryd-premium-teaser__body\">\n      <div class=\"ryd-premium-teaser__stats\" role=\"status\" aria-live=\"polite\">\n        <div class=\"ryd-premium-teaser__stat\">\n          <span class=\"ryd-premium-teaser__stat-label\">${statRaw}</span>\n          <span class=\"ryd-premium-teaser__stat-value\" id=\"ryd-premium-teaser-raw\">—</span>\n        </div>\n        <div class=\"ryd-premium-teaser__stat\">\n          <span class=\"ryd-premium-teaser__stat-label\">${statDislikes}</span>\n          <span class=\"ryd-premium-teaser__stat-value\" id=\"ryd-premium-teaser-dislikes\">—</span>\n        </div>\n        <div class=\"ryd-premium-teaser__stat\">\n          <span class=\"ryd-premium-teaser__stat-label\">${statLikes}</span>\n          <span class=\"ryd-premium-teaser__stat-value\" id=\"ryd-premium-teaser-likes\">—</span>\n        </div>\n      </div>\n      <p class=\"ryd-premium-teaser__status\" id=\"ryd-premium-teaser-status\"></p>\n      <section class=\"ryd-premium-teaser__breadcrumbs\" aria-label=\"${breadcrumbsAria}\">\n        <h3 class=\"ryd-premium-teaser__breadcrumbs-title\">${breadcrumbsTitle}</h3>\n        <ol class=\"ryd-premium-teaser__breadcrumbs-list\">\n          <li>${step1}</li>\n          <li>${step2}</li>\n          <li>${step3}</li>\n        </ol>\n      </section>\n    </div>\n  `;\n}\n\nfunction openTab(url) {\n  if (!url) return;\n\n  const browser = getBrowser();\n  try {\n    if (browser && browser.tabs && typeof browser.tabs.create === \"function\") {\n      browser.tabs.create({ url });\n      return;\n    }\n  } catch {\n    // ignore and fall back to window.open\n  }\n\n  try {\n    if (browser?.runtime?.sendMessage) {\n      browser.runtime.sendMessage({ message: \"ryd_open_tab\", url });\n      return;\n    }\n  } catch {\n    // ignore and fall back to window.open\n  }\n\n  try {\n    window.open(url, \"_blank\", \"noopener\");\n  } catch {\n    // ignore navigation failures\n  }\n}\n\nfunction handleManualDismiss(event) {\n  event?.preventDefault?.();\n  event?.stopPropagation?.();\n  applySettingsSuppression(true, true);\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/teaser/index.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\njest.mock(\"../../config\", () => ({\n  getApiEndpoint: jest.fn((path) => `https://api.test${path}`),\n  getChangelogUrl: jest.fn(() => \"moz-extension://unit-test/changelog/4/changelog_4.0.html\"),\n}));\n\njest.mock(\"../../utils\", () => {\n  const actual = jest.requireActual(\"../../utils\");\n  return {\n    ...actual,\n    getVideoId: jest.fn(),\n  };\n});\n\nconst enMessages = require(\"../../../_locales/en/messages.json\");\n\nfunction getMessage(key, substitutions) {\n  const entry = enMessages[key];\n  if (!entry) {\n    return key;\n  }\n  let message = entry.message ?? \"\";\n  if (substitutions == null) {\n    return message;\n  }\n  const values = Array.isArray(substitutions) ? substitutions : [substitutions];\n  values.forEach((value, index) => {\n    const replacement = value != null ? `${value}` : \"\";\n    message = message.replace(new RegExp(`\\\\$${index + 1}`, \"g\"), replacement);\n  });\n  return message;\n}\n\ndescribe(\"premiumAnalytics.teaser\", () => {\n  let initPremiumTeaser;\n  let setTeaserSuppressed;\n  let getVideoId;\n  let TEASER_SUPPRESSION_REASON_SETTINGS;\n  let TEASER_SUPPRESSION_REASON_PREMIUM;\n  let storageGetMock;\n  let storageSetMock;\n  let storageOnChangedAddListener;\n\n  function mountSecondary() {\n    document.body.innerHTML = `\n      <div id=\"columns\">\n        <div id=\"primary\"></div>\n        <div id=\"secondary\">\n          <div id=\"secondary-inner\"></div>\n        </div>\n      </div>\n    `;\n  }\n\n  beforeEach(() => {\n    jest.resetModules();\n    jest.clearAllMocks();\n    mountSecondary();\n    storageGetMock = jest.fn((keys, callback) => {\n      callback({ hidePremiumTeaser: false });\n    });\n    storageSetMock = jest.fn((values, callback) => {\n      if (typeof callback === \"function\") {\n        callback();\n      }\n    });\n    storageOnChangedAddListener = jest.fn();\n    global.chrome = {\n      i18n: {\n        getMessage,\n      },\n      runtime: {},\n      storage: {\n        sync: {\n          get: storageGetMock,\n          set: storageSetMock,\n        },\n        onChanged: {\n          addListener: storageOnChangedAddListener,\n        },\n      },\n    };\n\n    ({ getVideoId } = require(\"../../utils\"));\n    ({\n      initPremiumTeaser,\n      setTeaserSuppressed,\n      TEASER_SUPPRESSION_REASON_SETTINGS,\n      TEASER_SUPPRESSION_REASON_PREMIUM,\n    } = require(\"./index\"));\n\n    global.fetch = jest.fn().mockResolvedValue({\n      ok: true,\n      json: () =>\n        Promise.resolve({\n          dislikes: 123,\n          rawDislikes: 456,\n          likes: 789,\n        }),\n    });\n  });\n\n  afterEach(() => {\n    document.body.innerHTML = \"\";\n    delete global.fetch;\n    delete global.chrome;\n  });\n\n  async function flushPromises() {\n    await Promise.resolve();\n    await Promise.resolve();\n    await new Promise((resolve) => setTimeout(resolve, 0));\n  }\n\n  it(\"renders the teaser panel with fetched dislike data\", async () => {\n    getVideoId.mockReturnValue(\"abcdefghijk\");\n\n    await initPremiumTeaser();\n    await flushPromises();\n\n    const panel = document.querySelector(\".ryd-premium-teaser\");\n    expect(panel).not.toBeNull();\n    expect(global.fetch).toHaveBeenCalledWith(\"https://api.test/votes?videoId=abcdefghijk\", expect.any(Object));\n\n    const raw = panel.querySelector(\"#ryd-premium-teaser-raw\")?.textContent;\n    const dislikes = panel.querySelector(\"#ryd-premium-teaser-dislikes\")?.textContent;\n    const likes = panel.querySelector(\"#ryd-premium-teaser-likes\")?.textContent;\n\n    expect(raw).toBe(\"456\");\n    expect(dislikes).toBe(\"123\");\n    expect(likes).toBe(\"789\");\n  });\n\n  it(\"removes the panel when suppressed and restores it when unsuppressed\", async () => {\n    getVideoId.mockReturnValue(\"LMNOPQRSTUV\");\n\n    await initPremiumTeaser();\n    await flushPromises();\n    expect(document.querySelector(\".ryd-premium-teaser\")).not.toBeNull();\n\n    setTeaserSuppressed(true);\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n\n    setTeaserSuppressed(false);\n    await flushPromises();\n    expect(document.querySelector(\".ryd-premium-teaser\")).not.toBeNull();\n  });\n\n  it(\"skips rendering when a premium analytics panel is present\", async () => {\n    getVideoId.mockReturnValue(\"PREMIUM12345\");\n    const container = document.querySelector(\"#secondary-inner\");\n    const premium = document.createElement(\"section\");\n    premium.className = \"ryd-premium-analytics\";\n    container.appendChild(premium);\n\n    await initPremiumTeaser();\n    await flushPromises();\n\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n  });\n\n  it(\"cleans up stray teaser panels even when already suppressed\", async () => {\n    getVideoId.mockReturnValue(\"ZXCVBNMASDF\");\n\n    await initPremiumTeaser();\n    await flushPromises();\n\n    setTeaserSuppressed(true);\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n\n    const stray = document.createElement(\"section\");\n    stray.className = \"ryd-premium-teaser\";\n    document.querySelector(\"#secondary-inner\").appendChild(stray);\n\n    setTeaserSuppressed(true);\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n  });\n\n  it(\"keeps the teaser hidden while the settings suppression is active\", async () => {\n    getVideoId.mockReturnValue(\"SETTI123456\");\n\n    await initPremiumTeaser();\n    await flushPromises();\n    expect(document.querySelector(\".ryd-premium-teaser\")).not.toBeNull();\n\n    setTeaserSuppressed(true, TEASER_SUPPRESSION_REASON_SETTINGS);\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n\n    setTeaserSuppressed(true, TEASER_SUPPRESSION_REASON_PREMIUM);\n    setTeaserSuppressed(false, TEASER_SUPPRESSION_REASON_PREMIUM);\n    await flushPromises();\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n\n    setTeaserSuppressed(false, TEASER_SUPPRESSION_REASON_SETTINGS);\n    await flushPromises();\n    expect(document.querySelector(\".ryd-premium-teaser\")).not.toBeNull();\n  });\n\n  it(\"displays an error message when the vote request fails\", async () => {\n    getVideoId.mockReturnValue(\"QWERTYUIOP1\");\n    global.fetch = jest.fn().mockRejectedValue(new Error(\"network down\"));\n\n    await initPremiumTeaser();\n    await flushPromises();\n\n    const status = document.querySelector(\"#ryd-premium-teaser-status\");\n    expect(status?.textContent).toBe(getMessage(\"premiumTeaser_statusError\"));\n  });\n\n  it(\"persists the hide setting when the close button is clicked\", async () => {\n    getVideoId.mockReturnValue(\"DISMISSME01\");\n\n    await initPremiumTeaser();\n    await flushPromises();\n\n    const closeButton = document.querySelector(\"#ryd-premium-teaser-close\");\n    expect(closeButton).not.toBeNull();\n\n    storageSetMock.mockClear();\n\n    closeButton.click();\n    await flushPromises();\n\n    expect(storageSetMock).toHaveBeenCalledWith({ hidePremiumTeaser: true });\n    expect(document.querySelector(\".ryd-premium-teaser\")).toBeNull();\n  });\n\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/theme.js",
    "content": "export function getTextColor() {\n  return getComputedStyle(document.documentElement).getPropertyValue(\"--yt-spec-text-primary\").trim() || \"#ffffff\";\n}\n\nexport function getMutedTextColor() {\n  return getComputedStyle(document.documentElement).getPropertyValue(\"--yt-spec-text-secondary\").trim() || \"#b3b3b3\";\n}\n\nexport function getBorderColor(alpha = 0.15) {\n  const base = isDarkTheme() ? [255, 255, 255] : [15, 23, 42];\n  return `rgba(${base[0]},${base[1]},${base[2]},${alpha})`;\n}\n\nexport function getSurfaceColor(lightAlpha = 0.06, darkAlpha = 0.02) {\n  const base = isDarkTheme() ? [255, 255, 255] : [15, 23, 42];\n  const alpha = isDarkTheme() ? darkAlpha : lightAlpha;\n  return `rgba(${base[0]},${base[1]},${base[2]},${alpha})`;\n}\n\nexport function getHoverFillColor(lightAlpha = 0.18, darkAlpha = 0.18) {\n  const base = isDarkTheme() ? [255, 255, 255] : [15, 23, 42];\n  const alpha = isDarkTheme() ? darkAlpha : lightAlpha;\n  return `rgba(${base[0]},${base[1]},${base[2]},${alpha})`;\n}\n\nexport function isDarkTheme() {\n  const root = document.documentElement;\n  if (root.hasAttribute(\"dark\") && !root.hasAttribute(\"light\")) {\n    return true;\n  }\n  if (root.getAttribute(\"dark\") === \"false\") {\n    return false;\n  }\n  const colorValue = getComputedStyle(root).getPropertyValue(\"--yt-spec-text-primary\").trim();\n  const color = parseColor(colorValue);\n  if (color) {\n    const luminance = (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b) / 255;\n    return luminance > 0.5;\n  }\n  return true;\n}\n\nfunction parseColor(value) {\n  if (!value) {\n    return null;\n  }\n  const trimmed = value.trim();\n  if (trimmed.startsWith('#')) {\n    const hex = trimmed.slice(1);\n    if (hex.length === 3) {\n      const r = parseInt(hex[0] + hex[0], 16);\n      const g = parseInt(hex[1] + hex[1], 16);\n      const b = parseInt(hex[2] + hex[2], 16);\n      return { r, g, b };\n    }\n    if (hex.length === 6) {\n      const r = parseInt(hex.slice(0, 2), 16);\n      const g = parseInt(hex.slice(2, 4), 16);\n      const b = parseInt(hex.slice(4, 6), 16);\n      return { r, g, b };\n    }\n    return null;\n  }\n  const rgbMatch = trimmed.match(/rgba?\\(([^)]+)\\)/i);\n  if (rgbMatch) {\n    const parts = rgbMatch[1].split(',').map((part) => parseFloat(part.trim()));\n    if (parts.length >= 3) {\n      return { r: parts[0], g: parts[1], b: parts[2] };\n    }\n  }\n  return null;\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/theme.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\nimport { getTextColor, getMutedTextColor, getBorderColor, getSurfaceColor, getHoverFillColor, isDarkTheme } from \"./theme\";\n\ndescribe(\"premiumAnalytics.theme\", () => {\n  const originalGetComputedStyle = window.getComputedStyle;\n\n  beforeEach(() => {\n    window.getComputedStyle = jest.fn().mockImplementation(() => ({\n      getPropertyValue: (name) => {\n        if (name === \"--yt-spec-text-primary\") return \" #fefefe  \";\n        if (name === \"--yt-spec-text-secondary\") return \" #0b0b0b  \";\n        return \"\";\n      },\n    }));\n  });\n\n  afterEach(() => {\n    window.getComputedStyle = originalGetComputedStyle;\n  });\n\n  it(\"returns trimmed primary text color\", () => {\n    expect(getTextColor()).toBe(\"#fefefe\");\n  });\n\n  it(\"returns trimmed secondary text color\", () => {\n    expect(getMutedTextColor()).toBe(\"#0b0b0b\");\n  });\n\n  it(\"builds rgba border color with provided alpha\", () => {\n    expect(getBorderColor(0.33)).toBe(\"rgba(255,255,255,0.33)\");\n    expect(getBorderColor()).toBe(\"rgba(255,255,255,0.15)\");\n  });\n\n  it(\"derives surface colors based on theme\", () => {\n    document.documentElement.setAttribute('dark', '');\n    window.getComputedStyle.mockImplementation(() => ({\n      getPropertyValue: (name) => {\n        if (name === \"--yt-spec-text-primary\") return \"#fefefe\";\n        if (name === \"--yt-spec-text-secondary\") return \"#b0b0b0\";\n        return \"\";\n      },\n    }));\n    expect(isDarkTheme()).toBe(true);\n    expect(getSurfaceColor()).toBe('rgba(255,255,255,0.02)');\n    expect(getHoverFillColor()).toBe('rgba(255,255,255,0.18)');\n\n    document.documentElement.removeAttribute('dark');\n    document.documentElement.setAttribute('light', '');\n    window.getComputedStyle.mockImplementation(() => ({\n      getPropertyValue: (name) => {\n        if (name === \"--yt-spec-text-primary\") return \"#111111\";\n        if (name === \"--yt-spec-text-secondary\") return \"#333333\";\n        return \"\";\n      },\n    }));\n    expect(isDarkTheme()).toBe(false);\n    expect(getBorderColor(0.2)).toBe('rgba(15,23,42,0.2)');\n    expect(getSurfaceColor()).toBe('rgba(15,23,42,0.06)');\n    expect(getHoverFillColor()).toBe('rgba(15,23,42,0.18)');\n    document.documentElement.removeAttribute('light');\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/tierNotice.js",
    "content": "import { localize } from \"../utils\";\n\nconst PATREON_UPGRADE_URL = \"https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008649\";\n\nconst noticeState = {\n  initialized: false,\n  active: false,\n  panelElement: null,\n};\n\nfunction ensureListeners() {\n  if (noticeState.initialized) return;\n  document.addEventListener(\"yt-navigate-finish\", handleNavigation, { passive: true });\n  noticeState.initialized = true;\n}\n\nfunction handleNavigation() {\n  if (!noticeState.active) {\n    removePanel();\n    return;\n  }\n\n  const container = document.querySelector(\"#secondary #secondary-inner\") || document.querySelector(\"#secondary-inner\");\n  if (!container) {\n    setTimeout(handleNavigation, 250);\n    return;\n  }\n\n  let panel = noticeState.panelElement;\n  if (!panel || !panel.isConnected) {\n    panel = document.createElement(\"section\");\n    panel.className = \"ryd-premium-tier-notice ryd-premium-feature\";\n    container.insertBefore(panel, container.firstChild);\n    noticeState.panelElement = panel;\n  }\n\n  panel.innerHTML = createPanelMarkup();\n  wireCta(panel);\n}\n\nfunction createPanelMarkup() {\n  const title = localize(\"premiumTierNotice_title\");\n  const message = localize(\"premiumTierNotice_message\");\n  const cta = localize(\"premiumTierNotice_cta\");\n\n  return `\n    <div class=\"ryd-premium-tier-notice__content\">\n      <h2 class=\"ryd-premium-tier-notice__title\">${title}</h2>\n      <p class=\"ryd-premium-tier-notice__message\">${message}</p>\n      <div class=\"ryd-premium-tier-notice__actions\">\n        <a href=\"${PATREON_UPGRADE_URL}\" class=\"ryd-premium-tier-notice__cta\" id=\"ryd-premium-tier-upgrade\">${cta}</a>\n      </div>\n    </div>\n  `;\n}\n\nfunction wireCta(panel) {\n  const cta = panel.querySelector(\"#ryd-premium-tier-upgrade\");\n  if (!cta) return;\n  cta.addEventListener(\"click\", (event) => {\n    event.preventDefault();\n    window.open(PATREON_UPGRADE_URL, \"_blank\", \"noopener\");\n  });\n}\n\nfunction removePanel() {\n  if (noticeState.panelElement) {\n    noticeState.panelElement.remove();\n    noticeState.panelElement = null;\n  }\n}\n\nfunction resetState() {\n  noticeState.panelElement = null;\n}\n\nexport function showTierNotice() {\n  noticeState.active = true;\n  ensureListeners();\n  handleNavigation();\n}\n\nexport function hideTierNotice() {\n  noticeState.active = false;\n  removePanel();\n  resetState();\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/utils.js",
    "content": "export function sanitizeCount(value) {\n  const number = Number(value);\n  if (!Number.isFinite(number) || number <= 0) {\n    return 0;\n  }\n  return Math.trunc(number);\n}\n\nexport function toEpoch(value) {\n  if (!value) return null;\n  const ms = value instanceof Date ? value.getTime() : new Date(value).getTime();\n  return Number.isFinite(ms) ? ms : null;\n}\n\nexport function capitalize(value) {\n  return value ? value.charAt(0).toUpperCase() + value.slice(1) : \"\";\n}\n\nexport function debounce(fn, delay) {\n  let timeoutId;\n  return (...args) => {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => fn(...args), delay);\n  };\n}\n\nexport function safeJson(response) {\n  return response\n    .clone()\n    .json()\n    .catch(() => null);\n}\n"
  },
  {
    "path": "Extensions/combined/src/premiumAnalytics/utils.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\nimport {\n  sanitizeCount,\n  toEpoch,\n  capitalize,\n  debounce,\n  safeJson,\n} from \"./utils\";\n\ndescribe(\"premiumAnalytics.utils\", () => {\n  describe(\"sanitizeCount\", () => {\n    it.each([\n      [\"1234\", 1234],\n      [1234.8, 1234],\n      [\"0\", 0],\n      [-10, 0],\n      [\"not-a-number\", 0],\n    ])(\"normalizes %p to %p\", (input, expected) => {\n      expect(sanitizeCount(input)).toBe(expected);\n    });\n  });\n\n  describe(\"toEpoch\", () => {\n    it(\"converts ISO strings to epoch milliseconds\", () => {\n      const date = \"2025-01-02T03:04:05.000Z\";\n      expect(toEpoch(date)).toBe(new Date(date).getTime());\n    });\n\n    it(\"accepts Date instances\", () => {\n      const value = new Date(\"2025-01-02T06:07:08.000Z\");\n      expect(toEpoch(value)).toBe(value.getTime());\n    });\n\n    it(\"returns null for invalid inputs\", () => {\n      expect(toEpoch(null)).toBeNull();\n      expect(toEpoch(undefined)).toBeNull();\n      expect(toEpoch(\"invalid\")).toBeNull();\n    });\n  });\n\n  describe(\"capitalize\", () => {\n    it(\"capitalizes the first character\", () => {\n      expect(capitalize(\"likes\")).toBe(\"Likes\");\n    });\n\n    it(\"returns empty string for falsy values\", () => {\n      expect(capitalize(\"\")).toBe(\"\");\n      expect(capitalize()).toBe(\"\");\n      expect(capitalize(null)).toBe(\"\");\n    });\n  });\n\n  describe(\"debounce\", () => {\n    beforeEach(() => {\n      jest.useFakeTimers();\n    });\n\n    afterEach(() => {\n      jest.useRealTimers();\n    });\n\n    it(\"delays execution until the wait period passes\", () => {\n      const fn = jest.fn();\n      const debounced = debounce(fn, 200);\n\n      debounced(\"first\");\n      debounced(\"second\");\n\n      expect(fn).not.toHaveBeenCalled();\n      jest.advanceTimersByTime(200);\n\n      expect(fn).toHaveBeenCalledTimes(1);\n      expect(fn).toHaveBeenCalledWith(\"second\");\n    });\n  });\n\n  describe(\"safeJson\", () => {\n    it(\"returns parsed JSON when response is valid\", async () => {\n      const response = {\n        clone: () => ({\n          json: () => Promise.resolve({ ok: true }),\n        }),\n      };\n\n      await expect(safeJson(response)).resolves.toEqual({ ok: true });\n    });\n\n    it(\"returns null when parsing fails\", async () => {\n      const response = {\n        clone: () => ({\n          json: () => Promise.reject(new Error(\"boom\")),\n        }),\n      };\n\n      await expect(safeJson(response)).resolves.toBeNull();\n    });\n  });\n});\n"
  },
  {
    "path": "Extensions/combined/src/starRating.js",
    "content": "\nfunction createStarRating(rating, isMobile) {\n  let starRating = document.createElement(\"label\");\n\n  let starSlider = document.createElement(\"input\");\n  starSlider.setAttribute(\"class\", \"rating\");\n  starSlider.setAttribute(\"max\", \"5\");\n  starSlider.setAttribute(\"readonly\", \"\");\n  starSlider.setAttribute(\n    \"style\",\n    `--fill:rgb(255, 215, 0);--value:${rating.toString()};};background-color: transparent;`,\n  );\n  starSlider.setAttribute(\"type\", \"range\");\n\n  starRating.appendChild(starSlider);\n\n  let YTLikeButton;\n\n  if (isMobile) {\n    YTLikeButton = document.querySelector(\n      \"#app > div.page-container > ytm-watch > ytm-single-column-watch-next-results-renderer > ytm-slim-video-metadata-section-renderer > ytm-slim-video-action-bar-renderer > div > ytm-slim-metadata-toggle-button-renderer:nth-child(1)\",\n    );\n  } else {\n    YTLikeButton = document.querySelector(\n      \"#top-level-buttons-computed > ytd-toggle-button-renderer:nth-child(1)\",\n    );\n  }\n\n  YTLikeButton.insertAdjacentElement(\"afterend\", starRating);\n\n  try {\n    let YTBar = document.querySelector(\"#ryd-bar-container\");\n    YTBar.setAttribute(\"style\", \"width: 190%; height: 2px;\");\n  } catch (err) {\n    console.log(\"RateBar Not Present\");\n  }\n\n  let style = `<style>\n\n.rating {\n    --dir: right;\n    --fill: gold;\n    --fillbg: rgba(100, 100, 100, 0.15);\n    --star: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 17.25l-6.188 3.75 1.641-7.031-5.438-4.734 7.172-0.609 2.813-6.609 2.813 6.609 7.172 0.609-5.438 4.734 1.641 7.031z\"/></svg>');\n    --stars: 5;\n    --starSize: 2.8rem;\n    --symbol: var(--star);\n    --value: 1;\n    --w: calc(var(--stars) * var(--starSize));\n    --x: calc(100% * (var(--value) / var(--stars)));\n    block-size: var(--starSize);\n    inline-size: var(--w);\n    position: relative;\n    touch-action: manipulation;\n    -webkit-appearance: none;\n}\n\n[dir=\"rtl\"] .rating {\n    --dir: left;\n}\n\n.rating::-moz-range-track {\n    background: linear-gradient(to var(--dir), var(--fill) 0 var(--x), var(--fillbg) 0 var(--x));\n    block-size: 100%;\n    mask: repeat left center/var(--starSize) var(--symbol);\n}\n\n.rating::-webkit-slider-runnable-track {\n    background: linear-gradient(to var(--dir), var(--fill) 0 var(--x), var(--fillbg) 0 var(--x));\n    block-size: 100%;\n    mask: repeat left center/var(--starSize) var(--symbol);\n    -webkit-mask: repeat left center/var(--starSize) var(--symbol);\n}\n\n.rating::-moz-range-thumb {\n    height: var(--starSize);\n    opacity: 0;\n    width: var(--starSize);\n}\n\n.rating::-webkit-slider-thumb {\n    height: var(--starSize);\n    opacity: 0;\n    width: var(--starSize);\n    -webkit-appearance: none;\n}\n\n.rating,\n.rating-label {\n    display: block;\n    font-family: ui-sans-serif, system-ui, sans-serif;\n}\n\n.rating-label {\n    margin-block-end: 1rem;\n}\n\n</style>`;\n\n  document.head.insertAdjacentHTML(\"beforeend\", style);\n}\n\nexport { createStarRating };\n"
  },
  {
    "path": "Extensions/combined/src/state.js",
    "content": "import { getLikeButton, getDislikeButton, getButtons, getLikeTextContainer, getDislikeTextContainer } from \"./buttons\";\nimport { createRateBar } from \"./bar\";\nimport {\n  getBrowser,\n  getVideoId,\n  initializeLogging,\n  numberFormat,\n  getColorFromTheme,\n  querySelector,\n  localize,\n  createObserver,\n} from \"./utils\";\nimport { config, getApiEndpoint, DEV_API_URL, PROD_API_URL, isDevelopment } from \"./config\";\nconst LIKED_STATE = \"LIKED_STATE\";\nconst DISLIKED_STATE = \"DISLIKED_STATE\";\nconst NEUTRAL_STATE = \"NEUTRAL_STATE\";\n\nlet extConfig = {\n  disableVoteSubmission: false,\n  disableLogging: false,\n  coloredThumbs: false,\n  coloredBar: false,\n  colorTheme: \"classic\",\n  numberDisplayFormat: \"compactShort\",\n  showTooltipPercentage: false,\n  tooltipPercentageMode: \"dash_like\",\n  numberDisplayReformatLikes: false,\n  hidePremiumTeaser: false,\n  selectors: {\n    dislikeTextContainer: [],\n    likeTextContainer: [],\n    buttons: {\n      shorts: {\n        mobile: [],\n        desktop: [],\n      },\n      regular: {\n        mobile: [],\n        desktopMenu: [],\n        desktopNoMenu: [],\n      },\n      likeButton: {\n        segmented: [],\n        segmentedGetButtons: [],\n        notSegmented: [],\n      },\n      dislikeButton: {\n        segmented: [],\n        segmentedGetButtons: [],\n        notSegmented: [],\n      },\n    },\n    menuContainer: [],\n    roundedDesign: [],\n  },\n};\n\nlet storedData = {\n  likes: 0,\n  dislikes: 0,\n  previousState: NEUTRAL_STATE,\n};\n\nfunction isMobile() {\n  return location.hostname == \"m.youtube.com\";\n}\n\nfunction isShorts() {\n  return location.pathname.startsWith(\"/shorts\");\n}\n\nfunction isNewDesign() {\n  return document.getElementById(\"comment-teaser\") !== null;\n}\n\nfunction isRoundedDesign() {\n  return querySelector(extConfig.selectors.roundedDesign) !== null;\n}\n\nlet shortsObserver = null;\n\nif (isShorts() && !shortsObserver) {\n  console.log(\"Initializing shorts mutation observer\");\n  shortsObserver = createObserver(\n    {\n      attributes: true,\n    },\n    (mutationList) => {\n      mutationList.forEach((mutation) => {\n        if (\n          mutation.type === \"attributes\" &&\n          mutation.target.nodeName === \"TP-YT-PAPER-BUTTON\" &&\n          mutation.target.id === \"button\"\n        ) {\n          // console.log('Short thumb button status changed');\n          if (mutation.target.getAttribute(\"aria-pressed\") === \"true\") {\n            mutation.target.style.color =\n              mutation.target.parentElement.parentElement.id === \"like-button\"\n                ? getColorFromTheme(true)\n                : getColorFromTheme(false);\n          } else {\n            mutation.target.style.color = \"unset\";\n          }\n          return;\n        }\n        console.log(\"Unexpected mutation observer event: \" + mutation.target + mutation.type);\n      });\n    },\n  );\n}\n\nfunction isLikesDisabled() {\n  // return true if the like button's text doesn't contain any number\n  if (isMobile()) {\n    return /^\\D*$/.test(getButtons().children[0].querySelector(\".button-renderer-text\").innerText);\n  }\n  return /^\\D*$/.test(getLikeTextContainer().innerText);\n}\n\nfunction isVideoLiked() {\n  if (isMobile()) {\n    return getLikeButton().querySelector(\"button\").getAttribute(\"aria-label\") === \"true\";\n  }\n  return (\n    getLikeButton().classList.contains(\"style-default-active\") ||\n    getLikeButton().querySelector(\"button\")?.getAttribute(\"aria-pressed\") === \"true\"\n  );\n}\n\nfunction isVideoDisliked() {\n  if (isMobile()) {\n    return getDislikeButton().querySelector(\"button\").getAttribute(\"aria-label\") === \"true\";\n  }\n  return (\n    getDislikeButton().classList.contains(\"style-default-active\") ||\n    getDislikeButton().querySelector(\"button\")?.getAttribute(\"aria-pressed\") === \"true\"\n  );\n}\n\nfunction getState(storedData) {\n  if (isVideoLiked()) {\n    return { current: LIKED_STATE, previous: storedData.previousState };\n  }\n  if (isVideoDisliked()) {\n    return { current: DISLIKED_STATE, previous: storedData.previousState };\n  }\n  return { current: NEUTRAL_STATE, previous: storedData.previousState };\n}\n\n//---   Sets The Likes And Dislikes Values   ---//\nfunction setLikes(likesCount) {\n  console.log(`SET likes ${likesCount}`);\n  getLikeTextContainer().innerText = likesCount;\n}\n\nfunction setDislikes(dislikesCount) {\n  console.log(`SET dislikes ${dislikesCount}`);\n\n  const _container = getDislikeTextContainer();\n  _container?.removeAttribute(\"is-empty\");\n\n  let _dislikeText;\n  if (!isLikesDisabled()) {\n    if (isMobile()) {\n      getButtons().children[1].querySelector(\".button-renderer-text\").innerText = dislikesCount;\n      return;\n    }\n    _dislikeText = dislikesCount;\n  } else {\n    console.log(\"likes count disabled by creator\");\n    if (isMobile()) {\n      getButtons().children[1].querySelector(\".button-renderer-text\").innerText = localize(\"TextLikesDisabled\");\n      return;\n    }\n    _dislikeText = localize(\"TextLikesDisabled\");\n  }\n\n  if (_dislikeText != null && _container?.innerText !== _dislikeText) {\n    _container.innerText = _dislikeText;\n  }\n}\n\nfunction getLikeCountFromButton() {\n  try {\n    if (isShorts()) {\n      //Youtube Shorts don't work with this query. It's not necessary; we can skip it and still see the results.\n      //It should be possible to fix this function, but it's not critical to showing the dislike count.\n      return false;\n    }\n\n    let likeButton =\n      getLikeButton().querySelector(\"yt-formatted-string#text\") ?? getLikeButton().querySelector(\"button\");\n\n    let likesStr = likeButton.getAttribute(\"aria-label\").replace(/\\D/g, \"\");\n    return likesStr.length > 0 ? parseInt(likesStr) : false;\n  } catch {\n    return false;\n  }\n}\n\nfunction processResponse(response, storedData) {\n  const formattedDislike = numberFormat(response.dislikes);\n  setDislikes(formattedDislike);\n  if (extConfig.numberDisplayReformatLikes === true) {\n    const nativeLikes = getLikeCountFromButton();\n    if (nativeLikes !== false) {\n      setLikes(numberFormat(nativeLikes));\n    }\n  }\n  storedData.dislikes = parseInt(response.dislikes);\n  storedData.likes = getLikeCountFromButton() || parseInt(response.likes);\n  createRateBar(storedData.likes, storedData.dislikes);\n  if (extConfig.coloredThumbs === true) {\n    if (isShorts()) {\n      // for shorts, leave deactivated buttons in default color\n      let shortLikeButton = getLikeButton().querySelector(\"tp-yt-paper-button#button\");\n      let shortDislikeButton = getDislikeButton().querySelector(\"tp-yt-paper-button#button\");\n      if (shortLikeButton.getAttribute(\"aria-pressed\") === \"true\") {\n        shortLikeButton.style.color = getColorFromTheme(true);\n      }\n      if (shortDislikeButton.getAttribute(\"aria-pressed\") === \"true\") {\n        shortDislikeButton.style.color = getColorFromTheme(false);\n      }\n      shortsObserver.observe(shortLikeButton);\n      shortsObserver.observe(shortDislikeButton);\n    } else {\n      getLikeButton().style.color = getColorFromTheme(true);\n      getDislikeButton().style.color = getColorFromTheme(false);\n    }\n  }\n  //Temporary disabling this - it breaks all places where getButtons()[1] is used\n  // createStarRating(response.rating, isMobile());\n}\n\n// Tells the user if the API is down\nfunction displayError(error) {\n  getDislikeTextContainer().innerText = localize(\"textTempUnavailable\");\n}\n\nasync function setState(storedData) {\n  if (typeof window !== \"undefined\") {\n    window.__rydSetStateCalls = (window.__rydSetStateCalls || 0) + 1;\n  }\n  storedData.previousState = isVideoDisliked() ? DISLIKED_STATE : isVideoLiked() ? LIKED_STATE : NEUTRAL_STATE;\n  let statsSet = false;\n  console.log(\"Video is loaded. Adding buttons...\");\n\n  let videoId = getVideoId(window.location.href);\n  let likeCount = getLikeCountFromButton() || null;\n\n  let response = await fetch(getApiEndpoint(`/votes?videoId=${videoId}&likeCount=${likeCount || \"\"}`), {\n    method: \"GET\",\n    headers: {\n      Accept: \"application/json\",\n    },\n  })\n    .then((response) => {\n      if (!response.ok) displayError(response.error);\n      return response;\n    })\n    .then((response) => response.json())\n    .catch(displayError);\n  console.log(\"response from api:\");\n  console.log(JSON.stringify(response));\n  if (response !== undefined && !(\"traceId\" in response) && !statsSet) {\n    processResponse(response, storedData);\n  }\n}\n\nasync function setInitialState() {\n  await setState(storedData);\n}\n\nasync function initExtConfig() {\n  initializeDisableVoteSubmission();\n  initializeDisableLogging();\n  initializeColoredThumbs();\n  initializeColoredBar();\n  initializeColorTheme();\n  initializeNumberDisplayFormat();\n  initializeTooltipPercentage();\n  initializeTooltipPercentageMode();\n  initializeNumberDisplayReformatLikes();\n  initializeHidePremiumTeaser();\n  await initializeSelectors();\n}\n\nasync function initializeSelectors() {\n  let result = await fetch(getApiEndpoint(\"/configs/selectors\"), {\n    method: \"GET\",\n    headers: {\n      Accept: \"application/json\",\n    },\n  })\n    .then((response) => response.json())\n    .catch((error) => {\n      console.error(\"Error fetching selectors:\", error);\n    });\n  extConfig.selectors = result ?? extConfig.selectors;\n  console.log(result);\n}\n\nfunction initializeDisableVoteSubmission() {\n  getBrowser().storage.sync.get([\"disableVoteSubmission\"], (res) => {\n    if (res.disableVoteSubmission === undefined) {\n      getBrowser().storage.sync.set({ disableVoteSubmission: false });\n    } else {\n      extConfig.disableVoteSubmission = res.disableVoteSubmission;\n    }\n  });\n}\n\nfunction initializeDisableLogging() {\n  getBrowser().storage.sync.get([\"disableLogging\"], (res) => {\n    if (res.disableLogging === undefined) {\n      getBrowser().storage.sync.set({ disableLogging: true });\n      extConfig.disableLogging = true;\n    } else {\n      extConfig.disableLogging = res.disableLogging;\n    }\n    // Initialize console methods based on logging config\n    initializeLogging();\n  });\n}\n\nfunction initializeColoredThumbs() {\n  getBrowser().storage.sync.get([\"coloredThumbs\"], (res) => {\n    if (res.coloredThumbs === undefined) {\n      getBrowser().storage.sync.set({ coloredThumbs: false });\n    } else {\n      extConfig.coloredThumbs = res.coloredThumbs;\n    }\n  });\n}\n\nfunction initializeColoredBar() {\n  getBrowser().storage.sync.get([\"coloredBar\"], (res) => {\n    if (res.coloredBar === undefined) {\n      getBrowser().storage.sync.set({ coloredBar: false });\n    } else {\n      extConfig.coloredBar = res.coloredBar;\n    }\n  });\n}\n\nfunction initializeColorTheme() {\n  getBrowser().storage.sync.get([\"colorTheme\"], (res) => {\n    if (res.colorTheme === undefined) {\n      getBrowser().storage.sync.set({ colorTheme: false });\n    } else {\n      extConfig.colorTheme = res.colorTheme;\n    }\n  });\n}\n\nfunction initializeNumberDisplayFormat() {\n  getBrowser().storage.sync.get([\"numberDisplayFormat\"], (res) => {\n    if (res.numberDisplayFormat === undefined) {\n      getBrowser().storage.sync.set({ numberDisplayFormat: \"compactShort\" });\n    } else {\n      extConfig.numberDisplayFormat = res.numberDisplayFormat;\n    }\n  });\n}\n\nfunction initializeTooltipPercentage() {\n  getBrowser().storage.sync.get([\"showTooltipPercentage\"], (res) => {\n    if (res.showTooltipPercentage === undefined) {\n      getBrowser().storage.sync.set({ showTooltipPercentage: false });\n    } else {\n      extConfig.showTooltipPercentage = res.showTooltipPercentage;\n    }\n  });\n}\n\nfunction initializeTooltipPercentageMode() {\n  getBrowser().storage.sync.get([\"tooltipPercentageMode\"], (res) => {\n    if (res.tooltipPercentageMode === undefined) {\n      getBrowser().storage.sync.set({ tooltipPercentageMode: \"dash_like\" });\n    } else {\n      extConfig.tooltipPercentageMode = res.tooltipPercentageMode;\n    }\n  });\n}\n\nfunction initializeNumberDisplayReformatLikes() {\n  getBrowser().storage.sync.get([\"numberDisplayReformatLikes\"], (res) => {\n    if (res.numberDisplayReformatLikes === undefined) {\n      getBrowser().storage.sync.set({ numberDisplayReformatLikes: false });\n    } else {\n      extConfig.numberDisplayReformatLikes = res.numberDisplayReformatLikes;\n    }\n  });\n}\n\nfunction initializeHidePremiumTeaser() {\n  getBrowser().storage.sync.get([\"hidePremiumTeaser\"], (res) => {\n    if (res.hidePremiumTeaser === undefined) {\n      getBrowser().storage.sync.set({ hidePremiumTeaser: false });\n      extConfig.hidePremiumTeaser = false;\n    } else {\n      extConfig.hidePremiumTeaser = res.hidePremiumTeaser === true;\n    }\n  });\n}\n\nexport {\n  isMobile,\n  isShorts,\n  isVideoDisliked,\n  isVideoLiked,\n  isNewDesign,\n  isRoundedDesign,\n  getState,\n  setState,\n  setInitialState,\n  setLikes,\n  setDislikes,\n  getLikeCountFromButton,\n  LIKED_STATE,\n  DISLIKED_STATE,\n  NEUTRAL_STATE,\n  extConfig,\n  initExtConfig,\n  storedData,\n  isLikesDisabled,\n};\n"
  },
  {
    "path": "Extensions/combined/src/utils.js",
    "content": "import { extConfig, isShorts } from \"./state\";\n\nfunction numberFormat(numberState) {\n  return getNumberFormatter(extConfig.numberDisplayFormat).format(numberState);\n}\n\nfunction getNumberFormatter(optionSelect) {\n  let userLocales;\n  if (document.documentElement.lang) {\n    userLocales = document.documentElement.lang;\n  } else if (navigator.language) {\n    userLocales = navigator.language;\n  } else {\n    try {\n      userLocales = new URL(\n        Array.from(document.querySelectorAll(\"head > link[rel='search']\"))\n          ?.find((n) => n?.getAttribute(\"href\")?.includes(\"?locale=\"))\n          ?.getAttribute(\"href\"),\n      )?.searchParams?.get(\"locale\");\n    } catch {\n      console.log(\"Cannot find browser locale. Use en as default for number formatting.\");\n      userLocales = \"en\";\n    }\n  }\n\n  let formatterNotation;\n  let formatterCompactDisplay;\n  switch (optionSelect) {\n    case \"compactLong\":\n      formatterNotation = \"compact\";\n      formatterCompactDisplay = \"long\";\n      break;\n    case \"standard\":\n      formatterNotation = \"standard\";\n      formatterCompactDisplay = \"short\";\n      break;\n    case \"compactShort\":\n    default:\n      formatterNotation = \"compact\";\n      formatterCompactDisplay = \"short\";\n  }\n\n  return Intl.NumberFormat(userLocales, {\n    notation: formatterNotation,\n    compactDisplay: formatterCompactDisplay,\n  });\n}\n\nfunction localize(localeString, substitutions) {\n  try {\n    if (typeof chrome !== \"undefined\" && chrome?.i18n?.getMessage) {\n      const args = substitutions === undefined ? [localeString] : [localeString, substitutions];\n      const message = chrome.i18n.getMessage(...args);\n      if (message) {\n        return message;\n      }\n    }\n  } catch (error) {\n    console.warn(\"Localization lookup failed for\", localeString, error);\n  }\n\n  if (Array.isArray(substitutions)) {\n    return substitutions.join(\" \");\n  }\n\n  if (substitutions != null) {\n    return `${substitutions}`;\n  }\n\n  return localeString;\n}\n\nfunction getBrowser() {\n  if (typeof chrome !== \"undefined\" && typeof chrome.runtime !== \"undefined\") {\n    return chrome;\n  } else if (typeof browser !== \"undefined\" && typeof browser.runtime !== \"undefined\") {\n    return browser;\n  } else {\n    console.log(\"browser is not supported\");\n    return false;\n  }\n}\n\nfunction getVideoId(url) {\n  const urlObject = new URL(url);\n  const pathname = urlObject.pathname;\n  if (pathname.startsWith(\"/clip\")) {\n    return (document.querySelector(\"meta[itemprop='videoId']\") || document.querySelector(\"meta[itemprop='identifier']\"))\n      .content;\n  } else {\n    if (pathname.startsWith(\"/shorts\")) {\n      return pathname.slice(8);\n    }\n    return urlObject.searchParams.get(\"v\");\n  }\n}\n\nfunction isInViewport(element) {\n  const rect = element.getBoundingClientRect();\n  const height = innerHeight || document.documentElement.clientHeight;\n  const width = innerWidth || document.documentElement.clientWidth;\n  return (\n    // When short (channel) is ignored, the element (like/dislike AND short itself) is\n    // hidden with a 0 DOMRect. In this case, consider it outside of Viewport\n    !(rect.top == 0 && rect.left == 0 && rect.bottom == 0 && rect.right == 0) &&\n    rect.top >= 0 &&\n    rect.left >= 0 &&\n    rect.bottom <= height &&\n    rect.right <= width\n  );\n}\n\nfunction isShortsLoaded(videoId) {\n  if (!videoId) return false;\n\n  // Find all reel containers\n  const reelContainers = document.querySelectorAll(\".reel-video-in-sequence-new\");\n\n  for (const container of reelContainers) {\n    // Check if this container's thumbnail matches our video ID\n    const thumbnail = container.querySelector(\".reel-video-in-sequence-thumbnail\");\n    if (thumbnail) {\n      const bgImage = thumbnail.style.backgroundImage;\n      // YouTube thumbnail URLs contain the video ID in the format: /vi/VIDEO_ID/\n      if ((bgImage && bgImage.includes(`/${videoId}/`)) || (!bgImage && isInViewport(container))) {\n        // Check if this container has the renderer with visible experiment-overlay\n        const renderer = container.querySelector(\"ytd-reel-video-renderer\");\n        if (renderer) {\n          const experimentOverlay = renderer.querySelector(\"#experiment-overlay\");\n          if (\n            experimentOverlay &&\n            !experimentOverlay.hidden &&\n            window.getComputedStyle(experimentOverlay).display !== \"none\" &&\n            experimentOverlay.hasChildNodes()\n          ) {\n            return true;\n          }\n        }\n      }\n    }\n  }\n\n  return false;\n}\n\nfunction isVideoLoaded() {\n  const videoId = getVideoId(window.location.href);\n\n  // Check if this is a Shorts URL\n  if (isShorts()) {\n    return isShortsLoaded(videoId);\n  }\n\n  // Regular video checks\n  return (\n    // desktop: spring 2024 UI\n    document.querySelector(`ytd-watch-grid[video-id='${videoId}']`) !== null ||\n    // desktop: older UI\n    document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null ||\n    // mobile: no video-id attribute\n    document.querySelector('#player[loading=\"false\"]:not([hidden])') !== null\n  );\n}\n\nconst originalConsole = {\n  log: console.log.bind(console),\n  debug: console.debug.bind(console),\n  info: console.info.bind(console),\n  warn: console.warn.bind(console),\n  error: console.error.bind(console),\n};\n\nfunction initializeLogging() {\n  if (extConfig.disableLogging) {\n    console.log = () => {};\n    console.debug = () => {};\n  } else {\n    console.log = originalConsole.log;\n    console.debug = originalConsole.debug;\n  }\n}\n\nfunction getColorFromTheme(voteIsLike) {\n  let colorString;\n  switch (extConfig.colorTheme) {\n    case \"accessible\":\n      if (voteIsLike === true) {\n        colorString = \"dodgerblue\";\n      } else {\n        colorString = \"gold\";\n      }\n      break;\n    case \"neon\":\n      if (voteIsLike === true) {\n        colorString = \"aqua\";\n      } else {\n        colorString = \"magenta\";\n      }\n      break;\n    case \"classic\":\n    default:\n      if (voteIsLike === true) {\n        colorString = \"lime\";\n      } else {\n        colorString = \"red\";\n      }\n  }\n  return colorString;\n}\n\nfunction querySelector(selectors, element) {\n  let result;\n  for (const selector of selectors) {\n    result = (element ?? document).querySelector(selector);\n    if (result !== null) {\n      return result;\n    }\n  }\n}\n\nfunction querySelectorAll(selectors) {\n  let result;\n  for (const selector of selectors) {\n    result = document.querySelectorAll(selector);\n    if (result.length !== 0) {\n      return result;\n    }\n  }\n  return result;\n}\n\nfunction createObserver(options, callback) {\n  const observerWrapper = new Object();\n  observerWrapper.options = options;\n  observerWrapper.observer = new MutationObserver(callback);\n  observerWrapper.observe = function (element) {\n    this.observer.observe(element, this.options);\n  };\n  observerWrapper.disconnect = function () {\n    this.observer.disconnect();\n  };\n  return observerWrapper;\n}\n\nexport {\n  numberFormat,\n  getNumberFormatter,\n  getBrowser,\n  getVideoId,\n  isInViewport,\n  isVideoLoaded,\n  initializeLogging,\n  getColorFromTheme,\n  localize,\n  querySelector,\n  querySelectorAll,\n  createObserver,\n};\n"
  },
  {
    "path": "Extensions/combined/src/utils.spec.js",
    "content": "/**\n * @jest-environment jsdom\n */\n\njest.mock(\"./state\", () => {\n  const extConfig = {\n    disableVoteSubmission: false,\n    disableLogging: false,\n    coloredThumbs: false,\n    coloredBar: false,\n    colorTheme: \"classic\",\n    numberDisplayFormat: \"compactShort\",\n    selectors: {\n      dislikeTextContainer: [],\n      likeTextContainer: [],\n      buttons: {\n        shorts: { mobile: [], desktop: [] },\n        regular: {\n          mobile: [],\n          desktopMenu: [],\n          desktopNoMenu: [],\n        },\n        likeButton: {\n          segmented: [],\n          segmentedGetButtons: [],\n          notSegmented: [],\n        },\n        dislikeButton: {\n          segmented: [],\n          segmentedGetButtons: [],\n          notSegmented: [],\n        },\n      },\n      menuContainer: [],\n      roundedDesign: [],\n    },\n  };\n\n  const isShorts = jest.fn(() => false);\n\n  return {\n    extConfig,\n    isShorts,\n  };\n});\n\nimport { extConfig, isShorts } from \"./state\";\nimport {\n  numberFormat,\n  getNumberFormatter,\n  localize,\n  getBrowser,\n  getVideoId,\n  isInViewport,\n  isVideoLoaded,\n  initializeLogging,\n  getColorFromTheme,\n  querySelector,\n  querySelectorAll,\n  createObserver,\n} from \"./utils\";\n\nconst originalChrome = global.chrome;\nconst originalBrowser = global.browser;\nconst originalConsole = { ...console };\nconst originalNavigatorLanguage = navigator.language;\nconst originalDocumentQuerySelector = document.querySelector;\nconst originalDocumentQuerySelectorAll = document.querySelectorAll;\nconst originalLocationHref = window.location.href;\n\nfunction resetGlobals() {\n  global.chrome = originalChrome;\n  global.browser = originalBrowser;\n  console.log = originalConsole.log;\n  console.debug = originalConsole.debug;\n  console.info = originalConsole.info;\n  console.warn = originalConsole.warn;\n  console.error = originalConsole.error;\n  document.querySelector = originalDocumentQuerySelector;\n  document.querySelectorAll = originalDocumentQuerySelectorAll;\n  window.history.replaceState(null, \"\", originalLocationHref);\n  Object.defineProperty(navigator, \"language\", {\n    value: originalNavigatorLanguage,\n    configurable: true,\n  });\n  document.body.innerHTML = \"\";\n  document.head.innerHTML = \"\";\n  document.documentElement.lang = \"\";\n  jest.restoreAllMocks();\n  jest.clearAllMocks();\n  extConfig.disableLogging = false;\n  extConfig.colorTheme = \"classic\";\n  extConfig.numberDisplayFormat = \"compactShort\";\n  isShorts.mockReturnValue(false);\n}\n\ndescribe(\"utils\", () => {\n  beforeEach(() => {\n    resetGlobals();\n  });\n\n  afterAll(() => {\n    resetGlobals();\n  });\n\n  describe(\"numberFormat\", () => {\n    it(\"formats values using the configured formatter\", () => {\n      document.documentElement.lang = \"en\";\n      const value = 15320;\n      const expected = Intl.NumberFormat(\"en\", { notation: \"compact\", compactDisplay: \"short\" }).format(value);\n\n      expect(numberFormat(value)).toBe(expected);\n    });\n\n    it(\"uses the requested notation when configuration changes\", () => {\n      document.documentElement.lang = \"en\";\n      extConfig.numberDisplayFormat = \"standard\";\n      const value = 987654;\n      const expected = Intl.NumberFormat(\"en\", { notation: \"standard\", compactDisplay: \"short\" }).format(value);\n\n      expect(numberFormat(value)).toBe(expected);\n    });\n  });\n\n  describe(\"getNumberFormatter\", () => {\n    it(\"prefers the document language when available\", () => {\n      document.documentElement.lang = \"fr\";\n\n      const formatter = getNumberFormatter(\"compactLong\");\n      const options = formatter.resolvedOptions();\n\n      expect(options.locale.toLowerCase()).toContain(\"fr\");\n      expect(options.notation).toBe(\"compact\");\n      expect(options.compactDisplay).toBe(\"long\");\n    });\n\n    it(\"falls back to navigator language\", () => {\n      document.documentElement.lang = \"\";\n      Object.defineProperty(navigator, \"language\", {\n        value: \"de-DE\",\n        configurable: true,\n      });\n\n      const formatter = getNumberFormatter(\"standard\");\n      const options = formatter.resolvedOptions();\n\n      expect(options.locale.toLowerCase()).toContain(\"de\");\n      expect(options.notation).toBe(\"standard\");\n    });\n\n    it(\"uses link locale when document and navigator languages are missing\", () => {\n      document.documentElement.lang = \"\";\n      Object.defineProperty(navigator, \"language\", {\n        value: undefined,\n        configurable: true,\n      });\n\n      const link = document.createElement(\"link\");\n      link.setAttribute(\"rel\", \"search\");\n      link.setAttribute(\"href\", \"https://www.youtube.com/opensearch?locale=it-IT\");\n      document.head.appendChild(link);\n\n      const formatter = getNumberFormatter();\n      const options = formatter.resolvedOptions();\n\n      expect(options.locale.toLowerCase()).toContain(\"it\");\n    });\n\n    it(\"falls back to english when locale detection fails\", () => {\n      document.documentElement.lang = \"\";\n      Object.defineProperty(navigator, \"language\", {\n        value: undefined,\n        configurable: true,\n      });\n\n      const querySpy = jest.spyOn(document, \"querySelectorAll\").mockImplementation(() => {\n        throw new Error(\"boom\");\n      });\n      const consoleSpy = jest.spyOn(console, \"log\").mockImplementation(() => {});\n\n      const formatter = getNumberFormatter();\n      const options = formatter.resolvedOptions();\n\n      expect(options.locale.toLowerCase()).toContain(\"en\");\n      expect(consoleSpy).toHaveBeenCalledWith(\n        \"Cannot find browser locale. Use en as default for number formatting.\",\n      );\n      querySpy.mockRestore();\n    });\n  });\n\n  describe(\"localize\", () => {\n    it(\"returns the localized string from chrome\", () => {\n      global.chrome = {\n        i18n: {\n          getMessage: jest.fn().mockReturnValue(\"Return YouTube Dislike\"),\n        },\n      };\n\n      expect(localize(\"extensionName\")).toBe(\"Return YouTube Dislike\");\n      expect(global.chrome.i18n.getMessage).toHaveBeenCalledWith(\"extensionName\");\n    });\n  });\n\n  describe(\"getBrowser\", () => {\n    it(\"returns the chrome runtime when available\", () => {\n      global.chrome = { runtime: {} };\n\n      expect(getBrowser()).toBe(global.chrome);\n    });\n\n    it(\"falls back to the firefox browser object\", () => {\n      global.chrome = { runtime: undefined };\n      global.browser = { runtime: {} };\n\n      expect(getBrowser()).toBe(global.browser);\n    });\n\n    it(\"logs and returns false when unsupported\", () => {\n      global.chrome = undefined;\n      global.browser = undefined;\n      const spy = jest.spyOn(console, \"log\").mockImplementation(() => {});\n\n      expect(getBrowser()).toBe(false);\n      expect(spy).toHaveBeenCalledWith(\"browser is not supported\");\n    });\n  });\n\n  describe(\"getVideoId\", () => {\n    it(\"extracts the id from a standard watch URL\", () => {\n      const url = \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\";\n\n      expect(getVideoId(url)).toBe(\"dQw4w9WgXcQ\");\n    });\n\n    it(\"handles shorts URLs\", () => {\n      const url = \"https://www.youtube.com/shorts/xyz987\";\n\n      expect(getVideoId(url)).toBe(\"xyz987\");\n    });\n\n    it(\"uses the meta tag for clip URLs\", () => {\n      const meta = document.createElement(\"meta\");\n      meta.setAttribute(\"itemprop\", \"videoId\");\n      meta.content = \"clip123\";\n      document.head.appendChild(meta);\n\n      expect(getVideoId(\"https://www.youtube.com/clip/foobar\")).toBe(\"clip123\");\n    });\n  });\n\n  describe(\"isInViewport\", () => {\n    it(\"returns false for zero-sized rectangles\", () => {\n      const element = {\n        getBoundingClientRect: () => ({ top: 0, left: 0, bottom: 0, right: 0 }),\n      };\n\n      expect(isInViewport(element)).toBe(false);\n    });\n\n    it(\"returns true for elements fully inside the viewport\", () => {\n      const element = {\n        getBoundingClientRect: () => ({ top: 10, left: 20, bottom: 200, right: 300 }),\n      };\n\n      expect(isInViewport(element)).toBe(true);\n    });\n\n    it(\"returns false when element extends beyond the viewport\", () => {\n      const element = {\n        getBoundingClientRect: () => ({ top: -10, left: 0, bottom: 50, right: 50 }),\n      };\n\n      expect(isInViewport(element)).toBe(false);\n    });\n  });\n\n  describe(\"isVideoLoaded\", () => {\n    beforeEach(() => {\n      const origin = window.location.origin;\n      window.history.replaceState(null, \"\", `${origin}/watch?v=testid`);\n    });\n\n    it(\"returns true when desktop grid element is present\", () => {\n      document.querySelector = jest.fn().mockReturnValueOnce({});\n\n      expect(isVideoLoaded()).toBe(true);\n      expect(document.querySelector).toHaveBeenCalledWith(\"ytd-watch-grid[video-id='testid']\");\n    });\n\n    it(\"checks secondary selectors when the grid is absent\", () => {\n      document.querySelector = jest\n        .fn()\n        .mockReturnValueOnce(null)\n        .mockReturnValueOnce({})\n        .mockReturnValue(null);\n\n      expect(isVideoLoaded()).toBe(true);\n      expect(document.querySelector).toHaveBeenNthCalledWith(2, \"ytd-watch-flexy[video-id='testid']\");\n    });\n\n    it(\"falls back to player detection when previous lookups fail\", () => {\n      document.querySelector = jest\n        .fn()\n        .mockReturnValueOnce(null)\n        .mockReturnValueOnce(null)\n        .mockReturnValueOnce({});\n\n      expect(isVideoLoaded()).toBe(true);\n      expect(document.querySelector).toHaveBeenNthCalledWith(3, '#player[loading=\"false\"]:not([hidden])');\n    });\n\n    it(\"defers to shorts lookup when on a shorts URL\", () => {\n      isShorts.mockReturnValue(true);\n      const origin = window.location.origin;\n      window.history.replaceState(null, \"\", `${origin}/shorts/abc123`);\n\n      const container = document.createElement(\"div\");\n      container.className = \"reel-video-in-sequence-new\";\n\n      const thumbnail = document.createElement(\"div\");\n      thumbnail.className = \"reel-video-in-sequence-thumbnail\";\n      thumbnail.style.backgroundImage = \"url(https://i.ytimg.com/vi/abc123/default.jpg)\";\n      container.appendChild(thumbnail);\n\n      const renderer = document.createElement(\"ytd-reel-video-renderer\");\n      const overlay = document.createElement(\"div\");\n      overlay.id = \"experiment-overlay\";\n      overlay.appendChild(document.createElement(\"span\"));\n      renderer.appendChild(overlay);\n      container.appendChild(renderer);\n\n      document.body.appendChild(container);\n\n      expect(isVideoLoaded()).toBe(true);\n    });\n  });\n\n  describe(\"initializeLogging\", () => {\n    it(\"disables console output when configured\", () => {\n      extConfig.disableLogging = true;\n      const originalLog = console.log;\n      const originalDebug = console.debug;\n\n      initializeLogging();\n\n      expect(console.log).not.toBe(originalLog);\n      expect(console.debug).not.toBe(originalDebug);\n    });\n\n    it(\"restores console output when logging is enabled\", () => {\n      const stubLog = jest.fn();\n      const stubDebug = jest.fn();\n      console.log = stubLog;\n      console.debug = stubDebug;\n\n      extConfig.disableLogging = false;\n      initializeLogging();\n\n      expect(console.log).not.toBe(stubLog);\n      expect(console.debug).not.toBe(stubDebug);\n    });\n  });\n\n  describe(\"getColorFromTheme\", () => {\n    it(\"returns accessible colors when configured\", () => {\n      extConfig.colorTheme = \"accessible\";\n\n      expect(getColorFromTheme(true)).toBe(\"dodgerblue\");\n      expect(getColorFromTheme(false)).toBe(\"gold\");\n    });\n\n    it(\"returns neon colors when configured\", () => {\n      extConfig.colorTheme = \"neon\";\n\n      expect(getColorFromTheme(true)).toBe(\"aqua\");\n      expect(getColorFromTheme(false)).toBe(\"magenta\");\n    });\n\n    it(\"defaults to classic palette\", () => {\n      extConfig.colorTheme = \"classic\";\n\n      expect(getColorFromTheme(true)).toBe(\"lime\");\n      expect(getColorFromTheme(false)).toBe(\"red\");\n    });\n  });\n\n  describe(\"querySelector\", () => {\n    it(\"returns the first matching element\", () => {\n      document.body.innerHTML = `\n        <div class=\"first\"></div>\n        <div class=\"second\"></div>\n      `;\n\n      const result = querySelector([\".missing\", \".second\", \".first\"]);\n\n      expect(result).toBeInstanceOf(Element);\n      expect(result?.className).toBe(\"second\");\n    });\n\n    it(\"scopes the search to the provided element\", () => {\n      const host = document.createElement(\"div\");\n      host.innerHTML = '<span class=\"inner\"></span>';\n\n      const result = querySelector([\".inner\"], host);\n      expect(result).not.toBeNull();\n      expect(result?.className).toBe(\"inner\");\n    });\n\n    it(\"returns undefined when no selector matches\", () => {\n      expect(querySelector([\".unmatched\"])).toBeUndefined();\n    });\n  });\n\n  describe(\"querySelectorAll\", () => {\n    it(\"returns the first selector with results\", () => {\n      document.body.innerHTML = `\n        <div class=\"candidate\"></div>\n        <div class=\"candidate\"></div>\n      `;\n\n      const result = querySelectorAll([\".missing\", \".candidate\"]);\n\n      expect(result).toHaveLength(2);\n    });\n\n    it(\"returns an empty NodeList when no matches are found\", () => {\n      const result = querySelectorAll([\".still-missing\"]);\n\n      expect(result).toBeInstanceOf(NodeList);\n      expect(result).toHaveLength(0);\n    });\n  });\n\n  describe(\"createObserver\", () => {\n    it(\"wraps MutationObserver with helper methods\", () => {\n      const observeMock = jest.fn();\n      const disconnectMock = jest.fn();\n      const callback = jest.fn();\n\n      const originalMutationObserver = global.MutationObserver;\n      global.MutationObserver = jest.fn().mockImplementation(() => ({\n        observe: observeMock,\n        disconnect: disconnectMock,\n      }));\n\n      const wrapper = createObserver({ attributes: true }, callback);\n      const element = document.createElement(\"div\");\n\n      wrapper.observe(element);\n      wrapper.disconnect();\n\n      expect(global.MutationObserver).toHaveBeenCalledWith(callback);\n      expect(observeMock).toHaveBeenCalledWith(element, { attributes: true });\n      expect(disconnectMock).toHaveBeenCalled();\n\n      global.MutationObserver = originalMutationObserver;\n    });\n  });\n});\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike is an open-source extension that returns the YouTube dislike count.</b><br>\n    Available for Chrome and Firefox as a Web Extension.<br>\n    Also available for other browsers as JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## The Story\n\nOn November 10th, 2021, Google [announced](https://blog.youtube/news-and-events/update-to-youtube/) that the YouTube dislike count would be removed.\n\nAdditionally, the `dislike` field in the YouTube API was [removed](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) on December 13th, 2021, removing any ability to judge the quality of content before watching.\n\n## What it Does\n\nWith the removal of dislike stats from the YouTube API, our backend switched to using a combination of scraped dislike stats and estimates extrapolated from extension user data.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Why it Matters\n\nYou can learn more at our website at: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API documentation\n\nThird-party use of this open API is allowed with the following restrictions:\n\n- **Attribution**: This project should be clearly attributed with a link to [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Rate Limiting**: There are per client rate limits in place of 100 per minute and 10,000 per day. This will return a _429_ status code indicating that your application should back off.\n\nThe API is accessible over the following base URL:\nhttps://returnyoutubedislikeapi.com\n\nList of available endpoints is available here:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Get votes\n\nExample to get votes of a given YouTube video ID:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2022-04-09T21:44:20.5103Z\",\n  \"likes\": 31885,\n  \"rawDislikes\": 31946,\n  \"rawLikes\": 457,\n  \"dislikes\": 579721,\n  \"rating\": 1.2085329444119253,\n  \"viewCount\": 3762293,\n  \"deleted\": false\n}\n```\n\nA non-existent YouTube ID will return status code _404_ \"Not Found\".  \nAn incorrectly formatted YouTube ID will return status code _400_ \"Bad Request\".\n\n<!---\n## API documentation\n\nYou can view all documentation on our website.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Contributing\n\nPlease read the [contribution guide](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Support this project!\n\nYou can support this project by donating to us on the link below:\n\n[Donate](https://returnyoutubedislike.com/donate)\n\n## Sponsors\n\n[Become our sponsor and be featured in repository and on the website](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEar.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n#Return YouTube Dislike\n<p align=\"center\">\n  <b>إعادة زر عدم الإعجاب على يوتيوب هو امتداد مفتوح المصدر يعيد عرض عدد عدم الإعجاب على يوتيوب.</b><br>\n  متاح لمتصفح كروم وفايرفوكس كامتداد ويب.<br>\n  متاح أيضًا لمتصفحات أخرى كـ JS Userscript.<br><br>\n  <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## القصة\n\nفي 10 نوفمبر 2021، أعلنت جوجل [هنا](https://blog.youtube/news-and-events/update-to-youtube/) أن عدد عدم الإعجاب على يوتيوب سيتم إزالته.\n\nبالإضافة إلى ذلك، تم إزالة حقل `dislike` من واجهة برمجة تطبيقات يوتيوب [هنا](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) في 13 ديسمبر 2021، مما أزال أي قدرة على تقييم جودة المحتوى قبل مشاهدته.\n\n## ما الذي يفعله\n\nمع إزالة إحصائيات عدم الإعجاب من واجهة برمجة تطبيقات يوتيوب، تحول نظامنا الخلفي إلى استخدام مزيج من إحصائيات عدم الإعجاب المستخرجة، وتقديرات مستنبطة من بيانات مستخدمي الامتداد.\n\n[الأسئلة الشائعة](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## لماذا يهم\n\nيمكنك معرفة المزيد على موقعنا الإلكتروني: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## توثيق واجهة برمجة التطبيقات\n\nيسمح باستخدام هذه الواجهة المفتوحة من قبل أطراف ثالثة مع القيود التالية:\n\n- **النسبة**: يجب أن يُنسب هذا المشروع بوضوح مع رابط إلى [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **تحديد المعدل**: هناك حدود معدل لكل عميل تبلغ 100 في الدقيقة و 10,000 في اليوم. سيعيد هذا رمز الحالة _429_ مما يشير إلى أن تطبيقك يجب أن يتراجع.\n\nواجهة برمجة التطبيقات متاحة عبر عنوان URL الأساسي التالي:  \nhttps://returnyoutubedislikeapi.com\n\nقائمة النقاط النهائية المتاحة متاحة هنا:  \nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### الحصول على الأصوات\n\nمثال للحصول على أصوات فيديو معين على يوتيوب:  \n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nسيعيد معرف يوتيوب غير الموجود رمز الحالة _404_ \"غير موجود\".  \nسيعيد معرف يوتيوب غير الصحيح رمز الحالة _400_ \"طلب غير صالح\".\n\n<!---\n## توثيق واجهة برمجة التطبيقات\n\nيمكنك عرض جميع الوثائق على موقعنا الإلكتروني.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## المساهمة\n\nيرجى قراءة [دليل المساهمة](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## دعم هذا المشروع!\n\nيمكنك دعم هذا المشروع من خلال التبرع لنا على الرابط أدناه:\n\n[تبرع](https://returnyoutubedislike.com/donate)\n\n## الرعاة\n\n[كن راعينا](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEaz.md",
    "content": "[![Chrome Web Mağazası](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Mağazası İstifadəçilər](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla Rəyi](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla yükləmələri](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit sayı](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issue'lar](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![Lisans](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# YouTube bəyənməmə sayını geri qaytarın\n\n<p align=\"center\">\n    <b>YouTube Bəyənməmə Sayını əldə edin YouTube-da bəyənməmə sayını göstərən açıq mənbəli əlavədir.</b><br>\n    Chrome və Firefox-da Veb Uzantısı kimi mövcuddur.<br>\n    Ayrıca diğer tarayıcılar için JS Userscript olarak da mevcuttur.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Onun hekayəsi\n\n10 noyabr 2021-ci ildə Google elan etdi ki, YouTube bəyənməmə sayları silinəcək [Elan etdi](https://blog.youtube/news-and-events/update-to-youtube/).\n\nƏlavə olaraq, YouTube API-də \"like\" sahəsi 13 dekabr 2021-ci il tarixində [qaldırıldı](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) ve içeriğin kalitesini izlemeden önce yargılayabilme olanağı ortadan kaldırıldı.\n\n## Bunun nə faydası var?\n\nBəyənməmə statistikasının YouTube API-dən silinməsi ilə backendimiz genişləndirmə istifadəçi datasından təxmin edilən ümumi bəyənməmə statistikasının kombinasiyasından istifadə etməyə keçdi.\n\n[Tez-tez verilən suallar](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQtr.md)\n\n## Niyə Vacibdir\n\nƏtraflı məlumatı saytımızda tapa bilərsiniz: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API Sənədləri\n\nBu açıq API-nin üçüncü tərəfin istifadəsinə aşağıdakı məhdudiyyətlərlə icazə verilir:\n\n- **Atribut**: Bu layihə açıq şəkildə [returnyoutubedislike.com](https://returnyoutubedislike.com/) ünvanına yönləndirilməlidir.\n- **Sürət Məhdudiyyəti**: Bir istifadəçi üçün dəqiqədə 100 və gündə 10.000 sürət həddi var. Bu, _429_ status kodunu qaytarır, ərizənizin geri götürülməsi lazım olduğunu göstərir.\n\nAPI-yə aşağıdakı əsas URL vasitəsilə daxil olmaq olar:\nhttps://returnyoutubedislikeapi.com\n\nMövcud son nöqtələrin siyahısı burada mövcuddur:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Səslərin əldə edilməsi\n\nMüəyyən bir YouTube video ID-si üçün reytinqləri əldə etmək üçün bir nümunə:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nHeç bir mövcud YouTube ID-si 404 \"Tapılmadı\" status kodunu qaytarmayacaq.\n\n<!---\n## API Sənədləri\n\nSaytımızda bütün sənədlərlə tanış ola bilərsiniz.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## Töhfə verin\n\nZəhmət olmasa [töhfə bələdçisi](CONTRIBUTINGaz.md)nu oxuyun.\n\n## Bu Layihəyə Dəstək olun!\n\nAşağıdakı linkdən bizə ianə verə və bu layihəyə dəstək ola bilərsiniz:\n\n[Bağışlayın](https://returnyoutubedislike.com/donate)\n\n## Sponsorlar\n\n\n[Sponsorumuz olun](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEbg.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike е отворено разширение, което връща броя на дизлайковете в YouTube.</b><br>\n    Достъпно за Chrome и Firefox като уеб разширение.<br>\n     Също така е налично за други браузъри като JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Историята\n\nНа 10 ноември 2021 г. Google [обяви](https://blog.youtube/news-and-events/update-to-youtube/), че броят на дизлайковете в YouTube ще бъде премахнат.\n\nОсвен това, полето `dislike` в YouTube API беше [премахнато](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) на 13 декември 2021 г., което премахна всяка възможност за оценка на качеството на съдържанието преди гледането му.\n\n## Какво прави\n\nС премахването на статистиката за дизлайковете от YouTube API, нашият бекенд премина към използване на комбинация от скрейпната статистика за дизлайкове, оценки извлечени от данните на потребителите на разширението.\n\n[Често задавани въпроси](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQbg.md)\n\n## Защо е важно\n\nМожете да научите повече на нашия уебсайт: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Документация за API\n\nИзползването от трети страни на този отворен API е разрешено със следните ограничения:\n\n- **Атрибуция**: Този проект трябва ясно да се атрибутира с връзка към [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Ограничение на скоростта**: Има ограничения на скоростта за клиента от 100 на минута и 10 000 на ден. Това ще върне статусен код 429, който показва, че вашето приложение трябва да се оттегли.\n\nAPI е достъпен на следния базов URL адрес: \nhttps://returnyoutubedislikeapi.com\n\nСписък на наличните крайща е достъпен тук: \nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Получаване на гласове\n\nПример за получаване на гласове за даден YouTube видео идентификатор: \n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nНе съществуващ YouTube идентификатор ще върне статус код 404 \"Not Found\".\nГрешно формиран YouTube идентификатор ще върне 400 \"Bad Request\".\n\n<!---\n## Документация за API\n\nМоже да видите цялата документация на нашия уебсайт.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## Сътрудничество\n\nМоля, прочетете [ръководството за сътрудничество](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGbg.md).\n\n## Подкрепете този проект!\n\nМожете да подкрепите този проект, като ни дарите на следния линк:\n\n[Дарение](https://returnyoutubedislike.com/donate)\n\n## Спонсори\n\n\n\n[Become our sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEcn.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike是一个开源的扩展，可以显示 YouTube 的不喜欢数量。</b><br>\n    适用于 Chrome 和 Firefox 作为 Web 扩展。<br>\n    也可以作为 JS Userscript 在其他浏览器上使用。<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## 故事\n\n在2021年11月10日，Google [宣布](https://blog.youtube/news-and-events/update-to-youtube/) 将移除 YouTube 的不喜欢数量。\n\n此外，YouTube API 中的 `dislike` 字段于2021年12月13日 [被移除](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts)，移除了在观看之前评估内容质量的能力。\n\n## 它是如何工作的\n\n随着 YouTube API 中不喜欢统计信息的移除，我们的后端切换到使用从扩展用户数据中抓取的不喜欢统计信息和估算数据的组合。\n\n[常见问题解答](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQcn.md)\n\n## 为什么重要\n\n您可以在我们的网站上了解更多信息：[returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API 文档\n\n允许第三方使用此开放 API，但有以下限制：\n\n- **归因**: 该项目应清楚地归因于 [returnyoutubedislike.com](https://returnyoutubedislike.com/) 的链接。\n- **速率限制**: 客户端的速率限制为每分钟100次和每天10,000次。这将返回 _429_ 状态代码，表示您的应用程序应该减速。\n\nAPI 可以通过以下基本 URL 访问：\nhttps://returnyoutubedislikeapi.com\n\n可用端点列表在这里：\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### 获取投票\n\n示例获取给定 YouTube 视频 ID 的投票：\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\n不存在的 YouTube ID 将返回状态码 _404_ \"未找到\"。\n格式错误的 YouTube ID 将返回 _400_ \"请求无效\"。\n\n## 贡献\n\n请阅读[贡献指南](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGcn.md)。\n\n## 支持这个项目！\n\n您可以通过以下链接向我们捐赠来支持这个项目：\n\n[捐赠](https://returnyoutubedislike.com/donate)\n\n## 赞助商\n"
  },
  {
    "path": "READMEda.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike er en open-source udvidelse, som bringer antallet af dislikes tilbage på YouTube.</b><br>\n    Den er tilgængelig til Chrome og Firefox som en webudvidelse.<br>\n    Den er også tilgængelig i andre browsere som et JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Historien\n\nDen 10. november 2021, [annoncerede Google](https://blog.youtube/news-and-events/update-to-youtube/) at Youtube dislike tælleren ville blive fjernet.\n\nDesuden blev dislike-feltet i YouTube API'en [fjernet](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) den 13. december 2021, 2021, hvilket fjernede muligheden til at vurdere kvaliteten af indholdet før afspilning.\n\n## Hvad den gør\n\nMed fjernelsen af dislike stats fra Youtube API'en, har vores backend skiftet til at bruge en kombination af scraped dislike-statistic, hvor skøn er extrapoleret fra extension-brugerdata.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Hvorfor det betyder noget\n\nDu kan lære mere på vores hjemmeside: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API dokumentation\n\nTredjeparts brug af denne åbne API er tilladt under de følgende restriktioner:\n\n- **Tilskrivning**: Dette projekt skal tydeligt tilskrives med et link til [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Rategrænse**: Der er en grænse på 100 anmodninger per minut og 10.000 per dag for hver klient. Dette vil returnere en statuskode på _429_, som indikerer, at din applikation skal trække sig tilbage.\n\nAPI'en er tilgængelig via følgende base-URL:\nhttps://returnyoutubedislikeapi.com\n\nEn liste over tilgængelige endpoints er tilgængelig her:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Få stemmer\n\nEksempel på at få stemmer på en given YouTube-video-ID:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nEn ikke-eksisterende YouTube-ID vil returnere statuskoden _404_ \"Not Found\".\nEn forkert formet YouTube-ID vil returnere _400_ \"Bad Request\".\n\n<!---\n## API documentation\n\nDu kan se al dokumentation på vores hjemmeside.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## Bidrag\n\nLæs venligst [bidragsvejledningen](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Støt dette projekt!\n\nDu kan støtte dette projekt ved at donere til os på nedenstående link:\n\n[Doner](https://returnyoutubedislike.com/donate)\n\n## Sponsorer\n\n\n\n[Bliv vores sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEde.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike (Bring die YouTube Dislikes zurück) ist eine open-source Erweiterung, die den Dislike-Zähler zurückbringt.</b><br>\n    Für den <a href=\"https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi\">Chromium Browser</a> sowie für <a href=\"https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/\">Firefox</a> als Browser Erweiterung verfügbar.<br>\n    Für andere Browser ist es auch als JS Userscript verfügbar.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Die Geschichte <!-- ## The Story -->\n\nAm 10. November 2021 [kündigte Google an](https://blog.youtube/news-and-events/update-to-youtube/), dass der YouTube Dislike-Zähler von der Plattform entfernt werden sollte.\n\nZusätzlich wurde das `dislike` Feld in der YouTube API am 13. Dezember 2021 [entfernt](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) und damit auch jegliche Möglichkeit, die Qualität der Inhalte zu bewerten, bevor man das Video sah.\n\n## Was die Erweiterung macht <!-- ## What it Does -->\n\nMit der entfernung der Statistiken der YouTube API wechselte unser Backend zu einer Kombination von archivierten Dislike Statistiken sowie den extrapolierten Schätzungen durch die Nutzerdaten, die in dieser Erweiterung anfallen, um die Dislike-Zahlen zu berechnen.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Weshalb dies wichtig ist <!-- ## Why it Matters -->\nErfahre mehr dazu auf unserer Website: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API Dokumentierung <!-- ## API documentation -->\n\nDritte dürfen diese öffentliche API mit folgenden Restriktionen benutzen:\n\n- **Namensnennung**: Dieses Projekt sollte klar und mit dem Link [returnyoutubedislike.com](https://returnyoutubedislike.com/) versehen zugeordnet werden.\n- **Rate limits**: Es bestehen pro Nutzer Limitierungen von 100 pro Minute sowie 10'000 pro Tag. Dies wird eine Statusmeldung von _429_ ausgeben, die darauf hinweist, dass sich die Anwendung zurückziehen sollte.\n\nDie API ist unter der folgenden Basis-URL verfügbar:  \nhttps://returnyoutubedislikeapi.com\n\nEine Liste mit allen verfügbaren Endpunkten ist hier zu finden:  \nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Abstimmungsdaten erhalten <!-- ### Get votes -->\n\nBeispiel um die Abstimmungen einer gegebenen YouTube ID zu erhalten:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nNicht existierende YouTube IDs werden den Statuscode _404_ \"Not Found\" zurückgeben.\nFalsch geformte YouTube IDs werden den Statuscode _400_ \"Bad Request\" zurückgeben.\n\n<!---\n## API Dokumentation\n\nSie können die gesamte Dokumentation auf unserer Website einsehen.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Beitragen <!-- ## Contributing -->\n\nBitte lesen Sie das [Beitrags-Handbuch](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Unterstütze dieses Projekt! <!-- ## Support this project! -->\n\nUnterstütze gerne das Projekt mit einer Spende über den folgenden Link:\n\n[spenden](https://returnyoutubedislike.com/donate)\n\n## Sponsoren <!-- ## Sponsors -->\n\n[Werde unser Sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEes.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike (recuperar los *dislikes* de YouTube) es una extensión de código abierto que reintroduce el contador de *dislikes* (o \"No me gusta's\").</b><br>\n    Está disponible para Chrome y Firefox como una extensión web.<br>\n    También está disponible para otros navegadores como un *userscript* de JS.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## La historia\n\nEl 10 de noviembre de 2021, Google [anunció](https://blog.youtube/news-and-events/update-to-youtube/) que eliminarían el contador de _dislikes_ de YouTube.\n\nAdicionalmente, el campo `dislike` de la API de YouTube también fue [eliminado](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) el 13 de diciembre de 2021, acabando con la posibilidad de juzgar la calidad de un contenido antes de verlo.\n\n## Qué hace\n\nTras la retirada de las estadísticas de _dislikes_ de la API de YouTube, nuestro _backend_ pasó a usar una combinación de las estadísticas archivadas de _dislikes_ y datos extrapolados de los usuarios de la extensión.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Por qué importa\n\nPara más información, visita nuestro sitio web: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Documentación de la API\n\nSe permite el uso de terceros de esta API abierta bajo las siguientes restricciones:\n\n- **Atribución**: Este proyecto debe estar claramente atribuido con un enlace a [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Límites de velocidad**: Hay límites de velocidad por cliente de 100 solicitudes por minuto y 10.000 al día. Al excederlos se mostrará un código de estado _429_, indicando que tu aplicación debe bajar la velocidad.\n\nLa API es accesible a través de la siguiente URL base:\nhttps://returnyoutubedislikeapi.com\n\nLa lista de los _endpoints_ disponibles se puede consultar aquí:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Obtención de votos\n\nEjemplo para obtener los votos del ID de un vídeo de YouTube proporcionado:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nUn ID de YouTube no existente mostrará el código de estado _404_ \"Not Found\" (no encontrado).\nUn ID de YouTube mal estructurado mostrará el código _400_ \"Bad Request\" (solicitud incorrecta).\n\n<!---\n## Documentación de la API\n\nPuedes ver toda la documentación en nuestra página web.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Contribuciones\n\nLe solicitamos que lea la [guía de contribución](CONTRIBUTINGes.md) antes de empezar.\n\n## ¡Apoya este proyecto!\n\nPuedes apoyar este proyecto enviando un donativo a través del siguiente enlace:\n\n[Donar](https://returnyoutubedislike.com/donate)\n\n## Patrocinadores\n\n\n\n[Patrocínanos](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEfr.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike est une extension open-source qui ré-affiche les dislikes (pouces rouges) sur YouTube.</b><br>\n    Disponible pour Chrome et Firefox en tant qu'extension Web.<br>\n    Également disponible pour d'autres navigateurs en tant que JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Le récit des événements\n\nLe 10 novembre 2021, Google [a annoncé](https://blog.youtube/news-and-events/update-to-youtube/) que le compteur de dislike sur YouTube serait supprimé.\n\nEn outre, le champ `dislike` de l'API YouTube a été [supprimé](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) le 13 décembre 2021, supprimant ainsi toute possibilité de juger de la qualité du contenu d'une vidéo avant de la regarder.\n\n## Ce qu'il fait\n\nAvec la suppression des statistiques du nombre de dislike de l'API YouTube, notre backend est passé à l'utilisation d'une combinaison de statistiques du nombre de dislike scrapé et d'estimations extrapolées à partir des données d'extension des utilisateurs.\n\n[FAQ](Docs/FAQfr.md)\n\n## Pourquoi c'est important\n\nVous pouvez en savoir plus sur notre site web à l'adresse suivante : [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Documentation de l'API\n\nL'utilisation par des tiers de cette API ouverte est autorisée avec les restrictions suivantes :\n\n- **Attribution**: Ce projet doit être clairement nommé avec un lien vers [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Limitation des requêtes**: Il y a des limites de requêtes par client en place qui sont de 100 requêtes par minute et 10 000 par jour. Nous renverrons un code d'erreur _429_ indiquant que votre application devrait se calmer.\n\nL'API est accessible via l'URL de base suivante:\nhttps://returnyoutubedislikeapi.com\n\nLa liste des endpoints est disponible ici:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Obtenir les likes\n\nExemple pour obtenir les likes d'une vidéo YouTube avec un ID donné:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nSi aucune vidéo YouTube n'a cet ID, le code d'erreur _404_ \"Not Found\" sera retourné.\nUn ID YouTube invalide renverra le code d'erreur _400_ \"Bad Request\".\n\n<!---\n## Documentation de l'API\n\nVous pouvez consulter toute la documentation sur notre site web.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Contribution\n\nVeuillez lire le [guide des contributions](CONTRIBUTINGfr.md).\n\n## Soutenez ce projet !\n\nVous pouvez soutenir ce projet en faisant un don grâce au lien ci-dessous :\n\n[Donner](https://returnyoutubedislike.com/donate)\n\n## Sponsors\n\n\n\n[Devenez notre sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEgr.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Το Return YouTube Dislike είναι μια επέκταση ανοιχτού κώδικα η οποία επαναφέρει τον μετρητή dislike στο YouTube.</b><br>\n    Είναι διαθέσιμο για Chrome και Firefox ως πρόσθετη επέκταση.<br>\n    Επίσης είναι διαθέσιμο και σε άλλους φυλλομετρητές ως JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Ιστορικό\n\nΣτις 10 Νοεμβρίου 2021, η Google [ανακοίνωσε](https://blog.youtube/news-and-events/update-to-youtube/) ότι θα αφαιρέσει τον μετρητή dislike απο το YouTube.\n\nΑκολούθως, στις 13 Δεκεμβρίου 2021 [αφαιρέθηκε](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) το πεδίο `dislike` και απο το YouTube API, στερώντας κάθε δυνατότητα να κρίνουμε την ποιότητα του περιεχομένου πριν από την παρακολούθηση.\n\n## Πώς λειτουργεί\n\nΈπειτα απο την αφαίρεση των στατιστικών dislike απο το YouTube API, το backend μας άλλαξε ώστε να χρησιμοποιεί ένα συνδυασμό συλλογής στατιστικών, και εκτιμήσεων με βάση τα δεδομένα χρήσης της επέκτασης.\n\n[Συχνές Ερωτήσεις](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Γιατί έχει σημασία\n\nΜπορείτε να μάθετε περισσότερα στην ιστοσελίδα μας: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Τεκμηρίωση του API\n\nΗ χρήση απο τρίτους αυτού του ανοιχτού API επιτρέπεται με τους παρακάτω περιορισμούς:\n\n- **Αναφορά**: Να υπάρχει καθαρή αναφορά στον κώδικα αυτού του έργου με τη χρήση του παρακάτω συνδέσμου [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Όριο Χρήσης**: Υπάρχει όριο χρήσης ανα χρήστη το οποίο είναι 100 το λεπτό και 10000 τη μέρα. Σε αντίθετη περίπτωση επιστρέφεται μήνυμα λάθους _429_ υποδεικνύοντας οτι η εφαρμογή του χρήστη θα πρέπει να περιορίσει τη χρήση της.\n\nΤο API είναι διαθέσιμο στον παρακάτω σύνδεσμο:\nhttps://returnyoutubedislikeapi.com\n\nΛίστα με τα διαθέσιμα endpoints μπορείτε να βρείτε εδώ:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Λήψη ψήφων\n\nΠαράδειγμα ώστε να αντλήσετε τις ψήφους από συγκεκριμένο YouTube video ID:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nΜή διαθέσιμο YouTube ID θα επιστρέψει κωδικό status _404_ \"Not Found\".\nΛανθασμένη δομή YouTube ID θα επιστρέψει _400_ \"Bad Request\".\n\n<!---\n## API documentation\n\nYou can view all documentation on our website.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Συνεισφορά\n\nΠαρακαλώ διαβάστε τον [οδηγό συνεισφοράς](CONTRIBUTING.md).\n\n## Υποστηρίξτε αυτό το project!\n\nΜπορείτε να υποστηρίξετε αυτό το έργο κάνοντας δωρεά προς εμάς στον παρακάτω σύνδεσμο:\n\n[Δωρεά](https://returnyoutubedislike.com/donate)\n\n## Χορηγοί\n\n\n\n[Γίνετε χορηγός μας](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEhu.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>A Return YouTube Dislike egy nyílt forráskódú bővítmény, ami visszahozza a YouTube dislike számlálóját.</b><br>\n    Elérhető Chrome és Firefox böngészőkhöz bővítményként, illetve további böngészőkhöz JS Userscript formában.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Történet\n\n2021\\. november 10-én a Google [bejelentette](https://blog.youtube/news-and-events/update-to-youtube/), hogy eltávolítják a YouTube dislike (\"nem tetszik\") számlálóját.\n\nEzzel együtt a YouTube API `dislike` mezőjét is [törölték](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) 2021. december 13-án, így megszűnt az a lehetőség, hogy a tartalmak megtekintése előtt meggyőződjünk azok minőségéről.\n\n## Hogyan működik?\n\nA YouTube API változása utáni dislike statisztikákat a backendünk részben korábban begyűjtött adatokból, részben pedig a bővítményt használók adataiból kinyert becsléssel építi fel.\n\n[GYIK](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Miért számít ez?\n\nTöbbet is megtudhatsz a weboldalunkon: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API dokumentáció\n\nEnnek az API-nak a third-party felhasználását az alábbiak mellett engedélyezzük:\n\n-   **Forrásmegjelölés**: Egy erre a projektre mutató, jól látható [returnyoutubedislike.com](https://returnyoutubedislike.com/) linkkel.\n-   **Rate Limiting**: Kliensoldalon gyakoriság-korlátozás van beállítva percenként 100, naponta maximum 10 000 lekérésre. Túllépés esetén _429_ státuszkód jelzi, hogy az alkalmazásod nem küldhet több lekérést.\n\nAz API ezen az URL-en érhető el:\nhttps://returnyoutubedislikeapi.com\n\nTovábbi elérhető endpointok itt találhatók:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Szavazatok lekérése\n\nPélda egy adott YouTube videó ID adatainak lekéréséhez:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n    \"id\": \"kxOuG8jMIgI\",\n    \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n    \"likes\": 27326,\n    \"dislikes\": 498153,\n    \"rating\": 1.212014408444885,\n    \"viewCount\": 3149885,\n    \"deleted\": false\n}\n```\n\nNem létező YouTube ID esetén _404_ \"Not Found\" státuszkód, rossz formátumú YouTube ID esetén _400_ \"Bad Request\" válasz érkezik.\n\n<!---\n## API dokumentáció\n\nAz összes dokumentáció elolvasható a weboldalunkon.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## Kontribúció\n\nKérjük, olvasd el a [kontribúciós leírást](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Támogasd a projektet!\n\nTámogathatod ezt a projektet adományokkal az alábbi linken:\n\n[Támogatás](https://returnyoutubedislike.com/donate)\n\n## Szponzorok\n\n\n\n[Legyél szponzorunk!](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEid.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\nBaca ini dibahasa lain: [русский](READMEru.md), [Español](READMEes.md), [Nederlands](READMEnl.md), [Français](READMEfr.md), [日本語](READMEja.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Deutsch](READMEde.md), [Ελληνικά](READMEgr.md), [Svenska](READMEsv.md), [中文](READMEcn.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [Magyar](READMEhu.md), [Danish](READMEda.md)\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return Youtube Dislike adalah extension open-source yang memunculkan jumlah dislike di Youtube.<br>\n    Juga ada pada browser lain sebagai JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Latar Belakang\n\nPada 10 November 2021, Google [memberitakan](https://blog.youtube/news-and-events/update-to-youtube/) bahwa jumlah dislike di Youtube akan dihapus.\n\nSerta, data `dislike` pada API Youtube juga [dihapus](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) pada 13 Desember 2021, sehingga menghilangkan kemampuan untuk menilai qualitas konten video sebelum ditonton.\n\n## Apa yang terjadi\n\nDengan dihapusnya data dislike dari API Youtube, kami mengubah backend kami sehingga menggunakan kombinasi scraping data dislike, dan estimasi perkiraan dari data pengguna.\n\n[Pertanyaan yang Sering Ditanyakan](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQid.md)\n\n## Mengapa ini Penting\n\nKamu bisa mempelajari lebih lanjut pada website kami di: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Dokumentasi API\n\nPenggunaan pada pihak ketiga terhadap API ini diizinkan dengan beberapa batasan berikut:\n\n- **Pereferensian**: Proyek ini harus jelas direferensikan menggunakan link ke [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Batas Penggunaan**: Terdapat batas penggunaan pada setiap client yaitu 100 per menit dan 10,000 per hari. Jika lebih dari ini, akan ada kode status _429_ yang menandakan kamu harus berhenti menggunakannya.\n\nAPInya bisa diakses melalui URL berikut:\nhttps://returnyoutubedislikeapi.com\n\nDaftar semua endpoint yang ada:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Voting\n\nContoh untuk melakukan voting terhadap suatu video Youtube menggunakan id:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nAkan muncul kode status _404_ \"Not Found\" jika Youtube id tidak ditemukan.\nAkan muncul kode status _400_ \"Bad Request\" jika Youtube id memiliki format yang salah.\n\n<!---\n## API documentation\n\nYou can view all documentation on our website.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## Kontribusi\n\nTolong baca [panduan kontribusi](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGid.md).\n\n## Dukung proyek ini!\n\nKamu dapat mendukung proyek ini dengan cara donasi melalui link dibawah ini:\n\n[Donasi](https://returnyoutubedislike.com/donate)\n\n## Sponsor\n\n[Menjadi sponsor kami](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEja.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n別の言語: [English](README.md), [русский](READMEru.md), [Español](READMEes.md), [Nederlands](READMEnl.md), [Français](READMEfr.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Deutsch](READMEde.md), [Ελληνικά](READMEgr.md), [Svenska](READMEsv.md), [中文](READMEcn.md), [Polski](READMEpl.md), [Danish](READMEda.md), [العربية](READMEar.md), [Bahasa Indonesia](READMEid.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube DislikeはYouTubeの低評価を表示するためのオープンソースな拡張機能です。</b><br>\n    ChromeとFireFoxで拡張機能を利用可能です。<br>\n    また、他のブラウザでもJS Userscriptを利用できます。<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## これまでの経緯\n\n2021 年 10 月 10 日、Google は YouTube の低評価を非表示にすると[発表しました](https://blog.youtube/news-and-events/update-to-youtube/)。\n\nそして 2021 年 12 月 13 日、YouTube API からも低評価が[削除](<(https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts)>)され、動画のクオリティを判断する手段がなくなってしまいました。\n\n## What it Does\n\n<!-- この部分の翻訳が微妙? -->\n\nYouTube API から低評価が削除されたことにより、バックエンドは拡張機能のユーザーをもとにスクレイピングされたデータと組み合わせて表示するように切り替わりました\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## 低評価が重要な理由\n\nウェブサイトに詳細が書かれています: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API ドキュメント\n\nサードパーティは以下の制限のもとで API を利用できます:\n\n- **帰属**: [returnyoutubedislike.com](https://returnyoutubedislike.com/) へのリンクを明確に表記してください。\n- **レート制限**: クライアントごとに 1 分あたり 100 回、1 日あたり 10000 回という制限があります。制限に達すると*429*コードが返されます。\n\nAPI は以下の Base URL でアクセスできます:\nhttps://returnyoutubedislikeapi.com\n\n利用可能なエンドポイントはこちらを参照してください:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### 評価を取得\n\nAPI を利用して YouTube video ID から評価を取得する例です:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\n存在しない YouTube ID の場合 _404_ \"Not Found\" が返されます。\n不正なフォーマットの YouTube ID の場合 _400_ \"Bad Request\"が返されます。\n\n<!---\n## API ドキュメント\n\nAPIの完全なドキュメントは公式サイトを参照してください。\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## 貢献する\n\nこちらの[contribution guide](CONTRIBUTING.md)を読んでください。\n\n## プロジェクトを支援!\n\n以下のリンクに寄付をしてこのプロジェクトを支援できます:\n\n[寄付](https://returnyoutubedislike.com/donate)\n\n## スポンサー\n\n\n\n[Become our sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEkr.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n다른 언어로 전환: [English](README.md), [русский](READMEru.md), [Español](READMEes.md), [Nederlands](READMEnl.md), [Français](READMEfr.md), [日本語](READMEja.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Deutsch](READMEde.md), [Ελληνικά](READMEgr.md), [Svenska](READMEsv.md), [中文](READMEcn.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [Magyar](READMEhu.md), [Danish](READMEda.md)\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike는 YouTube 싫어요 수를 반환하는 오픈소스 확장 프로그램입니다.</b><br>\n    Chrome 및 Firefox에서 웹 확장 프로그램으로 제공됩니다.<br>\n    JS Userscript로 다른 브라우저에서도 제공됩니다.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## 배경\n\n2021년 11월 10일, Google은 YouTube 싫어요 수를 [삭제한다고 발표](https://blog.youtube/news-and-events/update-to-youtube/)했습니다.\n\n또한, 2021년 12월 13일에 Youtube API 에서 `dislike` 필드가 [제거](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts)되어 영상을 시청하기 전에 콘텐츠의 품질을 판단할 수 있는 기능이 삭제되었습니다.\n## 무엇을 하나요?\n\nYouTube API에서 dislike 통계가 제거되면서 백엔드는 스크래핑된 싫어요 통계와 확장 프로그램 사용자 데이터에서 추정한 추정치를 조합하여 사용하도록 전환했습니다.\n\n[자주 묻는 질문](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## 왜 중요한가요?\n\n자세한 내용은 [returnyoutubedislike.com](https://www.returnyoutubedislike.com/) 에서 확인할 수 있습니다.\n\n## API 문서\n\n다음 제한 사항 하에 서드파티 앱에서 이 Open API를 사용하는 것을 허용합니다.\n\n- **출처**: 서드파티 앱은 [returnyoutubedislike.com](https://returnyoutubedislike.com/) 링크와 함께 명확히 출처를 표시해야 합니다. \n- **Rate Limiting**: 클라이언트당 분당 100개, 하루 당 10,000개의 요청 제한이 있습니다. 하루 할당량 초과 시, 서버에서 429 에러를 반환합니다.\n\n아래 Base URL 을 통해 API에 접근할 수 있습니다:  \nhttps://returnyoutubedislikeapi.com\n\n사용 가능한 엔드포인트 목록은 여기에서 확인할 수 있습니다:<br>\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Vote 가져오기\n\n아래 예시 처럼 YouTube video ID에서 Vote를 가져올 수 있습니다. <br>\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\n존재하지 않는 YouTube ID는 상태 코드 _404_ \"Not Found\" 를 반환합니다.<br>\n잘못 구성된 YouTube ID는 상태 코드  _400_ \"Bad Request\" 를 반환합니다.\n\n<!---\n## API documentation\n\nYou can view all documentation on our website.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## 프로젝트 기여 (Contributing)\n\n[기여 가이드](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md) 를 확인해보세요.\n\n## 프로젝트 후원\n\n아래 링크를 통해 이 프로젝트를 지원해주세요!:\n\n[후원하기](https://returnyoutubedislike.com/donate)\n\n## 스폰서\n\n\n[우리의 스폰서가 되어주세요](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEnl.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike is een open-source extensie die het aantal dislikes van YouTube retourneert.</b><br>\n    Beschikbaar voor Chrome en Firefox als webextensie.<br>\n    Ook beschikbaar voor andere browsers als JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Het Verhaal\n\nOp 10 november 2021 [kondigde](https://blog.youtube/news-and-events/update-to-youtube/) that the YouTube dislike count would be removed.\n\nAdditionally, the `dislike` field in the YouTube API was [removed](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) Google aan dat het aantal dislikes op YouTube zou worden verwijderd.\n\n## Wat het doet\n\nMet de verwijdering van afkeerstatistieken uit de YouTube API, schakelde onze backend over op het gebruik van een combinatie van geschraapte afkeerstatistieken, schattingen geëxtrapoleerd uit gebruikersgegevens van extensies.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Waarom het uitmaakt\n\nU kunt meer informatie vinden op onze website op: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API-documentatie\n\nGebruik door derden van deze open API is toegestaan ​​met de volgende beperkingen:\n\n- **Naamsvermelding**: dit project moet duidelijk worden toegeschreven met een link naar [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Snelheidsbeperking**: Er zijn tarieflimieten per klant in plaats van 100 per minuut en 10.000 per dag. Hiermee wordt een statuscode _429_ geretourneerd die aangeeft dat uw toepassing moet worden uitgeschakeld.\n\nDe API is toegankelijk via de volgende basis-URL:\nhttps://returnyoutubedislikeapi.com\n\nLijst met beschikbare eindpunten is hier beschikbaar:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Stemmen krijgen\n\nVoorbeeld om stemmen te krijgen voor een bepaalde YouTube-video-ID:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nGeen bestaande YouTube-ID retourneert statuscode _404_ \"Niet gevonden\".\nVerkeerd gevormde YouTube-ID retourneert _400_ \"Slecht verzoek\".\n\n<!---\n## API documentation\n\nYou can view all documentation on our website.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Bijdragen\n\nLees de [bijdragengids](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Steun dit project!\n\nU kunt dit project steunen door aan ons te doneren via onderstaande link:\n\n[Doneer](https://returnyoutubedislike.com/donate)\n\n## Sponsoren\n\n\n\n[Wordt een sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEpl.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike to otwarte rozszerzenie, które przywraca licznik łapek w dół na YouTube.</b><br>\n    Dostępne jako rozszerzenie dla Chrome i Firefox.<br>\n    Dostępne także dla innych przeglądarek jako JS UserScript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Historia\n\nDnia 10 listopada 2021, Google [ogłosiło](https://blog.youtube/news-and-events/update-to-youtube/), że licznik łapek w dół na YouTube zostanie usunięty.\n\nDodatkowo, pole `dislike` w API YouTube zostało [usunięte](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) 13 grudnia 2021, usuwając przy tym jakąkolwiek możliwość oceny jakości filmu przed obejrzeniem.\n\n## Co to robi\n\nWraz z usunięciem statystyk łapek w dół z YouTube API, nasz backend przełączył się na kombinację scrape-owanych statystyk łapek w dół i szacunków ekstrapolowanych z danych użytkowników rozszerzenia.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Dlaczego to ma znaczenie\n\nMożna o tym przeczytać na naszej stronie: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Dokumentacja API\n\nUżywanie tego otwartego API jest dozwolone z następującymi ograniczeniami:\n\n- **Przypisanie**: Ten projekt powinien być jawnie przypisany z linkiem do [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Ograniczenie żądań**: Istnieją ograniczenia żądań do 100 na minutę i 10 000 na dzień. Przekroczenie zwróci kod _429_, mówiący aplikacji aby przyhamowała.\n\nAPI jest dostępne przez poniższe bazowe URL:\nhttps://returnyoutubedislikeapi.com\n\nLista dostępnych endpointów jest dostępna tutaj:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Pobierz głosy\n\nPrzykład otrzymywania głosów z danego ID filmu YouTube:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nNieistniejący ID filmu zwróci _404_ \"Not Found\".\nNiepoprawnie sformatowany ID filmu zwróci _400_ \"Bad Request\".\n\n<!---\n## API documentation\n\nCała dokumentacja jest dostępna na naszej stronie.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Współtworzenie\n\nProsimy przeczytać [przewodnik współtworzenia](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Wesprzyj ten projekt!\n\nMożesz wesprzeć ten projekt dotacjami poniżej:\n\n[Wesprzyj](https://returnyoutubedislike.com/donate)\n\n## Sponsorzy\n\n\n\n[Become our sponsor](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEpt_BR.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\nLeia isso em outros idiomas: [русский](READMEru.md), [Español](READMEes.md), [Nederlands](READMEnl.md), [Français](READMEfr.md), [日本語](READMEja.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Deutsch](READMEde.md), [Ελληνικά](READMEgr.md), [Svenska](READMEsv.md), [中文](READMEcn.md), [Polski](READMEpl.md), [Danish](READMEda.md), [العربية](READMEar.md) ou [English (Para Melhor precisão!)](README.md), [Bahasa Indonesia](READMEid.md)\n\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike é uma extensão de código aberto que traz de volta a contagem de dislikes do YouTube.</b><br>\n    Disponível para Chrome e Firefox como uma Extensão Web.<br>\n    Também disponível para outros navegadores como um JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## História\n\nEm 10 de novembro de 2021 o Google [anunciou](https://blog.youtube/news-and-events/update-to-youtube/) que o contador de Deslikes do Youtube seria removido.\n\nAdicionalmente, o campo `dislike` na API do YouTube foi [removido](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) em 13 de dezembro de 2021, removendo qualquer capacidade de julgar a qualidade do conteúdo antes de assistir.\n\n## Como funciona\n\nCom a remoção das estatísticas de dislike da API do YouTube, nosso back-end passou a usar uma combinação de estatísticas de dislike coletadas via scraping e estimativas baseadas nos dados dos usuários da extensão.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Por que Isso Importa\n\nVocê pode saber mais em nosso site: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Documentação da API\n\nO uso por terceiros desta API aberta é permitido com as seguintes restrições:\n\n- **Atribuição**: Este projeto deve ser claramente atribuído com um link para [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Limite de Taxa**: Existem limites por cliente de 100 requisições por minuto e 10.000 por dia. Se esses limites forem atingidos, será retornado um código de status 429, indicando que sua aplicação deve reduzir o volume de requisições.\n\nA API é acessível pela seguinte URL base:\nhttps://returnyoutubedislikeapi.com\n\nA lista de endpoints está disponível aqui:  \nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Obter dados de likes e dislikes\n\nExemplo de como obter os dados de likes e dislikes com base no ID do vídeo do YouTube:\n\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\nUm ID de YouTube inexistente retornará o código de status _404_ \"Não Encontrado\".  \nUm ID de YouTube malformado retornará o código _400_ \"Requisição Inválida\".\n\n<!---\n## API documentation\n\nYou can view all documentation on our website.\n[https://returnyoutubedislike.com/documentation/](https://returnyoutubedislike.com/documentation/) -->\n\n## Contribuindo\n\nPor favor leia o [guia de contribuição](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGpt_BR.md).\n\n## Ajude nosso projeto!\n\nVocê pode ajudar esse projeto doando para nós no link abaixo:\n\n[Doar](https://returnyoutubedislike.com/donate)\n\n## Patrocinadores\n\n[Torne-se nosso Patrocinador](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEru.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike — это расширение с открытым исходным кодом, которое возвращает счётчик отметок «Не нравится» на YouTube.</b><br>\n    Доступно для Chrome и Firefox в качестве веб-расширения.<br>\n    Также доступен для других браузеров в виде пользовательского скрипта.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## История\n\n10 ноября 2021 года Google [объявили](https://blog.youtube/news-and-events/update-to-youtube/), что счётчик «Не нравится» на YouTube будет удален.\n\nКроме того, поле отметок `dislike` в API YouTube было [удалено](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) 13 декабря 2021 года, убрав любую возможность судить о качестве контента перед просмотром.\n\n## Как оно работает\n\nС удалением статистики отметок из API YouTube наш сервер переключился на использование комбинации собранной статистики отметок «Не нравится» и оценок, экстраполированных из пользовательских данных расширения.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQru.md)\n\n## Почему это важно\n\nВы можете узнать больше на нашем веб-сайте: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Документация по API\n\nСтороннее использование этого открытого API разрешено со следующими ограничениями:\n\n- **Атрибуция**: Этот проект должен быть чётко описан со ссылкой на [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Ограничение**: Существуют ограничения скорости для каждого клиента — 100 в минуту и 10 000 в день. Это выдаст код ошибки _429_, указывающий на то, что ваше приложение должно быть отключено.\n\nAPI доступен по следующему основному URL-адресу:\nhttps://returnyoutubedislikeapi.com\n\nСписок доступных эндпоинтов доступен здесь:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Получить голоса\n\nПример получения голосов для заданного идентификатора видео на YouTube:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nНеверный идентификатор YouTube выдаст код ошибки _404_ «Не найдено».\nНеправильно отформатированный идентификатор YouTube выдаст код ошибки _400_ «Неверный запрос».\n\n<!---\n## Документация по API\n\nВы можете просмотреть всю документацию на нашем веб-сайте.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Участие/помощь в разработке\n\nПожалуйста, ознакомьтесь с [руководством по внесению вклада в проект](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGru.md).\n\n## Поддержите этот проект!\n\nВы можете поддержать этот проект, сделав нам пожертвование по ссылке ниже:\n\n[Пожертвовать](https://returnyoutubedislike.com/donate)\n\n## Спонсоры\n\n\n\n[Станьте нашим спонсором](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEsv.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike (Återinför YouTube Dislikes) är ett open-source tillägg som återinför antalet dislikes (nedåttummar) på YouTube.</b><br>\n    Tillgängligt som tillägg för <a href=\"https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi\">Chromium Browser</a> och <a href=\"https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/\">Firefox</a>.<br>\n    Finns även för andra webbläsare som JS Userscript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Berättelsen <!-- The Story -->\n\nDen 10 november 2021 [meddelade Google](https://blog.youtube/news-and-events/update-to-youtube/) att YouTubes dislikes skulle tas bort från plattformen.\n\nDessutom blev `dislike`-fältet i YouTubes API [borttaget](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) den 13 december 2021, vilket tog bort möjligheten att bedöma innehållets kvalitet innan visning.\n\n## Vad det gör <!-- What it Does -->\n\nMed borttagandet av dislike-statistiken från YouTubes API, bytte vår backend till att använda en kombination av skrapad dislike-statistik och uppskattningar utifrån data av tilläggsanvändare.\n\n[FAQ](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQ.md)\n\n## Varför det är viktigt <!-- Why it Matters -->\n\nDu kan läsa mer på vår webbplats: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API dokumentation <!-- API documentation -->\n\nTredjepartsanvändning av detta öppna API är tillåtet med följande restriktioner:\n\n- **Attribution**: Detta projekt ska tydligt tillskrivas med en länk till [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Frekvensbegränsning**: Det finns per klient hastighetsbegränsningar på 100 per minut och 10'000 per dag. Detta kommer att returnera en _429_ statuskod som indikerar att din applikation bör backa.\n\nAPI:et är tillgängligt på följande bas-URL:\nhttps://returnyoutubedislikeapi.com\n\nEn lista över tillgängliga ändpunkter finns här:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Hämta röster <!-- Get votes -->\n\nExempel för att hämta röster för en given YouTube-video-ID:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nInga existerande YouTube-ID returnerar statuskoden _404_ \"Not Found\".\nFelaktigt formade YouTube-ID returnerar _400_ \"Bad Request\".\n\n<!---\n## API Dokumentation\n\nDu kommer åt all dokumentation på vår webbplats.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Bidra <!-- Contributing -->\n\nLäs gärna [bidragsguiden](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTING.md).\n\n## Stöd detta projekt! <!-- Support this project! -->\n\nDu kan stödja detta projekt genom att donera till oss på länken nedan:\n\n[Donera](https://returnyoutubedislike.com/donate)\n\n## Sponsorer <!-- Sponsors -->\n\n\n\n[Stöd oss på Patreon](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEtr.md",
    "content": "[![Chrome Web Mağazası](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Mağazası Kullanıcıları](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla oylaması](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla indirmeleri](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit sayısı](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issue'lar](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![Lisans](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\nBunu diğer dillerde okuyun: [English](README.md), [русский](READMEru.md), [Español](READMEes.md), [Français](READMEfr.md), [Nederlands](READMEnl.md), [日本語](READMEja.md), [українська](READMEuk.md), [Deutsch](READMEde.md), [Ελληνικά](READMEgr.md), [Svenska](READMEsv.md), [中文](READMEcn.md), [Polski](READMEpl.md), [Magyar](READMEhu.md), [Danish](READMEda.md), [العربية](READMEar.md), [Bahasa Indonesia](READMEid.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# YouTube Dislike Sayısını Geri Getir\n\n<p align=\"center\">\n    <b>YouTube Dislike Sayısını Geri Getir, YouTube'daki dislike sayısını gösteren açık-kaynaklı bir uzantıdır.</b><br>\n    Chrome ve Firefox'da bir Web Uzantısı olarak mevcuttur.<br>\n    Ayrıca diğer tarayıcılar için JS Userscript olarak da mevcuttur.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Hikâyesi\n\n10 Kasım 2021 tarihinde Google, YouTube dislike sayısının kaldırılacağını [duyurdu](https://blog.youtube/news-and-events/update-to-youtube/).\n\nEk olarak, YouTube API'sindeki `dislike` alanı 13 Aralık 2021 tarihinde [kaldırıldı](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) ve içeriğin kalitesini izlemeden önce yargılayabilme olanağı ortadan kaldırıldı.\n\n## Ne İşe Yarar\n\nYouTube API'sinden dislike istatistiklerinin kaldırılmasıyla, backend'imiz, uzantı kullanıcı verilerinden tahmin edilen, toplanmış dislike istatistiklerinin bir birleşimini kullanmaya başladı.\n\n[SSS](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQtr.md)\n\n## Neden Önemlidir\n\nSitemizden daha fazla bilgi edinebilirsiniz: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## API Belgelemesi\n\nBu açık API'nin üçüncü taraflarca kullanımına, aşağıdaki kısıtlamalarla izin verilir:\n\n- **Atfetme**: Bu proje, açık bir biçimde [returnyoutubedislike.com](https://returnyoutubedislike.com/) adresine yönlendirilmelidir.\n- **Hız Sınırlaması**: Kullanıcı başına dakikada 100 ve günde 10.000 hız sınırlaması vardır. Bu, uygulamanızın geri çekilmesi gerektiğini belirten _429_ durum kodunu döndürür.\n\nAPI'ye aşağıdaki temel URL üzerinden erişilebilir:\nhttps://returnyoutubedislikeapi.com\n\nKullanılabilir endpoint'lerin bir listesi burada mevcuttur:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Oylamaları Elde Etme\n\nBelirli bir YouTube video ID'sinin oylamalarını elde etmek için bir örnek:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nHiçbir mevcut YouTube ID'si _404_ \"Not Found\" durum kodunu döndürmez.\nYanlış oluşturulmuş bir YouTube ID'si _400_ \"Bad Request\" durum kodunu döndürür.\n\n<!---\n## API Belgelemesi\n\nTüm belgelemeleri sitemizden inceleyebilirsiniz.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Katkıda Bulunma\n\nLütfen [katkı kılavuzu](CONTRIBUTINGtr.md) nu okuyun.\n\n## Bu Projeyi Destekle!\n\nAşağıdaki bağlantıdan bize bağış yapabilir ve bu projeye destek olabilirsiniz:\n\n[Bağış Yap](https://returnyoutubedislike.com/donate)\n\n## Sponsorlar\n\n\n\n[Sponsorumuz olun](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEuk.md",
    "content": "[![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Rating&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Chrome%20Users&style=flat&logo=google)](https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi)\n[![Mozilla rating](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Firefox%20Rating&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Mozilla downloads](https://img.shields.io/amo/users/return-youtube-dislikes?label=Firefox%20Users&style=flat&logo=firefox)](https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![License](https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n# Return YouTube Dislike\n\n<p align=\"center\">\n    <b>Return YouTube Dislike - це розширення з відкритим вихідним кодом, яке повертає лічильник відміток «Не подобається» на YouTube.</b><br>\n    Доступно для Chrome та Firefox як веброзширення.<br>\n    Також доступно для інших браузерів як JS UserScript.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Історія\n\n10 листопада 2021 року Google [оголосили](https://blog.youtube/news-and-events/update-to-youtube/), що лічильник відміток «Не подобається» на YouTube буде видалено.\n\nКрім того, поле позначок `dislike` у YouTube API було [видалено](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) 13 грудня 2021 року, прибравши єдину можливість оцінити якість вмісту перед переглядом.\n\n## Як це працює\n\nПісля видаленням статистики відміток з API YouTube наш сервер перейшов на використання комбінації заархівованих статистичних даних відміток «Не подобається» екстрапольованих із даними користувачів розширення.\n\n[ЧаПи](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQuk.md)\n\n## Чому це важливо\n\nВи можете дізнатися більше на нашому вебсайті: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Документація API\n\nВикористання цього відкритого API сторонніми особами дозволено з наступними обмеженнями:\n\n- **Атрибуція**: Цей проєкт має бути чітко описано, використовуючи посилання на [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Обмеження**: Існують обмеження на швидкісті для кожного клієнта - 100 за хвилину і 10 000 за день. Це видасть код помилки 429, який вказує на те, що вашому додатку слід завершити роботу.\n\nAPI доступно за наступною URL-адресою:\nhttps://returnyoutubedislikeapi.com\n\nПерелік доступних «ендпоінтів» можна переглянути тут:\nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Отримати оцінки\n\nПриклад отримання оцінок відео на YouTube за ID:\n`/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nНедійсний YouTube ID видасть код помилки 404 \"Not Found\".\nYouTube ID у невірному форматі видасть код помилки 400 \"Bad Request\".\n\n<!---\n## Документація API\n\nВи можете переглянути всю документацію на нашому сайті.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Взяти участь у розробці\n\nБудь ласка, ознайомтеся із [посібником внеску в проєкт](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGuk.md).\n\n## Підтримайте цей проєкт!\n\nВи можете підтримати цей проєкт пожертвою за посиланням нижче:\n\n[Підтримати](https://returnyoutubedislike.com/donate)\n\n## Спонсори\n\n\n\n[Станьте нашим спонсором](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "READMEvi.md",
    "content": "[![Cửa hàng Chrome Trực tuyến](https://img.shields.io/chrome-web-store/stars/gebbhagfogifgggkldgodflihgfeippi?label=Đánh%20giá%20Chrome&style=flat&logo=google)](https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/?hl=vi)\n[![Người dùng trên Cửa hàng Chrome Trực tuyến](https://img.shields.io/chrome-web-store/users/gebbhagfogifgggkldgodflihgfeippi?label=Người%20dùng%20Chrome&style=flat&logo=google)](https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/?hl=vi)\n[![Đánh giá trên Mozilla](https://img.shields.io/amo/stars/return-youtube-dislikes?label=Đánh%20giá%20Firefox&style=flat&logo=firefox)](https://addons.mozilla.org/vi/firefox/addon/return-youtube-dislikes/)\n[![Lượt tải trên Mozilla](https://img.shields.io/amo/users/return-youtube-dislikes?label=Người%20dùng%20Firefox&style=flat&logo=firefox)](https://addons.mozilla.org/vi/firefox/addon/return-youtube-dislikes/)\n[![Commit rate](https://img.shields.io/github/commit-activity/m/Anarios/return-youtube-dislike?label=Commits&style=flat)](https://github.com/Anarios/return-youtube-dislike/commits/main)\n[![Issues](https://img.shields.io/github/issues/Anarios/return-youtube-dislike?style=flat&label=Issues)](https://github.com/Anarios/return-youtube-dislike/issues)\n[![Discord](https://img.shields.io/discord/909435648170160229?label=Discord&style=flat&logo=discord)](https://discord.gg/UMxyMmCgfF)\n[![Giấy phép](https://img.shields.io/badge/License-GPLv3-blue.svg?label=Giấy%20phép&style=flat)](https://github.com/Anarios/return-youtube-dislike/blob/main/LICENSE)\n\n\nRead this in other languages: [English](README.md), [العربية](READMEar.md), [Azərbaycan dili](READMEaz.md), [български](READMEbg.md), [中文](READMEcn.md), [Danish](READMEda.md), [Deutsch](READMEde.md), [Español](READMEes.md), [Français](READMEfr.md), [Ελληνικά](READMEgr.md), [Magyar](READMEhu.md), [Bahasa Indonesia](READMEid.md), [日本語](READMEja.md), [한국어](READMEkr.md), [Nederlands](READMEnl.md), [Polski](READMEpl.md), [Português do Brasil](READMEpt_BR.md), [русский](READMEru.md), [Svenska](READMEsv.md), [Türkçe](READMEtr.md), [українська](READMEuk.md), [Tiếng Việt](READMEvi.md)\n\n\n\n# Return YouTube Dislike (Trả lại số lượt Không thích trên YouTube)\n\n<p align=\"center\">\n    <b>Return YouTube Dislike (Trả lại số lượt Không thích trên YouTube) là một tiện ích mở rộng mã nguồn mở nhằm phục hồi số lượt \"không thích\" trên YouTube.</b><br>\n    Tiện ích mở rộng dành cho <a href=\"https://chrome.google.com/webstore/detail/youtube-dislike-button/gebbhagfogifgggkldgodflihgfeippi/?hl=vi\">Chrome</a> và <a href=\"https://addons.mozilla.org/vi/firefox/addon/return-youtube-dislikes/\">Firefox</a>.<br>\n    Cũng có thể dùng như một JS Userscript (Tập lệnh người dùng JS) trong các trình duyệt khác.<br><br>\n    <img width=\"400px\" src=\"https://user-images.githubusercontent.com/18729296/141743755-2be73297-250e-4cd1-ac93-8978c5a39d10.png\"/>\n</p>\n\n## Căn nguyên <!-- ## The Story -->\n\nVào ngày 10 tháng 11 năm 2021, Google [thông báo](https://blog.youtube/news-and-events/update-to-youtube/) về việc loại bỏ số lượt \"không thích\" trên YouTube.\n\nThêm vào đó, vào ngày 13 tháng 12 năm 2021, Google [loại bỏ](https://support.google.com/youtube/thread/134791097/update-to-youtube-dislike-counts) trường `dislike` trong API của YouTube, do đó tước mất khả năng đánh giá chất lượng nội dung vi-đê-ô trước khi xem của người dùng.\n\n## Cách thức hoạt động <!-- ## What it Does -->\n\nVới việc số liệu thống kê \"không thích\" bị loại bỏ khỏi API của YouTube, đầu sau của chúng tôi chuyển sang sử dụng kết hợp số liệu \"không thích\" thu thập được và ước tính ngoại suy từ dữ liệu người dùng của tiện ích.\n\n[Câu Hỏi Thường Gặp](https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/FAQvn.md)\n\n## Tại sao số lượt Không thích lại quan trọng <!-- ## Why it Matters -->\n\nBạn có thể tìm hiểu thêm tại trang mạng: [returnyoutubedislike.com](https://www.returnyoutubedislike.com/)\n\n## Tài liệu API <!-- ## API documentation -->\n\nBên thứ ba được phép sử dụng API mở này, với các hạn chế sau:\n\n- **Ghi công**: Phải ghi công dự án này rõ ràng bằng đường dẫn [returnyoutubedislike.com](https://returnyoutubedislike.com/).\n- **Giới hạn Truy vấn**: Giới hạn truy vấn cho mỗi khách là 100 lần mỗi phút và 10.000 lần mỗi ngày. Mã trạng thái _429_ \"Quá Nhiều Yêu cầu\" sẽ được trả về khi khách đạt tới giới hạn trên.\n\nAPI có thể được truy cập với đường dẫn cơ sở sau:  \nhttps://returnyoutubedislikeapi.com\n\nDanh sách các hậu tố được liệt kê ở đây:  \nhttps://returnyoutubedislikeapi.com/swagger/index.html\n\n### Truy vấn đánh giá <!-- ### Get votes -->\n\nVí dụ cách truy vấn đánh giá với một ID của một vi-đê-ô trên YouTube:  \n`<Đường dẫn cơ sở>/votes?videoId=kxOuG8jMIgI`\n\n```json\n{\n  \"id\": \"kxOuG8jMIgI\",\n  \"dateCreated\": \"2021-12-20T12:25:54.418014Z\",\n  \"likes\": 27326,\n  \"dislikes\": 498153,\n  \"rating\": 1.212014408444885,\n  \"viewCount\": 3149885,\n  \"deleted\": false\n}\n```\n\nTrong trường hợp ID không tồn tại, mã trạng thái _404_ \"Không Tìm thấy\" sẽ được trả về.  \nTrong trường hợp ID có định dạng không hợp lệ, mã trạng thái _400_ \"Yêu cầu Không hợp lệ\" sẽ được trả về.\n\n<!---\n## Tài liệu API\n\nBạn có thể xem tất cả tài liệu trên trang của chúng tôi.\n[https://returnyoutubedislike.com/docs/](https://returnyoutubedislike.com/docs/) -->\n\n## Đóng góp <!-- ## Contributing -->\n\nVui lòng đọc [hướng dẫn đóng góp](https://github.com/Anarios/return-youtube-dislike/blob/main/CONTRIBUTINGvn.md).\n\n## Hỗ trợ dự án! <!-- ## Support this project! -->\n\nBạn có thể hỗ trợ dự án này bằng cách quyên góp cho chúng tôi theo đường dẫn bên dưới:\n\n[Quyên góp](https://returnyoutubedislike.com/donate)\n\n## Tài trợ <!-- ## Sponsors -->\n\n\n\n[Trở thành nhà tài trợ](https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601)\n"
  },
  {
    "path": "Website/.eslintignore",
    "content": "# js vendor file with import/require\nassets/vendor/**\n# static vendor file . use with nuxt.config.js script\nstatic/**\n# dependencies\nnode_modules\n# Nuxt build\n.nuxt\n# Nuxt generate\ndist\n"
  },
  {
    "path": "Website/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true,\n    browser: true,\n  },\n  parserOptions: {\n    parser: \"babel-eslint\",\n  },\n  extends: [\n    \"prettier\",\n    \"eslint:recommended\",\n    \"plugin:vue/recommended\",\n    \"plugin:prettier/recommended\",\n  ],\n  plugins: [\"vue\"],\n  rules: {\n    \"vue/multi-word-component-names\": 0,\n    \"no-console\": process.env.NODE_ENV === \"production\" ? \"warn\" : \"off\",\n    \"no-debugger\": process.env.NODE_ENV === \"production\" ? \"warn\" : \"off\",\n    // 'prettier/prettier': ['error', { semi: false }],\n    // semi: [2, 'never'],\n  },\n};\n"
  },
  {
    "path": "Website/README.md",
    "content": "Read this in other languages: [Nederlands](READMEnl.md), [Türkçe](READMEtr.md), [Deutsch](READMEde.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# return-youtube-dislike-site\n\n## Build Setup\n\n```bash\n# install dependencies\n$ npm install\n\n# serve with hot reload at localhost:3000\n$ npm run dev\n\n# lint your changes\n$ npm run lint\n\n# build for production and launch server\n$ npm run build\n$ npm run start\n\n# generate static project\n$ npm run generate\n```\n\nFor detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).\n\n## Recommended VSCode Setup\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Ctrl(Cmd)` + `Shift` + `P` > Open Settings (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Special Directories\n\nYou can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.\n\n### `assets`\n\nThe assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).\n\n### `components`\n\nThe components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components).\n\n### `layouts`\n\nLayouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).\n\n### `pages`\n\nThis directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing).\n\n### `plugins`\n\nThe plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).\n\n### `static`\n\nThis directory contains your static files. Each file inside this directory is mapped to `/`.\n\nExample: `/static/robots.txt` is mapped as `/robots.txt`.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static).\n\n### `store`\n\nThis directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store).\n"
  },
  {
    "path": "Website/READMEaz.md",
    "content": "Digər dillərdə oxuyun: [Nederlands](READMEnl.md), [Türkçe](READMEtr.md), [Deutsch](READMEde.md)\n\n# return-youtube-dislike-site\n\n## Layihənin Qurulması\n\n```bash\n# asılılıqları yüklə\n$ npm install\n\n# localhost:3000 ünvanında hot reload ilə işlət\n$ npm run dev\n\n# dəyişikliklərinizi lint ilə yoxlayın\n$ npm run lint\n\n# istehsal üçün tərtib et və serveri başlat\n$ npm run build\n$ npm run start\n\n# statik layihə yaradın\n$ npm run generate\n```\n\nƏtraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org) baxın.\n\n## Tövsiyə Edilən VSCode Quraşdırması\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Ctrl(Cmd)` + `Shift` + `P` > Ayarları Aç (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Xüsusi Qovluqlar\n\nAşağıdakı xüsusi qovluqları yarada bilərsiniz, bəzilərinin xüsusi davranışları var. Yalnız `pages` qovluğu məcburidir; digər funksiyalardan istifadə etmək istəmirsinizsə, onları silə bilərsiniz.\n\n### `assets`\n\n`assets` qovluğu kompilyasiya olunmamış fayllarınızı (Stylus və ya Sass faylları, şəkillər və şriftlər) saxlayır.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/directory-structure/assets) baxın.\n\n### `components`\n\n`components` qovluğu Vue.js komponentlərinizi saxlayır. Komponentlər səhifənizin müxtəlif hissələrini təşkil edir və səhifələrinizə, tərtibatlarınıza və digər komponentlərinizə təkrar istifadə edilə bilən şəkildə əlavə edilə bilər.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/directory-structure/components) baxın.\n\n### `layouts`\n\nLayoutlar, Nuxt tətbiqinizin görünüşünü və hissini dəyişdirmək istədiyiniz zaman kömək edir. Məsələn, bir yan panel əlavə etmək və ya mobil və masaüstü üçün fərqli layoutlar istifadə etmək kimi.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/directory-structure/layouts) baxın.\n\n### `pages`\n\nBu qovluq tətbiqinizin görünüşlərini və marşrutlarını ehtiva edir. Nuxt, bu qovluqdakı bütün `*.vue` fayllarını oxuyaraq Vue Router-i avtomatik olaraq qurur.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/get-started/routing) baxın.\n\n### `plugins`\n\n`plugins` qovluğu əsas Vue.js tətbiqi başladılmadan əvvəl işlətmək istədiyiniz JavaScript plaginlərini ehtiva edir. Vue plaginlərini əlavə etmək və funksiyaları və ya sabitləri daxil etmək üçün istifadə olunur. `Vue.use()` istifadə etmək lazım gəldikdə bu qovluqda fayl yaratmalı və yolunu `nuxt.config.js`-dəki plaginlərə əlavə etməlisiniz.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/directory-structure/plugins) baxın.\n\n### `static`\n\nBu qovluq statik fayllarınızı saxlayır. Bu qovluqdakı hər bir fayl `/` ünvanına xəritələnir.\n\nMəsələn: `/static/robots.txt` faylı `/robots.txt` kimi xəritələnir.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/directory-structure/static) baxın.\n\n### `store`\n\nBu qovluq Vuex store fayllarınızı ehtiva edir. Bu qovluqda fayl yaratmaq Vuex-i avtomatik olaraq aktivləşdirir.\n\nBu qovluğun istifadəsi haqqında daha ətraflı məlumat üçün [sənədləşməyə](https://nuxtjs.org/docs/2.x/directory-structure/store) baxın."
  },
  {
    "path": "Website/READMEbg.md",
    "content": "Прочетете това на други езици: [English](README.md), [Nederlands](READMEnl.md), [Türkçe](READMEtr.md)\n\n# return-youtube-dislike-site\n\n## Настройки за изграждане\n\n```bash\n# инсталирайте зависимостите (dependencies)\n$ npm install\n\n# sстартирайте с горещо презареждане (hot reload) на localhost:3000\n$ npm run dev\n\n# проверете вашите промени\n$ npm run lint\n\n# изградете за продукция и стартирайте сървъра\n$ npm run build\n$ npm run start\n\n# генерирайте статичен проект\n$ npm run generate\n```\n\nЗа подробно обяснение на как работят нещата, разгледайте [документацията](https://nuxtjs.org).\n\n## Препоръчителна настройка в VSCode\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Ctrl(Cmd)` + `Shift` + `P` > Open Settings (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Специални директории\n\nМожете да създадете следните допълнителни директории, част от които имат специално поведение. Само `pages` се изисква; можете да ги изтриете, ако не искате да използвате тяхната функционалност.\n\n### `assets`\n\nДиректорията за активи съдържа некомпилираните ви активи като файлове Stylus или Sass, изображения или шрифтове.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/directory-structure/assets).\n\n### `components`\n\nДиректорията за компоненти съдържа вашите компоненти на Vue.js. Компонентите образуват различните части на страницата ви и могат да бъдат преизползвани и включвани във вашите страници, оформления и дори други компоненти.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/directory-structure/components).\n\n### `layouts`\n\nОформленията са отлична помощ, когато искате да промените външния вид на вашия Nuxt ап, независимо дали искате да включите страничен бар или да имате различни оформления за мобилни и настолни компютри.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/directory-structure/layouts).\n\n### `pages`\n\nТази директория съдържа вашите изгледи и маршрути на приложението ви. Nuxt ще прочете всички файлове `*.vue` в тази директория и автоматично ще настрои Vue Router.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/get-started/routing).\n\n### `plugins`\n\nДиректорията с плъгини съдържа JavaScript плъгини, които искате да изпълните преди да инстанциирате основното приложение на Vue.js. Това е мястото, където да добавяте Vue плъгини и да внедрявате функции или константи. Всеки път, когато имате нужда от `Vue.use()`, трябва да създадете файл в `plugins/` и да добавите неговия път в плъгините в `nuxt.config.js`.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/directory-structure/plugins).\n\n### `static`\n\nТази директория съдържа статичните ви файлове. Всеки файл в тази директория е съпоставен с `/`.\n\nПример: `/static/robots.txt` е съпоставен като `/robots.txt`.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/directory-structure/static).\n\n### `store`\n\nТази директория съдържа файловете на вашето сървис за управление на състоянието Vuex. Създаването на файл в тази директория активира автоматично Vuex.\n\nПовече информация за използването на тази директория може да намерите в [документацията](https://nuxtjs.org/docs/2.x/directory-structure/store).\n"
  },
  {
    "path": "Website/READMEde.md",
    "content": "Read this in other languages: [English](README.md), [Nederlands](READMEnl.md), [Türkçe](READMEtr.md)\n\n# return-youtube-dislike-site\n\n## Build Setup\n\n```bash\n# Abhängigkeiten installieren\n$ npm install\n\n# Mit Hot Reload unter localhost:3000 bereitstellen\n$ npm run dev\n\n# Linten Ihrer Änderungen\n$ npm run lint\n\n# Für die Produktion erstellen und Server starten\n$ npm run build\n$ npm run start\n\n# Statisches Projekt generieren\n$ npm run generate\n```\n\nFür eine ausführliche Erklärung, wie die Dinge funktionieren, lesen Sie die [Dokumentation](https://nuxtjs.org).\n\n## Empfohlene VSCode-Einrichtung\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Strg(Cmd)` + `Shift` + `P` > Einstellungen öffnen (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Besondere Verzeichnisse\n\nSie können die folgenden zusätzlichen Verzeichnisse erstellen, von denen einige spezielle Verhaltensweisen haben. Nur `pages` ist erforderlich; Sie können sie löschen, wenn Sie ihre Funktionalität nicht verwenden möchten.\n\n### `assets`\n\nDas Verzeichnis assets enthält Ihre nicht kompilierten Assets wie Stylus- oder Sass-Dateien, Bilder oder Schriftarten.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).\n\n### `components`\n\nDas Verzeichnis components enthält Ihre Vue.js-Komponenten. Komponenten bilden die verschiedenen Teile Ihrer Seite und können in Ihren Seiten, Layouts und sogar anderen Komponenten wiederverwendet und importiert werden.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/directory-structure/components).\n\n### `layouts`\n\nLayouts sind eine große Hilfe, wenn Sie das Aussehen und Verhalten Ihrer Nuxt-App ändern möchten, ob Sie eine Seitenleiste einbeziehen oder unterschiedliche Layouts für mobile und Desktop haben möchten.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).\n\n### `pages`\n\nDieses Verzeichnis enthält Ihre Anwendungsansichten und Routen. Nuxt liest alle `*.vue`-Dateien in diesem Verzeichnis und richtet automatisch Vue Router ein.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/get-started/routing).\n\n### `plugins`\n\nDas Verzeichnis plugins enthält JavaScript-Plugins, die Sie ausführen möchten, bevor Sie die Root-Vue.js-Anwendung instanziieren. Hier fügen Sie Vue-Plugins hinzu und injizieren Funktionen oder Konstanten. Jedes Mal, wenn Sie `Vue.use()` verwenden möchten, sollten Sie eine Datei in `plugins/` erstellen und ihren Pfad zu Plugins in `nuxt.config.js` hinzufügen.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).\n\n### `static`\n\nDieses Verzeichnis enthält Ihre statischen Dateien. Jede Datei in diesem Verzeichnis ist auf `/` abgebildet.\n\nBeispiel: `/static/robots.txt` ist als `/robots.txt` abgebildet.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/directory-structure/static).\n\n### `store`\n\nDieses Verzeichnis enthält Ihre Vuex-Store-Dateien. Das Erstellen einer Datei in diesem Verzeichnis aktiviert automatisch Vuex.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/docs/2.x/directory-structure/store).\n"
  },
  {
    "path": "Website/READMEnl.md",
    "content": "Read this in other languages: [English](README.md), [Türkçe](READMEtr.md), [Deutsch](READMEde.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# return-youtube-dislike-site\n\n## Opstelling bouwen\n\n```bash\n# install dependencies\n$ npm install\n\n# serve with hot reload at localhost:3000\n$ npm run dev\n\n# lint your changes\n$ npm run lint\n\n# build for production and launch server\n$ npm run build\n$ npm run start\n\n# generate static project\n$ npm run generate\n```\n\nVoor gedetailleerde uitleg over hoe dingen werken, bekijk de [documentatie](https://nuxtjs.org).\n\n## Aanbevolen VSCode-instellingen\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Ctrl(Cmd)` + `Shift` + `P` > Instellingen Openen (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Speciale mappen\n\nU kunt de volgende extra mappen maken, waarvan sommige speciaal gedrag vertonen. Alleen `pagina's` zijn vereist; u kunt ze verwijderen als u hun functionaliteit niet wilt gebruiken.\n\n### `assets`\n\nDe assets-map bevat uw niet-gecompileerde activa zoals Stylus- of Sass-bestanden, afbeeldingen of lettertypen.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/directory-structure/assets).\n\n### `componenten`\n\nDe componentenmap bevat uw Vue.js-componenten. Componenten vormen de verschillende delen van uw pagina en kunnen opnieuw worden gebruikt en geïmporteerd in uw pagina's, lay-outs en zelfs andere componenten.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/directory-structure/components).\n\n### `lay-outs`\n\nLay-outs zijn een grote hulp wanneer je het uiterlijk van je Nuxt-app wilt veranderen, of je nu een zijbalk wilt opnemen of verschillende lay-outs voor mobiel en desktop wilt hebben.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/directory-structure/layouts).\n\n### `pagina's`\n\nDeze map bevat uw toepassingsweergaven en routes. Nuxt zal alle `*.vue` bestanden in deze map lezen en Vue Router automatisch instellen.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/get-started/routing).\n\n### `plugins`\n\nTDe directory met plug-ins bevat JavaScript-plug-ins die u wilt uitvoeren voordat u de roottoepassing Vue.js start. Dit is de plek om Vue-plug-ins toe te voegen en om functies of constanten te injecteren. Elke keer dat je `Vue.use()` moet gebruiken, moet je een bestand maken in `plugins/` en het pad toevoegen aan plug-ins in `nuxt.config.js`.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/directory-structure/plugins).\n\n### `static`\n\nTzijn directory bevat uw statische bestanden. Elk bestand in deze map is toegewezen aan `/`.\n\nVoorbeeld: `/static/robots.txt` wordt toegewezen als `/robots.txt`.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/directory-structure/static).\n\n### `store`\n\nDeze map bevat uw Vuex-winkelbestanden. Door een bestand in deze map aan te maken, wordt Vuex automatisch geactiveerd.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/docs/2.x/directory-structure/store).\n"
  },
  {
    "path": "Website/READMEtr.md",
    "content": "Read this in other languages: [English](README.md), [Nederlands](READMEnl.md), [Deutsch](READMEde.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# youtube-dislike-sayısını-geri-getir-site\n\n## Yapı Kurulumu\n\n```bash\n# bağımlılıkları yükle\n$ npm install\n\n# localhost:3000'de sıcak yeniden yükleme ile çalıştır\n$ npm run dev\n\n# değişikliklerine lint'i uygula\n$ npm run lint\n\n# üretim için yapıyı oluştur ve sunucuyu başlat\n$ npm run build\n$ npm run start\n\n# statik proje oluştur\n$ npm run generate\n```\n\nİşlerin nasıl yürüdüğüyle ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org)ye göz atın.\n\n## Önerilen VSCode Kurulumu\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Ctrl(Cmd)` + `Shift` + `P` > Varsayılan Ayarları Aç (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Özel Dizinler\n\nYou can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.\n\n### `assets`\n\nAssets dizini, Stylus veya Sass dosyaları, resimler veya yazı tipleri gibi derlenmemiş varlıklarınızı içerir.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/directory-structure/assets)ye göz atın.\n\n### `components`\n\nComponents dizini, Vue.js bileşenlerinizi içerir. Component'ler, sayfanızın farklı bölümlerini oluşturur ve yeniden kullanılabilir. Ayrıca sayfalarınıza, mizanpajlarınıza ve hatta diğer component'lerinize de aktarılabilir.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/directory-structure/components)ye göz atın.\n\n### `layouts`\n\nLayouts dizini, Nuxt uygulamanızın görünümünü ve verdiği hissi değiştirmek istediğinizde, bir kenar çubuğu eklemek istediğinizde veya mobil ve masaüstü için farklı düzenlere sahip olmak istediğinizde çok yardımcı olabilir.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/directory-structure/layouts)ye göz atın.\n\n### `pages`\n\nBu dizin, uygulama görünümlerinizi ve rotalarınızı içerir. Nuxt, bu dizindeki tüm `*.vue` dosyalarını okuyacak ve Vue Router'ı otomatik olarak kuracaktır.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/get-started/routing)ye göz atın.\n\n### `plugins`\n\nPlugins dizini, kök Vue.js Uygulamasını başlatmadan önce çalıştırmak istediğiniz JavaScript eklentilerini içerir. Burası Vue eklentileri eklemek ve işlevler veya sabitler enjekte etmek için kullanılan yerdir. `Vue.use()`u her kullanmanız gerektiğinde, `plugins/` içinde bir dosya oluşturmalı ve yolunu `nuxt.config.js` içinde eklentilere eklemelisiniz.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/directory-structure/plugins)ye göz atın.\n\n### `static`\n\nBu dizin statik dosyalarınızı içerir. Bu dizindeki her dosya `/` ile eşlenir.\n\nÖrnek: `/static/robots.txt`, `/robots.txt` olarak eşlenir.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/directory-structure/static)ye göz atın.\n\n### `store`\n\nBu dizin, Vuex mağaza dosyalarınızı içerir. Bu dizinde bir dosya oluşturmak, Vuex'i otomatik olarak etkinleştirecektir.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/docs/2.x/directory-structure/store)ye göz atın.\n"
  },
  {
    "path": "Website/READMEvi.md",
    "content": "Đọc bằng các ngôn ngữ khác: [English](README.md), [Nederlands](READMEnl.md), [Türkçe](READMEtr.md)\n\n# return-youtube-dislike-site\n\n## Thiết lập Xây dựng <!-- ## Build Setup -->\n\n```bash\n# cài đặt các đối tượng phụ thuộc\n$ npm install\n\n# chạy với tự động tải lại tại localhost:3000\n$ npm run dev\n\n# chạy lint với những thay đổi của bạn\n$ npm run lint\n\n# xây dựng ra thành phẩm và khởi động máy chủ\n$ npm run build\n$ npm run start\n\n# tạo dự án tĩnh\n$ npm run generate\n```\n\nĐể biết thêm thông tin chi tiết về cách mọi thứ hoạt động, hãy xem [tài liệu](https://nuxtjs.org).\n\n## Cài đặt VSCode được khuyến nghị <!-- ## Recommended VSCode Setup -->\n\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`\n- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`\n- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)\n\n> `Ctrl(Cmd)` + `Shift` + `P` > Mở Thiết lập Mặc định (JSON)\n\n```\n\"editor.formatOnSave\": true,\n\"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n}\n\"vetur.validation.template\": false,\n```\n\n## Các Thư mục Đặc biệt <!-- ## Special Directories -->\n\nBạn có thể tạo các thư mục thêm dưới đây, một vài thư mục trong số đó có chức năng đặc biệt. Chỉ thư mục `pages` là bắt buộc; bạn có thể xóa các thư mục còn lại nếu bạn không muốn sử dụng chức năng của chúng.\n\n### `assets` <!-- ### assets -->\n\nThư mục assets chứa các tệp tài nguyên không được biên dịch như các tệp Stylus và Sass, hình ảnh, hoặc phông chữ.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/directory-structure/assets).\n\n### `components` <!-- ### components -->\n\nThư mục components chứa các thành phần Vue.js. Các thành phần này tạo nên các phần của trang và có thể được tái sử dụng và được nhập vào các trang, bố cục và kể cả thành phần khác. \n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/directory-structure/components).\n\n### `layouts` <!-- ### layouts -->\n\nBố cục rất hữu dụng khi bạn cần thay đổi giao diện của ứng dụng Nuxt, ví dụ như thêm thanh bên hay tạo bố cục riêng biệt cho thiết bị di động và máy tính để bàn.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/directory-structure/layouts).\n\n### `pages` <!-- ### pages -->\n\nThư mục này chứa các giao diện và tuyến ứng dụng. Nuxt sẽ đọc tất cả tệp có dạng `*.vue` trong thư mục này và tự động thiết lập Bộ định tuyến Vue.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/get-started/routing).\n\n### `plugins` <!-- ### plugins -->\n\nThư mục này chứa các trình bổ trợ JavaScript được dùng để chạy trước khi khởi tạo ứng dụng Vue.js gốc. Đây là nơi để thêm các trình bổ trợ Vue và các hàm hoặc hằng số. Mỗi khi bạn muốn dùng hàm `Vue.use()`, bạn cần tạo một tệp trong thư mục `plugins/` và thêm đường dẫn tới trình bổ trợ này vào trong tệp `nuxt.config.js`.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/directory-structure/plugins).\n\n### `static` <!-- ### static -->\n\nThư mục này chứa các tệp tĩnh của bạn. Mỗi tệp trong thư mục này sẽ được gán đường dẫn `/`.\n\nVí dụ: `/static/robots.txt` được gán thành `/robots.txt`.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/directory-structure/static).\n\n### `store` <!-- ### store -->\n\nThư mục này chứa các tệp Vuex store. Tạo một tệp trong thư mục này sẽ tự động kích hoạt Vuex.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/docs/2.x/directory-structure/store).\n"
  },
  {
    "path": "Website/_locales/az.ts",
    "content": "import { az } from \"vuetify/src/locale\";\n// By HAJİAGHA SADİKHOV\nexport default {\n  ...az,\n  home: {\n    name: \"Ana Səhifə\",\n    title: \"YouTube Dislike Sayını Geri Qaytar\",\n    subtitle:\n      \"YouTube-dakı dislike saylarını geri qaytaran bir brauzer genişləndirməsi və API\",\n    ukraine: \"Ukraynaya Dəstək Ol\",\n    sponsors: \"Sponsorlar\",\n    becomeSponsor: \"Sponsorumuz olun\",\n  },\n  install: {\n    name: \"Yüklə\",\n    title: \"Platformanızı Seçin\",\n    subtitle: \"Firefox və bütün Chromium əsaslı brauzerlərdə mövcuddur\",\n    title2: \"Digər Platformalar\",\n    subtitle2:\n      \"Brauzeriniz hələ dəstəklənmirsə, UserScript metodunu sınayın\",\n    title3: \"Üçüncü Tərəf Tətbiqlər\",\n    subtitle3:\n      \"Risk tamamilə sizə məxsusdur, bizim tərəfimizdən heç bir məsuliyyət qəbul edilmir\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"İstifadəçi skripti\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - NewPipe çatalı)\",\n    iosJailbroken: \"iOS (Jailbreakli)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Rəsmi RYD sənədlərinə xoş gəlmisiniz!\",\n    subtitle: \"Başlamaq üçün menyudan bir bölmə seçin.\",\n    rights: {\n      title: \"İstifadə Hüquqları\",\n      subtitle:\n        \"İctimai API-nin üçüncü tərəflərin istifadəsində aşağıdakı məhdudiyyətlərə icazə verilir:\",\n      bullet1: \"İstinad: \",\n      bullet1text:\n        \"Bu layihəyə bu depoya və ya returnyoutubedislike.com saytına keçid verərək açıq şəkildə istinad edilməlidir.\",\n      bullet2: \"Sürət Məhdudiyyəti: \",\n      bullet2text:\n        \"İstifadəçi başına dəqiqədə 100 və gündə 10.000 sürət məhdudiyyəti var. Bu, tətbiqinizin geri çəkilməsi lazım olduğunu göstərən 429 status kodunu qaytarır.\",\n    },\n    url: {\n      title: \"URL Məlumatı\",\n      subtitle: \"API-ya bu URL vasitəsilə daxil olmaq olar: \",\n    },\n    endpoints: {\n      title: \"Mövcud Endpointlər\",\n      subtitle: \"Mövcud endpointlərin siyahısı burada mövcuddur: \",\n    },\n    fetching: {\n      title: \"Əsas Məlumat Alma Təlimi\",\n      subtitle:\n        \"Müəyyən bir YouTube ID-sinin səslərini əldə etmək üçün bir nümunə: \",\n      title2: \"Nümunə Sorğu: \",\n      url: \"Sorğu URL-i: \",\n      method: \"Sorğu Metodu: \",\n      headers: \"Başlıqlar: \",\n      response: \"Nəticə: \",\n      error1: 'Yanlış YouTube ID-si 404 \"Tapılmadı\" olaraq qaytarılır.',\n      error2:\n        'Səhv formatlaşdırılmış YouTube ID-si 400 \"Yanlış Sorğu\" olaraq qaytarılır.',\n    },\n  },\n  help: {\n    name: \"Kömək\",\n    title: \"Problem Həll Etmə\",\n    bullet1: \"Genişləndirmənin ən son versiyası olan \",\n    bullet11: \" versiyasının qurulduğundan əmin olun\",\n    bullet2:\n      \"Genişləndirməni silib yenidən yükləməyi sınayın, sonra brauzerinizi yenidən başladın (bütün aktiv sekmələri bağlayın, yalnız bir sekmə deyil)\",\n    bullet3: \"Bu linki açdığınızdan əmin olun: \",\n    bullet31: \"belə bir düz mətn görməlisiniz: \",\n    bullet4: \"Yuxarıdakı kömək etmədikdə - Discord serverimizdəki \",\n    bullet41:\n      \" kanalından problemi bildirin (İngilis dilində) Discord serverimiz: \",\n    bullet4a:\n      \"Bizə İşlətmə Sisteminiz, Brauzer Adınız və Brauzer Versiyanız haqqında məlumat verin\",\n    bullet4b: \"Konsol açıqkən (açmaq üçün \",\n    bullet4b1:\n      \" düyməsinə basın) problemi yaşadığınız səhifənin ekran görüntüsünü alın (yəni YouTube izləmə səhifəsinin) - Ekran görüntüsü nümunəsi aşağıdadır.\",\n    bullet4c:\n      \"Genişləndirmə quraşdırılmışkən brauzerinizin genişləndirmələr səhifəsinin ekran görüntüsünü alın.\",\n    bullet4c1: \"Genişləndirmələri görmək üçün bu linki ünvan çubuğuna yapışdırın: \",\n    firefox: \"(Firefox üçün)\",\n    chrome: \"(Chrome, Edge, Brave, Opera və Vivaldi üçün)\",\n    detected: \"Aşkarlanan:\",\n    altExampleScreenshot: \"nümunə ekran görüntüsü\",\n  },\n  faq: {\n    name: \"Ən çox verilən suallar\",\n    title: \"Ən çox verilən suallar\",\n    subtitle:\n      \"Hələ də problem yaşayırsınız? Discord serverimizə qoşulmaqdan çəkinməyin! (İngilis dilində)\",\n    bullet1: \"Genişləndirmə məlumatları haradan alır?\",\n    bullet1text:\n      \"Rəsmi YouTube dislike sayı API-si bağlanmazdan əvvəl arxivlənmiş məlumatlar və təxmin edilən genişləndirmə istifadəçisi davranışlarının birləşməsi ilə.\",\n    bullet2: \"Dislike sayı niyə yenilənmir?\",\n    bullet2text:\n      \"Hal-hazırda dislike hallarını cache-də saxlayırıq və çox tez-tez yeniləmirik. Videonun populyarlığından asılı olaraq dəyişir, lakin yenilənməsi bir neçə saat və ya bir neçə gün çəkə bilər.\",\n    bullet3: \"Bu necə işləyir?\",\n    bullet3text:\n      \"Genişləndirmə izlədiyiniz videonun ID-sini alır, API vasitəsilə dislike sayını qaytarır (həmçinin baxış sayınızı, bəyənmələrinizi və s. digər sahələri də). Genişləndirmə daha sonra dislike sayını və nisbətini göstərir. Bir videonu bəyənsəniz və ya bəyənməsəniz bu məlumat yadda saxlanılır və düzgün dislike sayını təxmin etmək üçün verilənlər bazasına göndərilir.\",\n    bullet4: \"Dislike sayımı sizinlə paylaşa bilərəm?\",\n    bullet4text:\n      \"Çox yaxın gələcəkdə bəli. Məzmun istehsalçılarının dislike sayıları üçün doğrulanma paylaşa bilmələri üçün Oauth və ya fərqli bir oxumaq üçün məhdud API istifadə etməyi planlaşdırırıq.\",\n    bullet5: \"Hansı məlumatları toplayırsınız və bunlar necə işlənir?\",\n    bullet5text:\n      \"Genişləndirmə yalnız düzgün işləmək üçün zəruri olan məlumatları toplayır (IP adresiniz və ya videonun ID-si kimi). Məlumatlarınız heç vaxt üçüncü tərəflərə satılmayacaq. Təhlükəsizliyi və məxfiliyi necə təmin etdiyimiz haqqında daha ətraflı məlumat üçün <a href=\\\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\\\">security FAQ</a>'ya baxın.\",\n    bullet6: \"API/Backend necə işləyir?\",\n    bullet6text:\n      \"Proqram, YouTube API-sinin dislike sayını arxivləşdirilmiş məlumatlar və genişləndirmə istifadəçisi məlumatlarını istifadə edir. Yaxın gələcəkdə məzmun istehsalçılarının dislike sayını göndərməsinə icazə verəcəyik və ArchiveTeam-in arxivlənmiş məlumatlarını verilənlər bazasına əlavə edəcəyik.\",\n    bullet7: \"Dislike sayı niyə 'DISLIKE'LAR QAPALI' olaraq göstərilir?\",\n    bullet7text:\n      \"Yazı zamanı dislike sayını deaktiv edən videolar üçün dislike sayını göstərmirik. Genişləndirmə belə videolar üçün 'DISLIKE'LAR QAPALI' mesajını göstərir.\",\n  },\n  donate: {\n    name: \"Bağış Et\",\n    subtitle:\n      \"İnterneti azad etmək səylərimizdə bizə bağışınızla dəstək ola bilərsiniz!\",\n    patreon: \"Patreon\",\n    crypto: \"Kripto\",\n  },\n  links: {\n    name: \"Linklər\",\n    title: \"Layihə Linkləri\",\n    subtitle: \"Layihəyə və inkişaf etdiricilərinə linklər\",\n    contact: \"Bizimlə Əlaqə\",\n    translators: \"Tərcüməçilər\",\n    coolProjects: \"Maraqlı Layihələr\",\n    sponsorBlockDescription: \"Videolara daxil olan reklamlardan keçər\",\n    filmotDescription: \"YouTube videolarını altyazıya görə axtarmağa imkan verir\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"dəstəklənmir.\",\n    considerUpgrade: \"Zəhmət olmasa ən son versiyaya yeniləyin.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/cs.ts",
    "content": "import { cs } from \"vuetify/src/locale\";\n// By Fjuro\nexport default {\n  ...cs,\n  home: {\n    name: \"Domů\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"Rozšíření prohlížeče a API, která zobrazí disliky na YouTube\",\n    ukraine: \"Podpořte Ukrajinu\",\n    sponsors: \"Sponzoři\",\n    becomeSponsor: \"Staňte se naším sponzorem\",\n  },\n  install: {\n    name: \"Instalace\",\n    title: \"Vyberte svou platformu\",\n    subtitle: \"Dostupné pro Firefox a všechny prohlížeče založené na Chromiu\",\n    title2: \"Další platformy\",\n    subtitle2:\n      \"Pokud váš prohlížeč ještě není podporován, vyzkoušejte tento UserScript\",\n    title3: \"Implementace třetích stran\",\n    subtitle3:\n      \"Žádná odpovědnost na naší straně, používejte na vlastní nebezpečí\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Uživatelský skript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular – fork NewPipe)\",\n    iosJailbroken: \"iOS (s jailbreakem)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Vítejte v oficiální dokumentaci RYD!\",\n    subtitle: \"Pro začátek vyberte sekci z menu.\",\n    rights: {\n      title: \"Práva k použití\",\n      subtitle:\n        \"Použití této API třetími stranami je povoleno s následujícími omezeními:\",\n      bullet1: \"Uvedení: \",\n      bullet1text:\n        \"Tento projekt by měl být viditelně uveden buď s odkazem na tento repozitář, nebo s odkazem na returnyoutubedislike.com\",\n      bullet2: \"Omezení požadavků: \",\n      bullet2text:\n        \"U API platí omezení na 100 požadavků za minutu a 10 000 za den. Při překročení tohoto limitu aplikace vrátí stavový kód 429, značící, že byste měli omezit své požadavky\",\n    },\n    url: {\n      title: \"Informace o URL\",\n      subtitle: \"API je dostupná na následující základní URL: \",\n    },\n    endpoints: {\n      title: \"Dostupné endpointy\",\n      subtitle: \"Seznam dostupných endpointů lze nalézt zde: \",\n    },\n    fetching: {\n      title: \"Základní návod na přístup\",\n      subtitle: \"Příklad získání hlasů daného ID YouTube videa: ID: \",\n      title2: \"Příklad požadavku: \",\n      url: \"URL požadavku: \",\n      method: \"Způsob žádosti: \",\n      headers: \"Hlavičky: \",\n      response: \"Odpověď: \",\n      error1: 'Neplatné YouTube ID vrátí stavový kód 404 \"Not Found\"',\n      error2:\n        'Nesprávně formátované YouTube ID vrátí stavový kód 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Nápověda\",\n    title: \"Řešení problémů\",\n    bullet1:\n      \"Ujistěte se, že používáte nejnovější verzi rozšíření. Momentálně je to verze \",\n    bullet11: \"\",\n    bullet2:\n      \"Zkuste odebrat a znovu nainstalovat rozšíření a poté restartovat váš prohlížeč (všechna aktivní okna, ne jen jednu kartu)\",\n    bullet3: \"Ujistěte se, že se otevře následující odkaz: \",\n    bullet31: \"měli byste vidět tento text: \",\n    bullet4:\n      \"Pokud nepomůže ani jedna z možností výše, nahlaste svůj problém do kanálu\",\n    bullet41: \"v našem\",\n    bullet4a:\n      \"Řekněte nám jaký máte operační systém, název prohlížeče a verzi prohlížeče\",\n    bullet4b:\n      \"Udělejte snímek obrazovky s problémem (např. stránka YouTube videa) s otevřenou konzolí (stiskněte \",\n    bullet4b1: \") - příkladný snímek viz níže.\",\n    bullet4c:\n      \"Udělejte snímek obrazovky stránky rozšíření vašeho prohlížeče s nainstalovaným rozšířením.\",\n    bullet4c1:\n      \"Pro zobrazení rozšíření zadejte následující adresu do adresního řádku: \",\n    firefox: \"pro Firefox\",\n    chrome: \"pro Chrome, Edge, Brave, Opera, Vivaldi\",\n    detected: \"Zjištěno:\",\n    altExampleScreenshot: \"ukázkový snímek obrazovky\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Často kladené dotazy\",\n    subtitle: \"Stále máte otázky? Připojte se na náš Discord server!\",\n    bullet1: \"Jak získává rozšíření svá data?\",\n    bullet1text:\n      \"Kombinací archivovaných dat z doby před oficiálním vypnutím rozhraní API YouTube dislike a extrapolovaného chování uživatelů rozšíření..\",\n    bullet2: \"Proč se neaktualizuje počet disliků?\",\n    bullet2text:\n      \"V současné době jsou počty disliků videí ukládány do mezipaměti a nejsou příliš často aktualizovány. Jejich aktualizace se liší v závislosti na popularitě videa, ale může trvat od několika hodin do několika dnů.\",\n    bullet3: \"Jak to funguje?\",\n    bullet3text:\n      \"Rozšíření načte ID videa, které sledujete, a pomocí našeho rozhraní API získá počet disliků (a další pole, jako jsou zobrazení, lajky atd.). Rozšíření pak na stránce zobrazí počet a poměr disliků. Pokud se vám video líbí nebo nelíbí, je to zaznamenáno a odesláno do databáze, takže lze extrapolovat přesný počet disliků.\",\n    bullet4: \"Můžu s vámi sdílet počet svých disliků?\",\n    bullet4text:\n      \"Již brzy. Hledáme způsob, jak použít rozhraní Oauth nebo jiného rozhraní API pouze pro čtení s omezeným rozsahem, aby tvůrci mohli ověřitelně sdílet své počty disliků.\",\n    bullet5: \"Jaká data sbíráte a jak je s nimi nakládáno?\",\n    bullet5text:\n      'Rozšíření shromažďuje pouze údaje, které jsou nezbytně nutné pro jeho správnou funkci, například IP adresu nebo ID sledovaného videa. Žádné z vašich údajů nebudou nikdy prodány třetím stranám. Pokud se chcete dozvědět více o tom, jak se staráme o zabezpečení a ochranu osobních údajů, podívejte se na naši <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">bezpečnostní FAQ</a>.',\n    bullet6: \"Jak funguje API/Backend?\",\n    bullet6text:\n      \"Backend používá archivovaná data z doby, kdy rozhraní YouTube ještě vracelo počet disliků, počty lajků/disliků uživatelů a extrapolaci. V blízké budoucnosti umožníme tvůrcům obsahu snadno a bezpečně odesílat počet disliků a do naší současné databáze přidáme archivovaná data ArchiveTeamu (4,56 miliardy videí). Můžete si také prohlédnout video na toto téma.\",\n    bullet7: \"Proč počet disliků zobrazuje 'DISLIKES DISABLED'?\",\n    bullet7text:\n      \"Někdy se u nedávno nahraného videa může zobrazit 'DISLIKES DISABLED' (disliky zakázány), i když je tvůrce nezakázal, což je způsobeno tím, jak zjišťujeme, zda jsou dislike zakázány. Mělo by to samo zmizet během několika hodin nebo tím, že dáte lajk či dislike danému videu.\",\n  },\n  donate: {\n    name: \"Přispět\",\n    subtitle:\n      \"Můžete podpořit naše úsilí ponechat internet bezplatný jakýmkoli darem!\",\n    patreon: \"Patreon\",\n    crypto: \"Kryptoměna\",\n  },\n  links: {\n    name: \"Odkazy\",\n    title: \"Odkazy projektu\",\n    subtitle: \"Odkazy na projekt a jeho vývojáře\",\n    contact: \"Kontaktujte mě\",\n    translators: \"Překladatelé\",\n    coolProjects: \"Bezva projekty\",\n    sponsorBlockDescription: \"Přeskočte reklamy integrované ve videích\",\n    filmotDescription: \"Prohledávejte YouTube videa pomocí titulků\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"není podporován.\",\n    considerUpgrade: \"Zvažte aktualizaci na nejnovější verzi.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/de.ts",
    "content": "import { en } from \"vuetify/src/locale\";\n// By tubyoub\nexport default {\n  ...en,\n  home: {\n    name: \"Startseite\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"Eine Browsererwiterung und eine API, welche dir die YouTube dislikes zeigt\",\n    ukraine: \"Unterstützt die Ukraine\",\n    sponsors: \"Sponsoren\",\n    becomeSponsor: \"Werde unser Sponsor\",\n  },\n  install: {\n    name: \"Installieren\",\n    title: \"Wähle deine Platform\",\n    subtitle: \"Erhältlich für Firefox und alle Chromium-basierten Browser\",\n    title2: \"Andere Platformen\",\n    subtitle2: \"Wenn dein Browser noch nicht unterstützt wird, versuche dieses UserSript\",\n    title3: \"Implementierungen von Drittanbietern\",\n    subtitle3: \"Keine Garantie von unserer Seite, Nutzung auf eigene Gefahr\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Benutzerskript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular – ein Fork von NewPipe)\",\n    iosJailbroken: \"iOS (Jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Willkommen auf der offiziellen RYD Dokumentation!\",\n    subtitle: \"Um zu beginnen, wähle einen Bereich aus dem Menü.\",\n    rights: {\n      title: \"Nutzungsrechte\",\n      subtitle: \"Die Nutzung dieser offenen API durch Dritte ist mit den folgenden Einschränkungen gestattet:\",\n      bullet1: \"Namensnennung: \",\n      bullet1text:\n        \"Dieses Projekt sollte eindeutig mit einem Link zu dieser Repo oder einem Link zu returnyoutubedislike.com gekennzeichnet werden\",\n      bullet2: \"Begrenzung der Anfragen: \",\n      bullet2text:\n        \"Es gibt Ratenbegrenzung pro Client von 100 pro Minute und 10.000 pro Tag. Dies führt zu einem Statuscode 429, der anzeigt, dass deine Anwendung vor der nächsten Anfrage warten sollte\",\n    },\n    url: {\n      title: \"URL Information\",\n      subtitle: \"Die API ist über die folgende Basis-URL zugänglich: \",\n    },\n    endpoints: {\n      title: \"Verfügbare Endpunkte\",\n      subtitle: \"Eine Liste aller Endpunkte sind hier zu finden: \",\n    },\n    fetching: {\n      title: \"Grundlegende Anleitung zum Abrufen der Daten\",\n      subtitle: \"Grundlegende Anleitung zum Abrufen der Bewertung von einer gegebenen YouTube Video ID: \",\n      title2: \"Besipiel Anfragen: \",\n      url: \"Anfrage-URL: \",\n      method: \"Anfrage Methoden: \",\n      headers: \"Header: \",\n      response: \"Antwort: \",\n      error1: 'Eine ungültige YouTube-ID liefert den Statuscode 404 \"Not Found\"',\n      error2: 'Eine falsch formatierte YouTube-ID liefert den Statuscode 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Hilfe\",\n    title: \"Fehlersuche\",\n    bullet1: \"Stelle sicher, dass du die neuste Version der Erweiterung installiert hast, derzeit ist das die Version \",\n    bullet11: \"\",\n    bullet2:\n      \"Versuche, die Erweiterung zu entfernen und erneut zu installieren, und starte dann den Browser neu (alle aktiven Fenster, nicht nur eine Registerkarte)\",\n    bullet3: \"Stelle sicher das du den Link öffnest: \",\n    bullet31: \"Du solltest diesen Text sehen: \",\n    bullet4: \"Wenn nichts von den oben gesagten Lösungen hilft - melde das Problem in\",\n    bullet41: \"in unserem\",\n    bullet4a: \"Gib uns den Namen deines Betriebssystems, deines Browsers und die Version des Browsers \",\n    bullet4b:\n      \"Mache einen Screenshot der Seite mit dem Problem (von der YouTube-Seite) mit der Konsole offen (drücke \",\n    bullet4b1: \") - wie in dem Screenshot unten.\",\n    bullet4c: \"Mache ein Screenshot von deinen installierten Erweiterungen in deinem Browser.\",\n    bullet4c1: \"Um diese Seite zu sehen, gib das in dei Adressleiste ein: \",\n    firefox: \"für Firefox und\",\n    chrome: \"für Chrome, Edge, Brave, Opera und Vivaldi\",\n    detected: \"Erkannt:\",\n    altExampleScreenshot: \"Beispiel-Screenshot\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Häufig gestellte Fragen\",\n    subtitle: \"Hast du noch Fragen? Dann komm auf unseren Discord!\",\n    bullet1: \"Woher bekommt die Erweiterung ihre Daten?\",\n    bullet1text:\n      \"Eine Kombination aus archivierten Daten aus der Zeit, bevor die offizielle YouTube-Dislike-API abgeschaltet wurde, und extrapoliertem Statistiken durch Analyse des Nutzerverhaltens.\",\n    bullet2: \"Warum updated der Dislike-Counter nicht?\",\n    bullet2text:\n      \"Momentan werden die Dislikes von Videos im Cache gespeichert und nicht sehr häufig aktualisiert. Die Aktualisierung hängt von der Popularität eines Videos ab, das kann zwischen ein paar Stunden und ein paar Tagen dauern.\",\n    bullet3: \"Wie funktioniert die Extension?\",\n    bullet3text:\n      \"Die Erweiterung sammelt die Video-ID des Videos, das du dir ansiehst, und ruft die Dislikes (und andere Felder wie Views, Likes usw.) über unsere API ab. Die Erweiterung zeigt dann die Anzahl der Dislikes und das Like/Dislike-Verhältnis auf der Seite an. Wenn dir ein Video gefällt oder nicht gefällt, wird dies aufgezeichnet und an die Datenbank gesendet, so dass eine genaue Anzahl der Dislikes bestimmt werden kann.\",\n    bullet4: \"Kann ich meine Dislikes mit euch teilen?\",\n    bullet4text:\n      \"Demnächst. Wir prüfen die Verwendung von Oauth oder einer anderen Read-Only-API mit begrenztem Umfang, damit die Youtuber ihre Dislike-Anzahl verifizieren können.\",\n    bullet5: \"Was für Daten sammelt ihr und wie werden sie verarbeitet?\",\n    bullet5text:\n      'Die Erweiterung sammelt nur Daten, die für ihr ordnungsgemäßes Funktionieren unbedingt erforderlich sind, z.B. die IP-Adresse oder die ID des Videos, das du dir ansiehst. Keine deiner Daten werden jemals an Dritte verkauft. Wenn du mehr darüber erfahren möchtest, wie wir mit Sicherheit und Datenschutz umgehen, lesen Sie unsere <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\"> Sicherheits-FAQ</a>.',\n    bullet6: \"Wie Funktioniert die API / das Backend?\",\n    bullet6text:\n      \"Das Backend verwendet archivierte von damals, als die YouTube API noch die Anzahl der Dislikes, die Anzahl der Likes und Dislikes der Extension-Nutzer und die Hochrechnung zurückgab. In naher Zukunft werden wir es Youtubern ermöglichen, ihre Dislike-Anzahl einfach und sicher zu übermitteln, und wir werden die archivierten Daten von ArchiveTeam (4,56 Milliarden Videos) in unsere aktuelle Datenbank aufnehmen. Du kannst dir auch ein Video zu diesem Thema ansehen.\",\n    bullet7: \"Warum zeigt der Dislike-Counter manchmal 'DISLIKES DISABLED'?\",\n    bullet7text:\n      \"Manchmal wird bei einem kürzlich hochgeladenen Video 'DISLIKES DISABLED' angezeigt, auch wenn der Youtuber es nicht deaktiviert hat. Das liegt daran, wie wir erkennen, ob Dislikes deaktiviert sind. Es sollte in ein paar Stunden verschwinden oder indem man das Video liked oder disliked und die Seite aktualisiert (hoffentlich).\",\n  },\n  donate: {\n    name: \"Spenden\",\n    subtitle: \"Du kannst unsere Bemühungen, das Internet frei zu halten, mit einer Spende unterstützen!\",\n    patreon: \"Patreon\",\n    crypto: \"Krypto\",\n  },\n  links: {\n    name: \"Links\",\n    title: \"Projekt-Links\",\n    subtitle: \"Link zu dem Projekt und deren Entwickler\",\n    contact: \"Kontakt\",\n    translators: \"Übersezter\",\n    coolProjects: \"Coole Projekte\",\n    sponsorBlockDescription: \"Überspringe integrierte Werbung in Videos\",\n    filmotDescription: \"Suche nach YouTube-Videos über ihre Untertitel\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"wird nicht unterstützt.\",\n    considerUpgrade: \"Bitte auf die neueste Version aktualisieren.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/en.ts",
    "content": "import { en } from \"vuetify/src/locale\";\n\nexport default {\n  ...en,\n  home: {\n    name: \"Home\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"Browser extension and an API that shows you dislikes on Youtube\",\n    ukraine: \"Support Ukraine\",\n    sponsors: \"Sponsors\",\n    becomeSponsor: \"Become our sponsor\",\n  },\n  install: {\n    name: \"Install\",\n    title: \"Select Your Platform\",\n    subtitle: \"Available for Firefox and all Chromium browsers\",\n    title2: \"Other Platforms\",\n    subtitle2: \"If your browser is not yet supported, try this UserScript\",\n    title3: \"Third Party Implementations\",\n    subtitle3: \"No liability on our side, use at your own risk\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Userscript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - a NewPipe fork)\",\n    iosJailbroken: \"iOS (Jailbroken)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Welcome to the official RYD docs!\",\n    subtitle: \"To get started, select a section from the menu.\",\n    rights: {\n      title: \"Usage Rights\",\n      subtitle:\n        \"Third party use of this open API is allowed with the following restrictions:\",\n      bullet1: \"Attribution: \",\n      bullet1text:\n        \"This project should be clearly attributed with either a link to this repo or a link to returnyoutubedislike.com\",\n      bullet2: \"Rate Limiting: \",\n      bullet2text:\n        \"There are per client rate limits in place of 100 per minute and 10,000 per day. This will return a 429 status code indicating that your application should back off\",\n    },\n    url: {\n      title: \"URL Information\",\n      subtitle: \"The API is accessible over the following base URL: \",\n    },\n    endpoints: {\n      title: \"Available Endpoints\",\n      subtitle: \"List of available endpoints is available here: \",\n    },\n    fetching: {\n      title: \"Basic Fetching Tutorial\",\n      subtitle: \"Example to get votes of a given YouTube video ID: \",\n      title2: \"Example Request: \",\n      url: \"Request URL: \",\n      method: \"Request Method: \",\n      headers: \"Headers: \",\n      response: \"Response: \",\n      error1: 'An invalid YouTube ID will return status code 404 \"Not Found\"',\n      error2:\n        'An incorrectly formatted YouTube ID will return 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Help\",\n    title: \"Troubleshooting\",\n    bullet1: \"Make sure you have latest version of extension installed, \",\n    bullet11: \"right now\",\n    bullet2:\n      \"Try removing extension and installing it again, then restarting the browser (all active windows, not just one tab)\",\n    bullet3: \"Make sure that this link opens: \",\n    bullet31: \"you should see plain text: \",\n    bullet4: \"If nothing of above helps - report your problem in\",\n    bullet41: \"in our\",\n    bullet4a: \"Tell us your Operating System, Browser Name and Browser Version\",\n    bullet4b:\n      \"Take a screenshot of the page with the problem (i.e. Youtube video page) with the console open (press \",\n    bullet4b1: \") - example screenshot below.\",\n    bullet4c:\n      \"Take a screenshot of the extensions page of your browser with the extension installed.\",\n    bullet4c1: \"To see extensions put this into address bar: \",\n    firefox: \"for Firefox\",\n    chrome: \"for Chrome, Edge, Brave, Opera, and Vivaldi\",\n    detected: \"Detected:\",\n    altExampleScreenshot: \"example screenshot\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Frequently Asked Questions\",\n    subtitle: \"Still have questions? Feel free to join our Discord!\",\n    bullet1: \"Where does the extension get its data?\",\n    bullet1text:\n      \"A combination of archived data from before the official YouTube dislike API shut down, and extrapolated extension user behavior.\",\n    bullet2: \"Why isn't the dislike count updating?\",\n    bullet2text:\n      \"Right now video dislikes are cached and they aren't updated very frequently. It varies depending on a video's popularity but can take anywhere between a few hours and a few days to update.\",\n    bullet3: \"How does this work?\",\n    bullet3text:\n      \"The extension collects the video ID of the video you are watching, and fetches the number of dislikes (and other fields like views, likes etc) using our API. The extension then displays the dislike count and ratio on the page. If you like or dislike a video, that is recorded and sent to the database so an accurate dislike count can be extrapolated.\",\n    bullet4: \"Can I share my dislike count with you?\",\n    bullet4text:\n      \"Coming soon. We are looking into using Oauth or a different read only API with a limited scope so creators can share their dislike counts verifiability.\",\n    bullet5: \"What data do you collect and how is it treated?\",\n    bullet5text:\n      'The extension only collects data that is strictly necessary for it to function properly, such as the IP address or ID of the video you\\'re watching. None of your data will ever be sold to 3rd parties. If you would like to know more about how we handle security and privacy check out our <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">security FAQ</a>.',\n    bullet6: \"How does the API/Backend work?\",\n    bullet6text:\n      \"The backend is using archived data from when the youtube api was still returning the dislike count, extension users like/dislike count and extrapolation. In the near future we will be allowing content creators to submit their dislike count easily and safely and we will be adding ArchiveTeam's archived data (4.56 billion videos) into our current database. You can also view a video on the topic.\",\n    bullet7: \"Why does the dislike count show 'DISLIKES DISABLED'?\",\n    bullet7text:\n      \"Sometimes a recently uploaded video might show 'DISLIKES DISABLED' even if the creator hasn't disabled it, this is due to how we are detecting if dislikes are disabled, it should go away in a few hours or by liking or disliking the video and refreshing the page (hopefully).\",\n  },\n  donate: {\n    name: \"Donate\",\n    subtitle:\n      \"You can support our efforts to keep the internet free with a donation!\",\n    patreon: \"Patreon\",\n    crypto: \"Crypto\",\n  },\n  links: {\n    name: \"Links\",\n    title: \"Project Links\",\n    subtitle: \"Links to the project and its developers\",\n    contact: \"Contact Me\",\n    translators: \"Translators\",\n    coolProjects: \"Cool Projects\",\n    sponsorBlockDescription: \"Skips ads integrated in video\",\n    filmotDescription: \"Search YouTube videos by subtitles\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"is not supported.\",\n    considerUpgrade: \"Consider upgrading to the latest version.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/es.ts",
    "content": "import { es } from \"vuetify/src/locale\";\n// By Alejandro Gayol\nexport default {\n  ...es,\n  home: {\n    name: \"Inicio\",\n    title: \"Return YouTube Dislike\",\n    subtitle:\n      \"Una extensión de navegador y una API que muestra el número de «dislikes» («No me gusta») en YouTube\",\n    ukraine: \"Apoya a Ucrania\",\n    sponsors: \"Patrocinadores\",\n    becomeSponsor: \"Hazte nuestro patrocinador\",\n  },\n  install: {\n    name: \"Instalación\",\n    title: \"Elige tu plataforma\",\n    subtitle: \"Disponible para Firefox y todos los navegadores Chromium\",\n    title2: \"Otras plataformas\",\n    subtitle2: \"Si tu navegador aún no es compatible, usa este UserScript\",\n    title3: \"Implementaciones de terceros\",\n    subtitle3: \"No nos hacemos responsables, úsalas bajo tu cuenta y riesgo\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Script de usuario\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - un fork de NewPipe)\",\n    iosJailbroken: \"iOS (con jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"¡Te damos la bienvenida a la documentación oficial de RYD!\",\n    subtitle: \"Para empezar, elige una sección del menú.\",\n    rights: {\n      title: \"Derechos de uso\",\n      subtitle:\n        \"Se permite el uso de terceros de esta API abierta bajo las siguientes restricciones: \",\n      bullet1: \"Atribución: \",\n      bullet1text:\n        \"El proyecto debe estar claramente atribuido con un enlace a este repositorio o a returnyoutubedislike.com\",\n      bullet2: \"Límites de velocidad: \",\n      bullet2text:\n        \"Hay límites de velocidad por cliente de 100 solicitudes por minuto y 10.000 al día. Al excederlos se mostrará un código de estado *429*, indicando que tu aplicación debe bajar la velocidad.\",\n    },\n    url: {\n      title: \"Información de URL\",\n      subtitle: \"Esta API es accesible a través de la siguiente URL base: \",\n    },\n    endpoints: {\n      title: \"«Endpoints» disponibles\",\n      subtitle:\n        \"La lista de los endpoints disponibles se puede consultar aquí: \",\n    },\n    fetching: {\n      title: \"Tutorial de consulta básica\",\n      subtitle:\n        \"Ejemplo para obtener los votos del ID de un vídeo de YouTube: \",\n      title2: \"Consulta de ejemplo: \",\n      url: \"URL de la consulta: \",\n      method: \"Método de la consulta: \",\n      headers: \"Encabezados: \",\n      response: \"Respuesta: \",\n      error1:\n        'Un ID de YouTube no existente mostrará el código de estado 404 \"Not Found\" (no encontrado).',\n      error2:\n        'Un ID de YouTube mal estructurado mostrará el código 400 \"Bad Request\" (solicitud incorrecta).',\n    },\n  },\n  help: {\n    name: \"Ayuda\",\n    title: \"Solución de problemas\",\n    bullet1:\n      \"Asegúrate de que tienes instalada la última versión de la extensión, \",\n    bullet11: \"\",\n    bullet2:\n      \"Prueba a eliminar la extensión e instalarla de nuevo, después reinicia el navegador (todas las ventanas activas, no solo una pestaña)\",\n    bullet3: \"Comprueba que puedes abrir este enlace: \",\n    bullet31: \"deberías ver este texto: \",\n    bullet4:\n      \"Si nada de esto surte efecto, informa de tu problema (en inglés) en el canal\",\n    bullet41: \"de nuestro\",\n    bullet4a:\n      \"Dinos cuáles son tu sistema operativo, el nombre y la versión de tu navegador\",\n    bullet4b:\n      \"Haz una captura de pantalla de la página que contenga el problema (como una página de un vídeo de YouTube) con la consola abierta (pulsa \",\n    bullet4b1: \"). Aquí puedes ver una captura de pantalla de ejemplo:\",\n    bullet4c:\n      \"Haz una captura de la página de extensiones de tu navegador con la extensión instalada.\",\n    bullet4c1:\n      \"Para ver las extensiones, escribe el siguiente texto en la barra de dirección: \",\n    firefox: \"para Firefox\",\n    chrome: \"para Chrome, Edge, Brave, Opera, Vivaldi\",\n    detected: \"Detectado:\",\n    altExampleScreenshot: \"captura de ejemplo\",\n  },\n  faq: {\n    name: \"Preguntas\",\n    title: \"Preguntas más frecuentes\",\n    subtitle:\n      \"¿Sigues teniendo problemas? ¡Pásate por nuestro servidor de Discord (en inglés)!\",\n    bullet1: \"¿De dónde obtiene sus datos la extensión?\",\n    bullet1text:\n      \"De una combinación de datos archivados antes de que la API oficial de «dislikes» de YouTube se apagase, extrapolada con las acciones realizadas por los usuarios de la extensión.\",\n    bullet2: \"¿Por qué no se actualiza el contador de «dislikes»?\",\n    bullet2text:\n      \"En la actualidad, las cuentas de «dislikes» se almacenan en una caché y no se actualizan con frecuencia. Esta frecuencia varía dependiendo de la popularidad de un vídeo, pero puede tardar entre unas pocas horas y unos pocos días en actualizarse.\",\n    bullet3: \"¿Cómo funciona la extensión?\",\n    bullet3text:\n      \"La extensión lee el ID del vídeo que estés viendo y carga la cifra de «dislikes» (y otras estadísticas, como visitas, «likes», etc.) a través de nuestra API. Después la extensión muestra el número de «dislikes» y la proporción de «likes»/«dislikes» en la página. Si utilizas los botones de «Me gusta» o «No me gusta» de un vídeo, esa información se guardará y enviará a la base de datos con la que se podrá extrapolar una cuenta de «dislikes» más precisa.\",\n    bullet4: \"¿Puedo compartir mi contador de «dislikes» con vosotros?\",\n    bullet4text:\n      \"Próximamente. Estamos investigando en usar Oauth u otra API de solo lectura con alcance limitado para que los creadores puedan compartir sus contadores de «dislikes» verificablemente.\",\n    bullet5: \"¿Qué datos almacenáis y cómo son guardados?\",\n    bullet5text:\n      'La extensión almacena únicamente aquellos datos estrictamente necesarios para su buen funcionamiento, cómo la dirección IP o el ID del vídeo que estés viendo. Tus datos nunca serán vendidos a terceros. Si quieres saber más sobre nuestras políticas de seguridad y privacidad, consulta nuestro <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">documento de preguntas frecuentes sobre seguridad</a>.',\n    bullet6: \"¿Cómo funciona la API y el «backend»?\",\n    bullet6text:\n      \"El «backend» utiliza datos archivados de cuando la API de YouTube seguía proporcionando el contador de «dislikes», los votos de usuarios de la extensión y su extrapolación. En un futuro cercano permitiremos que los creadores de contenido puedan proporcionar sus cifras de «dislikes» de forma fácil y segura, e incorporaremos los datos archivados de ArchiveTeam (4.560 millones de vídeos) en nuestra base de datos. También puedes ver un vídeo al respecto.\",\n    bullet7: \"¿Porque el contador de «dislikes» dice «DISLIKES DESACTIVADOS»?\",\n    bullet7text:\n      \"En ocasiones, un vídeo subido recientemente puede mostrar «DISLIKES DESACTIVADOS» aunque su creador no los haya desactivado, esto se debe a la forma en que detectamos si los «dislikes» están desactivados. El mensaje debería desaparecer en unas horas o al darle «like» o «dislike» al vídeo y actualizar la página (con suerte).\",\n  },\n  donate: {\n    name: \"Donar\",\n    subtitle:\n      \"¡Puedes apoyar nuestros esfuerzos para que Internet siga siendo con un donativo!\",\n    patreon: \"Patreon\",\n    crypto: \"Cripto\",\n  },\n  links: {\n    name: \"Enlaces\",\n    title: \"Enlaces del proyecto\",\n    subtitle: \"Enlaces del proyecto y sus desarrolladores\",\n    contact: \"Contacto\",\n    translators: \"Traductores\",\n    coolProjects: \"Proyectos interesantes\",\n    sponsorBlockDescription: \"Omite los anuncios integrados en vídeos\",\n    filmotDescription: \"Busca vídeos de YouTube a través de sus subtítulos\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"no es compatible.\",\n    considerUpgrade: \"Considera actualizar a la última versión.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/fr.ts",
    "content": "import { fr } from \"vuetify/src/locale\";\n\nexport default {\n  ...fr,\n  home: {\n    name: \"Accueil\",\n    title: \"Return YouTube Dislike\",\n    subtitle:\n      \"Une extension de navigateur et une API qui vous montre les dislike sur Youtube\",\n    ukraine: \"Soutenir l'Ukraine\",\n    sponsors: \"Sponsors\",\n    becomeSponsor: \"Devenez notre sponsor\",\n  },\n  install: {\n    name: \"Installer\",\n    title: \"Choisissez votre Plateforme\",\n    subtitle: \"Disponible pour Firefox et tous les navigateurs Chromium\",\n    title2: \"Autres platesformes\",\n    subtitle2:\n      \"Si votre navigateur n'est pas encore pris en charge, essayez ce UserScript\",\n    title3: \"Implémentations par des tiers\",\n    subtitle3:\n      \"Aucune responsabilité de notre part, à utiliser à vos risques et périls.\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Script utilisateur\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular – un fork de NewPipe)\",\n    iosJailbroken: \"iOS (jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Bienvenue sur la documentation officiel de RYD !\",\n    subtitle: \"Pour commencer, sélectionnez une section dans le menu.\",\n    rights: {\n      title: \"Droits d'utilisation\",\n      subtitle:\n        \"L'utilisation par des tiers de cette API ouverte est autorisée avec les restrictions suivantes : \",\n      bullet1: \"Attribution: \",\n      bullet1text:\n        \"Ce projet doit être clairement nommé avec un lien vers soit vers GitHub soit vers returnyoutubedislike.com\",\n      bullet2: \"Limites des requêtes\",\n      bullet2text:\n        \"Il y a des limites de requêtes par client en place qui sont de 100 requêtes par minute et 10 000 par jour. Nous renverrons un code d'erreur 429 indiquant que votre application devrait se calmer.\",\n    },\n    url: {\n      title: \"Information sur l'URL\",\n      subtitle: \"L'API est accesible via l'URL de base suivante : \",\n    },\n    endpoints: {\n      title: \"Endpoints disponibles\",\n      subtitle: \"La liste des endpoints disponibles est accesible ici : \",\n    },\n    fetching: {\n      title: \"Tutoriel de base sur la récupération des données\",\n      subtitle:\n        \"Exemple pour obtenir les likes d'une vidéo YouTube avec un ID donné : \",\n      title2: \"Example de requête : \",\n      url: \"URL de la requête : \",\n      method: \"Mode de requête : \",\n      headers: \"En-têtes (headers) : \",\n      response: \"Réponse : \",\n      error1:\n        \"Si aucune vidéo YouTube n'a cet ID, le code d'erreur 404 \\\"Not Found\\\" sera retourné\",\n      error2:\n        'Un ID YouTube invalide renverra le code d\\'erreur *400* \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Aide\",\n    title: \"Dépannage\",\n    bullet1:\n      \"Assurez-vous que la dernière version de l'extension est installée, \",\n    bullet11: \"à l'heure actuelle\",\n    bullet2:\n      \"Essayez de supprimer l'extension et de la réinstaller, puis redémarrez le navigateur (toutes les fenêtres actives, pas seulement un onglet).\",\n    bullet3: \"Assurez-vous que ce lien s'ouvre : \",\n    bullet31: \"vous devriez voir du texte brut : \",\n    bullet4:\n      \"Si rien de ce qui précède ne vous aide, signalez votre problème sur\",\n    bullet41: \"ou sur notre\",\n    bullet4a:\n      \"Indiquez-nous votre système d'exploitation ansi que le nom et la version de votre navigateur\",\n    bullet4b:\n      \"Faites une capture d'écran de la page présentant le problème (par exemple, la page de la vidéo youtube) avec la console ouverte (appuyez sur \",\n    bullet4b1: \") - exemple de capture d'écran ci-dessous.\",\n    bullet4c:\n      \"Faites une capture d'écran de la page des extensions de votre navigateur avec les extension installée.\",\n    bullet4c1:\n      \"Pour voir les extensions, mettez ceci dans la barre d'adresse : \",\n    firefox: \"pour Firefox\",\n    chrome: \"pour Chromium (Chrome, Edge, Brave, Opera, Vivaldi...)\",\n    detected: \"Détecté :\",\n    altExampleScreenshot: \"capture d'écran d'exemple\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Foire Aux Questions\",\n    subtitle:\n      \"Vous avez encore des questions ? N'hésitez pas à rejoindre notre Discord (en anglais) !\",\n    bullet1: \"Où l'extension obtient-elle ses données ?\",\n    bullet1text:\n      \"Une combinaison de données archivées datant d'avant la fermeture de l'API officielle de YouTube pour les dislikes, et d'extrapolation du comportement des utilisateurs.\",\n    bullet2: \"Pourquoi le nombre de dislike n'est-il pas mis à jour ?\",\n    bullet2text:\n      \"Actuellement, les dislike des vidéos sont mis en cache et ne sont pas mis à jour très fréquemment. La mise à jour varie en fonction de la popularité de la vidéo, mais peut prendre entre quelques heures et quelques jours.\",\n    bullet3: \"Comment cela fonctionne-t-il ?\",\n    bullet3text:\n      \"L'extension collecte l'ID de la vidéo que vous regardez, récupère les dislike (et d'autres champs comme les vues, les likes, etc.) en utilisant notre API. L'extension affiche ensuite le nombre de dislike et le ratio sur la page. Si vous liker ou disliker une vidéo, cela est enregistré et envoyé à la base de données afin qu'un nombre précis de dislikes puisse être extrapolé.\",\n    bullet4: \"Puis-je partager mon compte de dislike avec vous ?\",\n    bullet4text:\n      \"Prochainement. Nous envisageons d'utiliser Oauth ou une autre API en lecture seule avec un champ d'application limité afin que les créateurs puissent partager leurs compteurs de dislikes de manière vérifiable.\",\n    bullet5:\n      \"Quelles sont les données que vous collectez et comment sont-elles traitées ?\",\n    bullet5text:\n      \"L'extension ne collecte que les données strictement nécessaires à son bon fonctionnement, comme l'adresse IP ou l'ID de la vidéo que vous regardez. Aucune de vos données ne sera jamais vendue à des tiers. Si vous souhaitez en savoir plus sur la manière dont nous gérons la sécurité et la confidentialité, consultez notre <a href=\\\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQfr.md\\\">FAQ sur la sécurité</a>.\",\n    bullet6: \"Comment fonctionne l'API / le Backend ?\",\n    bullet6text:\n      \"Le backend utilise des données archivées de l'époque où l'API Youtube renvoyait encore le nombre de dislike, il utilise aussi le nombre de like/dislike des utilisateurs de l'extension et une extrapolation. Dans un avenir proche, nous allons permettre aux créateurs de contenu de soumettre leurs nombres de dislike facilement et en toute sécurité et nous allons ajouter les données archivées d'ArchiveTeam (4,56 milliards de vidéos) à notre base de données actuelle. Vous pouvez également visionner une vidéo sur le sujet.\",\n    bullet7:\n      'Pourquoi le compteur de dislike affiche-t-il \"Désactivé par le créateur\" ?',\n    bullet7text:\n      'Parfois, une vidéo récemment publiée peut afficher \"Désactivé par le créateur\" même si le créateur ne l\\'a pas désactivé. Cela est dû à la façon dont nous détectons si les dislikes sont désactivés, cela devrait disparaître dans quelques heures ou en likant ou en dislikant la vidéo et en rafraîchissant la page (avec un peu de chance).',\n  },\n  donate: {\n    name: \"Donner\",\n    subtitle:\n      \"Vous pouvez soutenir nos efforts pour que l'internet reste libre en faisant un don !\",\n    patreon: \"Patreon\",\n    crypto: \"Crypto\",\n  },\n  links: {\n    name: \"Liens\",\n    title: \"Liens du Projet\",\n    subtitle: \"Liens vers le projet et ses développeurs\",\n    contact: \"Contactez-moi\",\n    translators: \"Traducteurs\",\n    coolProjects: \"Projets Cools\",\n    sponsorBlockDescription:\n      \"Ignorer les publicités intégrées (sponso) dans la vidéo\",\n    filmotDescription: \"Rechercher des vidéos YouTube par sous-titres\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"n’est pas pris en charge.\",\n    considerUpgrade: \"Pensez à mettre à jour vers la dernière version.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/hu.ts",
    "content": "import { hu } from 'vuetify/src/locale';\n// By Gergely Pap\nexport default {\n    ...hu,\n    home: {\n        name: 'Főoldal',\n        title: 'Return YouTube Dislike',\n        subtitle:\n            'Bővítmény és API, ami visszahozza a YouTube dislike számlálót.',\n        ukraine: 'Támogatjuk Ukrajnát',\n        sponsors: 'Szponzorok',\n        becomeSponsor: 'Legyél a szponzorunk',\n    },\n    install: {\n        name: 'Telepítés',\n        title: 'Válassz platformot!',\n        subtitle: 'Elérhető Firefoxhoz és Chromium böngészőkhöz',\n        title2: 'Egyéb platformok',\n        subtitle2:\n            'Ha a böngésződ még nem támogatott, próbáld ezt a UserScriptet',\n        title3: 'Third Party implementációk',\n        subtitle3:\n            'Nem vállalunk érte felelősséget, használd saját belátásod szerint',\n        firefox: 'Firefox',\n        chrome: 'Chrome',\n        edge: 'Edge',\n        opera: 'Opera',\n        brave: 'Brave',\n        userscript: 'Felhasználói szkript',\n        tampermonkey: 'Tampermonkey',\n        androidReVanced: 'Android - ReVanced',\n        androidTubular: 'Android (Tubular – a NewPipe forkja)',\n        iosJailbroken: 'iOS (jailbreakelt)',\n        iosUYouPlus: 'iOS (uYou+)',\n    },\n    api: {\n        name: 'API',\n        title: 'Üdv a hivatalos RYD dokumentáción!',\n        subtitle: 'A kezdéshez válassz egy szekciót a menüből!',\n        rights: {\n            title: 'Felhasználási jogok',\n            subtitle:\n                'Ennek az API-nak a saját felhasználását az alábbiak mellett engedélyezzük:',\n            bullet1: 'Forrásmegjelölés: ',\n            bullet1text:\n                'Ezt a projektet jól láthatóan fel kell tüntetni egy repóra vagy a returnyoutubedislike.com oldalra mutató linkkel.',\n            bullet2: 'Rate Limiting: ',\n            bullet2text:\n                'Kliensoldalon gyakoriság-korlátozás van beállítva percenként 100, naponta maximum 10 000 lekérésre. Túllépés esetén 429 státuszkód jelzi, hogy az alkalmazásod nem küldhet több lekérést.',\n        },\n        url: {\n            title: 'URL információ',\n            subtitle: 'Az API ezen a base URL-en érhető el: ',\n        },\n        endpoints: {\n            title: 'Elérhető endpointok',\n            subtitle: 'Az elérhető endpointok listája itt található: ',\n        },\n        fetching: {\n            title: 'Tutorial egy lekéréshez',\n            subtitle:\n                'Példa egy adott YouTube videó ID adatainak lekéréséhez: ',\n            title2: 'Példa lekérés: ',\n            url: 'Lekérés URL: ',\n            method: 'Lekérés Method: ',\n            headers: 'Header: ',\n            response: 'Válasz: ',\n            error1: 'Nem létező YouTube ID esetén 404 \"Not Found\" válasz érkezik.',\n            error2: 'Rossz formátumú YouTube ID esetén 400 \"Bad Request\" választ küldünk.',\n        },\n    },\n    help: {\n        name: 'Segítség',\n        title: 'Hibaelhárítás',\n        bullet1:\n            'Győződj meg, hogy a bővítmény legújabb verziója van telepítve, ami ',\n        bullet11: 'jelenleg.',\n        bullet2:\n            'Próbáld meg újratelepíteni a bővítményt, majd újraindítani a böngészőt (az összes aktív ablakot, nem csak egy tabot).',\n        bullet3: 'Ellenőrizd ezt a linket: ',\n        bullet31: 'ezt a szöveget kellene látnod: ',\n        bullet4: 'Ha a fentiek közül egyik sem segít, jelentsd a problémát a',\n        bullet41: 'channelen a Discord szerverünkön: ',\n        bullet4a:\n            'Add meg az operációs rendszered, illetve a böngésződ nevét és verzióját!',\n        bullet4b:\n            'Készíts screenshotot a problémáról (pl. a YouTube videó oldala) úgy, hogy nyitva van a konzol (ehhez nyomd meg az ',\n        bullet4b1: ' billentyűt)! Példa:',\n        bullet4c: 'Készíts screenshotot a bővítmények oldalról!',\n        bullet4c1: 'A bővítmények listájához írd be a címsorba: ',\n        firefox: 'Firefox esetén',\n        chrome: 'Chrome, Edge, Brave, Opera és Vivaldi esetén',\n        detected: 'Észlelt:',\n        altExampleScreenshot: 'példa képernyőkép',\n    },\n    faq: {\n        name: 'GYIK',\n        title: 'Gyakran Ismételt Kérdések',\n        subtitle: 'Lenne még kérdésed? Csatlakozz a Discordunkhoz!',\n        bullet1: 'Honnan szerzi a bővítmény az adatokat?',\n        bullet1text:\n            'A YouTube dislike API leállítása előtt archivált adatok és a bővítményt használók adatainak kombinációjából.',\n        bullet2: 'Miért nem frissül a dislike számláló?',\n        bullet2text:\n            'Alapból a dislike-ok cache-elve vannak, és nem frissülnek túl gyakran. Ez a videó népszerűségétől függően változhat, de maximum pár órán vagy napon belül frissülnek az értékek.',\n        bullet3: 'Hogyan működik ez az egész?',\n        bullet3text:\n            'A bővítmény begyűjti az éppen megnyitott videó ID-ját, és a saját API-nkon keresztül lekéri a dislike számot (illetve mást is, például a megtekintések és like-ok számát). Ezután megjeleníti az oldalon a dislike számlálót és az arányt. Ha like-olsz vagy dislike-olsz egy videót, azt az adatbázisunk eltárolja, hogy ki tudjuk következtetni a pontos értékeket.',\n        bullet4: 'Megoszthatom a saját videóim dislike számait veletek?',\n        bullet4text:\n            'Hamarosan. Vizsáljuk egy Oauth vagy hasonló, csak olvasható, korlátozott API használatának lehetőségét, hogy a tartalomkészítők a valós dislike számaikat is meg tudják osztani.',\n        bullet5: 'Milyen adatot gyűjtötök és hogyan kezelitek azokat?',\n        bullet5text:\n            'A bővítmény csak olyan adatot gyűjt, ami a megfelelő működéshez nélkülözhetetlen, ilyen az IP cím és az éppen megtekintett videó ID-ja. Soha, semmilyen adatot nem adunk el harmadik fél számára. Ha szeretnél többet megtudni az adatkezelésünkről és a biztonságról, nézd meg a <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">Security GYIK-et</a>!',\n        bullet6: 'Hogyan működik az API/Backend?',\n        bullet6text:\n            'A backend a YouTube API változása előtt archivált adatokat és a bővítményt használók like/dislike adatait használja fel egy extrapolált értékhez. Hamarosan támogatni fogjuk, hogy a tartalomkészítők könnyen és biztonságosan beküldhessék a pontos dislike számaikat, illetve az ArchiveTeam által archivált (4,56 milliárd videót tartalmazó) adathalmazt is felvisszük az adatbázisunkba. Erről egy videót is megtekinthetsz.',\n        bullet7: \"Miért mutatja a számláló, hogy 'DISLIKES DISABLED'?\",\n        bullet7text:\n            \"Egy nemrég feltöltött videónál néha előfordulhat a 'DISLIKES DISABLED' üzenet akkor is, ha a készítője nem kapcsolta ki azt. Ennek köze van ahhoz, ahogyan a kikapcsolt dislike-okat ellenőrizzük, de pár órán belül el kell tűnnie, vagy ha nem, like-olás vagy dislike-olás utáni oldalfrissítésre (remélhetőleg).\",\n    },\n    donate: {\n        name: 'Támogatás',\n        subtitle:\n            'Támogathatod a szabad internetért folytatott erőfeszítéseinket egy adománnyal.',\n        patreon: 'Patreon',\n        crypto: 'Kripto',\n    },\n    links: {\n        name: 'Linkek',\n        title: 'Projekt linkek',\n        subtitle: 'Linkek a projekthez és a fejlesztőkhöz',\n        contact: 'Kapcsolat',\n        translators: 'Fordítók',\n        coolProjects: 'Hasonló projektek',\n        sponsorBlockDescription: 'Átugorja a videókba épített reklámokat.',\n        filmotDescription: 'YouTube videók keresése felirat alapján.',\n        github: 'GitHub',\n        discord: 'Discord',\n    },\n    layout: {\n        notSupported: 'nem támogatott.',\n        considerUpgrade: 'Fontold meg a frissítést a legújabb verzióra.',\n    },\n};\n"
  },
  {
    "path": "Website/_locales/id.ts",
    "content": "import { id } from \"vuetify/src/locale\";\n\nexport default {\n  ...id,\n  home: {\n    name: \"Beranda\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"Extension browser dan API yang memunculkan jumlah dislike di Youtube\",\n    ukraine: \"Dukung Ukraina\",\n    sponsors: \"Sponsor\",\n    becomeSponsor: \"Jadilah sponsor kami\",\n  },\n  install: {\n    name: \"Install\",\n    title: \"Pilih Platform\",\n    subtitle: \"Tersedia untuk Firefox dan semua browser Chromium\",\n    title2: \"Platform Lainnya\",\n    subtitle2: \"Jika kamu menggunakan browser yang belum support, coba gunakan UserScript\",\n    title3: \"Implementasi Pihak Ketiga\",\n    subtitle3: \"Kami tidak akan bertanggung jawab, gunakan dengan risiko sendiri\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Userscript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - fork NewPipe)\",\n    iosJailbroken: \"iOS (jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Selamat datang di dokumentasi ofisial RYD!\",\n    subtitle: \"Mula-mula, pilih bagian dari menu.\",\n    rights: {\n      title: \"Hak Penggunaan\",\n      subtitle:\n        \"Penggunaan pada pihak ketiga terhadap API ini diizinkan dengan beberapa batasan berikut:\",\n      bullet1: \"Pereferensian: \",\n      bullet1text:\n        \"Proyek ini harus jelas direferensikan menggunakan link ke repo ini atau ke returnyoutubedislike.com\",\n      bullet2: \"Batas Penggunaan: \",\n      bullet2text:\n        \"Terdapat batas penggunaan pada setiap client yaitu 100 per menit dan 10,000 per hari. Jika lebih dari ini, akan ada kode status 429 yang menandakan kamu harus berhenti menggunakannya\",\n    },\n    url: {\n      title: \"Informasi URL\",\n      subtitle: \"APInya bisa diakses melalui URL berikut: \",\n    },\n    endpoints: {\n      title: \"Endpoint\",\n      subtitle: \"Daftar semua endpoint yang ada: \",\n    },\n    fetching: {\n      title: \"Tutorial Fetching Sederhana\",\n      subtitle: \"Contoh untuk melakukan voting terhadap suatu video Youtube menggunakan id: \",\n      title2: \"Contoh Request: \",\n      url: \"URL Request: \",\n      method: \"Metode Request: \",\n      headers: \"Header: \",\n      response: \"Respon: \",\n      error1: 'Akan muncul kode status 404 \"Not Found\" jika Youtube id tidak ditemukan',\n      error2:\n        'Akan muncul kode status 400 \"Bad Request\" jika Youtube id memiliki format yang salah',\n    },\n  },\n  help: {\n    name: \"Bantuan\",\n    title: \"Troubleshooting\",\n    bullet1: \"Pastikan kamu memiliki versi extension terbaru, \",\n    bullet11: \"sekarang\",\n    bullet2:\n      \"Coba hapus extension dan install ulang , lalu restart semua browser yang terbuka\",\n    bullet3: \"Pastikan link berikut terbuka: \",\n    bullet31: \"kamu seharusnya dapat melihat teks berikut: \",\n    bullet4: \"Jika petunjuk sebelumnya masih belum membantu - laporkan masalah ini di\",\n    bullet41: \"pada\",\n    bullet4a: \"Beritahukan Sistem Operasi, Nama Browser, dan Versi Browser\",\n    bullet4b:\n      \"Tangkap layar halaman yang bermasalah (seperti halaman video Youtube) dengan membuka console (tekan \",\n    bullet4b1: \") - contoh tangkapan layar seperti dibawah ini.\",\n    bullet4c:\n      \"Tangkap layar halaman extension browser kamu dengan extension yang telah terinstall.\",\n    bullet4c1: \"Untuk melihat extension, masukkan ini pada address bar: \",\n    firefox: \"untuk Firefox\",\n    chrome: \"untuk Chrome, Edge, Brave, Opera, dan Vivaldi\",\n    detected: \"Terdeteksi:\",\n    altExampleScreenshot: \"contoh tangkapan layar\",\n  },\n  faq: {\n    name: \"Pertanyaan\",\n    title: \"Pertanyaan yang Sering Ditanyakan\",\n    subtitle: \"Masih punya pertanyaan? Silakan gabung Discord!\",\n    bullet1: \"Darimana extension ini mendapatkan datanya?\",\n    bullet1text:\n      \"Kombinasi data arsip dislike dari API ofisial Youtube sebelum dihapus, dan perkiraan perilaku pengguna extension.\",\n    bullet2: \"Mengapa jumlah dislike video tidak update?\",\n    bullet2text:\n      \"Untuk sekarang, data dislike video di cache dan tidak selalu update. Bervariasi tergantung pada popularitas video dan dapat memakan waktu beberapa jam sampai beberapa hari untuk melakukan update.\",\n    bullet3: \"Bagaimana cara kerjanya?\",\n    bullet3text:\n      \"Extension mengumpulkan id dari video yang sedang ditonton, mengambil data dislike (dan lainnya seperti jumlah views, likes, dll) menggunakan API kami. Lalu extension akan menampilkan jumlah dislike dan rasio pada halaman. Jika kamu melakukan like atau dislike pada suatu video, datanya disimpan ke database sehingga data dislike yang lebih akurat akan dapat diperkirakan.\",\n    bullet4: \"Bisakah saya membagikan jumlah dislike saya kepada kalian?\",\n    bullet4text:\n      \"Nanti. Kami sedang mencari tahu Oauth atau perbedaan antara API yang hanya bisa dibaca dengan scope terbatas sehingga pada creator bisa membagikan jumlah dislike mereka yang terverifikasi.\",\n    bullet5: \"Data apa saja yang kalian ambil dan apa yang terjadi kepada data tersebut?\",\n    bullet5text:\n      'Extension hanya mengambil data yang diperlukan untuk berjalan seperti seharusnya, seperti alamat IP atau id dari video yang sedang ditonton. Tidak ada satupun data yang akan dijual ke pihak ketiga. Jika kamu mau tahu lebih lanjut bagaimana kami menangani keamanan dan privasi, tolong cek <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQid.md\">pertanyaan mengenai keamanan</a>.',\n    bullet6: \"Bagaimana cara kerja API/backend?\",\n    bullet6text:\n      \"Backend menggunakan data arsip ketika Youtube masih memberikan data jumlah dislike, jumlah like/dislike dari pengguna extension, dan perkiraan. Pada waktu dekat kami akan mengizinkan creator untuk mengirimkan data jumlah dislike mereka secara mudah dan aman dan kami akan menambahkan data arsip dari ArchiveTeam (4.56 miliar video) kepada database kami. Kamu juga dapat melihat video pada topik.\",\n    bullet7: \"Mengapa jumlah dislike memunculkan kata 'DISLIKES DISABLED'?\",\n    bullet7text:\n      \"Kadang-kadang video yang baru saja diupload akan memunculkan 'DISLIKES DISABLED' walaupun creatornya belum mematikannya. ini dikarenakan bagaimana cara kami melakukan pengecekan terhadap dislike yang dimatikan, ini akan hilang dalam beberapa jam atau dengan cara memberi like/dislike kepada video tersebut dan merefresh halamannya (semoga saja berhasil).\",\n  },\n  donate: {\n    name: \"Donasi\",\n    subtitle:\n      \"Kamu dapat mendukung effort kami dengan donasi kalian!\",\n    patreon: \"Patreon\",\n    crypto: \"Kripto\",\n  },\n  links: {\n    name: \"Link\",\n    title: \"Link Proyek\",\n    subtitle: \"Link menuju proyek dan para developer\",\n    contact: \"Kontak Kami\",\n    translators: \"Penerjemah\",\n    coolProjects: \"Proyek Keren Lainnya\",\n    sponsorBlockDescription: \"Lewati iklan yang ada pada video\",\n    filmotDescription: \"Mencari video Youtube menggunakan subtitle\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"tidak didukung.\",\n    considerUpgrade: \"Pertimbangkan untuk memperbarui ke versi terbaru.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/it-IT.ts",
    "content": "import { en } from \"vuetify/src/locale\";\n\nexport default {\n  ...en,\n  home: {\n    name: \"Home\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"Estensione per browser e un API che ti mostra il numero di Non mi piace su YouTube\",\n    ukraine: \"Sostieni l'Ucraina\",\n    sponsors: \"Sponsor\",\n    becomeSponsor: \"Diventa nostro sponsor\",\n  },\n  install: {\n    name: \"Installa\",\n    title: \"Seleziona la tua piattaforma\",\n    subtitle: \"Disponibile per Firefox e per tutti i browser Chromium\",\n    title2: \"Altre piattaforme\",\n    subtitle2: \"Se il tuo browser non è ancora supportato, prova questo script utente\",\n    title3: \"Implementazioni di terze parti\",\n    subtitle3: \"Nessuna responsabilità da parte nostra, usa a tuo rischio e pericolo\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Script utente\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - un fork di NewPipe)\",\n    iosJailbroken: \"iOS (con jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Ecco a te i documenti RYD ufficiali!\",\n    subtitle: \"Per iniziare, seleziona una sezione dal menu.\",\n    rights: {\n      title: \"Diritti di utilizzo\",\n      subtitle:\n        \"L'utilizzo di questo API da parte di terzi è consentito secondo le seguenti restrizioni\",\n      bullet1: \"Attribuzione: \",\n      bullet1text:\n        \"Questo progetto deve venire chiaramente attribuito via un link a questa repo o a returnyoutubedislike.com\",\n      bullet2: \"Limiti di richieste: \",\n      bullet2text:\n        \"I limiti per il numero di richeste per ogni client sono di 100 al minuto o 10.000 al giorno. Questo comporterà un codice 429 che intima all'applicazione di interrompere le richieste.\",\n    },\n    url: {\n      title: \"Informazioni URL\",\n      subtitle: \"L'API è accesssibile dal seguente URL base: \",\n    },\n    endpoints: {\n      title: \"Endpoint disponibili\",\n      subtitle: \"È disponibile una lista di endpoint disponibili qui: \",\n    },\n    fetching: {\n      title: \"Tutorial per richiesta basica\",\n      subtitle: \"Esempio per ottenere i voti di un dato ID video di YouTube: \",\n      title2: \"Esempio richiesta: \",\n      url: \"URL richiesta: \",\n      method: \"Metodo richiesta: \",\n      headers: \"Intestazioni: \",\n      response: \"Risposta: \",\n      error1: 'Un ID YouTube non valido restituirà un codice 404 \"Not Found\"',\n      error2:\n        'Un ID YouTube formattato incorrettamente restituirà 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Aiuto\",\n    title: \"Risoluzione problemi\",\n    bullet1: \"Assicurati di aver installato l'ultima versione dell'estensione, \",\n    bullet11: \"adesso\",\n    bullet2:\n      \"Prova rimuovendo l'estensione e installandola di nuovo, poi riavvia il browser (tutte le finestre attive, non solo un pannello)\",\n    bullet3: \"Assicurati che questo link si apra: \",\n    bullet31: \"dovresti vedere solo testo: \",\n    bullet4: \"Se non funziona nulla di tutto ciò, segnala il tuo problema in\",\n    bullet41: \"nel nostro\",\n    bullet4a: \"Dicci il tuo sistema operativo, nome del browser e versione del browser\",\n    bullet4b:\n      \"Fai uno screenshot della pagina con il problema (es. pagina video di YouTube) con la console aperta (premi \",\n    bullet4b1: \"), screenshot di esempio in basso.\",\n    bullet4c:\n      \"Fai uno screenshot della pagina delle estensioni del tuo browser con l'estensione installata.\",\n    bullet4c1: \"Per vedere le estensioni inserisci questo nella barra di ricerca: \",\n    firefox: \"per Firefox\",\n    chrome: \"per Chrome, Edge, Brave, Opera e Vivaldi\",\n    detected: \"Rilevato:\",\n    altExampleScreenshot: \"screenshot di esempio\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Domande frequenti\",\n    subtitle: \"Hai ancora domande? Unisciti al nostro Discord!\",\n    bullet1: \"Da dove ottiene i propri dati l'estensione?\",\n    bullet1text:\n      \"Una combinazione di dati archiviati da prima della disattivazione dell'API ufficiale di YouTube per i Non mi piace e di dati estrapolati dall'utilizzo dell'estensione da parte degli utenti.\",\n    bullet2: \"Perché il contatore dei Non mi piace non si aggiorna?\",\n    bullet2text:\n      \"Adesso i Non mi piace sono precaricati e non vengono aggiornati spesso. Dipende dalla popolarità del video, ma può volerci da qualche ora a qualche giorno per aggiornarsi.\",\n    bullet3: \"Come funziona?\",\n    bullet3text:\n      \"L'estensione legge l'ID del video che stai guardando e ottiene il numero di Non mi piace (e altri parametri come visualizzazioni, Mi piace, ecc.) usando il nostro API. L'estensione, quindi, mostra il numero di Non mi piace e il rapporto sulla pagina. Se metti Mi piace o Non mi piace a un video, questo viene registrato e inviato al database così da estrapolare un numero di Non mi piace accurato.\",\n    bullet4: \"Posso condividere il mio numero di Non mi piace con voi?\",\n    bullet4text:\n      \"In arrivo a breve. Stiamo valutando l'utilizzo di Oauth o di un altro API in sola lettura con un'estensione limitata per permettere ai creatori di condividere la verificabilità del loro conteggio di Non mi piace.\",\n    bullet5: \"Che dati raccogliete e come sono trattati?\",\n    bullet5text:\n      'L\\'estensione raccoglie solo i dati strettamente necessari per funzionare in modo corretto, come l\\'indirizzo IP o l\\'ID del video che stai guardando. I tuoi dati non verranno mai venduti a terze parti. Se vuoi sapere di più su come gestiamo la sicurezza e la privacy dai un\\'occhiata al nostro <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">security FAQ</a>.',\n    bullet6: \"Come funziona l'API/backend?\",\n    bullet6text:\n      \"Il backend utilizza dati archiviati da quando l'API di YouTube restituiva ancora il numero di Non mi piace e dati sui Mi Piace/Non mi piace estrapolati dall'utilizzo dell'estensione da parte degli utenti. Nel prossimo futuro permetteremo ai creatori di contenuti di presentare il loro numero di Non mi piace in modo semplice e sicuro e aggiungeremo i dati archiviati da ArchiveTeam (4,56 miliardi di video) al nostro database corrente. Puoi anche guardare un video a riguardo.\",\n    bullet7: \"Perché il conteggio dei Non mi piace dice 'NON MI PIACE DISATTIVATI'?\",\n    bullet7text:\n      \"A volte un video caricato da poco potrebbe mostrare 'MI PIACE DISATTIVATI' anche se il creatore non li ha disattivati, ciò è dato da come rileviamo se i Non mi piace sono disattivati, dovrebbe sparire da solo in un paio d'ore o dopo aver messo Mi piace o Non mi piace al video e riaricato la pagina (si spera).\",\n  },\n  donate: {\n    name: \"Dona\",\n    subtitle:\n      \"Puoi sostenere il nostro lavoro per mantenere l'internet libero con una donazione!\",\n    patreon: \"Patreon\",\n    crypto: \"Cripto\",\n  },\n  links: {\n    name: \"Link\",\n    title: \"Link progetti\",\n    subtitle: \"Link al progetto e ai suoi sviluppatori\",\n    contact: \"Contattami\",\n    translators: \"Traduttori\",\n    coolProjects: \"Progetti interessanti\",\n    sponsorBlockDescription: \"Salta gli annunci integrati nei video\",\n    filmotDescription: \"Cerca video su YouTube tramite i sottotitoli\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"non è supportato.\",\n    considerUpgrade: \"Considera l’aggiornamento all’ultima versione.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/ja.ts",
    "content": "import { ja } from \"vuetify/src/locale\";\n\nexport default {\n  ...ja,\n  home: {\n    name: \"ホーム\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"YouTubeの低評価数の表示を復元するブラウザ拡張機能とAPI\",\n    ukraine: \"ウクライナをサポート\",\n    sponsors: \"スポンサー\",\n    becomeSponsor: \"スポンサーになる\",\n  },\n  install: {\n    name: \"インストール\",\n    title: \"プラットフォームの選択\",\n    subtitle: \"FirefoxとすべてのChromiumブラウザに対応しています。\",\n    title2: \"その他のプラットフォーム\",\n    subtitle2:\n      \"未対応のブラウザをお使いの場合は、以下のUserScriptをお試しください。\",\n    title3: \"サードパーティーによる実装\",\n    subtitle3:\n      \"開発者では責任を負いかねますので、ご自身の判断にてご利用ください。\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"ユーザースクリプト\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android（Tubular - NewPipeのフォーク）\",\n    iosJailbroken: \"iOS（脱獄）\",\n    iosUYouPlus: \"iOS（uYou+）\",\n  },\n  api: {\n    name: \"API\",\n    title: \"RYD公式ドキュメントへようこそ!\",\n    subtitle: \"まず、メニューからセクションを選択してください。\",\n    rights: {\n      title: \"使用権\",\n      subtitle:\n        \"このオープンAPIを第三者が使用することは、以下の制限付きで許可されています：\",\n      bullet1: \"属性：\",\n      bullet1text:\n        \"このレポ、もしくは returnyoutubedislike.com へのリンクのどちらかによって、明確に帰属させる必要があります。\",\n      bullet2: \"通信量制限：\",\n      bullet2text:\n        \"クライアントごとに、1分あたり100・1日あたり10,000という通信量制限が設けられています。これを超えた場合には、アプリケーションに通信を控えるよう促すステータスコード 429 を返します。\",\n    },\n    url: {\n      title: \"URL情報\",\n      subtitle: \"APIへのアクセスは、以下のベースURLから可能です：\",\n    },\n    endpoints: {\n      title: \"利用可能なエンドポイント\",\n      subtitle: \"利用可能なエンドポイントの一覧はこちらでご覧いただけます：\",\n    },\n    fetching: {\n      title: \"基本的なフェッチ操作のチュートリアル\",\n      subtitle: \"以下のYouTube IDから評価数を取り出した場合の例です：\",\n      title2: \"リクエスト例：\",\n      url: \"リクエストURL：\",\n      method: \"リクエスト方法：\",\n      headers: \"ヘッダー：\",\n      response: \"レスポンス：\",\n      error1:\n        'YouTube IDが無効な場合、ステータスコード 404 \"Not Found\" が返されます。',\n      error2:\n        'YouTube IDのフォーマットが正しくない場合、ステータスコード 400 \"Bad Request\" が返されます。',\n    },\n  },\n  help: {\n    name: \"ヘルプ\",\n    title: \"トラブルシューティング\",\n    bullet1: \"最新バージョンの拡張機能\",\n    bullet11: \"がインストールされていることを確認してください。\",\n    bullet2:\n      \"拡張機能を削除して再インストールし、すべてのウィンドウを閉じた上でブラウザを再起動してください。\",\n    bullet3: \"以下のリンクが開くことを確認してください：\",\n    bullet31: \"以下のプレーンテキストが表示されます：\",\n    bullet4:\n      \"上記で問題が解決しない場合、以下のチャンネルにて問題を報告してください。\",\n    bullet41: \"参照：\",\n    bullet4a: \"お使いのOS、ブラウザ名とバージョンを教えてください。\",\n    bullet4b: \"コンソールを開き（\",\n    bullet4b1:\n      \"キー）、問題のあるページ（例：YouTubeの動画ページ）のスクリーンショットを撮影します。\",\n    bullet4c:\n      \"拡張機能がインストールされているブラウザの拡張機能ページのスクリーンショットを撮影します。\",\n    bullet4c1: \"拡張機能ページを表示するには、以下のように入力してください：\",\n    firefox: \"：Firefox\",\n    chrome: \"：Chrome, Edge, Brave, Opera, Vivaldi\",\n    detected: \"検出:\",\n    altExampleScreenshot: \"例のスクリーンショット\",\n  },\n  faq: {\n    name: \"Q&A\",\n    title: \"よくある質問\",\n    subtitle:\n      \"他にも何か質問がありますか？我々のDiscordにお気軽にご参加ください。\",\n    bullet1: \"拡張機能のデータはどこから取得しているのですか？\",\n    bullet1text:\n      \"YouTube公式の低評価APIが停止する前に保存したデータと、拡張機能ユーザーの高評価/低評価数を元にした推定値を組み合わせて取得しています。\",\n    bullet2: \"低評価数が更新されないのはなぜですか？\",\n    bullet2text:\n      \"現在、低評価数はデータベース化されているため、あまり頻繁に更新されません。動画の再生数によって異なりますが、更新には数時間から数日かかることがあります。\",\n    bullet3: \"どのような仕組みで動作していますか？\",\n    bullet3text:\n      \"まず、視聴中の動画のID を収集し、API を使用して低評価数、再生数、高評価などの項目を取得します。次に、ページ上に低評価数と比率を表示します。動画に高評価や低評価があると、それが記録されてデータベースに蓄積されるため、正確な低評価数を推定することができます。\",\n    bullet4: \"低評価数を共有することはできますか？\",\n    bullet4text:\n      \"近日中に公開予定です。機能の実装のために、Oauth または範囲を限定した別の読み取り専用APIを使用することを検討しています。\",\n    bullet5: \"どのようなデータを収集し、どのように取り扱っていますか？\",\n    bullet5text:\n      '本拡張機能では、IPアドレスや視聴している動画のIDなど、正しく機能するために必要なデータのみを収集しています。あなたのデータは、第三者に販売されることは決してありません。セキュリティとプライバシーの取り扱いについてもっと知りたい場合には、 <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">セキュリティFAQ</a> をご参照ください。 ',\n    bullet6: \"API/バックエンドはどのような仕組みで動作していますか？\",\n    bullet6text:\n      \"バックエンドは、YouTube公式の低評価APIが停止する前に保存したデータと拡張機能ユーザーの高評価/低評価数を元にした推定値を組み合わせて取得しています。近い将来、コンテンツ制作者が簡単かつ安全に低評価数を提供できるようにし、Archive Team のアーカイブデータ（45億6000万動画）も現在のデータベースに追加する予定です。また、このトピックに関するビデオもご覧いただけます。\",\n    bullet7: \"低評価数に「投稿者により無効化」と表示されるのはなぜですか？\",\n    bullet7text:\n      \"最近投稿された動画では、投稿者によって無効にされていなくても「投稿者により無効化」と表示されることがあります。これは、評価数の表示が無効になっているかどうかを検出するシステムによる副作用で、数時間後、または動画に高評価ないし低評価をしてページを更新すると消えるものだと思われます。\",\n  },\n  donate: {\n    name: \"寄付\",\n    subtitle:\n      \"インターネットの自由を守るための我々の活動を、寄付によって応援してください！\",\n    patreon: \"Patreon\",\n    crypto: \"暗号資産\",\n  },\n  links: {\n    name: \"リンク\",\n    title: \"プロジェクトリンク集\",\n    subtitle: \"プロジェクトおよび開発者へのリンク\",\n    contact: \"お問い合わせ先\",\n    translators: \"翻訳者\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"はサポートされていません。\",\n    considerUpgrade: \"最新バージョンへのアップグレードを検討してください。\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/ko.ts",
    "content": "import { ko } from \"vuetify/src/locale\";\n//libaraldev\nexport default {\n  ...ko,\n  home: {\n    name: \"홈\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"유튜브에서 싫어요를 표시해주는 브라우저 확장 프로그램 및 API\",\n    ukraine: \"우크라이나 지원\",\n    sponsors: \"스폰서\",\n    becomeSponsor: \"스폰서가 되어 주세요\",\n  },\n  install: {\n    name: \"설치\",\n    title: \"플랫폼 선택\",\n    subtitle: \"파이어폭스와 모든 크로미엄 기반 브라우저에서 사용 가능\",\n    title2: \"다른 플랫폼\",\n    subtitle2:\n      \"당신의 브라우저가 아직 지원하지 않는 경우, 이 유저스크립트를 시도할 수 있습니다\",\n    title3: \"서드파티 구현\",\n    subtitle3: \"우리 측의 책임이 없으며, 사용자의 책임하에 사용하십시오\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"사용자 스크립트\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - NewPipe 포크)\",\n    iosJailbroken: \"iOS (탈옥)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"공식 RYD 문서에 오신 것을 환영합니다!\",\n    subtitle: \"시작하려면 메뉴에서 섹션을 선택하세요.\",\n    rights: {\n      title: \"사용 권한\",\n      subtitle:\n        \"이 오픈 API의 제 3자 사용은 다음 제한 사항들과 함꼐 허용됩니다:\",\n      bullet1: \"저작자 표시: \",\n      bullet1text:\n        \"이 프로젝트의 사용자는 저장소 또는 returnyoutubedislike.com에 대한 링크와 함께 명확하게 저자를 표기해야 합니다.\",\n      bullet2: \"속도 제한: \",\n      bullet2text:\n        \"하나의 클라이언트당 분당 100개와 하루당 10,000개의 요청 제한이 있습니다. 당신의 애플리케이션이 이를 초과할 경우 429 상태 코드를 반환할 것입니다.\",\n    },\n    url: {\n      title: \"URL 정보\",\n      subtitle: \"API는 다음 베이스 URL을 통해 액세스할 수 있습니다.: \",\n    },\n    endpoints: {\n      title: \"사용 가능한 엔드포인트\",\n      subtitle: \"사용 가능한 엔드포인트 리스트는 여기에서 사용 가능합니다: \",\n    },\n    fetching: {\n      title: \"기본 Fetching 튜토리얼\",\n      subtitle: \"주어진 유튜브 영상 ID에 대한 투표 수를 가져오는 예시: \",\n      title2: \"요청 예시: \",\n      url: \"요청 URL: \",\n      method: \"요청 메소드: \",\n      headers: \"헤더: \",\n      response: \"응답: \",\n      error1:\n        '유효하지 않은 유튜브 ID는 상태 코드 404 \"Not Found\"를 리턴합니다',\n      error2: '잘못된 포멧의 유튜브 ID는 400 \"Bad Request\"를 리턴합니다',\n    },\n  },\n  help: {\n    name: \"도움말\",\n    title: \"트러블슈팅\",\n    bullet1: \"지금 최신 버전이 설치되어 있는지 확인하세요. \",\n    bullet11: \"버전이 현재 최신 버전입니다.\",\n    bullet2:\n      \"확장 프로그램을 제거하고 재설치한 다음, 브라우저를 재시작 하세요(모든 활성 창을 말하며, 단지 하나의 탭을 말하는 것이 아닙니다)\",\n    bullet3: \"이 링크가 열리는지 확인하세요: \",\n    bullet31: \"평문이 표시되어야 합니다: \",\n    bullet4:\n      \"위의 내용이 도움이 되지 않는 경우, 우리의 디스코드에서 다음과 같은 채널에서 당신의 문제를 보고하세요.\",\n    bullet41: \"디스코드: \",\n    bullet4a: \"운영체제, 브라우저 이름과 브라우저 버전을 우리한테 말해주세요\",\n    bullet4b:\n      \"콘솔창과 함께 문제가 된 페이지(예시: 유튜브 영상 페이지)의 스크린샷을 찍으세요.(\",\n    bullet4b1: \"키를 누르세요) 아래는 스크린샷의 예시입니다.\",\n    bullet4c:\n      \"확장 브로그램이 설치된 당신의 브라우저의 확장 프로그램 페이지의 스크린샷을 촬영하세요.\",\n    bullet4c1: \"확장 프로그램들을 보려면 이것을 주소창에 입력하세요\",\n    firefox: \": 파이어폭스의 경우\",\n    chrome: \": 크롬, 엣지, 브레이브, 오페라, 그리고 비발디의 경우\",\n    detected: \"감지됨:\",\n    altExampleScreenshot: \"예시 스크린샷\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"자주 묻는 질문\",\n    subtitle:\n      \"여전히 의문점이 있으신가요? 우리의 디스코드에 무료로 가입하세요!\",\n    bullet1: \"확장 프로그램은 어디에서 데이터를 가져옵니까?\",\n    bullet1text:\n      \"공식 유튜브 싫어요 API가 닫히기 전에 보환된 데이터와 확장 프로그램 사용자 행동을 추정한 값의 조합입니다\",\n    bullet2: \"왜 싫어요 수가 업데이트 되지 않는거죠?\",\n    bullet2text:\n      \"현재 동영상 싫어요는 캐시되며 자주 업데이트 되지는 않습니다. 동영상의 인기도에 따라 다르지만, 업데이트되는 데 몇 시간에서 며칠이 걸릴 수 있습니다.\",\n    bullet3: \"어떻게 작동합니까?\",\n    bullet3text:\n      \"확장 프로그램은 시청 중인 동영상의 동영상 ID를 수집하며, 우리의 API를 사용하여 싫어요 수(및 조회수, 좋아요 등과 같은 기타 필드)를 가져옵니다. 그런 다음 확장 프로그램은 싫어요 수와 비율을 페이지에 표시합니다. 당신이 비디오에 좋아요나 싫어요를 할 경우, 그것은 기록되고 데이터베이스로 전송되어 정확한 싫어요 수를 추정할 수 있습니다.\",\n    bullet4: \"저의 싫어요 수를 공유할 수 있나요?\",\n    bullet4text:\n      \"곧 출시됩니다. 우리는 크리에이터가 검증이 된 싫어요 수를 공유할 수 있도록 Oauth 또는 제한된 범위의 다른 읽기 전용 API를 사용하는 방법을 검토하고 있습니다\",\n    bullet5: \"어떤 데이터를 수집하고 어떻게 처리합니까?\",\n    bullet5text:\n      '확장 프로그램은 제대로 작동하기 위해서 IP 주소 또는 당신이 본 영상의 ID 같은 순전히 필수적인 데이터만 수집합니다. 귀하의 데이터는 3자한테 판매되지 않습니다. 귀하가 우리가 어떻게 개인정보와 보안을 처리하는지 알고 싶다면 우리의 <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">보안 FAQ</a>를 확인하세요.',\n    bullet6: \"API/백엔드는 어떻게 작동하나요?\",\n    bullet6text:\n      \"백엔드는 유튜브 api가 여전히 싫어요 수를 반환할 때의 싫어요 수, 확장기능 사용자들의 좋아요/싫어요 수와 추정치로부터 보관된 데이터를 사용합니다. 가까운 미래에는 콘텐츠 제작자가 쉽고 안전하게 싫어요 수를 제출할 수 있게 될 것이며 우리는 ArchiveTeam의 보관 데이터(45억 6천만 동영상)를 현재 데이터베이스에 추가할 것입니다. 주제에 대한 비디오를 볼 수도 있습니다.\",\n    bullet7: \"싫어요 수에 '싫어요 비활성화됨'이 왜 표시됩니까?\",\n    bullet7text:\n      \"때때로 최근에 업로드한 동영상은 제작자가 비활성화하지 않았더라도 '싫어요 비활성화됨'으로 표시될 수 있습니다. 이는 싫어요가 비활성화되어 있는지 감지하는 방법 때문입니다. 몇 시간 내에 동영상에 좋아요 또는 싫어요 표시를 하거나 페이지를 새로고침을 하면(희망 사항) 사라집니다.\",\n  },\n  donate: {\n    name: \"기부\",\n    subtitle:\n      \"기부를 통해 인터넷의 자유를 유지하려는 우리의 노력을 지원할 수 있습니다!\",\n    patreon: \"Patreon\",\n    crypto: \"암호화폐\",\n  },\n  links: {\n    name: \"링크\",\n    title: \"프로젝트 링크\",\n    subtitle: \"프로젝트와 개발자들로 향하는 링크\",\n    contact: \"문의하기\",\n    translators: \"번역가들\",\n    coolProjects: \"Cool Projects\",\n    sponsorBlockDescription: \"동영상에 포함된 광고 건너뛰기\",\n    filmotDescription: \"자막으로 유튜브 동영상 검색\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"은(는) 지원되지 않습니다.\",\n    considerUpgrade: \"최신 버전으로 업데이트하는 것을 권장합니다.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/nl.ts",
    "content": "import { nl } from \"vuetify/src/locale\";\n\nexport default {\n  ...nl,\n  home: {\n    name: \"Home\",\n    title: \"Return YouTube Dislike\",\n    subtitle:\n      \"Browser-extensie en een API die je antipathieën op YouTube laat zien\",\n    ukraine: \"Support Ukraine\",\n    sponsors: \"Sponsors\",\n    becomeSponsor: \"Word onze sponsor\",\n  },\n  install: {\n    name: \"Installeren\",\n    title: \"Kies je platform\",\n    subtitle: \"Beschikbaar voor Firefox en alle Chromium-browsers\",\n    title2: \"Andere Platformen\",\n    subtitle2:\n      \"Als uw browser nog niet wordt ondersteund, probeer dan dit UserScript\",\n    title3: \"Implementaties van derden\",\n    subtitle3: \"Geen aansprakelijkheid aan onze kant, gebruik op eigen risico\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Gebruikersscript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - een fork van NewPipe)\",\n    iosJailbroken: \"iOS (jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Welkom bij de officiële RYD-documenten!\",\n    subtitle: \"Selecteer een sectie in het menu om aan de slag te gaan.\",\n    rights: {\n      title: \"Gebruiksrechten\",\n      subtitle:\n        \"Gebruik door derden van deze open API is toegestaan met de volgende beperkingen:\",\n      bullet1: \"Attributie: \",\n      bullet1text:\n        \"Dit project moet duidelijk worden toegeschreven met een link naar deze repo of een link om terug te keren naar youtubedislike.com\",\n      bullet2: \"Snelheidsbeperking: \",\n      bullet2text:\n        \"Er zijn per klant tarieflimieten in plaats van 100 per minuut en 10.000 per dag. Dit geeft een 429-statuscode terug die aangeeft dat uw toepassing moet worden uitgeschakeld\",\n    },\n    url: {\n      title: \"URL Informatie\",\n      subtitle: \"De API is toegankelijk via de volgende basis-URL: \",\n    },\n    endpoints: {\n      title: \"Beschikbare eindpunten\",\n      subtitle: \"Lijst met beschikbare eindpunten is hier beschikbaar: \",\n    },\n    fetching: {\n      title: \"Basiscursus ophalen\",\n      subtitle:\n        \"Voorbeeld om stemmen te krijgen voor een bepaalde YouTube-video-ID: \",\n      title2: \"Voorbeeld aanvraag: \",\n      url: \"Verzoek-URL: \",\n      method: \"Verzoekmethode:: \",\n      headers: \"Koppen: \",\n      response: \"Antwoord: \",\n      error1:\n        'Een ongeldige YouTube-ID retourneert statuscode 404 \"Niet gevonden\"',\n      error2:\n        'Een onjuist opgemaakte YouTube-ID retourneert 400 \"Slecht verzoek\"',\n    },\n  },\n  help: {\n    name: \"Help\",\n    title: \"Probleemoplossen\",\n    bullet1:\n      \"Zorg ervoor dat u de nieuwste versie van de extensie hebt geïnstalleerd, \",\n    bullet11: \"direct\",\n    bullet2:\n      \"Probeer de extensie te verwijderen en opnieuw te installeren, en start vervolgens de browser opnieuw (alle actieve vensters, niet slechts één tabblad)\",\n    bullet3: \"Zorg ervoor dat deze link opent: \",\n    bullet31: \"je zou platte tekst moeten zien: \",\n    bullet4: \"Als niets van het bovenstaande helpt - meld uw probleem dan in\",\n    bullet41: \"in onze\",\n    bullet4a: \"Vertel ons uw besturingssysteem, browsernaam en browserversie\",\n    bullet4b:\n      \"Maak een screenshot van de pagina met het probleem (d.w.z. de YouTube-videopagina) met de console open (druk op \",\n    bullet4b1: \") - voorbeeld screenshot hieronder.\",\n    bullet4c:\n      \"Maak een screenshot van de extensiepagina van uw browser waarop de extensie is geïnstalleerd.\",\n    bullet4c1: \"Om extensies te zien, plaats dit in de adresbalk: \",\n    firefox: \"voor Firefox\",\n    chrome: \"voor Chrome, Edge, Brave, Opera, Vivaldi\",\n    detected: \"Gedetecteerd:\",\n    altExampleScreenshot: \"voorbeeldscreenshot\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Veel Gestelde Vragen\",\n    subtitle:\n      \"Heeft u nog vragen? Voel je vrij om lid te worden van onze Discord!\",\n    bullet1: \"Waar haalt de extensie zijn gegevens vandaan?\",\n    bullet1text:\n      \"Een combinatie van gearchiveerde gegevens van voordat de officiële YouTube-dislike-API werd afgesloten, en geëxtrapoleerd gebruikersgedrag van extensies.\",\n    bullet2: \"Waarom wordt het aantal dislikes niet bijgewerkt?\",\n    bullet2text:\n      \"Op dit moment worden video's die niet leuk zijn in de cache opgeslagen en worden ze niet vaak bijgewerkt. Het varieert afhankelijk van de populariteit van een video, maar het kan een paar uur tot een paar dagen duren om te updaten.\",\n    bullet3: \"Hoe werkt dit?\",\n    bullet3text:\n      \"De extensie verzamelt de video-ID van de video die je aan het bekijken bent en haalt de afkeer (en andere velden zoals weergaven, vind-ik-leuks enz.) op met behulp van onze API. De extensie geeft vervolgens het aantal dislikes en de verhouding weer op de pagina. Als je een video leuk of niet leuk vindt, wordt deze opgenomen en naar de database gestuurd, zodat een nauwkeurige telling van de dislikes kan worden geëxtrapoleerd.\",\n    bullet4: \"Kan ik mijn niet leuk-teller met je delen?\",\n    bullet4text:\n      \"Binnenkort beschikbaar. We onderzoeken het gebruik van Oauth of een andere alleen-lezen API met een beperkte reikwijdte, zodat makers hun afkeer-aantallen verifieerbaarheid kunnen delen.\",\n    bullet5: \"Welke gegevens verzamelt u en hoe worden deze verwerkt?\",\n    bullet5text:\n      'De extensie verzamelt alleen gegevens die strikt noodzakelijk zijn om goed te kunnen functioneren, zoals het IP-adres of de ID van de video die u bekijkt. Geen van uw gegevens zal ooit worden verkocht aan derden. Als u meer wilt weten over hoe wij omgaan met beveiliging en privacy, bekijk dan onze <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">beveilings FAQ</a>.',\n    bullet6: \"Hoe werkt de API/Backend?\",\n    bullet6text:\n      \"De backend gebruikt gearchiveerde gegevens van toen de youtube-api nog steeds het aantal dislikes retourneerde, extensiegebruikers zoals het aantal likes/dislikes en extrapolatie. In de nabije toekomst zullen we het voor makers van inhoud mogelijk maken om eenvoudig en veilig hun afkeuren-aantal in te dienen en we zullen de gearchiveerde gegevens van ArchiveTeam (4,56 miljard video's) toevoegen aan onze huidige database. U kunt ook een video over het onderwerp bekijken.\",\n    bullet7:\n      \"Waarom wordt bij het aantal dislikes 'DISLIKES DISABLED' weergegeven?\",\n    bullet7text:\n      \"Soms kan een recent geüploade video 'DISLIKES UITGESCHAKELD' weergeven, zelfs als de maker dit niet heeft uitgeschakeld. Dit komt door de manier waarop we detecteren of dislikes zijn uitgeschakeld, het zou binnen een paar uur moeten verdwijnen of door de video leuk of niet leuk te vinden en de pagina vernieuwen (hopelijk).\",\n  },\n  donate: {\n    name: \"Doneer\",\n    subtitle:\n      \"Met een donatie kunt u onze inspanningen om het internet gratis te houden steunen!\",\n    patreon: \"Patreon\",\n    crypto: \"Crypto\",\n  },\n  links: {\n    name: \"Linken\",\n    title: \"Project Linken\",\n    subtitle: \"Linken naar het project en zijn ontwikkelaars\",\n    contact: \"Neem contact op met me\",\n    translators: \"Vertalers\",\n    coolProjects: \"Coole Projecten\",\n    sponsorBlockDescription: \"In video geïntegreerde advertenties overslaan\",\n    filmotDescription: \"Zoek YouTube-video's op ondertiteling\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"wordt niet ondersteund.\",\n    considerUpgrade: \"Overweeg te updaten naar de nieuwste versie.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/pl.ts",
    "content": "import { pl } from \"vuetify/src/locale\";\n// by itsbudyn#6502\nexport default {\n  ...pl,\n  home: {\n    name: \"Strona główna\",\n    title: \"Return YouTube Dislike\",\n    subtitle:\n      \"Rozszerzenie do przeglądarki i API pokazujące ilość łapek w dół na YouTube\",\n    ukraine: \"Wesprzyj Ukrainę\",\n    sponsors: \"Sponsorzy\",\n    becomeSponsor: \"Zostań naszym sponsorem\",\n  },\n  install: {\n    name: \"Instalacja\",\n    title: \"Wybierz swoją platformę\",\n    subtitle: \"Dostępne dla Firefox i wszystkich przeglądarek Chromium\",\n    title2: \"Inne platformy\",\n    subtitle2:\n      \"Jeżeli twoja przeglądarka nie jest wspierana, wypróbuj ten UserScript\",\n    title3: \"Implementacje od stron trzecich\",\n    subtitle3:\n      \"Nie ponosimy za nie odpowiedzialności, używasz na własne ryzyko\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Skrypt użytkownika\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular – fork NewPipe)\",\n    iosJailbroken: \"iOS (z jailbreakiem)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Witamy w oficjalnej dokumentacji RYD!\",\n    subtitle: \"Aby rozpocząć, wybierz sekcję z menu.\",\n    rights: {\n      title: \"Prawa do użytku\",\n      subtitle:\n        \"Używanie tego otwartego API jest dozwolone z następującymi ograniczeniami:\",\n      bullet1: \"Przypisanie: \",\n      bullet1text:\n        \"Ten projekt powinien być widocznie przypisany autorom za pomocą linku do tego repozytorium, albo do returnyoutubedislike.com\",\n      bullet2: \"Ograniczenia: \",\n      bullet2text:\n        \"Istnieją ograniczenia żądań dla klientów - 100 na minutę, oraz 10 000 na dzień. Przekroczenie będzie sygnowane zwrotem kodu 429 wskazujący, że Twoja aplikacja powinna przyhamować. \",\n    },\n    url: {\n      title: \"Informacje o URL\",\n      subtitle: \"API jest dostępne przez następujące bazowe URL: \",\n    },\n    endpoints: {\n      title: \"Dostępne endpointy\",\n      subtitle: \"Lista dostępnych endpointów znajduje się tutaj: \",\n    },\n    fetching: {\n      title: \"Poradnik - Podstawowe pobieranie\",\n      subtitle:\n        \"Przykład pozwalający otrzymać głosy z danego ID filmu na YouTube: \",\n      title2: \"Przykładowe żądanie: \",\n      url: \"URL żądania: \",\n      method: \"Metoda żądania: \",\n      headers: \"Nagłówki: \",\n      response: \"Odpowiedź: \",\n      error1: 'Nieprawidłowy ID filmu zwróci kod 404 \"Not Found\"',\n      error2:\n        'Nieprawidłowo sformatowany ID filmu zwróci kod 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Pomoc\",\n    title: \"Rozwiązywanie problemów\",\n    bullet1:\n      \"Upewnij się, że masz zainstalowaną najnowszą wersję rozszerzenia, \",\n    bullet11: \"obecnie.\",\n    bullet2:\n      \"Spróbuj usunąć rozszerzenie i zainstalować je ponownie, a następnie zrestartować przeglądarkę (wszystkie aktywne okna, nie tylko jedną kartę)\",\n    bullet3: \"Upewnij się, że ten link się otwiera: \",\n    bullet31: \"powinno się wyświetlić w czystym tekście: \",\n    bullet4: \"Jeśli nic powyżej nie pomoże - zgłoś problem na\",\n    bullet41: \"na naszym\",\n    bullet4a:\n      \"Powiedz z jakiego systemu operacyjnego korzystasz, oraz podaj nazwę i wersję przeglądarki\",\n    bullet4b:\n      \"Wykonaj zrzut ekranu problematycznej strony (tj. strony filmu na YouTube) z otwartą konsolą (naciśnij \",\n    bullet4b1: \") - przykładowy zrzut poniżej.\",\n    bullet4c:\n      \"Wykonaj zrzut ekranu strony z rozszerzeniami Twojej przeglądarki, wraz z zainstalowanym rozszerzeniem.\",\n    bullet4c1: \"Aby zobaczyć rozszerzenia, wklej do paska adresowego: \",\n    firefox: \"dla Firefox\",\n    chrome: \"dla Chrome, Edge, Brave, Opera oraz Vivaldi\",\n    detected: \"Wykryto:\",\n    altExampleScreenshot: \"przykładowy zrzut ekranu\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Często zadawane pytania\",\n    subtitle: \"Wciąż masz pytania? Zapraszamy na naszego Discorda!\",\n    bullet1: \"Skąd rozszerzenie otrzymuje swoje dane?\",\n    bullet1text:\n      \"Kombinacja danych zarchiwizowanych przed wyłączeniem oficjalnego API do łapek w dół, oraz ekstrapolowane zachowania użytkowników rozszerzenia.\",\n    bullet2: \"Dlaczego licznik łapek w dół się nie aktualizuje?\",\n    bullet2text:\n      \"Na chwilę obecną łapki w dół są buforowane i nie są często aktualizowane. Zależy to od popularności filmu, jednak może to zająć klika godzin lub kilka dni żeby licznik został zaktualizowany. \",\n    bullet3: \"Jak to działa?\",\n    bullet3text:\n      \"Rozszerzenie pobiera ID filmu, który oglądasz, a następnie używając naszego API pobiera liczbę łapek w dół (oraz inne pola, takie jak wyświetlenia, łapki w górę itd.). Rozszerzenie potem wyświetla liczbę łapek w dół oraz proporcje łapek na stronie. Jeżeli dasz filmowi łapkę w górę bądź w dół, ta akcja zostanie zarejestrowana i wysłana do bazy danych, aby dokonać ekstrapolacji dokładnej liczby łapek w dół.\",\n    bullet4: \"Czy mogę Wam udostępnić liczbę łapek w dół na moich filmach?\",\n    bullet4text:\n      \"Wkrótce. Przyglądamy się możliwościom użycia OAuth lub innego klucza API tylko do odczytu z ograniczonym zakresem, aby twórcy mogli udostępniać zweryfikowaną liczbę swoich łapek w dół.\",\n    bullet5: \"Jakie dane zbieracie i co z nimi robicie?\",\n    bullet5text:\n      'Rozszerzenie zbiera tylko dane wymagane do poprawnego działania, takie jak adres IP lub ID oglądanego filmu. Dane te nigdy nie będą sprzedane osobom trzecim. Jeżeli chcesz przeczytać więcej o tym, jak radzimy sobie z bezpieczeństwem i prywatnością, sprawdź nasze <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">FAQ bezpieczeństwa</a>.',\n    bullet6: \"Jak działa API/Backend?\",\n    bullet6text:\n      \"Backend używa danych zarchiwizowanych w momencie, gdy API YouTube wciąż zwracało liczby łapek w dół, oraz danych zebranych i ekstrapolowanych od użytkowników rozszerzenia. W niedalekiej przyszłości pozwolimy twórcom łatwo i bezpieczne udostępniać swoje liczniki łapek w dół oraz będziemy dodawać dane zarchiwizowane przez ArchiveTeam (4,56 miliardów filmów) do naszej bazy danych. Można też obejrzeć film na ten temat. \",\n    bullet7: \"Dlaczego licznik łapek w dół pokazuje 'WYŁĄCZONE PRZEZ TWÓRCĘ'?\",\n    bullet7text:\n      \"Czasami świeżo wrzucony film pokazuje 'WYŁĄCZONE PRZEZ TWÓRCĘ' nawet wtedy, gdy twórca nie wyłączył łapek. Jest to spowodowane naszym sposobem wykrywania wyłączonych łapek, powinno zniknąć za parę godzin, albo po oddaniu łapki i odświeżeniu strony (oby).\",\n  },\n  donate: {\n    name: \"Wesprzyj\",\n    subtitle:\n      \"Możesz wesprzeć pieniężnie nasze starania w utrzymaniu wolnego internetu!\",\n    patreon: \"Patreon\",\n    crypto: \"Krypto\",\n  },\n  links: {\n    name: \"Linki\",\n    title: \"Linki projektu\",\n    subtitle: \"Linki do projektu i ich developerów\",\n    contact: \"Skontaktuj się ze mną\",\n    translators: \"Tłumacze\",\n    coolProjects: \"Fajne projekty\",\n    sponsorBlockDescription: \"Pomiń reklamy zintegrowane z filmem\",\n    filmotDescription: \"Wyszukaj filmy na YouTube po napisach\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"nie jest obsługiwany.\",\n    considerUpgrade: \"Rozważ aktualizację do najnowszej wersji.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/pt_BR.ts",
    "content": "import { pt_BR } from \"vuetify/src/locale\";\n// by Unnamed-orbert\nexport default {\n  ...pt_BR,\n  home: {\n    name: \"Inicio\",\n    title: \"Return YouTube Dislike\",\n    subtitle: \"Esta extensão de navegador usando a nossa APU irá mostra seus Deslikes no Youtube\",\n    ukraine: \"Ajuda a Ucrania\",\n    sponsors: \"Patrocinaores\",\n    becomeSponsor: \"Torne-se nosso patrocinador\",\n  },\n  install: {\n    name: \"Instalação\",\n    title: \"Selecione sua plataforma\",\n    subtitle: \"Disponivel para Firefox e tidos navegadores Chromium\",\n    title2: \"Outras Plataformas\",\n    subtitle2: \"Se seu navegador não suportar isso, tente usa um UserScript\",\n    title3: \"Implementação de terceiro\",\n    subtitle3: \"Não nós garantimos com isso, use por sua conta e risco!\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"UserScript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - um fork do NewPipe)\",\n    iosJailbroken: \"iOS (com jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Bem-vindo aos documentos oficiais de RYD!\",\n    subtitle: \"Para começar, selecione um seletor o menu.\",\n    rights: {\n      title: \"Direitos de uso\",\n      subtitle:\n        \"Third party use of this open API is allowed with the following restrictions:\",\n      bullet1: \"Attribution: \",\n      bullet1text:\n        \"This project should be clearly attributed with either a link to this repo or a link to returnyoutubedislike.com\",\n      bullet2: \"Rate Limiting: \",\n      bullet2text:\n        \"There are per client rate limits in place of 100 per minute and 10,000 per day. This will return a 429 status code indicating that your application should back off\",\n    },\n    url: {\n      title: \"Informação da URL\",\n      subtitle: \"A API é acessivel seguindo o URL base: \",\n    },\n    endpoints: {\n      title: \"Available Endpoints\",\n      subtitle: \"List of available endpoints is available here: \",\n    },\n    fetching: {\n      title: \"Basic Fetching Tutorial\",\n      subtitle: \"Example to get votes of a given YouTube video ID: \",\n      title2: \"Examplo de Requesitação : \",\n      url: \"URL de requisitação: \",\n      method: \"Metodo de solicitação: \",\n      headers: \"Cabeçalhos: \",\n      response: \"Resposta: \",\n      error1: 'Isso é um ID do youtube invalido retornando o codigo 404 \"não encontrado\"',\n      error2: 'Isso é um ID do youtube mal formatada retornando o codigo 400 \"Solicitação ruim\"',\n    },\n  },\n  help: {\n    name: \"Ajuda\",\n    title: \"Troubleshooting\",\n    bullet1: \"Make sure you have latest version of extension installed, \",\n    bullet11: \"right now\",\n    bullet2: \"Tente remover a extensão e instalar novamente, ou tente reiniciar o navegador\",\n    bullet3: \"Faça isso abrindo este link:\",\n    bullet31: \"you should see plain text: \",\n    bullet4: \"Se não encontrou um ajuda - reporte seu problema no \",\n    bullet41: \"na nossa\",\n    bullet4a: \"Diga-nós o seu sistema operacional, navegador e Versão do navegador\",\n    bullet4b:\n      \"Take a screenshot of the page with the problem (i.e. Youtube video page) with the console open (press \",\n    bullet4b1: \") - example screenshot below.\",\n    bullet4c:\n      \"Take a screenshot of the extensions page of your browser with the extension installed.\",\n    bullet4c1: \"Veja as extensões put essa into barra de endereço: \",\n    firefox: \"para Firefox\",\n    chrome: \"para Chrome, Edge, Brave, Opera e Vivaldi\",\n    detected: \"Detectado:\",\n    altExampleScreenshot: \"captura de tela de exemplo\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Pergunta feitas com Frequencia\",\n    subtitle: \"Ainda tem perguntas? Sinta-se livre para nós acompanhar no Discord!\",\n    bullet1: \"Quais dados a extensão obtem Where does the extension get its data?\",\n    bullet1text:\n      \"A combination of archived data from before the official YouTube dislike API shut down, and extrapolated extension user behavior.\",\n    bullet2: \"Por que a contagem de deslike não está atualizando?\",\n    bullet2text:\n      \"Right now video dislikes are cached and they aren't updated very frequently. It varies depending on a video's popularity but can take anywhere between a few hours and a few days to update.\",\n    bullet3: \"Como isso funciona?\",\n    bullet3text:\n      \"The extension collects the video ID of the video you are watching, and fetches the number of dislikes (and other fields like views, likes etc) using our API. The extension then displays the dislike count and ratio on the page. If you like or dislike a video, that is recorded and sent to the database so an accurate dislike count can be extrapolated.\",\n    bullet4: \"Eu posso compartilha a contador de deslike com você?\",\n    bullet4text:\n      \"Coming soon. We are looking into using Oauth or a different read only API with a limited scope so creators can share their dislike counts verifiability.\",\n    bullet5: \"Quais os dados que vocs coletam para fazer isso funcionar?\",\n    bullet5text:\n      'A extensão apenas coleta os dados extritamente necessaria para o funcionamento proprietario, como o seu endereço de IP e ID do video  que vocês atualmente assistindo. None of your data will ever be sold to 3rd parties. If you would like to know more about how we handle security and privacy check out our <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">security FAQ</a>.',\n    bullet6: \"How does the API/Backend work?\",\n    bullet6text:\n      \"The backend is using archived data from when the youtube api was still returning the dislike count, extension users like/dislike count and extrapolation. In the near future we will be allowing content creators to submit their dislike count easily and safely and we will be adding ArchiveTeam's archived data (4.56 billion videos) into our current database. You can also view a video on the topic.\",\n    bullet7: \"Why does the dislike count show 'DISLIKES DISABLED'?\",\n    bullet7text:\n      \"Sometimes a recently uploaded video might show 'DISLIKES DISABLED' even if the creator hasn't disabled it, this is due to how we are detecting if dislikes are disabled, it should go away in a few hours or by liking or disliking the video and refreshing the page (hopefully).\",\n  },\n  donate: {\n    name: \"Doe\",\n    subtitle:\n      \"Você pode nós ajuda a melhora sua experiencia na internet com uma doação!\",\n    patreon: \"Patreon\",\n    crypto: \"Cripto\",\n  },\n  links: {\n    name: \"Links\",\n    title: \"Links de Projetos\",\n    subtitle: \"Links para o projeto e outros desenvolvedores\",\n    contact: \"Entre em Contato comigo (Only English, please!)\",\n    translators: \"Tradutores\",\n    coolProjects: \"Projetos de Qualidade\",\n    sponsorBlockDescription: \"Pule propagangas integradas nos videos\",\n    filmotDescription: \"Pesquise videos do YouTube pelas Legandas\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"não é compatível.\",\n    considerUpgrade: \"Considere atualizar para a versão mais recente.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/ru.ts",
    "content": "import { ru } from \"vuetify/src/locale\";\n// By Nikita Krupin\nexport default {\n  ...ru,\n  home: {\n    name: \"Главная\",\n    title: \"Вернуть YouTube дизлайки\",\n    subtitle:\n      \"Расширение для браузера и API, которые показывают вам отметки «Не нравится» в Youtube\",\n    ukraine: \"Поддержите Украину\",\n    sponsors: \"Спонсоры\",\n    becomeSponsor: \"Станьте нашим спонсором\",\n  },\n  install: {\n    name: \"Скачать\",\n    title: \"Выберите свою платформу\",\n    subtitle: \"Доступно в Firefox и во всех Chromium браузерах\",\n    title2: \"Другие платформы\",\n    subtitle2:\n      \"Если ваш браузер ещё не поддерживается, попробуйте этот пользовательский скрипт\",\n    title3: \"От других разработчиков\",\n    subtitle3:\n      \"Никакой ответственности с нашей стороны, используйте на свой страх и риск\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Пользовательский скрипт\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular — форк NewPipe)\",\n    iosJailbroken: \"iOS (джейлбрейк)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Добро пожаловать в официальную документацию RYD!\",\n    subtitle: \"Чтобы начать, выберите раздел в меню.\",\n    rights: {\n      title: \"Права пользования\",\n      subtitle:\n        \"Использование этого открытого API третьими лицами разрешено со следующими ограничениями:\",\n      bullet1: \"Aтрибуция: \",\n      bullet1text:\n        \"Этот проект должен быть чётко описан либо ссылкой на этот репозиторий, либо ссылкой на returnyoutubedislike.com\",\n      bullet2: \"Ограничение\",\n      bullet2text:\n        \"Существуют ограничения на скорость для каждого клиента - 100 в минуту и 10 000 в день. Это выдаст код ошибки 429, указывающее на то, что ваше приложение превысило лимит\",\n    },\n    url: {\n      title: \"Информация о URL-адресе\",\n      subtitle: \"API доступен по следующему URL-адресу: \",\n    },\n    endpoints: {\n      title: \"Доступные конечные точки (эндпоинты)\",\n      subtitle: \"Список доступных конечных точек доступен здесь: \",\n    },\n    fetching: {\n      title: \"Базовое руководство по выборке\",\n      subtitle:\n        \"Пример получения голосов на заданный видео идентификатор на YouTube \",\n      title2: \"Пример запроса: \",\n      url: \"URL-адрес запроса: \",\n      method: \"Способ запроса: \",\n      headers: \"Заголовок: \",\n      response: \"Ответ: \",\n      error1:\n        'Неверный идентификатор YouTube выдаст код ошибки 404 \"Не найдено\"',\n      error2:\n        'Неправильно отформатированный идентификатор YouTube выдаст код ошибки 400 \"Неверный запрос\"',\n    },\n  },\n  help: {\n    name: \"Помощь\",\n    title: \"Диагностика\",\n    bullet1: \"Убедитесь, что у вас установлена последняя версия расширения, \",\n    bullet11: \"прямо сейчас\",\n    bullet2:\n      \"Попробуйте удалить расширение и установить его снова, затем перезагрузите браузер (все активные окна, а не только одну вкладку).\",\n    bullet3: \"Убедитесь, что эта ссылка открывается:\",\n    bullet31: \"вы должны увидеть текст: \",\n    bullet4:\n      \"Если ничего из вышеперечисленного не помогает - сообщите о своей проблеме в\",\n    bullet41: \"в нашем\",\n    bullet4a:\n      \"Сообщите нам вашу операционную систему, Название браузера и версию браузера\",\n    bullet4b:\n      \"Сделайте снимок экрана страницы с проблемой (например, страницы видео на YouTube) с открытой консолью (нажмите \",\n    bullet4b1: \") - пример скриншота ниже.\",\n    bullet4c:\n      \"Сделайте снимок экрана страницы расширений вашего браузера с установленным расширением.\",\n    bullet4c1: \"Чтобы увидеть расширения, введите это в адресную строку: \",\n    firefox: \"для Firefox\",\n    chrome: \"для Chrome, Edge, Brave, Opera, Vivaldi\",\n    detected: \"Обнаружено:\",\n    altExampleScreenshot: \"пример скриншота\",\n  },\n  faq: {\n    name: \"ЧаВО\",\n    title: \"Частые вопросы\",\n    subtitle: \"Всё ещё есть вопросы? Не стесняйтесь заглянуть в наш Discord!\",\n    bullet1: \"Откуда расширение получает данные?\",\n    bullet1text:\n      \"Комбинация архивных данных, полученных до закрытия официального API YouTube с отметками «Не нравится», и экстраполяция поведения пользователей расширения.\",\n    bullet2: \"Почему количество отметок «Не нравится» не обновляется?\",\n    bullet2text:\n      \"На данный момент видео отметки «Не нравится» кэшируются, и обновляются не очень часто. Это зависит от популярности видео, но обновление может занять от нескольких часов до нескольких дней.\",\n    bullet3: \"Как это работает?\",\n    bullet3text:\n      \"Расширение берёт идентификатор видео которое вы смотрите, извлекает количество отметок «Не нравится» (и другие поля, такие как просмотры, отметки «Нравится» и т.д.) используя наш API. Затем расширение отображает количество отметок «Не нравится» на странице. Если если вы оставляете отметки «Нравится» или «Не нравится», это записывается и отправляется в базу данных, чтобы можно было экстраполировать точное количество отметок.\",\n    bullet4:\n      \"Могу я поделиться с вами своим количеством отметок «Не нравится»?\",\n    bullet4text:\n      \"Скоро. Мы рассматриваем возможность использования Oauth или другого API, доступного только для чтения, с ограниченной областью действия, чтобы создатели могли делиться своим количеством отметок «Не нравится».\",\n    bullet5: \"Какие данные вы собираете и как они обрабатываются?\",\n    bullet5text:\n      'Расширение собирает только те данные, которые строго необходимы для его правильной работы, такие как IP-адрес или идентификатор видео которое вы смотрите. Ваши данные никогда не будут проданы третьим лицам. Если вы хотите узнать больше о том, как мы обеспечиваем безопасность и конфиденциальность, ознакомьтесь с нашим <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQru.md\">ЧаВО по безопасности</a>.',\n    bullet6: \"Как работает API/серверная часть?\",\n    bullet6text:\n      \"Серверная часть использует архивные данные с тех пор, когда YouTube API всё ещё выдавал количество отметок «Не нравится», количество отметок «Нравится» и «Не нравится» пользователей расширения и экстраполяцию. В ближайшем будущем мы позволим создателям контента легко и безопасно отправлять количество своих отметок «Не нравится», и мы добавим архивные данные ArchiveTeam (4,56 миллиарда видео) в нашу текущую базу данных. Вы также можете просмотреть видео на эту тему.\",\n    bullet7:\n      \"Почему счётчик отметок «Не нравится» показывает 'DISLIKES DISABLED'?\",\n    bullet7text:\n      'Иногда недавно загруженное видео может показывать \"DISLIKES DISABLED\", это связано с тем как мы определяем отключены ли отметки «Не нравится», и должно исчезнуть через несколько часов или после получения отметки «Нравится» или «Не нравится» и обновления страницы (надеюсь).',\n  },\n  donate: {\n    name: \"Пожертвовать\",\n    subtitle:\n      \"Вы можете поддержать пожертвованием наши усилия по обеспечению свободного Интернета!\",\n    patreon: \"Patreon\",\n    crypto: \"Крипто\",\n  },\n  links: {\n    name: \"Ссылки\",\n    title: \"Ссылки\",\n    subtitle: \"Ссылки на проект и его разработчиков\",\n    contact: \"Связаться\",\n    translators: \"Переводчики\",\n    sponsorBlockDescription: \"Проматывает рекламу, встроенную в видео\",\n    filmotDescription: \"Поиск по субтитрам YouTube\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"не поддерживается.\",\n    considerUpgrade: \"Рекомендуется обновить до последней версии.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/sv.ts",
    "content": "import { sv } from \"vuetify/src/locale\";\n\nexport default {\n  ...sv,\n  home: {\n    name: \"Hem\",\n    title: \"Return YouTube Dislike\",\n    subtitle:\n      \"Webbläsartillägg och ett API som visar antalet ogilla på Youtube\",\n    ukraine: \"Stöd Ukraina\",\n    sponsors: \"Sponsorer\",\n    becomeSponsor: \"Bli vår sponsor\",\n  },\n  install: {\n    name: \"Installera\",\n    title: \"Välj din webbläsare\",\n    subtitle: \"Tillgängligt för Firefox och alla Chromium-webbläsare\",\n    title2: \"Andra webbläsare\",\n    subtitle2: \"Om din webbläsare ännu inte stöds, prova detta UserScript\",\n    title3: \"Tredjepartsimplementeringar\",\n    subtitle3: \"Inget ansvar från vår sida, använd på egen risk\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Användarskript\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular – en fork av NewPipe)\",\n    iosJailbroken: \"iOS (jailbreakad)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Välkommen till de officiella RYD-dokumenten!\",\n    subtitle: \"För att komma igång, välj ett avsnitt från menyn.\",\n    rights: {\n      title: \"Användningsrättigheter\",\n      subtitle:\n        \"Tredjepartsanvändning av detta öppna API är tillåtet med följande begränsningar:\",\n      bullet1: \"Tillskrivning: \",\n      bullet1text:\n        \"Detta projekt bör tydligt tillskrivas med antingen en länk till denna repo eller en länk till returnyoutubedislike.com\",\n      bullet2: \"Satsbegränsning: \",\n      bullet2text:\n        \"Det finns satsbegränsningar per användare med 100 per minut och 10000 per dag. Detta kommer att returnera en statushod 429 som indikerar att din applikation ska hålla sig borta\",\n    },\n    url: {\n      title: \"Webbadressinformation\",\n      subtitle: \"API:et är tillgängligt via följande baswebbadress: \",\n    },\n    endpoints: {\n      title: \"Tillgängliga slutpunkter\",\n      subtitle: \"Lista över tillgängliga slutpunkter finns här: \",\n    },\n    fetching: {\n      title: \"Grundläggande handledning för hämtning\",\n      subtitle:\n        \"Exempel för att hämta röster för ett bestämt YouTubevideo-ID: \",\n      title2: \"Example Request: \",\n      url: \"Request URL: \",\n      method: \"Request Method: \",\n      headers: \"Headers: \",\n      response: \"Response: \",\n      error1: 'Ett ogiltigt YouTube-ID returnerar statuskoden 404 \"Not Found\"',\n      error2:\n        'Ett felaktigt formaterat YouTube-ID returnerar 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Hjälp\",\n    title: \"Felsökning\",\n    bullet1:\n      \"Se till att du har den senaste versionen av tillägget installerat, \",\n    bullet11: \"just nu\",\n    bullet2:\n      \"Försök att avinstallera tillägget och installera det igen, starta sedan om webbläsaren (alla aktiva fönster, inte bara en flik)\",\n    bullet3: \"Se till att den här länken öppnas: \",\n    bullet31: \"du bör se vanlig text: \",\n    bullet4: \"Om inget av ovanstående hjälper - rapportera ditt problem i\",\n    bullet41: \"i vår\",\n    bullet4a:\n      \"Tala om för oss ditt operativsystem, webbläsarnamn och webbläsarversion\",\n    bullet4b:\n      \"Ta en skärmdump av sidan du har problem med (t.ex en youtube-videosida) med konsolfönstret öppet (tryck \",\n    bullet4b1: \") - exempel på skärmdump nedan.\",\n    bullet4c:\n      \"Ta en skärmdump av tilläggssidan i din webbläsare med tillägg installerat.\",\n    bullet4c1: \"För att se tilläggen skriv in detta i adressfältet: \",\n    firefox: \"för Firefox\",\n    chrome: \"för Chrome, Edge, Brave, Opera och Vivaldi\",\n    detected: \"Upptäckt:\",\n    altExampleScreenshot: \"exempelskärmdump\",\n  },\n  faq: {\n    name: \"FAQ\",\n    title: \"Vanliga frågor\",\n    subtitle: \"Har du fortfarande frågor? Gå gärna med i vår Discord!\",\n    bullet1: \"Var får tillägget sina data?\",\n    bullet1text:\n      \"En kombination av arkiverad data från innan det officiella YouTube ogilla-API:et stängdes av och och extrapolerat tilläggsanvändarbeteende.\",\n    bullet2: \"Varför uppdateras inte antalet ogilla?\",\n    bullet2text:\n      \"Just nu cachelagras ogilla för videoklipp och de uppdateras inte särskilt ofta. Det varierar beroende på en videos popularitet, men det kan ta allt från några timmar till några dagar att uppdatera.\",\n    bullet3: \"Hur fungerar detta?\",\n    bullet3text:\n      \"Tillägget samlar in video-ID:t för videon du tittar på och hämtar ogilla (and other fields like views, likes etc) med hjälp av vårt API. Tillägget visar sedan antalet ogilla och förhållandet på sidan. Om du gillar eller ogillar en video, spelas det in och skickas till databasen så att ett korrekt antal ogilla kan extrapoleras.\",\n    bullet4: \"Kan jag dela mina antalet ogilla med dig?\",\n    bullet4text:\n      \"Kommer snart. Vi tittar på att använda Oauth eller ett annat skrivskyddat API med ett begränsat omfång så att kreatörer kan dela med sig av verifierbarheten av sina antal ogilla.\",\n    bullet5: \"Vilken data samlar du in och hur behandlas den?\",\n    bullet5text:\n      'Tillägget samlar bara in data som är absolut nödvändig för att den ska fungera korrekt, som IP-adressen eller ID:t på videon du tittar på. Inga uppgifter kommer någonsin att säljas till tredjepart. Om du vill veta mer om hur vi hanterar säkerhet och integritet läs då vår <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">vanliga frågor om säkerhet</a>.',\n    bullet6: \"Hur fungerar API/Backend?\",\n    bullet6text:\n      \"Backend använder arkiverad data från när YouTubes API fortfarande returnerade antalet ogilla. Inom en snar framtid kommer vi att tillåta innehållsskapare att enkelt och säkert skicka in sina ogilla och vi kommer att lägga till ArchiveTeams arkiverade data (4,56 miljarder videor) i vår nuvarande databas. Du kan också se en video om ämnet.\",\n    bullet7: \"Varför visar antalet ogilla 'OGILLA ÄR INAKTIVERAT'?\",\n    bullet7text:\n      \"I bland kan en nyligen uppladdad video visa 'OGILLA ÄR INAKTIVERAT' även om ägaren inte har inaktiverat den. Detta beror på hur informationen om ogilla är inaktiverat hämtas ut. Det bör försvinna inom några timmar eller genom att gilla eller ogilla videon och uppdatera sidan (förhoppningsvis).\",\n  },\n  donate: {\n    name: \"Donera\",\n    subtitle:\n      \"Du kan stöda våra ansträngningar att hålla internet fritt med en donation!\",\n    patreon: \"Patreon\",\n    crypto: \"Krypto\",\n  },\n  links: {\n    name: \"Länkar\",\n    title: \"Projektlänkar\",\n    subtitle: \"Länkar till projektet och dess utvecklare\",\n    contact: \"Kontakta mig\",\n    translators: \"Översättare\",\n    coolProjects: \"Häftiga projekt\",\n    sponsorBlockDescription: \"Hoppar över annonser integrerade i videon\",\n    filmotDescription: \"Sök efter YouTube-videor i undertexterna\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"stöds inte.\",\n    considerUpgrade: \"Överväg att uppgradera till den senaste versionen.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/tr.ts",
    "content": "﻿import { tr } from \"vuetify/src/locale\";\n// By Batuhan Kara & İlyas Zan\nexport default {\n  ...tr,\n  home: {\n    name: \"Ana Sayfa\",\n    title: \"YouTube Dislike Sayısını Geri Getir\",\n    subtitle:\n      \"YouTube'daki dislike sayılarınızı geri getiren bir tarayıcı uzantısı ve API\",\n    ukraine: \"Ukrayna'ya Destek Ol\",\n    sponsors: \"Sponsorlar\",\n    becomeSponsor: \"Sponsorumuz olun\",\n  },\n  install: {\n    name: \"Yükle\",\n    title: \"Platformunuzu Seçin\",\n    subtitle: \"Firefox ve bütün Chromium tabanlı tarayıcılarda kullanılabilir\",\n    title2: \"Diğer Platformlar\",\n    subtitle2:\n      \"Tarayıcınız henüz desteklenmiyorsa UserScript yöntemini deneyin\",\n    title3: \"Üçüncü Parti Uygulamalar\",\n    subtitle3:\n      \"Riski tamamen size aittir, bizim tarafımızda sorumluluk kabul edilmemektedir\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Kullanıcı betiği\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - NewPipe çatallaması)\",\n    iosJailbroken: \"iOS (jailbreakli)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Resmî RYD belgelerine hoş geldiniz!\",\n    subtitle: \"Başlamak için menüden bir bölüm seçin.\",\n    rights: {\n      title: \"Kullanım Hakları\",\n      subtitle:\n        \"Herkese açık API'nin üçüncü parti kişilerin kullanımında aşağıdaki kısıtlamalara izin verir:\",\n      bullet1: \"Atıf: \",\n      bullet1text:\n        \"Bu proje, bu depoya ya da returnyoutubedislike.com sitesine bir bağlantı ile açıkça atfedilmelidir.\",\n      bullet2: \"Hız Sınırlaması: \",\n      bullet2text:\n        \"Kullanıcı başına dakikada 100 ve günde 10.000 hız sınırlaması vardır. Bu, uygulamanızın geri çekilmesi gerektiğini belirten 429 durum kodunu döndürür.\",\n    },\n    url: {\n      title: \"URL Bilgisi\",\n      subtitle: \"API'ye şu URL üzerinden ulaşılabilir: \",\n    },\n    endpoints: {\n      title: \"Kullanılabilir Endpoint'ler\",\n      subtitle: \"Kullanılabilir endpoint'lerin listesi burada mevcuttur: \",\n    },\n    fetching: {\n      title: \"Temel Veri Alma Eğitimi\",\n      subtitle:\n        \"Belirli bir YouTube ID'sinin oylamalarını elde etmek için bir örnek: \",\n      title2: \"Örnek İstek: \",\n      url: \"İstek URL'si: \",\n      method: \"İstek Yöntemi: \",\n      headers: \"Header'lar: \",\n      response: \"Sonuç: \",\n      error1: 'Geçersiz bir YouTube ID\\'si, 404 \"Not Found\" olarak döndürülür.',\n      error2:\n        'Yanlış biçimlendirilmiş bir YouTube ID\\'si, 400 \"Bad Request\" olarak döndürülür.',\n    },\n  },\n  help: {\n    name: \"Yardım\",\n    title: \"Sorun Giderme\",\n    bullet1: \"Uzantının en son sürümü olan \",\n    bullet11: \" sürümünün kurulu olduğundan emin olun\",\n    bullet2:\n      \"Uzantıyı kaldırıp yeniden yüklemeyi deneyin, sonra tarayıcınızı yeniden başlatın (tüm aktif sekmeleri kapatın, sadece tek bir sekmeyi değil)\",\n    bullet3: \"Şu bağlantıyı açtığınızdan emin olun: \",\n    bullet31: \"şöyle bir düz metin görmelisiniz: \",\n    bullet4: \"Yukarıdakiler yardımcı olmadıysa - Discord sunucumuzdaki \",\n    bullet41:\n      \" kanalından problemi bildirin (İngilizce bir şekilde) Discord sunucumuz: \",\n    bullet4a:\n      \"Bize İşletim Sisteminizi, Tarayıcı Adınızı ve Tarayıcı Sürümünüzü söyleyin\",\n    bullet4b: \"Konsol açıkken (açmak için \",\n    bullet4b1:\n      \" tuşuna basın) sorunu yaşadığınız sayfanın ekran görüntüsünü alın (yani YouTube watch sayfasının) - Ekran görüntüsü örneği aşağıdadır.\",\n    bullet4c:\n      \"Uzantı yüklüyken tarayıcınızın uzantılar sayfasının ekran görüntüsünü alın.\",\n    bullet4c1: \"Uzantıları görmek için şu linki adres çubuğuna yapıştırın: \",\n    firefox: \"(Firefox için)\",\n    chrome: \"(Chrome, Edge, Brave, Opera ve Vivaldi için)\",\n    detected: \"Algılandı:\",\n    altExampleScreenshot: \"örnek ekran görüntüsü\",\n  },\n  faq: {\n    name: \"SSS\",\n    title: \"Sıkça Sorulan Sorular\",\n    subtitle:\n      \"Hâlâ sorun mu yaşıyorsunuz? Discord sunucumuza katılmaktan çekinmeyin! (İngilizce)\",\n    bullet1: \"Uzantı, verileri nereden alıyor?\",\n    bullet1text:\n      \"Resmî YouTube dislike sayısı API'si kapatılmadan önceki arşivlenmiş verilerden ve tahmin edilen uzantı kullanıcısı davranışının bir birleşimiyle.\",\n    bullet2: \"Dislike sayısı neden güncellenmiyor?\",\n    bullet2text:\n      \"Şu anda dislike durumları önbelleğe alınır ve çok sık güncellenmez. Bir videonun popülerliğine bağlı olarak değişir ancak güncellenmesi birkaç saat ilâ birkaç gün sürebilir.\",\n    bullet3: \"Bu nasıl çalışıyor?\",\n    bullet3text:\n      \"Uzantı, izlediğiniz videonun ID'sini alır, API'miz üzerinden dislike sayılarınızı geri getirir (aynı zamanda görüntülenmenizi, like'ınızı vb. diğer alanları da). Uzantı daha sonra sayfada dislike sayısını ve oranını görüntüler. Bir videoya like veya dislike atarsanız bu kaydedilir ve veri tabanına gönderilir, böylece doğru dislike sayısını tahmin edebilir.\",\n    bullet4: \"Dislike sayımı sizinle paylaşabilir miyim?\",\n    bullet4text:\n      \"Çok yakında evet. İçerik üreticilerinin dislike sayıları için doğrulanabilirliğini paylaşabilmeleri amacıyla Oauth ya da sınırlı bir kapsamda farklı bir salt okunur API kullanmayı düşünüyoruz.\",\n    bullet5: \"Hangi verileri topluyorsunuz ve bunlar nasıl işleniyor?\",\n    bullet5text:\n      \"Uzantı, yalnızca izlediğiniz videonun IP adresi veya videonun ID'si gibi düzgün çalışması için kesinlikle gerekli olan verileri toplar. Verileriniz asla 3. taraflara satılmayacaktır. Güvenliği ve gizliliği nasıl ele aldığımız hakkında daha fazla bilgi için <a href=\\\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\\\">security FAQ</a>'ya gidin.\",\n    bullet6: \"API/Backend nasıl çalışıyor?\",\n    bullet6text:\n      \"Yazılım, YouTube API'sinin dislike sayısını ve uzantı kullanıcılarının like/dislike sayısı sonuçların genişletilmesinin döndürmeye devam ettiği zamana ait arşivlenmiş verileri kullanır. Yakın zamanda içerik üreticilerin dislike sayısını kolay ve güvenli bir şekilde göndermelerine izin vereceğiz ve ArchiveTeam'in arşivlenmiş verilerini (4,56 milyar video) veri tabanımıza ekleyeceğiz. Ayrıca konu ile ilgili videoyu da izleyebilirsiniz.\",\n    bullet7: \"Dislike sayısı neden 'DISLIKE'LAR KAPALI' olarak gözüküyor?\",\n    bullet7text:\n      \"Yazma sırasında like ve dislike sayısını devre dışı bırakan videoların dislike sayılarını göstermiyoruz. Uzantı, bu videolar için 'DISLIKE'LAR KAPALI' mesajını görüntüler. Yakında tüm videolarda dislike sayısını göstereceğiz. Bu, yalnızca geçici bir çözümdür. Bu nedenle insanlar uzantının bozuk olduğunu düşünmez (zaten iyi çalışmıyor). Bazen yakın zamanda yüklenen bir videoda, içerik üreticisi onu devre dışı bırakmamış olsa bile 'DISLIKE'LAR KAPALI' olarak gözükebilir. Bunun nedeni, dislike sayısını devre dışı bırakıp bırakmadığını tespit etmemizdir. Birkaç saat içinde videoyu like ya da dislike atarsanız veya sayfayı yenilerseniz kaybolması gerekir (umarız).\",\n  },\n  donate: {\n    name: \"Bağış Yap\",\n    subtitle:\n      \"İnterneti özgür bırakma çabamızda bize bağışınızla destek olabilirsiniz!\",\n    patreon: \"Patreon\",\n    crypto: \"Kripto\",\n  },\n  links: {\n    name: \"Bağlantılar\",\n    title: \"Proje Bağlantıları\",\n    subtitle: \"Projeye ve geliştiricilerine bağlantılar\",\n    contact: \"Bana Ulaşın\",\n    translators: \"Çevirmenler\",\n    coolProjects: \"Havalı Projeler\",\n    sponsorBlockDescription: \"Videolara gömülü reklamları pas geçer\",\n    filmotDescription: \"YouTube videolarını alt yazılara göre aramanızı sağlar\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"desteklenmiyor.\",\n    considerUpgrade: \"En son sürüme yükseltmeyi düşünün.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/uk.ts",
    "content": "import { uk } from \"vuetify/src/locale\";\n// By dsty#1614\nexport default {\n  ...uk,\n  home: {\n    name: \"Головна\",\n    title: \"Return YouTube Dislike\",\n    subtitle:\n      \"Браузерне розширення та API, що дозволяє вам бачити відмітки «Не подобається» на YouTube\",\n    ukraine: \"Підтримати Україну\",\n    sponsors: \"Спонсори\",\n    becomeSponsor: \"Станьте нашим спонсором\",\n  },\n  install: {\n    name: \"Завантажити\",\n    title: \"Оберіть вашу платформу\",\n    subtitle: \"Доступно на Firefox та усіх Chromium браузерах\",\n    title2: \"Інші платформи\",\n    subtitle2: \"Якщо ваш браузер не підтримується, спробуйте цей UserScript\",\n    title3: \"Стороннє ПЗ\",\n    subtitle3:\n      \"Ми не маємо відношення до цього, використовуйте на свій страх і ризик\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Користувацький скрипт\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular — форк NewPipe)\",\n    iosJailbroken: \"iOS (джейлбрейк)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Вітаємо у офіційній документації RYD!\",\n    subtitle: \"Щоб почати, виберіть розділ у меню.\",\n    rights: {\n      title: \"Права використання\",\n      subtitle:\n        \"Використання цього відкритого API сторонніми особами дозволено з наступними обмеженнями:\",\n      bullet1: \"Атрибуція: \",\n      bullet1text:\n        \"Цей проєкт має бути чітко описано, використовуючи посилання, або ж на цей репозиторій, або ж на returnyoutubedislike.com\",\n      bullet2: \"Обмеження: \",\n      bullet2text:\n        \"Існують обмеження на швидкісті для кожного клієнта - 100 за хвилину і 10 000 за день. Це видасть код помилки 429, який вказує на те, що вашому додатку слід завершити роботу\",\n    },\n    url: {\n      title: \"Інформація про посилання\",\n      subtitle: \"API доступний за наступним посиланням: \",\n    },\n    endpoints: {\n      title: \"Достпупні «ендпоінти»\",\n      subtitle: \"Перелік доступних «ендпоінтів» можна переглянути тут: \",\n    },\n    fetching: {\n      title: \"Базовий посібник по отриманню\",\n      subtitle: \"Приклад отримання оцінок відео на YouTube за ID \",\n      title2: \"Приклад запиту: \",\n      url: \"Посилання запиту: \",\n      method: \"Метод запиту: \",\n      headers: \"Заголовок: \",\n      response: \"Відповідь: \",\n      error1: 'Недійсний YouTube ID видасть код помилки 404 \"Not Found\"',\n      error2:\n        'YouTube ID у невірному форматі видасть код помилки 400 \"Bad Request\"',\n    },\n  },\n  help: {\n    name: \"Допомога\",\n    title: \"Усунення несправностей\",\n    bullet1:\n      \"Переконайтеся, що у вас встановлена остання версія розширення, наразі це - \",\n    bullet11: \"\",\n    bullet2:\n      \"Спробуйте видалити розширення і встановити його знову, а потім перезавантажте браузер (усі активні вікна, а не лише одну вкладку).\",\n    bullet3: \"Переконайтеся, що наступне посилання відкривається: \",\n    bullet31: \"і ви бачите там цей текст: \",\n    bullet4:\n      \"Якщо нічого з перерахованого вище не допомогло - повідомте про проблему в каналі\",\n    bullet41: \"у нашому\",\n    bullet4a: \"Повідомте нам вашу операційну систему, назву та версію браузера\",\n    bullet4b:\n      \"Зробіть знімок екрана сторінки з проблемою (тобто сторінки відео на YouTube) із відкритою консоллю (натисніть \",\n    bullet4b1: \") - приклад знімка екрана нижче.\",\n    bullet4c:\n      \"Зробіть знімок екрана сторінки з встановленими розширеннями вашого браузера.\",\n    bullet4c1: \"Щоб побачити розширення, напишіть це в адресний рядок: \",\n    firefox: \"для Firefox\",\n    chrome: \"для Chrome, Edge, Brave, Opera, Vivaldi\",\n    detected: \"Виявлено:\",\n    altExampleScreenshot: \"приклад знімка екрана\",\n  },\n  faq: {\n    name: \"ЧаПи\",\n    title: \"Часті питання\",\n    subtitle:\n      \"Залишилися питання? Не соромтеся приєднуватися до нашого Discord!\",\n    bullet1: \"Звідки розширення отримує дані?\",\n    bullet1text:\n      \"Комбінація архівних даних, отриманих до закриття офіційного API відміток «Не подобається» на YouTube та екстрапольованої поведінки користувачів розширення.\",\n    bullet2: \"Чому кількість відміток «Не подобається» не оновлюється?\",\n    bullet2text:\n      \"Наразі відмітки «Не подобається» кешуються й не оновлюються надто часто. Це залежить від популярності відео, але для оновлення може знадобитися від кількох годин до кількох днів.\",\n    bullet3: \"Як це працює?\",\n    bullet3text:\n      \"Розширення отримує ID відео, яке ви переглядаєте, та дізнається кількість відміток «Не подобається» (та інші дані: перегляди, відмітки «Подобається» тощо) за допомогою нашого API. Потім розширення відображає кількість відміток «Не подобається» та їх коефіцієнт на сторінці. Якщо ви ставите відмітки «Подобається» чи «Не подобається», це записується та надсилається до бази даних, щоб можна було екстраполювати точну їх кількість нелайків.\",\n    bullet4: \"Чи можу я поділитися з вами своїми відмітками «Не подобається»?\",\n    bullet4text:\n      \"Незабаром. Ми розглядаємо можливість використання Oauth або іншого API, доступного тільки для читання, з обмеженою областю дій, щоб творці могли ділитися своїми відмітками «Не подобається»\",\n    bullet5: \"Які дані ви збираєте та як вони обробляються?\",\n    bullet5text:\n      'Розширення збирає лише ті дані, які необхідні для правильної його роботи, такі як IP-адреси або ID відео, яке ви дивитеся. Ваші дані ніколи не будуть продані третім особам. Якщо ви хочете дізнатися більше про те, як ми забезпечуємо безпеку та конфіденційність, ознайомтесь з нашими <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQ.md\">ЧаПи по безпеці</a>.',\n    bullet6: \"Як працює API/серверна сторона?\",\n    bullet6text:\n      \"Серверна частина використовує архівні дані з тих пір, доки YouTube API все ще видавав кількість відміток «Не подобається», а також відмітки «Подобається»/«Не подобається» користувачів розширення та екстраполяцію. В найближчому майбутньому ми дозволимо творцям контенту легко та безпечно надсилати свої відмітки «Не подобається», а також ми додамо архівні дані ArchiveTeam (4,56 мільярди відео) до нашої поточної бази даних. Ви можете переглянути відео на цю тему.\",\n    bullet7:\n      \"Чому лічильник відміток «Не подобається» пише 'DISLIKES DISABLED'?\",\n    bullet7text:\n      \"Іноді на нещодавно завантаженому відео може з'являтися повідомлення 'DISLIKES DISABLED', навіть попри те, що автор не вимикав відмітки «Не подобається». Це пов’язано з тим, як ми визначаємо, чи вимкнені оцінки «Не подобається». Це повідомлення має зникнути через кілька годин або після отримання відмітки «Подобається»/«Не подобається» і оновлення сторінки (сподіваюся).\",\n  },\n  donate: {\n    name: \"Підтримати\",\n    subtitle:\n      \"Ви можете підтримати пожертвою наші зусилля зробити Інтернет вільнішим!\",\n    patreon: \"Patreon\",\n    crypto: \"Крипто\",\n  },\n  links: {\n    name: \"Посилання\",\n    title: \"Посилання проєкту\",\n    subtitle: \"Посилання на проєкт і його розробників\",\n    contact: \"Зв'язок зі мною\",\n    translators: \"Перекладачі\",\n    coolProjects: \"Круті проєкти\",\n    sponsorBlockDescription: \"пропускає вбудовану у відео рекламу\",\n    filmotDescription: \"пошук відео на YouTube по субтитрах\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"не підтримується.\",\n    considerUpgrade: \"Розгляньте оновлення до останньої версії.\",\n  },\n};\n"
  },
  {
    "path": "Website/_locales/vi.ts",
    "content": "import { vi } from \"vuetify/src/locale\";\n\nexport default {\n  ...vi,\n  home: {\n    name: \"Trang Chủ\",\n    title: \"Return YouTube Dislike\",\n    subtitle: 'Tiện ích mở rộng cho trình duyệt và API hiển thị số lượt \"Không thích\" trên YouTube',\n    ukraine: \"Ủng hộ U-crai-na\",\n    sponsors: \"Tài trợ\",\n    becomeSponsor: \"Trở thành nhà tài trợ của chúng tôi\",\n  },\n  install: {\n    name: \"Cài Đặt\",\n    title: \"Chọn Nền tảng\",\n    subtitle: \"Tương thích với trình duyệt Firefox và tất cả các trình duyệt Chromium\",\n    title2: \"Nền tảng Khác\",\n    subtitle2: \"Nếu trình duyệt của bạn chưa được hỗ trợ, hãy thử dùng Tập lệnh Người dùng\",\n    title3: \"Thực hiện bởi Bên Thứ ba\",\n    subtitle3: \"Nhóm phát triển không chịu trách nhiệm cho bất kì rủi ro nào bạn gặp phải khi sử dụng\",\n    firefox: \"Firefox\",\n    chrome: \"Chrome\",\n    edge: \"Edge\",\n    opera: \"Opera\",\n    brave: \"Brave\",\n    userscript: \"Tập lệnh người dùng\",\n    tampermonkey: \"Tampermonkey\",\n    androidReVanced: \"Android - ReVanced\",\n    androidTubular: \"Android (Tubular - một fork của NewPipe)\",\n    iosJailbroken: \"iOS (đã jailbreak)\",\n    iosUYouPlus: \"iOS (uYou+)\",\n  },\n  api: {\n    name: \"API\",\n    title: \"Chào mừng tới tài liệu RYD chính thức!\",\n    subtitle: \"Để bắt đầu, hãy chọn một mục trong danh sách.\",\n    rights: {\n      title: \"Quyền Sử dụng\",\n      subtitle:\n        \"Bên thứ ba được phép sử dụng API mở này, với các hạn chế sau:\",\n      bullet1: \"Ghi công: \",\n      bullet1text:\n        \"Phải ghi công dự án này rõ ràng bằng đường dẫn tới kho mã nguồn hoặc đường dẫn tới trang returnyoutubedislike.com\",\n      bullet2: \"Giới hạn Truy vấn: \",\n      bullet2text:\n        'Giới hạn truy vấn cho mỗi khách là 100 lần mỗi phút và 10.000 lần mỗi ngày. Mã trạng thái 429 \"Quá Nhiều Yêu cầu\" sẽ được trả về khi khách đạt tới giới hạn trên.',\n    },\n    url: {\n      title: \"Thông tin Đường dẫn\",\n      subtitle: \"API có thể được truy cập với đường dẫn cơ sở sau: \",\n    },\n    endpoints: {\n      title: \"Các Hậu tố\",\n      subtitle: \"Danh sách các hậu tố được liệt kê ở đây: \",\n    },\n    fetching: {\n      title: \"Hướng dẫn Truy vấn Cơ bản\",\n      subtitle: \"Ví dụ cách truy vấn đánh giá với một ID của một vi-đê-ô trên YouTube: \",\n      title2: \"Truy vấn Ví dụ: \",\n      url: \"Đường dẫn Truy vấn: \",\n      method: \"Phương thức Truy vấn: \",\n      headers: \"Phần đầu: \",\n      response: \"Phản hồi: \",\n      error1: 'Nếu ID không tồn tại, mã trạng thái 404 \"Không Tìm thấy\" sẽ được trả về\"',\n      error2:\n        'Nếu ID có định dạng không hợp lệ, mã trạng thái 400 \"Yêu cầu Không hợp lệ\" sẽ được trả về',\n    },\n  },\n  help: {\n    name: \"Trợ Giúp\",\n    title: \"Khắc phục Sự cố\",\n    bullet1: \"Hãy kiểm tra phiên bản của tiện ích mở rộng. Phiên bản mới nhất là: \",\n    bullet11: \"\",\n    bullet2:\n      \"Thử gỡ bỏ và cài lại tiện ích mở rộng, rồi tắt và mở lại trình duyệt (tắt toàn bộ cửa sổ của trình duyệt, không phải tắt chỉ một thẻ)\",\n    bullet3: \"Kiểm tra xem bạn có truy cập theo đường dẫn này được không: \",\n    bullet31: \"và nếu truy cập được, kiểm tra xem bạn có thấy đoạn văn bản thô tương tự đoạn văn bản dưới đây hay không: \",\n    bullet4: \"Nếu các bước trên không khắc phục được sự cố, hãy gửi báo cáo sự cố của bạn tới kênh\",\n    bullet41: \"trên\",\n    bullet4a: \"Cung cấp thông tin Hệ điều hành, Tên Trình duyệt và Phiên bản Trình duyệt (xem thông tin bên dưới)\",\n    bullet4b:\n      \"Tại trang có sự cố (ví dụ như trang phát vi-đê-ô trên YouTube), mở bảng điều khiển (nhấn nút \",\n    bullet4b1: \") và chụp màn hình. Xem hình ví dụ ở bên dưới.\",\n    bullet4c:\n      \"Mở trang quản lí tiện ích mở rộng trong trình duyệt mà bạn cài tiện ích mở rộng này.\",\n    bullet4c1: \"Để mở trang quản lí tiện ích, nhập nội dung sau vào thanh địa chỉ: \",\n    firefox: \"đối với trình duyệt Firefox\",\n    chrome: \"đối với các trình duyệt Chrome, Edge, Brave, Opera, và Vivaldi\",\n    detected: \"Phát hiện:\",\n    altExampleScreenshot: \"ảnh chụp màn hình ví dụ\",\n  },\n  faq: {\n    name: \"Hỏi-Đáp\",\n    title: \"Câu hỏi Thường gặp\",\n    subtitle: \"Bạn vẫn còn điều gì cần giải đáp? Hãy tham gia Discord của chúng tôi!\",\n    bullet1: \"Tiện ích mở rộng này lấy dữ liệu từ đâu?\",\n    bullet1text:\n      'Dữ liệu của tiện ích này bao gồm dữ liệu được lưu trữ trước khi YouTube loại bỏ số lượt \"không thích\" khỏi API chính thức và các ước tính ngoại suy từ hành vi của người dùng.',\n    bullet2: 'Tại sao số lượt \"không thích\" không được cập nhật?',\n    bullet2text:\n      'Hiện tại, số lượt \"không thích\" được lưu vào cơ sở dữ liệu và số lượt được hiển thị không được cập nhật liên tục. Việc cập nhật số lượt được hiển thị tùy thuộc vào độ phổ biến của vi-đê-ô, có thể mất vài giờ tới vài ngày để cập nhật.',\n    bullet3: \"Cách thức hoạt động của tiện ích mở rộng này?\",\n    bullet3text:\n      'Tiện ích này thu thập ID của vi-đê-ô mà bạn xem, rồi sử dụng API của chúng tôi để truy vấn số lượt \"không thích\" (cùng với các thông tin khác như lượt xem, số lượt \"thích\", v.v.). Sau đó, tiện ích sẽ hiển thị số lượt \"không thích\" và tỉ lệ \"thích\"/\"không thích\". Nếu bạn đánh giá \"thích\" hay \"không thích\" một vi-đê-ô, đánh giá này sẽ được ghi lại và gửi vào cơ sở dữ liệu, nhờ đó số lượt \"không thích\" có thể được ngoại suy chính xác.',\n    bullet4: 'Tôi có thể chia sẻ số lượt \"không thích\" của mình cho nhóm phát triển không?',\n    bullet4text:\n      'Tính năng này sẽ sớm có. Chúng tôi đang tìm hiểu cách dùng Oauth hay API chỉ-đọc với phạm vi giới hạn để các nhà sáng tạo nội dung có thể chia sẻ lượt \"không thích\" của mình.',\n    bullet5: \"Dữ liệu nào được tiện ích này thu thập và dữ liệu này được dùng ra sao?\",\n    bullet5text:\n      'Tiện ích này chỉ thu thập dữ liệu cần thiết để hoạt động chính xác, bao gồm địa chỉ IP và ID của vi-đê-ô mà bạn xem. Dữ liệu của bạn không bị bán cho bên thứ ba. Nếu bạn muốn biết thêm về cách chúng tôi đảm bảo tính bảo mật và quyền riêng tư, vui lòng tham khảo <a href=\"https://github.com/Anarios/return-youtube-dislike/blob/main/Docs/SECURITY-FAQvi.md\">những câu hỏi thường gặp về bảo mật</a>.',\n    bullet6: \"API/Đầu sau hoạt động ra sao?\",\n    bullet6text:\n      'Đầu cuối sử dụng dữ liệu được lưu trữ từ thời API của YoutTube vẫn còn cung cấp số lượt \"không thích\", số lượt \"thích\"/\"không thích\" từ người dùng và ước tính ngoại suy. Trong tương lai gần, tiện ích sẽ cho phép các nhà sáng tạo nội dung gửi số lượt \"không thích\" về cho nhóm phát triển một cách dễ dàng và bảo mật và chúng tôi cũng sẽ gộp dữ liệu của ArchiveTeam (4,56 triệu vi-đê-ô) vào cơ sở dữ liệu của chúng tôi. Bạn có thể xem vi-đê-ô về chủ đề này để biết thêm thông tin.',\n    bullet7: \"Tại sao nút \\\"Không thích\\\" hiện nội dung 'Chủ kênh đã Tắt Đánh giá'?\",\n    bullet7text:\n      \"Đôi khi vi-đê-ô mới được đăng tải sẽ hiển thị nút đánh giá với nội dung 'Chủ kênh đã Tắt Đánh giá' mặc dù chủ kênh không hề tắt chức năng đánh giá. Việc này là do cách thức mà tiện ích này xác định việc tắt đánh giá. Tình trạng này có thể sẽ biến mất sau vài giờ hoặc sau khi bạn đánh giá \\\"thích\\\" hay \\\"không thích\\\" vi-đê-ô này kèm theo việc tải lại trang.\",\n  },\n  donate: {\n    name: \"Quyên Góp\",\n    subtitle:\n      \"Bằng cách quyên góp, bạn có thể hỗ trợ nỗ lực của chúng tôi trong việc giữ cho Internet được miễn phí!\",\n    patreon: \"Patreon\",\n    crypto: \"Tiền mã hóa\",\n  },\n  links: {\n    name: \"Đường Dẫn\",\n    title: \"Các Đường dẫn của Dự án\",\n    subtitle: \"Đường dẫn tới dự án và nhóm phát triển\",\n    contact: \"Thông tin Liên lạc\",\n    translators: \"Người dịch\",\n    coolProjects: \"Các Dự án Thú vị\",\n    sponsorBlockDescription: \"Lướt qua quảng cáo được chèn trong nội dung vi-đê-ô\",\n    filmotDescription: \"Tìm vi-đê-ô YouTube bằng phụ đề\",\n    github: \"GitHub\",\n    discord: \"Discord\",\n  },\n  layout: {\n    notSupported: \"không được hỗ trợ.\",\n    considerUpgrade: \"Hãy cân nhắc nâng cấp lên phiên bản mới nhất.\",\n  },\n};\n"
  },
  {
    "path": "Website/layouts/default.vue",
    "content": "<template>\n  <v-app dark>\n    <!-- height = 4rem, margin-y = 1rem -->\n    <v-app-bar\n      app\n      class=\"topBar glass elevation-0 fly-in-from-top my-4 mx-auto\"\n    >\n      <!-- Translator desktop -->\n      <v-tabs centered center-active color=\"primary\" router show-arrows>\n        <v-tab v-for=\"link in links\" :key=\"link.path\" :to=\"link.path\">\n          {{ $vuetify.lang.t(`$vuetify.${link.name}.name`) }}\n        </v-tab>\n      </v-tabs>\n    </v-app-bar>\n\n    <!-- abstract background -->\n    <v-img\n      src=\"/ui/abstract.svg\"\n      style=\"position: fixed; left: 0; right: 0; width: 100vw; height: 100vh\"\n    />\n\n    <v-main style=\"padding-top: 4rem !important\">\n      <!-- min-height helps keep content centered, use .debug to to see it -->\n      <center\n        class=\"py-8 mx-auto d-flex flex-column justify-center items-center\"\n        style=\"width: 90vw; min-height: calc(100vh - 8rem)\"\n      >\n        <nuxt />\n      </center>\n    </v-main>\n\n    <!-- Translator mobile -->\n    <v-menu\n      top\n      left\n      offset-y\n      rounded=\"lg\"\n      nudge-top=\"16\"\n      class=\"d-flex flex-column\"\n      transition=\"slide-y-reverse-transition\"\n    >\n      <template v-slot:activator=\"{ on, attrs }\">\n        <v-btn text fab class=\"glass\" id=\"translator\" v-bind=\"attrs\" v-on=\"on\">\n          <v-icon>mdi-translate</v-icon>\n        </v-btn>\n      </template>\n      <v-list class=\"py-0\">\n        <v-list-item\n          v-for=\"(lang, index) in langs\"\n          :key=\"index\"\n          link\n          :class=\"$vuetify.lang.current === lang.locale ? 'primary--text' : ''\"\n          @click=\"changeLocale(lang.locale)\"\n        >\n          <v-list-item-title v-text=\"lang.name\"></v-list-item-title>\n        </v-list-item>\n      </v-list>\n    </v-menu>\n\n    <!--   Debugger Notification   -->\n    <v-snackbar\n      v-model=\"alert.show\"\n      :timeout=\"-1\"\n      class=\"ma-4 desktop-only\"\n      transition=\"slide-y-reverse-transition\"\n      color=\"primary\"\n      bottom\n      left\n      text\n    >\n      <v-icon color=\"primary\" class=\"mr-4\">mdi-alert-circle-outline</v-icon>\n      <span class=\"my-auto\" v-html=\"alert.html\"></span>\n\n      <template #action=\"{ attrs }\">\n        <v-btn\n          v-bind=\"attrs\"\n          color=\"primary\"\n          text\n          icon\n          @click=\"alert.show = false\"\n        >\n          <v-icon>mdi-close-circle-outline</v-icon>\n        </v-btn>\n      </template>\n    </v-snackbar>\n  </v-app>\n</template>\n\n<script>\nexport default {\n  data: () => ({\n    links: [\n      { name: \"home\", path: \"/\" },\n      { name: \"install\", path: \"/install\" },\n      { name: \"api\", path: \"/docs\" },\n      { name: \"help\", path: \"/help\" },\n      { name: \"faq\", path: \"/faq\" },\n      { name: \"donate\", path: \"/donate\" },\n      { name: \"links\", path: \"/links\" },\n    ],\n    langs: [\n      { name: \"English\", locale: \"en\" },\n      { name: \"Español\", locale: \"es\" },\n      { name: \"Türkçe\", locale: \"tr\" },\n      { name: \"Português (Brasil)\", locale: \"pt_BR\" },\n      { name: \"Русский\", locale: \"ru\" },\n      { name: \"Čeština\", locale: \"cs\" },\n      { name: \"日本語\", locale: \"ja\" },\n      { name: \"Français\", locale: \"fr\" },\n      { name: \"Deutsch\", locale: \"de\" },\n      { name: \"Українська\", locale: \"uk\" },\n      { name: \"한국어\", locale: \"ko\" },\n      { name: \"Polski\", locale: \"pl\" },\n      { name: \"Bahasa Indonesia\", locale: \"id\" },\n      { name: \"Tiếng Việt\", locale: \"vi\"},\n    ],\n    alert: {\n      show: false,\n      html: \"\",\n    },\n  }),\n  created() {\n    // fetch locale preference or browser default\n    if (process.client && navigator.language) {\n      if (!(\"locale\" in localStorage))\n        this.$vuetify.lang.current = navigator.language.slice(0, 2);\n      else this.$vuetify.lang.current = localStorage.locale;\n    }\n  },\n  mounted() {\n    setTimeout(() => {\n      // Chrome < 70 or FF < 60 unsupported warning popup\n      if (\n        (this.$ua._parsed.name == \"Chrome\" &&\n          parseInt(this.$ua._parsed.version.split(\".\")[0]) < 70) ||\n        (this.$ua._parsed.name == \"Firefox\" &&\n          parseInt(this.$ua._parsed.version.split(\".\")[0]) < 60)\n      ) {\n        this.alert.html = `<b style=\"background: #222; border-radius: .5rem; padding: .25rem .25rem .25rem .5rem; margin: 0 .25rem;\">${this.$ua._parsed.name} ${this.$ua._parsed.version.split(\".\")[0]}</b> ${this.$vuetify.lang.t(\"$vuetify.layout.notSupported\")} ${this.$vuetify.lang.t(\"$vuetify.layout.considerUpgrade\")}`;\n        this.alert.show = true;\n      }\n    }, 1000);\n  },\n  methods: {\n    changeLocale(locale) {\n      // reset to browser default if selection matches browser default\n      if (locale == navigator.language.slice(0, 2)) localStorage.clear();\n      // else save preference\n      else localStorage.locale = locale;\n      this.$vuetify.lang.current = locale;\n    },\n  },\n};\n</script>\n\n<style>\nhtml,\nbody {\n  height: 100%;\n  background: #111;\n  height: -webkit-fill-available; /* for MacOS/iOS overscroll */\n  scrollbar-color: #424242 #111;\n}\n\n::selection {\n  background: #f44;\n  color: #111;\n}\n\n::-webkit-scrollbar {\n  width: 1rem;\n}\n\n::-webkit-scrollbar-track {\n  background: #111; /* color of the tracking area */\n}\n\n::-webkit-scrollbar-thumb {\n  background-color: #333; /* color of the scroll thumb */\n  border-radius: 1rem 0 0 1rem; /* roundness of the scroll thumb */\n  border-bottom: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-left: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-top: 0.25rem solid #111; /* creates padding around scroll thumb */\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background-color: #f22; /* color of the scroll thumb */\n  border-radius: 1rem 0 0 1rem; /* roundness of the scroll thumb */\n  border-bottom: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-left: 0.25rem solid #111; /* creates padding around scroll thumb */\n  border-top: 0.25rem solid #111; /* creates padding around scroll thumb */\n}\n\n.debug {\n  /* usage: add class=\"debug\" to the element */\n  outline: 2px solid red;\n}\n\n.v-sheet.v-snack__wrapper {\n  border-radius: 0.75rem !important;\n}\n\n.mainAltButton {\n  margin: 0.25em;\n}\n\n.title-text {\n  font-size: 3rem;\n}\n.topBar {\n  padding: 0 3rem;\n  width: fit-content !important;\n  border-radius: 1rem !important;\n  /* overflow: hidden; */\n}\n.glass {\n  backdrop-filter: blur(16px) saturate(200%);\n  -webkit-backdrop-filter: blur(16px) saturate(200%);\n  background: rgba(42, 42, 42, 0.75) !important;\n}\n\n/* used in docs.vue */\n.flex-wrapper {\n  display: flex;\n  flex-wrap: nowrap;\n}\n\n#translator {\n  border-radius: 1rem !important;\n  position: fixed;\n  bottom: 2rem;\n  right: 1rem;\n}\n\n@media (max-width: 768px) {\n  /* mobile */\n  .title-text {\n    font-size: 2rem;\n  }\n  .topBar {\n    width: calc(\n      100vw - 2rem\n    ) !important; /* (2rem = mx-4) 1rem on left, 1rem on right */\n    padding: 0;\n  }\n  .flex-wrapper {\n    display: flex;\n    flex-wrap: wrap;\n  }\n  #translator {\n    bottom: 1rem;\n  }\n}\n\n/* used in docs.vue, help.vue and faq.vue */\n.width-constraint {\n  max-width: auto;\n  margin: 0 auto;\n}\n@media (min-width: 960px) {\n  /* tablet */\n  .width-constraint {\n    width: 75vw;\n  }\n}\n@media (min-width: 1264px) {\n  /* desktop */\n  .width-constraint {\n    width: 60vw;\n  }\n}\n\n@media (min-width: 1904) {\n  /* 4k/ultrawide */\n  .width-constraint {\n    width: 42vw;\n  }\n}\n\n/* animations and all that */\n.swoop-in-enter-active,\n.swoop-in-leave-active,\n.swoop-out-enter-active,\n.swoop-out-leave-active,\n.swoop-left-enter-active,\n.swoop-left-leave-active,\n.swoop-right-enter-active,\n.swoop-right-leave-active {\n  transition-duration: 0.1s;\n  transition-property: opacity, transform;\n}\n\n.swoop-left-enter,\n.swoop-right-leave-active {\n  opacity: 0;\n  transform: translate(1rem, 0);\n}\n\n.swoop-left-leave-active,\n.swoop-right-enter {\n  opacity: 0;\n  transform: translate(-1rem, 0);\n}\n\n.swoop-in-enter,\n.swoop-out-leave-active {\n  opacity: 0;\n  transform: scale(0.9);\n}\n\n.swoop-in-leave-active,\n.swoop-out-enter {\n  opacity: 0;\n  transform: scale(1.1);\n}\n\n.fly-in-from-top {\n  opacity: 0;\n  transform: scale(0.8) translateY(-12rem);\n  animation: fly-in-from-top 0.5s 0.3s ease forwards;\n}\n\n@keyframes fly-in-from-top {\n  0% {\n    opacity: 0;\n    transform: scale(0.8) translateY(-12rem);\n  }\n  100% {\n    opacity: 1;\n    transform: scale(1) translateY(0);\n  }\n}\n\n/* reduced-motion animations */\n@media (prefers-reduced-motion) {\n  .fly-in-from-top {\n    opacity: 1;\n    transform: none;\n    animation: none;\n  }\n\n  .swoop-in-enter-active,\n  .swoop-in-leave-active,\n  .swoop-out-enter-active,\n  .swoop-out-leave-active,\n  .swoop-left-enter-active,\n  .swoop-left-leave-active,\n  .swoop-right-enter-active,\n  .swoop-right-leave-active {\n    transition-duration: 0.05s;\n    transition-property: opacity;\n  }\n\n  .swoop-left-enter,\n  .swoop-right-leave-active {\n    opacity: 0;\n    transform: none;\n  }\n\n  .swoop-left-leave-active,\n  .swoop-right-enter {\n    opacity: 0;\n    transform: none;\n  }\n\n  .swoop-in-enter,\n  .swoop-out-leave-active {\n    opacity: 0;\n    transform: none;\n  }\n\n  .swoop-in-leave-active,\n  .swoop-out-enter {\n    opacity: 0;\n    transform: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "Website/layouts/error.vue",
    "content": "<template>\n  <v-app dark>\n    <h1 v-if=\"error.statusCode === 404\">\n      {{ pageNotFound }}\n    </h1>\n    <h1 v-else>\n      {{ otherError }}\n    </h1>\n    <NuxtLink to=\"/\"> Home page </NuxtLink>\n  </v-app>\n</template>\n\n<script>\nexport default {\n  layout: \"empty\",\n  props: {\n    error: {\n      type: Object,\n      default: null,\n    },\n  },\n  data() {\n    return {\n      pageNotFound: \"404 Not Found\",\n      otherError: \"An error occurred\",\n    };\n  },\n  head() {\n    const title =\n      this.error.statusCode === 404 ? this.pageNotFound : this.otherError;\n    return {\n      title,\n    };\n  },\n};\n</script>\n\n<style scoped>\nh1 {\n  font-size: 20px;\n}\n</style>\n"
  },
  {
    "path": "Website/nuxt.config.js",
    "content": "import en from \"./_locales/en\";\nimport es from \"./_locales/es\";\nimport tr from \"./_locales/tr\";\nimport ru from \"./_locales/ru\";\nimport cs from \"./_locales/cs\";\nimport ja from \"./_locales/ja\";\nimport fr from \"./_locales/fr\";\nimport uk from \"./_locales/uk\";\nimport ko from \"./_locales/ko\";\nimport pl from \"./_locales/pl\";\nimport de from \"./_locales/de\";\nimport hu from \"./_locales/hu\";\nimport id from \"./_locales/id\";\nimport vi from \"./_locales/vi\";\n// ...\nexport default {\n  // Global page headers: https://go.nuxtjs.dev/config-head\n  head: {\n    titleTemplate: \"Return YouTube Dislike\",\n    title: \"Return YouTube Dislike\",\n    htmlAttrs: {\n      lang: \"en\",\n    },\n    meta: [\n      { charset: \"utf-8\" },\n      { name: \"viewport\", content: \"width=device-width, initial-scale=1\" },\n\n      {\n        hid: \"description\",\n        name: \"description\",\n        content:\n          \"An extension that returns dislike statistics to YouTube using a combination of scraped dislike stats and estimates extrapolated from extension user data.\",\n      },\n      { hid: \"og:image\", name: \"og:image\", content: \"/logo.png\" },\n      { hid: \"theme-color\", name: \"theme-color\", content: \"#ff0000\" },\n    ],\n    link: [\n      { rel: \"icon\", type: \"image/svg+xml\", href: \"/logo.svg\" },\n      { rel: \"icon\", type: \"image/x-icon\", href: \"/favicon.ico\" },\n    ],\n  },\n\n  env: {\n    apiUrl: \"https://returnyoutubedislikeapi.com\",\n  },\n\n  server: { host: \"0.0.0.0\", port: 80 }, //LINE FOR DEBUGGING ONLY\n\n  target: \"static\",\n  css: [],\n  plugins: [],\n  components: true,\n  buildModules: [\"@nuxtjs/vuetify\"],\n  modules: [\"@nuxtjs/axios\", \"nuxt-user-agent\"],\n\n  // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify\n  vuetify: {\n    lang: {\n      locales: { en, es, tr, ru, cs, ja, fr, uk, ko, pl, de, hu, id, vi  /*...*/ },\n      current: \"en\",\n    },\n    theme: {\n      dark: true,\n      themes: {\n        dark: {\n          primary: \"#ff4444\",\n        },\n      },\n    },\n  },\n  /*\n  build: {\n    extend(config, ctx) {\n      // Run ESLint on save (dev-only)\n      if (ctx.isDev && ctx.isClient) {\n        config.module.rules.push({\n          enforce: \"pre\",\n          test: /\\.(js|vue)$/,\n          loader: \"eslint-loader\",\n          exclude: /(node_modules)/,\n        });\n      }\n    },\n  }\n*/\n};\n"
  },
  {
    "path": "Website/package.json",
    "content": "{\n  \"name\": \"return-youtube-dislike-site\",\n  \"version\": \"1.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"set NODE_OPTIONS=--openssl-legacy-provider && nuxt\",\n    \"build\": \"nuxt build\",\n    \"start\": \"nuxt start\",\n    \"generate\": \"set NODE_OPTIONS=--openssl-legacy-provider && nuxt generate\",\n    \"lint\": \"eslint --fix --ext .js,.vue --ignore-path .eslintignore .\"\n  },\n  \"dependencies\": {\n    \"@nuxtjs/axios\": \"^5.13.6\",\n    \"core-js\": \"^3.44.0\",\n    \"nuxt\": \"^2.15.7\",\n    \"nuxt-user-agent\": \"^1.2.2\",\n    \"vuetify\": \"^2.5.5\"\n  },\n  \"devDependencies\": {\n    \"@nuxtjs/vuetify\": \"^1.12.3\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^9.31.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-loader\": \"^4.0.2\",\n    \"eslint-plugin-prettier\": \"^4.0.0\",\n    \"eslint-plugin-vue\": \"^8.2.0\",\n    \"prettier\": \"^2.5.1\"\n  }\n}"
  },
  {
    "path": "Website/pages/debug.vue",
    "content": "<template>\n  <div>\n    <v-stepper :value=\"progress\" class=\"mt-12\" outlined max-width=\"800px\">\n      <v-stepper-header>\n        <v-stepper-step step=\"1\" :complete=\"steps.one\">Setup</v-stepper-step>\n        <v-divider />\n        <v-stepper-step step=\"2\" :complete=\"steps.two\"\n          >Extension Status</v-stepper-step\n        >\n        <v-divider />\n        <v-stepper-step step=\"3\" :complete=\"steps.three\"\n          >Server Connection</v-stepper-step\n        >\n        <v-divider />\n        <v-stepper-step step=\"4\" :complete=\"steps.four\"\n          >Browser Support</v-stepper-step\n        >\n        <v-divider />\n        <v-stepper-step step=\"5\" :complete=\"steps.five\">Report</v-stepper-step>\n      </v-stepper-header>\n\n      <v-stepper-content step=\"1\">\n        <h1>Getting Ready...</h1>\n        <v-progress-circular\n          indeterminate\n          size=\"50\"\n          width=\"5\"\n          color=\"primary\"\n        />\n      </v-stepper-content>\n\n      <v-stepper-content step=\"2\">\n        <h1>Ensuring the Extension is Running...</h1>\n        <v-progress-circular\n          indeterminate\n          size=\"50\"\n          width=\"5\"\n          color=\"primary\"\n        />\n      </v-stepper-content>\n\n      <v-stepper-content step=\"3\">\n        <h1>Testing Server Connection...</h1>\n        <v-progress-circular\n          indeterminate\n          size=\"50\"\n          width=\"5\"\n          color=\"primary\"\n        />\n      </v-stepper-content>\n\n      <v-stepper-content step=\"4\">\n        <h1>Checking Browser Information...</h1>\n        <v-progress-circular\n          indeterminate\n          size=\"50\"\n          width=\"5\"\n          color=\"primary\"\n        />\n      </v-stepper-content>\n\n      <v-stepper-content step=\"5\" style=\"text-align: left\">\n        <div class=\"reportHeader\">\n          <h1>Browser</h1>\n          <v-divider style=\"transform: translateY(1.5em)\" />\n        </div>\n        <v-alert\n          dense\n          outlined\n          :type=\"notices.browser.type\"\n          v-text=\"notices.browser.text\"\n        />\n        <span><b>BROWSER-</b> {{ userInformation.browser.name }}</span\n        ><br />\n        <span><b>VENDOR-</b> {{ userInformation.browser.vendor }} </span><br />\n        <span><b>VERSION-</b> {{ userInformation.browser.version }}</span\n        ><br />\n\n        <div class=\"reportHeader\">\n          <h1>System</h1>\n          <v-divider style=\"transform: translateY(1.5em)\" />\n        </div>\n        <v-alert\n          dense\n          outlined\n          :type=\"notices.system.type\"\n          v-text=\"notices.system.text\"\n        />\n        <span><b>OS-</b> {{ userInformation.system.os }}</span\n        ><br />\n        <span><b>VERSION-</b> {{ userInformation.system.version }} </span><br />\n        <span><b>TYPE-</b> {{ userInformation.system.type }}</span\n        ><br />\n\n        <div class=\"reportHeader\">\n          <h1>Extension</h1>\n          <v-divider style=\"transform: translateY(1.5em)\" />\n        </div>\n        <v-alert\n          dense\n          outlined\n          :type=\"notices.extension.type\"\n          v-text=\"notices.extension.text\"\n        />\n        <span\n          ><b>LATEST EXTENSION VERSION-</b>\n          {{\n            userInformation.extension.latestExtensionVersion ||\n            \"Failed to lookup data\"\n          }}</span\n        ><br />\n        <span\n          ><b>SERVER CONNECTION-</b>\n          {{\n            userInformation.extension.serverConnection\n              ? \"Working\"\n              : \"Failed to connect\"\n          }}</span\n        ><br />\n      </v-stepper-content>\n    </v-stepper>\n  </div>\n</template>\n\n<script>\nexport default {\n  data() {\n    return {\n      stepTime: 2500,\n      supportedBrowsers: [\"Firefox\", \"Chrome\", \"Brave\", \"Edge\", \"Opera\"],\n\n      progress: 1,\n      steps: {\n        one: false,\n        two: false,\n        three: false,\n        four: false,\n        five: false,\n      },\n\n      userInformation: {\n        browser: {\n          name: this.$ua._parsed.name,\n          vendor: this.$ua._parsed.vendor,\n          version: this.$ua._parsed.version,\n        },\n        system: {\n          os: this.$ua._parsed.os,\n          version: this.$ua._parsed.os_version,\n          type: this.$ua._parsed.category,\n        },\n        extension: {\n          serverConnection: null,\n          latestExtensionVersion: null,\n        },\n      },\n\n      notices: {\n        system: {\n          text: null,\n          type: null,\n        },\n        browser: {\n          text: null,\n          type: null,\n        },\n        extension: {\n          text: null,\n          type: null,\n        },\n      },\n    };\n  },\n\n  mounted() {\n    //---   Init Stuff   ---//\n    setTimeout(() => {\n      this.$axios\n        .$get(\n          \"https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/combined/manifest-chrome.json\",\n        )\n        .then((res) => {\n          this.userInformation.extension.latestExtensionVersion = res.version;\n        });\n\n      this.progress++;\n      this.steps.one = true;\n    }, this.stepTime);\n\n    //---   Check If Extension Is Running   ---//\n    setTimeout(() => {\n      this.progress++;\n      this.steps.two = true;\n    }, this.stepTime * 2);\n\n    //---   Check Server Connection ---//\n    setTimeout(() => {\n      this.$axios\n        .$get(\"https://returnyoutubedislikeapi.com/votes?videoId=QOFEgexls14\")\n        .then(() => {\n          this.userInformation.extension.serverConnection = true;\n        })\n        .catch(() => {\n          this.userInformation.extension.serverConnection = false;\n        });\n\n      this.progress++;\n      this.steps.three = true;\n    }, this.stepTime * 3);\n\n    setTimeout(() => {\n      this.progress++;\n      this.steps.four = true;\n      //this.steps.five = true;\n\n      //---   Parse Extension Data   ---//\n      this.notices.extension.text = `We are unable to automatically check that your extension is up to date. Please check that the number below matches your extension version.`;\n      this.notices.extension.type = \"warning\";\n\n      if (this.userInformation.extension.serverConnection != true) {\n        this.notices.extension.text = `Failed to connect to the server!`;\n        this.notices.extension.type = \"error\";\n      }\n\n      //---   Parse System Compatibility   ---//\n      this.notices.system.text = `${this.userInformation.system.os} is supported!`;\n      this.notices.system.type = \"success\";\n\n      if (this.userInformation.system.type != \"pc\") {\n        this.notices.system.text = `\"${this.userInformation.system.type}\" may not be a supported device type!`;\n        this.notices.system.type = \"warning\";\n      }\n\n      //---   Parse Browser Compatibility   ---//\n      this.notices.browser.text = `${this.userInformation.browser.name} ${this.userInformation.browser.version} is supported!`;\n      this.notices.browser.type = \"success\";\n\n      if (!this.supportedBrowsers.includes(this.userInformation.browser.name)) {\n        this.notices.browser.text = `${this.userInformation.browser.name} is not a supported browser! You may continue to use the extension, but we don't provide official support.`;\n        this.notices.browser.type = \"warning\";\n      }\n    }, this.stepTime * 4);\n  },\n};\n</script>\n\n<style scoped>\n.reportHeader {\n  display: flex;\n  margin-top: 1em;\n}\n</style>\n"
  },
  {
    "path": "Website/pages/docs/endpoints.vue",
    "content": "<template>\n  <div style=\"line-height: 2rem\">\n    <h2 class=\"primary--text\">\n      {{ $vuetify.lang.t(\"$vuetify.api.endpoints.title\") }}\n    </h2>\n    <div class=\"mt-4\">\n      {{ $vuetify.lang.t(\"$vuetify.api.endpoints.subtitle\") }}\n      <a :href=\"endpointUrl\" target=\"_blank\" v-text=\"endpointUrl\" />\n    </div>\n  </div>\n</template>\n\n<script>\nlet apiUrl = process.env.apiUrl;\nexport default {\n  data() {\n    return {\n      apiUrl: apiUrl,\n      endpointUrl: apiUrl + \"/swagger/index.html\",\n    };\n  },\n};\n</script>\n"
  },
  {
    "path": "Website/pages/docs/fetching.vue",
    "content": "<template>\n  <div style=\"line-height: 2rem\">\n    <h2 class=\"primary--text\">\n      {{ $vuetify.lang.t(\"$vuetify.api.fetching.title\") }}\n    </h2>\n\n    <div class=\"ma-2\">\n      {{ $vuetify.lang.t(\"$vuetify.api.fetching.subtitle\") }}\n\n      <a href=\"https://youtube.com/watch?v=kxOuG8jMIgI\" target=\"_blank\">\n        kxOuG8jMIgI\n      </a>\n    </div>\n\n    <h3 class=\"mt-6\">{{ $vuetify.lang.t(\"$vuetify.api.fetching.title2\") }}</h3>\n    <div class=\"ma-2\">\n      {{ $vuetify.lang.t(\"$vuetify.api.fetching.url\") }}\n      <a\n        :href=\"apiUrl + '/votes?videoId=kxOuG8jMIgI'\"\n        target=\"_blank\"\n        v-text=\"apiUrl + '/votes?videoId=kxOuG8jMIgI'\"\n      />\n      <br />\n      {{ $vuetify.lang.t(\"$vuetify.api.fetching.method\") }}\n      <a\n        href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET\"\n        target=\"_blank\"\n      >\n        HTTP/GET\n      </a>\n    </div>\n    <div class=\"mt-4 ml-2\">\n      {{ $vuetify.lang.t(\"$vuetify.api.fetching.headers\") }}\n    </div>\n    <div class=\"code px-4 py-2\">\n      Accept: text/html,application/xhtml+xml,application/xml;q=0.9<br />\n      Pragma: no-cache<br />\n      Cache-Control: no-cache<br />\n      Connection: keep-alive\n    </div>\n    <div class=\"mt-4 ml-2\">\n      {{ $vuetify.lang.t(\"$vuetify.api.fetching.response\") }}\n    </div>\n    <div class=\"code px-4 py-2\">\n      {\n      <br />\n      &nbsp;\"id\": \"kxOuG8jMIgI\",<br />\n      &nbsp;\"dateCreated\": \"2021-12-20T12:25:54.418014Z\",<br />\n      &nbsp;\"likes\": 27326,<br />\n      &nbsp;\"dislikes\": 498153,<br />\n      &nbsp;\"rating\": 1.212014408444885,<br />\n      &nbsp;\"viewCount\": 3149885,<br />\n      &nbsp;\"deleted\": false<br />\n      }\n    </div>\n    <br />\n    <v-alert border=\"left\" color=\"orange\" text type=\"info\" class=\"mb-0\">\n      <span>{{ $vuetify.lang.t(\"$vuetify.api.fetching.error1\") }}</span>\n      <br />\n      <span>{{ $vuetify.lang.t(\"$vuetify.api.fetching.error2\") }}</span>\n    </v-alert>\n  </div>\n</template>\n\n<script>\nexport default {\n  data() {\n    return {\n      apiUrl: process.env.apiUrl,\n    };\n  },\n};\n</script>\n\n<style scoped>\n.code {\n  color: #aaa;\n  background: #353535;\n  border-radius: 0.5rem;\n  font-family: monospace;\n  line-height: 2rem;\n}\n</style>\n"
  },
  {
    "path": "Website/pages/docs/index.vue",
    "content": "<template>\n  <div style=\"line-height: 2rem\">\n    <h2 class=\"primary--text\">{{ $vuetify.lang.t(\"$vuetify.api.title\") }}</h2>\n    <div class=\"mt-4\">{{ $vuetify.lang.t(\"$vuetify.api.subtitle\") }}</div>\n  </div>\n</template>\n"
  },
  {
    "path": "Website/pages/docs/url.vue",
    "content": "<template>\n  <div style=\"line-height: 2rem\">\n    <h2 class=\"primary--text\">\n      {{ $vuetify.lang.t(\"$vuetify.api.url.title\") }}\n    </h2>\n    <div class=\"mt-4\">\n      {{ $vuetify.lang.t(\"$vuetify.api.url.subtitle\") }}\n      <a :href=\"apiUrl\" target=\"_blank\" v-text=\"apiUrl\" />\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  data() {\n    return {\n      apiUrl: process.env.apiUrl,\n    };\n  },\n};\n</script>\n"
  },
  {
    "path": "Website/pages/docs/usage-rights.vue",
    "content": "<template>\n  <div style=\"line-height: 2rem\">\n    <h2 class=\"primary--text\">\n      {{ $vuetify.lang.t(\"$vuetify.api.rights.title\") }}\n    </h2>\n    <div class=\"my-4\">\n      {{ $vuetify.lang.t(\"$vuetify.api.rights.subtitle\") }}\n    </div>\n    <ul>\n      <li class=\"my-4\">\n        <b>{{ $vuetify.lang.t(\"$vuetify.api.rights.bullet1\") }}</b>\n        {{ $vuetify.lang.t(\"$vuetify.api.rights.bullet1text\") }}\n      </li>\n      <li class=\"my-4\">\n        <b>{{ $vuetify.lang.t(\"$vuetify.api.rights.bullet2\") }}</b>\n        {{ $vuetify.lang.t(\"$vuetify.api.rights.bullet2text\") }}\n      </li>\n    </ul>\n  </div>\n</template>\n"
  },
  {
    "path": "Website/pages/docs.vue",
    "content": "<template>\n  <!-- min-height overrides vertical centering from the parent default.vue layout -->\n  <div\n    class=\"width-constraint flex-wrapper\"\n    style=\"min-height: calc(100vh - 10rem); position: relative\"\n  >\n    <!-- docs navigation  -->\n    <v-list\n      class=\"py-0 mr-3\"\n      style=\"\n        background: transparent;\n        position: sticky;\n        top: 6rem;\n        align-self: flex-start;\n      \"\n    >\n      <v-list-item\n        v-for=\"(link, i) in links\"\n        :key=\"i\"\n        :to=\"link.to\"\n        router\n        class=\"mb-4\"\n        color=\"primary\"\n        style=\"overflow: hidden !important; border-radius: 0.75rem\"\n      >\n        <v-list-item-title style=\"text-align: right\">\n          <v-list-item-title\n            v-text=\"$vuetify.lang.t(`$vuetify.api.${link.name}.title`)\"\n          />\n        </v-list-item-title>\n        <v-list-item-icon>\n          <v-icon v-text=\"link.icon\" />\n        </v-list-item-icon>\n      </v-list-item>\n    </v-list>\n\n    <!-- docs content -->\n    <v-card\n      class=\"text-left glass pa-6\"\n      style=\"\n        flex-grow: 2;\n        height: max-content;\n        max-width: 90vw !important;\n        border-radius: 0.75rem;\n      \"\n    >\n      <NuxtChild />\n    </v-card>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name)\n      ? \"swoop-left\"\n      : \"swoop-right\";\n  },\n  data() {\n    return {\n      //---   Links To Generate Above    ---//\n      links: [\n        {\n          name: \"rights\",\n          icon: \"mdi-book-open-variant\",\n          to: \"/docs/usage-rights\",\n        },\n        {\n          name: \"url\",\n          icon: \"mdi-web\",\n          to: \"/docs/url\",\n        },\n        {\n          name: \"endpoints\",\n          icon: \"mdi-transit-connection-variant\",\n          to: \"/docs/endpoints\",\n        },\n        {\n          name: \"fetching\",\n          icon: \"mdi-school\",\n          to: \"/docs/fetching\",\n        },\n      ],\n    };\n  },\n};\n</script>\n"
  },
  {
    "path": "Website/pages/donate.vue",
    "content": "<template>\n  <div>\n    <h1 class=\"title-text\">\n      {{ $vuetify.lang.t(\"$vuetify.donate.name\") }}\n    </h1>\n    <p style=\"color: #999; margin-top: 0.5rem; margin-bottom: 1.5rem\">\n      {{ $vuetify.lang.t(\"$vuetify.donate.subtitle\") }}\n    </p>\n    <v-btn class=\"mainAltButton mb-2\" :href=\"patreonLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-patreon</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.donate.patreon\") }}\n    </v-btn>\n    <v-btn class=\"mainAltButton mb-2\" :to=\"cryptoLink\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-bitcoin</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.donate.crypto\") }}\n    </v-btn>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name)\n      ? \"swoop-left\"\n      : \"swoop-right\";\n  },\n  data: () => ({\n    patreonLink: \"https://www.patreon.com/returnyoutubedislike\",\n    cryptoLink: \"/pay/crypto\",\n  }),\n};\n</script>\n"
  },
  {
    "path": "Website/pages/faq.vue",
    "content": "<template>\n  <div class=\"width-constraint\">\n    <h1 class=\"title-text\">\n      {{ $vuetify.lang.t(\"$vuetify.faq.title\") }}\n    </h1>\n    <p style=\"color: #999; margin-top: 0.5rem; margin-bottom: 1.5rem\">\n      {{ $vuetify.lang.t(\"$vuetify.faq.subtitle\") }}\n    </p>\n\n    <v-expansion-panels>\n      <v-expansion-panel v-for=\"i in [1, 2, 3, 4, 5, 6, 7]\" :key=\"i\">\n        <v-expansion-panel-header>\n          {{ $vuetify.lang.t(`$vuetify.faq.bullet${i}`) }}\n        </v-expansion-panel-header>\n        <v-expansion-panel-content class=\"text-left\">\n          <hr style=\"border-color: #444\" />\n          <br />\n          <span v-html=\"$vuetify.lang.t(`$vuetify.faq.bullet${i}text`)\" />\n        </v-expansion-panel-content>\n      </v-expansion-panel>\n    </v-expansion-panels>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name)\n      ? \"swoop-left\"\n      : \"swoop-right\";\n  },\n};\n</script>\n"
  },
  {
    "path": "Website/pages/help.vue",
    "content": "<template>\n  <div class=\"width-constraint\">\n    <h1 class=\"title-text pt-12\">\n      {{ $vuetify.lang.t(\"$vuetify.help.title\") }}\n    </h1>\n    <ol style=\"line-height: 3rem; color: #aaa\" class=\"text-left\">\n      <li>\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet1\") }}\n        <code style=\"color: #eee\">\n          <b>{{ version }}</b>\n        </code>\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet11\") }}\n      </li>\n      <li>\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet2\") }}\n      </li>\n      <li>\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet3\") }}\n        <a\n          class=\"px-2 py-1\"\n          style=\"background: #222; border-radius: 0.25rem\"\n          href=\"https://returnyoutubedislikeapi.com/votes?videoId=QOFEgexls14\"\n        >\n          https://returnyoutubedislikeapi.com/votes?videoId=QOFEgexls14\n        </a>\n        , <br />\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet31\") }}\n        <br />\n        <span style=\"color: #eee\">\n          {\"id\":\"QOFEgexls14\", \"dateCreated\":\"2021-12-15T16:54:12.250813Z\",\n          \"likes\":2907, \"dislikes\":215, \"rating\":4.725641025641026,\n          \"viewCount\":28222, \"deleted\":false}\n        </span>\n      </li>\n      <li>\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet4\") }}\n        <code>#bugs-and-problems</code>\n        {{ $vuetify.lang.t(\"$vuetify.help.bullet41\") }}\n        <v-btn\n          class=\"mainAltButton\"\n          style=\"\n            font-size: 0.5rem;\n            height: 1.5rem;\n            color: #aaa;\n            padding-left: 0.25rem !important;\n            padding-right: 0.5rem !important;\n          \"\n          :href=\"discordLink\"\n          target=\"_blank\"\n        >\n          <v-icon size=\"1rem\" style=\"margin-right: 0.5em\">mdi-discord</v-icon>\n          {{ $vuetify.lang.t(\"$vuetify.links.discord\") }}\n        </v-btn>\n        <ol type=\"a\">\n          <li>\n            {{ $vuetify.lang.t(\"$vuetify.help.bullet4a\") }}\n            <v-btn\n              class=\"mainAltButton\"\n              style=\"\n                height: 1.5rem;\n                font-size: 0.75rem;\n                text-transform: none !important;\n                padding-left: 0.5rem !important;\n                padding-right: 0.25rem !important;\n              \"\n              target=\"_blank\"\n              @click=\"copyToClipboard(platform)\"\n            >\n              <v-icon size=\".75rem\" color=\"primary\" style=\"margin-right: 0.5em\"\n                >mdi-content-copy</v-icon\n              >\n              <span style=\"color: #f44\"> {{ $vuetify.lang.t(\"$vuetify.help.detected\") }} </span>\n              &nbsp;\n              {{ platform }}\n            </v-btn>\n          </li>\n\n          <li style=\"position: relative; width: 100%\">\n            {{ $vuetify.lang.t(\"$vuetify.help.bullet4b\") }}\n            <code>F12</code>\n            {{ $vuetify.lang.t(\"$vuetify.help.bullet4b1\") }}\n\n            <img\n              loading=\"eager\"\n              width=\"100%\"\n              style=\"border-radius: 1rem; border: 2px solid #333\"\n              src=\"ui/troubleshooting.webp\"\n              :alt=\"$vuetify.lang.t('$vuetify.help.altExampleScreenshot')\"\n            />\n          </li>\n\n          <li>\n            {{ $vuetify.lang.t(\"$vuetify.help.bullet4c\") }}\n            <br />\n            {{ $vuetify.lang.t(\"$vuetify.help.bullet4c1\") }}\n            <br />\n            <code>about:addons</code>\n            {{ $vuetify.lang.t(\"$vuetify.help.firefox\") }}\n            <br />\n            <code>chrome://extensions</code>\n            {{ $vuetify.lang.t(\"$vuetify.help.chrome\") }}\n          </li>\n        </ol>\n      </li>\n    </ol>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name)\n      ? \"swoop-left\"\n      : \"swoop-right\";\n  },\n  data() {\n    return {\n      platform:\n        this.$ua._parsed.os +\n        \" \" +\n        this.$ua._parsed.os_version +\n        \", \" +\n        this.$ua._parsed.name +\n        \" \" +\n        this.$ua._parsed.version,\n      version: \"loading\",\n      discordLink: \"https://discord.gg/mYnESY4Md5\",\n    };\n  },\n  mounted() {\n    fetch(\n      \"https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/combined/manifest-chrome.json\",\n    )\n      .then((response) => response.json())\n      .then((json) => {\n        this.version = json.version;\n      });\n    // .catch(console.error);\n  },\n  methods: {\n    copyToClipboard(text) {\n      navigator.clipboard.writeText(\"```\" + text + \"```\");\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "Website/pages/index.vue",
    "content": "<template>\n  <div class=\"d-flex flex-column justify-between\" style=\"height: calc(100vh - 10rem)\">\n    <div class=\"col\"></div>\n\n    <div class=\"col\">\n      <svg id=\"thumbslogo\" class=\"mb-4\" width=\"150\" height=\"150\" viewBox=\"0 0 24 24\" overflow=\"visible\">\n        <path\n          d=\"M14.9 3H6c-.9 0-1.6.5-1.9 1.2l-3 7c-.1.3-.1.5-.1.7v2c0 1.1.9 2 2 2h6.3l-.9 4.5c-.1.5 0 1 .4 1.4l1.1 1.1 6.5-6.6c.4-.4.6-.9.6-1.4V5c-.1-1.1-1-2-2.1-2zm7.4 12.8h-2.9c-.4 0-.7-.3-.7-.7V3.9c0-.4.3-.7.7-.7h2.9c.4 0 .7.3.7.7V15c0 .4-.3.8-.7.8z\"\n        />\n        <path id=\"plarrow\" d=\"m8 12.5 5.1-2.9L8 6.7v5.8z\" fill=\"#fff\" stroke=\"none\" />\n      </svg>\n\n      <h1 class=\"title-text\">\n        {{ $vuetify.lang.t(\"$vuetify.home.title\") }}\n      </h1>\n      <p class=\"mb-4\" style=\"color: #999; margin-top: 0\">\n        {{ $vuetify.lang.t(\"$vuetify.home.subtitle\") }}\n      </p>\n\n      <v-btn :to=\"installLink\" color=\"primary px-6\" style=\"font-size: 1.5em; padding: 1em; margin-bottom: 0.5em\">\n        <v-icon large class=\"mr-6\">mdi-tray-arrow-down</v-icon>\n        {{ $vuetify.lang.t(\"$vuetify.install.name\") }}\n      </v-btn>\n\n      <br />\n\n      <v-btn class=\"mainAltButton\" :href=\"githubLink\" target=\"_blank\">\n        <v-icon style=\"margin-right: 0.5em\">mdi-github</v-icon>\n        {{ $vuetify.lang.t(\"$vuetify.links.github\") }}\n      </v-btn>\n\n      <v-btn class=\"mainAltButton\" :href=\"discordLink\" target=\"_blank\">\n        <v-icon style=\"margin-right: 0.5em\">mdi-discord</v-icon>\n        {{ $vuetify.lang.t(\"$vuetify.links.discord\") }}\n      </v-btn>\n    </div>\n\n    <!--    <div class=\"mb-4\" style=\"color: #999\">-->\n    <!--      Get dislikes manually: <input placeholder=\" Video URL\">-->\n    <!--      <p id=\"output\"></p>-->\n    <!--    </div>-->\n\n    <v-spacer />\n    <div id=\"support-ukraine\" class=\"d-flex flex-column items-center py-2\">\n      <h3 class=\"mb-2\">\n        <v-img src=\"/ukraine-flag-xs.webp\" width=\"42px\" height=\"28px\"></v-img>\n        <a href=\"https://u24.gov.ua/\">\n          {{ $vuetify.lang.t(\"$vuetify.home.ukraine\") }}\n        </a>\n      </h3>\n    </div>\n\n    <div id=\"financiers\" class=\"d-flex flex-column items-center py-8\">\n      <h3 class=\"mb-4\">\n        <v-icon class=\"mb-2\">mdi-heart</v-icon>\n        {{ $vuetify.lang.t(\"$vuetify.home.sponsors\") }}\n      </h3>\n      <v-row class=\"justify-center mx-auto\">\n        <p v-for=\"sponsor in sponsors\" :key=\"sponsor.nameKey\" class=\"sponsor\">\n          <a :style=\"sponsor.link ? { cursor: 'pointer' } : { cursor: 'default' }\" :href=\"sponsor.link\" rel=\"sponsored\">\n            {{ $vuetify.lang.t(`$vuetify.${sponsor.nameKey}`) }}\n          </a>\n        </p>\n      </v-row>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name) ? \"swoop-left\" : \"swoop-right\";\n  },\n  data() {\n    return {\n      installLink: \"/install\",\n      githubLink: \"https://github.com/Anarios/return-youtube-dislike\",\n      discordLink: \"https://discord.gg/mYnESY4Md5\",\n      sponsors: [\n        {\n          nameKey: \"home.becomeSponsor\",\n          link: \"https://www.patreon.com/join/returnyoutubedislike/checkout?rid=8008601\",\n        },\n      ],\n    };\n  },\n  mounted() {\n    const YOUTUBE_REGEX =\n      /(?:http:|https:)*?\\/\\/(?:www\\.|)(?:youtube\\.com|m\\.youtube\\.com|youtu\\.|youtube-nocookie\\.com).*(?:v=|v%3D|v\\/|(?:a|p)\\/(?:a|u)\\/\\d.*\\/|watch\\?|vi(?:=|\\/)|\\/embed\\/|oembed\\?|be\\/|e\\/)([^&?%#/\\n]*)/;\n    let lastVideoId = \"\";\n    window.oninput = (e) => {\n      const videoId = (e.target.value.match(YOUTUBE_REGEX) || {})[1] || e.target.value;\n      if (videoId !== lastVideoId && videoId.length === 11) {\n        fetch(\"https://returnyoutubedislikeapi.com/votes?videoId=\" + (lastVideoId = videoId))\n          .then((resp) => resp.json())\n          .then(\n            (data) =>\n              (document.getElementById(\"output\").innerText = \"Likes=\" + data.likes + \" Dislikes=\" + data.dislikes)\n          );\n      }\n    };\n  },\n};\n</script>\n\n<style scoped>\n.sponsor {\n  margin: 1rem;\n  height: max-content;\n}\n\ninput {\n  background-color: #999999;\n}\n\n@media (max-width: 767px) {\n  .sponsor {\n    margin: 0.5rem;\n    height: max-content;\n  }\n}\n\n#thumbslogo {\n  opacity: 0;\n  fill: transparent;\n  stroke: #f44;\n  transition-property: opacity, transform;\n  transform: scale(0) rotate(180deg);\n  animation: popin 1s 0.3s ease-in-out 1 forwards, tap 0.3s 1.7s ease-in-out 1 forwards;\n}\n\n#plarrow {\n  opacity: 0;\n  transform: translateX(-0.25rem);\n  transition-property: opacity, transform;\n  animation: slidin 0.5s 1.7s ease 1 forwards;\n}\n\n@keyframes slidin {\n  0% {\n    opacity: 0;\n    transform: translateX(-0.25rem);\n  }\n  100% {\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n@keyframes popin {\n  0% {\n    transform: rotate(180deg) scale(0);\n    opacity: 0;\n  }\n  100% {\n    transform: rotate(0deg) scale(1);\n    opacity: 1;\n  }\n}\n\n@keyframes fadein {\n  0% {\n    opacity: 0;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n\n@keyframes tap {\n  0% {\n    fill: transparent;\n    stroke: #f44;\n    transform: scale(1);\n  }\n  25% {\n    fill: #f44;\n    stroke: none;\n    transform: scale(0.85);\n  }\n  100% {\n    fill: #f44;\n    stroke: none;\n    transform: scale(1);\n  }\n}\n\n/* reduced-motion animations */\n@media (prefers-reduced-motion) {\n  #thumbslogo {\n    opacity: 1;\n    fill: #f44;\n    stroke: none;\n    transform: none;\n    animation: none;\n  }\n\n  #thumbsripple {\n    opacity: 0;\n    transform: none;\n    animation: none;\n  }\n\n  #plarrow {\n    opacity: 0;\n    transform: none;\n    transition-property: opacity;\n    animation: fadein 0.5s 0.5s ease 1 forwards;\n  }\n}\n</style>\n"
  },
  {
    "path": "Website/pages/install.vue",
    "content": "<template>\n  <div>\n    <h1 class=\"title-text\">{{ $vuetify.lang.t(\"$vuetify.install.title\") }}</h1>\n\n    <p style=\"color: #999; margin-bottom: 1rem\">\n      {{ $vuetify.lang.t(\"$vuetify.install.subtitle\") }}\n    </p>\n\n    <v-btn class=\"mainAltButton mb-2\" :href=\"firefoxLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-firefox</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.firefox\") }}\n    </v-btn>\n\n    <v-btn class=\"mainAltButton mb-2\" :href=\"chromeLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-google-chrome</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.chrome\") }}\n    </v-btn>\n\n    <v-btn class=\"mainAltButton mb-2\" :href=\"chromeLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-microsoft-edge</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.edge\") }}\n    </v-btn>\n\n    <v-btn class=\"mainAltButton mb-2\" :href=\"chromeLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-opera</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.opera\") }}\n    </v-btn>\n\n    <v-btn class=\"mainAltButton mb-2\" :href=\"chromeLink\" target=\"_blank\">\n      <!-- brave logo icon -->\n      <svg\n          style=\"margin-right: 1rem\"\n          height=\"20\"\n          width=\"20\"\n          viewBox=\"0 0 24 24\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          fill=\"#fff\"\n      >\n        <path\n            d=\"m15.68 0 2.1 2.38s1.84-.51 2.7.36c.87.87 1.59 1.64 1.59 1.64l-.56 1.38.71 2.04-2.35 8.96c-.48 1.92-.82 2.66-2.2 3.63-1.38.97-3.88 2.66-4.29 2.92-.4.25-.92.69-1.38.69-.46 0-.97-.44-1.38-.7a185.8 185.8 0 0 1-4.3-2.9c-1.37-.98-1.7-1.72-2.19-3.64L1.78 7.8l.71-2.04-.56-1.38s.72-.77 1.59-1.64c.87-.87 2.7-.36 2.7-.36L8.33 0h7.36zM12 14.94c-.14 0-1.04.31-1.76.69-.72.37-1.24.63-1.4.74-.17.1-.07.3.08.4.15.11 2.2 1.7 2.4 1.87.2.18.48.47.68.47.2 0 .5-.3.69-.47.2-.17 2.24-1.76 2.4-1.86.14-.11.24-.3.08-.41l-1.41-.74a8.18 8.18 0 0 0-1.76-.7zm0-11.28s-.4 0-1.02.2-1.28.46-1.59.46c-.3 0-2.58-.43-2.58-.43s-2.7 3.26-2.7 3.96c0 .7.35.88.69 1.24l2.02 2.15c.2.2.59.51.35 1.07-.23.55-.58 1.26-.2 1.97.4.72 1.05 1.2 1.47 1.12.42-.08 1.42-.6 1.78-.83.36-.24 1.52-1.2 1.52-1.56 0-.36-1.2-1.02-1.42-1.17-.22-.15-1.22-.72-1.24-.95-.02-.22-.02-.29.28-.85.3-.56.83-1.3.74-1.8-.09-.5-.95-.75-1.56-.98-.62-.24-1.8-.67-1.95-.74-.15-.07-.1-.14.34-.18.45-.04 1.72-.21 2.3-.05.57.16 1.54.4 1.62.53.08.13.15.14.07.58-.08.45-.5 2.58-.54 2.96-.04.38-.12.63.29.72.4.1 1.1.26 1.33.26s.93-.16 1.33-.26c.41-.09.33-.34.3-.72-.05-.38-.47-2.51-.55-2.96-.08-.45-.01-.45.07-.58.08-.13 1.06-.37 1.63-.53.57-.16 1.85 0 2.3.05.44.04.48.1.33.18-.15.07-1.33.5-1.95.74-.61.23-1.47.49-1.56.98-.1.5.44 1.24.74 1.8s.3.63.29.85c-.02.23-1.03.8-1.25.95-.22.15-1.41.8-1.41 1.17 0 .37 1.15 1.32 1.51 1.56.37.23 1.36.75 1.78.83.42.08 1.08-.4 1.46-1.12.39-.71.04-1.42-.2-1.97-.23-.56.17-.87.36-1.07L19.2 9.1c.34-.36.68-.54.68-1.24s-2.7-3.96-2.7-3.96-2.27.43-2.57.43c-.3 0-.97-.25-1.59-.46-.61-.2-1.02-.2-1.02-.2z\"\n        />\n      </svg>\n      {{ $vuetify.lang.t(\"$vuetify.install.brave\") }}\n    </v-btn>\n\n    <!-- Userscript -->\n    <h3 style=\"margin-top: 3em; margin-bottom: 0\">\n      {{ $vuetify.lang.t(\"$vuetify.install.title2\") }}\n    </h3>\n    <p style=\"color: #999; margin-top: 0.5rem; margin-bottom: 0.5rem\">\n      {{ $vuetify.lang.t(\"$vuetify.install.subtitle2\") }}\n    </p>\n\n    <v-btn class=\"mainAltButton\" :href=\"scriptLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-script-text-outline</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.userscript\") }} &nbsp;\n      <div style=\"font-size: 0.55rem\">({{ $vuetify.lang.t(\"$vuetify.install.tampermonkey\") }})</div>\n    </v-btn>\n\n    <!-- 3rd party clients -->\n    <h3 style=\"margin-top: 3em\">\n      {{ $vuetify.lang.t(\"$vuetify.install.title3\") }}\n    </h3>\n    <p style=\"color: #999; margin-top: 0.5rem; margin-bottom: 0.5rem\">\n      {{ $vuetify.lang.t(\"$vuetify.install.subtitle3\") }}\n    </p>\n    <v-btn class=\"mainAltButton\" :href=\"reVanced\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-android</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.androidReVanced\") }}\n    </v-btn>\n    <v-btn class=\"mainAltButton\" :href=\"androidNewPipe\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-android</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.androidTubular\") }}\n    </v-btn>\n    <v-btn class=\"mainAltButton\" :href=\"iosJailbreakLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-apple</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.iosJailbroken\") }}\n    </v-btn>\n    <v-btn class=\"mainAltButton\" :href=\"iosuYouPlus\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-apple</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.install.iosUYouPlus\") }}\n    </v-btn>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name)\n        ? \"swoop-left\"\n        : \"swoop-right\";\n  },\n  data() {\n    return {\n      chromeLink:\n          \"https://chromewebstore.google.com/detail/return-youtube-dislike/gebbhagfogifgggkldgodflihgfeippi\",\n      firefoxLink:\n          \"https://addons.mozilla.org/en-US/firefox/addon/return-youtube-dislikes/\",\n      scriptLink:\n          \"https://github.com/Anarios/return-youtube-dislike/raw/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js\",\n\n      iosJailbreakLink: \"https://chariz.com/get/return-youtube-dislike/\",\n\n      androidNewPipe: \"https://github.com/polymorphicshade/NewPipe\",\n      androidVueTube: \"https://vuetube.app\",\n      iosuYouPlus: \"https://github.com/qnblackcat/uYouPlus\",\n\n      androidNewPipe: \"https://github.com/polymorphicshade/Tubular\",\n      reVanced: \"https://revanced.app/\",\n\n    };\n  },\n};\n</script>\n"
  },
  {
    "path": "Website/pages/links.vue",
    "content": "<template>\n  <div class=\"pt-12\">\n    <h1 class=\"title-text\">\n      {{ $vuetify.lang.t(\"$vuetify.links.title\") }}\n    </h1>\n\n    <p style=\"color: #9999; margin-top: 0.5rem; margin-bottom: 1rem\">\n      {{ $vuetify.lang.t(\"$vuetify.links.subtitle\") }}\n    </p>\n\n    <v-btn class=\"mainAltButton\" :href=\"githubLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-github</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.links.github\") }}\n    </v-btn>\n\n    <v-btn class=\"mainAltButton\" :href=\"discordLink\" target=\"_blank\">\n      <v-icon style=\"margin-right: 0.5em\">mdi-discord</v-icon>\n      {{ $vuetify.lang.t(\"$vuetify.links.discord\") }}\n    </v-btn>\n\n    <h1 style=\"margin-top: 1em\">\n      {{ $vuetify.lang.t(\"$vuetify.links.contact\") }}\n    </h1>\n\n    <v-icon style=\"margin-right: 0.5em\">mdi-email</v-icon>\n    selivano.d@gmail.com\n\n    <p style=\"color: #555\" class=\"my-8\">\n      Site by <v-icon color=\"#555\">mdi-discord</v-icon>\n      <a class=\"attr-link\" href=\"https://github.com/Frontesque\" target=\"_blank\"> Front#2990 </a>\n      <br />\n      & <v-icon color=\"#555\">mdi-discord</v-icon>\n      <a class=\"attr-link\" href=\"https://github.com/PickleNik\" target=\"_blank\"> PickleNik#0864 </a>\n    </p>\n    <!-- Translators -->\n    <div style=\"color: #555; width: 250px\" class=\"my-8\">\n      <b class=\"white--text\">\n        {{ $vuetify.lang.t(\"$vuetify.links.translators\") }}\n      </b>\n      <br />\n      <div v-for=\"translator in translators\" :key=\"translator.tag\" class=\"d-flex\">\n        {{ translator.lang }} - <v-spacer /> {{ translator.tag }}\n      </div>\n    </div>\n    <!-- Cool Projects -->\n    <div style=\"color: #555; width: 400px\">\n      <b class=\"white--text\">\n        {{ $vuetify.lang.t(\"$vuetify.links.coolProjects\") }}\n      </b>\n      <br />\n      <div v-for=\"project in coolProjects\" :key=\"project.url\" class=\"d-flex justify-center\">\n        <a class=\"attr-link\" :href=\"project.url\" target=\"_blank\"\n          >{{ project.name }} - {{ $vuetify.lang.t(project.description) }}\n        </a>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  transition(to, from) {\n    if (!from) return \"swoop-in\";\n    let routes = [\"index\", \"install\", \"docs\", \"help\", \"faq\", \"donate\", \"links\"];\n    if (routes.indexOf(to.name) < 0) return \"swoop-out\";\n    if (routes.indexOf(from.name) < 0) return \"swoop-in\";\n    return routes.indexOf(to.name) > routes.indexOf(from.name) ? \"swoop-left\" : \"swoop-right\";\n  },\n  data: () => ({\n    githubLink: \"https://github.com/Anarios/return-youtube-dislike\",\n    discordLink: \"https://discord.gg/mYnESY4Md5\",\n    emailLink: \"mailto:selivano.d@gmail.com \",\n    translators: [\n      {\n        tag: \"alexuspromago#9473\",\n        lang: \"Español\",\n      },\n      {\n        tag: \"trbatuhankara#7738\",\n        lang: \"Türkçe\",\n      },\n      {\n        tag: \"iLBAn#7421\",\n        lang: \"Türkçe\",\n      },\n      {\n        tag: \"PickleNik#0864\",\n        lang: \"Русский\",\n      },\n      {\n        tag: \"Fjuro#0179\",\n        lang: \"Čeština\",\n      },\n      {\n        tag: \"ShaCHO36P\",\n        lang: \"日本語\",\n      },\n      {\n        tag: \"NiniKo\",\n        lang: \"Français\",\n      },\n      {\n        tag: \"tubyoub\",\n        lang: \"Deutsch\",\n      },\n      {\n        tag: \"dsty#1614\",\n        lang: \"Українська\",\n      },\n      {\n        tag: \"liberaldev\",\n        lang: \"한국어\",\n      },\n      {\n        tag: \"itsbudyn#6502\",\n        lang: \"Polski\",\n      },\n      {\n        tag: \"ArtisGraphics\",\n        lang: \"Deutsch\",\n      },\n      {\n        tag: \"gpap#3743\",\n        lang: \"Magyar\",\n      },\n      {\n        tag: \"naufalk25\",\n        lang: \"Bahasa Indonesia\",\n      },\n      {\n        tag: \"khangquangtran\",\n        lang: \"Tiếng Việt\",\n      },\n    ],\n    coolProjects: [\n      {\n        name: \"SponsorBlock\",\n        url: \"https://sponsor.ajay.app/\",\n        description: \"$vuetify.links.sponsorBlockDescription\",\n      },\n      {\n        name: \"Filmot\",\n        url: \"https://filmot.com/\",\n        description: \"$vuetify.links.filmotDescription\",\n      },\n    ],\n  }),\n};\n</script>\n\n<style>\n.attr-link {\n  color: #555 !important;\n  text-decoration: none;\n  transition: all 0.2s;\n}\n.attr-link:hover {\n  text-decoration: underline;\n  color: #f77 !important;\n}\n</style>\n"
  },
  {
    "path": "Website/pages/pay/crypto.vue",
    "content": "<template>\n  <v-container>\n    <v-row dense>\n      <v-col v-for=\"card in cards\" :key=\"card.title\" :lg=\"4\" :sm=\"12\">\n        <v-card height=\"100%\">\n          <v-card-title>\n            {{ card.title }}\n          </v-card-title>\n          <v-card-text style=\"height: 80px\">\n            {{ card.address }}\n          </v-card-text>\n          <v-img :src=\"card.img\" :contain=\"true\" height=\"400px\" position=\"top\">\n          </v-img>\n        </v-card>\n      </v-col>\n    </v-row>\n  </v-container>\n</template>\n\n<script>\nexport default {\n  transition(to) {\n    return to.name == \"crypto\" ? \"swoop-in\" : \"swoop-out\";\n  },\n  data: () => ({\n    cards: [\n      {\n        title: \"Bitcoin\",\n        address: \"bitcoin:bc1q90v030fu4z2hzz3x6a4zcwxuzauxkt8gjv8pws\",\n        img: \"/qrs/bitcoin.jpg\",\n      },\n      {\n        title: \"Monero\",\n        address:\n          \"monero:431SM1vExRbdiq5jArCMkhey1g8kYhLYkbgkXYU4kgL6UrXNRzcXtz3HJDayph6Dcb3ErTg8ZAVqJGtS1Ya7Rr9URJ24Tbe\",\n        img: \"/qrs/monero.jpg\",\n      },\n      {\n        title: \"Ethereum\",\n        address: \"ethereum:0x981A99cDE3f4E1Ad49Ad84FB62Eca3606007eBEc\",\n        img: \"/qrs/ethereum.jpg\",\n      },\n    ],\n  }),\n};\n</script>\n\n<style></style>\n"
  },
  {
    "path": "Website/store/README.md",
    "content": "Read this in other languages: [Nederlands](READMEnl.md), [Türkçe](READMEtr.md), [Deutsch](READMEde.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# STORE\n\n**This directory is not required, you can delete it if you don't want to use it.**\n\nThis directory contains your Vuex Store files.\nVuex Store option is implemented in the Nuxt.js framework.\n\nCreating a file in this directory automatically activates the option in the framework.\n\nMore information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).\n"
  },
  {
    "path": "Website/store/READMEbg.md",
    "content": "Прочетете това на други езици: [English](README.md), [Nederlands](READMEnl.md), [Türkçe](READMEtr.md)\n\n# STORE\n\n**Тази директория не е задължителна, можете да я изтриете, ако не искате да я използвате.**\n\nТази директория съдържа файлове на вашите Vuex Store.\nОпцията за Vuex Store е реализирана в рамките на Nuxt.js.\n\nСъздаването на файл в тази директория автоматично активира опцията в рамките на фреймуърка.\n\nПовече информация относно използването на тази директория може да намерите в [документацията](https://nuxtjs.org/guide/vuex-store).\n"
  },
  {
    "path": "Website/store/READMEde.md",
    "content": "Read this in other languages: [English](README.md), [Nederlands](READMEnl.md), [Türkçe](READMEtr.md)\n\n# Speicher\n\n**Dieses Verzeichnis ist nicht erforderlich, Sie können es löschen, wenn Sie es nicht verwenden möchten.**\n\nDieses Verzeichnis enthält Ihre Vuex-Speicherdateien. \nDie Vuex-Speicheroption ist im Nuxt.js-Framework implementiert.\n\nDas Erstellen einer Datei in diesem Verzeichnis aktiviert die Option automatisch im Framework.\n\nWeitere Informationen zur Verwendung dieses Verzeichnisses finden Sie in [der Dokumentation](https://nuxtjs.org/guide/vuex-store).\n"
  },
  {
    "path": "Website/store/READMEnl.md",
    "content": "Read this in other languages: [English](READMEen.md), [Türkçe](READMEtr.md), [Deutsch](READMEde.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n\n# OPSLAAN\n\n**Deze map is niet vereist, u kunt deze verwijderen als u deze niet wilt gebruiken.**\n\nDeze map bevat uw Vuex Store-bestanden.\nDe Vuex Store-optie is geïmplementeerd in het Nuxt.js-framework.\n\nHet aanmaken van een bestand in deze map activeert automatisch de optie in het framework.\n\nMeer informatie over het gebruik van deze directory in [de documentatie](https://nuxtjs.org/guide/vuex-store).\n"
  },
  {
    "path": "Website/store/READMEtr.md",
    "content": "Read this in other languages: [English](README.md), [Nederlands](READMEnl.md), [Deutsch](READMEde.md), [български](READMEbg.md), [Tiếng Việt](READMEvi.md)\n\n# MAĞAZA\n\n**Bu dizin gerekli değildir, kullanmak istemiyorsanız silebilirsiniz.**\n\nBu dizin, Vuex Store dosyalarınızı içerir.\nVuex Store seçeneği Nuxt.js çerçevesinde uygulanmaktadır.\n\nBu dizinde bir dosya oluşturmak, çerçevedeki seçeneği otomatik olarak etkinleştirecektir.\n\nBu dizinin kullanımı ile ilgili daha fazla bilgi için [belgeleme](https://nuxtjs.org/guide/vuex-store)ye göz atın.\n"
  },
  {
    "path": "Website/store/READMEvi.md",
    "content": "Đọc bằng các ngôn ngữ khác: [English](README.md), [Nederlands](READMEnl.md), [Türkçe](READMEtr.md), [Tiếng Việt](READMEvi.md)\n\n# STORE\n\n**Thư mục này không bắt buộc có. Bạn có thể xóa thư mục này nếu bạn không muốn dùng tới.**\n\nThư mục này chứa các tệp Vuex Store.\nTùy chọn Vuex Store được thực hiện trong khung Nuxt.js.\n\nTạo một tệp trong thư mục này sẽ tự động kích hoạt tùy chọn này trong khung.\n\nThông tin về cách dùng thư mục này trong [tài liệu](https://nuxtjs.org/guide/vuex-store).\n"
  },
  {
    "path": "extension-description-store-ar.txt",
    "content": "يعيد Return YouTube Dislike القدرة على رؤية عدم الإعجاب على YouTube.\n\nإذا لم يعمل: افتح علامة تبويب الإضافات (chrome://extensions/)، قم بتعطيل هذه الإضافة ثم تمكينها مرة أخرى. يجب أن يحل هذا معظم المشاكل، حيث يوجد خطأ في Chromium يكسر الإضافة في بعض الحالات. نأمل أن يقوم فريق Chromium بإصلاح هذا قريبًا.\n\nبدءًا من 13 ديسمبر 2021، أزالت YouTube القدرة على رؤية عدم الإعجاب من واجهة برمجة التطبيقات الخاصة بهم.\nتهدف هذه الإضافة إلى إعادة القوة للمستخدمين باستخدام مزيج من بيانات الإعجاب وعدم الإعجاب المؤرشفة، بالإضافة إلى الإعجابات وعدم الإعجابات التي يقوم بها مستخدمو الإضافة لعرض التقييمات الأكثر دقة.\n\nحاليًا يحتوي على بيانات إعجاب/عدم إعجاب لأكثر من 200 مليون فيديو مخزنة قبل 13 ديسمبر 2021\n\nينمو بنشاط ويواكب التحميلات بعد 13 ديسمبر 2021\n\nكلما زاد عدد المستخدمين الذين يستخدمون الإضافة، كلما كانت أكثر دقة.\n\nقد تكون البيانات المعروضة للفيديوهات غير الشعبية التي تم تحميلها بعد 13 ديسمبر 2021 أقل دقة من الفيديوهات الأكثر شعبية.\n\nهذه الإضافة حاليًا في مرحلة تطوير نشطة، لذا إذا واجهت أي مشاكل، لا تتردد في الإبلاغ عنها على صفحتنا على GitHub أو في خادم Discord الخاص بنا.\n\nالمزيد من الميزات قادمة قريبًا!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-az.txt",
    "content": "Return YouTube Dislike YouTube-da bəyənməmələri görmək imkanını bərpa edir.\n\nƏgər işləmirsə: Genişlənmələr nişanını (chrome://extensions/) açın, bu genişləndirməni söndürüb yenidən aktiv edin. Bu, əksər problemləri aradan qaldırmalıdır, çünki bəzi hallarda genişləndirməni sıradan çıxaran Chromium xətası mövcuddur. Ümid edirik ki, Chromium komandası bunu tezliklə düzəldəcək.\n\n13 dekabr 2021‑dən etibarən YouTube öz API‑sindən bəyənməmələri görmə imkanını çıxardı.\nBu genişləndirmə ən dəqiq göstəriciləri göstərmək üçün arxivləşdirilmiş bəyənmə və bəyənməmə məlumatlarının, həmçinin genişləndirmə istifadəçilərinin etdiyi bəyənmə və bəyənməmələrin birləşməsindən istifadə edərək istifadəçilərə nəzarəti qaytarmağı hədəfləyir.\n\nHazırda 13 dekabr 2021‑dən əvvəl 200+ milyon videoya aid bəyənmə/bəyənməmə məlumatları saxlanılır.\n\n13 dekabr 2021‑dən sonrakı yükləmələrlə aktiv şəkildə böyüyür və yenilənir.\n\nGenişləndirmədən nə qədər çox istifadəçi istifadə etsə, nəticələr bir o qədər dəqiq olacaq.\n\n13 dekabr 2021‑dən sonra yüklənmiş, populyar olmayan videolarda, populyar videolarla müqayisədə göstərilən məlumatlar daha az dəqiq ola bilər.\n\nBu genişləndirmə hazırda aktiv inkişaf mərhələsindədir, buna görə hər hansı problem yaşasanız, GitHub səhifəmizdə və ya Discord serverimizdə tərəddüd etmədən xəbər verin.\n\nTezliklə daha çox funksiya gələcək!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-bg.txt",
    "content": "Return YouTube Dislike възстановява възможността да виждате дизлайковете във YouTube.\n\nАко не работи: отворете раздела за разширения (chrome://extensions/), деактивирайте това разширение и го активирайте отново. Това трябва да реши повечето проблеми, тъй като има грешка в Chromium, която нарушава работата на разширението в някои случаи. Надяваме се, че екипът на Chromium ще я оправи скоро.\n\nОт 13 декември 2021 г. YouTube премахна възможността да се виждат дизлайковете от техния API.\nТова разширение има за цел да възстанови контрола на потребителите, като използва комбинация от архивирани данни за харесвания и дизлайкове, както и лайковете и дизлайковете направени от потребителите на разширението, за да покаже най-точните оценки.\n\nВ момента има запазени данни за над 200 милиона видеа с лайкове/дизлайкове преди 13 декември 2021 г.\n\nАктивно се разширява и поддържа актуалност с качвания след 13 декември 2021 г.\n\nКолкото повече потребители използват разширението, толкова по-точно ще бъде.\n\nВидеа, качени след 13 декември 2021 г., които не са популярни, могат да имат по-малко точни данни.\n\nТова разширение в момента е в активна фаза на разработка, така че ако имате някакви проблеми, не се колебайте да ги докладвате на нашата GitHub страница или в нашия Discord сървър.\n\nОще функции ще бъдат добавени скоро!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-cs.txt",
    "content": "Return YouTube Dislike obnoví možnost vidět palce dolů na YouTube.\n\nPokud to nefunguje: otevřete kartu Rozšíření (chrome://extensions/), zakažte toto rozšíření a znovu jej povolte. To by mělo vyřešit většinu problémů, protože v Chromiu je bug, který v určitých případech rozbije rozšíření. Snad to tým Chromia brzy vyřeší.\n\nOd 13. prosince 2021, YouTube odebral možnost získat počet palců dolů z jejich API.\nToto rozšíření cílí na obnovení moci uživatelů používáním kombinace archivovaných dat palců nahoru a dolů a také palci nahoru a dolů udělenými uživateli tohoto rozšíření pro co nejpřesnější hodnocení.\n\nV současnosti má data palců nahoru a dolů 200+ milionu videí, která byla uložena před 13. prosincem 2021\n\nAktivně roste a zůstává v obraze s nahrávkami po 13. prosinci 2021\n\nČím více lidí bude rozšíření používat, tím přesnější bude.\n\nNepopulární videa nahraná po 13. prosinci 2021 mohou mít méně přesná data než populární videa.\n\nToto rozšíření je ve fázi aktivního vývoje, pokud narazíte na jakékoliv problémy, nestyďte se je nahlásit na naší GitHub stránce nebo na Discord serveru.\n\nDalší funkce přijdou brzy! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-de.txt",
    "content": "Return YouTube Dislike stellt die Fähigkeit wieder her, Dislikes auf YouTube zu sehen.\n\nFalls es nicht funktioniert: Öffne den Erweiterungen-Tab (chrome://extensions/), deaktiviere diese Erweiterung und aktiviere sie erneut. Dies sollte die meisten Probleme beheben, da es einen Fehler in Chromium gibt, der die Erweiterung in einigen Fällen außer Kraft setzt. Hoffentlich wird das Chromium-Team dies bald beheben.\n\nAb dem 13. Dezember 2021 hat YouTube die Möglichkeit entfernt, Dislikes über ihre API zu sehen.\nDiese Erweiterung zielt darauf ab, den Nutzern die Kontrolle zurückzugeben, indem sie eine Kombination aus archivierten Like- und Dislike-Daten sowie den Likes und Dislikes der Erweiterungsnutzer verwendet, um die genauesten Bewertungen anzuzeigen.\n\nDerzeit sind mehr als 200+ Millionen Video-Like-/Dislike-Daten vor dem 13. Dezember 2021 gespeichert.\n\nEs wächst aktiv und hält sich auf dem neuesten Stand mit Uploads nach dem 13. Dezember 2021.\n\nJe mehr Nutzer die Erweiterung verwenden, desto genauer wird sie sein.\n\nUnbeliebte Videos, die nach dem 13. Dezember 2021 hochgeladen wurden, können weniger genaue Daten anzeigen als beliebtere Videos.\n\nDiese Erweiterung befindet sich derzeit in einer aktiven Entwicklungsphase. Wenn Sie Probleme haben, zögern Sie nicht, sie auf unserer GitHub-Seite oder in unserem Discord-Server zu melden.\n\nWeitere Funktionen folgen bald!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-el.txt",
    "content": "Το Return YouTube Dislike είναι μια επέκταση ανοιχτού κώδικα η οποία επαναφέρει στο YouTube τον μετρητή dislike.\n\nΕάν δεν λειτουργεί: ανοίξτε την καρτέλα Επεκτάσεις (chrome://extensions/), απενεργοποιήστε αυτήν την επέκταση και ενεργοποιήστε την ξανά. Αυτό θα διορθώσει τα περισσότερα προβλήματα, καθώς υπάρχει ένα σφάλμα στο Chromium που σπάει την επέκταση σε ορισμένες περιπτώσεις. Ας ελπίσουμε ότι η ομάδα του Chromium θα το διορθώσει σύντομα.\n\nΣτις 13 Δεκεμβρίου 2021, το YouTube αφαίρεσε απο το API την δυνατότητα να εμφανίζονται τα dislikes.\nΗ επέκταση αυτή στοχεύει να επαναφέρει αυτή την δυνατότατα στους χρήστες. Ένας συνδυασμός αρχειοθετημένων δεδομένων like και dislike, καθώς και η χρήση των like και dislike των χρηστών της επέκτασης, προβάλουν με τον πιό ακριβή τρόπο την ακροαματικότητα των βίντεο.\n\nΑυτή την στιγμή υπάρχει μια βάση με 200+ εκατομμύρια καταχωρημένα βίντεο και τα likes/dislikes τους, προερχόμενα πριν απο τις 13 Δεκεμβρίου 2021. \n\nΑναπτύσσεται ενεργά και ενημερώνεται για τις μεταφορτώσεις βίντεο μετά τις 13 Δεκεμβρίου 2021.\n\nΌσο περισσότεροι χρήστες χρησιμοποιούν την επέκταση, τόσο πιο ακριβής θα είναι.\n\nΜη δημοφιλή βίντεο που ανέβηκαν μετά τις 13 Δεκεμβρίου 2021 ενδέχεται να εμφανίζουν λιγότερο ακριβή δεδομένα από τα πιο δημοφιλή βίντεο.\n\nΗ επέκταση βρίσκεται επί του παρόντος σε ενεργό φάση ανάπτυξης, επομένως εάν αντιμετωπίζετε προβλήματα, μη διστάσετε να τα αναφέρετε στη σελίδα μας στο GitHub ή στον διακομιστή Discord.\n\nΣύντομα με περισσότερες δυνατότητες! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-en.txt",
    "content": "Return YouTube Dislike restores the ability to see dislikes on YouTube.\n\nIf it doesn't work: open the Extensions tab (chrome://extensions/), disable this extension and enable it again. This should fix most of the problems, since there is a bug in Chromium that breaks the extension in some cases. Hopefully the Chromium team will fix this soon.\n\nStarting December 13th 2021, YouTube removed the ability to see dislikes from their API.\nThis extension aims to restore power to users by using a combination of archived like and dislike data, as well as the likes and dislikes made by extension users to show the most accurate ratings.\n\nCurrently has 200+ million videos likes/dislikes data stored before December 13th, 2021\n\nActively growing and keeping up to date with uploads after December 13th, 2021\n\nThe more users that use the extension, the more accurate it will be.\n\nUnpopular videos uploaded after December 13th, 2021 may have less accurate data shown than more popular videos.\n\nThis extension is currently in an active development phase, so if you experience any issues, don’t hesitate to report them on our GitHub page or in our Discord server.\n\nMore features to come soon! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-es.txt",
    "content": "Return YouTube Dislikes restaura la capacidad de ver los «dislikes» (\"No me gusta's\"), en YouTube.\n\nEn caso de que no funcione: abre la pestaña de Extensiones (chrome://extensions/), desactiva esta extensión y luego vuelve a activarla. Existe un fallo en Chromium que, en algunos casos, da problemas con la extensión. Esta solución debería arreglar la mayoría de los problemas. Con suerte, el equipo de Chromium resolverá el problema pronto.\n\nA partir del 13 de diciembre de 2021, YouTube quitó la capacidad de ver la cantidad de «dislikes» desde su API.\nEl fin de esta extensión es el de devolver poder a los usuarios mediante una combinación de datos archivados de cantidades de «likes» («Me gusta») y «dislikes» («No me gusta»), a los que se sumarán los «likes» y «dislikes» enviados por los usuarios de esta extensión, con el fin de mostrar valoraciones precisas.\n\nActualmente tiene almacenada la información recuperada antes del 13 de diciembre de 2021 de los «likes»/«dislikes» de más de 200 millones de vídeos.\n\nLa extensión está creciendo de forma activa y actualizándose con los vídeos subidos después del 13 de diciembre de 2021.\n\nCuantas más personas utilicen la extensión, más precisa será.\n\nLos vídeos menos populares que se hayan subido después del 13 de diciembre de 2021 podrían mostrar datos menos precisos que los vídeos más populares.\n\nEsta extensión está siendo desarrollada de forma activa: si tienes cualquier problema, no dudes en comentarlo a través de nuestra página de GitHub o en nuestro servidor de Discord (en inglés, por favor).\n\n¡Próximamente incluiremos más características!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-fr.txt",
    "content": "Return YouTube Dislike rétablit la possibilité de voir les dislikes sur YouTube.\n\nSi cela ne fonctionne pas : ouvrez l'onglet des extensions (chrome://extensions/) désactivez cette extension et réactivez-la. Il s'agit d'un bug dans Chromium qui casse l'extension dans certains cas. Cela devrait résoudre la plupart des problèmes. Espérons que l'équipe de Chromium corrigera ce problème bientôt.\n\nDepuis le 13 décembre 2021, YouTube a supprimé la possibilité de voir les dislikes de son API.\nCette extension vise à redonner le pouvoir aux utilisateurs en utilisant une combinaison de données archivées des likes et des dislikes, ainsi que les likes et les dislikes faits par les utilisateurs de l'extension pour montrer les évaluations les plus précises.\n\nNous disposons à ce jour de plus de 200 millions de vidéos avec leurs données de likes et de dislikes datant d'avant le 13 décembre 2021.\n\nCroissance active et reste à jour avec les uploads datant d'après le 13 décembre 2021.\n\nPlus il y aura d'utilisateurs de l'extension, plus elle sera précise.\n\nLes vidéos moins populaires mises en ligne ultérieurement au 13 décembre 2021 peuvent présenter des données moins précises que les vidéos plus populaires.\n\nCette extension est actuellement en développement actif, donc si vous rencontrez des problèmes, n'hésitez pas à les signaler sur notre page GitHub ou sur notre serveur Discord.\n\nD'autres fonctionnalités seront bientôt disponibles ! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-hu.txt",
    "content": "A Return YouTube Dislike visszaállítja a YouTube dislike számlálóját.\n\nHa nem működik: nyisd meg a bővítmények oldalt (chrome://extensions/), majd kapcsold ki és be ezt a bővítményt. Ezzel megoldhatók a hibák, amik néha előfordulhatnak chromium böngészőkben. Remélhetőleg a chromium csapata hamarosan javítja ezt.\n\n2021. december 13-ától a YouTube törölte a dislike-ok számát a saját API-jából. Ennek a bővítménynek az a célja, hogy visszaadja a hatalmat a felhasználók kezébe. A legpontosabb értékeléshez archivált like és dislike adatok, valamint a bővítményt használók beküldött adatainak kombinációja járul hozzá.\n\nJelenleg több, mint 200 millió videó like és dislike értékeit tároltuk el 2021. december 13. előtt.\n\nFolyamatosan bővítjük és követjük a feltöltéseket 2021. december 13. óta.\n\nMinél többen használják a bővítményt, annál pontosabbak lesznek az értékelések.\n\nAzoknál a videóknál, amelyeket 2021. december 13. óta töltöttek fel és kevésbé népszerűek, nem annyira pontos az értékelés, mint a népszerűbb tartalmak esetében.\n\nA bővítmény jelenleg aktív fejlesztés alatt áll, szóval ha bármilyen hibát találsz, jelentsd a Github oldalunkon vagy a Discord szerverünkön!\n\nHamarosan további funkciókkal bővül!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-id.txt",
    "content": "Return YouTube Dislike mengembalikan kemampuan untuk melihat jumlah dislike di Youtube.\n\nJika ini tidak bekerja: buka tab Extension (chrome://extensions/), matikan lalu hidupkan lagi extension ini. Ini biasanya memperbaiki banyak masalah, ini terjadi karena terdapat bug pada Chromium yang dapat merusak extension pada kasus tertentu. Semoga tim Chromium akan segera memperbaikinya.\n\nMulai 13 Desember 2021, Youtube menghapus kemampuan untuk melihat jumlah dislike dari API mereka.\nExtension ini bertujuan untuk mengembalikan kemampuan itu ke pengguna dengan menggunakan kombinasi data arsip like dan dislike, serta data like dan dislike dari pengguna extension untuk menunjukkan jumlah yang lebih akurat.\n\nSekarang terdapat 200+ juta data like/dislike video yang tersimpan sebelum 13 Desember 2021\n\nTumbuh secara aktif dan mengikuti perkembangan terbaru pada upload setelah 13 Desember 2021\n\nSemakin banyak pengguna menggunakan extension ini, semakin akurat datanya.\n\nVideo yang tidak populer yang diupload setelah 13 Desember 2021 mungkin memiliki data yang kurang akurat dibandingkan dengan video yang populer.\n\nExtension ini sedang aktif dikembangkan, jadi jika kamu mengalami masalah, jangan malu untuk melaporkannya pada halaman GitHub atau server Discord kami.\n\nLebih banyak fitur akan segera hadir!\n\nhttps://github.com/Anarios/return-youtube-dislike\n"
  },
  {
    "path": "extension-description-store-it.txt",
    "content": "Return YouTube Dislike ripristina la possibilità di vedere i Non mi piace su YouTube.\n\nSe non funziona: apri la scheda Estensioni (chrome://extensions/), disattiva questa estensione e riattivala. Questo dovrebbe risolvere la maggior parte dei problemi, poiché c’è un bug di Chromium che in alcuni casi interrompe l’estensione. Si spera che il team di Chromium lo risolva presto.\n\nA partire dal 13 dicembre 2021, YouTube ha rimosso dalla propria API la possibilità di vedere i Non mi piace.\nQuesta estensione mira a restituire il controllo agli utenti utilizzando una combinazione di dati archiviati di Mi piace e Non mi piace, oltre ai Mi piace e Non mi piace effettuati dagli utenti dell’estensione, per mostrare valutazioni il più accurate possibile.\n\nAttualmente dispone di dati sui Mi piace/Non mi piace di oltre 200 milioni di video memorizzati prima del 13 dicembre 2021.\n\nIn crescita attiva e costantemente aggiornata anche per i caricamenti successivi al 13 dicembre 2021.\n\nPiù utenti utilizzano l’estensione, più accurati saranno i risultati.\n\nI video poco popolari caricati dopo il 13 dicembre 2021 possono mostrare dati meno accurati rispetto ai video più popolari.\n\nQuesta estensione è attualmente in fase di sviluppo attivo; se riscontri problemi, segnalali senza esitazione sulla nostra pagina GitHub o nel nostro server Discord.\n\nAltre funzionalità in arrivo presto!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-ja.txt",
    "content": "Return YouTube Dislike は、YouTubeの「低評価」数を表示する機能を復元する拡張機能です。\n\n動作しない場合：拡張機能タブ（chrome://extensions/）を開き、この拡張機能を無効にし、再度有効にしてください。これはChromeのバグで、拡張機能が壊れる可能性もあります。上記の手順でほとんどの問題が解決するはずです。Chromium開発チームが早急にこれを修正してくれることを期待します。\n\n2021年12月13日から、YouTubeはAPIから「低評価」数を表示する機能を削除しました。\nこの拡張機能は、最も正確な評価を表示するためにアーカイブされた好き嫌いのデータ、および拡張機能のユーザーによって行われた好き嫌いの組み合わせを使用して、ユーザーの表示機能を復元することを目的としています。\n\n現在、2021年12月13日以前に保存された2億件以上の動画の高評価/低評価数のデータを保有しています。\n\n2021年12月13日以降も積極的にアップロードを行い、最新の状態を維持しています。\n\n本拡張機能をより多くのユーザーにご利用いただくことで、精度の高い評価を提供することができます。\n\n2021年12月13日以降にアップロードされたあまり人気のない動画では、人気のある動画に比べて表示されるデータの精度が低くなる可能性があります。\n\nこの拡張機能は現在も鋭意開発中であるため、何か問題が発生した場合には、遠慮なくGitHubページまたはDiscordサーバーで報告してください。\n\n近日中にさらに新機能を追加する予定です！\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-ko.txt",
    "content": "Return YouTube Dislike은 YouTube에서 싫어요 수를 다시 볼 수 있게 해 줍니다.\n\n작동하지 않을 경우: 확장 프로그램 탭(chrome://extensions/)을 열어 이 확장 프로그램을 비활성화했다가 다시 활성화하세요. 일부 경우에 확장 프로그램이 동작하지 않게 만드는 Chromium의 버그가 있어, 이 방법이 대부분의 문제를 해결합니다. Chromium 팀이 조속히 수정하길 바랍니다.\n\n2021년 12월 13일부터 YouTube는 API에서 싫어요 표시를 볼 수 있는 기능을 제거했습니다.\n이 확장 프로그램은 가장 정확한 평가를 보여 주기 위해, 보관된 좋아요/싫어요 데이터와 확장 프로그램 사용자들이 남긴 좋아요/싫어요를 결합하여 사용자에게 주도권을 돌려주는 것을 목표로 합니다.\n\n현재 2021년 12월 13일 이전의 2억 개 이상의 동영상에 대한 좋아요/싫어요 데이터를 보유하고 있습니다.\n\n2021년 12월 13일 이후 업로드된 동영상에 대해서도 지속적으로 성장하고 최신 상태를 유지합니다.\n\n더 많은 사용자가 이 확장 프로그램을 사용할수록 정확도는 높아집니다.\n\n2021년 12월 13일 이후 업로드된 인기가 낮은 동영상은 인기 있는 동영상에 비해 표시되는 데이터의 정확도가 낮을 수 있습니다.\n\n이 확장 프로그램은 현재 활발히 개발 중입니다. 문제가 발생하면 GitHub 페이지나 Discord 서버에서 주저하지 말고 보고해 주세요.\n\n더 많은 기능이 곧 제공될 예정입니다!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-nl.txt",
    "content": "Return YouTube Dislike herstelt de mogelijkheid om dislikes op YouTube te zien.\n\nWerkt het niet? Open het tabblad Extensies (chrome://extensions/), schakel deze extensie uit en weer in. Dit lost de meeste problemen op, omdat er een bug in Chromium zit die de extensie in sommige gevallen verbreekt. Hopelijk lost het Chromium-team dit snel op.\n\nVanaf 13 december 2021 heeft YouTube de mogelijkheid om dislikes te zien uit de API verwijderd.\nDeze extensie wil de regie teruggeven aan gebruikers door een combinatie te gebruiken van gearchiveerde like- en dislikegegevens, aangevuld met likes en dislikes van extensiegebruikers, om zo de meest nauwkeurige beoordelingen te tonen.\n\nMomenteel zijn er gegevens over likes/dislikes van meer dan 200 miljoen video's opgeslagen van vóór 13 december 2021.\n\nActief groeiend en up-to-date blijvend met uploads na 13 december 2021.\n\nHoe meer mensen de extensie gebruiken, hoe nauwkeuriger de data wordt.\n\nOnpopulaire video's die na 13 december 2021 zijn geüpload, kunnen minder nauwkeurige gegevens tonen dan populairdere video's.\n\nDeze extensie bevindt zich momenteel in actieve ontwikkeling; als je problemen ervaart, meld ze dan gerust op onze GitHub-pagina of op onze Discord-server.\n\nMeer functies volgen binnenkort!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-pl.txt",
    "content": "Return YouTube Dislike przywraca możliwość obejrzenia liczby łapek w dół na YouTube.\n\nJeśli nie działa: otwórz kartę rozszerzeń (chrome://extensions/), wyłącz je i włącz ponownie. Powinno to naprawić większość problemów, ponieważ jest bug w Chromium, który psuje rozszerzenie w niektórych przypadkach. Miejmy nadzieję, że zespół Chromium naprawi to niedługo.\n\nOd 13 grudnia 2021, Youtube usunął możliwość obejrzenia liczby łapek w dół z ich API.\nCelem tego rozszerzenia jest przywrócenie mocy użytkownikom, używając kombinacji zarchiwizowanych danych o łapkach w górę i dół, a także tych oddanych przez użytkowników rozszerzenia, aby pokazać najdokładniejsze statystyki.\n\nObecnie zawiera 200+ milionów łapek z filmów przechowanych przed 13 grudnia 2021. \n\nAktywnie rośnie i jest na czasie z filmami wrzucanymi po 13 grudnia 2021.\n\nIm więcej osób będzie korzystać z rozszerzenia, tym dokładniejsze ono będzie.\n\nMniej oglądane filmy wrzucone po 13 grudnia 2021 mogą mieć mniej dokładne dane niż te popularniejsze.\n\nRozszerzenie jest w fazie aktywnego rozwoju, więc jeżeli doświadczasz jakichkolwiek problemów, nie krępuj się i zgłoś je na naszej stronie na GitHub bądź na naszym serwerze Discord.\n\nWięcej funkcjonalności wkrótce!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-pt_BR.txt",
    "content": "Return YouTube Dislike restaura a possibilidade de ver os “não gostei” no YouTube.\n\nSe não funcionar: abra a aba Extensões (chrome://extensions/), desative esta extensão e ative-a novamente. Isso deve resolver a maioria dos problemas, pois há um bug no Chromium que em alguns casos quebra a extensão. Esperamos que a equipe do Chromium corrija isso em breve.\n\nA partir de 13 de dezembro de 2021, o YouTube removeu da sua API a possibilidade de ver os “não gostei”.\nEsta extensão tem como objetivo devolver o controle aos usuários usando uma combinação de dados arquivados de curtidas e “não gostei”, bem como as curtidas e “não gostei” feitos pelos usuários da extensão, para mostrar as avaliações mais precisas.\n\nAtualmente possui dados de curtidas/“não gostei” de mais de 200 milhões de vídeos armazenados antes de 13 de dezembro de 2021.\n\nCrescendo ativamente e mantendo-se atualizada com os envios após 13 de dezembro de 2021.\n\nQuanto mais usuários utilizarem a extensão, mais precisa ela será.\n\nVídeos pouco populares enviados após 13 de dezembro de 2021 podem exibir dados menos precisos do que vídeos mais populares.\n\nEsta extensão está atualmente em fase de desenvolvimento ativo; se você encontrar algum problema, não hesite em reportá-lo na nossa página do GitHub ou no nosso servidor do Discord.\n\nMais recursos em breve!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-pt_PT.txt",
    "content": "O Return YouTube Dislike restaura a possibilidade de ver os “não gostos” no YouTube.\n\nSe não funcionar: abra a página Extensões (chrome://extensions/), desative esta extensão e volte a ativá‑la. Isto deverá resolver a maioria dos problemas, pois existe um erro no Chromium que, em alguns casos, faz com que a extensão deixe de funcionar. Esperemos que a equipa do Chromium o resolva em breve.\n\nDesde 13 de dezembro de 2021, o YouTube removeu da sua API a possibilidade de ver os “não gostos”.\nEsta extensão tem como objetivo devolver o controlo aos utilizadores utilizando uma combinação de dados arquivados de “gostos” e “não gostos”, bem como os “gostos” e “não gostos” feitos pelos utilizadores da extensão, para apresentar as avaliações mais precisas.\n\nAtualmente possui dados de “gostos/ não gostos” de mais de 200 milhões de vídeos guardados antes de 13 de dezembro de 2021.\n\nEm crescimento ativo e sempre atualizada com os carregamentos após 13 de dezembro de 2021.\n\nQuanto mais utilizadores usarem a extensão, mais precisa ela será.\n\nVídeos pouco populares carregados depois de 13 de dezembro de 2021 podem apresentar dados menos precisos do que vídeos mais populares.\n\nEsta extensão encontra‑se atualmente em fase de desenvolvimento ativo; se encontrar algum problema, não hesite em reportá‑lo na nossa página do GitHub ou no nosso servidor do Discord.\n\nMais funcionalidades em breve!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-ru.txt",
    "content": "Return YouTube Dislike восстанавливает возможность видеть отметки «Не нравится» на YouTube.\n\nЕсли не работает: откройте вкладку расширений (chrome://extensions/), отключите это расширение и включите его снова. Это ошибка в chromium, которая в некоторых случаях приводит к сбою расширения. Это должно устранить большинство проблем. Надеюсь, команда chromium скоро исправит это\n\nНачиная с 13 декабря 2021 года YouTube удалил возможность видеть отметки «Не нравится» из своего API.\nЭто расширение призвано вернуть власть пользователям, используя сочетание архивных данных о отметках «Нравится» и «Не нравится», а также «Нравится» и «Не нравится», сделанных пользователями расширения, чтобы показать наиболее точные рейтинги.\n\nВ настоящее время более 200 миллионов данных об отметках «Нравится» и «Не нравится» к видео хранятся до 13 декабря 2021 года\n\nАктивно растёт и обновляется с новыми загрузками видео после 13 декабря 2021 года\n\nЧем больше пользователей используют расширение, тем точнее оно будет\n\nНепопулярные видео, загруженные после 13 декабря 2021 года, могут содержать менее точные данные, чем более популярные видео.\n\nЭто расширение в настоящее время находится в активной стадии разработки, поэтому, если у вас возникнут какие-либо проблемы, не стесняйтесь сообщать о них на нашей странице GitHub или на нашем сервере Discord.\n\nБольше возможностей появится в ближайшее время! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-sv_SE.txt",
    "content": "Return YouTube Dislike återställer möjligheten att se ogillningar på YouTube.\n\nOm det inte fungerar: öppna fliken Tillägg (chrome://extensions/), inaktivera detta tillägg och aktivera det igen. Detta åtgärdar de flesta problem eftersom det finns en bugg i Chromium som i vissa fall gör att tillägget slutar fungera. Förhoppningsvis åtgärdar Chromium-teamet detta snart.\n\nFrån och med den 13 december 2021 tog YouTube bort möjligheten att se ogillningar från sitt API.\nDetta tillägg syftar till att ge makten tillbaka till användarna genom att använda en kombination av arkiverade gilla- och ogilladata samt gilla- och ogillamarkeringar från tilläggsanvändare för att visa så korrekta betyg som möjligt.\n\nHar för närvarande data om gilla/ogilla för över 200 miljoner videor lagrade från före den 13 december 2021.\n\nVäxer aktivt och hålls uppdaterat även för uppladdningar efter den 13 december 2021.\n\nJu fler som använder tillägget, desto mer träffsäkert blir resultatet.\n\nOpopulära videor som laddats upp efter den 13 december 2021 kan visa mindre korrekta data än mer populära videor.\n\nDetta tillägg är för närvarande under aktiv utveckling; om du stöter på problem får du gärna rapportera dem på vår GitHub-sida eller i vår Discord-server.\n\nFler funktioner kommer snart!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-tr.txt",
    "content": "YouTube Dislike Sayısını Geri Getir YouTube'da beğenmeme durumlarını görme özelliğini geri getirir.\n\nEğer çalışmazsa: uzantılar sekmesini açın (chrome://extensions/) bu uzantıyı devre dışı bırakın ve yeniden aktifleştirin. Bu, chromium'daki bazı durumlarda uzantıyı bozan bir hatadır. Bu, sorunların çoğunu çözmelidir. Umarız chromium ekibi bunu yakın zamanda düzeltir.\n\n13 Aralık 2021 tarihinden itibaren YouTube, API'lerinden dislike'ları görme özelliğini kaldırdı.\nBu uzantı, önceden arşivlenmiş like ve dislike verilerinin bir birleşiminin yanı sıra, uzantı kullanıcıları tarafından en doğru derecelendirmeleri göstermek için yapılan like ve dislike'ların bir birleşimini kullanarak kullanıcılara gücü geri kazandırmayı amaçlar.\n\nŞu anda 13 Aralık 2021 tarihinden önce depolanmış tamı tamına 200 milyondan fazla videonun like/dislike verisi mevcut durumda\n\n13 Aralık 2021 tarihinden sonra ise yapılan yüklemelerle aktif olarak büyüyor ve güncel kalmaya devam ediyor\n\nUzantıyı ne kadar çok kullanıcı kullanırsa, veriler de o kadar doğru olur.\n\n13 Aralık 2021 tarihinden sonra yüklenen ama popüler olmayan videolar, daha popüler videolara nazaran daha az doğru verilere sahip olabilir.\n\nBu uzantı şu anda aktif bir geliştirme aşamasındadır, bu nedenle herhangi bir sorun yaşarsanız, bunları GitHub sayfamızda veya Discord sunucumuzda bildirmekten çekinmeyin.\n\nDaha fazla özellik yakında geliyor!\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-uk.txt",
    "content": "Return YouTube Dislike відновлює можливість бачити кількість відміток «Не подобається» на YouTube.\n\nЯкщо не працює: відкрийте вкладку розширень (chrome://extensions/), вимкніть це розширення та увімкніть його знову. Це має вирішити більшість проблем, оскільки в Chromium є помилка, яка в деяких випадках перешкоджає роботі розширення. Сподіваюся, команда Chromium незабаром виправить це.\n\nПочинаючи з 13 грудня 2021 року YouTube видалив можливість бачити відмітки «Не подобається» зі свого API.\nЦе розширення має намір повернути владу користувачам, використовуючи поєднання архівних даних про кількість відміток «Подобається» та «Не подобається», а також кількість цих відміток серед користувачів розширення, аби показати найточніші рейтинги.\n\nНаразі більше ніж 200 мільйонів даних про відмітки «Подобається»/«Не подобається» збережено до 13 грудня 2021 року.\n\nАктивно росте та оновюється новими відео, що завантажені після 13 грудня 2021 року.\n\nЩо більше користувачів використовує розширення, тим точніше воно буде.\n\nНе надто популярні відео, завантажені після 13 грудня 2021 року, можуть містити менш точні дані, ніж популярні відео.\n\nЦе розширення наразі знаходиться в активній стадії розробки, тому, якщо у вас виникнуть будь-які проблеми, не соромтесь повідомляти про них на нашій сторінці GitHub або на нашому сервері в Discord.\n\nБільше можливостей з'явиться найближчим часом! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-vi.txt",
    "content": "Return YouTube Dislike (Trả lại số lượt Không thích trên YouTube) là một tiện ích mở rộng nhằm khôi phục chức năng hiển thị số lượt \"không thích\" trên YouTube.\n\nNếu tiện ích này không hoạt động: Mở thẻ Tiện ích (chrome://extensions/), tắt tiện ích này và bật lại.\nChromium có một lỗi có thể phá hỏng tiện ích này trong một số trường hợp. Cách làm nêu trên có thể giải quyết hầu hết các vấn đề do lỗi này gây ra. Hi vọng rằng nhóm phát triển Chromium sẽ sớm khắc phục lỗi này.\n\nKể từ ngày 13 tháng 12 năm 2021, YouTube đã loại bỏ chức năng hiển thị số lượt \"không thích\" khỏi API của họ.\nTiện ích mở rộng này nhằm mục đích phục hồi quyền lợi của người dùng bằng việc hiển thị số lượt đánh giá một cách chính xác nhất, nhờ vào việc kết hợp dữ liệu về số lượt \"thích\" và \"không thích\" đã được lưu trữ với số lượt \"thích\" và \"không thích\" từ người dùng của tiện ích.\n\nTiện ích này hiện có dữ liệu được lưu trữ trước ngày 13 tháng 12 năm 2021 về số lượt \"thích\" và \"không thích\" của hơn 200 triệu vi-đê-ô.\n\nTiện ích này đang được phát triển tích cực và duy trì cập nhật đối với các đăng tải sau ngày 13 tháng 12 năm 2021.\n\nTiện ích này càng có nhiều người sử dụng, thì độ chính xác sẽ càng cao.\n\nVới những vi-đê-ô được đăng tải sau ngày 13 tháng 12 năm 2021, dữ liệu của những vi-đê-ô không phổ biến có thể kém chính xác so với dữ liệu của những vi-đê-ô phổ biến.\n\nTiện ích này hiện đang được phát triển tích cực, vì vậy vui lòng báo cáo bất kì vấn đề nào bạn gặp phải bằng trang GitHub hoặc máy chủ Discord.\n\nNhiều tính năng sắp được bổ sung! \n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-zh_CN.txt",
    "content": "\"Return YouTube Dislike\" 恢复了在 YouTube 上查看不喜欢的功能。\n\n如果它不起作用：打开扩展选项卡（chrome://extensions/），禁用此扩展，然后重新启用它。这应该可以解决大多数问题，因为在某些情况下，Chromium 中存在一个破坏扩展的错误。希望 Chromium 团队能尽快修复这个问题。\n\n从 2021 年 12 月 13 日开始，YouTube 移除了从其 API 中查看不喜欢的功能。\n这个扩展旨在通过使用归档的喜欢和不喜欢数据，以及扩展用户所做的喜欢和不喜欢，来显示最准确的评分，从而把权力还给用户。\n\n当前已经存储了超过 2 亿个视频的喜欢和不喜欢数据，截止到 2021 年 12 月 13 日。\n\n在 2021 年 12 月 13 日之后的上传中也在积极增长并持续更新。\n\n使用该扩展的用户越多，数据就会越准确。\n\n2021 年 12 月 13 日之后上传的不太受欢迎的视频，显示的数据可能不如更受欢迎的视频准确。\n\n该扩展目前处于积极开发阶段，因此如果您遇到任何问题，请毫不犹豫地在我们的 GitHub 页面或 Discord 服务器上报告。\n\n更多功能即将推出！\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "extension-description-store-zh_TW.txt",
    "content": "Return YouTube Dislike 可恢復在 YouTube 上顯示「不喜歡」數量的功能。\n\n若未正常運作：請開啟擴充功能頁面（chrome://extensions/），先停用本擴充功能再重新啟用。由於 Chromium 在某些情況下有會使擴充功能失效的錯誤，以上步驟通常可解決多數問題。也期待 Chromium 團隊能儘快修復。\n\n自 2021 年 12 月 13 日起，YouTube 已在其 API 中移除了顯示「不喜歡」數量的功能。\n本擴充功能旨在透過結合已封存的按讚/不喜歡資料，以及擴充功能使用者所送出的按讚與不喜歡，以呈現最精確的評價，將主導權還給使用者。\n\n目前已保存 2 億多部影片在 2021 年 12 月 13 日之前的按讚/不喜歡資料。\n\n在 2021 年 12 月 13 日之後上傳的影片，本專案也持續成長並保持資料更新。\n\n使用本擴充功能的使用者越多，結果就會越精確。\n\n於 2021 年 12 月 13 日之後上傳、但不太受歡迎的影片，所顯示的資料可能不如熱門影片精確。\n\n本擴充功能目前仍在積極開發中；若您遇到任何問題，歡迎在我們的 GitHub 頁面或 Discord 伺服器回報。\n\n更多功能即將推出！\n\nhttps://github.com/Anarios/return-youtube-dislike\n\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  resetMocks: true,\n  collectCoverage: false,\n  collectCoverageFrom: [\n    \"Extensions/combined/src/**/*.js\",\n    \"Extensions/combined/*.js\",\n  ],\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"return-youtube-dislike\",\n  \"version\": \"4.0.2\",\n  \"description\": \"Chrome extension to return youtube dislikes\",\n  \"main\": \"ryd.content-script.js\",\n  \"scripts\": {\n    \"start\": \"echo To build for development, please use \\\"npm run dev\\\". To build for production, please use \\\"npm run build\\\".\",\n    \"dev\": \"webpack --mode=development --watch\",\n    \"build\": \"webpack --mode=production\",\n    \"test\": \"jest\",\n    \"build:safari\": \"webpack --mode=production && xcrun safari-web-extension-converter Extensions/combined/dist/safari --project-location Extensions/combined/dist --bundle-identifier com.returnyoutubedislike.safari-ext --force\",\n    \"prepare\": \"husky install\"\n  },\n  \"lint-staged\": {\n    \"*\": \"prettier --write --ignore-unknown\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Anarios/return-youtube-dislike.git\"\n  },\n  \"keywords\": [\n    \"youtube\",\n    \"dislike\",\n    \"return\",\n    \"extension\"\n  ],\n  \"author\": \"Anarios\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Anarios/return-youtube-dislike/issues\"\n  },\n  \"homepage\": \"https://github.com/Anarios/return-youtube-dislike#readme\",\n  \"dependencies\": {\n    \"country-code-lookup\": \"^0.1.3\",\n    \"echarts\": \"^5.5.0\",\n    \"topojson-client\": \"^3.1.0\",\n    \"world-atlas\": \"^2.0.2\",\n    \"us-atlas\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.23.5\",\n    \"@babel/preset-env\": \"^7.23.5\",\n    \"@babel/runtime\": \"^7.23.5\",\n    \"babel-loader\": \"^10.0.0\",\n    \"babel-plugin-rewire\": \"^1.2.0\",\n    \"copy-webpack-plugin\": \"^11.0.0\",\n    \"filemanager-webpack-plugin\": \"^9.0.1\",\n    \"husky\": \"^9.1.7\",\n    \"jest\": \"^28.1.3\",\n    \"jest-environment-jsdom\": \"^30.0.4\",\n    \"lint-staged\": \"^16.1.2\",\n    \"prettier\": \"^3.2.2\",\n    \"webpack\": \"^5.89.0\",\n    \"webpack-cli\": \"^6.0.1\"\n  },\n  \"prettier\": {\n    \"printWidth\": 120\n  }\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require(\"path\");\nconst fs = require(\"fs\");\nconst CopyPlugin = require(\"copy-webpack-plugin\");\n\nconst extensionVersion = process.env.npm_package_version.replace(\"-\", \".\");\nconst entries = [\"ryd.content-script\", \"ryd.background\", \"popup\", \"ryd.changelog\"];\n\nconst ignorePatterns = [\n  \"**/manifest-**\",\n  \"**/dist/**\",\n  \"**/src/**\",\n  \"**/readme.md\",\n  ...entries.map((entry) => `**/${entry}.js`),\n];\n\nconst manifestTransform = (content, filename) => {\n  const filteredContent = content\n    .toString()\n    .split(\"\\n\")\n    .filter((str) => !str.trimStart().startsWith(\"//\"))\n    .join(\"\\n\");\n\n  const manifestData = JSON.parse(filteredContent);\n  manifestData.version = extensionVersion;\n  return JSON.stringify(manifestData, null, 2);\n};\n\nconst i18nTransform = (content, filename) => {\n  if (!filename.endsWith(\"messages.json\")) return content;\n\n  return content.toString().replace(/__RYD_VERSION__/g, extensionVersion);\n};\n\nclass MirrorJsOutputsPlugin {\n  constructor(targetDirs) {\n    this.targetDirs = targetDirs;\n  }\n\n  apply(compiler) {\n    compiler.hooks.afterEmit.tapPromise(\"MirrorJsOutputsPlugin\", async () => {\n      const { promises: fsp } = fs;\n      const outputPath = compiler.options.output.path;\n      const entries = await fsp.readdir(outputPath).catch(() => []);\n      const jsAssets = entries.filter((name) => name.endsWith(\".js\"));\n\n      await Promise.all(\n        this.targetDirs.map(async (dir) => {\n          const targetDir = path.join(outputPath, dir);\n          await fsp.mkdir(targetDir, { recursive: true });\n\n          const existingFiles = await fsp.readdir(targetDir).catch(() => []);\n          await Promise.all(\n            existingFiles\n              .filter((file) => file.endsWith(\".js\"))\n              .map((file) => fsp.rm(path.join(targetDir, file), { force: true }))\n          );\n\n          await Promise.all(\n            jsAssets.map((asset) =>\n              fsp.copyFile(path.join(outputPath, asset), path.join(targetDir, path.basename(asset))),\n            ),\n          );\n        }),\n      );\n    });\n  }\n}\n\nmodule.exports = {\n  entry: Object.fromEntries(\n    entries.map((entry) => [entry, path.join(__dirname, \"./Extensions/combined/\", `${entry}.js`)]),\n  ),\n  output: {\n    filename: \"[name].js\",\n    path: path.resolve(__dirname, \"Extensions/combined/dist\"),\n    clean: true,\n  },\n  cache: false,\n  optimization: {\n    minimize: false,\n  },\n  watchOptions: {\n    ignored: \"**/dist/**\",\n  },\n  plugins: [\n    // exclude locale files in moment\n    new CopyPlugin({\n      patterns: [\n        {\n          from: \"./Extensions/combined\",\n          to: \"./chrome\",\n          globOptions: {\n            ignore: ignorePatterns,\n          },\n          transform: i18nTransform,\n        },\n        {\n          from: \"./Extensions/combined/manifest-chrome.json\",\n          to: \"./chrome/manifest.json\",\n          transform: manifestTransform,\n        },\n        {\n          from: \"./Extensions/combined\",\n          to: \"./firefox\",\n          globOptions: {\n            ignore: ignorePatterns,\n          },\n          transform: i18nTransform,\n        },\n        {\n          from: \"./Extensions/combined/manifest-firefox.json\",\n          to: \"./firefox/manifest.json\",\n          transform: manifestTransform,\n        },\n        {\n          from: \"./Extensions/combined\",\n          to: \"./safari\",\n          globOptions: {\n            ignore: ignorePatterns,\n          },\n          transform: i18nTransform,\n        },\n        {\n          from: \"./Extensions/combined/manifest-safari.json\",\n          to: \"./safari/manifest.json\",\n          transform: manifestTransform,\n        },\n      ],\n    }),\n    new MirrorJsOutputsPlugin([\"chrome\", \"firefox\", \"safari\"]),\n  ],\n  experiments: {\n    topLevelAwait: true,\n  },\n  devtool: \"inline-source-map\",\n};\n"
  }
]