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. 架构总览与数据流
  2. 后端配置
  3. 前端配置
  4. 代码生成配置
  5. 运维 FAQ / 排错
  6. 验收清单
  7. 后续扩展方向

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/i18nmanifest/config/i18ni18n;后端需从项目根目录启动)。

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-CNen* 归一化为 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.gosetMenuData 中文原文作 key 查语言包
菜单管理/角色权限树标题 internal/app/system/logic/sysAuthRule/sys_auth_rule.goGetMenuList/GetMenuListSearch 响应时翻译,不污染缓存
字典标签 dict_label internal/app/common/logic/sysDictData/sys_dict_data.go 深拷贝后翻译,避免污染缓存
公开网站配置文案(登录页) internal/app/common/controller/site_config.go 登录标题/简介/版权等

数据库缓存中始终存中文原文,翻译发生在响应构建时,语言切换不会污染缓存。

2.5 后端新增一种语言(如 ja

  1. 新建 manifest/i18n/ja.yaml,复制 en-US.yaml 的词条并翻译;
  2. 修改 middleware.goSupportedLanguages 增加 "ja"normalizeLanguage 增加 ja 判定;
  3. 前端(见 3.7)同步增加 ja
  4. 重启后端。

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.tssetLocale 会:
    1. 更新 vue-i18n locale;
    2. 持久化 localStorage['art-locale']
    3. 同步 <html lang>
    4. 失效菜单缓存 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.vueel-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

  1. src/locales/common/ja.ts:复制 en-US.ts 并翻译;
  2. src/locales/index.tsmessages 增加 janormalizeLocale 增加 ja 分支;
  3. stores/app.tslocale 类型与 setLocale 参数扩展 'ja'
  4. utils/request.tsAccept-Language 增加 ja 判定;
  5. App.vue:Element Plus 增加 element-plus/es/locale/lang/ja
  6. 顶栏语言下拉 SideHeader.vue 增加选项;
  7. 各页面 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.templatelocale-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 生成后如何补英文(关键步骤)

  1. 生成代码后,打开 src/views/<模块>/<业务>/locale-en.ts
  2. 文件头部有 // TODO: <列名> 注释与中文占位值;
  3. 把每个 '中文': '中文' 的 value 改为英文(key 保持中文不动):
// 改前
'书名': '书名',
// 改后
'书名': 'Book Title',
  1. 复合文案同样处理:
'请输入书名': 'Enter book title',
'书名不能为空': 'Book title is required',
  1. 保存后,英文界面即时生效;未补的词条英文界面回退显示中文(不会报错)。

4.4 模板自定义

  • 模板位置:gfast-v34/resource/template/vm/go/ts/vue/sql/);
  • 路径配置:manifest/config/config.yamlgen 节:
    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/demoadmin-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/菜单标题仍是中文?
排查顺序:

  1. 语言包是否加载go run ./hack/i18ncheck,看 en-US.yaml 是否解析成功、抽样 key 是否翻译;
  2. 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} } }
  3. 后端是否重启:Go 代码改动需重启;语言包文件改动一般会触发 gi18n 热更新,但推荐重启确保生效;
  4. 后端工作目录:必须从项目根(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.tssetLocale 生效。


6. 验收清单

  • 后端 go build ./...go vet(改动包)通过;
  • go run ./hack/i18ncheck 抽样 key 翻译正确、en-US.yaml 无重复 key;
  • 前端 vue-tsc --noEmit 0 错误、vitest 通过;
  • 顶栏切换语言:布局/设置面板/Element Plus 组件/登录页即时切换;
  • curl -H "Accept-Language: en-US"zh-CN 请求同一接口,message 不同且正确;
  • 登录后菜单 meta.title、字典标签、网站配置文案随语言变化;
  • 代码生成:预览/生成的新页面含 registerLocaleModulet()locale-zh.ts/locale-en.ts 已产出,补英文后生效。

7. 后续扩展方向

  1. 方案 C(推荐)——数据库维护英文注释:给 tools_gen_table/tools_gen_table_column 增加英文注释字段(或建翻译表),代码生成管理页可维护「表英文名/列英文名」;locale-en.template 改读英文字段,有英文填英文、无英文回退中文,彻底免手工补词条。
  2. 多语言扩展:按 2.5 / 3.7 步骤增加 jazh-TW 等;SupportedLanguages(后端)与前端 locale 类型、Element Plus locale 同步扩展。
  3. 运营侧多语言:字典、网站配置、公告等用户可维护内容增加多语言字段,后台按语言维护(改动较大,需评估)。
  4. 词条完整度校验:前端可加脚本 diff locale-zh.tslocale-en.ts 的 key 集合,防漏翻(CI 可接入)。
作者:管理员  创建时间:2026-09-10 14:23
最后编辑:管理员  更新时间:2026-09-10 14:25