locales —— интернационализация
Интернационализация в семействе lh-ui — это чистый генератор событий: lhlocale не несёт никакой реактивности фреймворка, а адаптеры UI-библиотек превращают его номер версии version в сигнал перерендера своего фреймворка. Так один языковой пакет может одновременно обслуживать Vue3 / Vue2 / React / uniapp, а слой core не зависит ни от одного фреймворка.
Синглтон и импорт
Заголовок раздела «Синглтон и импорт»import { lhlocale, zhCN, enUS, formatMessage } from '@lh-ui/share/locales'import type { LhLocalePath, LhLocaleType, LhLocaleMessages } from '@lh-ui/share/locales'Таблица членов
Заголовок раздела «Таблица членов»| Член | Сигнатура | Назначение |
|---|---|---|
version |
get version(): number |
Номер версии: увеличивается при любом изменении языка (адаптер UI подписывается на него и запускает перерендер) |
subscribe |
(listener: LhLocaleListener) => () => void |
Подписка на изменения языка; возвращает функцию отписки |
use |
(options: { locale: LhLocaleType; messages: LhLocaleMessages }) => void |
Регистрирует язык и переключается на него |
t |
(path: LhLocalePath, fallbackOrParams?) => string |
Получает переведённый текст по пути |
tm |
(path: string, fallback?: string[]) => string[] |
Получает тексты в виде массива (названия месяцев / дней недели) |
getLocale |
() => LhLocaleType |
Возвращает текущий язык |
setLocale |
(locale: LhLocaleType) => void |
Переключает текущий язык |
add |
(locale: string, messages: LhLocaleMessages) => void |
Добавляет пользовательский языковой пакет (глубокое слияние) |
Подробности поведения методов
Заголовок раздела «Подробности поведения методов»t —— автодополнение литеральных ключей
Заголовок раздела «t —— автодополнение литеральных ключей»const t = lhlocale.t // свойство-стрелочная функция: при деструктуризации this не теряется
t('pagination.total') // '共 {total} 条't('pagination.total', { total: 42 }) // '共 42 条't('not.exists', 'резервный текст') // 'резервный текст't('not.exists') // 'not.exists' (возврат к самому пути)Тип path — LhLocalePath: объединение всех строковых путей до листьев, выведенное из структуры as const языкового пакета zh-CN (например, 'input.placeholder'). При опечатке в имени ключа редактор сообщит об ошибке. Динамическое построение пути (t(\pagination.${key}`)) уходит в перегрузку послабее, где тип сужается до string`.
Второй параметр принимает две формы: строка — fallback-значение; объект — параметры интерполяции {key} (под капотом — formatMessage).
tm —— тексты в виде массива
Заголовок раздела «tm —— тексты в виде массива»lhlocale.tm('datepicker.months')// ['1月', '2月', …, '12月']
lhlocale.tm('datepicker.weeks') // ← неверно: weeks — объект, а не массивlhlocale.tm('datepicker.weeks.sun', ['日']) // ← тоже неверно: до строковых листьев объекта добирается только t()t() возвращает только строки; доступ к листам-массивам (months) осуществляется через tm(). При промахе или не-массиве возвращается fallback (по умолчанию []).
use против add —— два легко путаемых метода
Заголовок раздела «use против add —— два легко путаемых метода»use |
add |
|
|---|---|---|
| Действие | Регистрирует и переключается | Только регистрирует (рассылка происходит лишь при переключении на этот язык) |
| Хранение messages | Записывается в пользовательский реестр userLocales, не перезаписывает встроенный реестр |
То же |
| Способ слияния | Устанавливается весь пакет | Глубокое слияние с «текущим активным пакетом» |
Почему add делает глубокое слияние: на стороне бизнеса обычно хотят изменить лишь несколько ключей (заменить «确认» на «提交»), а остальные ключи должны остаться доступными из встроенного пакета. Прежняя реализация заменяла пакет целиком, и забытые при передаче ключи просто исчезали.
Почему use не перезаписывает встроенный реестр: прежняя реализация locales[locale] = messages навсегда уничтожала встроенный языковой пакет (вернуться через последующий setLocale было уже нельзя). Теперь встроенные пакеты доступны только для чтения, а пользовательские имеют приоритет.
Семантика «без рассинхронизации» в setLocale
Заголовок раздела «Семантика «без рассинхронизации» в setLocale»lhlocale.setLocale('en-US') // языковой пакет не был зарегистрированlhlocale.getLocale() // 'en-US' ← сообщается запрошенный языкlhlocale.t('pagination.total')// текст из встроенного пакета по умолчанию, а не из en-USВыглядит противоречием, но это сделано осознанно: порядок разрешения — userLocales ?? встроенный locales ?? пакет по умолчанию. При отсутствии регистрации getLocale() сообщает запрошенный язык, а t() берёт содержимое пакета по умолчанию — оба значения не рассинхронизируются (не возникает странного сочетания «сообщается английский, а текст — китайский»), и сторона бизнеса понимает, что нужно вызвать add или use.
version и subscribe
Заголовок раздела «version и subscribe»const unsubscribe = lhlocale.subscribe((version: number) => { // язык изменился — запускаем перерендер})unsubscribe() // вызывается при размонтировании компонентаsubscribe возвращает функцию отписки (стиль RxJS). Внутренний bump() через try/catch изолирует плохих подписчиков и выводит [lhlocale] listener error: — если слушатель одного компонента выбросит исключение, другие компоненты это не затронет.
Как каждая платформа подписывается на version
Заголовок раздела «Как каждая платформа подписывается на version»// Vue3 / Vue2 / uniappconst version = ref(lhlocale.version)lhlocale.subscribe(() => (version.value = lhlocale.version))// В шаблоне зависим от version: при изменении происходит перерендер
// ReactuseSyncExternalStore(lhlocale.subscribe, () => lhlocale.version)Языковые файлы
Заголовок раздела «Языковые файлы»| Файл | Статус |
|---|---|
zh-CN.ts |
✅ Язык по умолчанию (as const, источник вывода LhLocalePath) |
en-US.ts |
✅ Включён |
ja-JP.ts / ko-KR.ts / de-DE.ts / fr-FR.ts |
Зарезервированные скелеты языков, fallback-перевод на английский; переключаться через setLocale можно сразу |
Импорт по подпути: @lh-ui/share/locales/lang/zh-CN.
38 ключей верхнего уровня
Заголовок раздела «38 ключей верхнего уровня»common / input / datepicker / timepicker / cascader / tag / badge / select / form / switch / slider / progress / avatar / descriptions / empty / alert / dialog / message / breadcrumb / pagination / tabs / steps / popconfirm / drawer / notification / loading / collapse / rate / backtop / image / menu / table / tree / treeSelect / transfer / upload / anchor / ariaLabels
Во всех шести языковых файлах число ключей верхнего уровня одинаково (по 38), структуры выровнены. Среди них ariaLabels — ключи текстов доступности (около 40 штук); каждый компонент читает их и генерирует aria-label.
Типичные тексты с интерполяцией:
pagination.total: '共 {total} 条'pagination.sizeChanger: '{size} 条/页'pagination.page: '第 {page} 页'steps.step: '第 {index} 步'rate.stars: '{n} 星'cascader.columnLabel: '第 {n} 级'collapse.toggle: '展开或折叠 {title}'datepicker.panelTitle: '{year}年{month}'menu.submenuLabel: '子菜单 {title}'Добавление нового языка
Заголовок раздела «Добавление нового языка»Три шага (те же инструкции — в src/locales/lang/README.md):
- Создать
src/locales/lang/<lang>.ts, экспортирующий структуруLhLocaleMessages(ключи верхнего уровня должны быть выровнены по 38 штук; проверить можно черезsatisfies). - Добавить константу в
LH_LANGUAGE_CONSTвsrc/constants/index.ts. - Смонтировать файл во встроенном реестре
localesвsrc/locales/locale.ts.
Затем запустить pnpm test (в theme.spec и кейсах, связанных с locale, есть регрессия структурной согласованности).
Связанные типы
Заголовок раздела «Связанные типы»| Тип | Определение | Назначение |
|---|---|---|
LhLocaleType |
Выводится из LH_LANGUAGE_CONST |
Объединение поддерживаемых кодов языков |
LhLocaleMessages |
Record<string, LhLocaleMessageValue> |
Тип языкового пакета целиком |
LhLocaleMessageValue |
string | readonly LhLocaleMessageValue[] | { readonly [key: string]: LhLocaleMessageValue } |
Листовые узлы языкового пакета (рекурсивно только для чтения) |
LhLeaves<T> |
Рекурсивный вывод | Объединение точечных путей всех строковых листьев, выводимое из структуры языкового пакета |
LhLocalePath |
LhLeaves<typeof zhCN> |
Тип литерального ключа для t() |
LhTranslate |
Тип с перегрузками | Сигнатура вызова t() |
LhTranslateArray |
(path: string, fallback?: string[]) => string[] |
Сигнатура вызова tm() |
LhLocaleListener |
(version: number) => void |
Слушатель изменения языка |
LhLocaleOptions |
{ locale: LhLocaleType; messages: LhLocaleMessages } |
Аргумент use() |
Примечания
Заголовок раздела «Примечания»LhLeavesвозвращаетneverдля ключей-массивов:datepicker.months— массив, он не адресуется по ключам, и точечный путьt()по нему не попадёт — для массивов всегда используетсяtm().tможно деструктурировать: это свойство-стрелочная функция, поэтомуconst t = lhlocale.tне теряет this.- Компоненты core не вызывают
lhlocaleнапрямую: rate / pagination обрабатывают интерполяцию через formatMessage, а поиск текстовых ключей по-прежнему выполняется на стороне UI — core не зависит от побочных эффектов синглтона. addделает глубокое слияние: перекрываются только переданные вами ключи, остальные сохраняются.
| API | 实际调用 | 返回值 |
|---|---|---|
lhlocale.t(path) | — | — |
lhlocale.t(path, params) | — | — |
lhlocale.tm(path) | — | — |
lhlocale.getLocale() | () | — |
lhlocale.version | (任何语言变更自增) | — |
pagination.total 这类带 {total} 占位符的文案,第二参传对象即做插值;datepicker.months 是数组叶子,只能经 tm() 取(t() 非字符串会回落 path 本身); 切到 ja-JP 等骨架语言时,缺失键由英文兜底,getLocale() 仍如实报告 'ja-JP'。
Copyright © 2024-2026 FengMo WenXue