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);
    }
}
相关推荐
vvw&1 小时前
如何在 Ubuntu 22.04 上安装 phpMyAdmin
linux·运维·服务器·mysql·ubuntu·php·phpmyadmin
奥顺互联V1 小时前
深入理解 ThinkPHP:框架结构与核心概念详解
大数据·mysql·开源·php
Clockwiseee5 小时前
PHP伪协议总结
android·开发语言·php
m0_7482475513 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
luck_00715 小时前
PhpSpreadsheet 导出excel 找不到setCellValueByColumnAndRow
php
生椰拿铁You15 小时前
解决Apache/2.4.39 (Win64) PHP/7.2.18 Server at localhost Port 80问题
php
索然无味io21 小时前
跨站请求伪造之基本介绍
前端·笔记·学习·web安全·网络安全·php
伟大无须多言21 小时前
企业资源规划系统(ERP)服务器上线项目实施指南
开发语言·php
网络安全(king)21 小时前
网络安全设备
网络·web安全·php
蜗牛hb1 天前
VMware Workstation虚拟机网络模式
开发语言·学习·php