概述

本项目使用 Babel 进行国际化(i18n)管理,通过 tfcommon.utils.translation 模块提供翻译功能。当前支持的语言包括:

  • zh_CN(简体中文)- 默认语言

  • en_US(英文)

目录结构

project-tianlu/
├── locale/                          # 国际化文件目录
│   ├── tianfu.pot                   # 翻译模板文件(由 Babel 自动生成)
│   ├── zh_CN/                       # 中文翻译
│   │   └── LC_MESSAGES/
│   │       ├── tianfu.po            # 可编辑的翻译源文件
│   │       └── tianfu.mo            # 编译后的二进制翻译文件
│   └── en_US/                       # 英文翻译
│       └── LC_MESSAGES/
│           ├── tianfu.po
│           └── tianfu.mo
├── babel/                           # Babel 配置文件
│   ├── Taskfile.yml                 # Babel 任务定义
│   └── babel.cfg                    # Babel 提取配置
└── src/
    └── translator.py                # 翻译模块初始化

核心组件

1. 翻译模块 (src/translator.py)

init_translations()

初始化翻译系统,设置翻译文件目录。

def init_translations():
    translation.init_translations(
        localedirs=[
            str(Path(__file__).parent.parent / "locale"),
        ]
    )

调用时机:在应用启动时(src/app.pylifespan 函数中)调用。

LocaleMiddleware

语言中间件,为每个请求设置默认语言为中文(zh_CN)。

class LocaleMiddleware(BaseHTTPMiddleware):
    """语言中间件:设置默认语言为中文(zh_CN)"""
    
    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        # 直接设置默认语言为中文
        translation.activate("zh_CN")
        response = await call_next(request)
        return response

注册位置:在 src/app.py 中通过 app.add_middleware(LocaleMiddleware) 注册。

2. 应用初始化 (src/app.py)

在 FastAPI 应用的生命周期中初始化翻译系统:

