本文目录导读:

在PHP项目中实现多语言与国际化的方式有很多,我会从最基础的方案到企业级方案,以及高性能实践的常见坑,给你一个完整的思维导图和代码示例。
国际化(i18n)与本地化(L10n)的区别
- i18n(国际化):代码层面的准备,把写死的文字抽离出来,不依赖特定语言。
- L10n(本地化):为特定地区/语言提供翻译内容、日期格式、货币符号等。
方案选型(从简单到复杂)
方案 1:原生数组 / 常量(最简单的 PHP 方式)
适合小型项目、不需要额外依赖的场景。
<?php
// lang.php
return [
'en' => [
'welcome' => 'Welcome to our website!',
'hello' => 'Hello, :name!',
'date_format' => 'm/d/Y',
'currency' => '$',
],
'zh_CN' => [
'welcome' => '欢迎访问我们的网站!',
'hello' => '你好,:name!',
'date_format' => 'Y-m-d',
'currency' => '¥',
],
];
核心调用逻辑:
<?php
class Translator {
private array $messages = [];
private string $locale = 'en';
public function __construct(string $locale = 'en') {
$this->setLocale($locale);
}
public function setLocale(string $locale): void {
$langFile = __DIR__ . "/lang/{$locale}.php";
$this->messages = file_exists($langFile) ? require $langFile : [];
$this->locale = $locale;
}
public function trans(string $key, array $params = []): string {
$message = $this->messages[$key] ?? $key;
// 处理占位符 :name
foreach ($params as $k => $v) {
$message = str_replace(":{$k}", $v, $message);
}
return $message;
}
}
// 使用
$t = new Translator('zh_CN');
echo $t->trans('welcome'); // 欢迎访问我们的网站!
echo $t->trans('hello', ['name' => '张三']); // 你好,张三!
优点:零依赖,性能极佳。 缺点:多语言文件夹需要人工管理,无法自动检测浏览器语言(需自行封装)。
方案 2:Gettext(传统国际标准)
PHP 原生支持 gettext 扩展,使用 .po / .mo 文件编译词条,适合大型系统或需要专业翻译团队。
安装与使用步骤:
-
确认扩展启用(php.ini):
extension=gettext
-
创建语言目录:
/var/www/locale/zh_CN/LC_MESSAGES/messages.po /var/www/locale/en_US/LC_MESSAGES/messages.po
-
.po文件格式:msgid "Welcome" msgstr "欢迎光临" msgid "Hello %s" msgstr "你好 %s"
-
编译为
.mo:msgfmt messages.po -o messages.mo
-
PHP 调用:
<?php $locale = 'zh_CN'; putenv("LANG={$locale}"); setlocale(LC_ALL, $locale); // 指定语言目录(必须是绝对路径) bindtextdomain('messages', '/var/www/locale'); textdomain('messages'); echo gettext('Welcome'); // 欢迎光临 echo _('Hello %s', '张三'); // 你好 张三
注意:Windows 下 PHP 通常没有 gettext 扩展,生产环境多为 Linux 可用。
方案 3:组件库思想(Symfony Translator 风格)
如果你在用框架(Laravel、Symfony、ThinkPHP)等,它们都有内置翻译器,这里以 Laravel 为模仿对象展示逻辑,因为这种设计可以脱离框架使用:
<?php
class Translator {
private array $fallback = [];
private array $current = [];
private string $locale;
private string $fallbackLocale;
public function __construct(string $locale, string $fallbackLocale = 'en') {
$this->locale = $locale;
$this->fallbackLocale = $fallbackLocale;
$this->loadMessages($locale);
$this->loadMessages($fallbackLocale);
}
private function loadMessages(string $locale): void {
$file = __DIR__ . "/lang/{$locale}.php";
if (!file_exists($file)) return;
$data = require $file;
if ($locale === $this->locale) {
$this->current = $data;
} else {
$this->fallback = $data;
}
}
public function trans(string $key, array $replace = []): string {
$segments = explode('.', $key);
// 递归查找
$message = $this->findRecursive($segments, $this->current);
if (!$message) {
$message = $this->findRecursive($segments, $this->fallback) ?? $key;
}
// 替换参数
foreach ($replace as $k => $v) {
$message = str_replace(':' . $k, $v, $message);
$message = str_replace('%' . $k . '%', $v, $message);
}
return $message;
}
private function findRecursive(array $segments, array $data): ?string {
$temp = $data;
foreach ($segments as $seg) {
if (!is_array($temp) || !array_key_exists($seg, $temp)) {
return null;
}
$temp = $temp[$seg];
}
return is_string($temp) ? $temp : null;
}
}
// 语言文件结构 lang/zh_CN.php
return [
'auth' => [
'login_success' => '登录成功',
'welcome' => '欢迎,:name',
],
'errors' => [
'not_found' => '页面不存在',
],
];
支持多级数组键名,并且有备选语言回退机制。
方案 4:高性能方案(编译为 PHP 常量)
当翻译文件巨大时,require 数组性能最优,还可以进一步优化为预编译为 PHP 类常量:
<?php
// lang/compiled/zh_CN.php
class zh_CN {
const WELCOME = '欢迎';
const LOGIN = '登录';
}
// 调用
echo zh_CN::WELCOME; // 更快的常量查找
借助工具(如 po2php)可以自动将 .po 文件编译成 PHP 类常量。
进阶技巧与最佳实践
复数规则处理
不同语言复数规则不同(如英文 0/1/2,中文/日文无区分):
<?php
public function transChoice(string $singular, string $plural, int $number, array $params = []): string {
$key = $number == 1 ? $singular : $plural;
$params['count'] = $number;
return $this->trans($key, $params);
}
// 用法
echo transChoice('1 item', ':count items', 5);
数字与货币格式化
使用 Intl 扩展(推荐,比原生 number_format 更专业):
<?php
$fmt = new \NumberFormatter('zh_CN', \NumberFormatter::CURRENCY);
echo $fmt->formatCurrency(1234.56, 'CNY'); // 输出 "¥1,234.56"
$dateFmt = new \IntlDateFormatter('zh_CN', \IntlDateFormatter::LONG, \IntlDateFormatter::NONE);
echo $dateFmt->format(new DateTime('2024-01-01')); // "2024年1月1日"
自动检测浏览器语言
<?php
function detectLocale(array $supportedLocales): string {
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
preg_match_all('/([a-z]{2}(?:-[A-Z]{2})?)/', $acceptLang, $matches);
foreach ($matches[1] as $lang) {
if (in_array($lang, $supportedLocales)) {
return $lang;
}
}
return $supportedLocales[0] ?? 'en';
}
使用 Composer 库(省事)
- symfony/translation —— 官方重量级,功能全面。
- illuminate/translation —— Laravel 的翻译组件,可独立使用。
- gettext/gettext —— 集成 Gettext 的现代封装。
总结推荐
| 项目规模 | 推荐方案 | 原因 |
|---|---|---|
| 小型工具/插件 | 原生数组 | 简单快速,零依赖 |
| 中型应用 | 自制翻译器(数组+回退) | 可控性高,支持占位符 |
| 大型系统/多语言团队 | Gettext 或 Symfony Translation | 标准化,翻译管理工具完善 |
| 高并发/性能敏感 | 编译成 PHP 数组/常量 | 无 IO 文件解析开销 |
最后建议:语言文本永远不要拼接写死在代码里,一定要走变量替换,并且保证时区/字符编码统一(UTF-8),如果需要长期维护,建议把翻译文件接入第三方翻译平台(如 Lokalise、Crowdin),通过 CI/CD 自动拉取语言包。