Preact I18n
Developer friendly full stack localization for preact apps.
This is a proof of concept and things might break or change at any time.
Getting Started
npm i -D @mpt/preact-i18n
Configuration
The configuration for a package is stored in a json5 file i18n.json5
{
// Translation data storage path:
projectData: "./i18n-data.json",
// The namespace for this package.
// It is recommended to use the npm package name as namespace.
namespace: "~",
// An array of globs whre to find jsx/tsx source files.
sources: [
"src/**"
],
// The output path for compiled language resources:
// "[lang]" is replaced with the language tag.
output: "dist/lang/[lang].json",
// The language, sources are written in:
sourceLanguage: "en",
// An array of languages this package is translated to.
// Default is an empty array.
languages: ["de", "ch"]
}
Runtime Setup
It is recommended to move the runtime configuration to a separate module:
import { I18n, Language, languageFactory } from "@mpt/preact-i18n";
export const i18n = new I18n({
// A set of clients that are used to fetch language resources:
clients: [
new FetchClient()
],
// Include the default language factory to
// support pluralization and interpolation:
languageFactory,
// When enabled, the document root "lang" attribute
// will be set to the current language name.
setLangAttribute: true,
// The following options should match your configuration:
namespace: "~",
sourceLanguage: "en"
});
// Export text fragment components:
export const T = i18n.T;
export const TX = i18n.TX;
Usage
import { h, render } from "preact";
import { Language } from "@mpt/preact-i18n";
import { i18n, T } from "./i18n";
// Always set the language before rendering anything:
// Otherwise all text fragments will be empty.
await i18n.setLanguageAuto(["en", "de", "ch"], "en");
render(
<Language.Provider use={i18n}>
<T value="Hello World!" id="0"/>
</Language.Provider>,
document.body
);
Text Components
There are two types of text components. <T>
for simple text and <TX>
for more complex things.
<T value="Hello World!" />
// Hello World!
Pluralization
<TX value={["Apple", "Apples"]} count={3} />
// Apples
<TX value={["Apple", "Apples"]} count={1} />
// Apple
<TX value={["{count} apple", "{count} apples"]} count={7} />
// 7 apples
The number of forms depends on the language. You can lookup the number in plurals.json5.
Interpolation
<TX value="Hello {name}!" fields={{ name: "World" }} />
// Hello World!
{
and }
Escaping Escaping is not supported, but you can add two simple fields for that purpose if needed:
<TX value="class {name} {<} ... {>}" fields={{ "<": "{", ">": "}", name: "Example" }} />
// class Example { ... }
Formatting
Formatters are functions that are used by text components to format interpolated values.
new I18n({
formatters: new Map([
[Date.prototype, (value: any, language: Language, format?: string) => {
return value.toLocaleString(language.name);
}],
["hex", (value: number | bigint) => {
return value.toString(16);
}]
])
});
<TX value="The current date is {now}" fields={{ now: new Date() }} />
// The current date is 7/19/2020, 6:15:33 PM
<TX value="The memory address is {address,hex}" fields={{ address: 0xE2740C980AC100B0n }} />
// The memory address is E2740C980AC100B0
Formatters are selected as follows:
- If a formatter name is specified (e.g.
hex
), that formatter is used. - If the value is an object, the formatter is selected based on the prototype chain.
- If the primitive type is not
"string"
, the formatter is selected based on the primitive type. - Else, the value is converted using
String(value)
Namespacing & Context
When writing a package with components, you don't want your translations to collide with others.
To prevent that, you can create an I18nContext
that provides text fragment components that automatically look up translations using a different namespace:
import { createContext } from "@mpt/preact-i18n";
const { T, TX } = createContext({
// The following options should match your configuration:
namespace: "~",
sourceLanguage: "en",
// Additional formatters that are only available to this context:
formatters: ...
});
export { T, TX };
Advanced Topics
Language context
The language context is used to pass the current language instance to text components.
This can be used to set the lang
attribute on the root element of your component if the component is beeing used in an application over which you have no control.
import { Language } from "@mpt/preact-i18n";
<Language.Consumer>{language => {
return <div lang={language?.name}>
...
</div>;
}</Language.Consumer>
Update handlers
Update handlers can be used to detect when the current language has been changed.
This is used by the I18n
class internally if setLangAttribute
is true:
i18n.addUpdateHandler(() => {
if (i18n.language) {
document.documentElement.lang = i18n.language.name;
}
});
Translation Workflow
Command Line
# Start translation workflow:
preact-i18n start
# Run diagnostics and compile translations:
preact-i18n compile
Writing Translations
Translations are written by editing the project's translation data file (i18n-data.json
)
It is recommended to use a specialized editor for writing translations like this vscode extension.