locales —— Internacionalización
La internacionalización de la familia lh-ui es un emisor de eventos puro: lhlocale no lleva ninguna
reactividad de framework; la capa de adaptación de cada biblioteca UI convierte su número de versión version
en la señal de re-render de su propio framework. De este modo un único paquete de idioma puede servir a la vez a
Vue3 / Vue2 / React / uniapp, mientras la capa de core no depende de ningún framework.
Singleton e importación
Sección titulada «Singleton e importación»import { lhlocale, zhCN, enUS, formatMessage } from '@lh-ui/share/locales'import type { LhLocalePath, LhLocaleType, LhLocaleMessages } from '@lh-ui/share/locales'Tabla de miembros
Sección titulada «Tabla de miembros»| Miembro | Firma | Cometido |
|---|---|---|
version |
get version(): number |
Número de versión: se incrementa con cualquier cambio de idioma (la capa de adaptación UI se suscribe a él para disparar el re-render) |
subscribe |
(listener: LhLocaleListener) => () => void |
Suscripción a cambios de idioma; devuelve una función para cancelar la suscripción |
use |
(options: { locale: LhLocaleType; messages: LhLocaleMessages }) => void |
Registra y cambia de idioma |
t |
(path: LhLocalePath, fallbackOrParams?) => string |
Obtiene el texto traducido a partir de la ruta |
tm |
(path: string, fallback?: string[]) => string[] |
Obtiene textos en array (nombres de mes / nombres de día de la semana) |
getLocale |
() => LhLocaleType |
Obtiene el idioma actual |
setLocale |
(locale: LhLocaleType) => void |
Cambia el idioma actual |
add |
(locale: string, messages: LhLocaleMessages) => void |
Añade un paquete de idioma personalizado (merge profundo) |
Detalles del comportamiento de cada método
Sección titulada «Detalles del comportamiento de cada método»t —— autocompletado de claves literales
Sección titulada «t —— autocompletado de claves literales»const t = lhlocale.t // propiedad con forma de arrow function; la desestructuración no pierde el this
t('pagination.total') // '共 {total} 条't('pagination.total', { total: 42 }) // '共 42 条't('not.exists', 'texto de respaldo') // 'texto de respaldo't('not.exists') // 'not.exists' (retrocede a la propia ruta)El tipo de path es LhLocalePath — el tipo unión de todas las rutas de hoja de string deducidas a partir de
la estructura as const del paquete de idioma zh-CN (por ejemplo 'input.placeholder'). Si se escribe mal el
nombre de la clave, el editor dará error. La composición dinámica de rutas
(t(\pagination.${key}`)) va a una sobrecarga relajada y el tipo se relaja a string`.
El segundo parámetro tiene dos formas: un string es el valor de respaldo; un objeto son los parámetros de
interpolación {key} (por debajo pasa por formatMessage).
tm —— textos en array
Sección titulada «tm —— textos en array»lhlocale.tm('datepicker.months')// ['1月', '2月', …, '12月']
lhlocale.tm('datepicker.weeks') // ← incorrecto: weeks es un objeto, no un arraylhlocale.tm('datepicker.weeks.sun', ['日']) // ← también incorrecto: las hojas de string de un objeto solo son accesibles con t()t() solo devuelve strings; las hojas de array (months) se acceden con tm(). Si no hay coincidencia o no es
un array, devuelve fallback (por defecto []).
use vs add —— dos métodos fáciles de confundir
Sección titulada «use vs add —— dos métodos fáciles de confundir»use |
add |
|
|---|---|---|
| Acción | Registra y cambia | Solo registra (solo difunde si es el idioma actual) |
| Almacenamiento de messages | Se registra en el registro de usuario userLocales, sin sobrescribir el registro integrado |
Ídem |
| Forma de combinar | Se establece el paquete entero | Merge profundo sobre el «paquete actualmente vigente» |
Por qué add es un merge profundo: la parte de la aplicación normalmente solo quiere cambiar unas pocas claves
(cambiar «确认» por «提交»), dejando que el resto siga disponible desde el paquete integrado. La implementación
antigua reemplazaba el paquete entero, de modo que las claves que no se pasaban desaparecían todas.
Por qué use no sobrescribe el registro integrado: la implementación antigua, locales[locale] = messages,
destruía permanentemente el paquete de idioma integrado (un setLocale posterior no podía volver). Ahora los
paquetes integrados son de solo lectura y los del usuario tienen prioridad.
La semántica de «sin desacoplamiento» de setLocale
Sección titulada «La semántica de «sin desacoplamiento» de setLocale»lhlocale.setLocale('en-US') // el paquete de idioma nunca ha sido registradolhlocale.getLocale() // 'en-US' ← informa del idioma solicitadolhlocale.t('pagination.total')// el texto del paquete por defecto integrado, no el de en-USParece contradictorio, pero es deliberado: el orden de resolución es userLocales ?? locales integradas ?? paquete por defecto. Cuando no está registrado, getLocale() informa del idioma solicitado y t() usa el contenido del
paquete por defecto — los dos no se desacoplan (no puede darse la combinación extraña de «el idioma informado
es el inglés, pero los textos son chinos»), de modo que la parte de la aplicación sabe que debe hacer add o use.
version y subscribe
Sección titulada «version y subscribe»const unsubscribe = lhlocale.subscribe((version: number) => { // el idioma ha cambiado; se dispara el re-render})unsubscribe() // se llama al desmontar el componentesubscribe devuelve una función de cancelación (estilo RxJS). bump() aísla internamente, con try/catch, a los
suscriptores defectuosos e imprime [lhlocale] listener error:; que el listener de un componente lance un error
no afecta a los demás.
Cómo se suscribe cada plataforma a version
Sección titulada «Cómo se suscribe cada plataforma a version»// Vue3 / Vue2 / uniappconst version = ref(lhlocale.version)lhlocale.subscribe(() => (version.value = lhlocale.version))// la plantilla depende de version; al cambiar, se re-renderiza
// ReactuseSyncExternalStore(lhlocale.subscribe, () => lhlocale.version)Archivos de idioma
Sección titulada «Archivos de idioma»| Archivo | Estado |
|---|---|
zh-CN.ts |
✅ Idioma por defecto (as const, origen de la deducción de LhLocalePath) |
en-US.ts |
✅ Habilitado |
ja-JP.ts / ko-KR.ts / de-DE.ts / fr-FR.ts |
Esqueletos de idioma reservados, traducción de respaldo al inglés, se puede cambiar directamente con setLocale |
Importación por subruta: @lh-ui/share/locales/lang/zh-CN.
Las 38 claves de nivel superior
Sección titulada «Las 38 claves de nivel superior»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
Los seis archivos de idioma coinciden en el número de claves de nivel superior (38 cada uno) y su estructura está
alineada. De ellos, ariaLabels es la clave de textos de accesibilidad (unas 40 entradas); los componentes la leen
para generar sus aria-label.
Textos de interpolación típicos:
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}'Añadir un nuevo idioma
Sección titulada «Añadir un nuevo idioma»Tres pasos (en src/locales/lang/README.md hay la misma explicación):
- Crea
src/locales/lang/<lang>.tsy exporta la estructuraLhLocaleMessages(las claves de nivel superior deben alinearse con las 38; se puede validar consatisfies). - Añade la constante a
LH_LANGUAGE_CONSTdesrc/constants/index.ts. - Monta el archivo en el registro integrado
localesdesrc/locales/locale.ts.
Después ejecuta pnpm test (theme.spec y los casos relacionados con locale tienen una regresión de
consistencia estructural).
Tipos relacionados
Sección titulada «Tipos relacionados»| Tipo | Definición | Uso |
|---|---|---|
LhLocaleType |
Deducido a partir de LH_LANGUAGE_CONST |
Unión de códigos de idioma soportados |
LhLocaleMessages |
Record<string, LhLocaleMessageValue> |
Tipo del paquete de idioma en su conjunto |
LhLocaleMessageValue |
string | readonly LhLocaleMessageValue[] | { readonly [key: string]: LhLocaleMessageValue } |
Hojas del paquete de idioma (recursivamente de solo lectura) |
LhLeaves<T> |
Deducción recursiva | A partir de la estructura del paquete de idioma deduce la unión de rutas de puntos de todas las hojas de string |
LhLocalePath |
LhLeaves<typeof zhCN> |
El tipo de la clave literal de t() |
LhTranslate |
Tipo con sobrecargas | La firma de llamada de t() |
LhTranslateArray |
(path: string, fallback?: string[]) => string[] |
La firma de llamada de tm() |
LhLocaleListener |
(version: number) => void |
Listener de cambios de idioma |
LhLocaleOptions |
{ locale: LhLocaleType; messages: LhLocaleMessages } |
El parámetro de entrada de use() |
LhLeavesdevuelveneverpara las claves de array:datepicker.monthses un array, no se aborda por clave, y la ruta de puntos det()no puede alcanzarlo — los arrays se manejan siempre contm().tse puede desestructurar: es una propiedad con forma de arrow function;const t = lhlocale.tno pierde el this.- Los componentes de core no llaman directamente a
lhlocale: rate / pagination procesan la interpolación con formatMessage, y la búsqueda de las claves de texto se completa en la capa UI — core no depende de los side effects del singleton. addes un merge profundo: solo sobrescribe las claves que pasas; el resto se mantiene.
| 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