hyperf 十四 国际化

一 安装

复制代码
composer require hyperf/translation:v2.2.33

二 配置

1、设置语言文件

文件结构:

/storage/languages/en/messages.php

/storage/languages/zh_CH/messages.php

php 复制代码
// storage/languages/en/messages.php
return [
    'welcome' => 'Welcome to our application :test :test2',
    'test' => '{2} TEST1|[3,10] TEST2|[20,*] TEST3',
];


// storage/languages/zh_CH/messages.php
return [
    'welcome' => '欢迎-使用  :test :test2',
];

2、设置配置文件

创建文件 /config/autoload/translation.php。

php 复制代码
#/config/autoload/translation.php
return [
    // 默认语言
    'locale' => 'zh_CN',
    // 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本
    'fallback_locale' => 'en',
    // 语言文件存放的文件夹
    'path' => BASE_PATH . '/storage/languages',
];

三 使用

1、临时设置语言

php 复制代码
// storage/languages/zh_CH/messages.php
return [
    'welcome' => '欢迎-使用',
];

#/config/autoload/translation.php
return [
    // 默认语言
    'locale' => 'en',
    // 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本
    'fallback_locale' => 'en',
    // 语言文件存放的文件夹
    'path' => BASE_PATH . '/storage/languages',
];

//App\Controller\TestController
use Hyperf\Di\Annotation\Inject;
use Hyperf\Contract\TranslatorInterface;

class TestController
{
    /**
     * @Inject
     * @var TranslatorInterface
     */
    private $translator;
    
    public function index()
    {
        $value = $this->translator->trans('messages.welcome', [], 'zh_CN');
        var_dump($value);
    }
}

# 输出
string(26) "欢迎-使用"

2、全局函数

php 复制代码
// storage/languages/zh_CH/messages.php
return [
    'welcome' => '欢迎-使用',
    'test1' => '测试',
];

// config/autoload/translation.php
return [
    // 默认语言
    'locale' => 'zn_CH',
    // 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本
    'fallback_locale' => 'en',
    // 语言文件存放的文件夹
    'path' => BASE_PATH . '/storage/languages',
];

// App\Controller\TestController
class TestController
{
    /**
     * @Inject
     * @var TranslatorInterface
     */
    private $translator;
    
    public function test2()
    {
        echo __('message.welcome') . "\n"; //欢迎-使用
        echo trans('message.welcome') . "\n";//欢迎-使用
    }
}

4、自定义占位符

php 复制代码
// storage/languages/en/messages.php
return [
    'welcome' => 'Welcome to our application :test :test2',
];

// config/autoload/translation.php
return [
    // 默认语言
    'locale' => 'en',
    // 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本
    'fallback_locale' => 'en',
    // 语言文件存放的文件夹
    'path' => BASE_PATH . '/storage/languages',
];

// App\Controller\TestController
class TestController
{
    /**
     * @Inject
     * @var TranslatorInterface
     */
    private $translator;
    
    public function test2()
    {
        echo __('message.welcome',['test'=>'qqq','test2':'aaa']) . "\n"; 
        //Welcome to our application qqq aaa
        echo trans('message.welcome',['test'=>'qqq','test2':'aaa']) . "\n";
        //Welcome to our application qqq aaa
    }
}

5、复数处理

php 复制代码
// storage/languages/en/messages.php
return [
    'test' => '{2} TEST1|[3,10] TEST2|[20,*] TEST3',
];

// config/autoload/translation.php
return [
    // 默认语言
    'locale' => 'en',
    // 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本
    'fallback_locale' => 'en',
    // 语言文件存放的文件夹
    'path' => BASE_PATH . '/storage/languages',
];

// App\Controller\TestController
class TestController
{
    /**
     * @Inject
     * @var TranslatorInterface
     */
    private $translator;
    
    public function test2()
    {
        echo $this->translator->transChoice('message.test',0)."\n";
        echo trans_choice('message.test',0) . "\n"; 
        echo trans_choice('message.test',2) . "\n"; 
        echo trans_choice('message.test',4) . "\n";
        echo trans_choice('message.test',22) . "\n";  
    }
}

//输出
 TEST1
 TEST1
TEST1
TEST2
TEST3

四 详解

1、调用

多语言的调用从注入开始,即Hyperf\Translation\Translator::__construct(TranslatorLoaderInterface loader, string locale)方法。根据配置文件TranslatorLoaderInterface 对应Hyperf\Translation\FileLoaderFactory类。读取配置文件/storage/languages/translation.path,并返回Hyperf\Translation\FileLoader类。即注入到Translator的构造的TranslatorLoaderInterface 实体类是FileLoader。

