// ==UserScript==
// @name Duolingo DuoHacker
// @name:vi Duolingo DuoHacker
// @name:zh-CN 多邻国 DuoHacker
// @name:zh-TW 多鄰國 DuoHacker
// @name:ja Duolingo DuoHacker
// @name:ko 듀오링고 DuoHacker
// @name:fr Duolingo DuoHacker
// @name:de Duolingo DuoHacker
// @name:es Duolingo DuoHacker
// @name:pt-BR Duolingo DuoHacker
// @name:pt-PT Duolingo DuoHacker
// @name:ru Дуолинго DuoHacker
// @name:ar Duolingo DuoHacker دوهاكر
// @name:tr Duolingo DuoHacker
// @name:id Duolingo DuoHacker
// @name:th Duolingo DuoHacker
// @name:pl Duolingo DuoHacker
// @name:nl Duolingo DuoHacker
// @name:it Duolingo DuoHacker
// @name:sv Duolingo DuoHacker
// @name:da Duolingo DuoHacker
// @name:fi Duolingo DuoHacker
// @name:nb Duolingo DuoHacker
// @name:cs Duolingo DuoHacker
// @name:hu Duolingo DuoHacker
// @name:ro Duolingo DuoHacker
// @name:uk Дуолінго DuoHacker
// @name:hi Duolingo DuoHacker
// @name:bn Duolingo DuoHacker
// @name:fa Duolingo DuoHacker دوهاکر
// @name:he Duolingo DuoHacker
// @name:ms Duolingo DuoHacker
// @name:fil Duolingo DuoHacker
// @name:el Duolingo DuoHacker
// @name:hr Duolingo DuoHacker
// @name:sk Duolingo DuoHacker
// @name:bg Дуолинго DuoHacker
// @name:sr Дуолинго DuoHacker
// @name:lt Duolingo DuoHacker
// @name:lv Duolingo DuoHacker
// @name:et Duolingo DuoHacker
// @name:sl Duolingo DuoHacker
// @name:ca Duolingo DuoHacker
// @name:af Duolingo DuoHacker
// @name:sw Duolingo DuoHacker
// @name:zu Duolingo DuoHacker
// @name:mn Duolingo DuoHacker
// @name:my Duolingo DuoHacker
// @name:km Duolingo DuoHacker
// @name:lo Duolingo DuoHacker
// @name:ur Duolingo DuoHacker
// @namespace https://github.com/DuoHacker/DuoHacker
// @version 2026.08.28
// @description The #1 Duolingo hack - Farm XP, Gems, Streaks and unlock Duolingo Max for free.
// @description:vi Công cụ hack Duolingo #1 - Farm XP, Gems, Streaks và mở khóa Duolingo Max miễn phí.
// @description:zh-CN 最强 Duolingo 辅助工具 - 自动刷 XP、宝石、连胜,免费解锁 Duolingo Max。
// @description:zh-TW 最強 Duolingo 輔助工具 - 自動刷 XP、寶石、連勝,免費解鎖 Duolingo Max。
// @description:ja Duolingo最強ハックツール - XP・ジェム・連続記録を自動取得、Duolingo Maxを無料解放。
// @description:ko 최고의 Duolingo 핵 툴 - XP, 젬, 스트릭 자동 획득 및 Duolingo Max 무료 해제.
// @description:fr Le hack Duolingo #1 - Farmez XP, Gemmes, Séries et débloquez Duolingo Max gratuitement.
// @description:de Der #1 Duolingo-Hack - XP, Gems, Serien farmen und Duolingo Max kostenlos freischalten.
// @description:es El hack #1 de Duolingo - Farmea XP, Gemas, Rachas y desbloquea Duolingo Max gratis.
// @description:pt-BR O hack #1 do Duolingo - Farme XP, Gemas, Sequências e desbloqueie o Duolingo Max de graça.
// @description:pt-PT O hack #1 do Duolingo - Faça farm de XP, Gemas, Sequências e desbloqueie o Duolingo Max gratuitamente.
// @description:ru Лучший хак для Duolingo - Фармите XP, Гемы, Серии и разблокируйте Duolingo Max бесплатно.
// @description:ar أفضل هاك لـ Duolingo - اجمع XP والجواهر والسلاسل وافتح Duolingo Max مجاناً.
// @description:tr Duolingo için #1 hack - XP, Gem, Seri farm'la ve Duolingo Max'ı ücretsiz aç.
// @description:id Hack Duolingo #1 - Farm XP, Gems, Streak dan buka kunci Duolingo Max gratis.
// @description:th แฮก Duolingo อันดับ 1 - ฟาร์ม XP, เพชร, สตรีคและปลดล็อก Duolingo Max ฟรี.
// @description:pl Najlepszy hack na Duolingo - Farmuj XP, Klejnoty, Serie i odblokuj Duolingo Max za darmo.
// @description:nl De #1 Duolingo-hack - Farm XP, Edelstenen, Reeksen en ontgrendel Duolingo Max gratis.
// @description:it Il miglior hack per Duolingo - Fai farm di XP, Gemme, Streak e sblocca Duolingo Max gratis.
// @description:sv Bästa Duolingo-hacket - Farma XP, Ädelstenar, Streaks och lås upp Duolingo Max gratis.
// @description:da Den bedste Duolingo-hack - Farm XP, Ædelstene, Striber og lås Duolingo Max op gratis.
// @description:fi Paras Duolingo-hakki - Farmmaa XP, Jalokivet, Putket ja avaa Duolingo Max ilmaiseksi.
// @description:nb Den beste Duolingo-hacken - Farm XP, Edelstener, Rekker og lås opp Duolingo Max gratis.
// @description:cs Nejlepší hack na Duolingo - Farmujte XP, Drahokamy, Série a odemkněte Duolingo Max zdarma.
// @description:hu A legjobb Duolingo-hack - Farmolj XP-t, Drágaköveket, Sorozatokat és oldd fel a Duolingo Maxot ingyen.
// @description:ro Cel mai bun hack pentru Duolingo - Farmează XP, Pietre, Serii și deblochează Duolingo Max gratuit.
// @description:uk Найкращий хак для Duolingo - Фармте XP, Самоцвіти, Серії та розблокуйте Duolingo Max безкоштовно.
// @description:hi Duolingo का #1 हैक - XP, Gems, Streaks फार्म करें और Duolingo Max मुफ्त में अनलॉक करें।
// @description:bn সেরা Duolingo হ্যাক - XP, Gems, Streaks ফার্ম করুন এবং বিনামূল্যে Duolingo Max আনলক করুন।
// @description:fa بهترین هک Duolingo - XP، جواهر و رکورد را فارم کنید و Duolingo Max را رایگان باز کنید.
// @description:he ההאק הטוב ביותר ל-Duolingo - צבור XP, אבנים יקרות, רצפים ופתח את Duolingo Max בחינם.
// @description:ms Hack Duolingo terbaik - Farm XP, Permata, Streak dan buka kunci Duolingo Max secara percuma.
// @description:fil Ang pinakamahusay na Duolingo hack - Mag-farm ng XP, Gems, Streaks at i-unlock ang Duolingo Max nang libre.
// @description:el Το καλύτερο hack για το Duolingo - Κάντε farm XP, Πετράδια, Σειρές και ξεκλειδώστε το Duolingo Max δωρεάν.
// @description:hr Najbolji Duolingo hack - Farmajte XP, Dragulje, Nizove i otključajte Duolingo Max besplatno.
// @description:sk Najlepší hack na Duolingo - Farmujte XP, Drahokamy, Série a odomknite Duolingo Max zadarmo.
// @description:bg Най-добрият хак за Duolingo - Фармете XP, Скъпоценни камъни, Серии и отключете Duolingo Max безплатно.
// @description:sr Najbolji Duolingo hak - Farmujte XP, Dragulje, Serije i otključajte Duolingo Max besplatno.
// @description:lt Geriausias Duolingo įsilaužimas - Farminkite XP, Brangakmenius, Serijas ir atrakinkite Duolingo Max nemokamai.
// @description:lv Labākais Duolingo hack - Farmējiet XP, Dārgakmeņus, Sērijas un atbloķējiet Duolingo Max bez maksas.
// @description:et Parim Duolingo häkk - Farmige XP, Kalliskive, Seeriad ja avage Duolingo Max tasuta.
// @description:sl Najboljši Duolingo hack - Farmajte XP, Dragulji, Serije in odklenite Duolingo Max brezplačno.
// @description:ca El millor hack per a Duolingo - Fes farm de XP, Gemmes, Ratxes i desbloqueja Duolingo Max gratis.
// @description:af Die beste Duolingo-hack - Boer XP, Edelstene, Reekse en ontsluit Duolingo Max gratis.
// @description:sw Hack bora ya Duolingo - Fanya farm ya XP, Vito, Mifululizo na fungua Duolingo Max bila malipo.
// @description:zu I-hack engcono kakhulu ye-Duolingo - Fama i-XP, Amagugu, Izindawo futhi vula i-Duolingo Max mahhala.
// @description:mn Duolingo-н хамгийн шилдэг хак - XP, Эрдэнийн чулуу, Цуваа фарм хийж Duolingo Max-ийг үнэгүй нээ.
// @description:my Duolingo hack အကောင်းဆုံး - XP, Gems, Streaks farm လုပ်ပြီး Duolingo Max ကို အခမဲ့ ဖွင့်ပါ။
// @description:km ហេគ Duolingo ល្អបំផុត - ដាំ XP, Gems, Streaks និងដោះសោ Duolingo Max ដោយឥតគិតថ្លៃ។
// @description:lo ແຮັກ Duolingo ອັນດັບໜຶ່ງ - ຟາມ XP, ເພັດ, ສາຍຕໍ່ເນື່ອງ ແລະ ປົດລັອກ Duolingo Max ຟຣີ.
// @description:ur Duolingo کا بہترین ہیک - XP، Gems، Streaks فارم کریں اور Duolingo Max مفت میں انلاک کریں۔
// @author DuoHacker
// @match https://*.duolingo.com/*
// @match https://*.duolingo.cn/*
// @icon https://assets.duohacker.io.vn/images/DuoHacker_Logo_NoBG_PNG.png
// @run-at document-end
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect duolingo.com
// @connect ssr-avatars.duolingo.com
// @connect stories.duolingo.com
// @connect goals-api.duolingo.com
// @connect duolingo-leaderboards-prod.duolingo.com
// @connect raw.githubusercontent.com
// @connect avatars.githubusercontent.com
// @connect greasyfork.org
// @connect assets.duohacker.io.vn
// @connect d35aaqx5ub95lt.cloudfront.net
// @connect font.duohacker.io.vn
// @connect simg-ssl.duolingo.com
// @connect d3gq3s1iyyx31w.cloudfront.net/
// @compatible chrome Tested on Chrome 120+ with Tampermonkey
// @compatible firefox Tested on Firefox 120+ with Tampermonkey / Violentmonkey
// @compatible edge Tested on Edge 120+ with Tampermonkey
// @compatible opera Supported via Tampermonkey / Violentmonkey
// @compatible safari Supported via Userscripts app
// @compatible brave Supported via Tampermonkey
// @homepageURL https://duohacker.io.vn
// @supportURL https://duohacker.io.vn/discord
// @copyright 2026, DuoHacker (https://github.com/DuoHacker)
// @license BY-NC-ND 4.0
// @downloadURL https://update.greasyfork.org/scripts/561041/Duolingo%20DuoHacker.user.js
// @updateURL https://update.greasyfork.org/scripts/561041/Duolingo%20DuoHacker.meta.js
// ==/UserScript==
(function() {
'use strict';
// ── i18n ──────────────────────────────────────────────────────────
var _I18N_KEY = 'dh2_lang';
var _lang = localStorage.getItem(_I18N_KEY) || 'vi';
var _isOutdated = false;
var _currentConnState = null;
var _LANGS = {
en: {
// ── Language selector ──
lang_select_label: 'Language',
lang_en: 'English',
lang_vi: 'Tiếng Việt',
lang_es: 'Español',
lang_fr: 'Français',
// ── Top bar ──
hide: 'Hide',
show: 'Show',
switch_v1: 'Switch to Solver',
switch_v2: 'Switch to V2',
// ── Connection status ──
connecting: 'Connecting',
connected: 'Connected',
error: 'Error',
outdated: 'Outdated',
// ── Page 1 (main) ──
donate: 'Donate',
xp_question: 'How much XP would you like to gain?',
gems_run_label: 'Click "RUN" to Farm Gems',
streak_question: 'How many Streak days to restore?',
extra_features: 'Extra Features',
// ── Page 2 (extra features) ──
back: 'Back',
farm_practice: 'How many practice lesson you would like to solve?',
farm_practice_sub: '0 = unlimited practice sessions',
shop_items: 'Shop Items',
auto_league: 'Auto League',
auto_league_sub: 'Farm XP to reach your target rank',
auto_daily_quest: 'Auto Daily Quest',
auto_daily_sub: 'Complete all daily quests',
claim_monthly: 'Claim Monthly Quest',
claim_monthly_sub: 'View & claim monthly quests',
free_super: 'Free Super Duolingo',
free_super_sub: 'Activate Super Duolingo for free',
// ── Page 4 (settings) ──
loop_delay: 'Loop delay (ms)',
free_duo_max: 'Free Duolingo Max',
free_duo_max_sub: 'Client-side only, reload to apply',
hide_profile: 'Hide Profile',
auto_solver: 'Auto Solver',
auto_solver_sub: 'Show Auto Solver buttons during lessons',
hide_animation: 'Hide Animation',
hide_animation_sub: 'Hide Duolingo images & animations',
lesson_shortener: 'Lesson Shortener',
lesson_shortener_sub: 'Replace lessons with 1 instant question',
stories_shortener: 'Story Skip Challenges',
stories_shortener_sub: 'Skip all challenges in stories',
safe_mode: 'Safe Mode',
safe_mode_sub: 'Adds more delay between clicks while solving lessons/practices',
view_credits: 'View Credits',
// ── Page 3 (shop) ──
search_placeholder: 'Search items...',
loading_shop: 'Loading shop...',
no_items_found: 'No items found.',
no_items_available: 'No items available.',
// ── Page 5 (account manager) ──
account_manager: 'Account Manager',
no_saved_accounts: 'No saved accounts.',
// ── Page 6 (monthly quests) ──
monthly_quests: 'Monthly Quests',
loading_quests: 'Loading quests...',
no_monthly_quests: 'No monthly quests found.',
quest_failed: 'Failed to load quests.',
// ── Page 7 (credits) ──
credits: 'Credits',
// ── Page V1 ──
xp_farming: 'XP Farming',
farm_gems: 'Farm Gems',
streak_farming: 'Streak Farming',
activate_super_q: 'Would you like to activate Free Super Duolingo?',
settings: 'Settings',
// ── Not connected ──
not_connected: 'Not connected.',
// ── Buttons (short labels) ──
btn_get: 'GET',
btn_run: 'RUN',
btn_stop: 'STOP',
btn_save: 'SAVE',
btn_saved: 'SAVED ✓',
btn_claim: 'CLAIM ALL',
btn_activate: 'ACTIVATE',
btn_done: 'DONE ✓',
btn_save_current: 'SAVE CURRENT',
btn_solve_all: 'SOLVE ALL',
btn_pause: 'PAUSE',
btn_loading: 'Loading...',
btn_running: 'Running...',
btn_got: 'GOT ✓',
btn_failed: 'FAILED',
// ── Hide Profile status ──
profile_private: 'Profile is private',
profile_public: 'Profile is public',
status_unavailable: 'Unavailable',
status_saving: 'Saving\u2026',
status_failed_retry: 'Failed \u2014 try again',
status_not_connected: 'Not connected',
status_loading: 'Loading\u2026',
// ── Page 11 (leaderboard) ──
leaderboard: 'Leaderboard',
leaderboard_sub: 'View your current league',
your_current_league: 'Your current league',
lb_loading: 'Loading leaderboard...',
lb_failed: 'Failed to load leaderboard.',
choose_reaction: 'Choose Reaction',
lb_promoted: '⬆ Promoted',
lb_demoted: '⬇ Demoted',
lb_safe: '🟡 Safe',
lb_ended: 'Contest ended',
// ── Leaderboard separators ──
lb_rank_up: 'Rank Up',
lb_rank_down: 'Rank Down',
// ── Monthly quest status ──
mq_completed: 'COMPLETED',
// ── Changelog badge ──
changelog_current: 'CURRENT',
// ── Account manager labels ──
acc_active: 'Active',
acc_login_btn: 'LOG IN',
// ── Connection error subtexts ──
conn_not_logged_in: 'Not logged in',
conn_invalid_token: 'Invalid token',
conn_failed_retry: 'Failed — retrying',
// ── Streak live counter suffixes ──
streak_day: 'day',
streak_days: 'days',
// ── Notification titles ──
notif_xp_done_title: 'XP Farm Done!',
notif_xp_stopped_title: 'XP Farm Stopped',
notif_gem_done_title: 'Gem Farm Done!',
notif_gem_farm_title: 'Gem Farm',
notif_streak_done_title: 'Streak Farm Done!',
notif_streak_farm_title: 'Streak Farm',
notif_streak_stopped_title: 'Streak Farm Stopped',
notif_practice_done_title: 'Farm Practice Done!',
notif_practice_title: 'Farm Practice',
notif_daily_done_title: 'Daily Quests Done!',
notif_all_done_title: 'All Done!',
notif_mq_title: 'Monthly Quests',
notif_mq_partial_title: 'Partial',
notif_super_title: 'Super Activated!',
notif_shop_title: 'Shop',
notif_error_title: 'Error',
notif_failed_title: 'Failed',
notif_stopped_title: 'Stopped',
notif_busy_title: 'Busy',
notif_not_connected_title: 'Not connected',
notif_not_loaded_title: 'Not loaded',
notif_nothing_to_do_title: 'Nothing to do',
notif_v1xp_title: 'V1 XP',
notif_acc_saved_title: 'Account Saved',
notif_acc_removed_title: 'Removed',
notif_acc_already_title: 'Already saved',
notif_acc_not_found_title: 'Not found',
// ── Notification bodies ──
notif_xp_done_body: (xp, loops) => `Farmed ${xp} XP in ${loops} loops.`,
notif_xp_done_inf_body: (xp) => `+${xp} XP gained.`,
notif_xp_stopped_body: (xp) => `+${xp} XP gained.`,
notif_gem_done_body: (gems) => `+${gems} gems gained.`,
notif_gem_no_rewards: 'No rewards available. Retrying…',
notif_streak_done_body: (days) => `Restored ${days} streak days.`,
notif_streak_no_days: 'No days could be saved. Please try again.',
notif_streak_done_inf_body: (days, unit) => `+${days} ${unit} gained.`,
notif_streak_stopped_body: (days) => `Processed ${days} days.`,
notif_practice_done_body: (n) => `Completed ${n} practice(s).`,
notif_practice_next_body: (n, total) => `Done ${n}${total > 0 ? ' / ' + total : ''} — loading next...`,
notif_practice_nav: 'Navigating to practice...',
notif_practice_busy: 'Practice farm already running.',
notif_daily_done_body: (n) => `Completed ${n} metric(s).`,
notif_all_done_body: 'All daily quests completed.',
notif_daily_error: 'Could not load quest data.',
notif_daily_fail: 'Quest completion failed.',
notif_mq_all_body: (n) => `All ${n} quest(s) claimed!`,
notif_mq_partial_body: (ok, total) => `${ok}/${total} quests claimed.`,
notif_mq_fail: 'No quests could be claimed.',
notif_mq_not_loaded: 'Open Monthly Quests first.',
notif_mq_nothing: 'No unclaimed quests found.',
notif_super_body: 'Free Super Duolingo activated!',
notif_super_fail: 'Activation failed. You may already have Super.',
notif_shop_got: (name) => `Got ${name}!`,
notif_shop_fail: 'Failed to get item.',
notif_v1xp_errors: 'Too many errors, stopping.',
notif_v1xp_done_body: (xp) => `Farmed ${xp} XP.`,
notif_v1gem_done_body: (gems) => `+${gems} gems gained.`,
notif_v1streak_done_body: (days) => `Farmed ${days} streak days.`,
notif_stopped_body: 'Farm stopped.',
notif_busy_v1: 'Stop current V1 farm first.',
notif_not_connected_body: 'Please wait.',
notif_not_connected_conn: 'Please wait for connection.',
notif_acc_saved_body: (name) => `Saved account: ${name}`,
notif_acc_removed_body: 'Account removed from list.',
notif_acc_already_body: 'This account is already in the list.',
notif_acc_not_found_body: 'Account not found.',
},
vi: {
// ── Language selector ──
lang_select_label: 'Ngôn ngữ',
lang_en: 'English',
lang_vi: 'Tiếng Việt',
lang_es: 'Español',
lang_fr: 'Français',
// ── Top bar ──
hide: 'Ẩn',
show: 'Hiện',
switch_v1: 'Đổi thành Solver',
switch_v2: 'Đổi thành V2',
// ── Connection status ──
connecting: 'Đang kết nối',
connected: 'Đã kết nối',
error: 'Lỗi',
outdated: 'Phiên bản cũ',
// ── Page 1 (main) ──
donate: 'Quyên góp',
xp_question: 'Số XP bạn muốn farm',
gems_run_label: 'Nhấn vào "CHẠY" để farm Gem',
streak_question: 'Số Streak bạn muốn khôi phục',
extra_features: 'Tính năng khác',
// ── Page 2 (extra features) ──
back: 'Quay lại',
farm_practice: 'Số bài Practice bạn muốn giải?',
farm_practice_sub: '0 = vô hạn luồng cày',
shop_items: 'Shop vật phẩm',
auto_league: 'Tự động cày bảng xếp hạng',
auto_league_sub: 'Cày XP để đạt thứ hạng mục tiêu',
auto_daily_quest: 'Hoàn thành nhiệm vụ hằng ngày',
auto_daily_sub: 'Hoàn thành tất cả nhiệm vụ hằng ngày',
claim_monthly: 'Hack huy hiệu tháng',
claim_monthly_sub: 'Xem & nhận huy hiệu tháng',
free_super: 'Duolingo Super miễn phí',
free_super_sub: 'Kích hoạt Duolingo Super khoảng 3 ngày miễn phí',
// ── Page 4 (settings) ──
loop_delay: 'Độ trễ vòng',
free_duo_max: 'Duolingo Max miễn phí',
free_duo_max_sub: 'Chỉ hoạt động ở Web, làm mới trang để áp dụng',
hide_profile: 'Ẩn profile',
auto_solver: 'Tự động giải',
auto_solver_sub: 'Tự động giải bài trong lesson',
hide_animation: 'Ẩn hiệu ứng',
hide_animation_sub: 'Ẩn hình ảnh và hiệu ứng của Duolingo',
lesson_shortener: 'Rút gọn bài học',
lesson_shortener_sub: 'Thay thế các bài học bằng 1 câu hỏi',
stories_shortener: 'Skip toàn bộ câu hỏi Stories',
stories_shortener_sub: 'Bỏ qua tất cả câu hỏi trong stories',
safe_mode: 'Chế độ an toàn',
safe_mode_sub: 'Tăng độ trễ giữa các lượt click khi giải lesson/farm practice',
view_credits: 'Xem credit',
// ── Page 3 (shop) ──
search_placeholder: 'Tìm kiếm vật phẩm...',
loading_shop: 'Đang tải shop...',
no_items_found: 'Không tìm thấy vật phẩm nào.',
no_items_available: 'Không có vật phẩm nào.',
// ── Page 5 (account manager) ──
account_manager: 'Quản lý tài khoản',
no_saved_accounts: 'Chưa có tài khoản nào được lưu.',
// ── Page 6 (monthly quests) ──
monthly_quests: 'Nhiệm vụ hằng tháng',
loading_quests: 'Đang tải nhiệm vụ...',
no_monthly_quests: 'Không tìm thấy nhiệm vụ tháng này.',
quest_failed: 'Tải nhiệm vụ thất bại.',
// ── Page 7 (credits) ──
credits: 'Credits',
// ── Page V1 ──
xp_farming: 'Cày XP',
farm_gems: 'Cày Gem',
streak_farming: 'Cày Streak',
activate_super_q: 'Bạn có muốn kích hoạt Super Duolingo miễn phí không?',
settings: 'Cài đặt',
// ── Not connected ──
not_connected: 'Chưa kết nối.',
// ── Buttons (short labels) ──
btn_get: 'LẤY',
btn_run: 'CHẠY',
btn_stop: 'DỪNG',
btn_save: 'LƯU',
btn_saved: 'ĐÃ LƯU ✓',
btn_claim: 'NHẬN TẤT CẢ',
btn_activate: 'KÍCH HOẠT',
btn_done: 'XONG ✓',
btn_save_current: 'LƯU HIỆN TẠI',
btn_solve_all: 'GIẢI HẾT',
btn_pause: 'TẠM DỪNG',
btn_loading: 'Đang tải...',
btn_running: 'Đang chạy...',
btn_got: 'ĐÃ LẤY ✓',
btn_failed: 'THẤT BẠI',
// ── Hide Profile status ──
profile_private: 'Hồ sơ đã ẩn',
profile_public: 'Hồ sơ công khai',
status_unavailable: 'Không khả dụng',
status_saving: 'Đang lưu…',
status_failed_retry: 'Lỗi — thử lại',
status_not_connected: 'Chưa kết nối',
status_loading: 'Đang tải…',
// ── Page 11 (leaderboard) ──
leaderboard: 'Bảng xếp hạng',
leaderboard_sub: 'Xem giải đấu hiện tại',
your_current_league: 'Giải đấu hiện tại',
lb_loading: 'Đang tải bảng xếp hạng...',
lb_failed: 'Không tải được bảng xếp hạng.',
choose_reaction: 'Chọn Reaction',
lb_promoted: '⬆ Thăng hạng',
lb_demoted: '⬇ Xuống hạng',
lb_safe: '🟡 An toàn',
lb_ended: 'Giải đấu đã kết thúc',
// ── Leaderboard separators ──
lb_rank_up: 'Thăng hạng',
lb_rank_down: 'Xuống hạng',
// ── Monthly quest status ──
mq_completed: 'HOÀN THÀNH',
// ── Changelog badge ──
changelog_current: 'HIỆN TẠI',
// ── Account manager labels ──
acc_active: 'Đang dùng',
acc_login_btn: 'ĐĂNG NHẬP',
// ── Connection error subtexts ──
conn_not_logged_in: 'Chưa đăng nhập',
conn_invalid_token: 'Token không hợp lệ',
conn_failed_retry: 'Lỗi — đang thử lại',
// ── Streak live counter suffixes ──
streak_day: 'ngày',
streak_days: 'ngày',
// ── Notification titles ──
notif_xp_done_title: 'Farm XP xong!',
notif_xp_stopped_title: 'Đã dừng farm XP',
notif_gem_done_title: 'Farm Gem xong!',
notif_gem_farm_title: 'Farm Gem',
notif_streak_done_title: 'Farm Streak xong!',
notif_streak_farm_title: 'Farm Streak',
notif_streak_stopped_title: 'Đã dừng farm Streak',
notif_practice_done_title: 'Farm Practice xong!',
notif_practice_title: 'Farm Practice',
notif_daily_done_title: 'Nhiệm vụ ngày xong!',
notif_all_done_title: 'Xong hết!',
notif_mq_title: 'Nhiệm vụ tháng',
notif_mq_partial_title: 'Một phần',
notif_super_title: 'Kích hoạt Super!',
notif_shop_title: 'Shop',
notif_error_title: 'Lỗi',
notif_failed_title: 'Thất bại',
notif_stopped_title: 'Đã dừng',
notif_busy_title: 'Đang bận',
notif_not_connected_title: 'Chưa kết nối',
notif_not_loaded_title: 'Chưa tải',
notif_nothing_to_do_title: 'Không có gì',
notif_v1xp_title: 'V1 XP',
notif_acc_saved_title: 'Đã lưu tài khoản',
notif_acc_removed_title: 'Đã xóa',
notif_acc_already_title: 'Đã có rồi',
notif_acc_not_found_title: 'Không tìm thấy',
// ── Notification bodies ──
notif_xp_done_body: (xp, loops) => `Đã farm ${xp} XP trong ${loops} vòng.`,
notif_xp_done_inf_body: (xp) => `+${xp} XP đã nhận.`,
notif_xp_stopped_body: (xp) => `+${xp} XP đã nhận.`,
notif_gem_done_body: (gems) => `+${gems} gem đã nhận.`,
notif_gem_no_rewards: 'Không có phần thưởng. Đang thử lại…',
notif_streak_done_body: (days) => `Đã khôi phục ${days} ngày streak.`,
notif_streak_no_days: 'Không thể lưu ngày nào. Vui lòng thử lại.',
notif_streak_done_inf_body: (days, unit) => `+${days} ${unit} đã nhận.`,
notif_streak_stopped_body: (days) => `Đã xử lý ${days} ngày.`,
notif_practice_done_body: (n) => `Đã hoàn thành ${n} bài practice.`,
notif_practice_next_body: (n, total) => `Xong ${n}${total > 0 ? ' / ' + total : ''} — đang tải tiếp...`,
notif_practice_nav: 'Đang điều hướng đến practice...',
notif_practice_busy: 'Farm practice đang chạy rồi.',
notif_daily_done_body: (n) => `Đã hoàn thành ${n} chỉ tiêu.`,
notif_all_done_body: 'Đã hoàn thành tất cả nhiệm vụ ngày.',
notif_daily_error: 'Không thể tải dữ liệu nhiệm vụ.',
notif_daily_fail: 'Hoàn thành nhiệm vụ thất bại.',
notif_mq_all_body: (n) => `Đã nhận ${n} nhiệm vụ!`,
notif_mq_partial_body: (ok, total) => `${ok}/${total} nhiệm vụ đã nhận.`,
notif_mq_fail: 'Không thể nhận nhiệm vụ nào.',
notif_mq_not_loaded: 'Mở trang Nhiệm vụ tháng trước.',
notif_mq_nothing: 'Không có nhiệm vụ nào chưa nhận.',
notif_super_body: 'Đã kích hoạt Super Duolingo miễn phí!',
notif_super_fail: 'Kích hoạt thất bại. Có thể bạn đã có Super rồi.',
notif_shop_got: (name) => `Đã nhận ${name}!`,
notif_shop_fail: 'Không lấy được vật phẩm.',
notif_v1xp_errors: 'Quá nhiều lỗi, đang dừng.',
notif_v1xp_done_body: (xp) => `Đã farm ${xp} XP.`,
notif_v1gem_done_body: (gems) => `+${gems} gem đã nhận.`,
notif_v1streak_done_body: (days) => `Đã farm ${days} ngày streak.`,
notif_stopped_body: 'Đã dừng farm.',
notif_busy_v1: 'Dừng farm V1 hiện tại trước.',
notif_not_connected_body: 'Vui lòng chờ.',
notif_not_connected_conn: 'Vui lòng chờ kết nối.',
notif_acc_saved_body: (name) => `Đã lưu tài khoản: ${name}`,
notif_acc_removed_body: 'Đã xóa tài khoản khỏi danh sách.',
notif_acc_already_body: 'Tài khoản này đã được lưu rồi.',
notif_acc_not_found_body: 'Không tìm thấy tài khoản.',
},
es: {
// ── Language selector ──
lang_select_label: 'Idioma',
lang_en: 'English',
lang_vi: 'Tiếng Việt',
lang_es: 'Español',
lang_fr: 'Français',
// ── Top bar ──
hide: 'Ocultar',
show: 'Mostrar',
switch_v1: 'Cambiar a Solver',
switch_v2: 'Cambiar a V2',
// ── Connection status ──
connecting: 'Conectando',
connected: 'Conectado',
error: 'Error',
outdated: 'Desactualizado',
// ── Page 1 (main) ──
donate: 'Donar',
xp_question: '¿Cuánto XP te gustaría ganar?',
gems_run_label: 'Haz clic en "EJECUTAR" para farmear Gemas',
streak_question: '¿Cuántos días de racha quieres restaurar?',
extra_features: 'Funciones extra',
// ── Page 2 (extra features) ──
back: 'Atrás',
farm_practice: '¿Cuántas lecciones de práctica quieres resolver?',
farm_practice_sub: '0 = sesiones de práctica ilimitadas',
shop_items: 'Artículos de la tienda',
auto_league: 'Liga automática',
auto_league_sub: 'Farmea XP para alcanzar tu rango objetivo',
auto_daily_quest: 'Misión diaria automática',
auto_daily_sub: 'Completa todas las misiones diarias',
claim_monthly: 'Reclamar misión mensual',
claim_monthly_sub: 'Ver y reclamar misiones mensuales',
free_super: 'Super Duolingo gratis',
free_super_sub: 'Activa Super Duolingo gratis',
// ── Page 4 (settings) ──
loop_delay: 'Retraso de bucle (ms)',
free_duo_max: 'Duolingo Max gratis',
free_duo_max_sub: 'Solo del lado del cliente, recarga para aplicar',
hide_profile: 'Ocultar perfil',
auto_solver: 'Resolución automática',
auto_solver_sub: 'Mostrar botones de resolución automática durante las lecciones',
hide_animation: 'Ocultar animación',
hide_animation_sub: 'Ocultar imágenes y animaciones de Duolingo',
lesson_shortener: 'Acortador de lecciones',
lesson_shortener_sub: 'Reemplaza las lecciones con 1 pregunta instantánea',
stories_shortener: 'Saltar desafíos de historias',
stories_shortener_sub: 'Salta todos los desafíos en las historias',
safe_mode: 'Modo seguro',
safe_mode_sub: 'Añade más retraso entre clics al resolver lecciones/prácticas',
view_credits: 'Ver créditos',
// ── Page 3 (shop) ──
search_placeholder: 'Buscar artículos...',
loading_shop: 'Cargando tienda...',
no_items_found: 'No se encontraron artículos.',
no_items_available: 'No hay artículos disponibles.',
// ── Page 5 (account manager) ──
account_manager: 'Administrador de cuentas',
no_saved_accounts: 'No hay cuentas guardadas.',
// ── Page 6 (monthly quests) ──
monthly_quests: 'Misiones mensuales',
loading_quests: 'Cargando misiones...',
no_monthly_quests: 'No se encontraron misiones de este mes.',
quest_failed: 'Error al cargar las misiones.',
// ── Page 7 (credits) ──
credits: 'Créditos',
// ── Page V1 ──
xp_farming: 'Farmeo de XP',
farm_gems: 'Farmear Gemas',
streak_farming: 'Farmeo de racha',
activate_super_q: '¿Te gustaría activar Super Duolingo gratis?',
settings: 'Ajustes',
// ── Not connected ──
not_connected: 'No conectado.',
// ── Buttons (short labels) ──
btn_get: 'OBTENER',
btn_run: 'EJECUTAR',
btn_stop: 'DETENER',
btn_save: 'GUARDAR',
btn_saved: 'GUARDADO ✓',
btn_claim: 'RECLAMAR TODO',
btn_activate: 'ACTIVAR',
btn_done: 'HECHO ✓',
btn_save_current: 'GUARDAR ACTUAL',
btn_solve_all: 'RESOLVER TODO',
btn_pause: 'PAUSAR',
btn_loading: 'Cargando...',
btn_running: 'Ejecutando...',
btn_got: 'OBTENIDO ✓',
btn_failed: 'FALLIDO',
// ── Hide Profile status ──
profile_private: 'El perfil es privado',
profile_public: 'El perfil es público',
status_unavailable: 'No disponible',
status_saving: 'Guardando…',
status_failed_retry: 'Error — reintentar',
status_not_connected: 'No conectado',
status_loading: 'Cargando…',
// ── Page 11 (leaderboard) ──
leaderboard: 'Clasificación',
leaderboard_sub: 'Ver tu liga actual',
your_current_league: 'Tu liga actual',
lb_loading: 'Cargando clasificación...',
lb_failed: 'Error al cargar la clasificación.',
choose_reaction: 'Elegir reacción',
lb_promoted: '⬆ Ascendido',
lb_demoted: '⬇ Descendido',
lb_safe: '🟡 Seguro',
lb_ended: 'Concurso finalizado',
// ── Leaderboard separators ──
lb_rank_up: 'Subir de rango',
lb_rank_down: 'Bajar de rango',
// ── Monthly quest status ──
mq_completed: 'COMPLETADO',
// ── Changelog badge ──
changelog_current: 'ACTUAL',
// ── Account manager labels ──
acc_active: 'Activa',
acc_login_btn: 'INICIAR SESIÓN',
// ── Connection error subtexts ──
conn_not_logged_in: 'No has iniciado sesión',
conn_invalid_token: 'Token inválido',
conn_failed_retry: 'Error — reintentando',
// ── Streak live counter suffixes ──
streak_day: 'día',
streak_days: 'días',
// ── Notification titles ──
notif_xp_done_title: '¡Farmeo de XP completado!',
notif_xp_stopped_title: 'Farmeo de XP detenido',
notif_gem_done_title: '¡Farmeo de Gemas completado!',
notif_gem_farm_title: 'Farmeo de Gemas',
notif_streak_done_title: '¡Farmeo de racha completado!',
notif_streak_farm_title: 'Farmeo de racha',
notif_streak_stopped_title: 'Farmeo de racha detenido',
notif_practice_done_title: '¡Farmeo de práctica completado!',
notif_practice_title: 'Farmeo de práctica',
notif_daily_done_title: '¡Misiones diarias completadas!',
notif_all_done_title: '¡Todo listo!',
notif_mq_title: 'Misiones mensuales',
notif_mq_partial_title: 'Parcial',
notif_super_title: '¡Super activado!',
notif_shop_title: 'Tienda',
notif_error_title: 'Error',
notif_failed_title: 'Fallido',
notif_stopped_title: 'Detenido',
notif_busy_title: 'Ocupado',
notif_not_connected_title: 'No conectado',
notif_not_loaded_title: 'No cargado',
notif_nothing_to_do_title: 'Nada que hacer',
notif_v1xp_title: 'V1 XP',
notif_acc_saved_title: 'Cuenta guardada',
notif_acc_removed_title: 'Eliminada',
notif_acc_already_title: 'Ya guardada',
notif_acc_not_found_title: 'No encontrada',
// ── Notification bodies ──
notif_xp_done_body: (xp, loops) => `Se farmearon ${xp} XP en ${loops} vueltas.`,
notif_xp_done_inf_body: (xp) => `+${xp} XP obtenidos.`,
notif_xp_stopped_body: (xp) => `+${xp} XP obtenidos.`,
notif_gem_done_body: (gems) => `+${gems} gemas obtenidas.`,
notif_gem_no_rewards: 'No hay recompensas disponibles. Reintentando…',
notif_streak_done_body: (days) => `Se restauraron ${days} días de racha.`,
notif_streak_no_days: 'No se pudo guardar ningún día. Inténtalo de nuevo.',
notif_streak_done_inf_body: (days, unit) => `+${days} ${unit} obtenidos.`,
notif_streak_stopped_body: (days) => `Se procesaron ${days} días.`,
notif_practice_done_body: (n) => `Se completaron ${n} práctica(s).`,
notif_practice_next_body: (n, total) => `Hecho ${n}${total > 0 ? ' / ' + total : ''} — cargando siguiente...`,
notif_practice_nav: 'Navegando a la práctica...',
notif_practice_busy: 'El farmeo de práctica ya está en curso.',
notif_daily_done_body: (n) => `Se completaron ${n} objetivo(s).`,
notif_all_done_body: 'Todas las misiones diarias completadas.',
notif_daily_error: 'No se pudieron cargar los datos de la misión.',
notif_daily_fail: 'Error al completar la misión.',
notif_mq_all_body: (n) => `¡${n} misión(es) reclamada(s)!`,
notif_mq_partial_body: (ok, total) => `${ok}/${total} misiones reclamadas.`,
notif_mq_fail: 'No se pudo reclamar ninguna misión.',
notif_mq_not_loaded: 'Abre primero Misiones mensuales.',
notif_mq_nothing: 'No se encontraron misiones sin reclamar.',
notif_super_body: '¡Super Duolingo gratis activado!',
notif_super_fail: 'Error al activar. Puede que ya tengas Super.',
notif_shop_got: (name) => `¡Obtuviste ${name}!`,
notif_shop_fail: 'Error al obtener el artículo.',
notif_v1xp_errors: 'Demasiados errores, deteniendo.',
notif_v1xp_done_body: (xp) => `Se farmearon ${xp} XP.`,
notif_v1gem_done_body: (gems) => `+${gems} gemas obtenidas.`,
notif_v1streak_done_body: (days) => `Se farmearon ${days} días de racha.`,
notif_stopped_body: 'Farmeo detenido.',
notif_busy_v1: 'Detén el farmeo V1 actual primero.',
notif_not_connected_body: 'Por favor espera.',
notif_not_connected_conn: 'Por favor espera la conexión.',
notif_acc_saved_body: (name) => `Cuenta guardada: ${name}`,
notif_acc_removed_body: 'Cuenta eliminada de la lista.',
notif_acc_already_body: 'Esta cuenta ya está en la lista.',
notif_acc_not_found_body: 'Cuenta no encontrada.',
},
fr: {
// ── Language selector ──
lang_select_label: 'Langue',
lang_en: 'English',
lang_vi: 'Tiếng Việt',
lang_es: 'Español',
lang_fr: 'Français',
// ── Top bar ──
hide: 'Masquer',
show: 'Afficher',
switch_v1: 'Passer à Solver',
switch_v2: 'Passer à V2',
// ── Connection status ──
connecting: 'Connexion',
connected: 'Connecté',
error: 'Erreur',
outdated: 'Obsolète',
// ── Page 1 (main) ──
donate: 'Faire un don',
xp_question: "Combien d'XP souhaitez-vous gagner ?",
gems_run_label: 'Cliquez sur « LANCER » pour farmer des Gemmes',
streak_question: 'Combien de jours de série voulez-vous restaurer ?',
extra_features: 'Fonctionnalités supplémentaires',
// ── Page 2 (extra features) ──
back: 'Retour',
farm_practice: 'Combien de leçons de pratique voulez-vous résoudre ?',
farm_practice_sub: '0 = sessions de pratique illimitées',
shop_items: 'Articles de la boutique',
auto_league: 'Ligue automatique',
auto_league_sub: "Farmez de l'XP pour atteindre votre rang cible",
auto_daily_quest: 'Quête quotidienne automatique',
auto_daily_sub: 'Complétez toutes les quêtes quotidiennes',
claim_monthly: 'Réclamer la quête mensuelle',
claim_monthly_sub: 'Voir et réclamer les quêtes mensuelles',
free_super: 'Super Duolingo gratuit',
free_super_sub: 'Activez Super Duolingo gratuitement',
// ── Page 4 (settings) ──
loop_delay: 'Délai de boucle (ms)',
free_duo_max: 'Duolingo Max gratuit',
free_duo_max_sub: 'Côté client uniquement, rechargez pour appliquer',
hide_profile: 'Masquer le profil',
auto_solver: 'Résolveur automatique',
auto_solver_sub: 'Afficher les boutons du résolveur automatique pendant les leçons',
hide_animation: "Masquer l'animation",
hide_animation_sub: 'Masquer les images et animations de Duolingo',
lesson_shortener: 'Raccourcisseur de leçons',
lesson_shortener_sub: 'Remplace les leçons par 1 question instantanée',
stories_shortener: 'Passer les défis des histoires',
stories_shortener_sub: 'Ignore tous les défis dans les histoires',
safe_mode: 'Mode sécurisé',
safe_mode_sub: 'Ajoute plus de délai entre les clics lors de la résolution des leçons/pratiques',
view_credits: 'Voir les crédits',
// ── Page 3 (shop) ──
search_placeholder: 'Rechercher des articles...',
loading_shop: 'Chargement de la boutique...',
no_items_found: 'Aucun article trouvé.',
no_items_available: 'Aucun article disponible.',
// ── Page 5 (account manager) ──
account_manager: 'Gestionnaire de comptes',
no_saved_accounts: 'Aucun compte enregistré.',
// ── Page 6 (monthly quests) ──
monthly_quests: 'Quêtes mensuelles',
loading_quests: 'Chargement des quêtes...',
no_monthly_quests: 'Aucune quête trouvée ce mois-ci.',
quest_failed: 'Échec du chargement des quêtes.',
// ── Page 7 (credits) ──
credits: 'Crédits',
// ── Page V1 ──
xp_farming: "Farming d'XP",
farm_gems: 'Farmer des Gemmes',
streak_farming: 'Farming de série',
activate_super_q: 'Voulez-vous activer Super Duolingo gratuitement ?',
settings: 'Paramètres',
// ── Not connected ──
not_connected: 'Non connecté.',
// ── Buttons (short labels) ──
btn_get: 'OBTENIR',
btn_run: 'LANCER',
btn_stop: 'ARRÊTER',
btn_save: 'ENREGISTRER',
btn_saved: 'ENREGISTRÉ ✓',
btn_claim: 'TOUT RÉCLAMER',
btn_activate: 'ACTIVER',
btn_done: 'TERMINÉ ✓',
btn_save_current: 'ENREGISTRER ACTUEL',
btn_solve_all: 'TOUT RÉSOUDRE',
btn_pause: 'PAUSE',
btn_loading: 'Chargement...',
btn_running: 'En cours...',
btn_got: 'OBTENU ✓',
btn_failed: 'ÉCHEC',
// ── Hide Profile status ──
profile_private: 'Le profil est privé',
profile_public: 'Le profil est public',
status_unavailable: 'Indisponible',
status_saving: 'Enregistrement…',
status_failed_retry: 'Échec — réessayer',
status_not_connected: 'Non connecté',
status_loading: 'Chargement…',
// ── Page 11 (leaderboard) ──
leaderboard: 'Classement',
leaderboard_sub: 'Voir votre ligue actuelle',
your_current_league: 'Votre ligue actuelle',
lb_loading: 'Chargement du classement...',
lb_failed: 'Échec du chargement du classement.',
choose_reaction: 'Choisir une réaction',
lb_promoted: '⬆ Promu',
lb_demoted: '⬇ Rétrogradé',
lb_safe: '🟡 Sûr',
lb_ended: 'Concours terminé',
// ── Leaderboard separators ──
lb_rank_up: 'Monter de rang',
lb_rank_down: 'Descendre de rang',
// ── Monthly quest status ──
mq_completed: 'TERMINÉ',
// ── Changelog badge ──
changelog_current: 'ACTUEL',
// ── Account manager labels ──
acc_active: 'Actif',
acc_login_btn: 'CONNEXION',
// ── Connection error subtexts ──
conn_not_logged_in: 'Non connecté',
conn_invalid_token: 'Jeton invalide',
conn_failed_retry: 'Échec — nouvelle tentative',
// ── Streak live counter suffixes ──
streak_day: 'jour',
streak_days: 'jours',
// ── Notification titles ──
notif_xp_done_title: "Farming d'XP terminé !",
notif_xp_stopped_title: "Farming d'XP arrêté",
notif_gem_done_title: 'Farming de Gemmes terminé !',
notif_gem_farm_title: 'Farming de Gemmes',
notif_streak_done_title: 'Farming de série terminé !',
notif_streak_farm_title: 'Farming de série',
notif_streak_stopped_title: 'Farming de série arrêté',
notif_practice_done_title: 'Farming de pratique terminé !',
notif_practice_title: 'Farming de pratique',
notif_daily_done_title: 'Quêtes quotidiennes terminées !',
notif_all_done_title: 'Tout est terminé !',
notif_mq_title: 'Quêtes mensuelles',
notif_mq_partial_title: 'Partiel',
notif_super_title: 'Super activé !',
notif_shop_title: 'Boutique',
notif_error_title: 'Erreur',
notif_failed_title: 'Échec',
notif_stopped_title: 'Arrêté',
notif_busy_title: 'Occupé',
notif_not_connected_title: 'Non connecté',
notif_not_loaded_title: 'Non chargé',
notif_nothing_to_do_title: 'Rien à faire',
notif_v1xp_title: 'V1 XP',
notif_acc_saved_title: 'Compte enregistré',
notif_acc_removed_title: 'Supprimé',
notif_acc_already_title: 'Déjà enregistré',
notif_acc_not_found_title: 'Introuvable',
// ── Notification bodies ──
notif_xp_done_body: (xp, loops) => `${xp} XP farmés en ${loops} boucles.`,
notif_xp_done_inf_body: (xp) => `+${xp} XP obtenus.`,
notif_xp_stopped_body: (xp) => `+${xp} XP obtenus.`,
notif_gem_done_body: (gems) => `+${gems} gemmes obtenues.`,
notif_gem_no_rewards: 'Aucune récompense disponible. Nouvelle tentative…',
notif_streak_done_body: (days) => `${days} jours de série restaurés.`,
notif_streak_no_days: "Aucun jour n'a pu être enregistré. Veuillez réessayer.",
notif_streak_done_inf_body: (days, unit) => `+${days} ${unit} obtenus.`,
notif_streak_stopped_body: (days) => `${days} jours traités.`,
notif_practice_done_body: (n) => `${n} pratique(s) terminée(s).`,
notif_practice_next_body: (n, total) => `${n}${total > 0 ? ' / ' + total : ''} terminé — chargement du suivant...`,
notif_practice_nav: 'Navigation vers la pratique...',
notif_practice_busy: 'Le farming de pratique est déjà en cours.',
notif_daily_done_body: (n) => `${n} objectif(s) complété(s).`,
notif_all_done_body: 'Toutes les quêtes quotidiennes terminées.',
notif_daily_error: 'Impossible de charger les données de quête.',
notif_daily_fail: 'Échec de la complétion de la quête.',
notif_mq_all_body: (n) => `${n} quête(s) réclamée(s) !`,
notif_mq_partial_body: (ok, total) => `${ok}/${total} quêtes réclamées.`,
notif_mq_fail: "Aucune quête n'a pu être réclamée.",
notif_mq_not_loaded: "Ouvrez d'abord les Quêtes mensuelles.",
notif_mq_nothing: 'Aucune quête non réclamée trouvée.',
notif_super_body: 'Super Duolingo gratuit activé !',
notif_super_fail: 'Échec de l\'activation. Vous avez peut-être déjà Super.',
notif_shop_got: (name) => `Vous avez obtenu ${name} !`,
notif_shop_fail: "Échec de l'obtention de l'article.",
notif_v1xp_errors: "Trop d'erreurs, arrêt en cours.",
notif_v1xp_done_body: (xp) => `${xp} XP farmés.`,
notif_v1gem_done_body: (gems) => `+${gems} gemmes obtenues.`,
notif_v1streak_done_body: (days) => `${days} jours de série farmés.`,
notif_stopped_body: 'Farming arrêté.',
notif_busy_v1: "Arrêtez d'abord le farming V1 actuel.",
notif_not_connected_body: 'Veuillez patienter.',
notif_not_connected_conn: 'Veuillez patienter pendant la connexion.',
notif_acc_saved_body: (name) => `Compte enregistré : ${name}`,
notif_acc_removed_body: 'Compte supprimé de la liste.',
notif_acc_already_body: 'Ce compte est déjà dans la liste.',
notif_acc_not_found_body: 'Compte introuvable.',
}
};
function _t(key, ...args) {
const d = _LANGS[_lang] || _LANGS.en;
const v = d[key];
const en = _LANGS.en[key];
// fallback to EN if VI value empty
const val = (v !== undefined && v !== '') ? v : (en !== undefined ? en : key);
// Support function values for parameterised strings
return typeof val === 'function' ? val(...args) : val;
}
function _setLang(l) {
if (l !== 'vi' && l !== 'en' && l !== 'es' && l !== 'fr') return;
_lang = l;
localStorage.setItem(_I18N_KEY, l);
_applyLang();
}
function _applyLang() {
// Static text nodes mapped by element id → translation key
const _ID_MAP = {
'DH_Hide_Txt': () => document.getElementById('DH_Hide_Ico')?.textContent === '' ? 'show' : 'hide',
'DH_Conn_Txt': () => _currentConnState === 'connected' && _isOutdated ? 'outdated' : (_currentConnState || 'connecting'),
// Page 1
'DH_Donate_Btn_Lbl': 'donate',
'DH_XP_Question': 'xp_question',
'DH_Gem_Run_Label': 'gems_run_label',
'DH_Streak_Question': 'streak_question',
'DH_ExtraFeatures_Lbl': 'extra_features',
// Page 2
'DH_Back_Txt': 'back',
'DH_Practice_Question': 'farm_practice',
'DH_Practice_Sub': 'farm_practice_sub',
'DH_Shop_Lbl': 'shop_items',
'DH_League_Title': 'auto_league',
'DH_League_Sub': 'auto_league_sub',
'DH_Quest_Title': 'auto_daily_quest',
'DH_Quest_Sub': 'auto_daily_sub',
'DH_Monthly_Title': 'claim_monthly',
'DH_Monthly_Sub': 'claim_monthly_sub',
// Page 4
'DH_Settings_Back_Txt': 'back',
'DH_LoopDelay_Lbl': 'loop_delay',
'DH_FreeDuoMax_Lbl': 'free_duo_max',
'DH_FreeDuoMax_Sub': 'free_duo_max_sub',
'DH_HideProfile_Lbl': 'hide_profile',
'DH_AutoSolver_Lbl': 'auto_solver',
'DH_AutoSolver_Sub': 'auto_solver_sub',
'DH_HideAnim_Lbl': 'hide_animation',
'DH_HideAnim_Sub': 'hide_animation_sub',
'DH_LessonShortener_Lbl': 'lesson_shortener',
'DH_LessonShortener_Sub': 'lesson_shortener_sub',
'DH_StoriesShortener_Lbl': 'stories_shortener',
'DH_StoriesShortener_Sub': 'stories_shortener_sub',
'DH_SafeMode_Lbl': 'safe_mode',
'DH_SafeMode_Sub': 'safe_mode_sub',
'DH_Credits_Btn': 'view_credits',
// Page 9 (license) / Page 10 (changelog)
'DH_License_Back_Txt': 'back',
'DH_Changelog_Back_Txt': 'back',
// Page 3 shop back (now has id)
'DH_Shop_Back_Txt': 'back',
// Account / Monthly Quest back + static labels
'DH_AccMgr_Back_Txt': 'back',
'DH_AccMgr_Title': 'account_manager',
'DH_MQ_Back_Txt': 'back',
'DH_MQ_Title': 'monthly_quests',
// Page 7
'DH_Credits_Back_Txt': 'back',
'DH_Credits_Title': 'credits',
// Page V1
'DH_V1_XP_Title': 'xp_farming',
'DH_V1_Gem_Title': 'farm_gems',
'DH_V1_Streak_Title': 'streak_farming',
'DH_V1_Settings_Lbl': 'extra_features',
'DH_V1_Settings_Back_Txt': 'back',
// Switch buttons
'DH_SwitchV1_Lbl': 'switch_v1',
'DH_SwitchV2_Lbl': 'switch_v2',
// Main action buttons
'DH_XP_Lbl': 'btn_get',
'DH_Gem_Lbl': 'btn_run',
'DH_Streak_Lbl': 'btn_run',
'DH_Practice_Lbl': 'btn_run',
'DH_League_Lbl': 'btn_run',
'DH_Quest_Lbl': 'btn_run',
'DH_MonthlyQuest_Claim_Lbl': 'btn_get',
'DH_Delay_Lbl': 'btn_save',
// Account / Monthly Quest
'DH_AccSave_Lbl': 'btn_save_current',
'DH_MQ_ClaimAll_Lbl': 'btn_claim',
// V1 buttons
'DH_V1_XP_Lbl': 'btn_run',
'DH_V1_Gem_Lbl': 'btn_run',
'DH_V1_Streak_Lbl': 'btn_run',
// Static initial state text
'DH_AccNoSaved_Txt': 'no_saved_accounts',
'DH_ShopLoading_Txt': 'loading_shop',
// Page 11 (leaderboard)
'DH_LB_Back_Txt': 'back',
// Page 12 (reaction picker)
'DH_React_Back_Txt': 'back',
'DH_React_Title': 'choose_reaction',
'DH_LB_Title': 'leaderboard',
'DH_React_Title': 'choose_reaction',
'DH_LB_Loading_Txt': 'lb_loading',
'DH_LB_Nav_Lbl': 'leaderboard',
};
for (const [id, key] of Object.entries(_ID_MAP)) {
const el = document.getElementById(id);
if (!el) continue;
if (typeof key === 'function') {
el.textContent = _t(key());
} else if (key) {
el.textContent = _t(key);
}
}
// Re-apply connection status text so it updates on lang switch
// try/catch because _currentConnState is declared later (let TDZ on first _applyLang call)
// Placeholder attributes
const ph = document.getElementById('DH_Shop_Search');
if (ph) ph.placeholder = _t('search_placeholder');
// Flag icon URLs per language
const _LANG_ICON = {
vi: 'https://d35aaqx5ub95lt.cloudfront.net/vendor/2b077d42185bc45d4896ed55f15c4fea.svg',
en: 'https://d35aaqx5ub95lt.cloudfront.net/vendor/bbe17e16aa4a106032d8e3521eaed13e.svg',
es: 'https://d35aaqx5ub95lt.cloudfront.net/vendor/59a90a2cedd48b751a8fd22014768fd7.svg',
fr: 'https://d35aaqx5ub95lt.cloudfront.net/vendor/482fda142ee4abd728ebf4ccce5d3307.svg',
};
const _LANG_CODE = { vi: 'VI', en: 'EN', es: 'ES', fr: 'FR' };
const _flagImg = (code) => `
`;
// Lang selector button label
const lb = document.getElementById('DH_LangSelector_Lbl');
if (lb) {
const code = _LANG_ICON[_lang] ? _lang : 'en';
lb.innerHTML = `${_flagImg(code)} ${_LANG_CODE[code]}`;
}
// Dropdown option labels
const opEn = document.getElementById('DH_LangOpt_en');
const opVi = document.getElementById('DH_LangOpt_vi');
const opEs = document.getElementById('DH_LangOpt_es');
const opFr = document.getElementById('DH_LangOpt_fr');
if (opEn) opEn.innerHTML = _flagImg('en') + ' ' + _t('lang_en');
if (opVi) opVi.innerHTML = _flagImg('vi') + ' ' + _t('lang_vi');
if (opEs) opEs.innerHTML = _flagImg('es') + ' ' + _t('lang_es');
if (opFr) opFr.innerHTML = _flagImg('fr') + ' ' + _t('lang_fr');
}
// ── End i18n ──────────────────────────────────────────────────────
// ── Unified fetch hook (Lesson Shortener + Stories Shortener + Free Max) ──
//
// ROOT CAUSE OF PREVIOUS CONFLICT:
// _installFakeSuper() injected a