@liujitcn/kratos-uni-app-core 0.0.21 → 0.0.24

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