locales —— l'internationalisation
L’internationalisation de la famille lh-ui est un pur émetteur d’événements : lhlocale ne porte aucune réactivité de framework ; la couche d’adaptation de chaque bibliothèque UI convertit son numéro de version version en le signal de nouveau rendu de son propre framework. Ainsi, un seul paquet de langues peut servir à la fois Vue3 / Vue2 / React / uniapp, tandis que la couche core ne dépend d’aucun framework.
Le singleton et l’import
Section intitulée « Le singleton et l’import »import { lhlocale, zhCN, enUS, formatMessage } from '@lh-ui/share/locales'import type { LhLocalePath, LhLocaleType, LhLocaleMessages } from '@lh-ui/share/locales'La table des membres
Section intitulée « La table des membres »| Membre | Signature | Rôle |
|---|---|---|
version |
get version(): number |
Le numéro de version : s’incrémente à chaque changement de langue (la couche d’adaptation UI s’y abonne pour déclencher le nouveau rendu) |
subscribe |
(listener: LhLocaleListener) => () => void |
S’abonner aux changements de langue ; renvoie une fonction de désabonnement |
use |
(options: { locale: LhLocaleType; messages: LhLocaleMessages }) => void |
Enregistrer et basculer la langue |
t |
(path: LhLocalePath, fallbackOrParams?) => string |
Récupérer le libellé traduit à partir d’un chemin |
tm |
(path: string, fallback?: string[]) => string[] |
Récupérer les libellés sous forme de tableau (noms des mois / des jours) |
getLocale |
() => LhLocaleType |
Obtenir la langue courante |
setLocale |
(locale: LhLocaleType) => void |
Basculer la langue courante |
add |
(locale: string, messages: LhLocaleMessages) => void |
Ajouter un paquet de langue personnalisé (fusion profonde) |
Le comportement détaillé de chaque méthode
Section intitulée « Le comportement détaillé de chaque méthode »t —— l’autocomplétion des clés littérales
Section intitulée « t —— l’autocomplétion des clés littérales »const t = lhlocale.t // propriété en fonction fléchée, la déstructuration ne perd pas le this
t('pagination.total') // '共 {total} 条't('pagination.total', { total: 42 }) // '共 42 条't('not.exists', 'Libellé de repli') // 'Libellé de repli't('not.exists') // 'not.exists' (retombe sur le chemin lui-même)Le type de path est LhLocalePath — l’union de tous les types des chemins des feuilles chaînes, déduite de la structure as const du paquet de langues zh-CN (par exemple 'input.placeholder'). Une erreur de clé est signalée par l’éditeur. L’assemblage dynamique du chemin (t(\pagination.${key}`)) passe par une surcharge permissive et le type se relâche en string`.
Le deuxième paramètre prend deux formes : une chaîne est la valeur de repli ; un objet est le paramètre d’interpolation {key} (le traitement sous-jacent passe par formatMessage).
tm —— les libellés en tableau
Section intitulée « tm —— les libellés en tableau »lhlocale.tm('datepicker.months')// ['1月', '2月', …, '12月']
lhlocale.tm('datepicker.weeks') // ← faux : weeks est un objet, pas un tableaulhlocale.tm('datepicker.weeks.sun', ['日']) // ← faux également : seules les feuilles chaînes d'un objet sont atteignables par t()t() ne renvoie que des chaînes ; les feuilles en tableau (months) sont accessibles via tm(). En cas d’échec ou de non-tableau, c’est le fallback qui est renvoyé ([] par défaut).
use vs add —— deux méthodes faciles à confondre
Section intitulée « use vs add —— deux méthodes faciles à confondre »use |
add |
|
|---|---|---|
| Action | Enregistrer et basculer | Enregistrer seulement (la diffusion n’a lieu que si c’est la langue courante) |
| Stockage des messages | Inscription dans le registre utilisateur userLocales, sans écraser le registre intégré |
idem |
| Mode de fusion | Le paquet entier est installé | Fusion profonde dans « le paquet actuellement en vigueur » |
Pourquoi add fait une fusion profonde : côté métier, on ne veut généralement changer que quelques clés (remplacer « Confirmer » par « Soumettre »), afin que les autres clés restent disponibles depuis le paquet intégré. L’ancienne implémentation remplaçait le paquet entier et toutes les clés oubliées disparaissaient.
Pourquoi use n’écrase pas le registre intégré : l’ancienne implémentation locales[locale] = messages détruisait définitivement le paquet de langues intégré (plus moyen de revenir avec setLocale ensuite). Désormais le paquet intégré est en lecture seule et les paquets utilisateurs passent devant.
La sémantique de « non-décrochage » de setLocale
Section intitulée « La sémantique de « non-décrochage » de setLocale »lhlocale.setLocale('en-US') // le paquet de langues n'a jamais été enregistrélhlocale.getLocale() // 'en-US' ← signale la langue demandéelhlocale.t('pagination.total')// le libellé du paquet par défaut intégré, pas celui de en-USCela paraît contradictoire, mais c’est délibéré : l’ordre de résolution est userLocales ?? locales intégrés ?? paquet par défaut. En l’absence d’enregistrement, getLocale() signale la langue demandée et t() utilise le contenu du paquet par défaut — les deux ne se décrochent pas (pas de combinaison bizarre du type « la langue annoncée est l’anglais, mais les libellés sont en chinois ») ; le côté métier sait alors qu’il doit faire add ou use.
version et subscribe
Section intitulée « version et subscribe »const unsubscribe = lhlocale.subscribe((version: number) => { // la langue a changé, déclencher le nouveau rendu})unsubscribe() // à appeler au démontage du composantsubscribe renvoie une fonction de désabonnement (style RxJS). bump() isole les mauvais abonnés via un try/catch interne et journalise [lhlocale] listener error: : qu’un auditeur d’un composant lève une erreur, cela n’affecte pas les autres composants.
Comment chaque plateforme s’abonne à version
Section intitulée « Comment chaque plateforme s’abonne à version »// Vue3 / Vue2 / uniappconst version = ref(lhlocale.version)lhlocale.subscribe(() => (version.value = lhlocale.version))// le template dépend de version ; tout changement déclenche le nouveau rendu
// ReactuseSyncExternalStore(lhlocale.subscribe, () => lhlocale.version)Les fichiers de langue
Section intitulée « Les fichiers de langue »| Fichier | État |
|---|---|
zh-CN.ts |
✅ Langue par défaut (as const, la source de la dérivation de LhLocalePath) |
en-US.ts |
✅ Activé |
ja-JP.ts / ko-KR.ts / de-DE.ts / fr-FR.ts |
Squelettes de langues réservés, traduction anglaise de repli, basculables directement par setLocale |
Import par sous-chemin : @lh-ui/share/locales/lang/zh-CN.
Les 38 clés racines
Section intitulée « Les 38 clés racines »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
Les six fichiers de langue ont le même nombre de clés racines (38 chacun) et des structures alignées. Parmi elles, ariaLabels est la clé des libellés d’accessibilité (environ 40 entrées) ; chaque composant la lit pour générer ses aria-label.
Les libellés d’interpolation typiques :
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}'Ajouter une nouvelle langue
Section intitulée « Ajouter une nouvelle langue »Trois étapes (la même explication se trouve dans src/locales/lang/README.md) :
- Créer
src/locales/lang/<lang>.tset exporter une structureLhLocaleMessages(les clés racines doivent être alignées sur les 38, vérifiable avecsatisfies). - Ajouter la constante dans
LH_LANGUAGE_CONSTdesrc/constants/index.ts. - La monter dans le registre intégré
localesdesrc/locales/locale.ts.
Ensuite, pnpm test (theme.spec et les cas liés à locale contiennent une régression de cohérence de structure).
Les types associés
Section intitulée « Les types associés »| Type | Définition | Usage |
|---|---|---|
LhLocaleType |
Dérivé de LH_LANGUAGE_CONST |
L’union des codes de langue pris en charge |
LhLocaleMessages |
Record<string, LhLocaleMessageValue> |
Le type global du paquet de langues |
LhLocaleMessageValue |
string | readonly LhLocaleMessageValue[] | { readonly [key: string]: LhLocaleMessageValue } |
Les feuilles du paquet de langues (récursivement en lecture seule) |
LhLeaves<T> |
Dérivation récursive | À partir de la structure du paquet de langues, génère l’union des chemins pointillés de toutes les feuilles chaînes |
LhLocalePath |
LhLeaves<typeof zhCN> |
Le type des clés littérales de t() |
LhTranslate |
Type de surcharge | La signature d’appel de t() |
LhTranslateArray |
(path: string, fallback?: string[]) => string[] |
La signature d’appel de tm() |
LhLocaleListener |
(version: number) => void |
L’auditeur des changements de langue |
LhLocaleOptions |
{ locale: LhLocaleType; messages: LhLocaleMessages } |
Le paramètre d’entrée de use() |
Points d’attention
Section intitulée « Points d’attention »LhLeavesrenvoieneverpour les clés de tableau :datepicker.monthsest un tableau, non adressable par clé ; le chemin pointillé det()ne peut pas l’atteindre — les tableaux passent systématiquement partm().test déstructurable : c’est une propriété en fonction fléchée,const t = lhlocale.tne perd pas le this.- Les composants core n’appellent pas
lhlocaledirectement : rate / pagination traitent l’interpolation avec formatMessage et la recherche des clés de libellés reste à la charge de la couche UI — core ne dépend pas des effets de bord du singleton. addfait une fusion profonde : seules les clés que vous passez sont surchargées, le reste est préservé.
| 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