/**
* ╔════╗─────────────╔╗───────────────╔╗──────╔════╗╔═══╗╔═══╗╔═╗─╔╗╔═══╗──╔═══╗╔═══╗╔═══╗
* ║╔╗╔╗║─────────────║║───────────────║║──────║╔╗╔╗║║╔═╗║║╔═╗║║║╚╗║║║╔═╗║──║╔═╗║║╔═╗║║╔═╗║
* ╚╝║║╚╝╔╗─╔╗╔══╗╔══╗║║───╔══╗╔══╗╔══╗║╚═╦╗─╔╗╚╝║║╚╝║╚═╝║║║─║║║╔╗╚╝║║║─╚╝──║╚═╝║║╚═╝║║║─║║
* ──║║──║║─║║║╔╗║║║═╣║║─╔╗║║═╣║══╣║══╣║╔╗║║─║║──║║──║╔╗╔╝║║─║║║║╚╗║║║║╔═╗──║╔══╝║╔╗╔╝║║─║║
* ──║║──║╚═╝║║╚╝║║║═╣║╚═╝║║║═╣╠══║╠══║║╚╝║╚═╝║──║║──║║║╚╗║╚═╝║║║─║║║║╚╩═║╔╗║║───║║║╚╗║╚═╝║
* ──╚╝──╚═╗╔╝║╔═╝╚══╝╚═══╝╚══╝╚══╝╚══╝╚══╩═╗╔╝──╚╝──╚╝╚═╝╚═══╝╚╝─╚═╝╚═══╝╚╝╚╝───╚╝╚═╝╚═══╝
* ──────╔═╝║─║║──────────────────────────╔═╝║
* ──────╚══╝─╚╝──────────────────────────╚══╝
*
* TypeLess - Auto Form Filler
* v1.0.5 by TRONG.PRO
*/
// i18n.js - Internationalization module for Auto Form Filler
// Guard: returns false when the extension has been reloaded/invalidated.
// All chrome.* calls should be skipped if this returns false.
function _i18nExtAlive() {
try { return !!(chrome && chrome.runtime && chrome.runtime.id); }
catch (_) { return false; }
}
const i18n = {
// Language dictionary
translations: {
en: {
'lang.en': 'English',
'lang.vi': 'Tiếng Việt',
'lang.zh_CN': '中文 (简体)',
'lang.zh_TW': '中文 (繁體)',
'lang.ko': '한국어',
'lang.ja': '日本語',
'btn.reload': 'Reload Page',
'btn.refresh_list': 'Refresh List',
'btn.save': 'Save',
'btn.cancel': 'Cancel',
'modal.cancel': 'Cancel',
'btn.export': 'Export',
'btn.import': 'Import',
'btn.minimize': 'Minimize',
'btn.expand': 'Expand',
'btn.hide': 'Hide',
'btn.lang': 'EN',
'btn.lang_tooltip': 'Switch Language',
'btn.copy': 'Copy',
'btn.paste': 'Paste',
'btn.smart': 'Smart Fill',
'btn.enable_right_click': 'Unlock Right Click',
'btn.save_html': 'Save HTML',
'btn.clear': 'Clear',
'btn.save_settings': 'Save Settings',
'btn.reset_ua': 'Reset Defaults',
'btn.open_options': 'Open Settings',
'btn.expand_tooltip': 'Expand',
'btn.minimize_tooltip': 'Minimize',
'btn.delete_profile': 'Delete profile',
'btn.copy_tooltip': 'Copy to clipboard',
'btn.apply_tooltip': 'Apply profile',
'btn.screenshot_full': 'Screenshot Full',
'btn.screenshot_visible': 'Screenshot Visible',
'btn.save_profile': 'Save Profile',
'btn.delete_confirm': 'Delete',
'btn.cancel_confirm': 'Cancel',
'btn.rename_tooltip': 'Rename profile',
'toolbar.title': '
TypeLess - Auto Form Filler',
'toolbar.empty': 'No profiles for this page. Click
to save.',
'toolbar.loading': 'Loading profiles...',
'notify.refreshed': 'Profile list refreshed',
'notify.saved': 'Saved profile "{name}" with {count} fields',
'notify.applied': 'Applied profile "{name}"',
'notify.deleted': 'Deleted profile "{name}"',
'notify.exported': 'Exported {count} profiles & settings',
'notify.imported': '📥 Import done:
+{added} added
↺{updated} updated
⚠️{skipped} skipped',
'notify.added_new': 'new',
'notify.updated': 'updated',
'notify.skipped': 'skipped',
'notify.lang_changed': 'Language changed to English',
'notify.copied': 'Copied to clipboard!',
'notify.pasted': 'Pasted from clipboard!',
'notify.smart': 'Smart filled {count} fields!',
'notify.right_click_enabled': 'Right click & copy enabled!',
'notify.html_saved': 'HTML saved successfully',
'notify.copied_all': 'Copied all profiles to clipboard',
'notify.no_fields_filled': 'No fields filled',
'notify.unlocked': 'Unlocked {count} elements',
'confirm.reload': 'Reload this page?',
'confirm.delete': 'Delete profile "{name}"?',
'modal.title': 'Save Profile',
'modal.profile_name': 'Profile Name:',
'modal.select_fields': 'Select fields to save ({count} fields):',
'modal.select_all': '✓ Select All',
'modal.deselect_all': '✗ Deselect',
'modal.save': '
Save Profile',
'modal.empty_value': '(empty)',
'modal.checked': '✓ Checked',
'alert.enter_name': 'Please enter profile name!',
'alert.select_field': 'Please select at least 1 field!',
'alert.no_fields': 'No fields found to save!',
'alert.no_export': 'No profiles to export!',
'alert.error_save': 'Error saving profile!',
'alert.error_apply': 'Error applying profile!',
'alert.page_not_supported': 'This page is not supported!',
'alert.error_import': 'Error importing file. Please check the format.',
'alert.invalid_json': 'Invalid JSON data',
'alert.profile_not_found': 'Profile not found!',
'alert.error_delete': 'Error deleting profile',
'alert.error_copy': 'Error copying to clipboard',
'overwrite.title': '
Profile "{name}" already exists for this page!',
'overwrite.current': '• Current profile: {count} fields',
'overwrite.new': '• New profile: {count} fields',
'overwrite.confirm': 'Do you want to OVERWRITE the old profile?',
'overwrite.confirm_btn': 'Overwrite',
'overwrite.cancel_btn': 'Cancel',
'field.count': '{count} fields',
'field.fullname': 'Full Name',
'field.firstName': 'First Name',
'field.lastName': 'Last Name',
'field.email': 'Email',
'field.phone': 'Phone',
'field.dob': 'Date of Birth',
'field.address': 'Address',
'field.city': 'City',
'field.zipCode': 'ZIP Code',
'field.country': 'Country',
'field.unknown': 'Unknown Field',
'popup.title': 'TypeLess - Auto Form Filler',
'popup.subtitle': 'Manage profiles',
'popup.toggle': 'Expand/Minimize Toolbar',
'popup.show': 'Show/Hide Toolbar',
'popup.empty': 'No profiles yet',
'popup.empty_hint': 'Click "Save" to create a new profile',
'popup.settings_moved': 'Settings have moved to a dedicated page for better experience.',
'popup.restricted_msg': 'TypeLess cannot work on system browser pages. Please switch to a regular webpage.',
'popup.restricted_msg_protocol': 'TypeLess cannot work on {protocol} pages (system browser page). Please switch to a regular webpage.',
'popup.restricted_unavailable': 'Not available on system pages',
'tab.profiles': 'Profiles',
'tab.tools': 'Tools',
'tab.data': 'Data Management',
'tab.settings': 'Settings',
'grp.actions': 'Utilities',
'grp.backup': 'Data Management',
'grp.extension_settings': 'Extension Settings',
'settings.personal': 'Personal Info',
'settings.address': 'Address',
'footer.by': 'by',
'footer.enjoy': 'Enjoy my work?',
'footer.buy': 'Buy me a
',
'footer.tip': 'or tip via',
'placeholder.profile_name': 'Example: Profile 1',
'placeholder.firstName': 'John',
'placeholder.lastName': 'Smith',
'placeholder.email': 'email@example.com',
'placeholder.phone': '0912345678',
'placeholder.dob': 'YYYY/MM/DD',
'placeholder.address': '123 Street',
'placeholder.city': 'Hanoi',
'placeholder.zipCode': '100000',
'placeholder.country': 'Vietnam',
'menu.smart_fill': '
Smart Fill Form',
'menu.save_profile': 'Save Form as Profile',
'menu.remove_readonly': '
Remove Readonly & Disabled',
'label.ua_android': 'Android User-Agent',
'label.ua_ios': 'iOS User-Agent',
'label.ua_macos': 'MacOS User-Agent',
'label.ua_windows': 'Windows User-Agent',
'label.ua_linux': 'Linux User-Agent',
'options.welcome_title': 'Welcome to TypeLess!',
'options.welcome_subtitle': 'Your ultimate auto-filling assistant.',
'options.personal_hint': 'Data used for Smart Fill feature.',
'options.language': 'Language',
'options.menu_welcome': 'Welcome',
'options.menu_personal': 'Personal Info',
'options.menu_ua': 'User Agents',
'options.menu_data': 'Data Management',
'options.menu_advanced': 'Advanced',
'options.ua_hint': 'Customize the User-Agent strings used by the switcher.',
'options.data_hint': 'Backup or restore your profiles and settings.',
'options.export_desc': 'Download a JSON file containing all your data.',
'options.import_desc': 'Upload a previously exported JSON file.',
'options.lang_label': 'Language / Ngôn ngữ',
'options.test_form_download': 'Download Demo Form',
'options.test_form_desc': 'Download the demo form to your computer to test auto-fill on a local file.',
'options.external_settings': 'Advanced Extension Settings',
'options.external_settings_desc': 'Manage site permissions, shortcuts, and management at browser level.',
'options.quick_start': 'Quick Start',
'options.step1': 'Navigate to any form you want to fill.',
'options.step2_pre': 'Click the ',
'options.step2_post': ' button on the toolbar to create a profile.',
'options.step3': 'Next time, just click the profile name to fill instantly!',
'options.step4_pre': 'Use ',
'options.step4_mid': 'Smart Fill',
'options.step4_post': ' to auto-complete common fields like Name, Email, Address.',
'options.saved': 'Saved!',
'options.reset_confirm': 'Reset all User-Agent strings to default?',
'options.reset_done': 'Reset to defaults!',
'options.dob_hint': '* Select from the calendar on the right or type manually (YYYY/MM/DD).',
// ── Privacy Policy (85 keys) ──
'privacy.menu_label': 'Privacy Policy',
'privacy.page_title': 'Privacy Policy',
'privacy.last_updated': 'Last Updated',
'privacy.last_updated_date': 'March 4, 2026',
'privacy.overview_title': 'Overview',
'privacy.overview_desc': 'TypeLess – Auto Form Filler is a browser extension that helps users save and auto-fill web forms. This privacy policy explains how we handle your data.',
'privacy.collection_title': 'Data Collection',
'privacy.collection_intro': 'Auto Form Filler collects and stores:',
'privacy.collection_item1': 'Form data you choose to save (names, addresses, text inputs, selections)',
'privacy.collection_item2': 'URLs of pages where profiles are created',
'privacy.collection_item3': 'User preferences (language selection, toolbar visibility)',
'privacy.storage_title': 'Data Storage',
'privacy.storage_item1': 'All data is stored locally on your device using the browser\'s built-in storage API (Chrome/Edge)',
'privacy.storage_item2': 'No cloud storage — data never leaves your browser',
'privacy.storage_item3': 'No external servers — we don\'t operate any servers',
'privacy.storage_item4': 'No transmission — your data is never sent anywhere',
'privacy.usage_title': 'Data Usage',
'privacy.usage_intro': 'Your saved data is used solely to:',
'privacy.usage_item1': 'Auto-fill web forms when you choose to apply a profile',
'privacy.usage_item2': 'Display saved profiles in the extension interface',
'privacy.usage_item3': 'Remember your preferences (language, toolbar state)',
'privacy.thirdparty_title': 'Third-Party Access',
'privacy.thirdparty_item1': 'No third-party sharing — your data is never shared with anyone',
'privacy.thirdparty_item2': 'No analytics — we don\'t track your usage',
'privacy.thirdparty_item3': 'No advertising — we don\'t collect data for ads',
'privacy.thirdparty_item4': 'No external APIs — we don\'t communicate with external services',
'privacy.control_title': 'User Control',
'privacy.control_intro': 'You have full control over your data:',
'privacy.control_item1': 'Delete profiles individually using the delete button in the toolbar',
'privacy.control_item2': 'Export profiles to backup your data as JSON',
'privacy.control_item3': 'Import profiles to restore from backup',
'privacy.control_item4': 'Clear all data by uninstalling the extension',
'privacy.permissions_title': 'Permissions Explained',
'privacy.permissions_intro': 'TypeLess requests the following permissions to provide its core functionality. Each permission is used only for its intended purpose and is essential for the extension to work properly.',
'privacy.perm_core_title': 'Core Permissions',
'privacy.perm_utility_title': 'Utility Permissions',
'privacy.perm_advanced_title': 'Advanced Permissions',
'privacy.perm_host_title': 'Host Permissions',
'privacy.perm_storage_purpose': 'Save your profiles and preferences locally on your device',
'privacy.perm_storage_privacy': '100% local storage using Chrome/Edge secure storage API. Data never leaves your device.',
'privacy.perm_activetab_purpose': 'Interact with form fields on the current webpage',
'privacy.perm_activetab_privacy': 'No background scanning, only works on explicit user action.',
'privacy.perm_scripting_purpose': 'Inject the floating toolbar and execute form-filling scripts',
'privacy.perm_scripting_privacy': 'No background scripts, only user-initiated actions.',
'privacy.perm_clipwrite_purpose': 'Copy profile data to clipboard for sharing between tabs/machines',
'privacy.perm_clipwrite_privacy': 'Only writes to clipboard when you explicitly request it.',
'privacy.perm_clipread_purpose': 'Paste saved profile data (JSON) from clipboard directly into TypeLess',
'privacy.perm_clipread_privacy': 'Clipboard read only on demand; no passive monitoring or transmission.',
'privacy.perm_contextmenu_purpose': 'Add TypeLess options to the browser right-click menu',
'privacy.perm_contextmenu_privacy': 'No data collection, only provides shortcuts to features.',
'privacy.perm_dnr_purpose': 'Modify request headers to support the User Agent Switching feature',
'privacy.perm_dnr_privacy': 'No network traffic monitored or redirected; only specific headers overwritten on demand.',
'privacy.perm_dnrhost_purpose': 'Allow declarativeNetRequest rules to apply across all URLs',
'privacy.perm_dnrhost_privacy': 'Only activates when user triggers UA switching; no background interception.',
'privacy.perm_downloads_purpose': 'Download files generated by the extension',
'privacy.perm_downloads_privacy': 'Only downloads files you explicitly request.',
'privacy.perm_webnav_purpose': 'Detect URL changes in Single-Page Apps (SPA) to automatically refresh the profile list',
'privacy.perm_webnav_privacy': 'Only listens to internal browser navigation events; no browsing history or page data is collected.',
'privacy.perm_allurls_purpose': 'Work on any website containing forms',
'privacy.perm_allurls_privacy': 'Extension only activates when you use it; no background scanning.',
'privacy.security_title': 'Security Guarantees',
'privacy.security_data_title': 'Data Protection',
'privacy.security_local': 'Local Only: All data stored exclusively on your device',
'privacy.security_notransmit': 'No Transmission: No data ever sent to external servers',
'privacy.security_noanalytics': 'No Analytics: No usage tracking or behavioral monitoring',
'privacy.security_nobg': 'No Background Activity: Extension only works when you interact with it',
'privacy.security_secure': 'Encrypted Storage: Profile data encrypted with AES-256-GCM before writing to storage',
'privacy.security_perm_title': 'Permission Minimization',
'privacy.security_essential': 'Essential Only: Each permission is required for core functionality',
'privacy.security_userctrl': 'User Controlled: All actions require explicit user initiation',
'privacy.security_transparent': 'Transparent: Clear explanations for why each permission is needed',
'privacy.security_nooverreach': 'No Overreach: No unnecessary or excessive permissions requested',
'privacy.security_ops_title': 'Operational Security',
'privacy.security_noremote': 'No Remote Code: All functionality runs locally in your browser',
'privacy.security_nothirdparty': 'No Third Parties: No integration with external services or APIs',
'privacy.security_noaccount': 'No Accounts: No registration or login required',
'privacy.security_offline': 'Offline Capable: Works completely without internet connection',
'privacy.security_trustedtypes': 'DOM XSS Protection: Trusted Types API policy prevents unsafe innerHTML injection',
'privacy.security_sensitive_warn': 'Sensitive Field Warning: Visual alert when saving pages with password / PIN / OTP fields',
'privacy.security_exclude_sensitive': 'Exclude Sensitive Fields: Option to skip saving password, PIN, and OTP fields entirely',
'privacy.children_title': "Children's Privacy",
'privacy.children_desc': 'This extension does not knowingly collect data from children under 13. It is intended for general use and treats all users the same way.',
'privacy.changes_title': 'Changes to Privacy Policy',
'privacy.changes_desc': 'We may update this privacy policy occasionally. Changes will be posted with an updated "Last Updated" date.',
'privacy.contact_title': 'Contact',
'privacy.contact_website': 'Website',
'privacy.contact_email': 'Email',
'privacy.contact_support': 'Extension Support',
'privacy.contact_support_desc': 'Through the support tab on Chrome Web Store or Microsoft Edge Add-ons',
'privacy.consent_title': 'Your Consent',
'privacy.consent_desc': 'By using TypeLess, you consent to this privacy policy.',
'privacy.created_by': 'Created by',
'privacy.version': 'Version',
'privacy.label_purpose': 'Purpose',
'privacy.label_privacy': 'Privacy',
'options.select_date': 'Select Date',
// ── Profile Manager (options page) ──────────────────────────────
'options.menu_profiles': 'Profiles',
'options.menu_privacy': 'Privacy Policy',
'options.profiles_hint': 'Manage, edit and customise your saved profiles and their fields.',
'options.profiles_search': 'Search profiles...',
'options.profiles_empty': 'No profiles yet. Save a form from the toolbar to get started.',
'options.profiles_no_match': 'No profiles match "{query}".',
'options.back_profiles': 'Back to Profiles',
'options.edit_profile_title': 'Edit: {name}',
'options.profile_saved': 'Profile saved!',
'options.delete_profile_confirm': 'Delete profile "{name}"? This cannot be undone.',
'options.no_fields': 'No fields saved. Add one below.',
'options.btn_edit': 'Edit',
'options.delete_all': 'Delete All',
'options.confirm_delete_all': 'Delete ALL profiles? This cannot be undone.',
'options.refresh_list': 'Refresh List',
'options.drag_to_reorder': 'Drag to reorder',
// Intro / landing page
'intro.title': 'TypeLess – Auto Form Filler',
'intro.desc': 'Save web form data as profiles and auto-fill them with one click. Supports Smart Fill for common fields (name, email, phone, address), full-page screenshot, User-Agent switching, and more. All data stored 100% locally — nothing ever leaves your device.',
'intro.available_on': 'Available on',
'intro.badge_local': '
100% Local',
'intro.badge_free': '
Free',
'intro.badge_noad': '
No Ads',
'intro.badge_multilang': '
6 Languages',
'store.chrome': 'Chrome Web Store',
'store.edge': 'Microsoft Edge',
'chip.toolbar': '
Toolbar',
'chip.focus_field': '
Focus Field',
'chip.ua': '
User-Agent',
'callout.save': 'Save',
'callout.smartfill': 'Smart Fill',
'callout.profilelist': 'Profile list',
'shortcut.enter_addfld': 'Enter (in Add Field)',
'error.common': 'Error: ',
'options.profile_delete_failed': 'Failed to delete profile.',
'options.profile_save_failed': 'Failed to save profile.',
// Guide image alt texts
'guide.alt_modal_focus_btn': 'Focus button in modal',
'guide.alt_modal_save_profile': 'Save profile modal',
'guide.alt_notify_applied': 'Applied notification',
'guide.alt_profile_editor': 'Profile editor',
'guide.alt_profiles_list': 'Profiles list',
'guide.alt_smartfill_result': 'Smart Fill result',
'guide.alt_toolbar_apply_profile': 'Toolbar apply profile',
'guide.alt_toolbar_overview': 'Toolbar overview',
'guide.alt_toolbar_save_btn': 'Toolbar save button',
'guide.alt_toolbar_smart_fill': 'Toolbar Smart Fill',
'options.add_field_label': '+ Add Field:',
'options.add_field_btn': 'Add',
'options.add_field_label_ph': 'Label',
'options.add_field_selector_ph': 'CSS selector or #id',
'options.add_field_value_ph': 'Value',
'options.focus_tip': 'Click the target icon to mark a field that gets focused after the profile is applied.',
'options.col_label': 'Label',
'options.col_value': 'Value',
'options.col_selector': 'Selector',
'options.col_type': 'Type',
'options.col_focus': 'Focus',
'options.col_delete': 'Delete',
// ── Save Profile Modal ──────────────────────────────────────────
'modal.focus_col': 'Focus',
'modal.focus_hint': 'After applying this profile, cursor will jump to this field',
'modal.focus_only_hint': 'Focus only — cursor jumps here after fill, but this field\'s value is not saved',
// ── Security / Privacy warnings (v1.0.5) ───────────────────────
'modal.security_warning_title': 'Security Warning',
'modal.security_warning_body': 'This page contains sensitive fields (password, PIN, OTP). Data is encrypted before saving, but consider carefully before saving login credentials.',
'modal.exclude_sensitive': 'Exclude sensitive fields (password, PIN, OTP)',
// ── Guide / Welcome page ────────────────────────────────────────
'guide.s1_title': '
Toolbar',
'guide.s1_desc': 'After installation, the TypeLess toolbar appears at the bottom of every web page. You can minimize, hide, or drag it to move.',
'guide.s1_caption': 'TypeLess toolbar displayed at the bottom of a page',
'guide.s1_save_desc': 'Save current form as a profile',
'guide.s1_smartfill_desc': 'Auto-fill from personal info',
'guide.s1_profilelist_desc': 'Click profile name to fill instantly',
'guide.s1_utils': 'Utilities',
'guide.s1_utils_desc': 'Screenshot, save HTML, switch UA',
'guide.s2_title': '
Save Profile',
'guide.s2_desc': 'Fill in the form as usual, then click the Save button on the toolbar. A dialog will appear letting you choose which fields to save.',
'guide.s2_step1_label': 'Step 2.1 — Click the Save button on the toolbar',
'guide.s2_step1_caption': 'Save button at the left of the toolbar',
'guide.s2_step2_label': 'Step 2.2 — Save Profile dialog',
'guide.s2_step2_desc': 'The dialog shows all fields found on the page. Check the fields you want to save, name the profile, then click Save Profile.',
'guide.s2_step2_caption': 'Save Profile dialog — select fields to save',
'guide.s2_tip': 'Fields without a value (empty) appear dimmed. Use Select All / Deselect to quickly toggle all.',
'guide.s2_focus_title': '
Choose Focus Field (optional)',
'guide.s2_focus_caption': '
column — click to select the field focused after apply',
'guide.s2_focus_desc': 'Click the
button at the end of a row to mark that field for auto-focus (cursor jumps there) right after applying the profile. Handy for continuing to type after auto-fill.',
'guide.s3_title': '
Apply Profile',
'guide.s3_desc': 'Next time you visit a page with a form, just click the profile name on the toolbar — all fields will be filled instantly.',
'guide.s3_caption': 'Click the profile name to auto-fill the form',
'guide.s3_tip': 'Use Ctrl+Shift+H to quickly show/hide the toolbar, and Ctrl+Shift+F (if set) to apply the last profile.',
'guide.s3_after_label': 'Notification after applying',
'guide.s3_after_caption': 'Confirmation notification that fill was successful',
'guide.s4_title': '
Smart Fill',
'guide.s4_desc': 'Smart Fill automatically detects and fills common fields (full name, email, phone, address…) based on the Personal Info you saved in the Personal Info tab.',
'guide.s4_col1_label': 'Click
on the toolbar',
'guide.s4_col1_caption': 'Smart Fill button on the toolbar',
'guide.s4_col2_label': 'Auto-fill result',
'guide.s4_col2_caption': 'Fields filled from personal info',
'guide.s4_tip': 'Set up your personal info in the Personal Info tab for Smart Fill to work more accurately. Includes: name, email, phone, date of birth, address.',
'guide.s5_title': '
Advanced Profile Management',
'guide.s5_desc': 'The Profiles tab in this settings page lets you edit each profile in detail: rename, change field values, fix CSS selectors, add/remove fields, reset the focus field.',
'guide.s5_col1_label': 'Profile list',
'guide.s5_col1_caption': 'Profile list — click Edit to modify',
'guide.s5_col2_label': 'Edit profile',
'guide.s5_col2_caption': 'Editor — edit fields, add/remove',
'guide.s6_title': '
Shortcuts & Tips',
'guide.s6_shortcut_key': 'Shortcut',
'guide.s6_shortcut_fn': 'Function',
'guide.s6_shortcut1': 'Show / hide toolbar',
'guide.s6_shortcut2': 'Apply last profile',
'guide.s6_shortcut3': 'Close Save Profile dialog',
'guide.s6_shortcut4': 'Quickly add a new field',
'guide.s6_tip1_title': 'Export / Import data',
'guide.s6_tip1_desc': 'Use the Data Management tab to back up and transfer profiles to another device.',
'guide.s6_tip2_title': 'Mobile simulation',
'guide.s6_tip2_desc': 'The User Agents tab lets you switch UA to view pages as on a phone.',
'guide.s6_tip3_title': 'Full-page screenshot',
'guide.s6_tip3_desc': 'The Screenshot button on the toolbar captures the entire page including scrolled content.',
'guide.s6_tip4_title': 'Unlock form',
'guide.s6_tip4_desc': 'The Unlock Right Click button removes readonly/disabled from locked input fields.',
},
},
// Current language
currentLang: 'en',
// Track loaded locales
loadedLocales: new Set(['en']),
// Load locale data dynamically
async loadLocale(lang) {
if (this.loadedLocales.has(lang)) return true;
try {
if (!_i18nExtAlive()) return false;
const url = chrome.runtime.getURL(`locales/${lang}.json`);
const response = await fetch(url);
if (!response.ok) throw new Error(`Failed to load locale: ${lang}`);
const data = await response.json();
this.translations[lang] = data;
this.loadedLocales.add(lang);
return true;
} catch (error) {
console.error(`Error loading locale ${lang}:`, error);
return false;
}
},
// Initialize i18n
async init() {
// Load saved language preference
const saved = await this.getLanguage();
if (saved) {
this.currentLang = saved;
} else {
// Auto-detect browser language
const browserLang = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
if (browserLang.startsWith('vi')) {
this.currentLang = 'vi';
} else if (browserLang.startsWith('zh-tw') || browserLang.startsWith('zh-hk')) {
this.currentLang = 'zh-TW';
} else if (browserLang.startsWith('zh')) {
this.currentLang = 'zh-CN';
} else if (browserLang.startsWith('ko')) {
this.currentLang = 'ko';
} else if (browserLang.startsWith('ja')) {
this.currentLang = 'ja';
} else {
this.currentLang = 'en';
}
}
// Load the language file if not English
if (this.currentLang !== 'en') {
await this.loadLocale(this.currentLang);
}
// Apply language class to body
if (typeof document !== 'undefined' && document.body) {
// Remove old lang-* class then add new one — never overwrite page's existing body classes
const _prevLang = Array.from(document.body.classList).find(c => c.startsWith('tl-lang-'));
if (_prevLang) document.body.classList.remove(_prevLang);
document.body.classList.add(`tl-lang-${this.currentLang}`);
}
return this.currentLang;
},
// Get translation
t(key, params = {}) {
const langData = this.translations[this.currentLang] || this.translations['en'] || {};
let text = langData[key] || key;
// Replace parameters
Object.keys(params).forEach(param => {
text = text.replace(`{${param}}`, params[param]);
});
// Resolve absolute paths for images if any
if (text.includes('icons/')) {
const getUrl = (path) => {
if (_i18nExtAlive()) {
try { return chrome.runtime.getURL(path); } catch (_) {}
}
return path;
};
text = text.replace(/icons\/([\w-]+)\.svg/g, (match) => getUrl(match));
text = text.replace('__MSG_@@extension_id__/', '');
}
return text;
},
// Switch language
async switchLanguage() {
const languages = ['en', 'vi', 'zh-CN', 'zh-TW', 'ko', 'ja'];
const currentIndex = languages.indexOf(this.currentLang);
const nextIndex = (currentIndex + 1) % languages.length;
const nextLang = languages[nextIndex];
// Ensure next locale is loaded
if (nextLang !== 'en') {
await this.loadLocale(nextLang);
}
this.currentLang = nextLang;
await this.saveLanguage(this.currentLang);
if (typeof document !== 'undefined' && document.body) {
// Remove old lang-* class then add new one — never overwrite page's existing body classes
const _prevLang = Array.from(document.body.classList).find(c => c.startsWith('tl-lang-'));
if (_prevLang) document.body.classList.remove(_prevLang);
document.body.classList.add(`tl-lang-${this.currentLang}`);
}
return this.currentLang;
},
// Set specific locale
async setLocale(lang) {
if (lang !== 'en') {
const success = await this.loadLocale(lang);
if (!success) return this.currentLang;
}
this.currentLang = lang;
await this.saveLanguage(lang);
if (typeof document !== 'undefined' && document.body) {
// Remove old lang-* class then add new one — never overwrite page's existing body classes
const _prevLang = Array.from(document.body.classList).find(c => c.startsWith('tl-lang-'));
if (_prevLang) document.body.classList.remove(_prevLang);
document.body.classList.add(`tl-lang-${this.currentLang}`);
}
return this.currentLang;
},
// Get URL for assets (handles both popup and content script contexts)
getAssetUrl(path) {
if (_i18nExtAlive()) {
try { return chrome.runtime.getURL(path); } catch (_) {}
}
return path;
},
// Apply alt text translations to all [data-i18n-alt] elements in the DOM
applyAltTexts(root) {
const scope = root || document;
scope.querySelectorAll('[data-i18n-alt]').forEach(el => {
const key = el.getAttribute('data-i18n-alt');
if (key) el.setAttribute('alt', this.t(key));
});
},
// Get current language
getCurrentLanguage() {
return this.currentLang;
},
// Save language preference
async saveLanguage(lang) {
try {
if (!_i18nExtAlive()) return false;
await chrome.storage.local.set({ language: lang });
return true;
} catch (error) {
console.error('Error saving language:', error);
return false;
}
},
// Get saved language
async getLanguage() {
try {
if (!_i18nExtAlive()) return undefined;
const result = await chrome.storage.local.get('language');
return result.language;
} catch (error) {
console.error('Error getting language:', error);
return undefined;
}
}
};
// Make available globally
if (typeof window !== 'undefined') {
window.i18n = i18n;
}
/**
* End of i18n.js
* ╔══╗─────────╔╗────╔═╗╔═╗╔╗───╔══╗╔═╗╔═╗╔═╦╗╔══╗──╔═╗╔═╗╔═╗
* ╚╗╔╝╔╦╗╔═╗╔═╗║║─╔═╗║═╣║═╣║╚╦╦╗╚╗╔╝║╬║║║║║║║║║╔═╣──║╬║║╬║║║║
* ─║║─║║║║╬║║╩╣║╚╗║╩╣╠═║╠═║║╬║║║─║║─║╗╣║║║║║║║║╚╗║╔╗║╔╝║╗╣║║║
* ─╚╝─╠╗║║╔╝╚═╝╚═╝╚═╝╚═╝╚═╝╚═╬╗║─╚╝─╚╩╝╚═╝╚╩═╝╚══╝╚╝╚╝─╚╩╝╚═╝
* ────╚═╝╚╝──────────────────╚═╝
*/