“过际化”(internationalization,常被简写为i18n),是指将字符串和其他一些具有区域特征的片段,从你的程序中提取(abstract)出来,并基于用户所在区域(比如语言、国家)而将其置于一个能被翻译和转化的层的过程。对于文本来说(text),这意味着用一个能够把它(或“信息”)翻译成用户所需语言的函数,来剥离文本的每一部分:
// 文本始终以英语输出
echo 'Hello World';
// 文本将以用户指定语言或默认英语输出
echo $translator->trans('Hello World');
locale的意思,单纯来说就是用户的语言和国家。在程序中它可以是任何一个字符串,用来管理和翻译(translation)其他格式信息(比如币种)。推荐使用以 ISO 639-1 语言代码,加一个下划线(_
),再跟一个 ISO 3166-1 alpha-2 国家代码(比如fr_FR
这个locale是指“法国法语”French/France)。
在本章中,你将学习如何使用Symfony框架中的translation组件。你可以阅读翻译组件来了解更多。整体上,翻译的过程有如下几步:
-
开启和配置Symfony的翻译服务;
-
将字符串抽象出来(如“xxxx”),这是通过调用Translator去剥离它们来实现的 (参考 翻译基础);
-
针对每个被支持的locale,创建翻译资源/文件,用于翻译程序中每一个待译字串;
-
针对request(请求)和可选的 基于用户整个session过程,来 确定、设置和管理用户的locale信息。
一、配置
翻译的过程,是通过translator
服务来处理的。该服务使用用户指定的locale,来查找并返回翻译过的信息。使用translator之前,在配置文件中开启它:
# app/config/config.yml
framework:
translator: { fallbacks: [en] }
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<framework:fallback>en</framework:fallback>
</framework:translator>
</framework:config>
</container>
// app/config/config.php
$container->loadFromExtension('framework', array(
'translator' => array('fallbacks' => array('en')),
));
关于 fallbacks关键字 以及Symfony在找不到翻译语种时该如何处理,请参考接下来的 翻译时的Locales回滚 以了解细节。
翻译时用到的locale信息,被存于request对象中。一般在路由中被设为 _locale
属性。请参考 The Locale and the URL。、
二、翻译基础
translator
服务负责完成对文本的翻译。为了翻译一个文本块(被称为“message”,以下称作“信息”),使用trans()方法。例如,你要在controller中翻译一个简单的信息:
// ...
use Symfony\Component\HttpFoundation\Response;
public function indexAction()
{
$translated = $this->get('translator')->trans('Symfony is great');
return new Response($translated);
}
上述代码被执行后,Symfony就尝试基于用户的 locale
来翻译 “Symfony is great”信息。为了让这个过程实现,你需要告诉Symfony如何通过一个“翻译源(translation resource)”来执行翻译。一般来说翻译源是一个文件,包含有成组的翻译信息,对应某一指定的locale。它就像个翻译时的“字典”,可被创建为多种格式,但推荐使用XLIFF(译注:就是后缀不同的xml格式):
XML文件
<!-- messages.fr.xlf -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="symfony_is_great">
<source>Symfony is great</source>
<target>J'aime Symfony</target>
</trans-unit>
</body>
</file>
</xliff>
YAML文件
# messages.fr.yml
Symfony is great: J'aime Symfony
PHP文件
// messages.fr.php
return array(
'Symfony is great' => 'J\'aime Symfony',
);
关于这类文件的存放位置等信息,请参考接下来的 翻译源/文件的命名和位置。
现在,如果用户的locale是法语(比如 fr_FR
或 fr_BE
),那么前面的信息将被翻译为 J'aime Symfony
。你也可以在参考接下来的 模板中的翻译(templates) 。
三、翻译的处理过程
为了能翻译一条信息,Symfony将执行以下简明流程:
-
先确定request对象中所存储的当前用户的
locale
信息; -
然后从由
locale
(比如fr_FR
)所决定的翻译源中,加载该翻译源目录下已经翻译好的信息。如果locale不存在,则使用由 fallback locale 所决定的翻译信息。最终结果,将生成了一个“翻译大词典”; -
如果能够从目录中找到待翻信息,翻译结果将被返回。否则, translator返回原始信息。
当使用trans()方法时,Symfony从对应的信息目录中,寻找准确的字符串,然后返回它(如果存在的话)。
四、信息占位符
有时,一条包含变量的信息,也需要被翻译:
use Symfony\Component\HttpFoundation\Response;
public function indexAction($name)
{
$translated = $this->get('translator')->trans('Hello '.$name);
return new Response($translated);
}
可是,对于这样一个字符串,创建相应的翻译是不可能的,因为translator始终在尝试寻找“确定信息”,包括变量值本身(比如“Hello Ryan”和“Hello Fabien”在translator看来是两条不同的信息)。其实,我们不必为 $name
变量编写每一种可能的翻译,你只需要在待译信息和翻译信息中同时使用相同的 "占位符" ,翻译时就会将 “占位符” 替换为此 “变量” 。代码如下:
...
$translated = $this->get('translator')->trans( 'Hello %name%',
array('%name%' => $name)
);
...
Symfony将查找原始信息(Hello %name%
)的翻译,然后用它们的值来替换占位符。创建翻译时仍和以前一样:
<!-- messages.fr.xlf -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>Hello %name%</source>
<target>Bonjour %name%</target>
</trans-unit>
</body>
</file>
</xliff>
// messages.fr.php
return array(
'Hello %name%' => 'Bonjour %name%',
);
# messages.fr.yml
'Hello %name%': Bonjour %name%
同样情况在模板中的处理方法,请参考接下来的 模板中的翻译之Twig Templates。
五、复数处理
(一)隐式的复数
另一个复杂场面,则是你在翻译中要面对基于某些变量的“复数状况”:
There is one apple.
There are 5 apples.
要处理这种特殊情况,可以使用 transChoice()
方法(模板中使用transchoice
标签/过滤器,可参考接下来的 模板中的翻译)。解决方法如下例所示:
例如,这里有一个俄语复数规则的数学呈现:
(($number % 10 == 1) && ($number % 100 != 11))
? 0
: ((($number % 10 >= 2)
&& ($number % 10 <= 4)
&& (($number % 100 < 10)
|| ($number % 100 >= 20)))
? 1
: 2
);
你已看到,在俄语中,可以有三种不同的复数形式,每一种的索引是0,1,2。对于每种形式,复数形态不同,所以翻译也是不同的。
当翻译因复数而有不同的形式时,你可以通过一个由pipe(|
)分隔的字符串,来提供出全部的形式。
'There is one apple|There are %count% apples'
要翻译成复数信息,使用 transChoice()
方法:
$translator->transChoice(
'There is one apple|There are %count% apples',
10,
array('%count%' => 10)
);
第二个参数 (本例是 10
) 是被描述对象的 number(数量),用于决定要使用哪种翻译,同时还要装载 %count%
占位符。
根据给定的数字,translator 会选择正确的复数形式。在英文中,当仅有一个物体时,多数单词只有一个单数形式,而其复数形式可以是所有其他数字 (0, 2, 3...)。因此,如果 count
是 1
,translator 将使用第一个字符串 (There is one apple
) 作为翻译。否则它就使用 There are %count% apples
。
这里有一个法语翻译:
'Il y a %count% pomme|Il y a %count% pommes'
就算字符串看起来(和英语的)很相似 (它由两个通过pipe分隔的子串构成),法语的规则却不同: 第一种形式 (没有复数) 用于当 count
是 0
或 1
。因此,当 count
是 0
或 1
时,translator将自动使用第一个字符串 (Il y a %count% pomme
)。
每一种locale有其自己的规则集,其中某些甚至有高达六种不同的复数形式,配合着背后的复杂规则,即哪个数字映射的是哪种复数形式。对于英语和法语来说,规则十分简单,但对于俄语,你可能不太想去搞清哪个规则对应哪个字符串。要帮助translators,你可以可选地对每个字符串“打标签”:
'one: There is one apple|some: There are %count% apples'
'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'
标签(tag)真的是只能对translator进行提示而不会影响到“用于决定使用哪种复数形式”的规则。标签可以是任何描述性的字符串,它以冒号(:
)结尾。标签并不需要和原始的待译信息相同,它们是可选的,translator并不使用它们(translator只根据tag在字符串中的位置来获取字符串)。
(二)显式的区间复数
对message(待译信息)进行复数处理的最简单方式就是,让translator使用内部逻辑,基于给定的数字,来决定要使用哪个字符串。有时,你会需要更多的控制权,或者希望在个别情况下(例如对于0
或负值的count)使用不同的翻译。对于这类场景,你可以使用显式的算术区间:
'{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf[ There are many apples'
区间(interval)遵循的是 ISO 31-11 注释。上面的字符串指定了四个区间: 确切的 0
, 确切的 1
, 2-19
, 和 20
以及更高。
你也可以显式地把 算术规则(math rules)和 标准规则 混合起来指定。本例中,如果count没有匹配到一个特定区间,标准规则(standard rules)将在移除显式规则之后生效:
'{0} There are no apples|[20,Inf[ There are many apples|There is one apple|a_few: There are %count% apples'
例如,对于 1
个苹果,标准规则 There is one apple
将被使用。对于 2-19
个苹果,第二个标准规则 There are %count% apples
将被使用。
一个 Interval
可以呈现出无限的数字组合: {1,2,3,4} 或者是两个数字之外的数字:
[1, +Inf[
]-1,2[
左边的分隔符可以是 [
(inclusive/包括) 或 ]
(exclusive/排除)。 右边的分隔符可以是 [
(exclusive/排除) 或 ]
(inclusive/包括)。除去数字,你还可以使用 -Inf
和 +Inf
来表达无限。
六、模板中的翻译
大多数情况下,翻译发生在模板中,对于Twig和PHP模板,Symfony提供了原生支持。
(一)Twig模板
Symfony提供了特殊的Twig标签(trans
和 transchoice
),用于对“静态文本块”信息提供翻译帮助。
{% trans %}Hello %name%{% endtrans %}
{% transchoice count %}
{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples
{% endtranschoice %}
使用 transchoice
标签,自动地从当前上下文关系中得到 %count%
变量,并将其传给translator。这种机制,只在你使用 %var%
这种格式的占位符时生效。
如果你需要在字符串中使用百分号%,要写两次来为它转义:
{% trans %}Percent: %percent%%%{% endtrans %}
你也可以指定 信息域(message domain),并传递一些附加的变量进来:
{% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
{% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}
{% transchoice count with {'%name%': 'Fabien'} from "app" %}
{0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples
{% endtranschoice %}
使用 trans 和 transchoice 过滤器,可用于翻译变量文本和复杂表达式:
{{ message|trans }}
{{ message|transchoice(5) }}
{{ message|trans({'%name%': 'Fabien'}, "app") }}
{{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
在翻译时无论使用标签还是过滤器,效果是相同的,但有一个微小区别:自动输出转义功能 仅对过滤器有效。换言之,如果你需要翻译出来的信息“不被转义”,则必须在translation调节器后面再跟一个raw过滤器:
| |
你可以通过单一标签,为整个twig模板设置一个翻译域(translation domain):
| |
注意这时仅会影响到当前模板,而不包括任何“被包容(included)”的模板(为的是减少副作用)。
(二)PHP模板
translator也可以在PHP模板中,通过translator helper来使用:
<?php echo $view['translator']->trans('Symfony is great') ?>
<?php echo $view['translator']->transChoice(
'{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
10,
array('%count%' => 10)
) ?>
(三)翻译源/文件的命名和位置
Symfony在以下位置寻找信息文件(即是translations/翻译信息):
-
app/Resources/translations
目录; -
app/Resources/<bundle name>/translations
目录; -
任何bundle下的
Resources/translations/
目录
上面的位置,是按照“优先权由高至低”的顺序排列的。这意味着,你可以用前面两个目录之一,来覆写某个bundle中的翻译信息。
覆写机制,基于键等级(key level)而执行:只有被覆写的键,才需要被列在高优先级的信息文件中。当一个键没有在信息文件中被找到时,translator将自动回滚到低优先级的信息文件中。
信息文件的文件名也很重要,每一个信息文件必须按下列命名路径来命名domain.locale.loader
:
-
domain: 这是一个可选项,用于组织信息文件成为群组(例如admin, navigation 或default messages)。参阅 使用翻译信息的域 Using Message Domains;
-
locale: 这是翻译信息的locale (例如 en_GB, en, 等等);
-
loader: 这是Symfony如何来加载和解析信息文件 的加载器(也就是
xlf
,php
,yml
等文件后缀).
加载器(loader)可以是任何已注册加载器的名称。Symfony默认提供了许多加载器,包括:
-
xlf
: 加载XLIFF文件; -
php
: 加载PHP文件; -
yml
: 加载YAML文件;
使用何种加载器的选择权完全在你,随你喜好。推荐使用 xlf 作为翻译的信息文件。更多选择,参考 加载信息目录 Loading Message Catalogs。
你也可以将翻译信息存在数据库中,或任何其他介质,只要提供一个自定义的类去实现 LoaderInterface
接口即可。参考 translation.loader
标签,以了解更多。
你可以在配置文件中通过paths
选项添加一个目录:
# app/config/config.yml
framework:
translator:
paths:
- '%kernel.root_dir%/../translations'
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-Instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
>
<framework:config>
<framework:translator>
<framework:path>%kernel.root_dir%/../translations</framework:path>
</framework:translator>
</framework:config>
</container>
// app/config/config.php
$container->loadFromExtension('framework', array(
'translator' => array(
'paths' => array(
'%kernel.root_dir%/../translations',
),
),
));
每次你创建一个新的翻译源(translation resource),或安装了一个包含翻译源的bundle时,一定要清除缓存,这样Symfony才能发现这个新的翻译源。
$ php app/console cache:clear
(四)翻译时Locale的回滚
假设一名用户的locale信息是 fr_FR
,而你正翻译的键是 Symfony is great
。为了找到法语信息,Symfony切实地检查若干locale的翻译源:
-
首先,Symfony在一个
fr_FR
的翻译源(例如messages.fr_FR.xlf)寻找翻译信息; -
如果没找到,Symfony在一个
fr
翻译源(比如messages.fr.xlf)继续寻找翻译信息; -
如果仍然没找到,Symfony使用fallbacks这个配置参数, 它被默认设为
en
(参阅 FrameworkBundle配置信息)。
2.6版Symfony引入了将缺少的翻译信息写入日志的能力。当Symfony无法找到给定locale的翻译信息时,它会把缺少的翻译信息给添加到日志文件中。参阅 logging。
(五)翻译数据库内容
翻译数据库内容时要用到Doctrine扩展中的 Translatable Extension 或 Translatable Behavior(PHP 5.4+)。更多信息请参考相应文档。
译注:使用Doctrine扩展有两种方式
一个是类库方式,一个是bundle方式。此处的Translatable Behavior,是指用bundle安装之后,翻译数据库内容时所应参考的用法)
(六)翻译约束信息
参考 如何翻译验证约束消息 以了解更多。
(七)处理用户的Locale
翻译的过程是取决于用户的locale的。阅读 如何操作用户的Locale 以了解如何处理,如下所示:
当前用户的locale被存在请求中,可以通过 Request
对象访问到:
use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request)
{
$locale = $request->getLocale();
}
要设置用户的locale,你可能希望创建一个自定义的事件监听,以便它在系统的其他部分(比如translator)需要它之前就被设置好:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// some logic to determine the $locale
$request->setLocale($locale);
}
在控制器中使用 $request->setLocale()
来设置locale以便左右translator,实在是太迟了。可以通过监听 (如上), URL (见下文) 或是直接对 translator
服务来调用 setLocale()
。下面参考 把locale信息“粘连”到用户的Session周期中 以了解更多内容:
symfony将locale设置,存储在Request中,这意味着该设置不可用在后续请求。在本章,你将学习如何保存这个locale到session中,以便相同的locale可以用在所有后续请求中。
a)创建LocaleListener
为了模拟该locale被存储在session,你需要去创建和注册一个新的事件监听。监听器看起来像是这样的。通常情况下,_locale
作为路由参数来表示locale,虽然他对你如何确定一个请求所需的locale无关紧要:
// src/AppBundle/EventListener/LocaleListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 15)),
);
}
}
然后注册监听器:
# config.yaml #
services:
app.locale_listener:
class: AppBundle\EventListener\LocaleListener
arguments: ['%kernel.default_locale%']
tags:
- { name: kernel.event_subscriber }
# config.xml #
<service id="app.locale_listener"
class="AppBundle\EventListener\LocaleListener">
<argument>%kernel.default_locale%</argument>
<tag name="kernel.event_subscriber" />
</service>
#config.php #
use Symfony\Component\DependencyInjection\Definition;
$container
->setDefinition('app.locale_listener', new Definition(
'AppBundle\EventListener\LocaleListener',
array('%kernel.default_locale%')
))
->addTag('kernel.event_subscriber')
就是这样!改变用户locale并看到他粘在整个请求中,现在庆祝一下吧。记住,去获取用户locale,请始终使用这个Request::getLocale方法:
// from a controller...
use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request)
{
$locale = $request->getLocale();
}
b)设置基于用户喜好的Locale
你可能希望进一步提高这一技术,并基于登录用户的用户实体定义locale。然而,由于LocaleListener
在 FirewallListener
之前调用,你无法访问已登录用户,FirewallListener
来负责处理认证并设置用户token在TokenStorage
上。
假设你已经在你的User
实体中定义了一个locale
属性,并且你想要去使用它作为给定用户的locale。要做到这一点,你可以挂钩巧取到登录过程,并在他们被重定向到第一页之前更新用户session的locale值。
要做到这一点,你需要一个security.interactive_login
事件的事件监听器:
// src/AppBundle/EventListener/UserLocaleListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/**
* Stores the locale of the user in the session after the
* login. This can be used by the LocaleListener afterwards.
*/
class UserLocaleListener
{
/**
* @var Session
*/
private $session;
public function __construct(Session $session)
{
$this->session = $session;
}
/**
* @param InteractiveLoginEvent $event
*/
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getLocale()) {
$this->session->set('_locale', $user->getLocale());
}
}
}
然后注册监听器:
# app/config/services.yml
services:
app.user_locale_listener:
class: AppBundle\EventListener\UserLocaleListener
arguments: ['@session']
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin }
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="app.user_locale_listener"
class="AppBundle\EventListener\UserLocaleListener">
<argument type="service" id="session"/>
<tag name="kernel.event_listener"
event="security.interactive_login"
method="onInteractiveLogin" />
</service>
</services>
</container>
// app/config/services.php
$container
->register('app.user_locale_listener', 'AppBundle\EventListener\UserLocaleListener')
->addArgument('session')
->addTag(
'kernel.event_listener',
array('event' => 'security.interactive_login', 'method' => 'onInteractiveLogin'
);
为了在用户更改语言偏好后立即更新语言,你需要在更新 User
实体后更新session。
下文的 Locale 和 URL 小节有讲到通过路由来设置locale。
由于你可以把locale存到用户的session中,根据用户的locale,它可能会尝试使用相同的URL来显示不同语言的资源。例如,http://www.example.com/contact
可以对某个用户显示英语内容,但对另一个用户有显示法语的。不幸的是,这会粗暴破坏互联网的一个基本原则:即,特定的URL要对用户返回相同的资源,不管是什么(语种的)用户。进一步搞砸问题的是,哪个版本的内容可以被搜索引擎检索到?
一个很好的策略是,把locale包容到URL之中。通过使用一个特殊的 _locale
参数(parameter),该策略已为路由系统完整支持:
# app/config/routing.yml
contact:
path: /{_locale}/contact
defaults: { _controller: AppBundle:Contact:index }
requirements:
_locale: en|fr|de
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="contact" path="/{_locale}/contact">
<default key="_controller">AppBundle:Contact:index</default>
<requirement key="_locale">en|fr|de</requirement>
</route>
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('contact', new Route(
'/{_locale}/contact',
array(
'_controller' => 'AppBundle:Contact:index',
),
array(
'_locale' => 'en|fr|de',
)
));
return $collection;
当使用特殊的 _locale
路由参数时,匹配到的locale将 自动设置到Request对象中 然后可以透过 getLocale()
方法来取出。换言之,如果一个用户访问的URI是 /fr/contact
,那么locale fr
将自动地被设置为当前请求的locale。
现在你可以使用 locale 来创建路由,用于程序中的其他需要翻译的页面。
参考 如何在路由中使用服务容器的参数 来了解如何避免在全部路由中写死 _locale
条件(requirement)。
设置默认的Locale
若无法确定用户的locale怎么办?在框架中配置好 default_locale
,即可确保用户在每一次请求中皆已被设置locale:
# app/config/config.yml
framework:
default_locale: en
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config default-locale="en" />
</container>
// app/config/config.php
$container->loadFromExtension('framework', array(
'default_locale' => 'en',
));
(八)对翻译进行调试
debug:translation 命令行语句从Symfony 2.6起引入。 Symfony 2.5之前,这一命令是translation:debug。
当你在不同语言的大量翻译信息中操作时,要跟踪到丢失了哪条信息以及哪条信息没有被使用,是很困难的。阅读 如何找到丢失或未使用的翻译信息 以了解如何发现这一类翻译信息。
(九)总结
使用Symfony的翻译组件,创建一个国际化的应用程序,将不再是一个“痛苦过程”,而是归结于以下简单步骤:
-
从程序中抽象出待翻译的信息,把每一条信息用
trans()
或transChoice()
方法替换(通过 使用Translator一文了解更多); -
通过创建信息文件(translation message file)将待译信息翻译成多个locale语种。Symfony能够找到并处理每一个文件,因为这些文件的名字遵循指定的命名约定;
-
管理好用户的locale,它可以存在request中,但是也可以存在用户的session中。