☰
Hyperf Translation 国际化组件实战指南:语言文件、占位符与复数规则的完整实现
2026/10/8 14:23:37 网站建设 项目流程
  • 后端
  • 微服务

【免费下载链接】hyperf

🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.

项目地址:https://gitcode.com/gh_mirrors/hy/hyperf
点击查看免费下载

Hyperf 的翻译(Translation)组件为应用提供了一套友好且完备的国际化(I18n)能力,让你可以轻松地让项目支持多种语言。本文以官方文档 docs/en/translation.md 为骨架,结合仓库中hyperf/translation组件的真实源码,完整讲解组件的安装、语言文件组织、三种 locale 配置方式、字符串翻译、占位符替换与复数处理,并深入剖析Translator、MessageSelector、FileLoader等核心类的底层实现,让你既能直接上手使用,也能理解其内部原理。

组件概览:一个可独立复用的翻译库

Hyperf 的国际化组件hyperf/translation是从illuminate/translation演化而来的独立翻译组件(见 src/translation/composer.json 中的描述)。它是一个独立组件,不依赖 Hyperf 框架主体,可以单独引入到其他项目或框架中使用;其依赖仅限于hyperf/collection、hyperf/context、hyperf/contract、hyperf/macroable、hyperf/stringable、hyperf/support等轻量级工具包与psr/container,并不需要整个框架运行时。

在 Hyperf 应用中,该组件通过 ConfigProvider 完成自动装配:

  • TranslatorLoaderInterface绑定到FileLoaderFactory(负责从语言文件加载翻译内容);
  • TranslatorInterface绑定到TranslatorFactory(负责创建翻译器实例并注入默认 locale 与 fallback locale);
  • 同时把默认配置文件发布到config/autoload/translation.php。

安装

使用 Composer 安装即可:

composer require hyperf/translation

安装完成后,组件会通过ConfigProvider自动注册依赖与配置发布项。如需生成配置文件,执行以下命令(Hyperf 框架标准发布方式):

php bin/hyperf.php vendor:publish hyperf/translation

语言文件的组织方式

Hyperf 的语言文件默认存放在storage/languages目录下(你也可以在配置中修改该目录)。每种语言对应一个子目录,目录名即语言标识,例如en表示英语、zh_CN表示简体中文。你可以根据实际需求自由创建新的语言目录和语言文件,目录结构示例如下:

/storage /languages /en messages.php /zh_CN messages.php

所有语言文件都返回一个数组,数组的键是字符串,值是对应语言的翻译文本:

<?php // storage/languages/en/messages.php return [ 'welcome' => 'Welcome to our application', ];

从源码角度看,文件加载逻辑位于 FileLoader.php 的loadPath()方法:它会拼接{path}/{locale}/{group}.php路径,若文件存在则通过getRequire()返回数组内容;语言文件名(如messages)即翻译键中的"组"(group)。

组件内部如何定位语言目录

FileLoaderFactory.php 从配置中心读取translation.path,默认值为BASE_PATH . '/storage/languages':

$path = $config->get('translation.path', BASE_PATH . '/storage/languages'); return make(FileLoader::class, compact('files', 'path'));

配置 locale

国际化的相关配置集中在config/autoload/translation.php文件中(该文件由组件发布,原始模板见 publish/translation.php),你可以按需修改:

<?php // config/autoload/translation.php return [ // 默认语言 'locale' => 'zh_CN', // 回退语言,当默认语言中缺少对应翻译文本时,会使用回退语言的对应文本 'fallback_locale' => 'en', // 语言文件存放目录 'path' => BASE_PATH . '/storage/languages', ];

配置项说明:

配置项默认值说明
localezh_CN默认语言,应用启动后翻译器使用的初始语言
fallback_localeen回退语言,当指定语言找不到翻译键时按此语言兜底
pathBASE_PATH . '/storage/languages'语言文件所在目录的绝对路径

工厂如何消费这些配置

TranslatorFactory.php 的创建逻辑印证了上述配置的作用:

$locale = $config->get('translation.locale', 'zh_CN'); $fallbackLocale = $config->get('translation.fallback_locale', 'en'); $loader = $container->get(TranslatorLoaderInterface::class); $translator = make(Translator::class, compact('loader', 'locale')); $translator->setFallback((string) $fallbackLocale);

配置临时 locale(按请求 / 协程生效)

除了全局默认 locale,你还可以在运行期为当前请求动态设置临时 locale。由于 Hyperf 运行在 Swoole 协程环境中,Translator::setLocale()会把语言存入Context(协程上下文),因此临时 locale 只在当前请求或当前协程的生命周期内有效,不会污染其他协程:

