@liujitcn/kratos-taro-app-core 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,113 +1,108 @@
1
1
  {
2
2
  "common.action.back": "Retour",
3
3
  "common.action.cancel": "Annuler",
4
- "common.action.confirm": "OK",
4
+ "common.action.confirm": "Confirmer",
5
5
  "common.action.delete": "Supprimer",
6
- "common.action.login": "Connexion",
6
+ "common.action.login": "Se connecter",
7
7
  "common.action.logout": "Se déconnecter",
8
8
  "common.action.new": "Nouveau",
9
- "common.action.openInNewWindow": "Une nouvelle fenêtre s'ouvre",
10
- "common.action.retry": "Réessayez",
9
+ "common.action.open_in_new_window": "Ouvrir dans une nouvelle fenêtre",
11
10
  "common.action.save": "Enregistrer",
12
- "common.action.search": "Rechercher",
13
11
  "common.field.language": "Langue",
14
12
  "common.language.en-US": "Anglais",
15
13
  "common.language.ja-JP": "Japonais",
16
14
  "common.language.zh-CN": "Chinois simplifié",
17
15
  "common.language.zh-TW": "Chinois traditionnel",
18
- "common.language.ko-KR": "coréen",
16
+ "common.language.ko-KR": "Coréen",
19
17
  "common.language.fr-FR": "Français",
20
- "common.language.es-ES": "espagnol",
21
- "common.message.loading": "est en cours de chargement",
22
- "common.message.networkError": "Erreur réseau, essayez un autre réseau",
23
- "common.message.requestError": "Erreur de demande",
24
- "common.message.unknown": "Traitement impossible pour le moment, veuillez réessayer plus tard",
25
- "common.title.notice": "Conseils",
26
- "core.auth.loginAgain": "Connectez-vous à nouveau",
27
- "core.auth.sessionExpired": "La page actuelle n'est pas valide, veuillez vous reconnecter",
28
- "core.config.missing": "La configuration du terminal mobile est manquante : {names}",
29
- "core.crypto.base64DecodeUnsupported": "L'environnement actuel ne prend pas en charge le décodage Base64",
30
- "core.crypto.base64EncodeUnsupported": "L'environnement actuel ne prend pas en charge l'encodage Base64",
31
- "core.crypto.encryptFailed": "Échec du cryptage du mot de passe",
32
- "core.crypto.passwordRequired": "Le mot de passe ne peut pas être vide",
33
- "core.crypto.passwordTooLong": "La longueur du mot de passe dépasse la limite",
34
- "core.crypto.publicKeyInvalid": "Le format de la clé publique n'est pas valide",
35
- "core.crypto.randomFailed": "Échec de la génération de nombres aléatoires",
36
- "core.crypto.unsupported": "L'environnement actuel ne prend pas en charge le cryptage des mots de passe",
37
- "core.file.downloadFailed": "Échec du téléchargement",
38
- "core.file.temporaryPathRequired": "Veuillez utiliser le chemin du fichier temporaire pour télécharger le mini-programme.",
39
- "core.file.uploadFailed": "Échec du téléchargement",
40
- "core.home.accountCapability": "Capacités du compte",
41
- "core.home.accountCapabilityDescription": "Les informations personnelles, les paramètres de l'application et l'accès à l'assistant intelligent ont été réservés",
42
- "core.home.crossPlatform": "Page croisée",
43
- "core.home.crossPlatformDescription": "Le même ensemble de codes de page s'adapte aux mini programmes H5 et WeChat",
44
- "core.home.demoScope": "Portée de la démonstration",
45
- "core.home.framework": "Cadre d'application",
46
- "core.home.language": "Langage de développement",
47
- "core.home.mainTitle": "Exemple de framework d'application",
48
- "core.home.stateManagement": "Gestion des statuts",
49
- "core.home.staticHome": "Page d'accueil statique",
50
- "core.home.staticHomeDescription": "La page d'accueil actuelle est utilisée pour afficher les informations du cadre et ne repose pas sur des interfaces métier\nSchéma de style",
51
- "core.home.styleSolution": "Styling",
52
- "core.home.subTitle": "Préserver la navigation universelle et l'expérience du centre personnel",
53
- "core.home.techStack": "Pile technologique actuelle",
54
- "core.login.agreePrefix": "J'ai lu et j'accepte",
55
- "core.login.agreeSeparator": "et",
56
- "core.login.behaviorClick": "Veuillez cliquer sur le texte dans l'ordre",
57
- "core.login.behaviorLoading": "Chargement...",
58
- "core.login.behaviorPuzzle": "Veuillez faire glisser le curseur pour terminer le puzzle",
59
- "core.login.behaviorRotate": "Faites glisser le curseur pour corriger l'image du cercle intérieur",
60
- "core.login.bindWechat": "Connectez-vous et liez WeChat",
61
- "core.login.captcha": "Veuillez saisir le code de vérification",
62
- "core.login.captchaLoadFailed": "Échec du chargement du code de vérification",
63
- "core.login.captchaTypeMissing": "Le type de code de vérification de connexion n'est pas configuré",
64
- "core.login.captchaTypeName": "Type de code de vérification de connexion",
65
- "core.login.captchaTypeResponseMissing": "L'interface du code de vérification ne renvoie pas le type",
66
- "core.login.captchaWriteFailed": "Échec de l'écriture de l'image du code de vérification",
67
- "core.login.defaultSubTitle": "Bienvenue sur cette application",
68
- "core.login.loginFailed": "Échec de la connexion, veuillez réessayer",
69
- "core.login.loginSuccess": "Connexion réussie",
70
- "core.login.password": "Veuillez saisir le mot de passe",
71
- "core.login.privacy": "\"Accord de confidentialité\"",
72
- "core.login.protocolMissing": "Les conditions d'utilisation et l'accord de confidentialité ne sont pas configurés",
73
- "core.login.protocolPrompt": "Veuillez d'abord lire et vérifier le contenu de l'accord. Après avoir cliqué sur OK, vous vérifierez automatiquement et continuerez à vous connecter.",
74
- "core.login.service": "\"Conditions d'utilisation\"",
75
- "core.login.tenant": "Veuillez saisir le numéro de locataire",
76
- "core.login.userName": "Veuillez saisir votre nom d'utilisateur ou votre numéro de téléphone portable",
77
- "core.login.userNameMobile": "Veuillez saisir votre nom d'utilisateur/numéro de téléphone portable",
78
- "core.login.userNamePassword": "Veuillez saisir votre nom d'utilisateur et votre mot de passe",
79
- "core.login.wechat": "Connexion WeChat en un clic",
80
- "core.login.wechatBindTip": "Ce compte WeChat n'a pas encore été lié. Veuillez vous connecter à votre compte existant pour terminer la liaison.",
81
- "core.login.wechatFailed": "La connexion à WeChat a échoué, veuillez réessayer plus tard.",
18
+ "common.language.es-ES": "Espagnol",
19
+ "common.message.loading": "Chargement",
20
+ "common.message.network_error": "Erreur réseau. Vérifiez votre connexion et réessayez.",
21
+ "common.message.request_error": "Échec de la requête",
22
+ "common.message.unknown": "Impossible de traiter cette demande. Réessayez plus tard.",
23
+ "common.title.notice": "Avis",
24
+ "core.auth.login_again": "Se reconnecter",
25
+ "core.auth.session_expired": "Votre session a expiré. Veuillez vous reconnecter.",
26
+ "core.config.missing": "Configuration de l’application manquante : {names}",
27
+ "core.crypto.base64_decode_unsupported": "Le décodage Base64 n’est pas pris en charge dans cet environnement",
28
+ "core.crypto.base64_encode_unsupported": "L’encodage Base64 n’est pas pris en charge dans cet environnement",
29
+ "core.crypto.encrypt_failed": "Échec du chiffrement du mot de passe",
30
+ "core.crypto.password_required": "Le mot de passe est obligatoire",
31
+ "core.crypto.password_too_long": "Le mot de passe dépasse la longueur autorisée",
32
+ "core.crypto.public_key_invalid": "Clé publique non valide",
33
+ "core.file.download_failed": "Échec du téléchargement",
34
+ "core.file.temporary_path_required": "Utilisez un chemin de fichier temporaire pour téléverser depuis un mini-programme",
35
+ "core.file.upload_failed": "Échec de l’envoi",
36
+ "core.home.account_capability": "Fonctionnalités du compte",
37
+ "core.home.account_capability_description": "Le profil, les paramètres de l’application et l’accès à l’assistant IA sont inclus",
38
+ "core.home.cross_platform": "Pages multiplateformes",
39
+ "core.home.cross_platform_description": "Une seule base de code pour H5 et le mini-programme WeChat",
40
+ "core.home.demo_scope": "Périmètre de démonstration",
41
+ "core.home.framework": "Framework de l’application",
42
+ "core.home.language": "Langage",
43
+ "core.home.main_title": "Exemple de framework d’application",
44
+ "core.home.state_management": "Gestion de l’état",
45
+ "core.home.static_home": "Accueil statique",
46
+ "core.home.static_home_description": "Cette page présente le framework sans utiliser d’API métier",
47
+ "core.home.style_solution": "Styles",
48
+ "core.home.sub_title": "Navigation partagée et expérience de compte",
49
+ "core.home.tech_stack": "Stack technique",
50
+ "core.login.agree_prefix": "J’ai lu et j’accepte",
51
+ "core.login.agree_separator": "et",
52
+ "core.login.behavior_click": "Cliquez sur les caractères dans l'ordre",
53
+ "core.login.behavior_loading": "Chargement...",
54
+ "core.login.behavior_puzzle": "Faites glisser le curseur pour terminer le puzzle",
55
+ "core.login.behavior_rotate": "Faites glisser le curseur pour remettre l'image intérieure à l'endroit",
56
+ "core.login.bind_wechat": "Se connecter et lier WeChat",
57
+ "core.login.captcha": "Saisissez le code de vérification",
58
+ "core.login.captcha_load_failed": "Échec du chargement du code de vérification",
59
+ "core.login.captcha_type_missing": "La méthode de vérification n’est pas configurée",
60
+ "core.login.captcha_type_name": "Méthode de vérification de connexion",
61
+ "core.login.captcha_type_response_missing": "La réponse de vérification ne contient aucun type",
62
+ "core.login.captcha_write_failed": "Échec de l’enregistrement de l’image de vérification",
63
+ "core.login.default_sub_title": "Bienvenue",
64
+ "core.login.login_failed": "Échec de la connexion. Réessayez.",
65
+ "core.login.login_success": "Connexion réussie",
66
+ "core.login.password": "Saisissez le mot de passe",
67
+ "core.login.privacy": "Politique de confidentialité",
68
+ "core.login.protocol_missing": "Les conditions d’utilisation et la politique de confidentialité ne sont pas configurées",
69
+ "core.login.protocol_prompt": "Lisez et acceptez d’abord les accords. Confirmez pour accepter et continuer.",
70
+ "core.login.service": "Conditions d’utilisation",
71
+ "core.login.tenant": "Saisissez le code du locataire",
72
+ "core.login.user_name": "Saisissez le nom d'utilisateur",
73
+ "core.login.user_name_mobile": "Saisissez le nom d’utilisateur ou le numéro de mobile",
74
+ "core.login.user_name_password": "Saisissez le nom d’utilisateur et le mot de passe",
75
+ "core.login.wechat": "Continuer avec WeChat",
76
+ "core.login.wechat_bind_tip": "Ce compte WeChat n’est pas lié. Connectez-vous à un compte existant pour le lier.",
77
+ "core.login.wechat_failed": "Échec de la connexion WeChat. Réessayez plus tard.",
82
78
  "core.navigation.home": "Accueil",
83
- "core.navigation.error.access": "Mode d'accès au menu invalide : {access}",
84
- "core.navigation.error.childTab": "Seul le menu secondaire mobile peut être utilisé comme onglet : {name}",
85
- "core.navigation.error.cycle": "Il y a un parent de boucle dans le menu mobile : {name}",
86
- "core.navigation.error.identity": "Identifiant de menu, nom ou application/chemin non valide",
87
- "core.navigation.error.itemObject": "L'élément de menu n'est pas un objet",
88
- "core.navigation.error.listMissing": "Réponse du menu liste plate manquante",
89
- "core.navigation.error.namePrefix": "Le nom du menu doit utiliser le préfixe de l'application : {name}",
90
- "core.navigation.error.parentInvalid": "Le numéro parent du menu mobile n'est pas valide : {name}",
91
- "core.navigation.error.parentMissing": "Il manque le numéro de parent dans le menu : {name}",
92
- "core.navigation.error.parentNotFound": "Le menu parent du mobile n'existe pas : {name}",
93
- "core.navigation.error.responseObject": "La réponse du menu n'est pas un objet",
94
- "core.navigation.error.rootTab": "Le menu secondaire mobile doit être utilisé comme onglet : {name}",
95
- "core.navigation.error.tabCount": "tabBar ne peut configurer que 0 ou 2 à 5 éléments",
96
- "core.navigation.error.unique": "L'identifiant, le nom et le chemin du menu doivent être uniques",
97
- "core.navigation.error.viewKey": "ViewKey non enregistré : {viewKey}",
98
- "core.navigation.my": "Le mien",
99
- "core.navigation.protocol": "Détails de l'accord",
100
- "core.protocol.loadFailed": "Échec du chargement de la configuration mobile",
101
- "core.protocol.notConfigured": "{title} n'est pas configuré et ne peut pas être affiché temporairement.",
102
- "core.protocol.privacy": "Accord de confidentialité",
103
- "core.protocol.service": "Conditions d'utilisation",
79
+ "core.navigation.error.access": "Mode d’accès au menu invalide : {access}",
80
+ "core.navigation.error.child_tab": "Seuls les menus d’application de deuxième niveau peuvent être des onglets : {name}",
81
+ "core.navigation.error.cycle": "Cycle de parenté détecté dans le menu : {name}",
82
+ "core.navigation.error.identity": "L’identifiant, le nom ou le chemin app/ du menu est invalide",
83
+ "core.navigation.error.item_object": "L’élément de menu n’est pas un objet",
84
+ "core.navigation.error.list_missing": "La réponse du menu ne contient pas de liste plate",
85
+ "core.navigation.error.name_prefix": "Le nom du menu doit commencer par App : {name}",
86
+ "core.navigation.error.parent_missing": "L’identifiant du parent du menu est manquant : {name}",
87
+ "core.navigation.error.parent_not_found": "Le parent du menu est introuvable : {name}",
88
+ "core.navigation.error.response_object": "La réponse du menu n’est pas un objet",
89
+ "core.navigation.error.root_tab": "Le menu d’application de deuxième niveau doit être un onglet : {name}",
90
+ "core.navigation.error.tab_count": "tabBar doit contenir 0 ou 2 à 5 éléments",
91
+ "core.navigation.error.unique": "L’identifiant, le nom et le chemin du menu doivent être uniques",
92
+ "core.navigation.error.view_key": "viewKey non enregistré : {viewKey}",
93
+ "core.navigation.my": "Mon compte",
94
+ "core.navigation.protocol": "Accord",
95
+ "core.protocol.load_failed": "Échec du chargement de la configuration de l’application",
96
+ "core.protocol.not_configured": "{title} n’est pas configuré et ne peut pas être consulté",
97
+ "core.protocol.privacy": "Politique de confidentialité",
98
+ "core.protocol.service": "Conditions d’utilisation",
104
99
  "core.status.CONFIG_ERROR": "Configuration de navigation invalide",
105
- "core.status.FORBIDDEN": "Pas encore d'accès\nLa page",
106
- "core.status.NOT_FOUND": "n'existe pas",
107
- "core.status.OFFLINE": "Réseau indisponible\nLa page",
108
- "core.status.PAGE_UNAVAILABLE": "est temporairement indisponible",
109
- "core.status.loading": "est en cours de chargement",
110
- "core.webview.embedBlocked": "La page H5 actuelle peut être restreinte par le site cible pour l'intégration",
111
- "core.webview.invalidUrl": "Adresse de lien valide manquante",
112
- "core.webview.openFailed": "Le lien ne peut pas être ouvert"
100
+ "core.status.forbidden": "Accès refusé",
101
+ "core.status.NOT_FOUND": "Page introuvable",
102
+ "core.status.offline": "Réseau indisponible",
103
+ "core.status.page_unavailable": "Page temporairement indisponible",
104
+ "core.status.loading": "Chargement",
105
+ "core.webview.embed_blocked": "La destination peut bloquer l’intégration sur H5",
106
+ "core.webview.invalid_url": "Aucun lien valide n’a été fourni",
107
+ "core.webview.open_failed": "Impossible d’ouvrir le lien"
113
108
  }
