feat(web): 多语言界面(中/英/日)与用户语言偏好字段

- 新增 internal/i18n 包:以原始中文字符串为 key 的翻译目录(zh 恒等,
  en 兜底,ja 缺译回退 en),t/tf 模板函数与 Accept-Language 解析
  (按 q 值排序,兜底英语)
- User 模型新增 language 字段(auto|en|zh|ja,默认 auto),
  AutoMigrate 自动加列;UserStore 新增 UpdateLanguage
- 语言解析:登录用户按偏好(auto 时取浏览器 Accept-Language),
  登录/封禁页按浏览器语言;所有 c.HTML 渲染注入 Lang 模板数据
- 全部页面模板(用户页 + 管理后台 12 页)接入翻译:导航、文件夹、
  邮件列表/阅读/写信、设置、登录、封禁页、表单、表格、JS 确认框等;
  html lang 属性随语言切换;日期函数(shortDate/time12/time12m)
  与 folderLabel 改为语言感知(上午/下午 vs AM/PM vs 午前/午後)
- 设置页新增「界面语言」选择(自动/English/中文/日本語),
  /settings 表单支持单独提交语言偏好;管理后台用户表单可预设语言
- 封禁原因等入库中文文案在展示时经 t 翻译(未知 key 原样回退)
- 测试:i18n 单测(解析/回退/译文)、render 测试补 Lang 数据、
  时间格式化测试适配新签名;全量 go test 通过
This commit is contained in:
dsh
2026-08-20 00:21:10 -04:00
parent d1ffd10408
commit c881146a40
38 changed files with 1909 additions and 768 deletions
+14 -1
View File
@@ -30,6 +30,8 @@ type UserStore interface {
ListAll(page, size int) ([]db.User, int64, error)
UpdateUsedBytes(id uint, delta int64) error
UpdatePassword(userID uint, hashedPassword string) error
// UpdateLanguage 更新用户界面语言偏好(auto | en | zh | ja)。
UpdateLanguage(userID uint, lang string) error
// TryReserveQuota 原子预扣 delta 字节:仅在不超过配额时生效并返回 true,
// 否则不做任何修改返回 false。防止并发提交绕过配额检查(TOCTOU)。
TryReserveQuota(userID uint, delta int64) (bool, error)
@@ -204,11 +206,22 @@ func (s *userStoreGorm) TryReserveQuota(userID uint, delta int64) (bool, error)
func (s *userStoreGorm) UpdatePassword(userID uint, hashedPassword string) error {
return s.db.Model(&db.User{}).Where("id = ?", userID).
Updates(map[string]interface{}{
"password_hash": hashedPassword,
"password_hash": hashedPassword,
"must_change_password": false,
}).Error
}
// UpdateLanguage 更新用户界面语言偏好。非法值(非 auto/en/zh/ja)回退 auto。
func (s *userStoreGorm) UpdateLanguage(userID uint, lang string) error {
switch lang {
case "auto", "en", "zh", "ja":
default:
lang = "auto"
}
return s.db.Model(&db.User{}).Where("id = ?", userID).
Update("language", lang).Error
}
// ListAll retrieves a paginated list of all users across all domains.
func (s *userStoreGorm) ListAll(page, size int) ([]db.User, int64, error) {
var users []db.User