<?php use Hyperf\Di\Annotation\Inject; use Hyperf\Contract\TranslatorInterface; class FooController { #[Inject] private TranslatorInterface $translator; public function index() { // 仅对当前请求或协程生命周期内有效 $this->translator->setLocale('zh_CN'); } }

其底层实现在 Translator.php 中:

public function getLocaleContextKey(): string { return sprintf('%s::%s', TranslatorInterface::class, 'locale'); } public function getLocale(): string { $locale = Context::get($this->getLocaleContextKey()); return (string) ($locale ?? $this->locale); } public function setLocale(string $locale) { Context::set($this->getLocaleContextKey(), $locale); }

注意getLocale()优先返回协程上下文中的 locale,只有未设置时才回落到构造时传入的默认 locale。对应的契约定义见 TranslatorInterface.php。

翻译字符串

方式一:注入 TranslatorInterface

直接注入Hyperf\Contract\TranslatorInterface,调用其trans方法即可完成字符串翻译:

<?php use Hyperf\Di\Annotation\Inject; use Hyperf\Contract\TranslatorInterface; class FooController { #[Inject] private TranslatorInterface $translator; public function index() { return $this->translator->trans('messages.welcome', [], 'zh_CN'); } }

trans方法的完整签名(见 TranslatorInterface.php):

public function trans(string $key, array $replace = [], ?string $locale = null): array|string;

三个参数分别表示:翻译键、占位符替换数组(可选)、临时指定语言(可选)。

方式二:使用全局函数__()或trans()

组件通过 Functions.php 注册了三个全局函数。函数第一个参数采用key(直接用翻译文本作为键)或file.key(文件加键)的形式:

echo __('messages.welcome'); echo trans('messages.welcome');

其中__()与trans()完全等价,都从容器中取出TranslatorInterface并调用其trans方法:

function __(string $key, array $replace = [], ?string $locale = null) { $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class); return $translator->trans($key, $replace, $locale); } function trans(string $key, array $replace = [], ?string $locale = null) { return __($key, $replace, $locale); }

键的解析规则

Translator::get()首先调用parseKey()把翻译键拆分为[namespace, group, item]三元组:

  • 不含::的普通键(如messages.welcome)按.分割:第一段是组名(group),其余段拼成条目名(item);
  • 含::的键视为命名空间键(namespace key),::前的部分是命名空间;
  • 解析结果会被缓存到parsed数组,避免同一键的重复解析开销。

若某个 locale 下找不到翻译,get()会按localeArray()返回的语言序列(当前语言 + fallback 语言)依次查找,最终仍找不到则直接返回原键名,方便在 UI 中快速定位缺失的语言键——这是"宁可返回 key 也不抛异常"的设计,具体逻辑在 Translator.php 的get()方法中。

在翻译字符串中定义占位符

你可以在语言字符串中定义占位符,所有占位符都以:作为前缀。例如,以用户名作为占位符:

<?php // storage/languages/en/messages.php return [ 'welcome' => 'Welcome :name', ];

使用函数的第二个参数替换占位符:

echo __('messages.welcome', ['name' => 'Hyperf']); // 输出:Welcome Hyperf

占位符的大小写规则

如果占位符全部大写,或首字母大写,则替换后的字符串也会呈现对应的大写形式:

'welcome' => 'Welcome, :NAME', // Welcome, HYPERF 'goodbye' => 'Goodbye, :Name', // Goodbye, Hyperf

这一行为由 Translator.php 的makeReplacements()实现,它对同一个键同时替换三种形态:

$line = str_replace( [':' . $key, ':' . Str::upper($key), ':' . Str::ucfirst($key)], [$value, Str::upper($value), Str::ucfirst($value)], $line );

替换数组的排序细节

makeReplacements()会先调用sortReplacements()对替换数组按键名长度降序排序,再执行替换:

protected function sortReplacements(array $replace): array { return (new Collection($replace))->sortBy(fn ($value, $key) => mb_strlen((string) $key) * -1)->all(); }

这样可以避免短键名先被替换而"截胡"长键名的问题,例如同时存在:name与:name_en时,name_en会优先被替换,保证结果的正确性。

处理复数(Pluralization)

不同语言的复数规则各不相同。中文通常不需要关注单复数,但在翻译其他语言(如英语、俄语、阿拉伯语)时,必须处理名词的复数形式。组件使用竖线字符"|"来区分字符串的单复数形式:

'apples' => 'There is one apple|There are many apples',

你也可以指定数字区间来构造更复杂的复数规则:

'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',

区间语法说明:

语法含义
{0}精确匹配数字 0
[1,19]闭区间,匹配 1 到 19(含两端)
[20,*]从 20 到无穷大
[*,5]从负无穷到 5(组件同样支持*作区间下界)

使用 trans_choice 取复数文本

定义好复数规则后,可通过全局函数trans_choice根据给定的"数量"获取对应字符串。在下面的示例中,由于数字大于1,将返回翻译字符串的复数形式:

echo trans_choice('messages.apples', 10); // 输出:There are many apples

除了全局函数trans_choice(),也可以使用Hyperf\Contract\TranslatorInterface的transChoice方法:

$this->translator->transChoice('messages.apples', 10);

底层实现:MessageSelector

复数选择的核心逻辑位于 MessageSelector.php 的choose()方法,处理流程分为两步:

  1. 区间条件优先匹配:先调用extract()/extractFromString(),用正则preg_match('/^[\{\[](https://link.gitcode.com/i/a2eb137607123d8aa67a39b8c892f326)[\}\]](.*)/s', ...)解析{0}、[1,19]这类带条件的片段,若命中区间则直接返回对应文本;
  2. 语言复数索引兜底:若无区间条件,则剥掉条件前缀后,调用getPluralIndex($locale, $number)根据语言获取复数索引。

getPluralIndex()内置了覆盖数十种语言(含zh_CN、en、fr、ru、ar等及各自地区变体)的复数规则表,例如中文恒返回索引0,英语按number == 1返回0否则1,俄语则按%10与%100的组合规则返回三态索引。Translator::choice()在调用选择器之前还会注入一个特殊占位符:

$replace['count'] = $number;

这意味着你可以在复数文本中使用:count占位符输出实际数量,例如'apples' => '{0} There are none|[1,19] There are :count apples|[20,*] There are many'。此外,choice()的$number参数也支持传入数组或可计数的对象,组件会自动取其元素数量作为判断依据。

进阶能力:JSON 翻译与命名空间覆盖

除 PHP 语言文件外,从 FileLoader.php 的源码可以看出组件还支持两类进阶用法,文档中虽未展开,但已内置于实现中:

JSON 翻译文件

FileLoader::loadJsonPaths()会尝试加载{path}/{locale}.json文件(例如storage/languages/zh_CN.json),解析失败时会抛出RuntimeException。配合Translator::getFromJson()方法,键可直接使用翻译文本本身,例如__('Welcome to our application')会先在 JSON 文件中查找,找不到再回落到普通语言文件,最终返回makeReplacements()处理后的文本。FileLoader还支持通过addJsonPath()注册额外的 JSON 翻译目录。

命名空间与 vendor 覆盖

当翻译键包含::(如package::messages.welcome)时,FileLoader::loadNamespaced()会先从命名空间提示路径(addNamespace()注册的 hint)加载翻译,再尝试从{path}/vendor/{namespace}/{locale}/{group}.php读取覆盖文件,并用array_replace_recursive()递归合并,实现"组件自带翻译 + 应用本地覆盖"的分层机制。

备选加载器:ArrayLoader

除了从文件加载,组件还提供了内存加载器 ArrayLoader.php,通过addMessages(string $locale, string $group, array $messages, ?string $namespace = null)把翻译直接注入内存,适合单元测试或动态注册翻译的场景。它与FileLoader共同实现TranslatorLoaderInterface,可以在容器中替换绑定。

组件装配与依赖注入关系一览

综合 ConfigProvider.php 的注册内容,整个组件的依赖关系如下:

接口 / 配置实现 / 来源说明
Hyperf\Contract\TranslatorInterfaceTranslatorFactory创建的Translator翻译器主入口,读取translation.locale与translation.fallback_locale
Hyperf\Contract\TranslatorLoaderInterfaceFileLoaderFactory创建的FileLoader语言加载器,读取translation.path
config/autoload/translation.phppublish/translation.php发布组件配置文件
全局函数__()/trans()/trans_choice()Functions.php通过ApplicationContext::getContainer()获取翻译器

Translator还使用了Macroabletrait(见 Translator.php 第 26 行),允许你在运行时为其扩展自定义方法。

测试验证

组件在 tests 目录下提供了三组测试,可用于验证本文描述的行为:

  • TranslatorTest.php:验证工厂默认 locale(zh_CN)、has()方法、占位符替换、复数选择与并发场景下的 locale 隔离等;
  • MessageSelectorTest.php:验证区间条件、复数索引及各类语言规则;
  • FileLoaderTest.php:验证文件加载、命名空间与 JSON 路径处理。

你可以在仓库根目录运行对应测试来印证实现行为:

composer test src/translation 2>/dev/null || vendor/bin/phpunit --configuration phpunit.xml.dist src/translation/tests

总结

Hyperf 的翻译组件以"语言文件 + 翻译器 + 选择器 + 加载器"四层结构,提供了一套从基础翻译到复杂复数的完整国际化方案。日常使用中只需记住四件事:语言文件放storage/languages并按locale/组.php组织;配置文件设好locale、fallback_locale与path;翻译时用__()、trans()或注入的TranslatorInterface;复数场景用trans_choice()配合|与区间语法。需要更精细控制时,还可借助协程上下文实现按请求切换语言、使用 JSON 翻译文件,或通过命名空间机制实现组件翻译的本地覆盖。

  • 后端
  • 微服务

【免费下载链接】hyperf

🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.

项目地址:https://gitcode.com/gh_mirrors/hy/hyperf
点击查看免费下载

相关推荐

上一篇:5分钟给Windows 11 24H2 LTSC装回Microsoft Store:LTSC-Add-MicrosoftStore快速上手教程
下一篇:工具调用、多轮强化学习与Deep Research:Hands-On Modern RL Agentic RL完整实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询