i18n
Show your script's text in the device language.
onescript/i18n picks one of your languages based on system.locale.languages (the device's language preferences), and provides message lookup, interpolation, plurals, and date and number formatting.
Quick start
Add a JSON file for each language to your project:
src/
app.tsx
locales/
en.json
zh-Hans.jsonlocales/en.json:
{
"greeting": "Hello, {name}!",
"cart": {
"items": { "one": "{count} item", "other": "{count} items" }
}
}locales/zh-Hans.json:
{
"greeting": "你好,{name}!",
"cart": {
"items": { "other": "{count} 件商品" }
}
}src/app.tsx:
import { createI18n } from "onescript/i18n"
import en from "./locales/en.json"
import zhHans from "./locales/zh-Hans.json"
const i18n = createI18n({
fallback: "en",
messages: { en, "zh-Hans": zhHans },
})
// When the device language is English:
i18n.locale // "en"
i18n.t("greeting", { name: "Ada" }) // "Hello, Ada!"
i18n.t("cart.items", { count: 1 }) // "1 item"
i18n.t("cart.items", { count: 3 }) // "3 items"
// When the device language is Simplified Chinese:
i18n.locale // "zh-Hans"
i18n.t("cart.items", { count: 3 }) // "3 件商品"Messages
- Messages are nested objects, and keys are dot paths such as
"cart.items". Keys are type-checked against thefallbacklanguage, so a typo is reported in the editor. {name}is replaced with the value you pass. Numbers are formatted for the message language, for example1234becomes1,234in English. A missing value throws an error.- An object with an
otherkey is a plural message and needs acount. The branch is chosen withIntl.PluralRules(zero,one,two,few,many,other); if that branch is missing,otheris used. - Other languages may leave out some messages. A missing message uses the
fallbacklanguage and logs a warning once per key.
Language matching
createI18n checks system.locale.languages in order and uses the first language that matches. If none matches, it uses fallback.
- Regions don't need to match:
en-GBmatchesen, andpt-BRmatchespt. - Chinese is matched by script:
zh-CNmatcheszh-Hans, andzh-TWandzh-HKmatchzh-Hant. - Different scripts never match: if only
zh-Hansis available, a Traditional Chinese user moves on to their next preferred language instead of seeing Simplified Chinese.
With en, zh-Hans, and zh-Hant messages and fallback: "en":
system.locale.languages | i18n.locale |
|---|---|
["zh-CN"] | "zh-Hans" |
["zh-HK"] | "zh-Hant" |
["en-GB"] | "en" |
["ja-JP", "zh-TW"] | "zh-Hant" (no Japanese, so the next preference is used) |
["ja-JP"] | "en" (nothing matches, so fallback is used) |
To ignore the device language, set locale:
const i18n = createI18n({
fallback: "en",
locale: "zh-Hans",
messages: { en, "zh-Hans": zhHans },
})
i18n.locale // always "zh-Hans"Dates and numbers
i18n.date(value, options) and i18n.number(value, options) format with the same language as your messages. options are the same as for Intl.DateTimeFormat and Intl.NumberFormat.
const date = new Date(2026, 9, 7)
// When i18n.locale is "en":
i18n.date(date, { dateStyle: "long" }) // "October 7, 2026"
i18n.number(1234.5) // "1,234.5"
i18n.number(9.5, { style: "currency", currency: "USD" }) // "$9.50"
i18n.number(0.25, { style: "percent" }) // "25%"
i18n.number(12500, { notation: "compact" }) // "13K"
// When i18n.locale is "zh-Hans":
i18n.date(date, { dateStyle: "long" }) // "2026年10月7日"
i18n.number(12500, { notation: "compact" }) // "1.3万"API
createI18n(options):
| Option | Type | Description |
|---|---|---|
fallback | string | The language with the most complete messages. Used when another language is missing a message; keys are typed from it. |
messages | Record<string, object> | Messages for each language, keyed by language tag such as "en" or "zh-Hans". |
locale | string | Optional. Use this language instead of the device language. |
Return value:
| Member | Description |
|---|---|
locale | The matched language. |
t(key, values?) | Returns a message. values are used for interpolation and plurals. |
date(value, options?) | Formats a date for locale. |
number(value, options?) | Formats a number for locale. |
matchLocale(requested, available, fallback) is also exported to apply the same matching rules on its own. For example, matchLocale(["zh-TW"], ["en", "zh-Hant"], "en") returns "zh-Hant".
Using other libraries
For ICU MessageFormat and other advanced features, install a library such as i18next and choose the language with system.locale.languages:
import i18next from "i18next"
import { system } from "onescript"
await i18next.init({
lng: system.locale.languages[0],
fallbackLng: "en",
resources: {
en: { translation: { greeting: "Hello, {{name}}!" } },
},
})How is this guide?
Last updated on