Repository navigation
fix: patterns bug #7872
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: patterns bug #7872
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,22 @@ | ||
| <script setup lang="ts"> | ||
| import { computed } from 'vue' | ||
| import { useI18n } from 'vue-i18n' | ||
| import zhCn from 'element-plus/es/locale/lang/zh-cn' | ||
| import en from 'element-plus/es/locale/lang/en' | ||
| import zhTw from 'element-plus/es/locale/lang/zh-tw' | ||
| import { normalizeLocale } from '@/locales/config' | ||
| import { RouterView } from 'vue-router' | ||
| import { TOOLTIP_SHOW_DELAY } from '@/components/global/mk-tooltip/constants' | ||
|
|
||
| const { locale } = useI18n({ useScope: 'global' }) | ||
| const elementLocales = { 'zh-CN': zhCn, 'en-US': en, 'zh-Hant': zhTw } | ||
| const elementLocale = computed(() => elementLocales[normalizeLocale(locale.value)]) | ||
|
|
||
| const tableConfig = { tooltipOptions: { showAfter: TOOLTIP_SHOW_DELAY } } | ||
| </script> | ||
|
|
||
| <template> | ||
| <el-config-provider :table="tableConfig"> | ||
| <el-config-provider :table="tableConfig" :locale="elementLocale"> | ||
| <RouterView /> | ||
| </el-config-provider> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # 国际化规则 | ||
|
|
||
| `src/locales` 负责界面语言、语言包和入口初始化。当前支持 `zh-CN`、`en-US` 与 `zh-Hant`, | ||
| 默认及缺失文案回退为简体中文。语言包暂随入口加载,新增语言时再评估按需加载。 | ||
|
|
||
| ## 分类 | ||
|
|
||
| 按“业务域 → 对象或场景 → 文案用途”组织;文件按业务域划分,不对应组件目录。 | ||
| 当前只建立实际使用的 common.json,不预建空业务包。 | ||
|
|
||
| | 业务域 | 内容 | | ||
| | ----------- | ------------------------------------------------ | | ||
| | common | 无业务含义的操作、基础校验、界面偏好和通用错误页 | | ||
| | auth | 登录、退出、找回密码、身份认证 | | ||
| | navigation | 导航菜单、面包屑、浏览器页面标题 | | ||
| | application | 智能体创建、配置、发布、版本 | | ||
| | knowledge | 知识库、文档、分段、问题、标签、检索 | | ||
| | model | 模型、供应商、模型参数与连接测试 | | ||
| | tool | 工具、MCP、Skill 管理 | | ||
| | workflow | 画布、节点、变量、调试与执行展示 | | ||
| | chat | 对话输入、消息操作、历史会话与对话状态 | | ||
| | workspace | 工作空间、成员、空间级设置 | | ||
| | system | 用户、角色、系统配置、许可证 | | ||
|
|
||
| 业务域内按实际对象拆分,如 knowledge.document;末级按用途使用: | ||
| `title`、`description`、`actions`、`fields`、`validation`、`messages`、`dialogs`、 | ||
| `states`、`options`、`empty`。fields 内使用字段名下的 label、placeholder、description。 | ||
| common.errors 用于通用错误场景,例如 common.errors.notFound.description。 | ||
|
|
||
| 归属以业务含义判断:知识库检索模式归 knowledge,知识库检索节点归 workflow。 | ||
| 系统资源管理复用原业务域,不复制到 system。多处使用不等于通用;相同中文但业务语义 | ||
| 不同可以使用不同 key。用户内容、提示词、协议枚举和已保存节点名称不进入语言包。 | ||
|
|
||
| ## 使用约定 | ||
|
|
||
| - key 使用表达业务含义的英文 camelCase,不使用中文、数字序号或 misc 等模糊分类。 | ||
| - JSON 文件不重复包裹业务域,由 index.ts 挂载命名空间;各语言保持相同 key 与插值参数。 | ||
| - 完整句子整体翻译,动态内容使用命名参数;不用片段拼接句子。 | ||
| - 组件显式导入 useI18n,并使用全局作用域的 t;不使用自动注入的 $t。 | ||
| - 依赖语言的选项和校验规则在 computed 或执行时翻译,不在模块初始化时固化翻译结果。 | ||
| - 新增业务包时更新 index.ts 的所有语言及 MessageSchema,以中文结构约束翻译 key。 | ||
| - 非组件代码通过调用方传入翻译能力,不依赖某个入口的全局国际化单例。 | ||
|
|
||
| ## 语言状态 | ||
|
|
||
| Admin 和 Chat 分别调用 createAppI18n,连接自己的 User Store;该模块不导入任何 Store。 | ||
| Store 是语言状态来源,通过 setLanguage 更新,不能直接修改 Vue I18n 的 locale。 | ||
| App.vue 通过当前 locale 配置 Element Plus,初始化同时维护 html.lang。 | ||
|
|
||
| 两个入口继续共用 MaxKB-locale。初始化按本地缓存、浏览器语言、默认中文选择; | ||
| 英语变体归一为 en-US;zh-Hant、zh-TW、zh-HK、zh-MO 归一为 zh-Hant, | ||
| 显式 zh-Hans 优先使用简体,其余语言回退 zh-CN。Element Plus 的繁体映射为 zh-tw。Admin 加载账号后继续采用账号语言。 | ||
| 跨标签页缓存变化同步到当前 Store。请求头复用原请求层的 user.language。 | ||
| 当前语言选择只保存本地,不写回账号;账号偏好优先级在账号设置模块中处理。 | ||
|
|
||
| ## 分批迁移 | ||
|
|
||
| 404 页面为当前样例,提供简体、英文及繁体切换。其余界面、路由标题、编辑器及日期格式专项 | ||
| 按模块接入,允许过渡期保留原中文。每批完成后等待用户检查,未经确认不扩展下一批。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /** 定义当前支持的界面语言与偏好存储规则。 */ | ||
| export const LANGUAGE_STORAGE_KEY = 'MaxKB-locale' | ||
| export const DEFAULT_LOCALE = 'zh-CN' | ||
| export const LOCALE_OPTIONS = [ | ||
| { value: 'zh-CN', label: '简体中文' }, | ||
| { value: 'en-US', label: 'English' }, | ||
| { value: 'zh-Hant', label: '繁體中文' }, | ||
| ] as const | ||
| export type Locale = (typeof LOCALE_OPTIONS)[number]['value'] | ||
|
|
||
| /** 将浏览器或账号语言映射到已支持的界面语言。 */ | ||
| export function normalizeLocale(language: string | null | undefined): Locale { | ||
| const [languageCode, ...subtags] = language?.replaceAll('_', '-').toLowerCase().split('-') ?? [] | ||
| if (languageCode === 'en') return 'en-US' | ||
| if (languageCode === 'zh') { | ||
| // 显式文字体系优先于地区,例如 zh-Hans-HK 仍使用简体。 | ||
| if (subtags.includes('hans')) return 'zh-CN' | ||
| if (subtags.includes('hant') || subtags.some((subtag) => ['tw', 'hk', 'mo'].includes(subtag))) return 'zh-Hant' | ||
| } | ||
| return DEFAULT_LOCALE | ||
| } | ||
|
|
||
| /** 优先读取本地偏好,其次使用浏览器语言,最终回退简体中文。 */ | ||
| export function getDefaultLanguage(): Locale { | ||
| return normalizeLocale(localStorage.getItem(LANGUAGE_STORAGE_KEY) || navigator.language) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "actions": { "goHome": "Back to home" }, | ||
| "fields": { "language": { "label": "Interface language" } }, | ||
| "errors": { "notFound": { "description": "This page does not exist or has been deleted." } } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** 为每个应用入口创建独立的国际化实例,并连接该入口的语言状态。 */ | ||
| import { watch } from 'vue' | ||
| import { createI18n } from 'vue-i18n' | ||
| import { DEFAULT_LOCALE, LANGUAGE_STORAGE_KEY, normalizeLocale, type Locale } from './config' | ||
| import zhCommon from './zh-CN/common.json' | ||
| import enCommon from './en-US/common.json' | ||
| import hantCommon from './zh-Hant/common.json' | ||
|
|
||
| type MessageSchema = { common: typeof zhCommon } | ||
| declare module 'vue-i18n' { | ||
| export interface DefineLocaleMessage { | ||
| common: MessageSchema['common'] | ||
| } | ||
| } | ||
|
|
||
| interface LanguageState { | ||
| language: string | ||
| setLanguage: (language: string) => void | ||
| } | ||
|
|
||
| /** 在应用挂载前绑定 Store;不依赖 Admin 或 Chat 的具体实现。 */ | ||
| export function createAppI18n(user: LanguageState) { | ||
| const i18n = createI18n<[MessageSchema], Locale, false>({ | ||
| legacy: false, | ||
| globalInjection: false, | ||
| locale: normalizeLocale(user.language), | ||
| fallbackLocale: DEFAULT_LOCALE, | ||
| messages: { | ||
| 'zh-CN': { common: zhCommon }, | ||
| 'en-US': { common: enCommon }, | ||
| 'zh-Hant': { common: hantCommon }, | ||
| }, | ||
| }) | ||
|
|
||
| watch( | ||
| () => user.language, | ||
| (language) => { | ||
| const locale = normalizeLocale(language) | ||
| i18n.global.locale.value = locale | ||
| document.documentElement.lang = locale | ||
| }, | ||
| { immediate: true, flush: 'sync' }, | ||
| ) | ||
|
|
||
| window.addEventListener('storage', (event) => { | ||
| if (event.storageArea === localStorage && (event.key === LANGUAGE_STORAGE_KEY || event.key === null)) { | ||
| const language = normalizeLocale(event.newValue || navigator.language) | ||
| if (user.language !== language) user.setLanguage(language) | ||
| } | ||
| }) | ||
| return i18n | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code ReviewPotential Issues
Optimization Suggestions
Minor
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "actions": { "goHome": "返回首页" }, | ||
| "fields": { "language": { "label": "界面语言" } }, | ||
| "errors": { "notFound": { "description": "页面不存在或已被删除" } } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "actions": { "goHome": "返回首頁" }, | ||
| "fields": { "language": { "label": "介面語言" } }, | ||
| "errors": { "notFound": { "description": "頁面不存在或已被刪除" } } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are the review findings:
Potential Issues
1.
-list item regex is too broad'(?<! )- .*'This only guards against a preceding space, so it will incorrectly match hyphen-minus in contexts like:
value - 5,x - y--flag with a following wordIt also lacks a lookbehind for
\nor start-of-line anchor pairing, unlike the heading patterns. Consider aligning it with the heading style (match line-start or after\n).2. Missing anchor consistency
Heading patterns use
(?<=^)but^in a lookbehind without the multiline flag only matches string start. If this regex runs on multi-line text withoutmflag,(?<=^)never fires except at position 0. The(?<=\n)branch covers most cases, but this is fragile — precompiled regexes should ideally use themflag. Verify how the backend applies these patterns.3. Heading negative lookaheads can mismatch across branches
For
##:(?<=\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*. The(?<!#)before##correctly prevents matching###, but the two alternatives duplicate logic unnecessarily. Could be simplified to(?<=^|\n)(?<!#)## (?!#).*only if variable-length lookbehind is supported (it's not in JS), so keeping two branches is acceptable — just note the duplication.4. Space pattern
(?<! ) (?! )is greedy on whitespace-normalized inputThis matches any single space between non-spaces, including spaces inside markdown links, code, or English words. For CJK text splitting that may be intended, but confirm it won't break URLs/code blocks.
5. Labels are Chinese while the file mixes conventions
Existing labels are Chinese (
模型优化,直接回答) so this is consistent — no issue, just noting for reviewers.Optimization Suggestions
Or generate programmatically from a loop over 1–6 to reduce repetition and copy-paste risk.
as constto keep literal types, consistent with the constants block above:Consider
mflag / documenting flags: export whether these strings are used withnew RegExp(value, 'gm')so consumers know expected behavior.Add a type/interface for the option shape (
{ label: string; value: string }[]) for reuse and clarity.Comment the lack of anchors for
-,空格, etc., since they behave differently from headings.Summary
Main concerns: the
-pattern over-matching and reliance on^without the multiline flag. The rest is stylistic/repetition cleanup.