14.国际化与本地化
在现代 Web 应用开发中,支持多语言和本地化已经成为一项基本需求。特别是当应用面向全球用户时,良好的国际化设计能够显著提升用户体验。Phalcon 框架虽然没有重复造轮子实现完整的国际化组件,但它提供了与 PHP 国际扩展的无缝集成能力,让我们能够轻松构建多语言应用。
多语言支持
国际化(通常简称 i18n,因为"国际化"一词英文有 18 个字母)是指设计软件时,使它能够适应各种语言和地区的需求,而不需要对软件内部结构进行大规模修改。在 PHP 生态中,这主要通过 intl 扩展来实现,Phalcon 框架充分利用了这一扩展提供的功能。
检测用户语言偏好
实现国际化的第一步是确定用户的语言偏好。最常见的方式是通过 HTTP 请求头中的 Accept-Language 字段来获取:
<?php
use Locale;
// 从HTTP头获取用户首选语言
$locale = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
// 输出可能是 'zh-CN'、'en-US' 或 'fr_FR' 等
echo $locale;
这段代码使用了 PHP intl 扩展中的 Locale 类来解析 HTTP 请求头,返回一个语言标识符。语言标识符通常由语言代码和地区代码组成,如 zh-CN 表示中国大陆的中文,en-US 表示美国英语。
在 Phalcon 应用中,我们还可以使用请求对象的 getBestLanguage() 方法来获取最佳语言:
<?php
// 在控制器中获取最佳语言
$language = $this->request->getBestLanguage();
这个方法会自动分析请求头,并返回与应用支持语言最匹配的语言代码。
Phalcon 翻译组件
Phalcon 提供了 Phalcon\Translate 组件来管理应用中的翻译字符串。这个组件支持多种适配器,包括数组、数据库等,让我们可以灵活地存储和检索翻译内容。
最常用的是数组适配器,它将翻译字符串存储在 PHP 数组中。下面是一个基本实现:
<?php
use Phalcon\Translate\Adapter\NativeArray;
use Phalcon\Translate\InterpolatorFactory;
use Phalcon\Translate\TranslateFactory;
// 创建翻译服务
$interpolator = new InterpolatorFactory();
$factory = new TranslateFactory($interpolator);
$translator = $factory->newInstance(
'array',
[
'content' => [
'welcome' => '欢迎来到我们的网站',
'hello' => '你好,%name%',
'current_time' => '当前时间是 %time%',
],
]
);
// 使用翻译服务
echo $translator->_('welcome'); // 输出: 欢迎来到我们的网站
echo $translator->_('hello', ['name' => '张三']); // 输出: 你好,张三
上面的代码首先创建了一个翻译工厂,然后使用数组适配器初始化了一个翻译服务。数组中的键是翻译字符串的标识符,值是对应的翻译文本。_() 方法用于获取翻译后的文本,第二个参数可以传递一个数组来替换文本中的占位符。
翻译文件管理
在实际应用中,我们不会将所有翻译字符串都硬编码在代码中,而是会将它们组织到单独的文件中。一种常见的做法是为每种语言创建一个翻译文件,然后根据用户的语言偏好加载相应的文件。
组织翻译文件
建议按照以下目录结构组织翻译文件:
app/
└── messages/
├── en.php
├── zh.php
├── fr.php
└── es.php
每个文件返回一个包含翻译字符串的数组。例如,中文翻译文件 zh.php 可能如下:
<?php
// app/messages/zh.php
return [
'welcome' => '欢迎来到我们的网站',
'hello' => '你好,%name%',
'current_time' => '当前时间是 %time%',
'login' => '登录',
'register' => '注册',
'logout' => '退出',
// 更多翻译...
];
对应的英文翻译文件 en.php:
<?php
// app/messages/en.php
return [
'welcome' => 'Welcome to our website',
'hello' => 'Hello, %name%',
'current_time' => 'Current time is %time%',
'login' => 'Login',
'register' => 'Register',
'logout' => 'Logout',
// More translations...
];
创建翻译服务
为了在应用中方便地使用翻译功能,我们可以创建一个翻译服务,并将其注册到依赖注入容器中。这样,在控制器、视图等任何地方都可以轻松访问翻译服务。
<?php
namespace App\Services;
use Phalcon\Di\Injectable;
use Phalcon\Translate\Adapter\NativeArray;
use Phalcon\Translate\InterpolatorFactory;
use Phalcon\Translate\TranslateFactory;
class TranslateService extends Injectable
{
/**
* 获取翻译服务
*
* @param string $language
* @return NativeArray
*/
public function getTranslator(string $language = null): NativeArray
{
// 如果没有指定语言,从请求中获取最佳语言
if (null === $language) {
$language = $this->request->getBestLanguage();
}
// 简化语言代码,例如将 'zh-CN' 简化为 'zh'
$shortLanguage = substr($language, 0, 2);
// 翻译文件路径
$translationFile = APP_PATH . '/messages/' . $shortLanguage . '.php';
// 如果翻译文件不存在,使用默认语言(英语)
if (!file_exists($translationFile)) {
$translationFile = APP_PATH . '/messages/en.php';
}
// 加载翻译内容
$messages = require $translationFile;
// 创建翻译服务
$interpolator = new InterpolatorFactory();
$factory = new TranslateFactory($interpolator);
return $factory->newInstance(
'array',
[
'content' => $messages,
]
);
}
}
然后在应用的依赖注入容器中注册这个服务:
<?php
// 在di.php或bootstrap文件中
$di->setShared(
'translate',
function () {
$translate = new App\Services\TranslateService();
return $translate->getTranslator();
}
);
现在,在控制器中可以这样使用翻译服务:
<?php
use Phalcon\Mvc\Controller;
class IndexController extends Controller
{
public function indexAction()
{
// 获取翻译服务
$t = $this->translate;
// 使用翻译
$this->view->welcomeMessage = $t->_('welcome');
$this->view->helloMessage = $t->_('hello', ['name' => '张三']);
}
}
在 Volt 模板中使用:
<h1>{{ t._('welcome') }}</h1>
<p>{{ t._('hello', ['name' => '张三']) }}</p>
日期时间本地化
处理不同语言和地区的日期和时间格式是本地化的重要部分。PHP 的 intl 扩展提供了 IntlDateFormatter 类来处理日期时间的本地化。
基本用法
<?php
use IntlDateFormatter;
// 创建日期格式化器
$formatter = new IntlDateFormatter(
'zh_CN', // 地区
IntlDateFormatter::FULL, // 日期格式
IntlDateFormatter::FULL, // 时间格式
'Asia/Shanghai', // 时区
IntlDateFormatter::GREGORIAN // 日历类型
);
// 格式化当前时间
$now = new DateTime();
echo $formatter->format($now);
// 输出:2023年10月5日星期四 中国标准时间 15:30:45
上面的代码创建了一个中文(中国)的日期时间格式化器,使用完整的日期和时间格式,时区设置为亚洲/上海。
自定义日期时间格式
除了使用预定义的格式常量,我们还可以使用自定义格式模式:
<?php
use IntlDateFormatter;
$formatter = new IntlDateFormatter(
'zh_CN',
IntlDateFormatter::NONE, // 不显示日期
IntlDateFormatter::NONE, // 不显示时间
'Asia/Shanghai',
IntlDateFormatter::GREGORIAN,
'yyyy年MM月dd日 HH:mm:ss' // 自定义格式
);
$now = new DateTime();
echo $formatter->format($now); // 输出:2023年10月05日 15:30:45
在 Phalcon 视图中使用
为了方便在视图中使用本地化的日期时间格式,我们可以创建一个自定义的 Volt 函数:
<?php
// 在应用引导文件中
$view->registerEngines(
[
'.volt' => function ($view) use ($di) {
$volt = new Phalcon\Mvc\View\Engine\Volt($view, $di);
// 注册本地化日期格式化函数
$volt->getCompiler()->addFunction(
'localized_date',
function ($resolvedArgs, $exprArgs) {
return 'App\\Helpers\\DateHelper::format(' . $resolvedArgs . ')';
}
);
return $volt;
},
]
);
然后创建一个日期辅助类:
<?php
namespace App\Helpers;
use IntlDateFormatter;
use Phalcon\Di\Injectable;
class DateHelper extends Injectable
{
public static function format($timestamp, $pattern = null)
{
// 获取当前语言
$language = $this->di->get('translate')->getLocale();
$formatter = new IntlDateFormatter(
$language,
IntlDateFormatter::NONE,
IntlDateFormatter::NONE,
'Asia/Shanghai',
IntlDateFormatter::GREGORIAN,
$pattern ?? 'yyyy年MM月dd日 HH:mm'
);
return $formatter->format($timestamp);
}
}
现在可以在 Volt 模板中直接使用这个函数:
<p>发布时间:{{ localized_date(article.created_at) }}</p>
<p>注册日期:{{ localized_date(user.registered_at, 'yyyy-MM-dd') }}</p>
货币与数字格式化
不同国家和地区有不同的数字和货币格式。例如,美国使用 1,234.56 的数字格式和 $1,234.56 的货币格式,而德国使用 1.234,56 的数字格式和 1.234,56 € 的货币格式。
数字格式化
使用 NumberFormatter 类可以轻松实现数字的本地化:
<?php
use NumberFormatter;
// 创建数字格式化器(中文/中国)
$formatter = new NumberFormatter('zh_CN', NumberFormatter::DECIMAL);
echo $formatter->format(1234567.89); // 输出:1,234,567.89
// 创建数字格式化器(德语/德国)
$formatter = new NumberFormatter('de_DE', NumberFormatter::DECIMAL);
echo $formatter->format(1234567.89); // 输出:1.234.567,89
// 创建数字格式化器(法语/法国)
$formatter = new NumberFormatter('fr_FR', NumberFormatter::DECIMAL);
echo $formatter->format(1234567.89); // 输出:1 234 567,89
货币格式化
同样,NumberFormatter 也可以用于货币格式化:
<?php
use NumberFormatter;
// 人民币格式化
$formatter = new NumberFormatter('zh_CN', NumberFormatter::CURRENCY);
echo $formatter->formatCurrency(1234.56, 'CNY'); // 输出:¥1,234.56
// 美元格式化
$formatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
echo $formatter->formatCurrency(1234.56, 'USD'); // 输出:$1,234.56
// 欧元格式化
$formatter = new NumberFormatter('de_DE', NumberFormatter::CURRENCY);
echo $formatter->formatCurrency(1234.56, 'EUR'); // 输出:1.234,56 €
百分比格式化
NumberFormatter 还支持百分比格式化:
<?php
use NumberFormatter;
// 百分比格式化
$formatter = new NumberFormatter('zh_CN', NumberFormatter::PERCENT);
echo $formatter->format(0.75); // 输出:75%
// 带两位小数的百分比
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 2);
echo $formatter->format(0.75); // 输出:75.00%
在 Phalcon 中集成数字格式化
为了在应用中方便使用数字格式化功能,我们可以创建一个格式化服务:
<?php
namespace App\Services;
use NumberFormatter;
use Phalcon\Di\Injectable;
class FormatService extends Injectable
{
/**
* 格式化数字
*
* @param float $number
* @param int $style
* @param int $precision
* @return string
*/
public function number($number, $style = NumberFormatter::DECIMAL, $precision = 2)
{
$language = $this->di->get('translate')->getLocale();
$formatter = new NumberFormatter($language, $style);
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $precision);
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $precision);
return $formatter->format($number);
}
/**
* 格式化货币
*
* @param float $amount
* @param string $currencyCode
* @return string
*/
public function currency($amount, $currencyCode = 'CNY')
{
$language = $this->di->get('translate')->getLocale();
$formatter = new NumberFormatter($language, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($amount, $currencyCode);
}
}
注册到依赖注入容器:
<?php
$di->setShared('format', function () {
return new App\Services\FormatService();
});
现在可以在控制器中使用:
<?php
$this->view->price = $this->format->currency(99.99);
$this->view->discount = $this->format->number(0.2, NumberFormatter::PERCENT);
语言切换实现
允许用户手动切换语言是国际化应用的基本功能。常见的实现方式有两种:使用 URL 参数或使用 Cookie 存储用户的语言偏好。
基于 URL 的语言切换
基于 URL 的语言切换是最常见的方式,也有利于 SEO。例如:
https://example.com/en/about- 英文版本https://example.com/zh/about- 中文版本
要实现这种方式,我们需要配置路由来识别语言参数:
<?php
use Phalcon\Mvc\Router;
$router = new Router();
// 定义带语言参数的路由组
$router->add('/{language:[a-z]{2}}/:controller/:action/:params', [
'language' => 1,
'controller' => 2,
'action' => 3,
'params' => 4
]);
// 默认路由(不带语言参数)
$router->add('/:controller/:action/:params', [
'language' => 'zh', // 默认语言
'controller' => 1,
'action' => 2,
'params' => 3
]);
return $router;
然后在基础控制器中处理语言参数:
<?php
use Phalcon\Mvc\Controller;
class ControllerBase extends Controller
{
public function initialize()
{
// 从路由获取语言参数
$language = $this->dispatcher->getParam('language');
// 验证语言是否受支持
$supportedLanguages = ['zh', 'en', 'fr', 'es'];
if (!in_array($language, $supportedLanguages)) {
$language = 'zh'; // 默认语言
}
// 设置翻译服务的语言
$this->di->setShared(
'translate',
function () use ($language) {
$translate = new App\Services\TranslateService();
return $translate->getTranslator($language);
}
);
// 将当前语言传递给视图
$this->view->language = $language;
}
}
基于 Cookie 的语言切换
另一种方式是使用 Cookie 存储用户的语言偏好。用户选择语言后,我们将语言代码存储在 Cookie 中,下次用户访问时读取 Cookie 并应用相应的语言。
<?php
// 在语言切换控制器中
class LanguageController extends ControllerBase
{
public function switchAction()
{
// 获取请求的语言
$language = $this->request->getPost('language');
// 验证语言是否受支持
$supportedLanguages = ['zh', 'en', 'fr', 'es'];
if (in_array($language, $supportedLanguages)) {
// 设置Cookie,有效期30天
$this->cookies->set('language', $language, time() + 86400 * 30);
}
// 重定向回之前的页面
$this->response->redirect($this->request->getHTTPReferer());
}
}
然后在基础控制器中读取 Cookie:
<?php
public function initialize()
{
// 从Cookie获取语言,默认为中文
$language = $this->cookies->get('language', 'zh')->getValue();
// 验证语言是否受支持
$supportedLanguages = ['zh', 'en', 'fr', 'es'];
if (!in_array($language, $supportedLanguages)) {
$language = 'zh';
}
// 设置翻译服务...
}
语言切换组件
为了方便用户切换语言,我们可以创建一个语言切换组件,在页面上显示语言选择器:
<?php
// app/views/components/language_switcher.volt
<form action="{{ url('language/switch') }}" method="post">
<select name="language" onchange="this.form.submit()">
<option value="zh" {% if language == 'zh' %}selected{% endif %}>中文</option>
<option value="en" {% if language == 'en' %}selected{% endif %}>English</option>
<option value="fr" {% if language == 'fr' %}selected{% endif %}>Français</option>
<option value="es" {% if language == 'es' %}selected{% endif %}>Español</option>
</select>
</form>
然后在布局文件中包含这个组件:
// app/views/index.volt
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{ t._('site_title') }}</title>
</head>
<body>
<div class="header">
{% include 'components/language_switcher' %}
<!-- 其他头部内容 -->
</div>
<div class="content">
{{ content() }}
</div>
</body>
</html>
高级国际化技巧
复数规则
不同语言有不同的复数规则。例如,英语有单数和复数两种形式,而阿拉伯语有六种不同的复数形式。使用 MessageFormatter 可以处理复杂的复数规则:
<?php
use MessageFormatter;
// 英语复数规则
$formatter = new MessageFormatter('en_US', '{0, plural, =0{No items} =1{One item} other{# items}}');
echo $formatter->format([0]); // 输出:No items
echo $formatter->format([1]); // 输出:One item
echo $formatter->format([5]); // 输出:5 items
// 阿拉伯语复数规则
$formatter = new MessageFormatter('ar_SA', '{0, plural, =0{لا عناصر} =1{عنصر واحد} =2{عنصران} few{# عناصر} many{# عنصر} other{# عنصر}}');
echo $formatter->format([0]); // 输出:لا عناصر
echo $formatter->format([1]); // 输出:عنصر واحد
echo $formatter->format([2]); // 输出:عنصران
echo $formatter->format([5]); // 输出:5 عناصر
echo $formatter->format([11]); // 输出:11 عنصر
字符串排序
不同语言有不同的字符排序规则。使用 Collator 类可以实现本地化的字符串排序:
<?php
use Collator;
// 创建中文排序器
$collator = new Collator('zh_CN');
$strings = ['苹果', '香蕉', '橙子', '西瓜', '草莓'];
$collator->sort($strings);
print_r($strings);
// 输出:Array ( [0] => 苹果 [1] => 草莓 [2] => 西瓜 [3] => 橙子 [4] => 香蕉 )
// 创建德语排序器
$collator = new Collator('de_DE');
$strings = ['Apfel', 'Banane', 'Orange', 'Erdbeere', 'Wassermelone'];
$collator->sort($strings);
print_r($strings);
// 输出:Array ( [0] => Apfel [1] => Banane [2] => Erdbeere [3] => Orange [4] => Wassermelone )
文本转写
Transliterator 类可以将文本从一种脚本转写到另一种脚本,例如将中文转换为拼音,或将日文转换为罗马字:
<?php
use Transliterator;
// 创建转写器:将中文转换为拼音
$transliterator = Transliterator::create('Han-Latin; Latin-ASCII; Lower()');
$chinese = '我爱编程';
echo $transliterator->transliterate($chinese); // 输出:wo ai bian cheng
// 将日文转换为罗马字
$transliterator = Transliterator::create('Hiragana-Latin; Katakana-Latin; Latin-ASCII; Lower()');
$japanese = '私はプログラミングが好きです';
echo $transliterator->transliterate($japanese); // 输出:watashi ha puroguramingu ga suki desu
国际化最佳实践
1. 规划国际化架构
在项目初期就应该规划国际化架构,而不是后期再添加。这包括设计翻译文件结构、选择合适的翻译存储方式(数组、数据库等)以及确定语言切换机制。
2. 使用统一的翻译键
使用有意义且一致的翻译键命名约定,例如:
// 好的做法
'button.login' => '登录',
'button.register' => '注册',
'message.welcome' => '欢迎来到我们的网站',
// 不好的做法
'login_btn' => '登录',
'register' => '注册',
'welcomemsg' => '欢迎来到我们的网站',
3. 避免硬编码文本
确保应用中所有用户可见的文本都通过翻译服务输出,避免在代码中硬编码文本。
4. 考虑 RTL 语言
有些语言(如阿拉伯语、希伯来语)是从右到左书写的。在设计 UI 时要考虑这一点,确保应用能够正确处理 RTL 布局。
5. 测试多种语言
不要假设翻译后的文本长度与原文本相同。有些语言比其他语言更冗长,可能会导致布局问题。测试所有支持的语言,确保 UI 在各种语言下都能正常显示。
6. 使用专业翻译
机器翻译通常不够准确,特别是对于技术术语和复杂句子。考虑使用专业翻译服务来翻译应用内容。
7. 缓存翻译数据
翻译数据通常不会频繁变化,因此可以将翻译数据缓存起来,提高应用性能:
<?php
public function getTranslator(string $language = null): NativeArray
{
$language = $language ?? 'zh';
$cacheKey = 'translate_' . $language;
// 尝试从缓存获取
if ($this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
// 加载翻译文件...
// 存入缓存,有效期1小时
$this->cache->set($cacheKey, $translator, 3600);
return $translator;
}
总结
国际化和本地化是构建全球应用的关键部分。Phalcon 框架虽然没有提供完整的国际化组件,但通过与 PHP intl 扩展的集成,我们可以轻松实现强大的国际化功能。
本章介绍了如何使用 Phalcon 的翻译组件来管理多语言内容,如何格式化日期、时间、数字和货币,以及如何实现语言切换功能。我们还探讨了一些高级国际化技巧和最佳实践。
通过合理规划和实施国际化策略,我们可以构建一个能够无缝适应不同语言和文化的应用,为全球用户提供出色的体验。