Internationalization (i18n)
Relevant source files
The following files were used as context for generating this wiki page:
- packages/plugin/i18n.ts
- packages/web/src/i18n.ts
- packages/plugin/assets/locales/en/translation.json
- packages/web/src/assets/locales/en/translation.json
- packages/web/src/assets/locales/zh-CN/translation.json
- packages/plugin/popup/components/HistoryTaskList.tsx
- packages/web/src/pages/(layout)/page.[slug].tsx
- packages/plugin/lib/single-file-extension-background.js
- packages/plugin/lib/single-file-extension-editor.js
The internationalization (i18n) system in web-archive is designed to provide a localized user experience for both the web interface and the browser extension. The implementation is bifurcated: the React-based components (web client and plugin popup) use the i18next framework, while the browser extension's core JavaScript files (background and content scripts) rely on the standard browser.i18n WebExtension API. This dual approach ensures that all user-facing text can be translated.
The system supports English (en) and Chinese (zh-CN) out of the box, with translation strings stored in JSON files. The user's language is automatically detected, with English serving as the fallback language.
Architecture
The i18n architecture is split across different parts of the application, each using a technology-appropriate method for handling translations.
The following diagram illustrates the different i18n implementations across the project's packages.
Configuration
Both the web and plugin packages use i18next and have nearly identical configuration files to initialize the service.
i18next Initialization
The configuration is handled in packages/web/src/i18n.ts and packages/plugin/i18n.ts. The i18next instance is initialized with the following key settings:
| Option | Value | Description |
|---|---|---|
fallbackLng | 'en' | The default language to use if the detected language is not available. |
debug | true | Enables debug logging to the console for development. |
interpolation.escapeValue | false | Disables value escaping, as React already handles XSS protection. |
resources | { 'en': ..., 'zh-CN': ... } | An object containing the translation strings for each supported language, loaded directly from JSON files. |
Sources: packages/web/src/i18n.ts:18-29, packages/plugin/i18n.ts:14-25
The system uses i18next-browser-languagedetector to automatically detect the user's preferred language from their browser settings.
// packages/web/src/i18n.ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import enTranslation from '~/assets/locales/en/translation.json'
import zhTranslation from '~/assets/locales/zh-CN/translation.json'
i18n
// detect user language
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
.init({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
'en': {
translation: enTranslation,
},
'zh-CN': {
translation: zhTranslation,
},
},
})
export default i18n
Sources: packages/web/src/i18n.ts:1-31
Language Resources
Translation strings are stored in separate JSON files for each package and language.
- Web App:
packages/web/src/assets/locales/ - Plugin Popup:
packages/plugin/assets/locales/
Each directory contains an en/translation.json and a zh-CN/translation.json file.
Example Translation Keys
| Package | Key | English Value (en/translation.json) |
|---|---|---|
web | open-read-mode | "open Read mode" |
web | delete-this-page-confirm | "Are you sure you want to delete this page?" |
plugin | save-page | "Save Page" |
plugin | task-scraping | "Scraping page" |
plugin | clear-finished-task-list | "Clear finished history task list" |
Sources: packages/web/src/assets/locales/en/translation.json, packages/plugin/assets/locales/en/translation.json
Implementation
React Components (web and plugin/popup)
React components leverage the react-i18next library and its useTranslation hook to access translated strings.
The following sequence diagram shows the flow of a component retrieving a translation.
Usage Example
The useTranslation hook is called within a component to get the t function, which is then used to retrieve strings by their key.
// packages/web/src/pages/(layout)/page.[slug].tsx
function ArchivePage() {
const { t } = useTranslation()
// ...
const handleDeletePage = async () => {
if (!window.confirm(t('delete-this-page-confirm')))
return
// ...
}
// ...
return (
// ...
<Button
variant="secondary"
size="sm"
onClick={() => setReadMode(!readMode)}
>
{readMode ? t('open-iframe-mode') : t('open-read-mode')}
</Button>
// ...
)
}
Sources: packages/web/src/pages/(layout)/page.[slug].tsx:21, 62, 92-96
A similar pattern is used in the plugin's popup components.
// packages/plugin/popup/components/HistoryTaskList.tsx
function ClearHistoryTaskListButton() {
const { t } = useTranslation()
function handleClick() {
sendMessage('clear-finished-task-list', {})
}
return (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
{/* ... */}
</TooltipTrigger>
<TooltipContent side="left" sideOffset={20}>
{t('clear-finished-task-list')}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}
Sources: packages/plugin/popup/components/HistoryTaskList.tsx:40-56
Browser Extension Scripts (plugin/lib)
The non-React parts of the browser extension, such as background and content scripts, use the standard WebExtension browser.i18n.getMessage API. This API retrieves strings from the _locales directory of the extension package, which is a standard location for WebExtension i18n.
Usage Example
In single-file-extension-background.js, several messages are retrieved to be used for context menus and button tooltips.
// packages/plugin/lib/single-file-extension-background.js
const we = "/src/ui/resources/icon_128.png",
pe = "/src/ui/resources/icon_128_wait",
be = browser.i18n.getMessage("buttonDefaultTooltip"),
ge = browser.i18n.getMessage("buttonBlockedTooltip"),
ye = browser.i18n.getMessage("buttonInitializingBadge"),
ve = browser.i18n.getMessage("buttonInitializingTooltip"),
ke = browser.i18n.getMessage("buttonErrorBadge"),
Ae = browser.i18n.getMessage("buttonBlockedBadge"),
Te = browser.i18n.getMessage("buttonOKBadge"),
xe = browser.i18n.getMessage("buttonSaveProgressTooltip"),
Ie = browser.i18n.getMessage("buttonUploadProgressTooltip"),
Ce = browser.i18n.getMessage("buttonAutoSaveActiveBadge"),
Ee = browser.i18n.getMessage("buttonAutoSaveActiveTooltip");
Sources: packages/plugin/lib/single-file-extension-background.js:1:10029-10515
Similarly, the editor script retrieves labels for UI elements it creates on the page.
// packages/plugin/lib/single-file-extension-editor.js
let EMBEDDED_IMAGE_BUTTON_MESSAGE, SHARE_PAGE_BUTTON_MESSAGE, SHARE_SELECTION_BUTTON_MESSAGE, ERROR_TITLE_MESSAGE;
try {
EMBEDDED_IMAGE_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelEmbeddedImageButton");
SHARE_PAGE_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelSharePageButton");
SHARE_SELECTION_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelShareSelectionButton");
ERROR_TITLE_MESSAGE = browser.i18n.getMessage("topPanelError");
} catch (error) {
// ignored
}
Sources: packages/plugin/lib/single-file-extension-editor.js:1155-1164
Summary
The web-archive project employs a robust and context-aware internationalization strategy. It uses the modern i18next framework for its React-based UIs, enabling features like automatic language detection and easy integration with components via hooks. For the core browser extension logic written in plain JavaScript, it adheres to the standard browser.i18n API, ensuring compatibility and performance. This dual system allows for complete localization coverage across the entire user-facing surface of the application.