Skip to content

国际化 ​

柠檬ERP 的界面和后端提示支持简体中文(默认)和英文。前端用 vue-i18n 语言包翻译界面文字、菜单和字典;后端用 Spring 标准的 MessageSource + 资源包,按请求头 Accept-Language 返回对应语言的提示。微信小程序(lemon-mp)使用自己的最小实现,与 Web 端共用字典英文译文。本页说明整体架构、日常开发中的写法、小程序的差异、新增一种语言的步骤和检查脚本。

最终用户看到的效果见用户手册 界面介绍 → 语言切换。

整体架构 ​

环节做法
选择语言顶栏和登录页右上角的 LangSwitch 组件;保存在浏览器 localStorage 的 lemon_erp_locale(值为 JSON 字符串,如 "en-US"),切换后刷新页面
初始语言上次选择的语言;没有选择过时,navigator.language 以 en 开头用 en-US,其他用 zh-CN
前端界面src/locales/<语言>/<模块>.js 语言包,模板中 $t('键'),脚本中 t('键')
菜单、字典数据库中保存中文;英文译文在 locales/en-US/menu.js(按菜单 ID)和 dict.js(按字典类型和值),没有译文时显示数据库中的名称
Ant Design、dayjsApp.vue 按界面语言选择 ant-design-vue 和 dayjs 的语言包(日期选择、分页、空状态等组件内置文字)
请求utils/request.js 的请求拦截器给每个请求加 Accept-Language: <界面语言>(浏览器默认发送的是系统语言,这里改为界面语言);数据大屏的独立 axios 实例(api/bi/screen.js)同样带上
后端消息各模块 src/main/resources/i18n/<模块>.properties(中文)和 <模块>_en.properties(英文),common/i18n/Messages 自动发现全部资源包
后端语言解析framework/config/I18nConfig:AcceptHeaderLocaleResolver(支持 zh-CN、zh、en,默认 zh-CN)+ 一个在安全过滤器之前设置 LocaleContextHolder 的过滤器
接口返回BizException、校验注解、全局异常处理、Messages.get 输出的文字按请求语言返回,R.msg 即为对应语言的提示

不翻译的内容

存入数据库的内容不翻译:站内消息、操作日志标题、单据和凭证摘要、自动生成的业务数据、任务执行日志、推送到钉钉等平台的消息,一律保存中文(不能按发起请求的用户的语言生成,否则同一张表里中英文混杂)。用户录入的数据(物料名称、备注等)原样显示。

前端 ​

语言包目录与命名 ​

lemon-web/src/locales
├── index.js            # createI18n、LOCALES、currentLocale / isZh / t / te / tt / setLocale
├── zh-CN
│   ├── common.js       # 通用词:action 操作、field 字段、status、msg 提示、placeholder、rule 校验、table、unit
│   ├── layout.js       # 布局、请求拦截器的提示(登录过期、网络错误等)
│   ├── route.js        # 固定路由(登录、首页、个人中心、数据大屏、404 等)的标题
│   ├── components.js   # 通用组件(FormDrawer、ImportModal、Excel 等)
│   ├── print.js        # 打印:name 中是打印名称(中文即匹配键)
│   ├── login.js、profile.js、message.js、mobile.js、screen.js、portal.js、dealer.js
│   ├── system.js、monitor.js、workflow.js、dashboard.js
│   ├── wms.js          # 内容多的模块可以拆到 _wms/ 下再合并导出(顶层键不能重复)
│   └── scm.js、mes.js、qms.js、fin.js、hr.js、crm.js、pm.js、bi.js
└── en-US
    ├── 与 zh-CN 同名的全部文件(键完全一致)
    ├── menu.js         # 菜单名称译文:{ 菜单ID: '英文' }
    └── dict.js         # 字典标签译文:{ 字典类型: { 字典值: '英文' } }
  • 每个文件默认导出一个对象,文件名就是键的第一段:locales/zh-CN/fin.js 中的 voucher.title 对应 t('fin.voucher.title')。
  • 键按 <模块>.<页面或组件>.<文字> 组织,同一模块多个页面共用的放在 <模块>.common 下;通用的操作、字段、状态、提示优先复用 common.*,不要在每个模块中重复定义「新增」「删除」「保存成功」。
  • index.js 用 import.meta.glob('./*/*.js', { eager: true }) 加载 locales/<语言>/ 下的一级文件,全部语言包在构建时打包;子目录(如 _wms/)中的文件需要由同名的一级文件导入合并。
  • 中文的菜单和字典直接用数据库中的名称,所以 zh-CN 下没有 menu.js、dict.js。

