hyperf 框架完善之国际化(多语言)(连载中)

尾调露台
• 阅读 1047

转发自白狼栈:查看原文

今天我们来看一下如何让 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-...

此时我们再试一下

curl http://127.0.0.1:9501/index/info?id=0
{"code":0,"message":"ID が無効です"}%

但是所有需要使用语言包的地方都要 setLocale 吗?自然有更好的解决方案。

引入中间价,全局处理这个问题。

中间件参考 https://hyperf.wiki/3.0/#/zh-...

中间件的生成可以在终端使用 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 之类的,方便判断?

巴拉巴拉一堆,说的似乎也有点道理。

下节课,我们继续完善。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Stella981 Stella981
3年前
KaliTools说明书+BurpSuit实战指南+SQL注入知识库+国外渗透报告
!(https://oscimg.oschina.net/oscnet/d1c876a571bb41a7942dd9752f68632e.gif"15254461546.gif")0X00KaliLinux Tools中文说明书!(https://oscimg.oschina.net/oscnet/
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这