根本原因是中间件执行顺序错位:loadlangpack默认优先级为10,若日志切换、路由识别等中间件未设更低优先级,则$request->lang()执行时语言尚未解析;log::channel()无效因门面缓存初始化实例,需用log::think()->setchannel()或每次new实例;插件多语言须自建小写短横线lang目录、注册专属中间件并在控制器前调lang::set(),且cookie需加plugin_前缀避免冲突。

Log::channel() 切换无效、$request->lang() 取不到值、日志写进 default.log —— 根本不是多语言配置错了,而是中间件执行顺序卡在 LoadLangPack 前后没对齐。
为什么 $request->lang() 在中间件里始终为空
ThinkPHP 的语言解析默认由 LoadLangPack 中间件完成,它默认优先级是 10。如果你的日志通道切换、路由模块识别、或自定义语言中间件没设更低优先级,那它们执行时 $request->lang() 还没被赋值,自然为空。
- 不要等
LoadLangPack执行完再读语言——它太晚了 - 改用更早可得的上下文:比如伪静态下路径第一段
explode('/', trim($request->url(), '/'))[0],前提是 Nginx/Apache 透传了原始 URI(需配fastcgi_param PATH_INFO $request_uri) - 若走子域名(
zh.example.com),直接解析$request->host(),并提前在config/lang.php里建好映射表
Log::channel() 切换后还是写进 default.log
这不是调用失败,是 ThinkPHP 的 Log 门面缓存了初始化时的 logger 实例。框架内部(如 SQL 日志、404 处理、异常捕获)用的仍是启动时绑定的 default 实例,Log::channel('zh') 只影响后续新生成的实例。
- 确保
config/log.php中'default' => 'file',而不是'default' => 'zh'——通道名只该出现在channels配置里 - 在中间件中别只调一次
Log::channel('zh'),改用Log::think()->setChannel('zh')(TP6.1+) - 更稳妥的做法:每次写日志前手动 new 实例,
$logger = Log::channel('zh'); $logger->error('msg');
插件内多语言切换不生效的隐藏陷阱
插件语言包不会被主应用的 Lang 中间件加载,必须自己注册中间件、手动调 Lang::set(),且时机必须在插件控制器执行前——否则插件内所有 lang() 都已固化为默认语言。
- 插件
lang/目录必须小写+短横线命名:lang/zh-cn/✔️,lang/ZH-CN/❌(Linux 区分大小写) - 每个语言子目录下必须有
common.php,内容是 PHP 数组格式:return ['submit' => '提交'];,JSON/YAML 被忽略 - 插件 Cookie 必须带前缀(如
plugin_wechat_lang),避免和主站think_lang冲突;前端切换时不能改全局 Cookie,要单独 fetch 插件接口或严格匹配path和domain
最易被忽略的是:Web 服务器是否透传原始请求路径。Nginx 默认重写后丢弃原始 URI,$request->url() 返回的是重写后的路径,语言前缀早已丢失——这时候再怎么调高中间件优先级也没用,得先从 fastcgi_param 配置开始查。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











