伪静态与多语言必须协同调试:Apache需正确配置mod_rewrite和AllowOverride,Nginx须显式设置fastcgi_split_path_info或改用s=参数;Lang::setLang()必须在路由分发前通过全局中间件执行,服务层调用lang()需确保容器管理及避免静态缓存,语言包占位符仅支持{name}格式,缺失key时默认返回原字符串且线上静默失败。

伪静态配不成功,控制器就 404;多语言逻辑层写错位置,lang() 函数永远返回原始 key。这两件事必须一起调,不能只改一边。
Apache 和 Nginx 的伪静态规则必须匹配 PATH_INFO 解析方式
ThinkPHP 默认用 PATH_INFO 模式解析 URL(比如 /index/index),但 Apache 和 Nginx 对它的支持机制不同:
- Apache 只要
.htaccess正确,且mod_rewrite开启、AllowOverride All配置到位,就能把/user/list映射成index.php/user/list,框架自动提取PATH_INFO - Nginx 默认不传
PATH_INFO,必须显式配置:fastcgi_split_path_info ^(.+\.php)(/.+)$;+fastcgi_param PATH_INFO $fastcgi_path_info;,否则$_SERVER['PATH_INFO']为空,路由全挂 - 更稳妥的 Nginx 写法是绕过
PATH_INFO,改用s=参数传参:try_files $uri $uri/ /index.php?s=$uri&$args;,这样框架走的是QUERY_STRING解析,兼容性更高
验证方法:访问 /index.php?s=/index/index 能进控制器,但 /index/index 404 → 就是服务器没正确传递路径信息。
Lang::setLang() 必须在路由解析前执行,否则控制器里 lang() 已缓存默认语言
ThinkPHP 的语言包加载和路由解析是两个独立流程,但有严格时序依赖:
立即学习“PHP免费学习笔记(深入)”;
-
Lang::setLang('en-us')必须在think\Route::dispatch()之前调用,否则控制器中第一次lang('hello')会按默认语言(如zh-cn)加载并缓存整个common.php数组 - 中间件是唯一可靠的位置,且必须是「全局中间件」,不能是「控制器中间件」或「闭包中间件」——因为后者执行时机晚于路由分发
- 别在
app\common\middleware\Lang.php的handle()里读 Session 后才调Lang::setLang(),要确保它早于任何lang()或Lang::get()调用 - 如果用了多模块(如
admin模块),语言包路径优先级是app/admin/lang/→app/lang/→ 框架默认,Lang::setLang()不影响这个查找顺序,只决定加载哪个目录下的文件
服务层调用 lang() 时要注意作用域和加载时机
服务类(如 app\service\UserService)里用 lang() 很常见,但容易出问题:
- 服务类构造函数里不能调
lang()—— 此时请求上下文未完全建立,Lang实例可能未初始化,返回空字符串或原 key - 方法体内调用没问题,但得确认该服务实例是在请求生命周期内创建的(比如通过容器
app()->get(UserService::class)获取),而不是new UserService()手动 new 的(会跳过容器生命周期管理) - 如果服务类做了静态缓存(如
private static $messages = [];),且缓存了lang()结果,那切换语言后不会刷新,必须清空或避免缓存翻译结果 - 语言包里的占位符只认
{name}格式,lang('welcome', ['name' => 'Tom'])对应'welcome' => '欢迎 {name}',写成:name或%s都无效
lang() 返回原 key 不报错,线上环境极易漏检
这是最隐蔽的问题:当语言包缺失某个 key,lang('missing_key') 默认返回字符串 "missing_key",而不是抛异常或打日志。
- 调试模式下会在
runtime/log/记一条 warning,但线上默认关闭,等于静默失败 - 建议在部署前跑一次完整性检查脚本:遍历所有控制器/视图中出现的
lang('xxx')字符串,再比对各语言包common.php是否都存在该 key - 别依赖「页面看起来正常」来判断多语言生效——比如中文站里
lang('submit')返回"submit",你可能以为是英文按钮,其实是 key 缺失 - 伪静态 URL 中带语言参数(如
/en-us/user/login)时,务必确保中间件从路径中提取语言码后立即调Lang::setLang(),而不是等控制器去解析,否则路由匹配阶段就已用错语言加载了语言包



















