Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"axios": "^1.19.0",
"codemirror": "^6.0.2",
"cropperjs": "^1.6.3",
"dayjs": "^1.11.23",
"dingtalk-jsapi": "^3.2.9",
"dompurify": "^3.4.14",
"echarts": "^6.1.0",
Expand Down
3 changes: 3 additions & 0 deletions ui/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ui/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import 'dayjs/locale/zh-cn'
import 'dayjs/locale/zh-tw'
import { useI18n } from 'vue-i18n'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
Expand Down
5 changes: 5 additions & 0 deletions ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ FormData;保存返回完整配置,页面以该返回值作为唯一数据来
中选择。认证配置仅提交 `login_value`、`type`、`max_attempts`、`failed_attempts`、`lock_time`,
不透传查询响应中的选项;跨域地址提交 `cross_domain_list` 数组,不另建 `cors_config`。

### 当前账号语言

`admin/auth/current-user.ts` 的 postCurrentUserLanguage 向 `/user/language` POST `{ language }`。
头部菜单在成功后更新当前入口的语言状态与用户档案,不整页刷新;失败保留原语言。

### 当前账号密码

`admin/auth/current-user.ts` 的 postCurrentUserPassword 向 `/user/current/reset_password`
Expand Down
7 changes: 6 additions & 1 deletion ui/src/api/admin/auth/current-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ const postCurrentUserPassword = (password: PasswordRequest) => {
return post<PasswordRequest, boolean>('/user/current/reset_password', password)
}

export default { postCurrentUserPassword, getCurrentUserInfo, getCurrentUserRoleList, getCurrentUserWorkspaceList }
/** 保存当前账号的语言偏好。 */
const postCurrentUserLanguage = (language: string) => {
return post<{ language: string }, void>('/user/language', { language })
}

export default { postCurrentUserLanguage, postCurrentUserPassword, getCurrentUserInfo, getCurrentUserRoleList, getCurrentUserWorkspaceList }
2 changes: 1 addition & 1 deletion ui/src/assets/iconfont.js

Large diffs are not rendered by default.

