hyperf 框架完善之国际化(多语言)
今天我们来看一下如何让 hyperf 支持国际化。
所谓的国际化就是多语言,比如前面抛出的异常信息“id无效”,我们希望客户端选择中文的时候提示“id无效”,选择英文的时候提示“id is invalid”,选择日语的时候提示“ID が無効です”等等,这里的国际化指的并不是全站内容的国际化,比如用户提问的问题内容。
首先多语言需要依赖 hyperf/translation 组件,容器内使用 composer 安装。
composer require hyperf/translation:3.0.*
生成 Translation 组件的配置文件 config/autoload/translation.php。
/data/project/questions # php bin/hyperf.php vendor:publish hyperf/translation [DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Config\Listener\RegisterPropertyHandlerListener listener. [DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\ExceptionHandler\Listener\ExceptionHandlerListener listener. [DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\DbConnection\Listener\RegisterConnectionResolverListener listener. [hyperf/translation] publishes [config] successfully.
config/autoload/translation.php 内容如下:
<?php declare(strict_types=1); return [ // 默认语言 'locale' => 'zh_CN', // 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本 'fallback_locale' => 'en', // 语言文件存放的文件夹 'path' => BASE_PATH . '/storage/languages', ];
其中 path 指的是语言文件存放的路径,默认配置在 storage/languages 下面。
参考下面的命令,分别创建3个语言包目录并分别创建3个 params.php 文件。
/data/project/questions # mkdir -p storage/languages/en /data/project/questions # mkdir -p storage/languages/zh_CN /data/project/questions # mkdir -p storage/languages/ja /data/project/questions # touch storage/languages/en/params.php /data/project/questions # touch storage/languages/zh_CN/params.php /data/project/questions # touch storage/languages/ja/params.php
params.php 内容分别如下:
storage/languages/en/params.php
<?php declare(strict_types=1); return [ 'id_invalid' => 'id is invalid', ];
storage/languages/ja/params.php
<?php declare(strict_types=1); return [ 'id_invalid' => 'ID が無効です', ];
storage/languages/zh_CN/params.php
<?php declare(strict_types=1); return [ 'id_invalid' => 'id 无效', ];
这3个文件分别表示对应3种语言,中文简体、英文和日语,后面如果我们有新的东西要翻译,只需要参考 params.php 这样配置即可。
下面尝试翻译一下,IndexService::info 方法改写如下:
public function info(int $id) { if ($id <= 0) { throw new BusinessException(trans('params.id_invalid')); } return ['info' => 'data info']; }
之前写的“id无效”已经被我们替换成 trans('params.id_invalid') 了,其中 trans 是全局翻译函数,函数的第一个参数使用键(指使用翻译字符串作为键的键) 或者是 文件.键 的形式,这个很好理解。
curl 测试一下。
curl http://127.0.0.1:9501/index/info\?id\=0 {"code":0,"message":"id 无效"}%
这与我们配置的默认语言(config/autoload/translation.php内配置的 locale)相符。
但是,我们要支持现 locale 跟随客户端动态更新才能满足需求 。
IndexService::info 更改如下:
public function info(int $id) { if ($id <= 0) { $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class); $translator->setLocale('ja'); throw new BusinessException(trans('params.id_invalid')); } return ['info' => 'data info']; }
IndexService::info 更改如下:
public function info(int $id) { if ($id <= 0) { $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class); $translator->setLocale('ja'); throw new BusinessException(trans('params.id_invalid')); } return ['info' => 'data info']; }
在这段代码中,我们通过容器(Container)获取对象,容器获得的对象都是单例,也就是说在整个生命周期,通过容器获得的对象会更加高效,减少了大量无意义的对象创建和销毁。
容器参考 https://hyperf.wiki/3.0/#/zh-cn/di?id=%e8%8e%b7%e5%8f%96%e5%ae%b9%e5%99%a8%e5%af%b9%e8%b1%a1
此时我们再试一下
curl http://127.0.0.1:9501/index/info\?id\=0 {"code":0,"message":"ID が無効です"}%
但是所有需要使用语言包的地方都要 setLocale 吗?自然有更好的解决方案。
引入中间价,全局处理这个问题。
中间件参考 https://hyperf.wiki/3.0/#/zh-cn/middleware/middleware
中间件的生成可以在终端使用 php bin/hyperf.php gen:migration xxx 生成,hyperf 提供了 gen 系列的一堆命令,大家可以在控制台输入 php bin/hyperf.php 后回车查看 php bin/hyperf.php 所支持的所有命令,这里就不列举了。
我们在终端输入下列命令,生成一个叫 GlobalMiddleware 的中间件。
/data/project/questions # php bin/hyperf.php gen:middleware GlobalMiddleware
生成好的 GlobalMiddleware.php 位于 App\Middleware 目录,全局中间件还需要在 config/autoload/middlewares.php 文件内配置才会生效。
config/autoload/middlewares.php 配置如下:
<?php declare(strict_types=1); return [ 'http' => [ \App\Middleware\GlobalMiddleware::class, // 全局中间件 ], ];
现在我们把 IndexService::info 方法内 setLocale 的逻辑移到 App\Middleware\GlobalMiddleware::process 方法内试试。
App\Middleware\GlobalMiddleware::class 代码如下:
<?php declare(strict_types=1); namespace App\Middleware; use Hyperf\Contract\TranslatorInterface; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; class GlobalMiddleware implements MiddlewareInterface { /** * @var TranslatorInterface */ protected $translator; public function __construct(ContainerInterface $container, TranslatorInterface $translator) { $this->translator = $translator; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if ($lang = $request->getHeaderLine('lang')) { $this->translator->setLocale($lang); } return $handler->handle($request); } }
中间价中接收来自请求头的 lang 参数来 setLocale,如此一来就实现了动态设置语言的功能。
curl 请求测试下
✗ curl http://127.0.0.1:9501/index/info\?id\=0 --header "lang:en" {"code":0,"message":"id is invalid"}% ✗ curl http://127.0.0.1:9501/index/info\?id\=0 --header "lang:ja" {"code":0,"message":"ID が無効です"}% ✗ curl http://127.0.0.1:9501/index/info\?id\=0 --header "lang:zh_CN" {"code":0,"message":"id 无效"}% ✗ curl http://127.0.0.1:9501/index/info\?id\=0 --header "lang:abc" {"code":0,"message":"id is invalid"}%
最后一个 "lang:abc" 是随便输入的,即不存在的语言包时默认是以 translation.php 的 fallback_locale 配置为准。
到此,框架即轻松实现国际化多语言的功能了。
我们自以为写的框架已经非常完美了,拿去和客户端对接,客户端一来说就说你没发现问题吗,你 response 的 code 都是 0,我怎么区分接口请求成功还是失败?举个例子,id 无效的时候能不能给个 code = 100001 之类的,方便判断?
巴拉巴拉一堆,说的似乎也有点道理。
下节课,我们继续完善。
- 评论区