这几天公司一个老管理后台要加多语言,因为我们有几家海外客户,需要用英文界面。虽然后台之前是全中文的,但老板一句话,就得改。我翻开代码一看,所有页面都是硬编码的中文汉字,这要是一个个去替换,得改到猴年马月。后来想起ThinkPHP自带多语言功能,专门试了试,却发现官方文档写得比较零碎,自己折腾了大半天才把最简单的语言切换跑通。这里就把我的实践过程记录下来,顺便分享几个我自己绕过的坑,希望能帮你省点时间。
在开始之前,先弄明白ThinkPHP多语言解决的什么问题
简单说,就是给你的项目准备两份(或多份)语言文件,一份中文,一份英文。然后在项目的配置里开启多语言,之后你可以在模板里用lang()函数或者{:lang('key')}标签来输出翻译文本。系统会根据你当前的语言标识去对应语言包中取词条。
这样就把“画面显示的文字”和“程序逻辑”拆开了。你想让整站变成英文,不需要改动一行PHP,只要切换默认语言即可。
第一步:开启多语言配置
在TP8里,多语言默认是关闭的。你需要修改config/lang.php配置文件(可能位于config/lang.php或config/app.php,看你的版本)。我这里用的是TP8.0,配置文件是config/lang.php。最关键的是设置'lang_switch_on'为true,并设置默认语言。
// config/lang.php
return [
// 默认语言
'default_lang' => 'zh-cn',
// 多语言开关
'lang_switch_on' => true,
// 允许的语言列表
'allow_lang_list' => ['zh-cn', 'en-us'],
];
这里我用zh-cn和en-us作为标识,你也可以用zh和en,看团队习惯。
第二步:创建语言文件
TP的语言包放在app/lang/目录下,每个语言一个子目录。例如:
app
└── lang
├── zh-cn.php
└── en-us.php
这里注意,语言文件名与default_lang一致。我创建了两个文件,里面返回一个数组,键是词条名,值是对应的译文。
zh-cn.php
<?php
return [
'welcome' => '欢迎使用后台管理系统',
'login' => '登录',
'logout' => '退出',
'user' => '用户',
'order' => '订单',
'settings' => '设置',
'action' => '操作',
'status' => '状态',
];
en-us.php
<?php
return [
'welcome' => 'Welcome to Admin Panel',
'login' => 'Login',
'logout' => 'Logout',
'user' => 'User',
'order' => 'Order',
'settings' => 'Settings',
'action' => 'Actions',
'status' => 'Status',
];
第三步:在模板中使用语言包
在后台的HTML模板中,把原来写死的文字用{:lang('word')}替换。比如原来的代码:
<h1>欢迎使用后台管理系统</h1>
<button>登录</button>
改成:
<h1>{:lang('welcome')}</h1>
<button>{:lang('login')}</button>
如果你在控制器里也要用,可以用lang('order')函数。比如在返回的JSON信息中,提示语就可以换成多语言。
return json(['msg' => lang('action_success')]);
不过我这里没定义action_success,你就理解这个意思。
第四步:实现语言切换
多语言的核心就是能够动态切换。ThinkPHP默认通过请求参数来切换语言。官方推荐的方式是在URL中带上l参数。例如?l=en-us。你可以在URL上手动加这个参数,框架会在请求开始时读取$_GET['l']并设置当前语言,前提是开启了lang_switch_on。
为了让切换更友好,我在模板顶部加了一个语言下拉框,表单提交或跳转时带上l参数。下面是一个简单的示例:
<select onchange="location.href='?l='+this.value">
<option value="zh-cn" {if $Think.get.l == 'en-us'}else{/if} selected??{/if}>中文</option>
<option value="en-us">English</option>
</select>
这段代码里,我用了ThinkPHP模板引擎的语法,$Think.get.l可以获取到当前的l参数。如果存在就高亮选中,不然默认中文。
实际项目中,语言切换往往需要让整个页面都记住选择,我喜欢把语言写入cookie。你可以在公共控制器的初始化方法中读取并设置:
// 公共控制器的initialize方法
public function initialize()
{
$lang = $this->request->cookie('lang');
if ($lang) {
thinkfacadeLang::setLangSet($lang);
}
parent::initialize();
}
然后在切换接口里动态写入cookie,再重定向回当前页。这里就不详细写控制器代码了,思路就是这样:获取语言参数,写入cookie,最后继续执行。
实战小案例:给一个订单列表页面加多语言
光说不练假把式。我拿后台的订单列表做了个简单的演示。假设页面输出表头和状态列。
控制器方法(简化版)
<?php
namespace appcontroller;
use thinkController;
use thinkfacadeView;
class Order extends Controller
{
public function index()
{
$orders = [
['id'=>1, 'user'=>'张三', 'status'=>1],
['id'=>2, 'user'=>'李四', 'status'=>0],
];
View::assign('orders', $orders);
return View::fetch();
}
}
模板文件(order/index.html)
<table>
<thead>
<tr>
<th>{:lang('order')} ID</th>
<th>{:lang('user')}</th>
<th>{:lang('status')}</th>
<th>{:lang('action')}</th>
</tr>
</thead>
<tbody>
{foreach $orders as $order}
<tr>
<td>{$order.id}</td>
<td>{$order.user}</td>
<td>
{if $order.status == 1}
<span style="color: green">{:lang('paid')}</span>
{else}
<span style="color: red">{:lang('unpaid')}</span>
{/if}
</td>
<td>
<a href="/order/edit?id={$order.id}" rel="external nofollow" >{:lang('edit')}</a>
<a href="/order/delete?id={$order.id}" rel="external nofollow" >{:lang('delete')}</a>
</td>
</tr>
{/foreach}
</tbody>
</table>
这里我用了'paid'、'unpaid'、'edit'、'delete'这些词条,你没有定义的话页面会直接显示这些英文键名。为了演示完整,我把它们也加进语言包里:
// 中文增加
'paid' => '已支付',
'unpaid' => '未支付',
'edit' => '编辑',
'delete' => '删除',
// 英文增加
'paid' => 'Paid',
'unpaid' => 'Unpaid',
'edit' => 'Edit',
'delete' => 'Delete',
遇到的一些坑
坑一:语言文件位置放错了
我开始把语言文件直接放到config下面,结果一点效果都没有。TP8的语言包默认路径是app/lang,不是config。你还可以在lang.php配置里重新指定路径,但没必要除非你有特殊部署。
坑二:URL参数l不生效
如果你配置了'lang_switch_on' => true,但访问?l=en-us还是中文,可能是你没在公共控制器的initialize里设置,或者没使用Lang::load()加载语言包。在TP8中,请求参数l会自动被识别,但前提是你要开启allow_lang_list。我发现如果allow_lang_list没有包含en-us,则不会切换。
坑三:模板的lang函数被当成普通方法
在模板里使用{:lang('welcome')}时,如果前面的:没写,它就变成纯文本输出了。我上次就少写冒号,结果页面上直接显示“lang(‘welcome’)”。所以千万别漏了。
坑四:JS里的文字没法直接翻译
这种方法只适合模板输出的HTML。如果你在JS里写了alert('确定删除吗?'),那就无法直接通过语言包替换。解决思路是:在PHP端把语言包传给JS,或者用Ajax调用后端接口。我目前做的项目JS提示不多,所以暂时没做特殊处理,但如果你全站都靠JS弹窗,那得在模板中把语言包赋值到页面上。
例如在模板底部加一行:
<script>window.LANG = {!! json_encode(lang('confirm_delete')) !!};</script>
这样JS里就能用LANG.confirm_delete了。
更灵活的做法:使用变量而不是当前请求
如果你需要基于用户设置(比如用户在个人中心选择了英文,以后每次登录都是英文),那你得把语言标识存进用户表或缓存里,然后在登录后动态设置语言。
我一般是这样做的:在用户登录时,把他的语言偏好存到缓存,然后在全局中间件中读取并设置。
// 全局中间件 handle
public function handle($request, Closure $next)
{
$userLang = $request->cookie('lang');
if (!$userLang) {
$userLang = 'zh-cn';
}
thinkfacadeLang::setLangSet($userLang);
return $next($request);
}
最后一个小结
多语言并不是一个复杂功能,但它涉及配置、变量传递、模板处理等多个环节。用ThinkPHP 8的系统语言包功能,能省掉造轮子的时间。只要建立一个清晰的词条管理方式,后续加新语言也很简单——再写一个语言包就行。另外,除了词条,你还要考虑日期格式、货币单位、数字分隔符等,这些可能会比文字更麻烦,但那就是另一个话题了。
希望我这篇实战笔记对你有点用处,至少让你少走几个弯路。

