ThinkPHP后台多语言需同时满足三条件:显式注册Lang中间件、语言包路径严格匹配规范(如app/lang/zh-cn/common.php)、手动切换时校验白名单并调用Lang::setLocale()与Lang::load()。

ThinkPHP 后台多语言不是配完 lang_switch_on 就自动生效的——Lang 中间件没注册、语言包路径写错、切换逻辑放错位置,三个地方任一出问题,lang('xxx') 就会原样返回键名或空字符串。
Lang 中间件必须显式注册,否则检测和加载全失效
ThinkPHP 6 的 Lang 中间件默认不启用,哪怕你开了 lang_switch_on,它也不会自动运行。常见错误是只改了配置,却没在中间件栈里加它。
- 全站生效:把
think\middleware\Lang加到app/middleware.php的全局中间件数组里 - 仅后台路由生效:在路由分组里用
->middleware(Lang::class)显式绑定(注意命名空间) - 别在控制器构造函数里调
Lang::detect()—— 中间件已做这事,重复调可能覆盖结果 - 中间件执行顺序很重要:如果自定义中间件依赖语言环境,得确保它在
Lang之后执行
语言包路径和文件名必须严格匹配规范
ThinkPHP 只按固定规则找语言包:app/lang/{lang}/common.php 或 app/lang/{lang}/xx.php。任何偏差都会导致“Language file not exists”警告或静默失败。
-
{lang}必须是小写短横线格式,如zh-cn、en-us,不能是zh_CN、zh或ZH-CN - 文件必须是 PHP 脚本,返回纯关联数组,例如:
return ['login' => '登录', 'user.name' => '用户名']; - 嵌套键如
user.name要求语言包里结构一致:return ['user' => ['name' => '用户名']],平铺写法'user_name' => '用户名'不识别 - 目录权限要可读,且不能有 BOM 头(尤其 Windows 编辑器易带)
手动切换语言时,Lang::setLocale() 必须配合白名单校验
后台常需用户点击切换或从 URL 参数(如 ?lang=ja-jp)触发,但 Lang 中间件默认不解析 GET 参数,得自己处理,且不校验就直接设 locale 是严重风险。
立即学习“PHP免费学习笔记(深入)”;
- 从请求取值后必须校验是否在白名单内:
$lang = input('lang'); if (!in_array($lang, config('app.lang_list'))) { $lang = config('app.default_lang'); } - 设完立刻调
Lang::setLocale($lang),再补一句Lang::load()更稳妥(尤其非中间件上下文) - 别只存 cookie/session 而不设 locale —— 下次请求若没触发检测,仍用默认语言
- 如果用了
lang/:lang这类路由,记得在闭包里把:lang值传给Lang::setLocale(),不能只取不用
后台模板和控制器里调 lang() 返回键名?先查 Lang::range()
lang('login') 返回 'login' 本身,90% 是语言包根本没加载成功,而不是翻译没写对。这时候别猜,直接看运行时实际加载了哪些键。
- 在控制器或调试页面中加一行:
dump(Lang::range());,输出当前已加载的所有键值对 - 如果返回空数组,说明语言包路径/命名/中间件三者至少一个断了
- 如果数组里有内容但缺你要的键,检查是否拼写一致(大小写、点号、下划线)、是否被模块级路径隔离(如后台模块语言包应放在
app/lang/zh-cn/admin.php并显式加载) -
Lang::getLangSet()可确认当前生效的 locale 值,避免误判
最常被忽略的是「模块级语言包未启用」和「Lang 中间件注册时机太晚」——前者导致后台专属文案找不到,后者让控制器里第一次调 lang() 时环境还没准备好。这两个点不排查,其他都白配。



