在代码中翻译 ​

位置写法
模板{{ $t('wms.warehouse.createTitle') }}、:label="$t('common.field.status')"
<script setup>import { useI18n } from 'vue-i18n',const { t } = useI18n()
普通 js 模块(store、utils、components/<模块>/xxx.js、路由)import { t } from '@/locales'
有译文时翻译,否则用原值tt(键, 原值)(菜单、字典等数据库中的名称)
判断当前语言isZh()、currentLocale()(如 toLocaleString(currentLocale()))
vue
<a-form-item :label="$t('wms.common.warehouse')" name="keyword">
  <a-input v-model:value="query.keyword" :placeholder="$t('wms.flow.codeOrName')" allow-clear />
</a-form-item>
<a-popconfirm :title="$t('wms.warehouse.confirmDelete', { name: record.name })" @confirm="remove(record.id)">
js
const { t } = useI18n()

// 表格列、下拉选项、校验规则可以直接在模块顶层用 t() 定义:切换语言会刷新整个页面,不需要写成 computed
const columns = [
  { title: t('wms.common.warehouse'), key: 'name', width: 200 },
  { title: t('common.field.type'), key: 'type', width: 150, dict: 'wms_warehouse_type' },
  { title: t('common.action.operation'), key: 'action', width: 220, fixed: 'right' }
]
const rules = {
  code: [{ required: true, message: t('wms.warehouse.inputCode') }],
  name: [{ required: true, whitespace: true, message: t('common.rule.required', { name: t('wms.warehouse.name') }) }]
}

message.success(t('common.msg.deleteSuccess'))

插值与特殊字符:

  • 参数写 {name}:confirmDelete: '确定删除仓库「{name}」?',调用 t('wms.warehouse.confirmDelete', { name: record.name })。
  • @、|、$、{、} 是 vue-i18n 消息语法的特殊字符,文字中需要时写成字面量插值,如邮箱示例 'admin{'@'}example.com'。
  • 整句带参数翻译,不要拼接:中英文语序不同,t('a') + name + t('b') 在英文中很难读通。写成一个带 {name} 的完整句子。
  • 复数、量词:英文没有「个、张、单」这类量词,中文的 {n} 张 在英文中可以是 {n} orders,按语义分别翻译。

路由、菜单、字典、表格列 ​

  • 固定路由:router/index.js 中 meta: { title: t('route.login') },标题在 route.js。
  • 菜单:登录后 store/user.js 把 /auth/menus 返回的菜单名称经 tt('menu.' + 菜单ID, 数据库名称) 翻译后再生成路由、侧边栏、标签和菜单搜索;标签栏中已保存的标签在刷新后按当前语言重新取标题。新增内置菜单时,在 locales/en-US/menu.js 中按 sql/init.sql 中的菜单 ID 加译文(注释写中文名称)。
  • 字典:store/dict.js 加载字典后把标签替换为 tt('dict.<类型>.<值>', 数据库标签),useDict、DictTag、toOptions、导出 Excel、导入时按标签匹配都自动使用译文。新增内置字典时在 locales/en-US/dict.js 中加译文。数据大屏等直接拿到字典值的地方用 components/screen/screen.js 的 dictLabel / withDictLabel。
  • 表格列:列 title 用 t(),导出 Excel 的表头、导入模板的表头直接取列的 title,所以导出、导入跟随界面语言(导入时也按当前语言的表头识别列)。
  • 菜单管理、字典管理、业务字典页面显示数据库中的原文,这是维护数据的页面,不做翻译。租户在「业务字典」中新增的取值没有英文译文,英文界面显示租户填写的名称;译文按字典类型和键值匹配,所以改过名称的内置取值在英文界面仍显示原来的译文。

打印 ​

打印模板按名称匹配字段、列和签字栏,而名称要在两种语言下都能匹配已保存的模板和上传的 JRXML,所以:

  • 匹配键始终是中文名称:utils/printTemplates.js 的 PRINT_TYPES 由 locales/zh-CN/print.js 的 name 生成;printLabel(中文名称) 得到当前语言的显示名称。
  • 调用 printDocs 时,单据中的名称可以直接写中文(显示时自动翻译),也可以传入 t() 翻译后的名称(print.js 按语言包反查回中文匹配键),也可以显式指定匹配键:字段 [显示名称, 值, 是否整行, 匹配键]、签字栏 [显示名称, 值, 匹配键]、明细列 { title, id: 匹配键 }。
  • 新增可配置模板的单据时,名称加到 locales/zh-CN/print.js 和 en-US/print.js 的 name 中,再在 PRINT_TYPES 中引用,见 单据编号与打印。
  • 后端 JasperReports 中固定输出的文字(合计、备注、冒号等)由 PrintService / PrintDesigner 用 Messages.get 按请求语言生成。

