Интернационализация (i18n)
PreffX использует единый корневой сигнал языка, который управляет всеми переводами. Язык хранится в атрибуте <html lang="..."> и синхронизирован с сигналом lang, поэтому его изменение автоматически обновляет все словари.
Чтение языка
Утилита lang — это доступный только для чтения сигнал, хранящий текущий код языка:
import type { PC } from 'preffx';
export const CurrentLang: PC = (_props, { lang }) => {
return <span>Current language: {lang}</span>;
};Внутри блоков computed значение отслеживается реактивно:
import type { PC } from 'preffx';
export const Caption: PC = (_props, { computed, lang }) => {
const greeting = computed(() =>
lang.value === 'ru' ? 'Привет' : 'Hello'
);
return <p>{greeting}</p>;
};Переключение языка
Вызовите setLang, чтобы изменить язык. Он записывает значение в атрибут <html lang> и, в свою очередь, обновляет сигнал lang, используемый всеми словарями.
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>
);
};Простые словари
Передайте обычный объект, ключи которого — языки, а затем выберите активный через 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>
);
};Ленивые словари с dict
Когда локаль велика или загружается асинхронно, используйте утилиту dict. Она принимает карту «код языка → резолвер» (синхронный или асинхронный) и возвращает прокси, поля которого являются реактивными сигналами. Обращайтесь к полю как к значению или вызывайте его как функцию.
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)— первый аргумент:Record<lang, () => Dict | Promise<Dict>>.dict(resolvers, initial)— второй необязательный аргумент предоставляет исходный объект, используемый до того, как резолвер выполнится.- Резолвер для текущего
langавтоматически выполняется повторно при смене языка. - Поля-функции становятся вызываемыми и возвращают вычисляемые сигналы:
captions.openProfile('42')реактивен. - Используйте
.peek()на поле, чтобы прочитать его без реактивного отслеживания.
Установка начального языка
Начальный lang считывается из атрибута <html lang> во время createRoot, либо может быть задан явно:
import { createRoot } from 'preffx';
createRoot({ defaultLang: 'en' }).mount(App);Сводка утилит
| Опция | Тип | Описание |
|---|---|---|
defaultLang | string | Язык по умолчанию, когда ни один не задан/объявлен |
setLang | (value) => void | Переключение текущего языка |
lang | ReadonlySignal | Текущий язык (реактивный) |
dict | (...) => DictProxy | Реактивные поязыковые словари |