多语言网站基础及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 祖鲁语

 

文哥博客(https://wenge365.com)属于文野个人博客,欢迎浏览使用

联系方式:qq:52292959 邮箱:52292959@qq.com

备案号:粤ICP备18108585号 友情链接