Gettext 本地化
PHP 的 Gettext 扩展实现了 GNU gettext 国际化框架,是 PHP 应用实现多语言(i18n)最常用的方案。它使用 .po(Portable Object)和 .mo(Machine Object)文件管理翻译,被 WordPress、Drupal 等知名项目采用。
前置知识
阅读本节前,建议先了解:intl 扩展、iconv 字符编码转换
基础概念
Gettext 工作流程
- 编写源代码中使用
_()或gettext()标记需要翻译的字符串 - 使用
xgettext提取所有可翻译字符串生成.pot模板 - 翻译人员复制
.pot为.po文件并翻译 - 使用
msgfmt将.po编译为二进制.mo文件 - PHP 运行时加载
.mo文件进行翻译
安装
bash
# 编译安装
./configure --with-gettext
# Ubuntu/Debian
sudo apt-get install php-gettext
# 安装 gettext 工具
sudo apt-get install gettext基本用法
绑定文本域
php
<?php
declare(strict_types=1);
// 设置语言环境
putenv('LC_ALL=zh_CN');
setlocale(LC_ALL, 'zh_CN');
// 绑定文本域(.mo 文件名)
bindtextdomain('myapp', '/path/to/locales');
bind_textdomain_codeset('myapp', 'UTF-8');
// 设置默认文本域
textdomain('myapp');
// 使用 gettext 翻译
echo gettext('Hello World') . PHP_EOL;
// 使用别名 _()
echo _('Welcome to our website') . PHP_EOL;多文本域
php
<?php
declare(strict_types=1);
// 主文本域
bindtextdomain('main', '/locales/main');
textdomain('main');
echo _('Hello'); // 使用 main 域
// 错误消息文本域
bindtextdomain('errors', '/locales/errors');
echo dgettext('errors', 'File not found'); // 使用 errors 域
// 特定文本域 + 类别
echo dngettext('errors', 'One error', '{count} errors', $count);带变量的翻译
php
<?php
declare(strict_types=1);
// 简单替换
$greeting = sprintf(_('Welcome, %s!'), $userName);
// 带上下文的翻译
echo _('context|Button text'); // pgettext
echo pgettext('button', 'Submit');
// 复数形式
$n = 5;
echo sprintf(ngettext('%d item', '%d items', $n), $n);目录结构
/locales/
zh_CN/
LC_MESSAGES/
myapp.po
myapp.mo
en_US/
LC_MESSAGES/
myapp.po
myapp.mo
ja_JP/
LC_MESSAGES/
myapp.po
myapp.mo.po 文件示例
msgid "Hello World"
msgstr "你好世界"
msgid "Welcome to our website"
msgstr "欢迎访问我们的网站"
msgid "%d item"
msgid_plural "%d items"
msgstr[0] "%d 个项目"
msgstr[1] "%d 个项目"
msgctxt "button"
msgid "Submit"
msgstr "提交"实战示例
多语言管理器
php
<?php
declare(strict_types=1);
class I18nManager
{
private string $domain;
private string $localeDir;
private string $currentLocale;
public function __construct(string $domain = 'app', string $localeDir = '/locales')
{
$this->domain = $domain;
$this->localeDir = $localeDir;
}
/**
* 切换语言
*/
public function setLocale(string $locale): void
{
$this->currentLocale = $locale;
putenv("LC_ALL={$locale}");
setlocale(LC_ALL, $locale);
bindtextdomain($this->domain, $this->localeDir);
bind_textdomain_codeset($this->domain, 'UTF-8');
textdomain($this->domain);
}
/**
* 翻译
*/
public function translate(string $message, array $params = []): string
{
$translated = gettext($message);
if (!empty($params)) {
$translated = sprintf($translated, ...array_values($params));
}
return $translated;
}
/**
* 获取支持的语言列表
*/
public function getAvailableLocales(): array
{
$locales = [];
$dirs = glob($this->localeDir . '/*', GLOB_ONLYDIR);
foreach ($dirs as $dir) {
$locale = basename($dir);
$moFile = $dir . '/LC_MESSAGES/' . $this->domain . '.mo';
if (file_exists($moFile)) {
$locales[] = $locale;
}
}
return $locales;
}
}
// 使用示例
$i18n = new I18nManager('myapp', '/path/to/locales');
$i18n->setLocale('zh_CN');
echo $i18n->translate('Hello World');
echo $i18n->translate('Welcome, %s!', ['张三']);
$i18n->setLocale('en_US');
echo $i18n->translate('Hello World');注意事项
.mo文件需要通过msgfmt工具从.po编译setlocale()的值因操作系统而异- Web 环境下可能需要重启才能生效
下一节
继续学习:Math 数学函数