From c042ac7801f1288491750d383d9413c97e32bef8 Mon Sep 17 00:00:00 2001 From: GabenGar <87906913+GabenGar@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:14:32 +0500 Subject: [PATCH] feat(extension): multilang support (#89) * feat: validate locales script * feat: popup translation * feat: options translation --- apps/extension/.vscode/settings.json | 4 + apps/extension/package.json | 4 +- apps/extension/scripts/validate.mjs | 73 ++ apps/extension/src/_locales.schema.json | 43 + apps/extension/src/_locales/en/messages.json | 856 ++---------------- apps/extension/src/_locales/ru/messages.json | 854 ++--------------- apps/extension/src/lib/localization/index.ts | 1 + apps/extension/src/lib/localization/lib.ts | 13 + .../lib/localization/predefined-messages.ts | 46 + apps/extension/src/manifest-chrome.json | 2 +- apps/extension/src/manifest-firefox.json | 6 +- .../src/manifest-firefox.schema.json | 4 +- .../src/options/components/layouts/layout.tsx | 7 +- apps/extension/src/options/pages/home.tsx | 7 +- apps/extension/src/options/routes.tsx | 19 +- .../src/popup/components/forms/form.tsx | 26 +- .../src/popup/components/layouts/header.tsx | 7 +- .../src/popup/components/layouts/layout.tsx | 3 +- apps/extension/src/popup/pages/home/page.tsx | 15 +- .../extension/src/popup/pages/home/viewer.tsx | 131 +-- apps/extension/src/popup/routes.tsx | 19 +- package.json | 3 +- turbo.json | 3 +- 23 files changed, 492 insertions(+), 1654 deletions(-) create mode 100644 apps/extension/scripts/validate.mjs create mode 100644 apps/extension/src/_locales.schema.json create mode 100644 apps/extension/src/lib/localization/index.ts create mode 100644 apps/extension/src/lib/localization/lib.ts create mode 100644 apps/extension/src/lib/localization/predefined-messages.ts diff --git a/apps/extension/.vscode/settings.json b/apps/extension/.vscode/settings.json index b317e1b7..59b983fe 100644 --- a/apps/extension/.vscode/settings.json +++ b/apps/extension/.vscode/settings.json @@ -11,6 +11,10 @@ { "fileMatch": ["src/manifest-chrome.json"], "url": "./src/manifest-chrome.schema.json" + }, + { + "fileMatch": ["src/_locales/*/messages.json"], + "url": "./src/_locales.schema.json" } ] } diff --git a/apps/extension/package.json b/apps/extension/package.json index 4ba8178d..cfe48111 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -17,11 +17,13 @@ "build": "webpack --config webpack.prod.js && web-ext build --no-config-discovery --config=web-ext-config.mjs", "dev": "node scripts/dev.mjs", "lint": "web-ext lint --output=json --pretty", + "validate": "node scripts/validate.mjs", "open-analyzer": "opener http://127.0.0.1:8888/" }, "imports": { "#popup/*": "./src/popup/*/index.ts", - "#options/*": "./src/options/*/index.ts" + "#options/*": "./src/options/*/index.ts", + "#*": "./src/*/index.ts" }, "repository": { "type": "git", diff --git a/apps/extension/scripts/validate.mjs b/apps/extension/scripts/validate.mjs new file mode 100644 index 00000000..c6923e89 --- /dev/null +++ b/apps/extension/scripts/validate.mjs @@ -0,0 +1,73 @@ +// @ts-check + +import { readdir, readFile } from "node:fs/promises"; +import path from "node:path"; +import { cwd } from "node:process"; + +const localesPath = path.join(cwd(), "src", "_locales"); + +validate(); + +async function validate() { + /** + * @type {(import("node:fs").Dirent)[]} + */ + let localesDir; + + try { + localesDir = await readdir(localesPath, { + encoding: "utf8", + recursive: true, + withFileTypes: true, + }); + } catch (error) { + // @ts-expect-error + throw new Error(`Failed to read locales folder "${localesPath}".`, { + cause: error, + }); + } + + /** + * @type {Map>} + */ + const messageKeys = new Map(); + const localeFiles = localesDir.filter((dirEntry) => { + return dirEntry.isFile(); + }); + + for await (const dirEntry of localeFiles) { + const locale = dirEntry.path; + + /** + * @type {string} + */ + let contents; + + try { + const entryPath = path.join(dirEntry.path, dirEntry.name); + contents = await readFile(entryPath, { encoding: "utf8" }); + } catch (error) { + // @ts-expect-error + throw new Error(`Failed to read locale file "${dirEntry.path}".`, { + cause: error, + }); + } + + /** + * @type {Record} + */ + const messageData = JSON.parse(contents); + + const messages = Object.keys(messageData); + + messageKeys.set(locale, new Set(messages)); + } + + const counts = new Set( + [...messageKeys.values()].map((messages) => messages.size) + ); + + if (counts.size !== 1) { + throw new Error("Not all languages are equally supported."); + } +} diff --git a/apps/extension/src/_locales.schema.json b/apps/extension/src/_locales.schema.json new file mode 100644 index 00000000..6975fd8f --- /dev/null +++ b/apps/extension/src/_locales.schema.json @@ -0,0 +1,43 @@ +{ + "$id": "locale-messages", + "title": "LocaleMessages", + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["message"], + "properties": { + "message": { "type": "string" }, + "description": { "type": "string" }, + "placeholders": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["content"], + "properties": { + "content": { + "type": "string" + }, + "example": { + "type": "string" + } + } + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["message"], + "properties": { + "message": { "type": "string" }, + "description": { "type": "string" } + } + } + ] + } +} diff --git a/apps/extension/src/_locales/en/messages.json b/apps/extension/src/_locales/en/messages.json index c4f76220..48aa6a8e 100644 --- a/apps/extension/src/_locales/en/messages.json +++ b/apps/extension/src/_locales/en/messages.json @@ -1,818 +1,120 @@ { - "extName": { + "extension_title": { "message": "Link Overwatch" }, - "extDescription": { + "extension_description": { "message": "Watch over your links." }, - "donateWithPaypalLabel": { - "message": "Donate with PayPal" + "Status": { + "message": "Status" }, - "donateLabel": { - "message": "Donate" + "Message": { + "message": "Message" }, - "enableOnThisPage": { - "message": "Enable translation on this page" + "Data": { "message": "Data" }, + "Name": { + "message": "Name" }, - "disableOnThisPage": { - "message": "Disable translation on this page" + "Unknown Error": { + "message": "Unknown Error" }, - "initialTextArea": { - "message": "Enter text" + "URL parser": { + "message": "URL parser" }, - "showLink": { - "message": "Translate this page" + "URL": { + "message": "URL" }, - "openInGoogleLabel": { - "message": "Open in Google Translate" + "Analyze": { + "message": "Analyze" }, - "openInDeeplLabel": { - "message": "Open in DeepL Translator" + "No URL is selected.": { + "message": "No URL is selected." }, - "copyLabel": { - "message": "Copy" + "Unknown method $METHOD$": { + "message": "Unknown method \"$METHOD$\".", + "placeholders": { + "method": { + "content": "$1", + "example": "POST" + } + } }, - "copiedLabel": { - "message": "Copied." + "URL is required.": { + "message": "URL is required." }, - "listenLabel": { - "message": "Listen" + "Options": { + "message": "Options" }, - "targetLangLabel": { - "message": "Target language" + "There are no options currently.": { + "message": "There are no options currently." }, - "allLangLabel": { - "message": "All languages" + "Source code": { + "message": "Source code" }, - "recentLangLabel": { - "message": "Recent languages" + "Initializing...": { + "message": "Initializing..." }, - "settingsLabel": { - "message": "Settings" + "Submitting...": { + "message": "Submitting..." }, - "generalLabel": { - "message": "General" + "Ready to submit.": { + "message": "Ready to submit." }, - "translationApiLabel": { - "message": "Translation engine" + "Submit": { + "message": "Submit" }, - "googleApiLabel": { - "message": "Google translate API" + "URLs": { + "message": "URLs" }, - "googleApiCaptionLabel": { - "message": "Use Google Translate API. No registration is required." + "Full URL": { + "message": "Full URL" }, - "deeplApiLabel": { - "message": "DeepL API" + "Decoded URL": { + "message": "Decoded URL" }, - "deeplApiCaptionLabel": { - "message": "Use DeepL API. You must register with DeepL API Free or DeepL API Pro to obtain an authentication key." + "Origin Details":{ + "message": "Origin Details" }, - "howToUseDeeplLabel": { - "message": "How to register DeepL API" + "Origin":{ + "message": "Origin" }, - "deeplPlanLabel": { - "message": "DeepL API plan" + "Protocol":{ + "message": "Protocol" }, - "deeplPlanCaptionLabel": { - "message": "Select the DeepL API plan for which you registered." + "Username": { + "message": "Username" }, - "deeplFreeLabel": { - "message": "DeepL API Free" + "Password":{ + "message": "Password" }, - "deeplProLabel": { - "message": "DeepL API Pro" + "Host":{ + "message": "Host" }, - "deeplAuthKeyLabel": { - "message": "Authentication key" + "Hostname":{ + "message": "Hostname" }, - "deeplAuthKeyCaptionLabel": { - "message": "Enter the authentication key for the DeepL API." + "Port":{ + "message": "Port" }, - "targetLangCaptionLabel": { - "message": "Select the default target language." + "Pathname Details":{ + "message": "Pathname Details" }, - "webPageLabel": { - "message": "Web page" + "Pathname":{ + "message": "Pathname" }, - "ifShowCandidateLabel": { - "message": "Show translation candidates" + "Search Parameters Details":{ + "message": "Search Parameters Details" }, - "ifShowCandidateCaptionLabel": { - "message": "Show multiple translation candidates when a single word is translated." + "Search":{ + "message": "Search" }, - "whenSelectTextLabel": { - "message": "Behavior when selecting text" + "Search parameters":{ + "message": "Search parameters" }, - "ifAutoTranslateLabel": { - "message": "Display translation panel" + "Fragment Details":{ + "message": "Fragment Details" }, - "ifAutoTranslateCaptionLabel": { - "message": "Directly display the translation panel without displaying the button." - }, - "ifShowButtonLabel": { - "message": "Display translation button" - }, - "ifShowButtonCaptionLabel": { - "message": "Display the translation button to open the panel when clicked." - }, - "dontshowbuttonlabel": { - "message": "Don't display button or panel" - }, - "dontshowbuttonCaptionlabel": { - "message": "Don't display the translation button or the translation panel." - }, - "ifCheckLangLabel": { - "message": "Do not display the button if translation is not required" - }, - "ifCheckLangCaptionLabel": { - "message": "Detects the language of the selected text, and if it is the same as the target language, the button is not displayed." - }, - "ifChangeSecondLangOnPageCaptionLabel": { - "message": "Detects the language of the selected text, and if it is the same as the default target language, translate it into the second language." - }, - "disableTranslationLabel": { - "message": "Disable translation" - }, - "isDisabledInTextFieldsLabel": { - "message": "Disable translation in text fields" - }, - "isDisabledInTextFieldsCaptionLabel": { - "message": "Don't display translation button or panel when selecting text in a text field." - }, - "isDisabledInCodeElementLabel": { - "message": "Disable translation in code tags" - }, - "isDisabledInCodeElementCaptionLabel": { - "message": "Don't display translation button or panel when selecting text in code tag." - }, - "ifOnlyTranslateWhenModifierKeyPressedLabel": { - "message": "Translate on modifier key pressed" - }, - "ifOnlyTranslateWhenModifierKeyPressedCaptionLabel": { - "message": "Only display translation on specified modifier key pressed." - }, - "modifierKeyLabel": { - "message": "Modifier Key" - }, - "shiftLabel": { - "message": "Shift" - }, - "ctrlLabel": { - "message": "Ctrl" - }, - "altLabel": { - "message": "Alt" - }, - "cmdLabel": { - "message": "Command" - }, - "disableUrlListLabel": { - "message": "URL list to disable translation" - }, - "disableUrlListCaptionLabel": { - "message": "If the page URL matches the list, translation on the web page is disabled. The list allows \"*\" wildcards." - }, - "toolbarLabel": { - "message": "Toolbar popup" - }, - "ifChangeSecondLangLabel": { - "message": "Automatically switch to the second language" - }, - "ifChangeSecondLangCaptionLabel": { - "message": "Detects the language of the input text, and if it is the same as the default target language, translate it into the second language." - }, - "secondTargetLangLabel": { - "message": "Second language" - }, - "secondTargetLangCaptionLabel": { - "message": "Select the second target language." - }, - "ignoredDocumentLangLabel": { - "message": "Disable translation by lang attribute" - }, - "ignoredDocumentLangCaptionLabel": { - "message": "Disable translation if the lang attribute in the html element of the page matches the list. Enter comma-separated language tags." - }, - "waitTimeLabel": { - "message": "Waiting time to translate" - }, - "waitTimeCaptionLabel": { - "message": "Specify the waiting time from the input of a character to the start of translation. (millisecond)" - }, - "waitTime2CaptionLabel": { - "message": "If you translate it many times in a short time, it may become unusable for a while." - }, - "menuLabel": { - "message": "Context menu" - }, - "ifShowMenuLabel": { - "message": "Display the context menu" - }, - "ifShowMenuCaptionLabel": { - "message": "Add items to the context menu displayed when right clicking on the web page or the tab." - }, - "pageTranslationLabel": { - "message": "Page translation" - }, - "pageTranslationOpenToLabel": { - "message": "Tab to display translation results" - }, - "pageTranslationOpenToCaptionLabel": { - "message": "Specify the tab to display the results of the page translation." - }, - "newTabLabel": { - "message": "New tab" - }, - "currentTabLabel": { - "message": "Current tab" - }, - "styleLabel": { - "message": "Style" - }, - "themeLabel": { - "message": "Theme" - }, - "themeCaptionLabel": { - "message": "Specify the color scheme." - }, - "lightLabel": { - "message": "Light" - }, - "darkLabel": { - "message": "Dark" - }, - "systemLabel": { - "message": "System" - }, - "buttonStyleLabel": { - "message": "Translation button" - }, - "buttonStyleCaptionLabel": { - "message": "Specify the style of the translation button displayed on the web page." - }, - "buttonSizeLabel": { - "message": "Size" - }, - "buttonPositionLabel": { - "message": "Display position" - }, - "topRightLabel": { - "message": "Top right" - }, - "bottomRightLabel": { - "message": "Bottom right" - }, - "topLeftLabel": { - "message": "Top left" - }, - "bottomLeftLabel": { - "message": "Bottom left" - }, - "topLabel": { - "message": "Top" - }, - "bottomLabel": { - "message": "Bottom" - }, - "leftLabel": { - "message": "Left" - }, - "rightLabel": { - "message": "Right" - }, - "positionOffsetLabel": { - "message": "Display position - Offset" - }, - "panelStyleLabel": { - "message": "Translation panel" - }, - "panelStyleCaptionLabel": { - "message": "Specify the style of the translation panel displayed on the web page." - }, - "widthLabel": { - "message": "Width" - }, - "heightLabel": { - "message": "Height" - }, - "fontSizeLabel": { - "message": "Font size" - }, - "referencePointLabel": { - "message": "Display position - Reference point" - }, - "bottomSelectedTextLabel": { - "message": "Bottom of selected text" - }, - "topSelectedTextLabel": { - "message": "Top of selected text" - }, - "clickedPointLabel": { - "message": "Clicked point" - }, - "displayDirectionLabel": { - "message": "Display position - Direction" - }, - "resultFontColorLabel": { - "message": "Font color of translation result" - }, - "isOverrideColorsLabel": { - "message": "Override default colors with colors below" - }, - "candidateFontColorLabel": { - "message": "Font color of translation candidates" - }, - "bgColorLabel": { - "message": "Background color" - }, - "otherLabel": { - "message": "Other" - }, - "isShowOptionsPageWhenUpdatedLabel": { - "message": "Display option page when updating" - }, - "isShowOptionsPageWhenUpdatedCaptionLabel": { - "message": "Display the options page when Simple Translate is updated. You can know the update contents quickly." - }, - "isDebugModeLabel": { - "message": "Enable debug mode" - }, - "isDebugModeCaptionLabel": { - "message": "When debug mode is enabled, the log is output to the debugger." - }, - "exportSettingsLabel": { - "message": "Export settings" - }, - "exportSettingsCaptionLabel": { - "message": "Save the settings as a json file on the computer." - }, - "exportButtonLabel": { - "message": "Export" - }, - "importSettingsLabel": { - "message": "Import Settings" - }, - "importSettingsCaptionLabel": { - "message": "Load the settings file saved on the computer." - }, - "importButtonLabel": { - "message": "Import" - }, - "resetSettingsLabel": { - "message": "Reset settings" - }, - "resetSettingsCaptionLabel": { - "message": "Restore all settings to default." - }, - "resetSettingsButtonLabel": { - "message": "Reset" - }, - "shortcutsLabel": { - "message": "Shortcuts" - }, - "keyboardShortcutsLabel": { - "message": "Keyboard shortcuts" - }, - "setKeyboardShortCutsMessage": { - "message": "Set keyboard shortcuts." - }, - "typeShortcutMessage": { - "message": "Type a shortcut" - }, - "typeLetterMessage": { - "message": "Type a letter" - }, - "includeModifierKeysMessage": { - "message": "Include either Ctrl or Alt" - }, - "includeMacModifierKeysMessage": { - "message": "Include either Command, Ctrl or Alt" - }, - "invalidLetterMessage": { - "message": "The letter can not be used" - }, - "invalidShortcutMessage": { - "message": "The shortcut can not be used" - }, - "clear": { - "message": "Clear" - }, - "reset": { - "message": "Reset" - }, - "informationLabel": { - "message": "Information" - }, - "additionalPermissionLabel": { - "message": "Enable translation on web pages" - }, - "additionalPermissionCaptionLabel": { - "message": "Additional permissions are required to use Simple Translate on web pages." - }, - "enableLabel": { - "message": "Enable" - }, - "backersLabel": { - "message": "Backers" - }, - "licenseLabel": { - "message": "License" - }, - "donationLabel": { - "message": "Please make a donation" - }, - "donationCaptionLabel": { - "message": "Thank you for using Simple Translate.
Your support will be a big encouragement, as I continue to develop the add-on.
If you like Simple Translate, I would be pleased if you could consider donating." - }, - "amazonTitleLabel": { - "message": "amazon.co.jp eGift Cards" - }, - "sponsorsLabel": { - "message": "Sponsors" - }, - "addonPageLabel": { - "message": "Add-on page" - }, - "extensionPageLabel": { - "message": "Extension page" - }, - "privacyPolicyLabel": { - "message": "Privacy policy" - }, - "amazonUrl": { - "message": "https://www.amazon.co.jp/dp/B004N3APGO?language=en_US" - }, - "addonUrl": { - "message": "https://addons.mozilla.org/en-US/firefox/addon/simple-translate/?src=optionpage" - }, - "networkError": { - "message": "Error: Check network connection status." - }, - "unavailableError": { - "message": "Error: Service usage limit reached. Please wait a while and try again." - }, - "deeplAuthError": { - "message": "Error: Authentication of DeepL API failed. Please set the authentication key and plan correctly on the settings page." - }, - "unknownError": { - "message": "Error: Unknown error" - }, - "translatePageMenu": { - "message": "Translate this page" - }, - "translateTextMenu": { - "message": "Translate selected text" - }, - "translateLinkMenu": { - "message": "Translate selected link" - }, - "openPopupDescription": { - "message": "Open toolbar popup" - }, - "lang_af": { - "message": "Afrikaans" - }, - "lang_sq": { - "message": "Albanian" - }, - "lang_am": { - "message": "Amharic" - }, - "lang_ar": { - "message": "Arabic" - }, - "lang_hy": { - "message": "Armenian" - }, - "lang_az": { - "message": "Azerbaijani" - }, - "lang_eu": { - "message": "Basque" - }, - "lang_be": { - "message": "Belarusian" - }, - "lang_bn": { - "message": "Bengali" - }, - "lang_bs": { - "message": "Bosnian" - }, - "lang_bg": { - "message": "Bulgarian" - }, - "lang_ca": { - "message": "Catalan" - }, - "lang_ceb": { - "message": "Cebuano" - }, - "lang_ny": { - "message": "Chewa" - }, - "lang_zh_CN": { - "message": "Chinese (Simplified)" - }, - "lang_zh_TW": { - "message": "Chinese (Traditional)" - }, - "lang_co": { - "message": "Corsican" - }, - "lang_hr": { - "message": "Croatian" - }, - "lang_cs": { - "message": "Czech" - }, - "lang_da": { - "message": "Danish" - }, - "lang_nl": { - "message": "Dutch" - }, - "lang_en": { - "message": "English" - }, - "lang_eo": { - "message": "Esperanto" - }, - "lang_et": { - "message": "Estonian" - }, - "lang_fi": { - "message": "Finnish" - }, - "lang_fr": { - "message": "French" - }, - "lang_fy": { - "message": "Frisian" - }, - "lang_gl": { - "message": "Galician" - }, - "lang_ka": { - "message": "Georgian" - }, - "lang_de": { - "message": "German" - }, - "lang_el": { - "message": "Greek" - }, - "lang_gu": { - "message": "Gujarati" - }, - "lang_ht": { - "message": "Haitian" - }, - "lang_ha": { - "message": "Hausa" - }, - "lang_haw": { - "message": "Hawaiian" - }, - "lang_he": { - "message": "Hebrew" - }, - "lang_hi": { - "message": "Hindi" - }, - "lang_hu": { - "message": "Hungarian" - }, - "lang_is": { - "message": "Icelandic" - }, - "lang_ig": { - "message": "Igbo" - }, - "lang_id": { - "message": "Indonesian" - }, - "lang_ga": { - "message": "Irish" - }, - "lang_it": { - "message": "Italian" - }, - "lang_ja": { - "message": "Japanese" - }, - "lang_jv": { - "message": "Javanese" - }, - "lang_kn": { - "message": "Kannada" - }, - "lang_kk": { - "message": "Kazakh" - }, - "lang_km": { - "message": "Khmer" - }, - "lang_rw": { - "message": "Kinyarwanda" - }, - "lang_ky": { - "message": "Kirghiz" - }, - "lang_ko": { - "message": "Korean" - }, - "lang_ku": { - "message": "Kurdish" - }, - "lang_lo": { - "message": "Laotian" - }, - "lang_la": { - "message": "Latin" - }, - "lang_lv": { - "message": "Latvian" - }, - "lang_lt": { - "message": "Lithuanian" - }, - "lang_lb": { - "message": "Luxembourgish" - }, - "lang_mk": { - "message": "Macedonian" - }, - "lang_mg": { - "message": "Malagasy" - }, - "lang_ms": { - "message": "Malay" - }, - "lang_ml": { - "message": "Malayalam" - }, - "lang_mt": { - "message": "Maltese" - }, - "lang_mi": { - "message": "Maori" - }, - "lang_mr": { - "message": "Marathi" - }, - "lang_mn": { - "message": "Mongolian" - }, - "lang_hmn": { - "message": "Monk" - }, - "lang_my": { - "message": "Myanmar" - }, - "lang_ne": { - "message": "Nepali" - }, - "lang_no": { - "message": "Norwegian" - }, - "lang_nb": { - "message": "Norwegian (bokmål)" - }, - "lang_or": { - "message": "Odia (Oriya)" - }, - "lang_fa": { - "message": "Persian" - }, - "lang_pl": { - "message": "Polish" - }, - "lang_pt": { - "message": "Portuguese" - }, - "lang_pa": { - "message": "Punjabi" - }, - "lang_ps": { - "message": "Pushto" - }, - "lang_ro": { - "message": "Romanian" - }, - "lang_ru": { - "message": "Russian" - }, - "lang_sm": { - "message": "Samoan" - }, - "lang_gd": { - "message": "Scottish Gaelic" - }, - "lang_sr": { - "message": "Serbian" - }, - "lang_st": { - "message": "Sesotho" - }, - "lang_sn": { - "message": "Shona" - }, - "lang_sd": { - "message": "Sindhi" - }, - "lang_si": { - "message": "Sinhala" - }, - "lang_sk": { - "message": "Slovak" - }, - "lang_sl": { - "message": "Slovenian" - }, - "lang_so": { - "message": "Somali" - }, - "lang_es": { - "message": "Spanish" - }, - "lang_su": { - "message": "Sundanese" - }, - "lang_sw": { - "message": "Swahili" - }, - "lang_sv": { - "message": "Swedish" - }, - "lang_tl": { - "message": "Tagalog" - }, - "lang_tg": { - "message": "Tajiki" - }, - "lang_ta": { - "message": "Tamil" - }, - "lang_tt": { - "message": "Tatar" - }, - "lang_te": { - "message": "Telugu" - }, - "lang_th": { - "message": "Thai" - }, - "lang_tr": { - "message": "Turkish" - }, - "lang_tk": { - "message": "Turkmen" - }, - "lang_uk": { - "message": "Ukrainian" - }, - "lang_ur": { - "message": "Urdu" - }, - "lang_ug": { - "message": "Uyghur" - }, - "lang_uz": { - "message": "Uzbek" - }, - "lang_vi": { - "message": "Vietnamese" - }, - "lang_cy": { - "message": "Welsh" - }, - "lang_xh": { - "message": "Xosa" - }, - "lang_yi": { - "message": "Yiddish" - }, - "lang_yo": { - "message": "Yoruba" - }, - "lang_zu": { - "message": "Zulu" - }, - "lang_en_US": { - "message": "English (American)" - }, - "lang_en_GB": { - "message": "English (British)" - }, - "lang_pt_PT": { - "message": "Portuguese" - }, - "lang_pt_BR": { - "message": "Portuguese (Brazilian)" - }, - "lang_zh": { - "message": "Chinese" + "Hash":{ + "message": "Hash" } } diff --git a/apps/extension/src/_locales/ru/messages.json b/apps/extension/src/_locales/ru/messages.json index e2130b77..b6116674 100644 --- a/apps/extension/src/_locales/ru/messages.json +++ b/apps/extension/src/_locales/ru/messages.json @@ -1,818 +1,120 @@ { - "extName": { + "extension_title": { "message": "Ссылконадзиратель" }, - "extDescription": { + "extension_description": { "message": "Надзирайте над вашими ссылками." }, - "donateWithPaypalLabel": { - "message": "Пожертвование через PayPal" + "Status": { + "message": "Статус" }, - "donateLabel": { - "message": "Пожертвовать" + "Message": { + "message": "Сообщение" }, - "enableOnThisPage": { - "message": "Включить перевод на этой странице" + "Data": { "message": "Данные" }, + "Name": { + "message": "Название" }, - "disableOnThisPage": { - "message": "Отключить перевод на этой странице" + "Unknown Error": { + "message": "Неизвестная Ошибка" }, - "initialTextArea": { - "message": "Введите текст" + "URL parser": { + "message": "Просмотрщик Ссылок" }, - "showLink": { - "message": "Перевести страницу" + "URL": { + "message": "Ссылка" }, - "openInGoogleLabel": { - "message": "Открыть в Переводчике Google" + "Analyze": { + "message": "Анализировать" }, - "openInDeeplLabel": { - "message": "Открыть в DeepL Translator" + "No URL is selected.": { + "message": "Ссылка не выбрана." }, - "copyLabel": { - "message": "Копировать" + "Unknown method $METHOD$": { + "message": "Неизвестный метод \"$METHOD$\".", + "placeholders": { + "method": { + "content": "$1", + "example": "POST" + } + } }, - "copiedLabel": { - "message": "Скопировано." + "URL is required.": { + "message": "Ссылка обязательна." }, - "listenLabel": { - "message": "Слушать" - }, - "targetLangLabel": { - "message": "Язык перевода" - }, - "allLangLabel": { - "message": "Все языки" - }, - "recentLangLabel": { - "message": "Недавние языки" - }, - "settingsLabel": { + "Options": { "message": "Настройки" }, - "generalLabel": { - "message": "Общие" - }, - "translationApiLabel": { - "message": "Система перевода" - }, - "googleApiLabel": { - "message": "Google Переводчик API" - }, - "googleApiCaptionLabel": { - "message": "Использовать Google Translate API. Регистрация не требуется." - }, - "deeplApiLabel": { - "message": "DeepL API" - }, - "deeplApiCaptionLabel": { - "message": "Использовать DeepL API. Для получения ключа аутентификации необходимо зарегистрироваться в DeepL API Free или DeepL API Pro." - }, - "howToUseDeeplLabel": { - "message": "Как зарегистрироваться в DeepL API" - }, - "deeplPlanLabel": { - "message": "Тарифный план DeepL API" - }, - "deeplPlanCaptionLabel": { - "message": "Выберите тарифный план DeepL API, на который вы зарегистрированы." - }, - "deeplFreeLabel": { - "message": "DeepL API Free" - }, - "deeplProLabel": { - "message": "DeepL API Pro" - }, - "deeplAuthKeyLabel": { - "message": "Ключ аутентификации" - }, - "deeplAuthKeyCaptionLabel": { - "message": "Введите ключ аутентификации для DeepL API." - }, - "targetLangCaptionLabel": { - "message": "Выберите язык перевода по умолчанию." - }, - "webPageLabel": { - "message": "Веб-страница" - }, - "ifShowCandidateLabel": { - "message": "Показывать варианты перевода" - }, - "ifShowCandidateCaptionLabel": { - "message": "Показывать несколько вариантов перевода одиночного слова." - }, - "whenSelectTextLabel": { - "message": "Поведение при выделении текста" - }, - "ifAutoTranslateLabel": { - "message": "Показать панель с переводом" - }, - "ifAutoTranslateCaptionLabel": { - "message": "Сразу показать панель с переводом, не показывая кнопку." - }, - "ifShowButtonLabel": { - "message": "Показать кнопку перевода" - }, - "ifShowButtonCaptionLabel": { - "message": "Показать кнопку для открытия панели с переводом." - }, - "dontshowbuttonlabel": { - "message": "Не отображать кнопку или панель" - }, - "dontshowbuttonCaptionlabel": { - "message": "Не показывать ни кнопку перевода, ни панель с переводом." - }, - "ifCheckLangLabel": { - "message": "Не показывать кнопку, если перевод не требуется" - }, - "ifCheckLangCaptionLabel": { - "message": "Если язык выделенного текста совпадает с языком перевода, то кнопка не показывается." - }, - "ifChangeSecondLangOnPageCaptionLabel": { - "message": "Переключаться на дополнительный язык перевода, если язык выделенного текста совпадает с языком перевода по умолчанию." - }, - "disableTranslationLabel": { - "message": "Отключить перевод" - }, - "isDisabledInTextFieldsLabel": { - "message": "Отключить перевод в текстовых полях" - }, - "isDisabledInTextFieldsCaptionLabel": { - "message": "Не показывать ни кнопку перевода, ни панель с переводом при выделении текста в текстовом поле." - }, - "isDisabledInCodeElementLabel": { - "message": "Отключить перевод в коде" - }, - "isDisabledInCodeElementCaptionLabel": { - "message": "Не показывать кнопку перевода или панель при выделении текста в коде." - }, - "ifOnlyTranslateWhenModifierKeyPressedLabel": { - "message": "Переводить при нажатии клавиши модификатора" - }, - "ifOnlyTranslateWhenModifierKeyPressedCaptionLabel": { - "message": "Переводить только c модификатором" - }, - "modifierKeyLabel": { - "message": "Клавиша модификатор" - }, - "shiftLabel": { - "message": "Shift" - }, - "ctrlLabel": { - "message": "Ctrl" - }, - "altLabel": { - "message": "Alt" - }, - "cmdLabel": { - "message": "Command" - }, - "disableUrlListLabel": { - "message": "Список URL для которых перевод будет отключен" - }, - "disableUrlListCaptionLabel": { - "message": "Если URL страницы внесен в список, то перевод будет отключен. Список позволяет использовать подстановку \"*\"." - }, - "toolbarLabel": { - "message": "Всплывающее окно на панели инструментов" - }, - "ifChangeSecondLangLabel": { - "message": "Автоматически переключаться на дополнительный язык перевода" - }, - "ifChangeSecondLangCaptionLabel": { - "message": "Переключаться на дополнительный язык перевода в окне на панели инструментов, если язык исходного текста совпадает с языком перевода по умолчанию." - }, - "secondTargetLangLabel": { - "message": "Дополнительный язык перевода" - }, - "secondTargetLangCaptionLabel": { - "message": "Выберите дополнительный язык перевода." - }, - "ignoredDocumentLangLabel": { - "message": "Отключить перевод по аттрибуту lang" - }, - "ignoredDocumentLangCaptionLabel": { - "message": "Отключить перевод если язык страницы совпадает с языком в списке. Введите список языков через запятую." - }, - "waitTimeLabel": { - "message": "Задержка перед переводом" - }, - "waitTimeCaptionLabel": { - "message": "Настройте задержку между вводом символа и началом процесса перевода (миллисекунды)." - }, - "waitTime2CaptionLabel": { - "message": "Если запускать перевод слишком часто, сервис может стать временно недоступным." - }, - "menuLabel": { - "message": "Контекстное меню" - }, - "ifShowMenuLabel": { - "message": "Добавить пункты в контекстное меню" - }, - "ifShowMenuCaptionLabel": { - "message": "Добавить команды перевода в контекстное меню веб-страницы и контекстное меню вкладки." - }, - "pageTranslationLabel": { - "message": "Перевод страницы" - }, - "pageTranslationOpenToLabel": { - "message": "Вкладка для отображения результатов перевода" - }, - "pageTranslationOpenToCaptionLabel": { - "message": "Укажите вкладку для отображения результатов перевода страницы." - }, - "newTabLabel": { - "message": "Новая вкладка" - }, - "currentTabLabel": { - "message": "Текущая вкладка" - }, - "styleLabel": { - "message": "Стиль" - }, - "themeLabel": { - "message": "Тема" - }, - "themeCaptionLabel": { - "message": "Выберите тему оформления." - }, - "lightLabel": { - "message": "Светлая" - }, - "darkLabel": { - "message": "Тёмная" - }, - "systemLabel": { - "message": "Системные" - }, - "buttonStyleLabel": { - "message": "Кнопка перевода" - }, - "buttonStyleCaptionLabel": { - "message": "Настройте стиль кнопки перевода, показываемой на веб-странице." - }, - "buttonSizeLabel": { - "message": "Размер" - }, - "buttonPositionLabel": { - "message": "Положение" - }, - "topRightLabel": { - "message": "Вверху справа" - }, - "bottomRightLabel": { - "message": "Внизу справа" - }, - "topLeftLabel": { - "message": "Вверху слева" - }, - "bottomLeftLabel": { - "message": "Внизу слева" - }, - "topLabel": { - "message": "Вверху" - }, - "bottomLabel": { - "message": "Внизу" - }, - "leftLabel": { - "message": "Слева" - }, - "rightLabel": { - "message": "Справа" - }, - "positionOffsetLabel": { - "message": "Положение - Сдвиг" - }, - "panelStyleLabel": { - "message": "Панель с переводом" - }, - "panelStyleCaptionLabel": { - "message": "Настройте стиль панели с переводом, показываемой на веб-странице." - }, - "widthLabel": { - "message": "Ширина" - }, - "heightLabel": { - "message": "Высота" - }, - "fontSizeLabel": { - "message": "Размер шрифта" - }, - "referencePointLabel": { - "message": "Положение" - }, - "bottomSelectedTextLabel": { - "message": "Снизу от выделенного текста" - }, - "topSelectedTextLabel": { - "message": "Сверху от выделенного текста" - }, - "clickedPointLabel": { - "message": "Рядом с курсором" - }, - "displayDirectionLabel": { - "message": "Положение - Направление" - }, - "resultFontColorLabel": { - "message": "Цвет шрифта перевода" - }, - "isOverrideColorsLabel": { - "message": "Переопределить цвета по умолчанию цветами ниже" - }, - "candidateFontColorLabel": { - "message": "Цвет шрифта вариантов перевода" - }, - "bgColorLabel": { - "message": "Цвет фона" - }, - "otherLabel": { - "message": "Другое" - }, - "isShowOptionsPageWhenUpdatedLabel": { - "message": "Показать настройки при обновлении" - }, - "isShowOptionsPageWhenUpdatedCaptionLabel": { - "message": "Показать настройки после обновления расширения. Позволяет быстро узнать что нового." - }, - "isDebugModeLabel": { - "message": "Включить режим отладки" - }, - "isDebugModeCaptionLabel": { - "message": "Если включен режим отладки, то журнал работы будет выводиться в отладчик." - }, - "exportSettingsLabel": { - "message": "Экспорт настроек" - }, - "exportSettingsCaptionLabel": { - "message": "Сохраните настройки в виде json-файла на своём компьютере." - }, - "exportButtonLabel": { - "message": "Экспорт" - }, - "importSettingsLabel": { - "message": "Импорт настроек" - }, - "importSettingsCaptionLabel": { - "message": "Загрузите файл настроек, сохраненный на компьютере." - }, - "importButtonLabel": { - "message": "Импорт" - }, - "resetSettingsLabel": { - "message": "Сброс настроек" - }, - "resetSettingsCaptionLabel": { - "message": "Восстановить все настройки по умолчанию." - }, - "resetSettingsButtonLabel": { - "message": "Сброс" - }, - "shortcutsLabel": { - "message": "Горячие клавиши" - }, - "keyboardShortcutsLabel": { - "message": "Сочетания клавиш" - }, - "setKeyboardShortCutsMessage": { - "message": "Настройка сочетаний клавиш." - }, - "typeShortcutMessage": { - "message": "Введите сочетание клавиш" - }, - "typeLetterMessage": { - "message": "Введите букву" - }, - "includeModifierKeysMessage": { - "message": "Нажмите клавишу Ctrl или Alt" - }, - "includeMacModifierKeysMessage": { - "message": "Нажмите клавишу Command, Ctrl или Alt" - }, - "invalidLetterMessage": { - "message": "Эта буква не может быть использована" - }, - "invalidShortcutMessage": { - "message": "Это сочетание клавиш не может быть использовано" - }, - "clear": { - "message": "Очистить" - }, - "reset": { - "message": "Сброс" - }, - "informationLabel": { - "message": "Информация" - }, - "additionalPermissionLabel": { - "message": "Включить перевод на веб-страницах" - }, - "additionalPermissionCaptionLabel": { - "message": "Для использования Simple Translate на веб-страницах требуются дополнительные разрешения." - }, - "enableLabel": { - "message": "Включить" - }, - "backersLabel": { - "message": "При поддержке" - }, - "licenseLabel": { - "message": "Лицензия" - }, - "donationLabel": { - "message": "Пожалуйста, поблагодарите автора" - }, - "donationCaptionLabel": { - "message": "Спасибо за использование Simple Translate.
Ваша поддержка это большая помощь в дальнейшем развитии расширения.
Если вам нравится Simple Translate, пожалуйста, подумайте о внесении небольшого пожертвования." - }, - "amazonTitleLabel": { - "message": "Карты eGift от amazon.co.jp" - }, - "sponsorsLabel": { - "message": "Спонсоры" - }, - "addonPageLabel": { - "message": "Страница расширения" - }, - "extensionPageLabel": { - "message": "Страница расширения" - }, - "privacyPolicyLabel": { - "message": "Политика конфиденциальности" - }, - "amazonUrl": { - "message": "https://www.amazon.co.jp/dp/B004N3APGO?language=ru_RU" - }, - "addonUrl": { - "message": "https://addons.mozilla.org/ru/firefox/addon/simple-translate/?src=optionpage" - }, - "networkError": { - "message": "Ошибка: проверьте состояние сетевого подключения." - }, - "unavailableError": { - "message": "Ошибка: достигнут предел использования сервиса. Подождите некоторое время и повторите попытку." - }, - "deeplAuthError": { - "message": "Ошибка: аутентификация DeepL API не удалась. Пожалуйста, установите правильный ключ аутентификации и тарифный план на странице настроек." - }, - "unknownError": { - "message": "Ошибка: неизвестная ошибка" - }, - "translatePageMenu": { - "message": "Перевести всю страницу" - }, - "translateTextMenu": { - "message": "Перевести выделенный текст" - }, - "translateLinkMenu": { - "message": "Перевести страницу по ссылке" - }, - "openPopupDescription": { - "message": "Показать всплывающее окно на панели инструментов" - }, - "lang_af": { - "message": "Африкаанс" - }, - "lang_sq": { - "message": "Албанский" - }, - "lang_am": { - "message": "Амхарский" - }, - "lang_ar": { - "message": "Арабский" - }, - "lang_hy": { - "message": "Армянский" - }, - "lang_az": { - "message": "Азербайджанский" - }, - "lang_eu": { - "message": "Баскский" - }, - "lang_be": { - "message": "Белорусский" - }, - "lang_bn": { - "message": "Бенгальский" - }, - "lang_bs": { - "message": "Боснийский" - }, - "lang_bg": { - "message": "Болгарский" - }, - "lang_ca": { - "message": "Каталанский" - }, - "lang_ceb": { - "message": "Себуано" - }, - "lang_ny": { - "message": "Чева" - }, - "lang_zh_CN": { - "message": "Китайский (упрощенный)" - }, - "lang_zh_TW": { - "message": "Китайский (традиционный)" - }, - "lang_co": { - "message": "Корсиканский" - }, - "lang_hr": { - "message": "Хорватский" - }, - "lang_cs": { - "message": "Чешский" - }, - "lang_da": { - "message": "Датский" - }, - "lang_nl": { - "message": "Нидерландский" - }, - "lang_en": { - "message": "Английский" - }, - "lang_eo": { - "message": "Эсперанто" - }, - "lang_et": { - "message": "Эстонский" - }, - "lang_fi": { - "message": "Финский" - }, - "lang_fr": { - "message": "Французский" - }, - "lang_fy": { - "message": "Фризский" - }, - "lang_gl": { - "message": "Галисийский" - }, - "lang_ka": { - "message": "Грузинский" - }, - "lang_de": { - "message": "Немецкий" - }, - "lang_el": { - "message": "Греческий" - }, - "lang_gu": { - "message": "Гуджарати" - }, - "lang_ht": { - "message": "Гаитянский" - }, - "lang_ha": { - "message": "Хауса" - }, - "lang_haw": { - "message": "Гавайский" - }, - "lang_he": { - "message": "Иврит" - }, - "lang_hi": { - "message": "Хинди" - }, - "lang_hu": { - "message": "Венгерский" - }, - "lang_is": { - "message": "Исландский" - }, - "lang_ig": { - "message": "Игбо" - }, - "lang_id": { - "message": "Индонезийский" - }, - "lang_ga": { - "message": "Ирландский" - }, - "lang_it": { - "message": "Итальянский" - }, - "lang_ja": { - "message": "Японский" - }, - "lang_jv": { - "message": "Яванский" - }, - "lang_kn": { - "message": "Каннада" - }, - "lang_kk": { - "message": "Казахский" - }, - "lang_km": { - "message": "Кхмерский" - }, - "lang_rw": { - "message": "Киньяруанда" - }, - "lang_ky": { - "message": "Киргизский" - }, - "lang_ko": { - "message": "Корейский" - }, - "lang_ku": { - "message": "Курдский" - }, - "lang_lo": { - "message": "Лаосский" - }, - "lang_la": { - "message": "Латынь" - }, - "lang_lv": { - "message": "Латышский" - }, - "lang_lt": { - "message": "Литовский" - }, - "lang_lb": { - "message": "Люксембургский" - }, - "lang_mk": { - "message": "Македонский" - }, - "lang_mg": { - "message": "Малагасийский" - }, - "lang_ms": { - "message": "Малайский" - }, - "lang_ml": { - "message": "Малаялам" - }, - "lang_mt": { - "message": "Мальтийский" - }, - "lang_mi": { - "message": "Маори" - }, - "lang_mr": { - "message": "Маратхи" - }, - "lang_mn": { - "message": "Монгольский" - }, - "lang_hmn": { - "message": "Хмонг" - }, - "lang_my": { - "message": "Мьянманский" - }, - "lang_ne": { - "message": "Непальский" - }, - "lang_no": { - "message": "Норвежский" - }, - "lang_nb": { - "message": "Норвежский (bokmål)" - }, - "lang_or": { - "message": "Одия (Ория)" - }, - "lang_fa": { - "message": "Фарси" - }, - "lang_pl": { - "message": "Польский" - }, - "lang_pt": { - "message": "Португальский" - }, - "lang_pa": { - "message": "Панджаби" - }, - "lang_ps": { - "message": "Пушту" - }, - "lang_ro": { - "message": "Румынский" - }, - "lang_ru": { - "message": "Русский" - }, - "lang_sm": { - "message": "Самоанский" - }, - "lang_gd": { - "message": "Шотландский Гэльский" - }, - "lang_sr": { - "message": "Сербский" - }, - "lang_st": { - "message": "Сесото" - }, - "lang_sn": { - "message": "Шона" - }, - "lang_sd": { - "message": "Синдхи" - }, - "lang_si": { - "message": "Сингальский" - }, - "lang_sk": { - "message": "Словацкий" - }, - "lang_sl": { - "message": "Словенский" - }, - "lang_so": { - "message": "Сомалийский" - }, - "lang_es": { - "message": "Испанский" - }, - "lang_su": { - "message": "Суданский" - }, - "lang_sw": { - "message": "Суахили" - }, - "lang_sv": { - "message": "Шведский" + "There are no options currently.": { + "message": "На данный момент нету доступных настроек." }, - "lang_tl": { - "message": "Тагальский" + "Source code": { + "message": "Исходный код" }, - "lang_tg": { - "message": "Таджикский" + "Initializing...": { + "message": "Инициализирем..." }, - "lang_ta": { - "message": "Тамильский" + "Submitting...": { + "message": "Обрабатываем..." }, - "lang_tt": { - "message": "Татарский" + "Ready to submit.": { + "message": "Готово к отправке." }, - "lang_te": { - "message": "Телугу" + "Submit": { + "message": "Отправить" }, - "lang_th": { - "message": "Тайский" + "URLs": { + "message": "Ссылки" }, - "lang_tr": { - "message": "Турецкий" + "Full URL": { + "message": "Полная ссылка" }, - "lang_tk": { - "message": "Туркменский" + "Decoded URL": { + "message": "Раскодированная ссылка" }, - "lang_uk": { - "message": "Украинский" + "Origin Details": { + "message": "Детали Источника" }, - "lang_ur": { - "message": "Урду" + "Origin": { + "message": "Источник" }, - "lang_ug": { - "message": "Уйгурский" + "Protocol": { + "message": "Протокол" }, - "lang_uz": { - "message": "Узбекский" + "Username": { + "message": "Имя пользователя" }, - "lang_vi": { - "message": "Вьетнамский" + "Password": { + "message": "Пароль" }, - "lang_cy": { - "message": "Валлийский" + "Host": { + "message": "Домен" }, - "lang_xh": { - "message": "Коса" + "Hostname": { + "message": "Доменное имя" }, - "lang_yi": { - "message": "Идиш" + "Port": { + "message": "Порт" }, - "lang_yo": { - "message": "Йоруба" + "Pathname Details": { + "message": "Детали Пути" }, - "lang_zu": { - "message": "Зулу" + "Pathname": { + "message": "Путь" }, - "lang_en_US": { - "message": "Английский (американский)" + "Search Parameters Details": { + "message": "Детали Поисковых Параметров" }, - "lang_en_GB": { - "message": "Английский (британский)" + "Search": { + "message": "Поисковая строка" }, - "lang_pt_PT": { - "message": "Португальский" + "Search parameters": { + "message": "Поисковые параметры" }, - "lang_pt_BR": { - "message": "Португальский (бразильский)" + "Fragment Details": { + "message": "Детали Фрагмента" }, - "lang_zh": { - "message": "Китайский" + "Hash": { + "message": "Идентификатор фрагмента" } } diff --git a/apps/extension/src/lib/localization/index.ts b/apps/extension/src/lib/localization/index.ts new file mode 100644 index 00000000..d70539c2 --- /dev/null +++ b/apps/extension/src/lib/localization/index.ts @@ -0,0 +1 @@ +export { getLocalizedMessage } from "./lib"; diff --git a/apps/extension/src/lib/localization/lib.ts b/apps/extension/src/lib/localization/lib.ts new file mode 100644 index 00000000..0654eef8 --- /dev/null +++ b/apps/extension/src/lib/localization/lib.ts @@ -0,0 +1,13 @@ +import browser from "webextension-polyfill"; +import type { default as messages } from "../../_locales/en/messages.json"; +import type { IPredefinedMessage } from "./predefined-messages"; + +type IMessage = keyof typeof messages | IPredefinedMessage; + +export function getLocalizedMessage(message: IMessage, ...substitutions: string[]) { + return browser.i18n.getMessage(message, substitutions); +} + +export function getCurrentLocale() { + return browser.i18n.getUILanguage(); +} diff --git a/apps/extension/src/lib/localization/predefined-messages.ts b/apps/extension/src/lib/localization/predefined-messages.ts new file mode 100644 index 00000000..3fcff3ad --- /dev/null +++ b/apps/extension/src/lib/localization/predefined-messages.ts @@ -0,0 +1,46 @@ +export type IPredefinedMessage = + | IExtensionID + | IUILocale + | IBIDIDir + | IBIDIReversedDir + | IBIDIStartEdge + | IBIDIEndEdge; + +/** + * The extension's internally-generated UUID. You might use this string to construct URLs for resources inside the extension. + * Even unlocalized extensions can use this message. + * + * You can't use this message in a manifest file. + * + * Also note that this ID is not the add-on ID returned by runtime.id, and that can be set using the browser_specific_settings key in manifest.json. + * It's the generated UUID that appears in the add-on's URL. + * This means that you can't use this value as the extensionId parameter to runtime.sendMessage(), + * and can't use it to check against the id property of a runtime.MessageSender object. + */ +type IExtensionID = "@@extension_id"; + +/** + * The current locale; you might use this string to construct locale-specific URLs. + */ +type IUILocale = "@@ui_locale"; + +/** + * The text direction for the current locale, + * either "ltr" for left-to-right languages such as English + * or "rtl" for right-to-left languages such as Arabic. + */ +type IBIDIDir = "@@bidi_dir"; + +/** + * If the `"@@bidi_dir"` is `"ltr"`, then this is "rtl"; otherwise, it's "ltr". + */ +type IBIDIReversedDir = "@@bidi_reversed_dir"; +/** + * If the `@@bidi_dir` is "ltr", then this is "left"; otherwise, it's "right". + */ +type IBIDIStartEdge = "@@bidi_start_edge"; + +/** + * If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left". + */ +type IBIDIEndEdge = "@@bidi_end_edge"; diff --git a/apps/extension/src/manifest-chrome.json b/apps/extension/src/manifest-chrome.json index 1962c5b9..95eae659 100644 --- a/apps/extension/src/manifest-chrome.json +++ b/apps/extension/src/manifest-chrome.json @@ -2,7 +2,7 @@ "manifest_version": 3, "version": "0.0.1", "name": "__MSG_extName__", - "description": "__MSG_extDescription__", + "description": "__MSG_extension_description__", "default_locale": "en", "permissions": [], "options_ui": { diff --git a/apps/extension/src/manifest-firefox.json b/apps/extension/src/manifest-firefox.json index 350b42b0..0be00ba3 100644 --- a/apps/extension/src/manifest-firefox.json +++ b/apps/extension/src/manifest-firefox.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "version": "0.0.1", - "name": "__MSG_extName__", - "description": "__MSG_extDescription__", + "name": "__MSG_extension_title__", + "description": "__MSG_extension_description__", "default_locale": "en", "browser_specific_settings": { "gecko": { @@ -37,7 +37,7 @@ "19": "assets/icons/logo/19.png", "16": "assets/icons/logo/16.png" }, - "default_title": "__MSG_extName__", + "default_title": "__MSG_extension_title__", "default_popup": "popup/index.html" } } diff --git a/apps/extension/src/manifest-firefox.schema.json b/apps/extension/src/manifest-firefox.schema.json index fe179820..db922da6 100644 --- a/apps/extension/src/manifest-firefox.schema.json +++ b/apps/extension/src/manifest-firefox.schema.json @@ -11,10 +11,10 @@ "type": "string" }, "name": { - "const": "__MSG_extName__" + "type": "string" }, "description": { - "const": "__MSG_extDescription__" + "type": "string" }, "default_locale": { "const": "en" diff --git a/apps/extension/src/options/components/layouts/layout.tsx b/apps/extension/src/options/components/layouts/layout.tsx index 7ba041a3..6b5e3ef0 100644 --- a/apps/extension/src/options/components/layouts/layout.tsx +++ b/apps/extension/src/options/components/layouts/layout.tsx @@ -1,12 +1,15 @@ import { Outlet } from "react-router"; import { LinkExternal } from "@repo/ui/links"; +import { getLocalizedMessage } from "#lib/localization"; import styles from "./layout.module.scss"; export function Layout() { return ( <> -
Link overwatch
+
+ {getLocalizedMessage("extension_title")} +
@@ -20,7 +23,7 @@ export function Layout() { "https://github.com/GabenGar/todos/tree/master/apps/extension" } > - Source code + {getLocalizedMessage("Source code")} diff --git a/apps/extension/src/options/pages/home.tsx b/apps/extension/src/options/pages/home.tsx index 9e1faada..d812d754 100644 --- a/apps/extension/src/options/pages/home.tsx +++ b/apps/extension/src/options/pages/home.tsx @@ -1,15 +1,18 @@ import { Page } from "@repo/ui/pages"; import { Overview, OverviewHeader } from "@repo/ui/articles"; +import { getLocalizedMessage } from "#lib/localization"; export function HomePage() { - const heading = "Options"; + const heading = getLocalizedMessage("Options"); return ( {() => ( <> - There are no options currently + + {getLocalizedMessage("There are no options currently.")} + )} diff --git a/apps/extension/src/options/routes.tsx b/apps/extension/src/options/routes.tsx index 68363fe2..115c023e 100644 --- a/apps/extension/src/options/routes.tsx +++ b/apps/extension/src/options/routes.tsx @@ -5,6 +5,7 @@ import { } from "react-router"; import { Preformatted } from "@repo/ui/formatting"; import { DescriptionList, DescriptionSection } from "@repo/ui/description-list"; +import { getLocalizedMessage } from "#lib/localization"; import { Layout } from "#options/components/layouts"; import { HomePage } from "./pages/home"; @@ -30,15 +31,15 @@ function RootError() { return ( {status}} /> {statusText}} /> {String(data)}} /> @@ -51,11 +52,11 @@ function RootError() { return ( {name}} /> {message}} /> @@ -65,11 +66,13 @@ function RootError() { return ( Unknown Error} + dKey={getLocalizedMessage("Name")} + dValue={ + {getLocalizedMessage("Unknown Error")} + } /> {String(error)}} /> diff --git a/apps/extension/src/popup/components/forms/form.tsx b/apps/extension/src/popup/components/forms/form.tsx index 989597d6..b32f30b9 100644 --- a/apps/extension/src/popup/components/forms/form.tsx +++ b/apps/extension/src/popup/components/forms/form.tsx @@ -1,28 +1,38 @@ import type { ReactNode } from "react"; import { Form as RouterForm, - FormProps, + type FormProps, type FormMethod, useActionData, useNavigation, + type Navigation, } from "react-router"; import { createBlockComponent } from "@repo/ui/meta"; import { ButtonSubmit } from "@repo/ui/buttons"; +import { Preformatted } from "@repo/ui/formatting"; +import { getLocalizedMessage } from "#lib/localization"; import { InputSection } from "./section"; import styles from "./form.module.scss"; -import { Preformatted } from "@repo/ui/formatting"; export interface IFormProps extends Omit { id: string; method?: FormMethod; children?: (formID: string) => ReactNode; + submitButton?: (state: Navigation["state"]) => ReactNode; } export const Form = createBlockComponent(styles, Component); -function Component({ id, className, children, ...props }: IFormProps) { +function Component({ + id, + className, + submitButton, + children, + ...props +}: IFormProps) { const navigation = useNavigation(); + navigation.state; const data = useActionData(); const formID = `${id}-form`; @@ -32,9 +42,9 @@ function Component({ id, className, children, ...props }: IFormProps) { {navigation.state === "loading" ? ( - <>Initializing... + getLocalizedMessage("Initializing...") ) : navigation.state === "submitting" ? ( - <>Submitting... + getLocalizedMessage("Submitting...") ) : data instanceof Error ? (
  1. @@ -42,13 +52,15 @@ function Component({ id, className, children, ...props }: IFormProps) {
) : ( - <>Ready to submit. + getLocalizedMessage("Ready to submit.") )}
- Submit + {!submitButton + ? getLocalizedMessage("Submit") + : submitButton(navigation.state)} diff --git a/apps/extension/src/popup/components/layouts/header.tsx b/apps/extension/src/popup/components/layouts/header.tsx index e5ce4008..65149156 100644 --- a/apps/extension/src/popup/components/layouts/header.tsx +++ b/apps/extension/src/popup/components/layouts/header.tsx @@ -1,8 +1,9 @@ import browser from "webextension-polyfill"; import { createBlockComponent, type IBaseComponentProps } from "@repo/ui/meta"; import { Button } from "@repo/ui/buttons"; +import { getLocalizedMessage } from "#lib/localization"; -import styles from "./header.module.scss" +import styles from "./header.module.scss"; interface IProps extends IBaseComponentProps<"header"> {} @@ -13,10 +14,10 @@ function Component({ ...props }: IProps) {