国际化
柠檬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、dayjs | App.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())) |
<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)">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 # 英文# crm 模块的消息(默认语言:简体中文)。键为 <模块>.<分类>.<名称>,分类:error 业务异常、valid 校验、text 返回前端的文字、label 名称;
# 参数写作 {0},按 MessageFormat 格式化,文字中的单引号要写成两个
crm.error.campaignDoesNotExist=市场活动不存在
crm.error.contractAlreadyLinkedSalesOrder=合同已关联销售订单 {0}
crm.valid.companyNameRequired=公司名称不能为空
crm.label.salesContract=销售合同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._]+$校验)。分类:
| 分类 | 用途 | 示例 |
|---|---|---|
error | BizException 的业务异常 | 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_LOCALES | zh_CN / [zh_CN, zh, en] |
业务异常
BizException 的第一个参数是消息键,其余为参数:
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翻译再传入:
throw new BizException("system.error.jobRunning", Messages.get(handler.name()));- 全局异常处理中的通用提示(没有权限、参数格式不正确、缺少参数、数据已存在、上传文件过大、系统繁忙等)在
common.properties的common.text.*中。
校验注解
message 写成 {键},由 Bean Validation 通过 messageSource 按请求语言插值:
public record LeadRequest(
@NotBlank(message = "{crm.valid.companyNameRequired}") @Size(max = 128, message = "{crm.valid.companyNameCannotExceedCharacters}") String company,
@Email(message = "{crm.valid.invalidEmailFormat}") String email) {
}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等。键登记在声明它的模块的资源包中:
@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 说明是有意保留的中文(检查脚本跳过):
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 写入任务表的任务名称、登录日志中的登录方式。
语言的传递与解析
- 前端每个请求带
Accept-Language: zh-CN或en-US。 I18nConfig注册的过滤器(顺序-104:在RequestContextFilter之后、Spring Security 过滤器链之前)用AcceptHeaderLocaleResolver解析语言并写入LocaleContextHolder,所以安全过滤器输出的 401、403 提示也能按语言返回;Spring MVC 处理请求时用同一个LocaleResolver。- 解析规则:先精确匹配
SUPPORTED_LOCALES,再按语言匹配不带国家的项,所以en-US、en-GB、en都使用英文资源包,zh-TW使用中文;不支持的语言、没有请求头时为zh-CN。 - 定时任务、
@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/<语言>/<模块>.js | lemon-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.language | uni.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)为例:
- 前端语言包:新建
lemon-web/src/locales/ja-JP/,复制en-US/下的全部文件(包括menu.js、dict.js)并翻译,键与zh-CN完全一致。 - 注册语言:
locales/index.js的LOCALES加{ value: 'ja-JP', label: '日本語', short: '日' };需要按浏览器语言自动选择时,修改initialLocale()的判断。 - 组件库语言:
App.vue中引入ant-design-vue/es/locale/ja_JP和dayjs/locale/ja,按当前语言选择(现在用isZh()二选一,改为按currentLocale()映射)。 - 检查脚本:
scripts/check-i18n.mjs的LANGS加上ja-JP。 - 后端:
Messages.SUPPORTED_LOCALES加上Locale.JAPANESE;为每个模块新建src/main/resources/i18n/<模块>_ja.properties(键与默认资源包一致)。scripts/check_i18n.py目前只比对默认资源包和_en,需要按同样方式加上_ja的检查。 - 打印:
locales/ja-JP/print.js的name翻译显示名称即可,匹配键仍是中文,不需要改printTemplates.js。 - 检查只对中文有意义的分支(
isZh())在新语言下的表现,例如金额大写、单位候选、宽度调整(views/monitor/server/index.vue的标签宽度)。
检查脚本
前端:check-i18n.mjs
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
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
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标签宽度、按钮组可能放不下,检查窄屏和英文下的显示。