- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
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', ];配置项说明:
| 配置项 | 默认值 | 说明 |
|---|---|---|
locale | zh_CN | 默认语言,应用启动后翻译器使用的初始语言 |
fallback_locale | en | 回退语言,当指定语言找不到翻译键时按此语言兜底 |
path | BASE_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()方法,处理流程分为两步:
- 区间条件优先匹配:先调用
extract()/extractFromString(),用正则preg_match('/^[\{\[](https://link.gitcode.com/i/a2eb137607123d8aa67a39b8c892f326)[\}\]](.*)/s', ...)解析{0}、[1,19]这类带条件的片段,若命中区间则直接返回对应文本; - 语言复数索引兜底:若无区间条件,则剥掉条件前缀后,调用
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\TranslatorInterface | TranslatorFactory创建的Translator | 翻译器主入口,读取translation.locale与translation.fallback_locale |
Hyperf\Contract\TranslatorLoaderInterface | FileLoaderFactory创建的FileLoader | 语言加载器,读取translation.path |
config/autoload/translation.php | publish/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.
相关推荐
Hyperf 国际化(多语言)组件完整实战指南:语言文件、占位符与复数规则
Hyperf 国际化(多语言)组件完整实战指南:语言文件、占位符与复数规则 导读 Hyperf 提供了开箱即用的国际化(i18n)支持,让您的应用可以轻松面向多
后端微服务Hyperf I18n 多语言国际化:translation 组件完整实战指南
Hyperf I18n 多语言国际化:translation 组件完整实战指南 Hyperf 的国际化(I18n)能力由独立的 hyperf/translati
后端Web框架微服务RPC框架异步编程Docker快速部署Wan2.1-Fun-1.3B-InP:从镜像拉取到视频输出全程实录
Docker快速部署Wan2.1 Fun 1.3B InP:从镜像拉取到视频输出全程实录 想要快速体验最新的AI视频生成技术吗?🤔 今天我将为大家详细介绍如何
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考