ThinkPHP 6+ 官方主推 lang(),L() 是旧版遗留接口;lang() 严格依赖 Lang::setLang() 预设语言环境,支持 {key} 占位符和 item|items 复数语法,调用前须确保语言码(如 zh-cn)正确、语言包路径规范且已通过中间件优先设置。

直接用 lang() 函数,不是 L(),也不是模板标签;它在控制器、模型、中间件里都能调用,且必须确保语言已提前设置好。
lang() 和 L() 到底该用哪个
ThinkPHP 6+ 官方主推 lang(),L() 是旧版遗留接口,虽仍可用但不推荐新项目使用。两者行为有关键差异:
-
lang()严格依赖Lang::setLang()设置的语言环境,不自动探测 -
L()在未显式设置时会尝试从 URL 参数(lang=)、Cookie、Session 自动识别(前提是lang_switch_on开启),但逻辑隐蔽、调试困难 -
lang()支持{key}占位符和item|items复数语法;L()不支持复数,占位符需手动str_replace - 模板中写
{:lang('hello')}可以,但{__('hello')}是别名,本质还是调lang()
lang() 调用前必须满足的三个条件
哪怕语言包文件全对、lang_switch_on 已开启,lang() 仍可能返回原 key 字符串(比如直接输出 'login_failed' 而不是翻译后内容)——大概率是以下任一条件没满足:
-
Lang::setLang($lang)必须在任何lang()调用之前执行,典型位置是中间件的handle()方法最开头 - 语言码必须小写且带短横,如
zh-cn,不是ZH_CN、zh_CN或zhcn;Linux 服务器上大小写敏感,错一个字符就加载失败 - 语言包路径必须是
lang/zh-cn/common.php,且该文件必须以return ['login_failed' => '登录失败'];形式返回数组;放错目录(如config/lang.php)或用 JSON/YAML 都无效
占位符和复数怎么写才生效
lang() 的语法非常轻量,不兼容 gettext,也不接受任意格式占位符:
立即学习“PHP免费学习笔记(深入)”;
- 只认
{name}这种大括号包裹的 key 名,不支持:name、%s、{0}等写法 - 传参必须是关联数组:
lang('welcome', ['name' => 'Tom'])→ 语言包里对应'welcome' => '欢迎 {name}' - 复数用竖线分隔:
'item|items';传入数字1时取左边,其他值(包括0、2、1.5)都取右边 - 如果 key 不存在,
lang()默认静默返回原字符串(如lang('missing')返回'missing'),线上环境不会报错也不会提示,容易误判为“已生效”
为什么 lang() 在控制器构造函数里调用无效
因为 ThinkPHP 的请求生命周期中,中间件执行早于控制器实例化。你在控制器 __construct() 里调 Lang::setLang('en'),此时 lang() 在中间件或路由层可能已被调用过,翻译已按默认语言缓存。
正确做法是:在中间件(如 app\common\middleware\Lang.php)中读取用户偏好(Session / Cookie / URL),并第一时间调用 Lang::setLang():
public function handle($request, \Closure $next)
{
$lang = $request->session('lang', 'zh-cn');
\think\Lang::setLang($lang);
return $next($request);
}
注意:这个中间件必须注册在全局中间件队列靠前位置(比如放在 app/middleware.php 的第一个),否则后面中间件里若已调用 lang(),就来不及了。



















