From ac8b6f642bdfddb6e763cfbf664ff64cc6d53345 Mon Sep 17 00:00:00 2001 From: keven1024 Date: Sun, 24 May 2026 11:29:11 +0800 Subject: [PATCH] refactor(i18n): consolidate i18n logic into pkg/i18n, fix TOML loading issue, and migrate translation files --- .../refactor-i18n-use-pkg/.openspec.yaml | 2 + .../changes/refactor-i18n-use-pkg/design.md | 49 +++++++++++++++++++ .../changes/refactor-i18n-use-pkg/proposal.md | 29 +++++++++++ .../specs/share-download-notify/spec.md | 16 ++++++ .../changes/refactor-i18n-use-pkg/tasks.md | 23 +++++++++ 5 files changed, 119 insertions(+) create mode 100644 openspec/changes/refactor-i18n-use-pkg/.openspec.yaml create mode 100644 openspec/changes/refactor-i18n-use-pkg/design.md create mode 100644 openspec/changes/refactor-i18n-use-pkg/proposal.md create mode 100644 openspec/changes/refactor-i18n-use-pkg/specs/share-download-notify/spec.md create mode 100644 openspec/changes/refactor-i18n-use-pkg/tasks.md diff --git a/openspec/changes/refactor-i18n-use-pkg/.openspec.yaml b/openspec/changes/refactor-i18n-use-pkg/.openspec.yaml new file mode 100644 index 0000000..ce9d1c6 --- /dev/null +++ b/openspec/changes/refactor-i18n-use-pkg/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-01 diff --git a/openspec/changes/refactor-i18n-use-pkg/design.md b/openspec/changes/refactor-i18n-use-pkg/design.md new file mode 100644 index 0000000..94367b6 --- /dev/null +++ b/openspec/changes/refactor-i18n-use-pkg/design.md @@ -0,0 +1,49 @@ +## Context + +项目使用 Go workspace(go.work),`pkg/` 下有多个共享包(models、utils、services、i18n)。worker 模块通过 workspace 引用这些包。 + +当前 `pkg/i18n` 存在两个问题: +1. `Init()` 中注册的是 JSON 格式解析器,但 locales 目录下的文件是 `.toml`,导致实际加载翻译时什么都加载不到 +2. 只有英文一种翻译,缺少其他语言 + +worker 独立维护了 7 种语言的翻译文件和一套完整的 i18n 初始化/加载逻辑,但这套逻辑使用动态路径查找(尝试多个相对路径),在容器化部署中脆弱。 + +## Goals / Non-Goals + +**Goals:** +- 修复 `pkg/i18n` 的 TOML 加载 bug +- 将所有翻译文件集中到 `pkg/i18n/locales/`,通过 `embed.FS` 编译进二进制,消除运行时路径依赖 +- worker 移除本地 i18n 实现,改用 `pkg/i18n` + +**Non-Goals:** +- 不增加新的翻译 key +- 不修改 `Init()` 函数签名 +- 不影响任何 HTTP API 或前端行为 + +## Decisions + +### 1. 使用 embed.FS 而非动态路径 + +worker 当前通过 `filepath.Glob` 查找翻译文件,需要尝试 4 个不同路径。`pkg/i18n` 已使用 `//go:embed` 将文件编译进二进制,更可靠。翻译文件全部迁移到 `pkg/i18n/locales/`,worker 不再携带翻译文件。 + +### 2. 保留 Init() 函数不变 + +`Init()` 函数对外保持现有签名,只修改内部实现:将 `bundle.RegisterUnmarshalFunc("json", json.Unmarshal)` 改为 `bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)`,并将文件后缀过滤从 `.json` 改为 `.toml`。 + +### 3. worker 调用方式 + +worker 在 `main.go` 中调用 `pkgi18n.Init()`,`notify.go` 中将原来的三个本地函数替换为两次 `pkgi18n.TWithData()` 调用。 + +### 4. go.mod 依赖调整 + +`pkg/i18n/go.mod` 新增: +- `github.com/BurntSushi/toml` +- `github.com/nicksnyder/go-i18n/v2` +- `golang.org/x/text` + +worker/go.mod 新增 `pkg/i18n`,如果 `go-i18n`、`BurntSushi/toml`、`golang.org/x/text` 不再被 worker 直接使用则移至 indirect。 + +## Risks / Trade-offs + +- [翻译文件删除] 删除 `worker/internal/i18n/` 不可逆 → 迁移前确认 pkg/i18n/locales/ 中所有文件正确 +- [go.mod 变动] worker 依赖关系变化可能引起 `go mod tidy` 意外移除仍需要的包 → 执行后检查 go.sum 和编译结果 diff --git a/openspec/changes/refactor-i18n-use-pkg/proposal.md b/openspec/changes/refactor-i18n-use-pkg/proposal.md new file mode 100644 index 0000000..e8d1e63 --- /dev/null +++ b/openspec/changes/refactor-i18n-use-pkg/proposal.md @@ -0,0 +1,29 @@ +## Why + +worker 模块自己实现了一套独立的 i18n 加载逻辑(动态文件路径查找、手动注册 TOML 解析),而 `pkg/i18n` 已经提供了通用的 i18n 封装但从未被使用,且其实现存在 bug(注册的是 JSON 格式,实际文件是 TOML)。统一到 `pkg/i18n` 可以消除重复、修复 bug、让后续模块复用。 + +## What Changes + +- 修复 `pkg/i18n`:将格式从 JSON 改为 TOML,更新 `go.mod` 添加缺失依赖 +- 将 worker/internal/i18n/ 的 7 种语言翻译文件迁移到 `pkg/i18n/locales/` +- 将 `pkg/i18n` 加入 `go.work` 工作区 +- worker/go.mod 中添加 `pkg/i18n` 依赖,移除对 `go-i18n`、`BurntSushi/toml`、`golang.org/x/text/language` 的直接引用 +- 删除 worker 中的 `loadI18nBundle`、`mustLocalize`、`localizeEmail` 函数,改用 `pkg/i18n.TWithData` +- 删除 `worker/internal/i18n/` 目录 + +## Capabilities + +### New Capabilities + +无新能力,纯重构。 + +### Modified Capabilities + +- `share-download-notify`:i18n 实现层迁移到 `pkg/i18n`,接口行为(邮件本地化主题和正文)不变 + +## Impact + +- **pkg/i18n**:go.mod 新增 `github.com/BurntSushi/toml`、`github.com/nicksnyder/go-i18n/v2`、`golang.org/x/text` 依赖,locales 目录新增 6 种语言文件 +- **go.work**:新增 `./pkg/i18n` 模块 +- **worker**:notify.go 逻辑简化,go.mod 依赖变化,`worker/internal/i18n/` 目录删除 +- 不影响任何 API 或前端 diff --git a/openspec/changes/refactor-i18n-use-pkg/specs/share-download-notify/spec.md b/openspec/changes/refactor-i18n-use-pkg/specs/share-download-notify/spec.md new file mode 100644 index 0000000..3f6c7d9 --- /dev/null +++ b/openspec/changes/refactor-i18n-use-pkg/specs/share-download-notify/spec.md @@ -0,0 +1,16 @@ +## MODIFIED Requirements + +### Requirement: Localized email notification sent on download +`share:notify` Worker 任务 SHALL 对 `notify_emails` 中每个邮件地址通过 SMTP 发送通知邮件,邮件语言使用 `RedisShareInfo.Locale` 对应的翻译,不支持的 locale 回退到英文。支持的语言为:`en`、`de`、`fr`、`ja`、`ko`、`zh-CN`、`zh-TW`。翻译由 `pkg/i18n` 提供,翻译文件以 TOML 格式嵌入二进制。 + +#### Scenario: Email sent in creator's locale +- **WHEN** SMTP 配置完整(`smtp.host` 非空)且 `shareInfo.Locale="zh-CN"` +- **THEN** Worker 向目标地址发送邮件,邮件 Subject 和 Body 为中文内容 + +#### Scenario: Email locale fallback to English +- **WHEN** `shareInfo.Locale` 为空或不支持的语言代码 +- **THEN** Worker 使用英文模板发送邮件 + +#### Scenario: SMTP not configured +- **WHEN** `smtp.host` 为空 +- **THEN** 邮件通知被跳过(不计入失败数),记录 warn 日志 diff --git a/openspec/changes/refactor-i18n-use-pkg/tasks.md b/openspec/changes/refactor-i18n-use-pkg/tasks.md new file mode 100644 index 0000000..1d65686 --- /dev/null +++ b/openspec/changes/refactor-i18n-use-pkg/tasks.md @@ -0,0 +1,23 @@ +## 1. 修复 pkg/i18n + +- [x] 1.1 修改 `pkg/i18n/go.mod`:添加 `github.com/BurntSushi/toml`、`github.com/nicksnyder/go-i18n/v2`、`golang.org/x/text` 依赖 +- [x] 1.2 修改 `pkg/i18n/i18n.go`:将 `Init()` 内部的 JSON 解析器替换为 TOML 解析器,文件后缀过滤从 `.json` 改为 `.toml` +- [x] 1.3 将 `worker/internal/i18n/` 下的所有 `.toml` 文件复制到 `pkg/i18n/locales/`(保留已有的 active.en.toml,添加 de/fr/ja/ko/zh-CN/zh-TW) +- [x] 1.4 在 `pkg/i18n/` 目录下运行 `go mod tidy`,生成 go.sum + +## 2. 更新 go.work + +- [x] 2.1 在 `go.work` 的 `use` 块中添加 `./pkg/i18n` +- [x] 2.2 在项目根目录运行 `go work sync` 更新 go.work.sum + +## 3. 更新 worker + +- [x] 3.1 在 `worker/go.mod` 中添加 `pkg/i18n` 依赖 +- [x] 3.2 在 `worker/main.go` 中调用 `pkgi18n.Init()`,处理返回的 error +- [x] 3.3 修改 `worker/internal/tasks/notify.go`:删除 `loadI18nBundle`、`mustLocalize`、`localizeEmail` 三个函数及其相关 import,改用 `pkgi18n.TWithData` 生成邮件主题和正文 +- [x] 3.4 在 `worker/` 目录下运行 `go mod tidy`,确认 `go-i18n`、`BurntSushi/toml`、`golang.org/x/text` 被正确移至 indirect 或移除 + +## 4. 清理 + +- [x] 4.1 删除 `worker/internal/i18n/` 目录 +- [x] 4.2 在项目根目录运行 `go build ./...` 确认编译通过