首页
/ Gogs 本地化翻译同步:`gogs import locale` 命令的完整流程与源码解析

Gogs 本地化翻译同步:`gogs import locale` 命令的完整流程与源码解析

2026-09-07 16:58:16作者:胡唯隽

Gogs 的界面多语言翻译通过 Crowdin 平台协作完成,官方开发者文档 docs/dev/import_locale.md 定义了将 Crowdin 上翻译成果批量同步回仓库 conf/locale/ 目录的标准流程。本篇以该文档为主线,完整还原从上传源文件、构建下载、执行 import locale 子命令到提交 Pull Request 的全部操作步骤,并结合 cmd/gogs/import.go 的实现源码,深入讲解该命令如何跳过英语源文件、修复 Crowdin 引号转义问题以及纠正文件时间戳,帮助维护者理解并正确使用这套本地化导入机制。

一、翻译同步的背景:locale 文件与多语言配置

Gogs 自 v0.5.0 起支持多语言界面,每种语言对应 conf/locale/ 目录下的一份 INI 文件,如 locale_en-US.inilocale_zh-CN.inilocale_ja-JP.ini 等,目录中还包含一个 TRANSLATORS 文件用于维护译者信息。

可用的界面语言在 conf/app.ini[i18n] 段中声明:

[i18n]
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,gl-ES,uk-UA,en-GB,hu-HU,sk-SK,id-ID,fa-IR,vi-VN,pt-PT,mn-MN,ro-RO
NAMES = English,简体中文,繁體中文(香港),繁體中文(臺灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어,galego,українська,English (United Kingdom),Magyar,Slovenčina,Indonesian,Persian,Vietnamese,Português,Монгол,Română

该配置在 internal/conf/conf.go 中被映射到 I18n 变量,其结构体定义在 internal/conf/static.go

type i18nConf struct {
	Langs     []string          `delim:","`
	Names     []string          `delim:","`
	dateLangs map[string]string `ini:"-"`
}

LangsNames 以逗号为分隔符解析为两个等长、顺序一一对应的列表。理解这一点非常关键——后文的导入命令正是依赖 conf.I18n.Langs 来决定哪些语言文件需要被导入。

二、官方文档定义的完整同步流程

以下是 docs/dev/import_locale.md 给出的标准操作步骤,每一步都不可跳过:

第 1 步:上传最新英文源文件到 Crowdin

将仓库中最新的 locale_en-US.ini 上传到 Crowdin 项目的源文件区。英文文件是所有翻译的基准,模板更新后各语言翻译才能与之对齐。

第 2 步:在 Crowdin 上构建并下载

在 Crowdin 项目中执行构建(Build),下载包含全部语言翻译的 ZIP 压缩包并解压。

第 3 步:切换到仓库根目录

cd <gogs 仓库根目录>

第 4 步:执行 import 子命令

$ ./.bin/gogs import locale --source <path to the unzipped directory> --target ./conf/locale
Locale files has been successfully imported!

其中 ./.bin/gogs 是通过构建工具生成的本地可执行文件,--source 指向解压后的目录,--target 指向仓库内的 conf/locale 目录。执行成功时命令会打印 Locale files has been successfully imported!(对应 cmd/gogs/import.go 中的 fmt.Println)。

第 5 步:启动 Web 服务验证

按照文档要求,运行 moon run gogs:dev 启动开发用 Web 服务器,然后在浏览器中访问站点,确认界面没有因为 locale 文件变化而报错——这一步是防止翻译文件存在语法错误导致本地化初始化失败的兜底检查。

第 6~9 步:提交并发起 Pull Request

git checkout -b update-locales
# 暂存变更
git add -A
git commit -m "locale: sync from Crowdin"
git push origin update-locales

随后在上游仓库平台上基于 update-locales 分支创建 Pull Request,提交信息约定俗成地采用 locale: sync from Crowdin 格式,便于在历史中识别翻译同步类提交。

三、命令参数与前置校验

import locale 子命令的注册定义在 cmd/gogs/import.go。整个 import 命令组的定位是「将便携数据导入为本地 Gogs 数据」,目前仅挂载了 locale 一个子命令:

参数 别名 是否必填 说明
--source 存放新 locale 文件的源目录(即 Crowdin ZIP 解压目录)
--target 存放旧 locale 文件的目标目录(通常为 ./conf/locale
--config -c 自定义配置文件路径,用于覆盖默认的 conf/app.ini

参数校验逻辑位于 cmd/gogs/import.gorunImportLocale 函数,包含三层检查,任何一层失败都会立即返回带上下文的错误:

  1. --source 未指定 → source directory is not specified
  2. --target 未指定 → target directory is not specified
  3. 两个路径必须真实存在且是目录(通过 osx.IsDir 判断),否则返回 source directory %q does not exist or is not a directory 或对应的 target 错误。

校验通过后调用 conf.Init(configFromLineage(cmd)) 初始化全局配置。这里有一个值得注意的细节:configFromLineage(定义于 cmd/gogs/cmd.go)会沿着 CLI 命令的 Lineage() 逐级向上查找 --config 标志的值,因为子命令可能看不到父命令上设置的参数。因此 --config 既可以挂在 import locale 子命令上,也可以挂在父命令层,行为一致。

四、导入实现的三个关键细节

命令拿到源/目标目录后,真正的文件处理逻辑集中在 cmd/gogs/import.go 的循环中,源码揭示了三个容易被忽略但至关重要设计决策。

4.1 跳过英语:只同步 Langs[1:]

// Cut out en-US.
for _, lang := range conf.I18n.Langs[1:] {
    name := fmt.Sprintf("locale_%s.ini", lang)
    source := filepath.Join(cmd.String("source"), name)
    target := filepath.Join(cmd.String("target"), name)
    if !osx.IsFile(source) {
        continue
    }
    ...
}

循环遍历的是 conf.I18n.Langs[1:]——即从 [i18n] LANGS 列表中剔除第一项 en-US 之后的所有语言。原因很直接:英文源文件以仓库中的 locale_en-US.ini 为唯一事实来源,Crowdin 上的英文副本可能滞后或不一致,若覆盖回来会破坏基准。同时,每个语言的文件名严格按 locale_<lang>.ini 约定拼接,不在 LANGS 列表中的语言即使出现在解压目录里也不会被导入,这保证了导入范围永远受 app.ini[i18n] 配置约束。

对于源目录中缺失的某语言文件,代码直接 continue 跳过而不报错——部分语言翻译进度落后时仍可以同步其他已完成的语言。

4.2 修复 Crowdin 的引号转义

badChars := []byte(`="`)
escapedQuotes := []byte(`\"`)
regularQuotes := []byte(`"`)

源码注释明确写道:Crowdin 会对「字符串内部含有引号」的值整体用双引号包裹,而这会破坏 Gogs 使用的 INI 解析器。因此命令逐行扫描源文件,当一行在 =" 之后出现、且" 结尾时,视为 Crowdin 的包裹格式:

idx := bytes.Index(line, badChars)
if idx > -1 && line[len(line)-1] == '"' {
    // We still want the "=" sign
    line = append(line[:idx+1], line[idx+2:len(line)-1]...)
    line = bytes.ReplaceAll(line, escapedQuotes, regularQuotes)
}

处理逻辑是:保留第一个 =/" 及其前面的部分(idx+1 保证等号不被误删),丢弃从第二个特殊字符到行尾引号之外的包裹层,再把内部的 \" 反转义回普通 ",最后把改写后的行写入目标文件。这是典型的「适配上游工具输出格式」的防御性代码,也是直接手工复制 Crowdin 导出文件而不经过此命令会导致界面显示异常(引号裸露、字符串截断)的根本原因。

4.3 回拨文件修改时间

// Modification time of files from Crowdin often ahead of current,
// so we need to set back to current.
_ = os.Chtimes(target, now, now)

Crowdin 导出的文件时间戳常常「超前于」当前时间(时区或服务器时间偏差所致)。如果不做处理,git status 的依赖工具、增量构建缓存等可能因异常 mtime 产生误判,因此命令在写完目标文件后立即把 mtime 重置为导入时刻 now

五、导入后的配置联动与本地覆盖机制

导入完成后,新语言文件立即被 internal/conf/conf.go 中的配置加载流程消费:File.Section("i18n").MapTo(I18n)[i18n] 段映射到 i18nConf,其中的 dateLangs 则来自 i18n.datelang 段,用于把标准 locale 名称映射为日期时间插件所需的语言代码(DateLang 方法在查不到映射时回退为 "en",见 internal/conf/static.go)。

除了走 Crowdin 的正式同步通道,docs/advancing/localization.mdx 还描述了两种用户侧的本地化覆盖方式,与导入流程互为补充:

  • 本地测试翻译:将修改后的 locale 文件放到 custom/conf/locale/<file> 后重启 Gogs 即可加载,无需改动 Git 历史;
  • 自定义覆盖:创建 custom/conf/locale/locale_<lang>.ini,文件中只需包含要覆盖的键,未指定的键自动回退到官方翻译。

这意味着维护者在验证某条翻译修复时,可以先用 custom 目录快速试错,确认无误后再走本文的 Crowdin 同步流程合并回主线。

六、小结:一条命令背后的工程约定

对照 docs/dev/import_locale.md 的流程与 cmd/gogs/import.go 的实现,可以归纳出 Gogs 本地化同步的几条核心约定:

  1. 英文是唯一基准:导入命令硬编码跳过 LANGS 首位的 en-US,英文更新只经由仓库提交生效;
  2. 导入范围由 [i18n] LANGS 决定:新增语言时须先在 conf/app.ini 中登记,否则新文件不会被同步;
  3. 必须走 import locale 而非手工拷贝:Crowdin 导出的引号格式需要命令内建的反转义处理;
  4. 同步后必须启动服务验证moon run gogs:dev 起服务、浏览器访问是提交前的强制检查项;
  5. 提交遵循 locale: sync from Crowdin 信息约定,并统一使用 update-locales 分支名,便于 Code Review 识别。

遵循上述流程,维护者即可安全、可追溯地把 Crowdin 上的多语言翻译成果合入仓库,且导入过程对缺失文件、格式异常、时间戳偏差都有明确的源码级兜底。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389