Internationalization (i18n)
PreffX uses a single root-level language signal that drives all translations. The language is stored in the <html lang="..."> attribute and synchronized with the lang signal, so changing it updates every dictionary automatically.
Reading the language
The lang utility is a read-only signal holding the current language code:
import type { PC } from 'preffx';
export const CurrentLang: PC = (_props, { lang }) => {
return <span>Current language: {lang}</span>;
};Inside computed blocks the value is tracked reactively:
import type { PC } from 'preffx';
export const Caption: PC = (_props, { computed, lang }) => {
const greeting = computed(() =>
lang.value === 'ru' ? 'Привет' : 'Hello'
);
return <p>{greeting}</p>;
};Switching the language
Call setLang to change the language. It writes to the <html lang> attribute and, in turn, updates the lang signal used by all dictionaries.
import type { PC } from 'preffx';
export const LangSwitch: PC = (_props, { setLang }) => {
return (
<div>
<button onClick={() => setLang('en')}>EN</button>
<button onClick={() => setLang('ru')}>RU</button>
</div>
);
};Simple dictionaries
Pass a plain object keyed by language, then select the active one through computed:
import type { PC } from 'preffx';
const dictionary = {
en: { openProfile: 'Open profile', showStats: 'Show statistics' },
ru: { openProfile: 'Открыть профиль', showStats: 'Показать статистику' }
};
export const App: PC = (_props, { computed, lang }) => {
const captions = computed(() => dictionary[lang.value] || dictionary.en);
return (
<div>
<button>{captions.openProfile}</button>
<button>{captions.showStats}</button>
</div>
);
};Lazy dictionaries with dict
When a locale is large or loads asynchronously, use the dict utility. It takes a map of language code → resolver (sync or async) and returns a proxy whose fields are reactive signals. Access a field as a value or call it as a function.
import type { APC } from 'preffx';
const getRuDictionary = async () => {
return {
openProfile(name: string) {
return 'Открыть профиль № ' + name;
},
showStats: 'Показать статистику'
};
};
const getEnDictionary = async () => {
return {
openProfile(name: string) {
return 'Open profile № ' + name;
},
showStats: 'Show stats'
};
};
export const App: APC = async (_props, { dict, setLang }) => {
const initialDict = await getEnDictionary();
const captions = dict({ ru: getRuDictionary, en: getEnDictionary }, initialDict);
return (
<div>
<button onClick={() => setLang('en')}>EN</button>
<button onClick={() => setLang('ru')}>RU</button>
<span>
<button>{captions.openProfile('42')}</button>
<button>{captions.showStats}</button>
</span>
</div>
);
};dict(resolvers)— first argument:Record<lang, () => Dict | Promise<Dict>>.dict(resolvers, initial)— second optional argument provides the initial object used before a resolver resolves.- The resolver for the current
langis re-run automatically when the language changes. - Function fields become callables that return computed signals:
captions.openProfile('42')is reactive. - Access
.peek()on a field to read it without reactive tracking.
Setting the initial language
The initial lang is read from the <html lang> attribute at createRoot time, or can be set explicitly:
import { createRoot } from 'preffx';
createRoot({ defaultLang: 'en' }).mount(App);Utils summary
| Option | Type | Description |
|---|---|---|
defaultLang | string | Fallback language when none is set/declared |
setLang | (value) => void | Switch the current language |
lang | ReadonlySignal | Current language (reactive) |
dict | (...) => DictProxy | Reactive, per-language dictionaries |