- 在门诊增强页面添加表格列的国际化标签 - 在门诊增强页面添加操作按钮的国际化文本 - 在门诊财务日结结算页面实现表单字段的国际化 - 在门诊财务日结结算页面实现表格列标题的国际化 - 在门诊财务日结结算页面添加操作按钮的国际化 - 集成 vue-i18n 并在组件中使用国际化方法 - 更新日结详情提示消息为国际化文本 - 实现导出文件名和成功消息的国际化
22 KiB
22 KiB
HealthLink-HIS 多语言(i18n)技术方案
文档类型: 架构设计
版本: V1.0
日期: 2026-06-24
作者: zhugeliang(架构师)
状态: 待确认
一、现状分析
1.1 后端现状
| 项目 | 值 |
|---|---|
| Spring Boot 版本 | 4.0.6 |
| 已有 i18n 基础 | I18nConfig (SessionLocaleResolver + LocaleChangeInterceptor, lang参数) |
| 已有 i18n 资源 | i18n/messages.properties + i18n/messages_zh_CN.properties (56行) |
| MessageUtils | 已封装 MessageUtils.message(code, args) 静态方法 |
| 消息键体系 | PromptMsgConstant (150+ key, 10个模块接口) |
| 硬编码中文错误 | ~851处 R.fail("中文") 散落在各 Controller/AppService |
| 数据库枚举值 | 状态值以数字存储, 前端映射显示 |
核心问题:
- 后端已有 i18n 基础设施, 但只用了 ~56 条消息, 远不够覆盖
- 大量业务错误消息硬编码为中文字符串 (
R.fail("中文")) PromptMsgConstant有 150+ 消息键, 但只有 zh_CN 翻译
1.2 前端现状
| 项目 | 值 |
|---|---|
| 技术栈 | Vue 3 + Vite + Element Plus + Pinia |
| 页面数量 | ~200+ .vue 文件 |
| ElMessage/ElNotification 调用 | ~2109处 (仅 views/) |
| 路由标题 | meta.title 硬编码中文 |
| 侧边栏/菜单 | meta.title 从数据库动态加载 |
| 布局组件 | Navbar/Sidebar/Settings 中有硬编码中文 |
| Element Plus 组件 | el-dialog title, el-button label, el-table column label 等 |
| 已有 i18n 方案 | 无 (未安装 vue-i18n) |
核心问题:
- 零 i18n 基础, 所有中文硬编码在 .vue 文件中
- Element Plus 自身也是中文 (需配置 locale)
- 菜单标题来自数据库, 需要数据库层面的多语言支持
1.3 移动端现状
| 项目 | 值 |
|---|---|
| 技术栈 | Vue 3 + Vite + Element Plus |
| 页面数量 | 14 个 .vue 文件 |
| 已有 i18n 方案 | 无 |
二、方案设计
2.1 总体架构
┌─────────────────────────────────────┐
│ 语言选择器 (全局) │
│ Cookie/Session 存储当前语言 │
└──────────┬──────────────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ zh_CN (默认)│ │ en_US (英语) │ │ vi_VN (越南语) │
└───────────┘ └──────────────┘ └──────────────┘
│ │ │
┌─────┴────────────────┴────────────────┴─────┐
│ │
┌────┴────┐ ┌──────┴──────┐
│ 前端 i18n │ │ 后端 i18n │
│ vue-i18n │ │ MessageSource│
└────┬────┘ └──────┬──────┘
│ │
▼ ▼
JSON 语言包文件 Properties 资源文件
(src/locales/) (i18n/messages_*.properties)
2.2 核心技术选型
前端
| 方案 | 选择 | 理由 |
|---|---|---|
| i18n 库 | vue-i18n v9 | Vue 3 官方推荐, 生态成熟, 与 Element Plus 完美集成 |
| 语言包格式 | JSON | 易编辑、易翻译、易从数据库导出、易做 CI/CD 检查 |
| 语言切换 | Cookie | 持久化, 刷新不丢失, 与后端 SessionLocaleResolver 保持一致 |
| Element Plus | 动态切换 locale | el-config-provider + setLocale API |
后端
| 方案 | 选择 | 理由 |
|---|---|---|
| i18n 引擎 | Spring MessageSource (已有) | 无需新增依赖, 已有 I18nConfig + MessageUtils |
| 资源文件格式 | Properties | Spring Boot 原生支持, 已有基础 |
| 语言切换 | Cookie/Header (新增) | 保持向后兼容, 同时支持 lang query param |
数据库 (菜单/字典多语言)
| 方案 | 选择 | 理由 |
|---|---|---|
| 菜单多语言 | 新建 sys_menu_i18n 表 |
不修改已有 sys_menu 表结构, 零侵入 |
| 字典多语言 | 新建 sys_dict_type_i18n + sys_dict_data_i18n 表 |
字典数据量大, 独立表便于管理 |
| 业务数据多语言 | 暂不支持 | 患者名、诊断名等业务数据不需多语言 |
三、详细设计
3.1 前端实现
3.1.1 目录结构
healthlink-his-ui/src/
├── locales/ # 语言包目录
│ ├── index.js # vue-i18n 配置 + 导出
│ ├── zh_CN.js # 简体中文 (基于现有硬编码提取)
│ ├── en_US.js # English
│ └── vi_VN.js # Tiếng Việt
├── plugins/
│ └── i18n.js # vue-i18n 插件初始化
├── store/modules/
│ ├── app.js # ← 新增: language 状态
│ └── settings.js # ← 新增: language 配置
├── layout/
│ └── components/
│ └── LanguageSwitcher.vue # 新增: 语言切换下拉组件
├── utils/
│ └── request.js # ← 修改: 请求头带 Accept-Language
└── ...views/** # ← 批量修改: 硬编码中文 → $t('key')
3.1.2 语言包结构设计
// locales/zh_CN.js 结构示例
export default {
// 系统通用
common: {
confirm: '确认',
cancel: '取消',
save: '保存',
delete: '删除',
edit: '编辑',
add: '新增',
search: '搜索',
export: '导出',
import: '导入',
reset: '重置',
submit: '提交',
close: '关闭',
tip: '提示',
success: '操作成功',
fail: '操作失败',
loading: '加载中...',
noData: '暂无数据',
},
// 登录/认证
auth: {
login: '登录',
username: '用户名',
password: '密码',
captcha: '验证码',
loginSuccess: '登录成功',
logout: '退出登录',
tokenExpired: '登录已过期,请重新登录',
},
// 系统管理
system: {
userManage: '用户管理',
roleManage: '角色管理',
menuManage: '菜单管理',
deptManage: '部门管理',
dictManage: '字典管理',
},
// 门诊挂号
registration: {
outpatientReg: '门诊挂号',
patientSearch: '患者检索',
registerSuccess: '挂号成功',
refund: '退号',
refundSuccess: '退号成功',
},
// 消息提示 (对应后端 PromptMsgConstant)
msgs: {
addSuccess: '{0}添加成功',
saveSuccess: '{0}保存成功',
deleteSuccess: '{0}删除成功',
operationSuccess: '{0}操作成功',
alreadyExists: '{0}已经存在',
operationFailed: '操作失败,请联系管理员',
dataDeletedByOthers: '操作失败,该数据已被他人删除,请刷新后重试',
dataModifiedByOthers: '操作失败,该数据已被他人更改,请刷新后重试',
noDuplicateSubmit: '请勿重复提交',
querySuccess: '查询成功',
},
// 菜单标题 (与 sys_menu 的 menu_name 对应, 用于前端硬编码部分)
menus: {
dashboard: '首页',
patientManage: '患者管理',
registration: '挂号收费',
outpatient: '门诊管理',
inpatient: '住院管理',
pharmacy: '药品管理',
// ... 所有菜单项
},
// Element Plus 组件
element: {
pagination: {
total: '共 {total} 条',
pageSize: '每页 {pageSize} 条',
validator: '每页 {n} 条,至少 1 条',
},
table: {
emptyText: '暂无数据',
},
dialog: {
confirm: '确定',
cancel: '取消',
},
upload: {
exceedSize: '上传文件大小超出限制',
fileNameExceedLength: '上传的文件名最长 {0} 个字符',
},
},
}
3.1.3 前端改造策略
Phase 1 — 基础设施 (不破坏现有功能)
- 安装
vue-i18n@9 - 创建
locales/目录和zh_CN.js(先搬运现有中文) - 配置
plugins/i18n.js - 修改
main.js引入 i18n - 修改
request.js添加Accept-Language请求头 - 新增
LanguageSwitcher.vue组件 - 修改
app.jsstore 增加language状态
Phase 2 — 核心页面改造 (逐步推进)
8. 登录页 views/login.vue → 全部 $t()
9. 布局组件 layout/** → 全部 $t()
10. 系统管理模块 views/system/** → 全部 $t()
11. 门诊模块 views/charge/**, views/doctorstation/** → 全部 $t()
12. 住院模块 views/inpatientNurse/** → 全部 $t()
13. 药品模块 views/drug/**, views/pharmacy*/** → 全部 $t()
14. 其余模块依次推进
Phase 3 — 辅助功能 15. Element Plus 组件语言包切换 16. 路由 meta.title 多语言化 17. 表单校验提示国际化 (Element Plus form rules)
3.1.4 语言切换流程
用户点击 LanguageSwitcher
↓
Cookie.set('language', 'en_US')
↓
Pinia store.app.language = 'en_US'
↓
vue-i18n.global.locale.value = 'en_US'
↓
Element Plus setLocale(en)
↓
Axios 请求头: Accept-Language: en-US
↓
页面响应式更新 (所有 $t() 自动重新渲染)
3.2 后端实现
3.2.1 资源文件扩展
healthlink-his-application/src/main/resources/i18n/
├── messages.properties # 默认 (英文 fallback)
├── messages_zh_CN.properties # 中文 (现有, 扩充)
├── messages_en_US.properties # 英文 (新建)
└── messages_vi_VN.properties # 越南语 (新建)
3.2.2 消息分类体系
# 1. 系统通用 (已有, 扩充)
apl.common.M00001 = {0} added successfully
apl.common.M00002 = {0} saved successfully
...
# 2. 用户认证
auth.login.success = Login successful
auth.user.not.exists = User does not exist / Incorrect password
auth.password.retry.limit.count = Password entered incorrectly {0} times
...
# 3. 文件上传
upload.exceed.maxSize = Upload file size exceeds limit
...
# 4. 权限
no.permission = You do not have permission for [{0}]
...
# 5. 挂号收费 (registration/payment)
apl.payment.M00001 = Actual payment amount does not match expected
apl.payment.M00003 = Please select a payment method
...
# 6. 药品管理 (pharmacy)
apl.pharmacy.outOfStock = Insufficient inventory for {0}
apl.pharmacy.dispenseSuccess = Dispensing successful
...
# 7. 住院管理 (inpatient)
...
# 8. 检查检验 (check/lab)
...
# 9. 手术麻醉 (surgery/anesthesia)
...
# 10. 电子病历 (emr)
...
# 11. 护理管理 (nursing)
...
# 12. 医保 (yb)
...
3.2.3 硬编码消息迁移策略
优先级 P0 — 高频路径 (登录、挂号、退号、缴费、退药)
SysLoginService.java— 登录相关 ~6处OutpatientRegistrationAppServiceImpl.java— 挂号/退号 ~10处PaymentRecServiceImpl.java— 收费/退费 ~30处
优先级 P1 — 核心业务 (药品、住院、手术、EMR)
- 药房 dispensense/approval ~50处
- 住院登记/结算 ~20处
- 手术管理 ~15处
- EMR 病程记录 ~10处
优先级 P2 — 其他模块
- 其余 ~700处按模块逐步迁移
迁移方式:
// Before:
return R.fail("请选择调配药师");
// After:
return R.fail(MessageUtils.message("apl.pharmacy.selectDispenser"));
3.2.4 语言检测优先级
1. Cookie "language" (最高优先级, 持久化)
2. HTTP Header "Accept-Language"
3. Query Param "lang" (已有, 向后兼容)
4. Session 中保存的语言
5. 默认 Locale.SIMPLIFIED_CHINESE
新增 LanguageCookieLocaleResolver 继承 SessionLocaleResolver:
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver clr = new CookieLocaleResolver("lang");
clr.setDefaultLocale(Constants.DEFAULT_LOCALE);
clr.setCookieMaxAge(Duration.ofDays(365));
return clr;
}
3.3 数据库多语言
3.3.1 sys_menu_i18n 表
CREATE TABLE IF NOT EXISTS sys_menu_i18n (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
menu_id BIGINT NOT NULL REFERENCES sys_menu(menu_id),
lang_code VARCHAR(10) NOT NULL, -- 'zh_CN', 'en_US', 'vi_VN'
menu_name VARCHAR(100) NOT NULL,
UNIQUE(menu_id, lang_code)
);
COMMENT ON TABLE sys_menu_i18n IS '菜单多语言表';
COMMENT ON COLUMN sys_menu_i18n.menu_id IS '关联 sys_menu.menu_id';
COMMENT ON COLUMN sys_menu_i18n.lang_code IS '语言代码';
COMMENT ON COLUMN sys_menu_i18n.menu_name IS '菜单名称(多语言)';
查询逻辑:
-- 优先取当前语言, 回退到中文, 再回退到默认
SELECT COALESCE(
(SELECT menu_name FROM sys_menu_i18n WHERE menu_id = m.menu_id AND lang_code = 'en_US'),
(SELECT menu_name FROM sys_menu_i18n WHERE menu_id = m.menu_id AND lang_code = 'zh_CN'),
m.menu_name
) AS menu_name
FROM sys_menu m;
3.3.2 sys_dict_data_i18n 表
CREATE TABLE IF NOT EXISTS sys_dict_data_i18n (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
dict_code BIGINT NOT NULL,
dict_sort INT NOT NULL,
lang_code VARCHAR(10) NOT NULL,
dict_label VARCHAR(100) NOT NULL,
dict_value VARCHAR(100) NOT NULL,
css_class VARCHAR(100),
list_class VARCHAR(100),
is_default CHAR(1) DEFAULT 'N',
UNIQUE(dict_code, dict_sort, lang_code)
);
COMMENT ON TABLE sys_dict_data_i18n IS '字典数据多语言表';
3.3.3 字典类型 i18n
CREATE TABLE IF NOT EXISTS sys_dict_type_i18n (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
dict_id BIGINT NOT NULL REFERENCES sys_dict_type(dict_id),
lang_code VARCHAR(10) NOT NULL,
dict_name VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL,
UNIQUE(dict_id, lang_code)
);
COMMENT ON TABLE sys_dict_type_i18n IS '字典类型多语言表';
四、实施计划
Sprint 1: 基础设施搭建 (预计 2-3 天)
| 任务 | 模块 | 工作量 | 负责人 |
|---|---|---|---|
| T1.1 安装 vue-i18n@9 | 前端 | 0.5h | 赵云 |
| T1.2 创建 locales/ 目录结构 + zh_CN.js | 前端 | 2h | 赵云 |
| T1.3 配置 plugins/i18n.js + main.js 集成 | 前端 | 1h | 赵云 |
| T1.4 扩展后端 messages_zh_CN.properties | 后端 | 2h | 关羽 |
| T1.5 创建 messages_en_US.properties (翻译 P0 消息) | 后端 | 3h | 关羽 |
| T1.6 创建 messages_vi_VN.properties (翻译 P0 消息) | 后端 | 3h | 关羽 |
| T1.7 改造 I18nConfig 支持 Cookie + Header | 后端 | 1h | 关羽 |
| T1.8 新增 LanguageSwitcher.vue 组件 | 前端 | 1h | 赵云 |
| T1.9 修改 request.js 添加 Accept-Language | 前端 | 0.5h | 赵云 |
| T1.10 编译验证 + 冒烟测试 | 全栈 | 1h | 张飞 |
产出: 语言切换功能可用, 登录页/布局组件中英文切换
Sprint 2: 核心模块国际化 (预计 3-4 天)
| 任务 | 模块 | 工作量 | 负责人 |
|---|---|---|---|
| T2.1 登录页 login.vue 全部 $t() | 前端 | 2h | 赵云 |
| T2.2 布局组件 (Navbar/Sidebar/Settings) 全部 $t() | 前端 | 3h | 赵云 |
| T2.3 Element Plus 组件语言包切换 | 前端 | 2h | 赵云 |
| T2.4 系统管理模块 views/system/** 全部 $t() | 前端 | 4h | 赵云 |
| T2.5 后端 P0 消息全部翻译 (auth/payment/upload/permission) | 后端 | 3h | 关羽 |
| T2.6 后端硬编码迁移 P0 (SysLoginService, Registration, Payment) | 后端 | 4h | 关羽 |
| T2.7 Flyway 迁移: sys_menu_i18n + sys_dict_*_i18n 表 | DBA | 1h | 荀彧 |
| T2.8 菜单/字典 Service 层支持多语言查询 | 后端 | 3h | 关羽 |
| T2.9 编译验证 + 冒烟测试 | 全栈 | 1h | 张飞 |
产出: 系统管理 + 登录 + 布局 中英文/越语可用
Sprint 3: 业务模块国际化 (预计 5-7 天)
| 任务 | 模块 | 工作量 | 负责人 |
|---|---|---|---|
| T3.1 门诊模块 (charge/doctorstation) 全部 $t() | 前端 | 6h | 赵云 |
| T3.2 门诊模块后端硬编码迁移 | 后端 | 4h | 关羽 |
| T3.3 门诊模块消息翻译 (en_US + vi_VN) | 后端 | 4h | 关羽 |
| T3.4 住院模块 (inpatientNurse) 全部 $t() | 前端 | 5h | 赵云 |
| T3.5 住院模块后端硬编码迁移 | 后端 | 3h | 关羽 |
| T3.6 住院模块消息翻译 (en_US + vi_VN) | 后端 | 3h | 关羽 |
| T3.7 药品模块 (drug/pharmacy) 全部 $t() | 前端 | 5h | 赵云 |
| T3.8 药品模块后端硬编码迁移 | 后端 | 4h | 关羽 |
| T3.9 药品模块消息翻译 (en_US + vi_VN) | 后端 | 3h | 关羽 |
| T3.10 EMR/病历模块 全部 $t() | 前端 | 4h | 赵云 |
| T3.11 EMR 后端硬编码迁移 | 后端 | 2h | 关羽 |
| T3.12 EMR 消息翻译 (en_US + vi_VN) | 后端 | 2h | 关羽 |
| T3.13 其余模块 (检验/检查/手术/护理/质控...) 全部 $t() | 前端 | 10h | 赵云 |
| T3.14 其余模块后端硬编码迁移 | 后端 | 8h | 关羽 |
| T3.15 其余模块消息翻译 (en_US + vi_VN) | 后端 | 8h | 关羽 |
| T3.16 路由 meta.title 多语言化 | 前端 | 2h | 赵云 |
| T3.17 表单校验提示国际化 | 前端 | 2h | 赵云 |
| T3.18 编译验证 + 全模块冒烟测试 | 全栈 | 2h | 张飞 |
产出: 全模块中英文/越语可用
Sprint 4: 移动端 + 验收 (预计 2-3 天)
| 任务 | 模块 | 工作量 | 负责人 |
|---|---|---|---|
| T4.1 移动端 healthlink-his-mobile 安装 vue-i18n | 移动端 | 0.5h | 赵云 |
| T4.2 移动端 14 个页面全部 $t() | 移动端 | 4h | 赵云 |
| T4.3 移动端语言切换器 | 移动端 | 1h | 赵云 |
| T4.4 全模块端到端测试 (中文→英文→越语) | 测试 | 2h | 张飞 |
| T4.5 性能测试 (语言切换不卡顿) | 测试 | 1h | 张飞 |
| T4.6 验收 + 文档更新 | 华佗 | 1h | 华佗 |
五、工作量估算
| 阶段 | 前端 | 后端 | DBA | 测试 | 总计 |
|---|---|---|---|---|---|
| Sprint 1 | 5h | 10h | 0 | 0 | 15h |
| Sprint 2 | 12h | 11h | 1h | 0 | 24h |
| Sprint 3 | 37h | 29h | 0 | 0 | 66h |
| Sprint 4 | 6h | 0 | 0 | 3h | 9h |
| 合计 | 60h | 50h | 1h | 3h | ~114h (约 2-3 周) |
六、风险与应对
| 风险 | 影响 | 应对 |
|---|---|---|
| 2109+ 处前端硬编码修改量大 | 前端工作量大 | 分批推进, 优先核心模块, 非核心可延后 |
| 851+ 处后端硬编码迁移 | 后端工作量大 | 按模块优先级分批迁移, P0→P1→P2 |
| 数据库菜单/字典数据量大 | 迁移困难 | 提供 SQL 脚本自动从现有数据初始化 |
| 翻译质量 (越南语) | 用户体验差 | 先机器翻译, 上线后逐步人工校对 |
| 语言包文件过大 | 加载性能 | 按需加载 (lazy load), 分包加载 |
| 第三方组件未国际化 | 部分中文残留 | Element Plus 已支持, 其他按需处理 |
| 动态路由菜单多语言 | 需改后端 | 菜单查询 SQL 增加 COALESCE 回退逻辑 |
七、扩展性设计
7.1 新增语言只需 3 步
- 创建
locales/{langCode}.js(前端) +messages_{langCode}.properties(后端) - 在
locales/index.js注册新语言 - 在
I18nConfig中确认默认值 (无需改动)
7.2 翻译管理工具 (可选增强)
后续可接入:
- Crowdin / Lokalise: 在线翻译协作平台
- xliff-js: 导出/导入 XLIFF 格式
- 自定义脚本: 从
.vue文件自动提取所有中文, 生成翻译模板
7.3 数据库多语言扩展
如需其他业务表多语言 (如科室名称、诊断名称), 可按相同模式扩展:
-- 通用多语言表模板
CREATE TABLE {table}_i18n (
id BIGINT PRIMARY KEY,
source_id BIGINT NOT NULL,
lang_code VARCHAR(10) NOT NULL,
field_name VARCHAR(100) NOT NULL,
field_value TEXT NOT NULL,
UNIQUE(source_id, lang_code, field_name)
);
八、验证标准
8.1 功能验证
- 语言切换器正常显示 (中/英/越)
- 切换后页面所有中文文本变为对应语言
- 刷新页面后语言设置保持 (Cookie)
- Element Plus 组件 (分页/弹窗/表格/表单) 均为对应语言
- 后端 API 返回的错误消息为当前语言
- 登录页、系统管理、门诊、住院、药品、EMR 等核心模块全部覆盖
- 移动端 14 个页面全部支持多语言
8.2 兼容性验证
- 不传 lang 参数时默认中文 (向后兼容)
- 现有前端页面不因 i18n 改造而样式错乱
- 数据库菜单查询多语言回退正常 (无翻译 → 显示中文 → 显示默认)
- 编译无 ERROR, 无新增 WARNING
8.3 性能验证
- 语言切换响应 < 100ms
- 首屏加载时间增加 < 50KB (gzip)
- 无明显内存泄漏
九、附录
A. 前端需提取的中文文本类型
- ElMessage 提示文本 (~2109处)
- ElMessageBox 确认/提示文本
- el-dialog title 属性
- el-button 文本内容
- el-table column label
- el-form label / placeholder / 校验提示
- 路由 meta.title (部分硬编码)
- 布局组件 中的固定文本 (Sidebar/Navbar/Settings)
- Element Plus 自身组件文本 (pagination/table/dialog)
- 页面标题 h1/h2 等
B. 后端需迁移的硬编码消息类型
- R.fail("中文") — 业务错误 (~851处)
- R.ok(data, "中文") — 成功消息 (~150处, 已有 PromptMsgConstant 覆盖)
- Service 层抛出的 ServiceException — 异常消息
- 日志中的中文 — 可选, 生产日志建议英文
C. 消息键命名规范
{模块}.{子类}.{编号}
示例:
apl.common.M00001 → APL 通用消息
auth.login.success → 认证-登录-成功
payment.collect.fail → 收费-缴费-失败
pharmacy.dispense.select_pharmacist → 药品-发药-选择药师
registration.refund.success → 挂号-退号-成功