多语言网站基础及thinkphp多语言设置
所属分类:php | 发布于 2024-12-30
想给网站实现多语言,目标是这样的
https://wenge365.com/cn //中文首页
https://wenge365.com/en //英文首页
本来以为用thinkphp实现起来很简单,没想到还是要费点功夫。这里讲多语言网站要了解的内容以及用thinkphp实现多语言网站的方法记录在这里。
1、多语言网站的语言表示形式
目前有两种,动态url参数和用特定标识符固定在url上。
1.1、使用动态url参数,例如
https://wenge365.com/?lang=zh-cn
https://wenge365.com/?lang=en-us
1.2、特定标识符固定在url上,例如
https://wenge365.com/cn //中文首页
https://wenge365.com/en //英文首页
thinkphp使用的第一种,如果要用第一种表示方式,那就比较简单,跟着文档就能轻易实现。如果要使用第二种,就相对麻烦一点,要自己实现,这篇文章讲的就是第二种的实现方式。
2、语言标识符的选择
用zh_cn表示中文你介意吗,如果你不介意,那说明你不是强迫症患者。如果不是有强迫症的患者,可以忽略这段内容。
一般语言标识有三种选择:
- 仅语言Language代码,例如 en,zh
- 仅地区Region代码,例如 cn,us
- 语言地区language-region组合代码,例如zh-cn,en-us,zh-tw
三种表示方法各有优缺点,个人在这里中文选择了地区代码cn,英文选择了语言代码en,个人爱好而已。
这里附上国际化语言的通用表达方式
language-script-region-variant-extension-privateuse
1. language:这部分是ISO 639规定的代码,比如中文是zh。在最后小节会附上各个国家(地区)的语言代码参考表。
2. script:表示变体,比如简体汉字是zh-Hans,繁体汉字是zh-Hant。
3. region:表示语言使用的地理区域,比如zh-Hans-CN就是中国大陆所使用的简体中文。
4. variant:表示方言。
5. extenstion-privateuse:表示扩展用途和私有标识。
一般约定,language标签全部小写,region标签全部大写,script标签只有首字母大写。不同标签之间用连字符-链接。
3、thinkphp实现多语言
thinkphp默认的多语言第一步就是加载多语言包的中间件LoadLangPack,使用Cookie保存语言,我们是自己的方式实现,所以这一步可以略过。
3.1、配置lang.php
// 这里仅列出需要的配置项
// 默认语言
'default_lang' => env('lang.default_lang', 'en'),
// 允许的语言列表
'allow_lang_list' => ['cn', 'en'],
// Accept-Language转义为对应语言包名称
'accept_language' => [
'zh-cn' => 'cn',
'zh-hans-cn' => 'cn',
],
// 带描述的扩展语言包,前端语言切换的时候使用
'lang_display_list' => [
'cn' => '简体中文',
'en' => 'English',
],
其中lang_display_list是新增的,其它都是系统自带的,不过语言名称由zh-cn改成了cn,en-us改成了en。
3.2、在BaseController中做默认语言检测和设置
// BaseController
// Language
protected string $currentLanguage = '';
protected function initialize()
{
parent::initialize();
// 设置语言
$this->setLanguage();
}
/**
* 检测当前语言并设置语言
* @return void
*/
protected function setLanguage(): void
{
if ($this->request->param('manualLangSet')) {
$this->currentLanguage = $this->request->param('manualLangSet');
} else if ($this->request->param('langSet')) {
$this->currentLanguage = $this->request->param('langSet');
} else {
$acceptLanguages = config('lang.accept_language');
$acceptLanguage = $this->request->server('HTTP_ACCEPT_LANGUAGE');
if (preg_match('/^([a-z\d\-]+)/i', $acceptLanguage, $matches)) {
$matchedAcceptLanguage = strtolower($matches[1]);
$this->currentLanguage = $acceptLanguages[$matchedAcceptLanguage] ?? config('lang.default_lang');
} else {
$this->currentLanguage = config('lang.default_lang');
}
}
// 合法性判断
if (!in_array($this->currentLanguage, config('lang.allow_lang_list'))) {
$this->currentLanguage = config('lang.default_lang');
}
Lang::setLangSet($this->currentLanguage);
}
/**
* 设置两个变量到view
* 这个方法是根据个人习惯修改了thinkphp的默认方式,这里只列出关键代码
* @return View
*/
protected function view(string $template = '', $vars = [], $code = 200, $filter = null): View
{
$vars['langList'] = config('lang.lang_display_list');
$vars['currentLanguage'] = $this->currentLanguage;
return view($template, $vars, $code, $filter);
}
3.3、配置route.php
use think\facade\Route;
use think\facade\Config;
$langList = Config::get("lang.allow_lang_list");
foreach ($langList as $lang) {
$langAppend = ['langSet'=>$lang];
// set custom rules
// demo, 这里使用name()函数,给路由起个名称,在url()函数中使用这个名称
Route::rule("/{$lang}/demoTest", "demo/demoTest")->append($langAppend)
->name("demoTest_{$lang}");
// index process, the second without name
Route::rule("{$lang}$", 'index/indexPage')->append($langAppend)->name("index_{$lang}");
Route::rule("{$lang}/$", 'index/indexPage')->append($langAppend);
}
// thinkphp default rules,这里也可以移到上面的循环中去
Route::auto('cn/:controller/[:action]', ':controller/:action')->append(['langSet'=>'cn']);
Route::auto('en/:controller/[:action]', ':controller/:action')->append(['langSet'=>'en']);
注意:thinkphp8.1新增了Route::auto()函数,可以自定义默认的URL解析规则,通过充值默认解析规则,可以无缝衔接之前的解析规则,没这个函数的要,每个url都要列在上面。
3.4、处理index/index
在IndexController的index方法中需要做一个跳转,跳转到对应的语言首页
public function index()
{
// 路由的name()起作用了
return redirect(url("index_{$this->currentLanguage}"));
}
// 最终语言的首页在这里
public function indexPage(): \think\response\View
{
return $this->view();
}
其实语言首页url也可以封装到BaseController里面去,因为除了这里会用到,页面模版中也会用到,用于回到首页。
3.5、在common.php封装一个语言切换函数
if (!function_exists('switchLanguage')) {
function switchLanguage($fromLanguage, $toLanguage) {
$currentUrl = request()->url();
return preg_replace("/{$fromLanguage}/", $toLanguage, $currentUrl, 1);
}
}
3.6、在view中实现语言切换
<div class="btn-group mr-3 align-self-end">
<button type="button" class="btn btn-sm btn-choose-language dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
{$langList[$currentLanguage]}
</button>
<div class="dropdown-menu dropdown-menu-right" style="">
{foreach $langList as $langKey=>$langVal}
<a class="dropdown-item" href="{:switchLanguage($currentLanguage, $langKey)}">{$langVal}</a>
{/foreach}
</div>
</div>
4、语言代码-语言名称对照表
- af 南非语
- af-ZA 南非语
- ar 阿拉伯语
- ar-AE 阿拉伯语(阿联酋)
- ar-BH 阿拉伯语(巴林)
- ar-DZ 阿拉伯语(阿尔及利亚)
- ar-EG 阿拉伯语(埃及)
- ar-IQ 阿拉伯语(伊拉克)
- ar-JO 阿拉伯语(约旦)
- ar-KW 阿拉伯语(科威特)
- ar-LB 阿拉伯语(黎巴嫩)
- ar-LY 阿拉伯语(利比亚)
- ar-MA 阿拉伯语(摩洛哥)
- ar-OM 阿拉伯语(阿曼)
- ar-QA 阿拉伯语(卡塔尔)
- ar-SA 阿拉伯语(沙特阿拉伯)
- ar-SY 阿拉伯语(叙利亚)
- ar-TN 阿拉伯语(突尼斯)
- ar-YE 阿拉伯语(也门)
- az 阿塞拜疆语
- az-AZ 阿塞拜疆语(拉丁文)
- az-AZ 阿塞拜疆语(西里尔文)
- be 比利时语
- be-BY 比利时语
- bg 保加利亚语
- bg-BG 保加利亚语
- bs-BA 波斯尼亚语(拉丁文,波斯尼亚和黑塞哥维那)
- ca 加泰隆语
- ca-ES 加泰隆语
- cs 捷克语
- cs-CZ 捷克语
- cy 威尔士语
- cy-GB 威尔士语
- da 丹麦语
- da-DK 丹麦语
- de 德语
- de-AT 德语(奥地利)
- de-CH 德语(瑞士)
- de-DE 德语(德国)
- de-LI 德语(列支敦士登)
- de-LU 德语(卢森堡)
- dv 第维埃语
- dv-MV 第维埃语
- el 希腊语
- el-GR 希腊语
- en 英语
- en-AU 英语(澳大利亚)
- en-BZ 英语(伯利兹)
- en-CA 英语(加拿大)
- en-CB 英语(加勒比海)
- en-GB 英语(英国)
- en-IE 英语(爱尔兰)
- en-JM 英语(牙买加)
- en-NZ 英语(新西兰)
- en-PH 英语(菲律宾)
- en-TT 英语(特立尼达)
- en-US 英语(美国)
- en-ZA 英语(南非)
- en-ZW 英语(津巴布韦)
- eo 世界语
- es 西班牙语
- es-AR 西班牙语(阿根廷)
- es-BO 西班牙语(玻利维亚)
- es-CL 西班牙语(智利)
- es-CO 西班牙语(哥伦比亚)
- es-CR 西班牙语(哥斯达黎加)
- es-DO 西班牙语(多米尼加共和国)
- es-EC 西班牙语(厄瓜多尔)
- es-ES 西班牙语(传统)
- es-ES 西班牙语(国际)
- es-GT 西班牙语(危地马拉)
- es-HN 西班牙语(洪都拉斯)
- es-MX 西班牙语(墨西哥)
- es-NI 西班牙语(尼加拉瓜)
- es-PA 西班牙语(巴拿马)
- es-PE 西班牙语(秘鲁)
- es-PR 西班牙语(波多黎各(美))
- es-PY 西班牙语(巴拉圭)
- es-SV 西班牙语(萨尔瓦多)
- es-UY 西班牙语(乌拉圭)
- es-VE 西班牙语(委内瑞拉)
- et 爱沙尼亚语
- et-EE 爱沙尼亚语
- eu 巴士克语
- eu-ES 巴士克语
- fa 法斯语
- fa-IR 法斯语
- fi 芬兰语
- fi-FI 芬兰语
- fo 法罗语
- fo-FO 法罗语
- fr 法语
- fr-BE 法语(比利时)
- fr-CA 法语(加拿大)
- fr-CH 法语(瑞士)
- fr-FR 法语(法国)
- fr-LU 法语(卢森堡)
- fr-MC 法语(摩纳哥)
- gl 加里西亚语
- gl-ES 加里西亚语
- gu 古吉拉特语
- gu-IN 古吉拉特语
- he 希伯来语
- he-IL 希伯来语
- hi 印地语
- hi-IN 印地语
- hr 克罗地亚语
- hr-BA 克罗地亚语(波斯尼亚和黑塞哥维那)
- hr-HR 克罗地亚语
- hu 匈牙利语
- hu-HU 匈牙利语
- hy 亚美尼亚语
- hy-AM 亚美尼亚语
- id 印度尼西亚语
- id-ID 印度尼西亚语
- is 冰岛语
- is-IS 冰岛语
- it 意大利语
- it-CH 意大利语(瑞士)
- it-IT 意大利语(意大利)
- ja 日语
- ja-JP 日语
- ka 格鲁吉亚语
- ka-GE 格鲁吉亚语
- kk 哈萨克语
- kk-KZ 哈萨克语
- kn 卡纳拉语
- kn-IN 卡纳拉语
- ko 朝鲜语
- ko-KR 朝鲜语
- kok 孔卡尼语
- kok-IN 孔卡尼语
- ky 吉尔吉斯语
- ky-KG 吉尔吉斯语(西里尔文)
- lt 立陶宛语
- lt-LT 立陶宛语
- lv 拉脱维亚语
- lv-LV 拉脱维亚语
- mi 毛利语
- mi-NZ 毛利语
- mk 马其顿语
- mk-MK 马其顿语(FYROM)
- mn 蒙古语
- mn-MN 蒙古语(西里尔文)
- mr 马拉地语
- mr-IN 马拉地语
- ms 马来语
- ms-BN 马来语(文莱达鲁萨兰)
- ms-MY 马来语(马来西亚)
- mt 马耳他语
- mt-MT 马耳他语
- nb 挪威语(伯克梅尔)
- nb-NO 挪威语(伯克梅尔)(挪威)
- nl 荷兰语
- nl-BE 荷兰语(比利时)
- nl-NL 荷兰语(荷兰)
- nn-NO 挪威语(尼诺斯克)(挪威)
- ns 北梭托语
- ns-ZA 北梭托语
- pa 旁遮普语
- pa-IN 旁遮普语
- pl 波兰语
- pl-PL 波兰语
- pt 葡萄牙语
- pt-BR 葡萄牙语(巴西)
- pt-PT 葡萄牙语(葡萄牙)
- qu 克丘亚语
- qu-BO 克丘亚语(玻利维亚)
- qu-EC 克丘亚语(厄瓜多尔)
- qu-PE 克丘亚语(秘鲁)
- ro 罗马尼亚语
- ro-RO 罗马尼亚语
- ru 俄语
- ru-RU 俄语
- sa 梵文
- sa-IN 梵文
- se 北萨摩斯语
- se-FI 北萨摩斯语(芬兰)
- se-FI 斯科特萨摩斯语(芬兰)
- se-FI 伊那里萨摩斯语(芬兰)
- se-NO 北萨摩斯语(挪威)
- se-NO 律勒欧萨摩斯语(挪威)
- se-NO 南萨摩斯语(挪威)
- se-SE 北萨摩斯语(瑞典)
- se-SE 律勒欧萨摩斯语(瑞典)
- se-SE 南萨摩斯语(瑞典)
- sk 斯洛伐克语
- sk-SK 斯洛伐克语
- sl 斯洛文尼亚语
- sl-SI 斯洛文尼亚语
- sq 阿尔巴尼亚语
- sq-AL 阿尔巴尼亚语
- sr-BA 塞尔维亚语(拉丁文,波斯尼亚和黑塞哥维那)
- sr-BA 塞尔维亚语(西里尔文,波斯尼亚和黑塞哥维那)
- sr-SP 塞尔维亚(拉丁)
- sr-SP 塞尔维亚(西里尔文)
- sv 瑞典语
- sv-FI 瑞典语(芬兰)
- sv-SE 瑞典语
- sw 斯瓦希里语
- sw-KE 斯瓦希里语
- syr 叙利亚语
- syr-SY 叙利亚语
- ta 泰米尔语
- ta-IN 泰米尔语
- te 泰卢固语
- te-IN 泰卢固语
- th 泰语
- th-TH 泰语
- tl 塔加路语
- tl-PH 塔加路语(菲律宾)
- tn 茨瓦纳语
- tn-ZA 茨瓦纳语
- tr 土耳其语
- tr-TR 土耳其语
- ts 宗加语
- tt 鞑靼语
- tt-RU 鞑靼语
- uk 乌克兰语
- uk-UA 乌克兰语
- ur 乌都语
- ur-PK 乌都语
- uz 乌兹别克语
- uz-UZ 乌兹别克语(拉丁文)
- uz-UZ 乌兹别克语(西里尔文)
- vi 越南语
- vi-VN 越南语
- xh 班图语
- xh-ZA 班图语
- zh 中文
- zh-CN 中文(简体)
- zh-HK 中文(香港)
- zh-MO 中文(澳门)
- zh-SG 中文(新加坡)
- zh-TW 中文(繁体)
- zu 祖鲁语
- zu-ZA 祖鲁语