// ==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