From 53b152929846e7abcacd22ee9053eaae3f3bc12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=92=99=E6=81=A9?= <37646958+XuMengEnEli@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:45:09 +0800 Subject: [PATCH] feat(locales): support runtime dynamic language list (#8279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(locales): support runtime dynamic language list - 将 SupportedLanguagesType 从硬编码的 'en-US' | 'zh-CN' 联合类型放宽为 string,支持应用在运行时注册自定义语言 - @vben/constants 新增 setSupportLanguages / onSupportLanguagesChange / getSupportLanguages,以发布订阅方式动态管理语言列表(订阅时立即回调当前值) - language-toggle 与 user-dropdown 组件改为订阅动态列表,不再直接引入静态 SUPPORT_LANGUAGES - use-simple-locale 对未知语言回退到 en-US - vxe-table 集成对未打包的自定义语言回退到 en-US 语言包 向后兼容:不调用 setSupportLanguages 时,行为与之前完全一致。 * fix(constants): 语言列表注册表返回独立快照 getSupportLanguages 与监听器回调改为返回克隆快照,存储时也做克隆,避免外部直接修改数组或元素对象污染注册表内部状态、绕过 setSupportLanguages 导致其他订阅者不同步。 * fix(constants): 偏好设置面板语言列表改为订阅动态注册表 - general.vue 不再直接引入静态 SUPPORT_LANGUAGES,改为通过 onSupportLanguagesChange 订阅,运行时替换语言列表后设置面板同步刷新 - setSupportLanguages 通知时迭代监听器快照,避免监听器在回调中取消订阅导致跳过后续监听器 * fix(constants): 固化本次更新快照后再通知监听器 监听器回调中嵌套调用 setSupportLanguages 时,后续监听器仍收到本次更新的列表,而不是嵌套调用的新列表。 --- .../src/use-simple-locale/messages.ts | 7 +-- packages/@core/preferences/src/types.ts | 2 +- packages/constants/src/core.ts | 50 ++++++++++++++++++- .../layouts/src/widgets/language-toggle.vue | 13 ++++- .../preferences/blocks/general/general.vue | 14 ++++-- .../widgets/user-dropdown/user-dropdown.vue | 14 ++++-- .../effects/plugins/src/vxe-table/init.ts | 11 ++-- packages/locales/src/typing.ts | 2 +- 8 files changed, 94 insertions(+), 19 deletions(-) diff --git a/packages/@core/composables/src/use-simple-locale/messages.ts b/packages/@core/composables/src/use-simple-locale/messages.ts index 573cdd745..2ba80801a 100644 --- a/packages/@core/composables/src/use-simple-locale/messages.ts +++ b/packages/@core/composables/src/use-simple-locale/messages.ts @@ -1,6 +1,6 @@ -export type Locale = 'en-US' | 'zh-CN'; +export type Locale = string; -export const messages: Record> = { +export const messages: Record> = { 'en-US': { cancel: 'Cancel', collapse: 'Collapse', @@ -25,4 +25,5 @@ export const messages: Record> = { }, }; -export const getMessages = (locale: Locale) => messages[locale]; +export const getMessages = (locale: Locale) => + messages[locale] ?? messages['en-US'] ?? {}; diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index 2eca22924..ffc46b83f 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -16,7 +16,7 @@ import type { ThemeModeType, } from '@vben-core/typings'; -type SupportedLanguagesType = 'en-US' | 'zh-CN'; +type SupportedLanguagesType = string; type CustomPreferencesValue = boolean | number | string; interface CustomPreferencesOption { diff --git a/packages/constants/src/core.ts b/packages/constants/src/core.ts index 3861466aa..4e21951df 100644 --- a/packages/constants/src/core.ts +++ b/packages/constants/src/core.ts @@ -5,11 +5,11 @@ export const LOGIN_PATH = '/auth/login'; export interface LanguageOption { label: string; - value: 'en-US' | 'zh-CN'; + value: string; } /** - * Supported languages + * 支持的语言(默认值,可在运行时通过 setSupportLanguages 覆盖) */ export const SUPPORT_LANGUAGES: LanguageOption[] = [ { @@ -21,3 +21,49 @@ export const SUPPORT_LANGUAGES: LanguageOption[] = [ value: 'en-US', }, ]; + +// --- 动态语言列表管理 --- +type LanguageListListener = (languages: LanguageOption[]) => void; + +// 深拷贝语言列表,外部拿到的永远是独立快照, +// 避免绕过 setSupportLanguages 直接修改注册表内部状态 +function cloneLanguages(languages: LanguageOption[]): LanguageOption[] { + return languages.map((language) => ({ ...language })); +} + +let currentLanguages: LanguageOption[] = cloneLanguages(SUPPORT_LANGUAGES); +const listeners: LanguageListListener[] = []; + +/** + * 获取当前动态语言列表 + */ +export function getSupportLanguages(): LanguageOption[] { + return cloneLanguages(currentLanguages); +} + +/** + * 更新语言列表并通知所有监听器 + */ +export function setSupportLanguages(languages: LanguageOption[]) { + // 先固化本次快照,即使监听器回调中嵌套调用 setSupportLanguages, + // 后续监听器收到的仍是本次更新的列表 + const nextLanguages = cloneLanguages(languages); + currentLanguages = nextLanguages; + // 迭代监听器快照,避免监听器在回调中取消订阅导致跳过后续监听器 + for (const listener of listeners.slice()) { + listener(cloneLanguages(nextLanguages)); + } +} + +/** + * 订阅语言列表变化,返回取消订阅函数 + */ +export function onSupportLanguagesChange(listener: LanguageListListener) { + listeners.push(listener); + // 订阅时立即回调当前值,便于消费方初始化 + listener(cloneLanguages(currentLanguages)); + return () => { + const index = listeners.indexOf(listener); + if (index !== -1) listeners.splice(index, 1); + }; +} diff --git a/packages/effects/layouts/src/widgets/language-toggle.vue b/packages/effects/layouts/src/widgets/language-toggle.vue index 0c5f6d58f..bcca8b03f 100644 --- a/packages/effects/layouts/src/widgets/language-toggle.vue +++ b/packages/effects/layouts/src/widgets/language-toggle.vue @@ -1,7 +1,10 @@