其他 ​

  • 金额大写(moneyUpper)、中文模块名去掉「管理」后缀等只对中文有意义的处理,用 isZh() 判断。
  • 数字、日期的本地化格式用 toLocaleString(currentLocale(), …)、Intl.NumberFormat(currentLocale(), …)。
  • 有意保留的中文(如按中文匹配键判断、中文大写金额、中文单位候选)在行内写 // i18n-ignore,检查脚本会跳过该行。

后端 ​

资源包 ​

每个模块在 src/main/resources/i18n/ 下放两个文件,文件名即模块名(不带下划线):

lemon-modules/lemon-crm/src/main/resources/i18n
├── crm.properties       # 默认语言:简体中文
└── crm_en.properties    # 英文
properties
# crm 模块的消息(默认语言:简体中文)。键为 <模块>.<分类>.<名称>,分类:error 业务异常、valid 校验、text 返回前端的文字、label 名称;
# 参数写作 {0},按 MessageFormat 格式化,文字中的单引号要写成两个

crm.error.campaignDoesNotExist=市场活动不存在
crm.error.contractAlreadyLinkedSalesOrder=合同已关联销售订单 {0}
crm.valid.companyNameRequired=公司名称不能为空
crm.label.salesContract=销售合同
properties
crm.error.campaignDoesNotExist=Campaign does not exist
crm.error.contractAlreadyLinkedSalesOrder=Contract is already linked to sales order {0}
crm.valid.companyNameRequired=Company name is required
crm.label.salesContract=Sales Contract
  • 键为 <模块>.<分类>.<名称>,前两段只能是小写字母(检查脚本按 ^[a-z]+\.[a-z]+\.[A-Za-z0-9._]+$ 校验)。分类:
分类用途示例
errorBizException 的业务异常crm.error.leadDoesNotExist
valid校验注解的消息crm.valid.companyNameRequired
text其他返回前端的文字common.text.systemBusyTryAgainLater
label名称:模块名、单据名、业务参数名、待办标题、任务名称等crm.label.salesContract
enum枚举的显示名称framework.enum.CaptchaMode.afterFailure
  • 文件为 UTF-8(ResourceBundleMessageSource.setDefaultEncoding("UTF-8")),直接写中文,不需要 \u 转义。
  • 始终按 MessageFormat 格式化(alwaysUseMessageFormat):参数写 {0}、{1},文字中的单引号必须写成两个(order''s),否则单引号及其后的占位符会被当作字面量。
  • 参数一律先 String.valueOf 再填入,数字不会按语言加千分位(与原来的中文提示一致);需要格式化的数字在调用前自行格式化。
  • 中英文两个文件的键必须一一对应、占位符一致;英文中不能出现中文。
  • 资源包按模块拆分,各模块只登记自己用到的键;不同文件中不能有内容不同的同名键。
  • 键不存在时返回键本身(useCodeAsDefaultMessage),页面上看到 xxx.error.yyy 就是漏登记了。

Messages 工具类 ​

com.lemon.erp.common.i18n.Messages:

