I18n
I18n is a reactive internationalization manager based on vanilla-signal, with source code located in src/i18n.js. It can create independent instances via new I18n(options) or createI18n(options). It also retains legacy utility functions like setLanguages/getLanguages/getLocale/getLang/t for compatibility with older projects.
It is recommended to use independent instances in new code to avoid sharing global language state across multiple pages or components.
Installation
npm:
npm install vanilla-signal-i18n
script:
<script src="https://unpkg.com/vanilla-signal-i18n/dist/index.umd.js"></script>
Import
// esm
import { I18n, createI18n, defaultI18n, t } from 'vanilla-signal-i18n';
// umd: Global variable `vanillaSignalI18n`
const { I18n, createI18n, defaultI18n, t } = vanillaSignalI18n;
Basic Usage
const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'en',
messages: {
en: {
common: {
confirm: 'Confirm',
hello: 'Hello {name}',
},
},
zh: {
common: {
confirm: '确定',
hello: '你好 {name}',
},
},
},
});
i18n.t('common.confirm'); // 确定
i18n.t('common.hello', { name: 'World' }); // 你好 World
Keys support dot notation paths. When a translation is not found in the current language, it falls back in the order of zh-cn -> zh -> en. If still not found, the original key is returned.
Reactive Translation
i18n.t() reads internal signals. When called within a vanilla-signal reactive context, changes to locale or messages will automatically trigger re-computation.
import { createEffect } from 'vanilla-signal';
const i18n = new I18n({
locale: 'en',
messages: {
en: { title: 'Settings' },
zh: { title: '设置' },
},
});
createEffect(() => {
document.title = i18n.t('title');
});
i18n.setLocale('zh');
If you only need to read the locale signal, you can also use getLocaleSignal():
const locale = i18n.getLocaleSignal();
createEffect(() => {
console.log(locale());
});
Appending Language Packs
i18n.addMessages('zh', {
modal: {
title: '提示',
cancel: '取消',
},
});
i18n.addMessages('en', {
modal: {
title: 'Tip',
cancel: 'Cancel',
},
});
addMessages(locale, messages) deeply merges with existing language packs by default. To replace the language pack for a specific locale:
i18n.addMessages('zh', { ok: '确定' }, { merge: false });
To replace or merge complete language packs:
i18n.setMessages(
{
en: { save: 'Save' },
zh: { save: '保存' },
},
{ merge: true }
);
Namespaces
createTranslator(namespace) creates a translation function with a prefix, suitable for use within components.
const modalT = i18n.createTranslator('modal');
modalT('title'); // Equivalent to i18n.t('modal.title')
modalT('confirm');
Missing Translations
By default, missing translations return the original key. You can output warnings via warnMissing or customize the return value via missing.
const i18n = createI18n({
locale: 'zh',
fallbackLocale: 'en',
warnMissing: true,
missing: ({ key }) => `[missing:${key}]`,
});
Subscribing to Changes
subscribe(handler) listens for changes in locale, fallback locale, and messages, suitable for integration with non-signal code.
const unsubscribe = i18n.subscribe((event) => {
console.log(event.type, event.locale);
});
i18n.setLocale('zh');
unsubscribe();
Event types:
| Type | Triggered When |
|---|---|
locale |
Current language changes |
fallback-locale |
Fallback language changes |
messages |
Language packs are set or appended |
Default Instance
defaultI18n is the built-in default instance of the library. Legacy utility functions delegate to it:
import {
setLanguages,
getLanguages,
getLocale,
getLang,
t,
} from 'vanilla-signal-i18n';
setLanguages({
en: { ok: 'OK' },
zh: { ok: '确定' },
});
t('ok'); // Returns text based on current default locale
getLanguages();
getLocale();
getLang();
t() is also compatible with the old signature:
t(
'ok',
{
en: { ok: 'OK' },
zh: { ok: '确定' },
},
'zh'
); // 确定
For new code requiring interpolation, dynamic language switching, or isolated language states for different components, it is recommended to use the i18n.t() method of an independent I18n instance.
Options
| Parameter | Type | Default | Description |
|---|---|---|---|
locale |
string |
Auto | Initial language; reads html lang or browser if empty |
fallbackLocale |
string |
'en' |
Fallback language |
messages |
Record<string, Record<string, any>> |
{} |
Language packs |
languages |
Record<string, Record<string, any>> |
{} |
Alias for messages, for backward compatibility |
missing |
(payload: object) => string |
null |
Handler function for missing translations |
warnMissing |
boolean |
false |
Whether to output warning for missing translations |
Instance Properties
The state of I18n is accessed via instance methods; no public properties are exposed for direct modification. messages are shallow/deep cloned upon writing to prevent direct reuse of external object references.
Instance Methods
| Method | Description |
|---|---|
getLocaleSignal() |
Get the signal accessor for the current locale |
getLocale() |
Get the current locale |
setLocale(locale) |
Set the current locale |
getFallbackLocale() |
Get the fallback locale |
setFallbackLocale(locale) |
Set the fallback locale |
setMessages(messages, options) |
Set complete language packs |
setLanguages(messages) |
Compatible alias for setMessages() |
addMessages(locale, messages, options) |
Append or replace language pack for a specific locale |
getMessages(locale) |
Get language pack for a specific locale; returns all if no locale provided |
getLanguages() |
Compatible alias for getMessages() |
has(key, options) |
Check if a key has an available translation |
resolve(key, options) |
Resolve translation, returning matched locale and original value |
t(key, params, options) |
Get translated text and perform interpolation |
createTranslator(namespace, defaults) |
Create a namespaced translation function |
subscribe(handler) |
Subscribe to i18n state changes, returns unsubscribe function |
destroy() |
Clean up listeners and language packs |
Translation Value Types
Translation values can be strings, numbers, booleans, or functions.
const i18n = createI18n({
locale: 'en',
messages: {
en: {
count: ({ value }) => `${value} items`,
},
},
});
i18n.t('count', { value: 3 }); // 3 items
Functions receive two parameters:
| Parameter | Description |
|---|---|
params |
Interpolation parameters passed when calling t() |
context |
{ key, locale, i18n } |
Locale Rules
Locales are normalized to lowercase, and _ is converted to -:
i18n.setLocale('zh_CN');
i18n.getLocale(); // zh-cn
When looking up translations, it falls back in the order of full locale, base language, fallback locale, and fallback base language. For example, if the current locale is zh-CN and the fallback locale is en-US, the lookup order is:
zh-cn -> zh -> en-us -> en