GFast-V34 + admin-ui 多语言(i18n)使用教程
适用版本:后端
gfast-v34(GoFrame v2.10.x)+ 前端gfast-ui(Vue 3 + Element Plus + vue-i18n 9)
当前支持语言:zh-CN / en-US,可在两端各自扩展
目录
1. 架构总览与数据流
多语言采用「前端持久化语言 → 请求头传递 → 后端中间件注入 ctx → 输出边界统一翻译」的整体方案,数据库内容(菜单标题/字典标签/网站配置文案)在响应时翻译,前端静态 UI 用 vue-i18n 渲染。
localStorage['art-locale'](zh-CN / en-US) ← 语言唯一事实来源
│
├─ axios 请求拦截器:每个请求带 Accept-Language: zh-CN | en-US
│
▼
后端 MiddlewareI18n 中间件(/api/v1 分组)
│ 解析:Accept-Language 头 > query(lang) > cookie(lang) > 站点配置 DefaultLang > zh-CN
│ 规范化:en* → en-US,其余 → zh-CN
│ 注入:r.SetCtx(gi18n.WithLanguage(ctx, lang)),并写入项目 ctx 常量 CtxKeyLanguage
▼
控制器/逻辑层
├─ 动态数据:菜单标题 / 字典标签 / 网站配置文案 返回前 libI18n.T(ctx, 中文原文)
└─ 业务消息:liberr.ErrIsNil(ctx, err, "中文原文") / gerror.New("中文原文")
▼
输出边界(两条路径都会翻译 message)
├─ 自定义 MiddlewareHandlerResponse(替代 ghttp.MiddlewareHandlerResponse)
│ 错误 err.Error() / code.Message() → libI18n.T(ctx, msg)
└─ libResponse.RJson(SusJson/FailJson/JsonExit 显式返回)
msg → libI18n.T(ctx, msg)
▼
响应 { code, data, message }(message 已按请求语言)
│
▼
前端:
├─ 后端 message 直接展示(已翻译,不二次处理)
├─ 动态数据(菜单/字典)直接用后端返回值
└─ 静态 UI:vue-i18n t('key'),切换语言即时生效核心约定(务必理解):中文原文即 key。语言包中动态数据/业务消息的 key 就是中文原文(如 系统管理、获取菜单失败、书名),en-US 语言文件补英文;未命中时安全回退显示中文原文。因此:
- 后端存量中文消息不需要逐个改代码,只需在
en-US.yaml补词条; - 前端生成页/业务页的动态文案(表/列注释)也走同一策略。
2. 后端配置
2.1 语言包文件与 key 约定
文件位置:gfast-v34/manifest/i18n/(gi18n 默认搜索 manifest/i18n、manifest/config/i18n、i18n;后端需从项目根目录启动)。
manifest/i18n/
├── zh-CN.yaml # 可为空(未命中自动回退原文);如需覆盖默认文案可加条目
└── en-US.yaml # 中文原文 -> 英文 词条en-US.yaml 示例:
# ==================== 固定/通用消息 ====================
"获取菜单失败": "Failed to load menus"
"验证码输入错误": "Incorrect captcha"
# ==================== 菜单标题(sys_auth_rule) ====================
"用户管理": "Users"
"商品列表": "Goods List"
"储值套餐修改": "Edit Value Card Package"
key 命名规则:
| 类型 | key | 示例 |
|---|---|---|
| 动态数据/业务消息 | 中文原文 | 系统管理、获取数据失败、书名 |
| 前端生成页动态文案 | 中文原文(含拼接) | 请输入书名、请选择状态、书名不能为空 |
⚠️ YAML 不能有重复 key:一旦重复,整个
en-US.yaml解析失败,所有翻译全部失效(表现为请求头是 en 但返回仍是中文)。提交前务必检查重复。
常用辅助工具:
# 1) 词条提取:扫描后端 Go 源码里的中文消息,生成 en-US 草稿(需人工校对补英文)
go run ./hack/extract_i18n.go > manifest/i18n/en-US.draft.yaml
# 2) 自检:验证语言包能否加载、抽样 key 是否正确翻译
go run ./hack/i18ncheck
2.2 语言解析中间件(MiddlewareI18n)
文件:gfast-v34/internal/app/common/logic/middleware/middleware.go
已注册在 /api/v1 分组(internal/router/router.go):
group.Middleware(commonService.Middleware().MiddlewareCORS)
group.Middleware(commonService.Middleware().MiddlewareI18n) // 语言解析
group.Middleware(commonService.Middleware().MiddlewareHandlerResponse) // 统一响应(翻译)
解析优先级:请求头 Accept-Language > query lang > cookie lang > 站点配置 DefaultLang > 默认 zh-CN;en* 归一化为 en-US,其余归 zh-CN(白名单在 SupportedLanguages 中维护)。
站点默认语言:后台「网站配置」中的 defaultLang 字段(sys_site_config 表)。
2.3 输出消息翻译(覆盖所有成功/失败/异常消息)
- 自定义
MiddlewareHandlerResponse:复制 GoFrame 内置行为,写出{code,message,data}前对err.Error()/code.Message()执行libI18n.T(r.GetCtx(), msg)。 libResponse.RJson:对显式返回的msg同样翻译(SusJson/FailJson/JsonExit均走这里)。
效果:业务代码里的中文消息零改动,自动按请求语言输出;en-US.yaml 有词条即翻译,无词条回退中文。
2.4 动态数据翻译(响应时)
| 数据 | 位置 | 说明 |
|---|---|---|
用户菜单标题 meta.title |
internal/app/system/logic/sysUser/sys_user.go(setMenuData) |
中文原文作 key 查语言包 |
| 菜单管理/角色权限树标题 | internal/app/system/logic/sysAuthRule/sys_auth_rule.go(GetMenuList/GetMenuListSearch) |
响应时翻译,不污染缓存 |
字典标签 dict_label |
internal/app/common/logic/sysDictData/sys_dict_data.go |
深拷贝后翻译,避免污染缓存 |
| 公开网站配置文案(登录页) | internal/app/common/controller/site_config.go |
登录标题/简介/版权等 |
数据库缓存中始终存中文原文,翻译发生在响应构建时,语言切换不会污染缓存。
2.5 后端新增一种语言(如 ja)
- 新建
manifest/i18n/ja.yaml,复制en-US.yaml的词条并翻译; - 修改
middleware.go:SupportedLanguages增加"ja",normalizeLanguage增加ja判定; - 前端(见 3.7)同步增加
ja; - 重启后端。
3. 前端配置
3.1 目录结构
admin-ui/src/locales/
├── index.ts # vue-i18n 实例 + registerLocaleModule
└── common/
├── zh-CN.ts # 公共语言包(框架/布局/设置面板/通用按钮/上传/验证码/表格)
└── en-US.ts页面级语言包就近放在页面目录(与视图同目录):
src/views/system/user/
├── index.vue
├── locale-zh.ts # 本页中文词条
└── locale-en.ts # 本页英文词条3.2 i18n 实例配置(src/locales/index.ts)
export const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: normalizeLocale(localStorage.getItem('art-locale') || 'zh-CN'),
fallbackLocale: 'zh',
missingWarn: false, // 「中文原文即 key」策略:未命中不弹警告
fallbackWarn: false,
messages: { zh: zhCN, en: enUS }
})
// 页面级语言包动态注册(合并进全局实例,幂等)
export function registerLocaleModule(locale: 'zh' | 'en', messages: Record<string, unknown>) {
i18n.global.mergeLocaleMessage(locale, messages)
}
3.3 语言切换与持久化
- 顶栏「语言」下拉(
SideHeader.vue)→app.setLocale('zh-CN' | 'en-US')。 stores/app.ts的setLocale会:- 更新 vue-i18n locale;
- 持久化
localStorage['art-locale']; - 同步
<html lang>; - 失效菜单缓存
art-menu并重新拉取(user.fetchUserMenus()),保证后端返回的菜单标题是新语言。
3.4 请求语言头(src/utils/request.ts)
请求拦截器自动携带:
const locale = localStorage.getItem('art-locale') || 'zh-CN'
config.headers['Accept-Language'] = locale === 'en-US' || locale === 'en' ? 'en-US' : 'zh-CN'
响应拦截器:后端 message 已按语言返回,直接 ElMessage.error(res.message) 展示;401 弹窗等用 t('common.*')。
3.5 Element Plus 组件语言
App.vue 用 el-config-provider 动态切换(无需刷新):
<el-config-provider :locale="elementLocale">
<router-view />
</el-config-provider>
const elementLocale = computed(() => (app.locale === 'en-US' ? en : zhCn))
3.6 页面翻译规范(新增/改造页面)
固定 UI 文案 → 公共包 key(t('common.*')):
<el-button>{{ t('common.search') }}</el-button>
<el-empty :description="t('common.empty')" />
<span>{{ t('common.total', { count: total }) }}</span>
页面私有/动态文案 → 页面级语言包 + 就近注册:
// <script setup>
import { useI18n } from 'vue-i18n'
import { registerLocaleModule } from '@/locales'
import userZh from './locale-zh'
import userEn from './locale-en'
registerLocaleModule('zh', userZh)
registerLocaleModule('en', userEn)
const { t } = useI18n()
// locale-zh.ts
export default {
system: { user: {
addUser: '新增用户',
confirmDelete: '确认删除选中的 {count} 个用户?', // 命名插值
// 动态文案(中文原文即 key,可直接做 key)
'请输入昵称': '请输入昵称'
} }
}
// locale-en.ts —— 与 zh 的 key 集合保持一致
export default {
system: { user: {
addUser: 'Add User',
confirmDelete: 'Confirm deleting {count} selected user(s)?',
'请输入昵称': 'Please enter a nickname'
} }
}
要点:
- zh/en 的 key 集合必须一致,否则英文界面会回退显示中文(安全但不完整);
- 带变量用 vue-i18n 命名插值:
t('key', { count: n }),语言文件里写{count}; - 字典标签(
useDict)与后端菜单标题由后端翻译,前端直接用返回值; - 校验规则、列定义等在 setup 里一次性计算的
t()不会随语言切换重渲染——如需即时切换,改为computed(参照dict/menu页面做法)或切换语言后刷新页面。
3.7 前端新增一种语言(如 ja)
src/locales/common/ja.ts:复制en-US.ts并翻译;src/locales/index.ts:messages增加ja,normalizeLocale增加ja分支;stores/app.ts的locale类型与setLocale参数扩展'ja';utils/request.ts:Accept-Language增加ja判定;App.vue:Element Plus 增加element-plus/es/locale/lang/ja;- 顶栏语言下拉
SideHeader.vue增加选项; - 各页面
locale-ja.ts补齐(未补回退中文/英文)。
4. 代码生成配置
代码生成功能已内置多语言支持:新生成的页面自动接入 i18n,并自动产出表级语言包。
4.1 生成页面自动 i18n(模板已改造)
后端模板 resource/template/vm/vue/*.template(list / edit / detail / tree / tree-virtual)会自动生成:
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { registerLocaleModule } from '@/locales'
import DemoGenClassZh from '../locale-zh' // 列表页用 ../,编辑/详情组件用 ../../
import DemoGenClassEn from '../locale-en'
registerLocaleModule('zh', DemoGenClassZh)
registerLocaleModule('en', DemoGenClassEn)
const { t } = useI18n()
...
</script>
- 固定文案(查询/重置/新增/修改/批量删除/导出Excel/导入Excel/刷新/详情/删除/操作/暂无数据/共X条/是/否/正常/停用/取消/确定 等)→
t('common.*')(公共包已内置); - 动态文案(
请输入{列注释}、请选择{列注释}、{列注释}不能为空、{表注释}修改/添加/详情、树表列标题、主键字段、副表 tab)→t('中文原文')全 key 化。
4.2 生成器自动产出语言包(方案 B)
新增模板:resource/template/vm/ts/locale-zh.template 与 locale-en.template。
执行「生成代码」(/system/tools/gen/genCode)时,除原文件外,会自动写出:
admin-ui/src/views/<模块>/<业务>/
├── list/index.vue
├── list/component/edit.vue
├── list/component/detail.vue
├── locale-zh.ts ← 自动生成(中文恒等词条,key=值=中文原文)
└── locale-en.ts ← 自动生成(英文骨架,TODO 占位)- 词条覆盖:表注释、全部列注释、复合文案(
请输入X/请选择X/选择X/X不能为空/X修改/X添加/X详情)、副表(Attachment)表名; - 语言包文件始终覆盖写入(纯生成产物,重新生成自动刷新词条清单)。
4.3 生成后如何补英文(关键步骤)
- 生成代码后,打开
src/views/<模块>/<业务>/locale-en.ts; - 文件头部有
// TODO: <列名>注释与中文占位值; - 把每个
'中文': '中文'的 value 改为英文(key 保持中文不动):
// 改前
'书名': '书名',
// 改后
'书名': 'Book Title',
- 复合文案同样处理:
'请输入书名': 'Enter book title',
'书名不能为空': 'Book title is required',
- 保存后,英文界面即时生效;未补的词条英文界面回退显示中文(不会报错)。
4.4 模板自定义
- 模板位置:
gfast-v34/resource/template/vm/(go/、ts/、vue/、sql/); - 路径配置:
manifest/config/config.yaml的gen节:gen: templatePath: "./resource/template/vm" # 模板目录 frontDir: "../admin-ui" # 前端项目目录(生成的 vue/ts/locale 写到这) - 预览即改即生效(请求时实时渲染);正式生成写盘。
4.5 验证样例页(可选)
cd gfast-v34
DSH_GEN_RENDER_TEST=1 go test ./internal/app/system/logic/toolsGenTable/ -run TestRenderTemplatesToAdminUI -v
会把演示表(book/region/area/chapter)生成到 admin-ui/src/views/demo 与 admin-ui/src/api/demo,供 vue-tsc 校验;验证完删除这两个目录。
4.6 代码生成注意事项
- 生成页面的 locale 相对路径:列表页
../locale-zh,编辑/详情组件(list/component/)../../locale-zh——由模板固定,勿改; - 语言包始终覆盖:会覆盖开发者在旧版
locale-en.ts里的英文吗?——会。若已人工翻译,重新生成前请先把英文回填到locale-zh.ts之外的地方备份,或在locale-en.template中维护固定词条(推荐把通用词条放common包); - 列注释含单引号(如
经理's桌)会破坏t('...')语法,生成后需手工修正——列注释尽量不用单引号; - 新表的菜单标题(SQL 生成写入
sys_auth_rule)走后端响应时翻译,需在manifest/i18n/en-US.yaml补对应词条(可用hack/extract_i18n.go+ 人工翻译)。
5. 运维 FAQ / 排错
Q1:请求头是 en-US,返回的 message/菜单标题仍是中文?
排查顺序:
- 语言包是否加载:
go run ./hack/i18ncheck,看en-US.yaml是否解析成功、抽样 key 是否翻译; - en-US.yaml 是否有重复 key:重复会导致整个文件解析失败、全部词条失效。检查方法:
$keys=@{}; Get-Content manifest/i18n/en-US.yaml | ForEach-Object { if ($_ -match '^"([^"]+)":') { $k=$Matches[1]; if ($keys.ContainsKey($k)){"dup: $k (line $($keys[$k]))"} else {$keys[$k]=1} } } - 后端是否重启:Go 代码改动需重启;语言包文件改动一般会触发 gi18n 热更新,但推荐重启确保生效;
- 后端工作目录:必须从项目根(
gfast-v34/)启动,manifest/i18n才能被找到。
Q2:英文界面出现中文?
词条未命中 → 安全回退中文。属预期行为,补 en-US.yaml / 页面 locale-en.ts 词条即可。
Q3:动态拼接消息(如 {列注释}不能为空)为什么翻译不了?
因为它是由列注释拼出来的动态字符串,词条无法预置。做法:把完整中文串(如 书名不能为空)作为 key 加进语言包;代码生成场景由 locale-en.ts 自动带出该 key,补英文即可。
Q4:切换语言后菜单/字典还是旧语言?app.setLocale 会清 art-menu 并重拉菜单;若仍旧,检查浏览器是否缓存了旧数据,或刷新页面。
Q5:数据库里的内容(字典标签、网站配置文案)如何多语言?
响应时翻译(中文原文即 key):后端把 dict_label、登录页文案按请求语言翻译后返回。运营新加的中文词条需同步补 en-US.yaml;如需运营直接在后台维护多语言,需扩展(见第 7 节方案 C)。
Q6:Element Plus 组件没随语言切换?App.vue 已用 el-config-provider :locale 动态切换;确认 stores/app.ts 的 setLocale 生效。
6. 验收清单
- 后端
go build ./...、go vet(改动包)通过; -
go run ./hack/i18ncheck抽样 key 翻译正确、en-US.yaml无重复 key; - 前端
vue-tsc --noEmit0 错误、vitest通过; - 顶栏切换语言:布局/设置面板/Element Plus 组件/登录页即时切换;
-
curl -H "Accept-Language: en-US"与zh-CN请求同一接口,message不同且正确; - 登录后菜单
meta.title、字典标签、网站配置文案随语言变化; - 代码生成:预览/生成的新页面含
registerLocaleModule与t(),locale-zh.ts/locale-en.ts已产出,补英文后生效。
7. 后续扩展方向
- 方案 C(推荐)——数据库维护英文注释:给
tools_gen_table/tools_gen_table_column增加英文注释字段(或建翻译表),代码生成管理页可维护「表英文名/列英文名」;locale-en.template改读英文字段,有英文填英文、无英文回退中文,彻底免手工补词条。 - 多语言扩展:按 2.5 / 3.7 步骤增加
ja、zh-TW等;SupportedLanguages(后端)与前端 locale 类型、Element Plus locale 同步扩展。 - 运营侧多语言:字典、网站配置、公告等用户可维护内容增加多语言字段,后台按语言维护(改动较大,需评估)。
- 词条完整度校验:前端可加脚本 diff
locale-zh.ts与locale-en.ts的 key 集合,防漏翻(CI 可接入)。
最后编辑:管理员 更新时间:2026-09-10 14:25