成员说明
messageSource()启动时扫描 classpath*:i18n/*.properties,不带下划线的文件各是一个资源包;同一实例由 I18nConfig 注册为 Spring 的 messageSource(Bean Validation 也用它插值)
get(键, 参数…)按当前请求的语言取消息
get(Locale, 键, 参数…)按指定语言取消息;存库内容用 get(Messages.DEFAULT_LOCALE, 键) 取中文
locale()当前请求的语言;不在请求中(定时任务、异步线程)时为 DEFAULT_LOCALE(简体中文),不使用服务器的系统语言
isDefaultLanguage()当前是否为中文
exists(Locale, 键)键是否存在
DEFAULT_LOCALE / SUPPORTED_LOCALESzh_CN / [zh_CN, zh, en]

业务异常 ​

BizException 的第一个参数是消息键,其余为参数:

java
throw new BizException("crm.error.leadDoesNotExist");
throw new BizException("crm.error.contractAlreadyLinkedSalesOrder", c.getOrderNo());
// 指定错误码
throw new BizException(R.FORBIDDEN, "framework.error.supplierPortalOnlySupplierAccounts");
  • GlobalExceptionHandler 返回 R.fail(code, e.getLocalizedMessage()),即按请求语言取消息;getMessage() 为中文,用于日志。
  • 参数本身也是需要翻译的名称时,先用 Messages.get 翻译再传入:
java
throw new BizException("system.error.jobRunning", Messages.get(handler.name()));
  • 全局异常处理中的通用提示(没有权限、参数格式不正确、缺少参数、数据已存在、上传文件过大、系统繁忙等)在 common.properties 的 common.text.* 中。

校验注解 ​

message 写成 {键},由 Bean Validation 通过 messageSource 按请求语言插值:

java
public record LeadRequest(
        @NotBlank(message = "{crm.valid.companyNameRequired}") @Size(max = 128, message = "{crm.valid.companyNameCannotExceedCharacters}") String company,
        @Email(message = "{crm.valid.invalidEmailFormat}") String email) {
}
properties
crm.valid.companyNameCannotExceedCharacters=公司名称不能超过128个字符

长度、范围等数字直接写在消息中(与注解的 max 保持一致),不使用 {max} 这类注解属性占位符。Excel 导入(ImportExecutor)复用同一套校验,错误信息同样按请求语言输出。

返回前端的其他文字与名称 ​

  • 接口直接返回的提示文字(如工时超出的警告):Messages.get("pm.text.hoursLeaveTimesheetPlusLeaveExceeds", …)。
  • 跨模块登记的名称在声明处写消息键,输出处 Messages.get:编码规则目录 DocNoCatalog(模块名和单据名)、业务参数 BizRule(模块、名称、说明)、下拉参数的取值 OptionEnum.label()、记录类型 RecordType、流程业务 FlowBusiness(名称和变量 label)、首页待办 TodoItem.title、定时任务 JobHandler.name()、参数设置 Setting、引用检查 ReferenceChecker 返回的单据名称、门户类型 PortalKind 等。键登记在声明它的模块的资源包中:
java
@Bean
@Order(6)
public DocNoCatalog crmDocNoCatalog() {
    return DocNoCatalog.of("crm.label.crm",
            doc("XL", "crm.label.lead"),
            doc("SJ", "crm.label.opportunity"));
}

@Bean
public FlowBusiness contractFlow() {
    return FlowBusiness.of("crm_contract", "crm.label.salesContractApproval",
            number("amount", "crm.label.contractAmount"),
            text("contractNo", "crm.label.contractNo"));
}
  • 枚举的显示名称:枚举保存消息键(如 CaptchaMode.AFTER_FAILURE("framework.enum.CaptchaMode.afterFailure")),输出时 Messages.get(e.label())。

存入数据库的内容 ​

站内消息、操作日志标题(@OperLog(title = …))、任务执行结果、自动生成的摘要和备注等保存中文,在行内写 // i18n-ignore 说明是有意保留的中文(检查脚本跳过):

java
messageSender.send(List.of(owner), Message.of(MessageCategory.REMIND, "今天需要跟进客户「" + r.get("name") + "」", // i18n-ignore 站内消息存库
        "计划跟进时间 " + r.get("nextFollowTime"), "/crm/customer"));

@OperLog(title = "仓库管理", type = OperType.INSERT) // i18n-ignore

需要复用资源包中的名称写入数据库时,用 Messages.get(Messages.DEFAULT_LOCALE, 键),例如 SysJobService 写入任务表的任务名称、登录日志中的登录方式。

语言的传递与解析 ​

  1. 前端每个请求带 Accept-Language: zh-CN 或 en-US。
  2. I18nConfig 注册的过滤器(顺序 -104:在 RequestContextFilter 之后、Spring Security 过滤器链之前)用 AcceptHeaderLocaleResolver 解析语言并写入 LocaleContextHolder,所以安全过滤器输出的 401、403 提示也能按语言返回;Spring MVC 处理请求时用同一个 LocaleResolver。
  3. 解析规则:先精确匹配 SUPPORTED_LOCALES,再按语言匹配不带国家的项,所以 en-US、en-GB、en 都使用英文资源包,zh-TW 使用中文;不支持的语言、没有请求头时为 zh-CN。
  4. 定时任务、@Async 异步线程中没有请求,Messages.locale() 为中文。

后端测试默认中文;lemon-admin 的 I18nTests 验证了按 Accept-Language 返回业务异常、校验注解、安全过滤器的英文提示。

小程序 ​

微信小程序(lemon-mp)同样支持简体中文和英文,但小程序环境不允许 eval / new Function,不能运行 vue-i18n 的消息编译,因此使用 lemon-mp/src/locales/index.js 中的最小实现,详见 微信小程序开发 → 多语言。

环节Web 端小程序
语言包lemon-web/src/locales/<语言>/<模块>.jslemon-mp/src/locales/<语言>/<区>.js(common、approval、hr、pm、crm、wms、mes),键的第一段是文件名
翻译函数vue-i18n 的 $t / t / te / tt自己实现的 t(键, 参数)、te,模板中 $t;只支持 {name} 插值,没有复数、链接消息,文字中的 @、| 不需要转义
选择语言顶栏切换,localStorage 的 lemon_erp_locale,切换后刷新页面「我的 → 语言」,uni.setStorageSync('lemon_mp_locale'),locale 是 ref,切换后立即生效
初始语言navigator.languageuni.getSystemInfoSync().language,en 开头为英文
字典en-US/dict.js直接引用 Web 端的 @web/locales/en-US/dict.js,dictLabel(type, value, fallback)
菜单en-US/menu.js不使用菜单,工作台入口名称写在小程序语言包中
页面标题、底部导航路由标题pages.json 只能写中文,运行时用 useTitle(键)、applyTabBar() 按语言设置
请求Accept-Language: <界面语言>同样由 utils/request.js 加上,后端提示按语言返回

新增内置字典时,lemon-web/src/locales/en-US/dict.js 中的译文同时供 Web 端和小程序使用,不需要在小程序中重复登记。推送到微信的订阅消息内容来自站内消息,与钉钉等平台的推送一样保持中文。

新增页面或提示时要做什么 ​

改动前端后端
新页面页面文字全部用 $t / t,键加到 zh-CN/<模块>.js 和 en-US/<模块>.js无
小程序新页面页面文字用 t / $t,键加到 lemon-mp/src/locales/zh-CN/<区>.js 和 en-US/<区>.js;页面标题用 useTitle无
新菜单(内置)en-US/menu.js 按菜单 ID 加译文init.sql 中登记菜单(中文)
新字典(内置)en-US/dict.js 加 字典类型: { 值: '英文' }init.sql 中登记字典(中文)
新的业务校验—throw new BizException("模块.error.xxx", 参数),两个资源包加键
请求体校验—message = "{模块.valid.xxx}",两个资源包加键
编码规则、业务参数、待办、定时任务、流程业务等登记—名称写消息键,两个资源包加键
站内消息、操作日志—保持中文,行内 // i18n-ignore
可配置的打印单据zh-CN/print.js、en-US/print.js 的 name 加名称—

提交前运行两个检查脚本(见下文;修改了小程序时再运行 lemon-mp 的 npm run i18n:check),确保零问题;并在浏览器中切换为 English 检查一遍页面布局,英文通常比中文长,按钮、列宽、标签宽度可能需要调整。

新增一种语言 ​

以新增日文(ja-JP)为例:

  1. 前端语言包:新建 lemon-web/src/locales/ja-JP/,复制 en-US/ 下的全部文件(包括 menu.js、dict.js)并翻译,键与 zh-CN 完全一致。
  2. 注册语言:locales/index.js 的 LOCALES 加 { value: 'ja-JP', label: '日本語', short: '日' };需要按浏览器语言自动选择时,修改 initialLocale() 的判断。
  3. 组件库语言:App.vue 中引入 ant-design-vue/es/locale/ja_JP 和 dayjs/locale/ja,按当前语言选择(现在用 isZh() 二选一,改为按 currentLocale() 映射)。
  4. 检查脚本:scripts/check-i18n.mjs 的 LANGS 加上 ja-JP。
  5. 后端:Messages.SUPPORTED_LOCALES 加上 Locale.JAPANESE;为每个模块新建 src/main/resources/i18n/<模块>_ja.properties(键与默认资源包一致)。scripts/check_i18n.py 目前只比对默认资源包和 _en,需要按同样方式加上 _ja 的检查。
  6. 打印:locales/ja-JP/print.js 的 name 翻译显示名称即可,匹配键仍是中文,不需要改 printTemplates.js。
  7. 检查只对中文有意义的分支(isZh())在新语言下的表现,例如金额大写、单位候选、宽度调整(views/monitor/server/index.vue 的标签宽度)。

检查脚本 ​

前端:check-i18n.mjs ​

bash
cd lemon-web
npm run i18n:check                       # 检查整个 src
node scripts/check-i18n.mjs src/views/crm src/components/crm   # 只检查指定目录或文件

检查内容(有问题时退出码为 1,并输出「共 N 个问题」;没有问题输出「多语言检查通过」):

标记内容
[en-US 缺少] / [zh-CN 缺少]两种语言的语言包键不一致(menu、dict 只有译文,不检查)
[语言 特殊字符]文字中出现未转义的 @、|、$
[语言 缺少] 键(文件)代码中 t('…')、$t('…')、tt('…')、te('…') 使用的字面量键在语言包中不存在(拼接出来的动态键不检查)
[中文] 文件:行号代码中残留的中文(去掉注释和 console 输出后);有意保留的在行内写 i18n-ignore
[en-US/menu.js 缺少] / [en-US/dict.js 缺少]sql/init.sql 中 INSERT INTO sys_menu 的菜单、INSERT INTO sys_dict_data 的字典数据在英文译文中没有对应的键

npm run i18n:check 在多语言检查之后还会执行 node scripts/gen-enums.mjs --check,检查 src/constants/enums.js 是否与后端枚举一致,不一致时提示运行 npm run gen:enums(见 前端开发规范)。

小程序:check-i18n.mjs ​

bash
cd lemon-mp
npm run i18n:check

检查 zh-CN 与 en-US 的键是否一致([en-US 缺少] / [zh-CN 缺少])、代码中 t('…') / $t('…') / te('…') 使用的字面量键是否存在([语言 缺少] 键(文件))、代码中残留的中文([中文] 文件:行号,行内 i18n-ignore 的除外)。pages.json、manifest.json 只能写中文,不检查。没有问题时输出「多语言检查通过」。

后端:check_i18n.py ​

bash
python3 scripts/check_i18n.py                              # 检查全部 lemon-* 模块
python3 scripts/check_i18n.py lemon-modules/lemon-crm      # 只检查指定模块的 Java 代码(资源包始终全部检查)
标记内容
[资源包]键格式不对、中英文键不一致、占位符不一致、英文中有中文、单引号未写成两个、不同文件中同名键内容不同
[缺少键]new BizException("键")、Messages.get("键")、校验注解 message = "{键}" 使用的键不存在
[中文]Java 代码中残留的中文字符串字面量(注释和 log.xxx(...) 除外);有意保留的在行内写 // i18n-ignore

没有问题时输出「后端国际化检查通过」。

常见坑 ​

  • 页面上显示了键本身(如 crm.error.xxx、wms.warehouse.xxx):键漏登记,或登记在了另一个语言的文件中。运行检查脚本即可定位。
  • 英文提示中缺了参数或出现 {0}:英文文字中有单个单引号(如 order's),MessageFormat 把单引号之后当作字面量;写成 order''s。
  • vue-i18n 报编译错误或文字被截断:文字中有 @、|、{,按 {'@'} 的方式转义。
  • 前端切换为英文后后端提示仍是中文:直接用 axios / fetch 发起的请求没有带 Accept-Language,请统一用 @/utils/request(数据大屏这类独立实例要自己带请求头)。
  • 定时任务、异步线程中取到的总是中文:这是预期行为,那里没有请求语言;生成的内容会存库,本来就应该是中文。
  • 把翻译后的文字存进了数据库:例如用 Messages.get(键) 拼接站内消息标题,英文用户触发时会存入英文。存库内容用中文字面量(// i18n-ignore)或 Messages.get(Messages.DEFAULT_LOCALE, 键)。
  • 打印模板匹配不上:把翻译后的显示名称当成匹配键。匹配键必须是 print.js 中的中文名称,见上文「打印」。
  • 导入模板与界面语言不一致:导入按当前语言的列标题匹配,用中文模板在英文界面导入会提示缺少列,这是预期行为;列 title 不要写死中文。
  • 拼接句子:t('a') + n + t('b') 在英文中语序不对,写成一个带参数的完整句子。
  • 新增内置菜单、字典后英文界面显示中文:忘了在 en-US/menu.js、dict.js 中加译文。npm run i18n:check 会按 init.sql 中的 INSERT INTO sys_menu / sys_dict_data 逐条检查,提示 [en-US/menu.js 缺少]、[en-US/dict.js 缺少];用户在页面上新增的菜单、租户新增的业务字典取值没有译文,英文界面显示原名称。
  • 英文界面布局错乱:英文更长,表格列宽、a-descriptions 标签宽度、按钮组可能放不下,检查窄屏和英文下的显示。

基于 Apache-2.0 协议开源