一 安装
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);
}
}