若无/storage/languages/translation.path配置文件,可使用默认配置vendor\hyperf\translation\publish\translation.php生成。

php 复制代码
php bin/hyperf.php vendor:publish hyperf/translation

使用的语言标志比如en、zn_ch,在上下文中读取和设置。

Translator内调用顺序:

Translator::trans()->Translator::get()->Translator::getLine()->Translator::load()->FileLoader::load()

根据FileLoader::load()调用FileLoader::loadJsonPaths(),可以将不同语言的不同文件统一放到json文件中,使用FileLoader::addJsonPath()设置对应文件。会便利对应文件加载内容,就是对应语言的全部内容。

根据FileLoader::load()调用FileLoader::loadPath(),加载对应文件。比如翻译a.b,a是对应语言的组名,b对应是键名,文件是/storage/languages/对应语言/a.php。

根据FileLoader::load()调用FileLoader::loadNamespaced(),用命名空间加载。这里所谓命名空间就是,对比默认路径,设置一个键名对应非默认路径。也是调用loadPath()实现,不过传入非默认路径,用命名空间获取路径值,用FileLoader::addNamespace()设置命名空间和路径值。

Translator::trans()->Translator::get()->Translator::getLine()->Translator::makeReplacements()->sTranslator::ortReplacements()

根据Translator::ortReplacements(),查询字符串中":占位符"或":占位符全大写"或":占位符首字母大写"。

Translator::transChoice()->Translator::choice()->Translator::makeReplacements()->Translator::getSelector()->MessageSelector::choose()

Translator::choice()也调用Translator::get()但是中心加载了本地语言的标识。Translator::getSelector()将替换值作为数字,MessageSelector::choose()解析字符串、替换字符换中对应数字条件字符,并根据不同语言处理数字,返回最终结果。

全局函数在vendor\hyperf\translation\src\Function.php中,在其composer.json中自动加载。

2、源码

php 复制代码
#Hyperf\Translation\Translator
class Translator implements TranslatorInterface
{
    public function __construct(TranslatorLoaderInterface $loader, string $locale)
    {
        $this->loader = $loader;
        $this->locale = $locale;
    }
    public function trans(string $key, array $replace = [], ?string $locale = null)
    {
        return $this->get($key, $replace, $locale);
    }
     public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string
    {
        return $this->choice($key, $number, $replace, $locale);
    }
    public function get(string $key, array $replace = [], ?string $locale = null, bool $fallback = true)
    {
        [$namespace, $group, $item] = $this->parseKey($key);

        // Here we will get the locale that should be used for the language line. If one
        // was not passed, we will use the default locales which was given to us when
        // the translator was instantiated. Then, we can load the lines and return.
        $locales = $fallback ? $this->localeArray($locale)
        : [$locale ?: $this->locale()];
        foreach ($locales as $locale) {
            if (!is_null($line = $this->getLine(
                $namespace,
                $group,
                $locale,
                $item,
                $replace
            ))) {
                break;
            }
        }

        // If the line doesn't exist, we will return back the key which was requested as
        // that will be quick to spot in the UI if language keys are wrong or missing
        // from the application's language files. Otherwise we can return the line.
        return $line ?? $key;
    }
     public function choice(string $key, $number, array $replace = [], ?string $locale = null): string
    {
        $line = $this->get(
            $key,
            $replace,
            $locale = $this->localeForChoice($locale)
        );

        // If the given "number" is actually an array or countable we will simply count the
        // number of elements in an instance. This allows developers to pass an array of
        // items without having to count it on their end first which gives bad syntax.
        if (is_array($number) || $number instanceof Countable) {
            $number = count($number);
        }

        $replace['count'] = $number;

        return $this->makeReplacements(
            $this->getSelector()->choose($line, $number, $locale),
            $replace
        );
    }
    protected function localeForChoice(?string $locale): string
    {
        return $locale ?: $this->locale() ?: $this->fallback;
    }
     protected function getLine(string $namespace, string $group, string $locale, $item, array $replace)
    {
        $this->load($namespace, $group, $locale);
        if (!is_null($item)) {
            $line = Arr::get($this->loaded[$namespace][$group][$locale], $item);
        } else {
            // do for hyperf Arr::get
            $line = $this->loaded[$namespace][$group][$locale];
        }
        if (is_string($line)) {
            return $this->makeReplacements($line, $replace);
        }

        if (is_array($line) && count($line) > 0) {
            foreach ($line as $key => $value) {
                $line[$key] = $this->makeReplacements($value, $replace);
            }

            return $line;
        }

        return null;
    }
    
}

