中间件读取语言优先从路由参数{locale}获取,其次Cookie、Header;设置语言必须调用app()->setLocale($locale)且在视图渲染前生效,不可仅改配置或session。

中间件里怎么读取和设置语言
语言切换的中间件核心就两件事:从请求里拿到用户想用的语言(比如 URL 路径、cookie 或 header),然后告诉 Laravel 用它渲染后续内容。关键不是“存”,而是“激活”——App::setLocale() 必须在视图渲染前执行,且得在 trans()、__() 等函数调用之前生效。
常见错误是把 App::setLocale() 放在中间件 handle() 末尾,或者只设了 locale 却没同步到 Lang 的运行时状态。Laravel 6+ 默认用 Illuminate\Translation\Translator,它依赖 app()->getLocale(),而这个值由 App::setLocale() 控制,不是靠改配置文件或环境变量。
- 优先从
request()->route()->parameter('locale')取(对应/zh/home这类路由) - fallback 到
request()->cookie('locale'),再 fallback 到request()->header('Accept-Language')解析(别直接信 header 值,要用substr($lang, 0, 2)截取主语言码) - 设完必须立刻调用
app()->setLocale($locale),不能只改config('app.locale') - 如果用了缓存翻译文件(
php artisan lang:publish或自定义缓存),记得清掉旧缓存,否则新 locale 加载不到翻译
为什么路由参数方式比 session 更可靠
Session 依赖客户端 cookie + 服务端存储,中间件执行时 session 可能还没 fully started(尤其在某些中间件顺序下),导致 session('locale') 读不到;而路由参数是 Laravel 路由解析后就确定的,中间件里绝对可读。
典型翻车场景:用户点链接 /en/about,但中间件里去读 session('locale'),结果还是上一次的 zh,页面渲染一半才切过去,__('Welcome') 输出中文。
- 路由定义要显式带 locale 参数:
Route::prefix('{locale}')->group(...),并加where('locale', '[a-z]{2}')约束 - 避免在中间件里写
session()->put('locale', $locale)—— 不必要,还可能污染 session - 如果真要用 session,确保该中间件在
\Illuminate\Session\Middleware\StartSession::class之后执行(查app/Http/Kernel.php的$middlewareGroups['web']顺序)
如何让 URL 切换语言时不丢失当前路径
这不是中间件的事,但常被一起问。中间件只管“当前请求用什么语言”,跳转逻辑得在 Blade 或控制器里做。关键是生成目标语言 URL 时,保留除 locale 外的所有路由参数和查询字符串。
硬拼 URL 容易漏 query 或嵌套参数,route() 辅助函数最稳,但它默认用当前 locale。所以得手动覆盖:
@foreach(['zh' => '中文', 'en' => 'English'] as $locale => $label)
<a href="{{ route(Route::currentRouteName(), array_merge(Request::route()->parameters(), ['locale' => $locale]) + Request::query()) }}">{{ $label }}</a>
</foreach>
- 别用
url()->current()拼接,它不处理 locale 参数替换 - 如果当前路由没命名(
->name('home')),先补上,不然route()报Route [xxx] not defined - 注意
Request::query()是关联数组,直接 + 数组会丢键,用array_merge()安全
多语言中间件上线后容易忽略的兼容点
本地开发跑得通,一上生产就出问题,大概率卡在这几个地方:
- 缓存配置没清:执行过
php artisan config:cache后,config/app.php的'locale' => env('APP_LOCALE', 'en')就固化了,中间件设的 locale 会被覆盖——解决方法是别在 config 里写死 locale,中间件设完就生效,不用动 config - 队列任务里语言失效:中间件不跑在队列中,
Mail::to(...)->send(new WelcomeMail())发邮件时默认用 config 的 locale。必须在任务里手动app()->setLocale($user->preferred_locale) - API 请求没走 web 中间件组:如果 API 路由写在
routes/api.php,默认不经过 web 组里的语言中间件。要么把中间件加进api组,要么单独给 API 写个轻量中间件(只设 locale,不重定向、不处理 cookie)
语言切换看着简单,真正卡住人的永远是“谁在什么时候读了哪个 locale 值”。中间件只是起点,后面视图、邮件、队列、API 全得对齐。










