ThinkPHP语言侦测默认仅初始化时执行一次,关键在正确配置输入源顺序及Cookie写入;需显式启用Lang中间件、确保其位于SessionInit后路由前,并在多应用/域名/CLI/队列等场景手动补全语言上下文。

ThinkPHP 的语言侦测(Lang::detect())默认只在请求初始化阶段执行一次,由 Lang 中间件触发,之后不再重复运行——这不是 bug,而是设计使然。问题本质是:你误以为它该“持续响应”或“多次识别”,但它本就只做一次上下文初始化。要解决“只运行一次”带来的切换失效、手动设语言不生效等问题,关键不是让它多跑几次,而是确保它在正确时机、用正确方式、拿到正确输入。
语言侦测实际只运行一次,但你可以控制它的输入源
框架按固定顺序读取语言标识:
- GET 参数(如
?lang=ja-jp)→ - Cookie(默认键名为
think_lang)→ - 请求头(如
think-lang: zh-cn)→ - 浏览器
Accept-Language
这个顺序不可改,但每个环节都可开关或重命名:
// config/lang.php 'detect_var' => 'lang', // GET参数名,?lang=en-us 才有效 'use_cookie' => true, 'cookie_var' => 'think_lang',// 必须和前端写入的键名完全一致 'header_var' => 'think-lang',// 前端需带 header: think-lang: en-us
✅ 正确做法:
立即学习“PHP免费学习笔记(深入)”;
- 切换语言时,统一走 Cookie 写入
think_lang(推荐),避免 GET 参数污染 URL 或 Header 配置复杂 - 前端 JS 切语言:
cookie('think_lang', 'en-us', { path: '/' })(用 ThinkPHP 自带的 cookie 辅助函数,自动处理 domain/path) - 后端切换(如用户点按钮):
cookie('think_lang', $lang)+Lang::set($lang)(后者仅影响本次请求,前者保证下次请求也生效)
❌ 常见错误:
- 前端写
document.cookie = "lang=zh-cn"→ 键名错,Lang 中间件直接忽略 -
Lang::set('en-us')单独调用 → 不写 Cookie,下个请求又回退到默认语言 - 中间件未注册或顺序错 → Lang 根本没执行,侦测为零次,不是一次
Lang 中间件必须显式启用且位置正确
它不会自动加载,必须出现在全局中间件栈中,并位于 SessionInit 之后、路由之前:
// app/middleware.php
return [
\think\middleware\SessionInit::class,
\think\middleware\Lang::class, // 必须在这儿,不能注释、不能挪后
// ... 其他中间件
];如果用了多应用模式(如 app/admin),还要在对应应用的 middleware.php 里单独加一遍。
域名/路径等动态场景,必须在 Lang 中间件 handle() 开头就完成语言设置
比如 zh.example.com 要切中文,不能等控制器才设:
// app/middleware/Lang.php 或全局中间件中
public function handle($request, \Closure $next)
{
$host = parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST);
$map = require app()->getConfigPath() . 'lang_domains.php';
$lang = $map[$host] ?? config('lang.default_lang');
// 关键:用 Config::set 改 default_lang,不是 Lang::setLocale()
\think\Config::set('lang.default_lang', $lang);
// 同时写 Cookie,确保后续请求也识别
cookie('think_lang', $lang, ['expire' => 86400, 'path' => '/']);
return $next($request);
}注意:
Lang::setLocale()在控制器里调基本无效,因为语言包已在初始化阶段载入完毕;验证器提示、系统异常等早已绑定默认语言。
静态资源或 CLI 场景需手动补全语言上下文
模板中
{:lang('submit')}或验证器提示,依赖请求期语言环境-
CLI 命令(如
php think send:notice)没有自动侦测,必须手动:Lang::setLang('zh-cn'); Lang::load(app()->getAppPath() . 'lang/zh-cn/common.php'); 队列任务同理:
php think queue:work是长进程,首次加载后语言环境固定,每次任务开始前应重新Lang::setLang($lang)并Lang::load()对应包
不复杂但容易忽略



