@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期管理"""
    # 应用启动时:初始化数据库(创建所有表)
    await init_database()
    init_translations()  # 初始化翻译系统
    yield
    # 应用关闭时:清理资源
    engine.dispose()
    await async_engine.dispose()

Babel 任务

项目使用 Task 管理 Babel 相关任务。所有 Babel 任务定义在 babel/Taskfile.yml 中,通过主 Taskfile.ymlincludes 引入。

Includes 机制

在主 Taskfile.yml 中,通过 includes 字段引入 Babel 任务:

includes:
  babel: babel/Taskfile.yml

工作原理

  • babel 是命名空间前缀,用于区分不同 Taskfile 中的任务

  • babel/Taskfile.yml 是被引入的 Taskfile 路径(相对于主 Taskfile)

  • 引入后,可以通过 task babel:<任务名> 的方式调用 Babel Taskfile 中定义的任务

  • 例如:task babel:extract 会执行 babel/Taskfile.yml 中定义的 extract 任务

优势

  • 模块化:将 Babel 相关任务独立管理,保持主 Taskfile 简洁

  • 命名空间:通过 babel: 前缀避免任务名冲突

  • 可维护性:Babel 配置集中在一个文件中,便于维护和更新

Babel Taskfile 详细说明

babel/Taskfile.yml 文件结构如下:

变量定义 (vars)

vars:
  BABEL_CFG: "babel/babel.cfg"           # Babel 提取配置文件路径
  POT_FILE: "locale/tianfu.pot"          # 翻译模板文件(POT)路径
  DOMAIN: "tianfu"                        # 翻译域名称,用于区分不同的翻译集合
  TRANSLATIONS_DIR: "locale"              # 翻译文件根目录
  PYBABEL: "uvx --managed-python --from babel pybabel"  # pybabel 命令(通过 uvx 执行)
  VENV_PACKAGES: ".venv/lib/site-packages"  # 虚拟环境包目录(用于提取第三方库翻译)

变量说明

  • BABEL_CFG:指定 Babel 如何从源码中提取翻译标记的配置文件

  • POT_FILE:POT(Portable Object Template)文件,包含所有待翻译的原始文本

  • DOMAIN:翻译域,用于在同一项目中管理多组翻译(当前使用 tianfu

  • TRANSLATIONS_DIR:所有语言翻译文件的根目录

  • PYBABEL:使用 uvx 工具执行 babel 包中的 pybabel 命令,确保使用正确的 Python 环境

  • VENV_PACKAGES:虚拟环境中的第三方包路径,当前用于提取 fitpy 库的翻译

任务定义 (tasks)

可用任务

1. task babel:extract

从源码中提取待翻译文本到 POT 文件。

task babel:extract

任务定义

extract:
  desc: "从源码中提取待翻译文本到 POT 文件"
  vars:
    BABEL_SOURCES: "{{.VENV_PACKAGES}}/fitpy"
  cmds:
    - "{{.PYBABEL}} extract -F {{.BABEL_CFG}} -k gettext_lazy -k gettext_noop -k ngettext_lazy:1,2 -k pgettext_lazy:1c,2 -k npgettext_lazy:1c,2,3 -o {{.POT_FILE}} {{.BABEL_SOURCES}}"

功能

  • 扫描源码中的翻译标记(如 gettext_lazygettext_noop 等)

  • 生成 locale/tianfu.pot 模板文件

参数说明

  • -F {{.BABEL_CFG}}:指定 Babel 提取配置文件

  • -k <keyword>:指定要提取的翻译函数关键字

    • gettext_lazy:延迟翻译函数(用于模型字段等)

    • gettext_noop:标记但不翻译的函数

    • ngettext_lazy:1,2:复数形式的延迟翻译(参数位置 1 和 2)

    • pgettext_lazy:1c,2:带上下文的延迟翻译(1c 表示上下文,2 表示消息)

    • npgettext_lazy:1c,2,3:带上下文的复数延迟翻译

  • -o {{.POT_FILE}}:指定输出的 POT 文件路径

  • {{.BABEL_SOURCES}}:要扫描的源码路径(当前为 .venv/lib/site-packages/fitpy

注意:任务内部定义了 BABEL_SOURCES 变量,覆盖了全局变量,当前配置为提取 fitpy 第三方库的翻译。如需提取项目自身代码,可修改此变量为 src

2. task babel:update

从 POT 文件更新所有语言的 PO 文件。

task babel:update

任务定义

update:
  desc: "从 POT 文件更新 PO 文件"
  deps:
    - extract
  cmds:
    - "{{.PYBABEL}} update -D {{.DOMAIN}} -i {{.POT_FILE}} -d {{.TRANSLATIONS_DIR}}"

功能

  • 自动依赖 babel:extract 任务(确保 POT 文件是最新的)

  • 更新所有语言的 PO 文件(如 locale/zh_CN/LC_MESSAGES/tianfu.polocale/en_US/LC_MESSAGES/tianfu.po

  • 保留已有的翻译内容,只添加新的待翻译条目

  • 标记已删除的翻译条目为过时(但不会删除)

参数说明

  • deps: [extract]:任务依赖,执行前会自动运行 babel:extract

  • -D {{.DOMAIN}}:指定翻译域(tianfu

  • -i {{.POT_FILE}}:指定输入的 POT 文件路径

  • -d {{.TRANSLATIONS_DIR}}:指定翻译文件目录(locale

工作流程

  1. 自动执行 babel:extract 生成最新的 POT 文件

  2. 扫描 locale 目录下的所有语言目录

  3. 对每个语言的 PO 文件进行更新:

    • 添加新的待翻译条目

    • 保留已有翻译

    • 标记已删除的条目为过时

3. task babel:compile

编译 PO 文件到 MO 文件(二进制格式,运行时使用)。

task babel:compile

任务定义

compile:
  desc: "编译 PO 文件到 MO 文件"
  sources:
    - "{{.TRANSLATIONS_DIR}}/**/*.po"
  generates:
    - "{{.TRANSLATIONS_DIR}}/**/*.mo"
  cmds:
    - "{{.PYBABEL}} compile -D {{.DOMAIN}} -d {{.TRANSLATIONS_DIR}}"

功能

  • *.po 文件编译为 *.mo 文件(二进制格式)

  • MO 文件是运行时实际使用的翻译文件,性能更好

参数说明

  • sources:指定源文件模式,Task 会检查这些文件的变化来决定是否需要重新编译

  • generates:指定生成的文件模式,用于增量构建

  • -D {{.DOMAIN}}:指定翻译域(tianfu

  • -d {{.TRANSLATIONS_DIR}}:指定翻译文件目录(locale

增量构建

  • Task 会自动检查 sources 中的 PO 文件是否有变化

  • 只有当 PO 文件被修改时,才会重新编译对应的 MO 文件

  • 这提高了构建效率,特别是在大型项目中

注意:修改 PO 文件后必须执行此任务,否则翻译不会生效。应用运行时读取的是 MO 文件,不是 PO 文件。

4. task babel:init

初始化新的语言翻译文件。

task babel:init LANG=en_US DOMAIN=tianfu

任务定义

init:
  desc: "初始化翻译文件目录,注意文件已存在的话会被覆盖,需要 LANG 参数,例如 task babel:init LANG=en_US DOMAIN=tianfu"
  cmds:
    - "{{.PYBABEL}} init -D {{.DOMAIN}} -i {{.POT_FILE}} -d {{.TRANSLATIONS_DIR}} -l {{.LANG}}"

功能

  • 为新的语言创建初始 PO 文件

  • 基于 POT 文件生成空的翻译模板

参数说明

  • LANG必需参数,语言代码(如 en_USzh_CNja_JP

  • DOMAIN可选参数,翻译域(默认为 tianfu,可通过全局变量覆盖)

  • -D {{.DOMAIN}}:指定翻译域

  • -i {{.POT_FILE}}:指定输入的 POT 模板文件

  • -d {{.TRANSLATIONS_DIR}}:指定翻译文件目录

  • -l {{.LANG}}:指定要初始化的语言代码

使用示例

# 初始化英文翻译(使用默认域 tianfu)
task babel:init LANG=en_US

# 初始化日文翻译(使用默认域 tianfu)
task babel:init LANG=ja_JP

# 初始化英文翻译(指定域)
task babel:init LANG=en_US DOMAIN=myapp

生成的文件结构
执行 task babel:init LANG=ja_JP 后,会创建:

locale/
└── ja_JP/
    └── LC_MESSAGES/
        └── tianfu.po  # 新创建的翻译文件

注意

  • 如果目标 PO 文件已存在,会被覆盖(所有已有翻译会丢失)

  • 建议在初始化前备份现有翻译文件

  • 初始化后需要手动编辑 PO 文件添加翻译内容

工作流程

添加新翻译

  1. 提取待翻译文本

    task babel:extract
    
  2. 更新 PO 文件

    task babel:update
    
  3. 编辑翻译文件

    • 打开 locale/zh_CN/LC_MESSAGES/tianfu.polocale/en_US/LC_MESSAGES/tianfu.po

    • msgstr "" 字段添加翻译内容

  4. 编译翻译文件

    task babel:compile
    
  5. 重启应用:使新的翻译生效

添加新语言

  1. 初始化新语言文件

    task babel:init LANG=ja_JP DOMAIN=tianfu
    
  2. 编辑翻译

    • 打开 locale/ja_JP/LC_MESSAGES/tianfu.po

    • 添加翻译内容

  3. 编译翻译文件

    task babel:compile
    
  4. 更新代码

    • src/translator.pySUPPORTED_LANGUAGES 中添加新语言

    • 根据需要修改 LocaleMiddleware 或添加语言检测逻辑

在代码中使用翻译

使用 tfcommon.utils.translation

项目依赖 tfcommon 包提供的翻译功能。在代码中使用翻译时,需要:

  1. 导入翻译函数

    from tfcommon.utils.translation import gettext_lazy, gettext
    
  2. 标记待翻译文本

    # 延迟翻译(用于模型字段等)
    name = gettext_lazy("User Name")
    
    # 立即翻译(用于视图、API 响应等)
    message = gettext("Operation successful")
    
  3. 确保语言已激活

    • 通过 LocaleMiddleware 自动为每个请求激活默认语言(zh_CN)

    • 如需动态切换语言,可调用 translation.activate(language_code)

打包部署

PyInstaller 打包配置

项目使用 PyInstaller 打包时,需要在 packup/tianlu_spc.spec 文件中配置国际化文件的打包。

国际化文件打包配置

Analysisdatas 参数中配置:

a = Analysis(
    # ... 其他配置 ...
    datas=[
        # 打包国际化文件(locale 目录)
        ("../locale", "locale"),  # (源路径, 目标路径)
    ],
    # ... 其他配置 ...
)

配置说明

  • ("../locale", "locale"):将项目根目录的 locale 目录打包到可执行文件的 locale 目录

  • 格式为 (源路径, 目标路径) 元组,源路径相对于 spec 文件位置

  • 这确保所有翻译文件(.po.mo)都被包含在打包结果中

工作原理

  1. PyInstaller 会将 ../locale(相对于 spec 文件)目录下的所有文件复制到打包后的 locale 目录

  2. 保持目录结构:locale/zh_CN/LC_MESSAGES/tianfu.mo 等文件都会被正确打包

  3. 在打包环境中,locale 目录位于可执行文件同级的 _internal 目录中

运行时路径处理

翻译模块使用相对路径定位 locale 目录:

str(Path(__file__).parent.parent / "locale")

路径解析

  • 开发环境__file__ 指向 src/translator.py,路径解析为项目根目录的 locale

  • 打包环境__file__ 指向 _internal/src/translator.pyc,路径解析为 _internal/locale

这确保在开发环境和打包环境中都能正确找到翻译文件。

打包输出结构

打包完成后,国际化文件位于以下位置:

dist/tianlu_spc/
└── _internal/              # 内部文件目录
    └── locale/             # 国际化文件(由 datas 配置打包)
        ├── zh_CN/
        │   └── LC_MESSAGES/
        │       ├── tianfu.po
        │       └── tianfu.mo
        └── en_US/
            └── LC_MESSAGES/
                ├── tianfu.po
                └── tianfu.mo

验证打包结果
打包完成后,检查 dist/tianlu_spc/_internal/locale/ 目录,确认所有翻译文件都已包含。

注意事项

  1. 翻译文件编译:确保在打包前执行 task babel:compile,生成最新的 .mo 文件

  2. 路径配置:如果修改了 locale 目录的位置,需要同步更新 spec 文件中的 datas 配置

  3. 测试验证:打包后应在目标环境中测试翻译功能是否正常工作

配置说明

Babel 配置 (babel/babel.cfg)

# https://babel.pocoo.org/en/latest/messages.html#extraction-method-mapping-and-configuration
[python: **.py]

配置 Babel 提取 Python 文件中的翻译标记。

Taskfile 变量 (babel/Taskfile.yml)

  • BABEL_CFG:Babel 配置文件路径

  • POT_FILE:翻译模板文件路径

  • DOMAIN:翻译域名称(tianfu

  • TRANSLATIONS_DIR:翻译文件目录

  • PYBABEL:pybabel 命令(通过 uvx 执行)

  • BABEL_SOURCES:待提取的源码路径

注意事项

  1. 修改 PO 文件后必须编译:只有编译后的 MO 文件才会被运行时使用。

  2. 默认语言:当前默认语言为中文(zh_CN),在 LocaleMiddleware 中硬编码设置。

  3. 翻译域:项目使用 tianfu 作为翻译域,所有翻译文件都以此命名。

  4. 提取源:当前 Babel 配置从 .venv/lib/site-packages/fitpy 提取翻译,这是第三方库的翻译。如需提取项目自身代码的翻译,需要修改 BABEL_SOURCES 变量。

  5. 语言检测get_supported_language() 函数可以检测系统默认语言,但当前未在中间件中使用。

可以改进建议

  1. 动态语言切换:支持通过请求头(如 Accept-Language)或查询参数动态切换语言。

  2. 项目代码翻译提取:配置 Babel 提取项目自身代码中的翻译标记,而不仅仅是第三方库。

  3. 翻译缓存:对于高频访问的翻译,可以考虑添加缓存机制。

相关资源