Files
his/md/architecture/MULTILANG_I18N_DESIGN.md
chenqi 83f340b6bb feat(i18n): 实现门诊增强和门诊财务国际化功能
- 在门诊增强页面添加表格列的国际化标签
- 在门诊增强页面添加操作按钮的国际化文本
- 在门诊财务日结结算页面实现表单字段的国际化
- 在门诊财务日结结算页面实现表格列标题的国际化
- 在门诊财务日结结算页面添加操作按钮的国际化
- 集成 vue-i18n 并在组件中使用国际化方法
- 更新日结详情提示消息为国际化文本
- 实现导出文件名和成功消息的国际化
2026-06-28 06:58:53 +08:00

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 — 基础设施 (不破坏现有功能)

  1. 安装 vue-i18n@9
  2. 创建 locales/ 目录和 zh_CN.js (先搬运现有中文)
  3. 配置 plugins/i18n.js
  4. 修改 main.js 引入 i18n
  5. 修改 request.js 添加 Accept-Language 请求头
  6. 新增 LanguageSwitcher.vue 组件
  7. 修改 app.js store 增加 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 步

  1. 创建 locales/{langCode}.js (前端) + messages_{langCode}.properties (后端)
  2. locales/index.js 注册新语言
  3. 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. 前端需提取的中文文本类型

  1. ElMessage 提示文本 (~2109处)
  2. ElMessageBox 确认/提示文本
  3. el-dialog title 属性
  4. el-button 文本内容
  5. el-table column label
  6. el-form label / placeholder / 校验提示
  7. 路由 meta.title (部分硬编码)
  8. 布局组件 中的固定文本 (Sidebar/Navbar/Settings)
  9. Element Plus 自身组件文本 (pagination/table/dialog)
  10. 页面标题 h1/h2 等

B. 后端需迁移的硬编码消息类型

  1. R.fail("中文") — 业务错误 (~851处)
  2. R.ok(data, "中文") — 成功消息 (~150处, 已有 PromptMsgConstant 覆盖)
  3. Service 层抛出的 ServiceException — 异常消息
  4. 日志中的中文 — 可选, 生产日志建议英文

C. 消息键命名规范

{模块}.{子类}.{编号}

示例:
apl.common.M00001        → APL 通用消息
auth.login.success       → 认证-登录-成功
payment.collect.fail     → 收费-缴费-失败
pharmacy.dispense.select_pharmacist  → 药品-发药-选择药师
registration.refund.success  → 挂号-退号-成功