如何为 1Panel 新增一种界面语言并按贡献指南完成全部注册与验证?
【免费下载链接】1Panel🔥 1Panel is a modern, open-source Linux server management panel and a lightweight AI management platform.项目地址: https://gitcode.com/GitHub_Trending/1p/1Panel
为 1Panel 增加一种界面语言不是复制一个语言文件就结束的:一个完整 locale 会同时涉及前端(Vue 3 +vue-i18n+ Element Plus)、Core 服务(Go i18n YAML)、Agent 服务(独立 Go i18n YAML)、应用商店元数据模型,以及项目 README 五个面。只改前端模块不构成完整的 locale 支持。docs/TRANSLATION.md 给出了全部需要创建或修改的文件清单、验证命令和 PR checklist,CONTRIBUTING.md 的 "Add a new translation" 一节也指向这份指南,并建议参考已有的参考 PR(西语完整 locale 的#10352)作为具体例子。
本文按该指南的 Step 0–11 走一遍完整流程,并以指南中 PR 标题约定示例使用的德语(运行时 localede-DE)作为贯穿示例,说明每一处改动的位置、写法和完成后的验证方式。
第一步:先确定 locale 代码,再动手改代码
先选定规范化的运行时 locale(canonical runtime locale):用 BCP 47 语言标签,并在运行时 locale 的所有出现位置保持同一套大小写。文件名和第三方 locale 包的大小写不一定和它一致,不能由运行时 locale 直接推导文件名——大小写或编码不同时要显式补一条映射。
指南中给出的代码对照表(以现有语言为例):
| Language | 运行时 locale | 前端模块 | Element Plus pack | 后端 YAML | 应用商店 key | README |
|---|---|---|---|---|---|---|
| Simplified Chinese | zh | zh.ts | zh-cn | zh.yaml | zh | README.zh-Hans.md |
| Traditional Chinese | zh-Hant | zh-Hant.ts | zh-tw | zh-Hant.yaml | zh-hant | README.zh-Hant.md |
| English | en | en.ts | en | en.yaml | en | README.md |
| Brazilian Portuguese | pt-BR | pt-br.ts | pt-br | pt-BR.yaml | pt-br | README.pt-br.md |
| Spanish (Spain) | es-ES | es-es.ts | es | es-ES.yaml | es-es | README.es-es.md |
| Lao | lo | lo.ts | lo | lo.yaml | lo | README.lo.md |
规则:简单编码用全小写(ja、ko、lo),变体用标准 BCP 47 大小写(pt-BR、es-ES、zh-Hant)。对德语示例,运行时 locale 为de-DE,后续代码块中的占位符含义为:
<runtime-locale>:你选定的运行时 locale,如de-DE;<module-name>:前端模块文件名(不含扩展名),大小写可不同,如de-de;<app-store-key>:应用商店 JSON key,全小写,如de-de;<Native language name>:该语言的本地化名称,用作界面菜单显示名。
改动前,先全仓搜索硬编码的 locale 集合,避免漏掉新增的选择器、白名单或兼容性映射:
rg "zh-Hant|pt-BR|es-ES" frontend core agent把模式替换成你要新增的 locale 后执行,命中位置都要逐一检查是否需要加入新语言。
第二步:前端翻译模块与两处注册
新增语言模块
从英文模块复制并翻译,注意从你改动所在的同一分支复制,不要依赖固定的 key 数或行数:
frontend/src/lang/modules/en.ts -> frontend/src/lang/modules/<module-name>.ts以下保持不变(除非英文源发生了结构性变化):对象 key、嵌套结构和 TypeScript 语法;{0}、${name}、{{ .detail }}等插值占位符;HTML 标签、Markdown、URL、产品名、命令和配置键;文件末尾的getFuLocaleMessage合并。传给getFuLocaleMessage的必须是运行时 locale,即使模块文件名大小写不同:
import { getFuLocaleMessage } from '@/lang/fu'; const message = { commons: { // Translated values. }, }; export default { ...getFuLocaleMessage('<runtime-locale>'), ...message, };机器翻译可以做第一遍,但每条字符串都要在界面上下文中复查。
注册 loader 与 FU 消息
在 frontend/src/lang/index.ts 的LOCALE_LOADERS中加入运行时 locale 到模块文件的映射(该文件当前包含zh、zh-Hant、en、pt-BR、ja、ru、ms、ko、lo、tr、fa、es-ES共 12 个条目,新语言需要第 13 条):
const LOCALE_LOADERS: Record<string, LocaleLoader> = { // Existing entries. '<runtime-locale>': () => import('./modules/<module-name>'), };在 frontend/src/lang/fu.ts 的fuLocales中加同一 key。这部分消息属于共享 FU 表格和 steps 组件,不由主语言模块提供:
const fuLocales: Record<string, FuLocaleMessage> = { // Existing entries. '<runtime-locale>': { fu: { table: { more: '...', custom_table_rows: '...', }, steps: { cancel: '...', prev: '...', next: '...', finish: '...', }, }, }, };第三步:注册 Element Plus locale
Element Plus 为日期选择、分页、弹层等组件提供自己的翻译。在 frontend/src/App.vue 中导入对应语言包并在i18nLocale计算属性里映射运行时 locale:
import localePack from 'element-plus/es/locale/lang/<element-plus-code>'; const i18nLocale = computed(() => { // Existing mappings. if (globalStore.language === '<runtime-locale>') return localePack; return zhCn; });先在frontend/node_modules/element-plus/es/locale/lang/中确认语言包存在。若 Element Plus 不提供该语言,不要在 PR 中导入一个不存在的模块,而是把所选 fallback 写入 PR 说明。
第四步:把语言暴露到每个前端入口
登录页:frontend/src/views/login/components/login-form.vue 中当前有两个不同登录布局对应的语言菜单,新 locale 要加进两个菜单块,再把本地化名称加进languageLabelMap:
const languageLabelMap: Record<string, string> = { // Existing entries. '<runtime-locale>': '<Native language name>', };指南明确提醒:只改languageLabelMap只会改变选中项的显示名,不会新增可选菜单项,所以两种登录布局都要手工验证。
面板设置:在 frontend/src/views/setting/panel/index.vue 的languageOptions中加一项:
const languageOptions = ref([ // Existing entries. { value: '<runtime-locale>', label: '<Native language name>' }, ]);公开分享页:在 frontend/src/views/share/index.vue 的supportedLocales中加运行时 locale。这个列表控制浏览器语言识别,以及公开分享请求使用的Accept-Language头。
与 locale 相关的 fallback:审查按语言选择 API 字段的比较逻辑。指南举的例子是 frontend/src/views/log/operation/index.vue:操作记录提供中文和英文两个详情字段,中文运行时 locale 用detailZH,其余 locale 保留可见的detailENfallback;应优先用一个共享 fallback 分支,而不是把每个非中文 locale 逐个加进新的硬编码列表。
第五步:放开登录接口的语言校验
后端会校验登录请求携带的语言,新语言必须加入 core/app/dto/auth.go 中Language字段的oneof校验标签:
Language string `json:"language" validate:"oneof=zh en ... <runtime-locale>"`当前该行实际内容为validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr 'es-ES' fa lo",注意带连字符的编码在现有写法中加了引号。没有这处改动,新语言能出现在登录页上,但登录请求会作为非法参数被拒绝。编辑完成后对该 Go 文件跑gofmt。
第六步:Core 与 Agent 的 YAML 目录(两个独立模块)
Core 和 Agent 是各自独立的 Go module,目录互不相通,必须分别复制翻译。
Core:
core/i18n/lang/en.yaml -> core/i18n/lang/<runtime-locale>.yaml然后在 core/i18n/i18n.go 的langFiles中注册:
var langFiles = map[string]string{ // Existing entries. "<runtime-locale>": "lang/<runtime-locale>.yaml", }Agent:
agent/i18n/lang/en.yaml -> agent/i18n/lang/<runtime-locale>.yaml在 agent/i18n/i18n.go 的langFiles中做同样注册。两边翻译时只改值,保留全部 YAML key 和占位符,例如:
ErrInvalidParams: "Translated text: {{ .detail }}" ErrRecordExist: "Translated text"两个关键约束:不要把 Core 的 YAML 拷进 Agent,两边 key 集合不同;这两个i18n.go都用//go:embed lang/*内嵌目录,若任一文件加载失败会 panic([i18n] failed to init language files),所以 YAML 语法错误会直接导致对应服务起不来。
第七步:扩展应用商店的 locale 模型
应用商店名称和描述使用独立的 locale 字段,需要在仓库两侧 schema 各加一个字段:
在 agent/app/dto/app.go 的Locale结构体中(当前包含en、ja、ms、pt-br、ru、zh-hant、zh、ko、tr、es-es、fa、lo):
type Locale struct { // Existing fields. NewLanguage string `json:"<app-store-key>"` }在 frontend/src/api/interface/app.ts 的Locale接口中用同一 JSON key:
interface Locale { // Existing fields. '<app-store-key>': string; }同时在 core/cmd/server/app/app_config.yml 的description示例中加上新 key——这个内嵌模板会在 server 命令为本地应用配置做脚手架时写入。
另外审查 frontend/src/utils/app-store.ts:能直接小写映射到应用商店 key 的简单编码通常无需特殊处理;区域性或文字变体可能需要显式的运行时 locale 到应用商店 key 的映射。指南同时说明:这些 schema 改动只是让仓库"能读"该 locale,翻译后的应用名称和描述维护在外部应用商店数据源中,需要单独协调;在该数据存在之前,应用商店会 fallback 到英文。
第八步:本地化 README
把根 README 复制到 docs 目录并翻译:
README.md -> docs/README.<readme-code>.md翻译时保留 Markdown 结构、HTML、图片、链接、命令、badge 和代码示例;从docs/下的新位置逐个核对相对链接——在仓库根目录能用的链接,在本地化文件里可能需要不同的相对路径。然后保持语言导航同步:
- 在 README.md 的语言 badge 行加入新 README 链接;
- 在现有每个
docs/README.*.md的语言 badge 行加入同一链接; - 在 docs/TRANSLATION.md 的 Locale code reference 表中加入该 locale。
badge 标签用语言本地名。README 后缀可以沿用现有文档惯例(zh-Hans、pt-br、es-es),不必与运行时 locale 大小写一致。
第九步:格式化与重新生成 API 文档
对所有改动的 Go 源文件跑gofmt:
gofmt -w core/app/dto/auth.go core/i18n/i18n.go gofmt -w agent/app/dto/app.go agent/i18n/i18n.go登录语言枚举和应用商店 locale 模型都体现在生成的 Swagger 文件里。安装与仓库兼容的swag命令后,从 Core 模块运行生成测试:
cd core go test ./cmd/server/docs -run TestGenerateSwaggerDoc它会更新core/cmd/server/docs/下的生成文件,不要手工编辑 Swagger 输出。如果生成器不可用,要在 PR 中明确说明,而不是提交过期的手工修改。
第十步:自动化验证
从仓库根目录执行(除命令中显式cd的位置外):
# Frontend type checking. cd frontend npm run type-check # Lint only the frontend files changed by the locale contribution. ./node_modules/.bin/eslint \ src/lang/modules/<module-name>.ts \ src/lang/index.ts \ src/lang/fu.ts \ src/App.vue \ src/views/login/components/login-form.vue \ src/views/setting/panel/index.vue \ src/views/share/index.vue \ src/views/log/operation/index.vue \ src/api/interface/app.ts \ src/utils/app-store.ts # Compile and test the directly affected Go packages. cd ../core go test ./i18n ./app/dto cd ../agent go test ./i18n ./app/dto # Check whitespace errors in all changed files. cd .. git diff --checkeslint参数清单即本次改动涉及的前端文件列表,<module-name>替换为你新建的模块文件名。go test ./i18n ./app/dto编译并测试直接受影响的包——由于两个i18n.go都在加载失败时 panic,YAML 解析错误和 key 问题会在这里暴露。
再人工比对每个翻译目录与英文源:前端模块的 key 与嵌套一致;Core 和 Agent 的每个 YAML 与对应英文文件 key 完全一致;占位符、格式化 token、HTML 标签、URL、命令均保留;YAML 可解析且无重复 key;README 链接与 badge 均能解析到目标文件。
第十一步:手工验证清单
指南给出的界面级验证项(勾选式):
- 两种登录布局都显示该语言本地名且可选中;
- 登录请求携带该 locale 时成功,不被校验拒绝;
- 设置 -> 面板 -> 语言能切换到该 locale,刷新后保留;
- Element Plus 的日期、分页、选择器、确认组件显示预期语言;
- 一条代表性的 Core API 错误消息是翻译后的;
- 一条代表性的 Agent 任务/错误消息是翻译后的;
- 公开分享页能识别浏览器 locale 并发送预期的
Accept-Language值; - 操作日志详情通过正确的中文或英文 fallback 保持可见;
- 应用商店元数据在该 locale 可用时使用它,缺失时 fallback 到英文;
- 本地化 README 渲染正确,每个 README 语言 badge 都链向它;
- 常见屏幕尺寸下布局、标点、截断和文字方向正确。
若目标是右到左语言(RTL),指南提醒要额外验证登录页、导航、表单、表格、对话框以及 IP 地址这类混合方向内容;RTL 布局支持可能需要超出翻译字符串的独立前端改动。
外部资源与 PR 收尾
有两类运行时资源不在本仓库维护:应用商店的名称和描述来自外部应用商店数据源;以language/lang.tar.gz下载的语言 shell 资源通过 1Panel 资源服务发布。仓库内的 locale 支持不会自动更新它们;新语言如需要,要与相应维护者协调发布,并在 PR 中记录状态;除非维护者要求,不要把生成或下载的归档加进仓库。
PR 标题约定:
feat(i18n): add <language name> (<runtime-locale>) locale support指南给的示例是feat(i18n): add German (de-DE) locale support。
提交前对照指南末尾的 "Complete pull-request checklist" 逐项检查,它覆盖了本文全部步骤:五种代码的文档化、前端模块/LOCALE_LOADERS/fuLocales/App.vue映射、两个登录菜单与languageLabelMap、languageOptions、supportedLocales、locale 敏感 fallback、登录校验器、Core 与 Agent 目录及注册、应用商店三处 schema 与app-store.ts、README 与 badge 同步、Go 格式化与 Swagger 重生成、各类检查与手工项、外部资源工作状态。若某个面(如应用商店外部数据)刻意不在本次范围内,要在 PR 中明确说明。
【免费下载链接】1Panel🔥 1Panel is a modern, open-source Linux server management panel and a lightweight AI management platform.项目地址: https://gitcode.com/GitHub_Trending/1p/1Panel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考