@@ -0,0 +1,22 @@
1
+ /* 此文件由 scripts/sync_locales.py 生成,请勿手工修改。 */
2
+ import localeZhCn from './zh-CN.json'
3
+ import localeEnUs from './en-US.json'
4
+ import localeEsEs from './es-ES.json'
5
+ import localeFrFr from './fr-FR.json'
6
+ import localeJaJp from './ja-JP.json'
7
+ import localeKoKr from './ko-KR.json'
8
+ import localeZhTw from './zh-TW.json'
9
+
10
+ export const LOCALE_MESSAGES = {
11
+ 'zh-CN': localeZhCn,
12
+ 'en-US': localeEnUs,
13
+ 'es-ES': localeEsEs,
14
+ 'fr-FR': localeFrFr,
15
+ 'ja-JP': localeJaJp,
16
+ 'ko-KR': localeKoKr,
17
+ 'zh-TW': localeZhTw,
18
+ } as const satisfies Record<string, Record<string, string>>
19
+
20
+ export type GeneratedLocale = keyof typeof LOCALE_MESSAGES
21
+ export const DEFAULT_LOCALE: GeneratedLocale = 'zh-CN'
22
+ export const SUPPORTED_LOCALES = Object.keys(LOCALE_MESSAGES) as GeneratedLocale[]
@@ -5,12 +5,17 @@
5
5
  import Taro from '@tarojs/taro'
