Files
cursor/plans/ota-i18n-middleware-22f17591.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

5.2 KiB


todos:

  • id: "middleware" content: "新建 app/middleware/Language.php:读 X-Lang、归一化、每请求 locale()、设 $request->language" status: pending
  • id: "register" content: "config/middleware.php 全局组在 TraceId 前注册 Language 中间件" status: pending
  • id: "config" content: "config/translation.php 增加 supported=['zh_CN','en'],保留默认 zh_CN" status: pending
  • id: "trans-files" content: "新建 resource/translations/{zh_CN,en}/messages.php,含 errors(按 OtaErrorCode)+ validation + common.ok" status: pending
  • id: "exception" content: "BusinessException message 改可选;Handler 渲染时 trans('errors.'+code);Logic 7 处抛出去掉中文只传 code" status: pending
  • id: "result-auth" content: "Result::fail 支持空 message 回退 trans;AdminAuth/InternalTokenAuth 去掉中文消息" status: pending
  • id: "validate" content: "3 个 validator 的 $message 改翻译 key;AdminController::validate 返回前 trans();补全 validation.* 译文" status: pending
  • id: "verify" content: "internal/admin 带 X-Lang 验证消息随 locale 变化 + 连续请求无串台,跑收尾检测脚本" status: pending isProject: false

OTA 接口国际化(i18n)

结论先行

「中间件解析 header」思路正确,但在 Webman(常驻进程)里核心动作是 locale($lang),不是把语言塞进 $request->language$request->language 只作附带标记(给 Logic/日志用,非必须)。已具备:config/translation.phpsymfony/translation。缺:语言中间件、翻译文件、把硬编码消息接到 trans()

约定:自定义头 X-Lang(值 zh_CN / en),默认 zh_CN,覆盖 internal + admin。

数据流

flowchart LR
  req["HTTP 请求 X-Lang: en"] --> lang["Language 中间件 locale(en) + request->language"]
  lang --> auth["Auth 中间件 Result::fail(code)"]
  auth --> ctrl["Controller / Validate"]
  ctrl --> logic["Logic throw BusinessException(code)"]
  logic --> handler["Handler trans(code) 渲染"]
  handler --> resp["{code, message(已按 locale 翻译), data}"]

改动点

1. 语言中间件(新增)

新建 app/middleware/Language.php(MiddlewareInterface):

  • X-Lang(可兼容 X-Language),归一化(zh/zh-CN/zh_cnzh_CN;en/en-USen),不在支持列表则用默认。
  • 每个请求都调 locale($lang)(进程级全局状态,必须每请求重置,否则串语言)。
  • $request->language = $lang(附带)。
  • 支持列表 + 默认值读 config('translation')(见第 5 步)。

2. 注册为全局中间件

config/middleware.php'' 组在 TraceId 前加入 app\middleware\Language::class,确保 admin/internal 鉴权失败消息也按 locale 翻译。

3. 翻译文件(新增)

新建 resource/translations/zh_CN/messages.phpresource/translations/en/messages.php,返回两段:

  • errors => 以 OtaErrorCode 各常量字符串为 key 的消息(对应现 Logic / Auth 里的中文)。
  • validation => 校验消息 key(见第 6 步)。

调用形如 trans('errors.' . $code)trans($validationKey)

4. 消息改为 trans() 取值

  • app/exception/Handler.php:渲染 BusinessExceptionmessage = trans('errors.' . $code)(为空回退 code)。
  • app/exception/BusinessException.php:message 改为可选;Logic 抛出时只传 code,去掉硬编码中文(FirmwareLogic 5 处、OtaReportLogic/OtaCheckLogic/UpgradeRecordQueryLogic 各 1 处)。需要动态文案的保留可选 message 入参。
  • app/support/Result.php:fail(string $code, ?string $message = null, ...),$message 为空时取 trans('errors.' . $code);成功 messagetrans('common.ok')
  • 两个 Auth 中间件(AdminAuthInternalTokenAuth)Result::fail 去掉中文,只传 code。

5. 配置补充

config/translation.php 增加 supported => ['zh_CN','en'],locale 维持 zh_CN 作默认。中间件据此校验/回退。

6. 校验消息国际化(think\Validate)

为统一到单一 i18n(trans),不引入 think\Lang 第二套:

  • 3 个 validator(FirmwareCreateValidateFirmwareListValidateUpgradeRecordListValidate)的 $message 值改为翻译 key(如 validation.firmware.product_key_required)。
  • app/admin/controller/AdminController.php validate() 返回前对错误信息做 trans()(key 命中则翻译,未命中原样返回)。
  • 两个 locale 的 messages.php 补全这些 validation.* key。

验证

  • internal:POST /internal/ota/check 带/不带 X-Lang: en,断言鉴权失败、业务异常、参数错误的 message 随 locale 变化。
  • admin:固件创建非法参数,X-Lang: en 返回英文校验消息。
  • 连续两请求(en 后 zh_CN)验证无 locale 串台(常驻进程回归)。
  • 收尾跑 ~/.cursor/skills/slot-backend-completion-report/scripts/report.sh 并附检测结果。

不做 / 待确认

  • 不替换为 Accept-Language(已选自定义头)。
  • 英文译文先给准确直译,文案润色后续可调。