Skip to content

Gettext 本地化

PHP 的 Gettext 扩展实现了 GNU gettext 国际化框架,是 PHP 应用实现多语言(i18n)最常用的方案。它使用 .po(Portable Object)和 .mo(Machine Object)文件管理翻译,被 WordPress、Drupal 等知名项目采用。

前置知识

阅读本节前,建议先了解:intl 扩展iconv 字符编码转换

基础概念

Gettext 工作流程

  1. 编写源代码中使用 _()gettext() 标记需要翻译的字符串
  2. 使用 xgettext 提取所有可翻译字符串生成 .pot 模板
  3. 翻译人员复制 .pot.po 文件并翻译
  4. 使用 msgfmt.po 编译为二进制 .mo 文件
  5. 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 数学函数

参考链接