Binary file added ui/src/assets/login-animation/4robot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ Admin、Chat 共用的 `App.vue` 通过 Config Provider 的 `table.tooltipOption

### MdEditor、MdEditorMagnify、MdPreview

位于 `global/markdown-editor/`,底层 Props、事件通过 `$attrs` 透传,语言来自 User Store。
位于 `global/markdown-editor/`,底层 Props、事件通过 `$attrs` 透传;默认语言读取当前应用的 Vue I18n 全局实例,
不直接依赖 Admin 或 Chat Store。语言切换只更新 language 属性,不重建编辑器或改写正文;
通过既有 language 属性仍可显式覆盖。繁体 zh-Hant 映射到已注册的 zh-TW 语言包。

- `MdEditor` 默认关闭 Prettier,提供 `defFooters` 插槽。
- `MdEditorMagnify` 接收字符串 `v-model` 和 `title`,弹窗确认后回写并触发 `submitDialog`;
Expand Down
11 changes: 4 additions & 7 deletions ui/src/components/global/markdown-editor/MdEditor.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script setup lang="ts">
import { computed } from 'vue'
import { MdEditor as MdEditorV3 } from 'md-editor-v3'
import { useStore } from '@/stores'
import { useI18n } from 'vue-i18n'
import { MARKDOWN_LOCALES, normalizeLocale } from '@/locales/config'
import { sanitizeMarkdown } from './sanitize'

defineOptions({ name: 'MdEditor', inheritAttrs: false })
Expand All @@ -10,12 +11,8 @@ defineSlots<{
defFooters?(): unknown
}>()

const { user } = useStore()
const language = computed(() => {
const currentLanguage = user.language.toLowerCase()
if (['zh-tw', 'zh-hant', 'zh-hk', 'zh-mo'].includes(currentLanguage)) return 'zh-TW'
return currentLanguage.startsWith('zh') ? 'zh-CN' : 'en-US'
})
const { locale } = useI18n({ useScope: 'global' })
const language = computed(() => MARKDOWN_LOCALES[normalizeLocale(locale.value)])
</script>

<template>
Expand Down
11 changes: 4 additions & 7 deletions ui/src/components/global/markdown-editor/MdPreview.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
<script setup lang="ts">
import { computed } from 'vue'
import { MdPreview as MdPreviewV3 } from 'md-editor-v3'
import { useStore } from '@/stores'
import { useI18n } from 'vue-i18n'
import { MARKDOWN_LOCALES, normalizeLocale } from '@/locales/config'
import { sanitizeMarkdown } from './sanitize'

defineOptions({ name: 'MdPreview', inheritAttrs: false })

const { user } = useStore()
const language = computed(() => {
const currentLanguage = user.language.toLowerCase()
if (['zh-tw', 'zh-hant', 'zh-hk', 'zh-mo'].includes(currentLanguage)) return 'zh-TW'
return currentLanguage.startsWith('zh') ? 'zh-CN' : 'en-US'
})
const { locale } = useI18n({ useScope: 'global' })
const language = computed(() => MARKDOWN_LOCALES[normalizeLocale(locale.value)])
</script>

<template>
Expand Down
24 changes: 20 additions & 4 deletions ui/src/layout/avatar-dropdown/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { useRouter } from 'vue-router'
import CurrentUserApi from '@/api/admin/auth/current-user'
import LoginApi from '@/api/admin/auth/login'
import { LOGIN_METHOD } from '@/api/enums'
import { LOCALE_OPTIONS, type Locale } from '@/locales/config'
Expand All @@ -12,10 +13,24 @@ import perm from '@/permission/index.ts'

defineOptions({ name: 'AvatarDropdown' })

const currentLanguage = ref<Locale>('zh-CN')
const isChangingLanguage = ref(false)
const router = useRouter()
const { auth, user, theme } = useStore()

// 保存账号偏好后同步界面,不刷新页面,避免丢失未保存内容。
function handleChangeLanguage(language: Locale) {
if (isChangingLanguage.value || language === user.language) return
isChangingLanguage.value = true
return CurrentUserApi.postCurrentUserLanguage(language)
.then(() => {
user.setLanguage(language)
if (user.userInfo) user.userInfo.language = language
})
.finally(() => {
isChangingLanguage.value = false
})
}

// 修改当前账号密码。
const changePasswordDialogRef = ref<InstanceType<typeof ChangePasswordDialog>>()

Expand Down Expand Up @@ -84,7 +99,7 @@ function toUrl(url?: string) {
<template #icon><MkIcon name="icon_passkeys_outlined" /></template>
<span>API Key</span>
</MkDropdownItem>
<!-- // TODO 语言 -->
<!-- 切换账号界面语言 -->
<MkDropdownItem @click.stop class="p-0!" v-if="perm.system.other.SWITCH_LANGUAGE()">
<MkDropdown class="w-full" trigger="hover" placement="left-start">
<div class="flex-between w-full gap-2 p-2">
Expand All @@ -101,8 +116,9 @@ function toUrl(url?: string) {
v-for="language in LOCALE_OPTIONS"
:key="language.value"
selectable
:selected="currentLanguage === language.value"
@click="currentLanguage = language.value"
:selected="user.language === language.value"
:disabled="isChangingLanguage"
@click="handleChangeLanguage(language.value)"
>
{{ language.label }}
</MkDropdownItem>
Expand Down
14 changes: 9 additions & 5 deletions ui/src/locales/LOCALE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## 分类

按“业务域 → 对象或场景 → 文案用途”组织;文件按业务域划分,不对应组件目录。
当前只建立实际使用的 common.json,不预建空业务包。
当前仅建立实际使用的 auth.json;common 等业务包在有文案接入时创建,不保留空包。

| 业务域 | 内容 |
| ----------- | ------------------------------------------------ |
Expand All @@ -25,7 +25,6 @@
业务域内按实际对象拆分,如 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。多处使用不等于通用;相同中文但业务语义
Expand All @@ -45,15 +44,20 @@ common.errors 用于通用错误场景,例如 common.errors.notFound.descripti

Admin 和 Chat 分别调用 createAppI18n,连接自己的 User Store;该模块不导入任何 Store。
Store 是语言状态来源,通过 setLanguage 更新,不能直接修改 Vue I18n 的 locale。
App.vue 通过当前 locale 配置 Element Plus,初始化同时维护 html.lang。
App.vue 通过当前 locale 配置 Element Plus,并注册 Day.js 的 zh-cn、zh-tw 日期语言包;
英文使用内置 en。日期组件按当前 Element Plus 语言读取日期文案,不修改时区及原始值。
初始化同时维护 html.lang。Markdown 编辑器与预览读取当前应用的 locale,繁体映射为 zh-TW,
语言包在入口通过 configureMarkdownEditor 注册,不跟随语言切换重复初始化或重建编辑器。

两个入口继续共用 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。
当前语言选择只保存本地,不写回账号;账号偏好优先级在账号设置模块中处理。
登录前的语言选择只保存本地;头部菜单调用当前账号语言接口,成功后同步 Store、
用户档案和本地偏好,不刷新页面。Admin 后续加载账号时继续采用已保存的账号语言。

## 分批迁移

404 页面为当前样例,提供简体、英文及繁体切换。其余界面、路由标题、编辑器及日期格式专项
Admin 登录与找回密码页面支持三语切换,错误页暂不接入翻译。复用的账号与扫码组件同时影响 Chat 登录和
外观预览;平台配置的品牌文案保持原值,第三方扫码界面由 SDK 提供。Element Plus 日期组件及 Markdown 编辑器、预览已接入三语。其余界面、路由标题及业务日期格式
按模块接入,允许过渡期保留原中文。每批完成后等待用户检查,未经确认不扩展下一批。
9 changes: 8 additions & 1 deletion ui/src/locales/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ 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: '繁體中文' },
{ value: 'en-US', label: 'English' },
] as const
export type Locale = (typeof LOCALE_OPTIONS)[number]['value']

/** Markdown 编辑器的繁体包注册为 zh-TW。 */
export const MARKDOWN_LOCALES = {
'zh-CN': 'zh-CN',
'zh-Hant': 'zh-TW',
'en-US': 'en-US',
} as const satisfies Record<Locale, string>

/** 将浏览器或账号语言映射到已支持的界面语言。 */
export function normalizeLocale(language: string | null | undefined): Locale {
const [languageCode, ...subtags] = language?.replaceAll('_', '-').toLowerCase().split('-') ?? []
Expand Down
86 changes: 86 additions & 0 deletions ui/src/locales/en-US/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"credentials": {
"fields": {
"username": {
"placeholder": "Enter your username"
},
"password": {
"placeholder": "Enter your password"
},
"captcha": {
"label": "Verification image",
"placeholder": "Enter the verification code"
}
},
"validation": {
"usernameRequired": "Enter your username",
"passwordRequired": "Enter your password",
"captchaRequired": "Enter the verification code"
}
},
"login": {
"actions": {
"submit": "Log in",
"forgotPassword": "Forgot password?"
},
"description": "Other login methods",
"options": {
"local": "Account login",
"ldap": "LDAP login",
"cas": "CAS",
"oidc": "OIDC",
"oauth2": "OAuth2",
"saml2": "SAML2",
"dingtalk": "DingTalk",
"lark": "Lark",
"wecom": "WeCom"
}
},
"forgotPassword": {
"title": "Reset password",
"actions": {
"backToLogin": "Back to login",
"sendCode": "Send code",
"resendCode": "Resend ({seconds}s)",
"submit": "Reset password"
},
"fields": {
"email": {
"placeholder": "Enter your email address"
},
"verificationCode": {
"placeholder": "Enter the verification code"
},
"password": {
"placeholder": "Enter a password of 6–30 characters"
},
"confirmPassword": {
"placeholder": "Confirm your password"
}
},
"validation": {
"emailRequired": "Enter your email address",
"emailInvalid": "Enter a valid email address",
"codeRequired": "Enter the verification code",
"passwordLength": "Password must be 6–30 characters",
"confirmPasswordRequired": "Confirm your password",
"passwordMismatch": "The passwords do not match"
},
"messages": {
"codeSent": "Verification code sent. Check your email.",
"passwordReset": "Password reset. Log in with your new password."
}
},
"qrCode": {
"title": "Log in with {provider} QR code",
"messages": {
"sdkLoadFailed": "Failed to load the {provider} QR code SDK"
}
},
"dingtalkClient": {
"messages": {
"missingCode": "DingTalk did not return an authorization code",
"authorizationFailed": "DingTalk authorization failed. Use another login method."
}
}
}
5 changes: 0 additions & 5 deletions ui/src/locales/en-US/common.json

This file was deleted.

16 changes: 8 additions & 8 deletions ui/src/locales/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
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'
import zhAuth from './zh-CN/auth.json'
import enAuth from './en-US/auth.json'
import hantAuth from './zh-Hant/auth.json'

type MessageSchema = { common: typeof zhCommon }
type MessageSchema = { auth: typeof zhAuth }
declare module 'vue-i18n' {
export interface DefineLocaleMessage {
common: MessageSchema['common']
auth: MessageSchema['auth']
}
}

Expand All @@ -26,9 +26,9 @@ export function createAppI18n(user: LanguageState) {
locale: normalizeLocale(user.language),
fallbackLocale: DEFAULT_LOCALE,
messages: {
'zh-CN': { common: zhCommon },
'en-US': { common: enCommon },
'zh-Hant': { common: hantCommon },
'zh-CN': { auth: zhAuth },
'en-US': { auth: enAuth },
'zh-Hant': { auth: hantAuth },
},
})

Expand Down
Loading
Loading