#Hyperf\Translation\FileLoaderFactory
class FileLoaderFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $config = $container->get(ConfigInterface::class);
        $files = $container->get(Filesystem::class);
        $path = $config->get('translation.path', BASE_PATH . '/storage/languages');

        return make(FileLoader::class, compact('files', 'path'));
    }
}

#Hyperf\Translation\FileLoader 
class FileLoader implements TranslatorLoaderInterface
{
    public function __construct(Filesystem $files, string $path)
    {
        $this->path = $path;
        $this->files = $files;
    }
    public function load(string $locale, string $group, ?string $namespace = null): array
    {
        if ($group === '*' && $namespace === '*') {
            return $this->loadJsonPaths($locale);
        }

        if (is_null($namespace) || $namespace === '*') {
            return $this->loadPath($this->path, $locale, $group);
        }

        return $this->loadNamespaced($locale, $group, $namespace);
    }
     public function addNamespace(string $namespace, string $hint)
    {
        $this->hints[$namespace] = $hint;
    }
    public function addJsonPath(string $path)
    {
        $this->jsonPaths[] = $path;
    }
    protected function loadNamespaced(string $locale, string $group, string $namespace): array
    {
        if (isset($this->hints[$namespace])) {
            $lines = $this->loadPath($this->hints[$namespace], $locale, $group);

            return $this->loadNamespaceOverrides($lines, $locale, $group, $namespace);
        }

        return [];
    }
    protected function loadPath(string $path, string $locale, string $group): array
    {
        if ($this->files->exists($full = "{$path}/{$locale}/{$group}.php")) {
            return $this->files->getRequire($full);
        }

        return [];
    }
    protected function loadJsonPaths(string $locale): iterable
    {
        return collect(array_merge($this->jsonPaths, [$this->path]))
            ->reduce(function ($output, $path) use ($locale) {
                if ($this->files->exists($full = "{$path}/{$locale}.json")) {
                    $decoded = json_decode($this->files->get($full), true);

                    if (is_null($decoded) || json_last_error() !== JSON_ERROR_NONE) {
                        throw new RuntimeException("Translation file [{$full}] contains an invalid JSON structure.");
                    }

                    $output = array_merge($output, $decoded);
                }

                return $output;
            }, []);
    }
}

#Hyperf\Translation\MessageSelector
class MessageSelector
{
public function choose(string $line, $number, string $locale)
    {
        $segments = explode('|', $line);

        if (($value = $this->extract($segments, $number)) !== null) {
            return trim($value);
        }

        $segments = $this->stripConditions($segments);

        $pluralIndex = $this->getPluralIndex($locale, $number);

        if (count($segments) === 1 || ! isset($segments[$pluralIndex])) {
            return $segments[0];
        }

        return $segments[$pluralIndex];
    }
 private function extract(array $segments, $number)
    {
        foreach ($segments as $part) {
            if (! is_null($line = $this->extractFromString($part, $number))) {
                return $line;
            }
        }
    }
private function stripConditions(array $segments): array
    {
        return collect($segments)->map(function ($part) {
            return preg_replace('/^[\{\[]([^\[\]\{\}]*)[\}\]]/', '', $part);
        })->all();
    }
private function stripConditions(array $segments): array
    {
        return collect($segments)->map(function ($part) {
            return preg_replace('/^[\{\[]([^\[\]\{\}]*)[\}\]]/', '', $part);
        })->all();
    }
public function getPluralIndex(string $locale, int $number): int
    {
    switch ($locale) {
            ......
            case 'en':
            ......
            return ($number == 1) ? 0 : 1;
    }
    ......
    }
}
php 复制代码
#vendor\hyperf\translation\src\Function.php
if (! function_exists('__')) {
    function __(string $key, array $replace = [], ?string $locale = null)
    {
        $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class);
        return $translator->trans($key, $replace, $locale);
    }
}

if (! function_exists('trans')) {
    function trans(string $key, array $replace = [], ?string $locale = null)
    {
        return __($key, $replace, $locale);
    }
}

if (! function_exists('trans_choice')) {
    function trans_choice(string $key, $number, array $replace = [], ?string $locale = null): string
    {
        $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class);
        return $translator->transChoice($key, $number, $replace, $locale);
    }
}
相关推荐
BingoGo1 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack1 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户3074596982072 天前
PHP 扩展——从入门到理解
php
鹏仔先生3 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下3 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip3 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒3 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog2503 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis3 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel
Cheng小攸3 天前
渗透行为分析与检测
开发语言·php