API多语言失效的根本原因是App::setLocale()未在请求生命周期早期、API中间件中显式调用,且未与Accept-Language解耦;必须通过LocaleMiddleware校验并设置locale,否则__(),验证消息等均保持默认语言。

API 接口返回的错误提示、成功消息始终是英文,或切换语言后没生效——根本原因不是翻译文件写错了,而是 App::setLocale() 没在正确时机、正确作用域下执行,且未与请求上下文解耦。
API 响应中 __() 不随 Accept-Language 变化
这是最常见错觉:以为浏览器带了 Accept-Language: zh-CN,Laravel 就会自动切语言。实际不会。__() 完全依赖当前请求生命周期内最后一次 App::setLocale() 的调用结果,而框架默认不为 API 请求做这件事。
- 必须显式注册中间件(如
LocaleMiddleware)到$middlewareGroups['api'],不能只加在web组 - 中间件里要用
$request->getPreferredLanguage(['en', 'zh_CN', 'ja'])提取并校验,不能直接取 header 字符串——否则可能传入zh-CN;q=0.9这类非法值导致InvalidArgumentException -
config('app.available_locales')必须与resources/lang/下的目录名完全一致(比如用zh_CN就不能写成zh-cn或zh) - 验证错误消息(如
validation.php)也受此 locale 控制,但 FormRequest 的messages()方法会在failedValidation之前执行,此时 locale 若未设置,就会固化为默认值
route() 生成的 URL 在 API 中永远不带 locale 前缀
别试了。route('api.users.index') 永远输出 /api/users,哪怕你在中间件里调了 App::setLocale('zh_CN')。因为 route() 只看路由定义和传参,和当前 locale 无关。
- API 路由通常不用前缀,
route()本就不该也不需要生成带语言的 URL - 如果你在响应里返回跳转链接(如
next字段),应手动拼接或使用辅助函数,例如:url('/api/users?locale=' . app()->getLocale()) - 若硬要前缀(如
/zh/api/users),得把{locale}显式写进路由定义,并在每个route()调用时传['locale' => ...]——这对纯 API 场景既冗余又易错
FormRequest 验证失败时消息仍是默认语言
这是因为 Laravel 的验证器在构造 MessageBag 时就已锁定语言环境。一旦进入 failedValidation(),再调 App::setLocale() 已无效。
- 解决方案是重写 FormRequest 的
messages()方法,直接返回多语言键值对:return ['email.required' => __('validation.email.required')]; - 不要在
rules()里用__('validation.email.required')——它会被当字符串字面量解析,不触发翻译 - 更稳妥的做法是在控制器中手动验证:
$validator = Validator::make(...)->setLocale(app()->getLocale());,再调$validator->validate() - 注意:自定义
messages()后,attributes()和values()仍需单独处理,它们不自动继承 locale
locale 参数覆盖时忽略非法值导致静默回退
客户端传 ?locale=fr,而你的白名单只有 ['en', 'zh_CN'],如果代码里没做严格校验,App::setLocale('fr') 会抛出异常,或静默降级到 fallback_locale,前端却收不到任何提示。
- 务必用
in_array($locale, config('app.available_locales'), true)校验,true参数防止'1' == 'en'类型混淆 - 校验失败时,不要跳过设置,而是明确设回当前有效 locale(如从 session 读)或返回 400 错误:
throw new HttpResponseException(response()->json(['message' => __('errors.invalid_locale')], 400)); - 避免在响应体里混用语言:比如 locale=zh 时,却因某处漏设
App::setLocale(),导致__('auth.failed')返回英文,而其他字段是中文
真正容易被忽略的是:API 的多语言本质是「每请求一次语言上下文」,不是「用户一次选择永久生效」。它不依赖 session、不修改全局配置、不生成带前缀的 URL——所有逻辑必须收敛在单次请求的中间件 → 控制器 → 响应链中,少一步,就断一环。


