6
6
  import { create } from 'zustand'
7
7
  import type { KratosTaroModule } from '../module'
8
- import type { GetLanguageResponse } from '../rpc/base/v1/language'
9
-
10
- /** Taro 端支持的语言区域。 */
11
- export const SUPPORTED_LOCALES = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP', 'ko-KR', 'fr-FR', 'es-ES'] as const
8
+ import type { OptionLanguageResponse } from '../rpc/base/v1/language'
9
+ import {
10
+ DEFAULT_LOCALE as GENERATED_DEFAULT_LOCALE,
11
+ SUPPORTED_LOCALES as GENERATED_SUPPORTED_LOCALES,
12
+ type GeneratedLocale,
13
+ } from './generated'
14
+
15
+ /** Taro 端已打包的语言区域;运行时可切换列表由 base_language 接口决定。 */
16
+ export const SUPPORTED_LOCALES = GENERATED_SUPPORTED_LOCALES
12
17
  /** Taro 端支持的语言区域类型。 */
13
- export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]
18
+ export type SupportedLocale = GeneratedLocale
14
19
  /** 单个模块的扁平语言包。 */
15
20
  export type LocaleMessages = Record<string, string>
16
21
  /** 翻译插值参数。 */
@@ -20,12 +25,8 @@ export type LocaleParams = Record<string, string | number>
20
25
  export interface LocaleOption {
21
26
  /** 标准语言代码。 */
22
27
  language_code: SupportedLocale
23
- /** 语言名称。 */
24
- language_name: string
25
28
  /** 本地语言名称。 */
26
29
  native_name: string
27
- /** 排序值。 */
28
- sort: number
29
30
  }
