--- 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.php`、`symfony/translation`。缺:语言中间件、翻译文件、把硬编码消息接到 `trans()`。 约定:自定义头 `X-Lang`(值 `zh_CN` / `en`),默认 `zh_CN`,覆盖 internal + admin。 ## 数据流 ```mermaid 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_cn` → `zh_CN`;`en`/`en-US` → `en`),不在支持列表则用默认。 - **每个请求都调 `locale($lang)`**(进程级全局状态,必须每请求重置,否则串语言)。 - 设 `$request->language = $lang`(附带)。 - 支持列表 + 默认值读 `config('translation')`(见第 5 步)。 ### 2. 注册为全局中间件 [config/middleware.php](config/middleware.php) 的 `''` 组在 `TraceId` 前加入 `app\middleware\Language::class`,确保 admin/internal 鉴权失败消息也按 locale 翻译。 ### 3. 翻译文件(新增) 新建 `resource/translations/zh_CN/messages.php` 与 `resource/translations/en/messages.php`,返回两段: - `errors` => 以 `OtaErrorCode` 各常量字符串为 key 的消息(对应现 Logic / Auth 里的中文)。 - `validation` => 校验消息 key(见第 6 步)。 调用形如 `trans('errors.' . $code)`、`trans($validationKey)`。 ### 4. 消息改为 trans() 取值 - [app/exception/Handler.php](app/exception/Handler.php):渲染 `BusinessException` 时 `message = trans('errors.' . $code)`(为空回退 code)。 - [app/exception/BusinessException.php](app/exception/BusinessException.php):`message` 改为可选;Logic 抛出时只传 code,去掉硬编码中文(`FirmwareLogic` 5 处、`OtaReportLogic`/`OtaCheckLogic`/`UpgradeRecordQueryLogic` 各 1 处)。需要动态文案的保留可选 message 入参。 - [app/support/Result.php](app/support/Result.php):`fail(string $code, ?string $message = null, ...)`,`$message` 为空时取 `trans('errors.' . $code)`;成功 `message` 用 `trans('common.ok')`。 - 两个 Auth 中间件(`AdminAuth`、`InternalTokenAuth`)`Result::fail` 去掉中文,只传 code。 ### 5. 配置补充 [config/translation.php](config/translation.php) 增加 `supported => ['zh_CN','en']`,`locale` 维持 `zh_CN` 作默认。中间件据此校验/回退。 ### 6. 校验消息国际化(think\Validate) 为统一到单一 i18n(trans),不引入 think\Lang 第二套: - 3 个 validator(`FirmwareCreateValidate`、`FirmwareListValidate`、`UpgradeRecordListValidate`)的 `$message` 值改为翻译 key(如 `validation.firmware.product_key_required`)。 - [app/admin/controller/AdminController.php](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(已选自定义头)。 - 英文译文先给准确直译,文案润色后续可调。