
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@wxt-dev/i18n
Advanced tools
Type-safe wrapper around browser.i18n.getMessage with additional features
@wxt-dev/i18n@wxt-dev/i18n is a simple, type-safe wrapper around the browser.i18n APIs. It provides several benefits over the standard API:
It also provides several benefits over other non-web extension specific i18n packages:
However, it does have one major downside:
browser.i18n API, to change the language, users must change the browser's language[!IMPORTANT] You don't have to use
wxtto use this package - it will work in any bundler setup. See Installation without WXT for more details.
Install @wxt-dev/i18n via your package manager:
pnpm i @wxt-dev/i18n
Add the WXT module to your wxt.config.ts file and setup a default locale:
export default defineConfig({
modules: ['@wxt-dev/i18n/module'],
manifest: {
default_locale: 'en',
},
});
Create a localization file at <srcDir>/locales/<default_locale>.yml or move your existing localization files there.
# <srcDir>/locales/en.yml
helloWorld: Hello world!
@wxt-dev/i18nsupports the standard messages format, so if you already have localization files at<rootDir>/public/_locale/<lang>/messages.json, you don't need to convert them to YAML or refactor them - just move them to<srcDir>/locales/<lang>.jsonand they'll just work out of the box!
To get a translation, use the auto-imported i18n object or import it manually:
import { i18n } from '#i18n';
i18n.t('helloWorld'); // "Hello world!"
And you're done! Using WXT, you get type-safety out of the box.
Install @wxt-dev/i18n via your package manager:
pnpm i @wxt-dev/i18n
Create a messages file at _locales/<lang>/messages.json or move your existing translations there:
{
"helloWorld": {
"message": "Hello world!"
}
}
[!NOTE] For the best DX, you should to integrate
@wxt-dev/i18ninto your build process. This enables:
- Plural forms
- Simple messages file format
- Type safety
See Build Integrations to set it up.
Create the i18n object, export it, and use it wherever you want!
import { createI18n } from '@wxt-dev/i18n';
export const i18n = createI18n();
i18n.t('helloWorld'); // "Hello world!";
The module can be configured via the i18n config:
export default defineConfig({
modules: ['@wxt-dev/i18n'],
i18n: {
// ...
},
});
Options have JSDocs available in your editor, or you can read them in the source code: I18nOptions.
[!DANGER] You can only use the file format discussed on this page if you have integrated
@wxt-dev/i18ninto your build process. If you have not integrated it into your build process, you must use JSON files in the_localesdirectory, like a normal web extension.
You can define your messages in several different file types:
.yml.yaml.json.jsonc.json5.tomlYou can have translations at the top level or nest them into groups:
ok: OK
cancel: Cancel
welcome:
title: Welcome to XYZ
dialogs:
confirmation:
title: 'Are you sure?'
To access a nested key, use .:
i18n.t('ok'); // "OK"
i18n.t('cancel'); // "Cancel"
i18n.t('welcome.title'); // "Welcome to XYZ"
i18n.t('dialogs.confirmation.title'); // "Are you sure?"
Because @wxt-dev/i18n is based on browser.i18n, you define substitutions the same way, with $1-$9:
hello: Hello $1!
order: Thanks for ordering your $1
$To escape the dollar sign, put another $ in front of it:
dollars: $$$1
i18n.t('dollars', ['1.00']); // "$1.00"
[!WARNING] Plural support languages like Arabic, that have different forms for "few" or "many", is not supported right now. Feel free to open a PR if you are interested in this!
To get a different translation based on a count:
items:
1: 1 item
n: $1 items
Then you pass in the count as the second argument to i18n.t:
i18n.t('items', 0); // "0 items"
i18n.t('items', 1); // "1 item"
i18n.t('items', 2); // "2 items"
To add a custom translation for 0 items:
items:
0: No items
1: 1 item
n: $1 items
i18n.t('items', 0); // "No items"
i18n.t('items', 1); // "1 item"
i18n.t('items', 2); // "2 items"
If you need to pass a custom substitution for $1 instead of the count, just add the substitution:
items:
0: No items
1: $1 item
n: $1 items
i18n.t('items', 0, ['Zero']); // "No items"
i18n.t('items', 1, ['One']); // "One item"
i18n.t('items', 2, ['Multiple']); // "Multiple items"
@wxt-dev/i18n is compatible with the message format used by browser.i18n.
[!IMPORTANT] This means if you're migrating to
@wxt-dev/i18nand you're already using the verbose format, you don't have to change anything!
A key is treated as "verbose" when it is:
message, description and/or placeholder{
"appName": {
"message": "GitHub - Better Line Counts",
"description": "The app's name, should not be translated"
},
"ok": "OK",
"deleteConfirmation": {
"title": "Delete XYZ?",
"message": "You cannot undo this action once taken."
}
}
In this example, only appName is considered verbose. deleteConfirmation is not verbose because it contains the additional property title.
i18n.t('appName'); // ✅ "GitHub - Better Line Counts"
i18n.t('appName.message'); // ❌
i18n.t('ok'); // ✅ "OK"
i18n.t('deleteConfirmation'); // ❌
i18n.t('deleteConfirmation.title'); // ✅ "Delete XYZ?"
i18n.t('deleteConfirmation.message'); // ✅ "You cannot undo this action once taken."
If this is confusing, don't worry! With type-safety, you'll get a type error if you do it wrong. If type-safety is disabled, you'll get a runtime warning in the devtools console.
[!WARNING] Using the verbose format is not recommended. I have yet to see a translation service and software that supports this format out of the box. Stick with the simple format when you can.
To use the custom messages file format, you'll need to use @wxt-dev/i18n/build to transform the custom format to the one expected by browsers.
But TLDR, all you need is:
// wxt.config.ts
export default defineConfig({
modules: ['@wxt-dev/i18n/module'],
});
Types are generated whenever you run wxt prepare or another build command.
If you're not using WXT, you'll have to pre-process the localization files yourself. Here's a basic script to generate the _locales/.../messages.json and wxt-i18n-structure.d.ts files:
// build-i18n.js
import {
parseMessagesFile,
generateChromeMessagesFile,
generateTypeFile,
} from '@wxt-dev/i18n/build';
// Read your localization files
const messages = {
en: await parseMessagesFile('path/locales/en.yml'),
de: await parseMessagesFile('path/locales/de.yml'),
// ...
};
// Generate JSON files for the extension
await generateChromeMessagesFile('dist/_locales/en/messages.json', messages.en);
await generateChromeMessagesFile('dist/_locales/de/messages.json', messages.de);
// ...
// Generate a types file based on your default_locale
await generateTypeFile('wxt-i18n-structure.d.ts', messages.en);
Then run the script:
node generate-i18n.js
If your build tool has a dev mode, you'll also want to rerun the script whenever you change a localization file.
Once you've generated wxt-i18n-structure.d.ts (see the Custom section), you can use it to pass the generated type into createI18n:
import type { WxtI18nStructure } from './wxt-i18n-structure';
export const i18n = createI18n<WxtI18nStructure>();
And just like that, you have type safety!
For better DX, you can configure your editor with plugins and extensions.
The I18n Ally Extension adds several features to VS Code:
You'll need to configure it the extension so it knows where your localization files are and what function represents getting a translation:
.vscode/i18n-ally-custom-framework.yml:
# An array of strings which contain Language Ids defined by VS Code
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
languageIds:
- typescript
- typescriptreact
# Look for t("...")
usageMatchRegex:
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
# Disable other built-in i18n ally frameworks
monopoly: true
.vscode/settings.json:
{
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested"
}
As of time of writing, Aug 18, 2024, no extensions exist for Zed to add I18n support.
Install the I18n Ally plugin. The docs are limited around their Jetbrains support, but you'll probably need to configure the plugin similar to VS Code... Not sure where the files go though.
Please open a PR if you figure it out!
FAQs
Type-safe wrapper around browser.i18n.getMessage with additional features
We found that @wxt-dev/i18n demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.