30
31
 
31
32
  /** Taro 语言状态。 */
@@ -35,7 +36,7 @@ export interface LocaleStoreState {
35
36
  supportedLocales: SupportedLocale[]
36
37
  }
37
38
 
38
- const DEFAULT_LOCALE: SupportedLocale = 'zh-CN'
39
+ const DEFAULT_LOCALE: SupportedLocale = GENERATED_DEFAULT_LOCALE
39
40
  const LOCALE_STORAGE_KEY = 'kratos-app:locale'
40
41
  const localeMessages = new Map<SupportedLocale, LocaleMessages>()
41
42
  const localeChangeHandlers = new Set<() => void | Promise<void>>()
@@ -49,27 +50,18 @@ export const useLocaleStore = create<LocaleStoreState>(() => ({
49
50
 
50
51
  /** 规范化语言区域到应用白名单。 */
51
52
  export function normalizeLocale(value?: string): SupportedLocale {
52
- const normalized = String(value || '').replace('_', '-').toLowerCase()
53
- if (normalized.startsWith('ja')) return 'ja-JP'
54
- if (normalized.startsWith('en')) return 'en-US'
55
- if (normalized.startsWith('zh-tw') || normalized.startsWith('zh-hk') || normalized.startsWith('zh-mo')) return 'zh-TW'
56
- if (normalized.startsWith('ko')) return 'ko-KR'
57
- if (normalized.startsWith('fr')) return 'fr-FR'
58
- if (normalized.startsWith('es')) return 'es-ES'
59
- return DEFAULT_LOCALE
53
+ return parseSupportedLocale(value) ?? DEFAULT_LOCALE
60
54
  }
61
55
 
62
56
  /** 将接口或系统语言代码解析为已打包的语言区域。 */
63
57
  function parseSupportedLocale(value?: string): SupportedLocale | undefined {
64
58
  const normalized = String(value || '').replace('_', '-').toLowerCase()
65
- if (normalized.startsWith('zh-tw') || normalized.startsWith('zh-hk') || normalized.startsWith('zh-mo')) return 'zh-TW'
66
- if (normalized.startsWith('zh')) return 'zh-CN'
67
- if (normalized.startsWith('ja')) return 'ja-JP'
68
- if (normalized.startsWith('en')) return 'en-US'
69
- if (normalized.startsWith('ko')) return 'ko-KR'
70
- if (normalized.startsWith('fr')) return 'fr-FR'
71
- if (normalized.startsWith('es')) return 'es-ES'
72
- return undefined
59
+ if (!normalized) return undefined
60
+ const alias = normalized.startsWith('zh-hk') || normalized.startsWith('zh-mo') ? 'zh-tw' : normalized
61
+ const exact = SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === alias)
62
+ if (exact) return exact
63
+ const language = alias.split('-', 1)[0]
64
+ return SUPPORTED_LOCALES.find((locale) => locale.toLowerCase().split('-', 1)[0] === language)
73
65
  }
74
66
 
75
67
  /** 初始化持久化语言偏好。 */
@@ -81,28 +73,23 @@ export function initializeLocale(): SupportedLocale {
81
73
  return locale
82
74
  }
83
75
 
84
- /** 应用后端语言配置,并在当前语言不可用时回退到接口主语言。 */
85
- export function applyLanguageConfig(response: GetLanguageResponse): void {
76
+ /** 应用后端语言配置,并在当前语言不可用时回退到接口第一项。 */
77
+ export function applyLanguageConfig(response: OptionLanguageResponse): void {
86
78
  const options = response.languages.reduce<LocaleOption[]>((items, item) => {
87
79
  const languageCode = parseSupportedLocale(item.language_code)
88
80
  if (!languageCode || items.some((option) => option.language_code === languageCode)) return items
89
81
  items.push({
90
82
  language_code: languageCode,
91
- language_name: item.language_name || t(`common.language.${languageCode}`),
92
- native_name: item.native_name || item.language_name || languageCode,
93
- sort: item.sort,
83
+ native_name: item.native_name || languageCode,
94
84
  })
95
85
  return items
96
86
  }, [])
97
- const languageOptions = options.length ? options.sort((left, right) => left.sort - right.sort) : getFallbackLanguageOptions()
87
+ const languageOptions = options.length ? options : getFallbackLanguageOptions()
98
88
  const supportedLocales = languageOptions.map((item) => item.language_code)
99
- const primaryLocale = parseSupportedLocale(response.primary_language_code)
100
89
  const currentLocale = getCurrentLocale()
101
90
  const locale = supportedLocales.includes(currentLocale)
102
91
  ? currentLocale
103
- : primaryLocale && supportedLocales.includes(primaryLocale)
104
- ? primaryLocale
105
- : supportedLocales[0] ?? DEFAULT_LOCALE
92
+ : supportedLocales[0] ?? DEFAULT_LOCALE
106
93
  useLocaleStore.setState({ locale, languageOptions, supportedLocales })
107
94
  if (locale !== currentLocale) Taro.setStorageSync(LOCALE_STORAGE_KEY, locale)
108
95
  }
@@ -117,21 +104,21 @@ export function getSupportedLocales(): SupportedLocale[] {
117
104
  return useLocaleStore.getState().supportedLocales
118
105
  }
119
106
 
120
- /** 注册所有模块贡献的语言包并校验七语键集合。 */
107
+ /** 注册所有模块贡献的语言包并校验语言键集合。 */
121
108
  export function registerLocaleMessages(modules: KratosTaroModule[]): void {
122
109
  localeMessages.clear()
123
110
  SUPPORTED_LOCALES.forEach((locale) => localeMessages.set(locale, {}))
124
111
  modules.forEach((module) => {
125
- const expectedKeys = Object.keys(module.messages?.[DEFAULT_LOCALE] || {}).sort()
112
+ const expectedKeys = requiredLocaleKeys(module.messages?.[DEFAULT_LOCALE] || {})
126
113
  SUPPORTED_LOCALES.forEach((locale) => {
127
114
  const messages = module.messages?.[locale]
128
115
  if (!messages) throw new Error(`${module.name} 缺少 ${locale} 语言包`)
129
- const keys = Object.keys(messages).sort()
116
+ const keys = requiredLocaleKeys(messages)
130
117
  if (keys.join('\u0000') !== expectedKeys.join('\u0000')) {
131
118
  throw new Error(`${module.name} 的 ${locale} 语言包键集合不一致`)
132
119
  }
133
120
  const target = localeMessages.get(locale) as LocaleMessages
134
- keys.forEach((key) => {
121
+ Object.keys(messages).forEach((key) => {
135
122
  if (!key.startsWith('common.') && !key.startsWith('core.') && !key.startsWith('system.')) {
136
123
  throw new Error(`${module.name} 的语言键命名空间无效: ${key}`)
137
124
  }
@@ -201,11 +188,27 @@ function assertLocalePlaceholders(
201
188
  }
202
189
  }
203
190
 
191
+ function requiredLocaleKeys(messages: LocaleMessages): string[] {
192
+ return Object.keys(messages)
193
+ .filter((key) => !key.startsWith('common.language.'))
194
+ .sort()
195
+ }
196
+
204
197
  function getFallbackLanguageOptions(): LocaleOption[] {
205
- return SUPPORTED_LOCALES.map((languageCode, sort) => ({
198
+ return SUPPORTED_LOCALES.map((languageCode) => ({
206
199
  language_code: languageCode,
207
- language_name: t(`common.language.${languageCode}`),
208
- native_name: languageCode,
209
- sort,
200
+ native_name: fallbackNativeLanguageName(languageCode),
210
201
  }))
211
202
  }
203
+
204
+ function fallbackNativeLanguageName(languageCode: SupportedLocale): string {
205
+ return {
206
+ 'zh-CN': '简体中文',
207
+ 'zh-TW': '繁體中文',
208
+ 'en-US': 'English',
209
+ 'ja-JP': '日本語',
210
+ 'ko-KR': '한국어',
211
+ 'fr-FR': 'Français',
212
+ 'es-ES': 'Español',
213
+ }[languageCode]
214
+ }