发布于 2015-08-27 16:26:59 | 317 次阅读 | 评论: 0 | 来源: 网络整理
The term “internationalization” (often abbreviated i18n) refers to the process of abstracting strings and other locale-specific pieces out of your application into a layer where they can be translated and converted based on the user’s locale (i.e. language and country). For text, this means wrapping each with a function capable of translating the text (or “message”) into the language of the user:
// text will *always* print out in English echo 'Hello World'; // text can be translated into the end-user's language or // default to English echo $translator->trans('Hello World');
注解
The term locale refers roughly to the user’s language and country. It can be any string that your application uses to manage translations and other format differences (e.g. currency format). The ISO 639-1 language code, an underscore (_
), then the ISO 3166-1 alpha-2 country code (e.g. fr_FR
for French/France) is recommended.
In this chapter, you’ll learn how to use the Translation component in the Symfony framework. You can read the Translation component documentation to learn even more. Overall, the process has several steps:
Translator
(“翻译基本用法”);Translations are handled by a translator
service that uses the user’s locale to lookup and return translated messages. Before using it, enable the translator
in your configuration:
# 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')), ));
See 设置默认语言 for details on the fallbacks
key and what Symfony does when it doesn’t find a translation.
The locale used in translations is the one stored on the request. This is typically set via a _locale
attribute on your routes (see 多语言和URL).
Translation of text is done through the translator
service (Translator
). To translate a block of text (called a message), use the trans()
method. Suppose, for example, that you’re translating a simple message from inside a controller:
// ... use SymfonyComponentHttpFoundationResponse; public function indexAction() { $translated = $this->get('translator')->trans('Symfony is great'); return new Response($translated); }
When this code is executed, Symfony will attempt to translate the message “Symfony is great” based on the locale
of the user. For this to work, you need to tell Symfony how to translate the message via a “translation resource”, which is usually a file that contains a collection of translations for a given locale. This “dictionary” of translations can be created in several different formats, XLIFF being the recommended format:
<!-- messages.fr.xliff --> <?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>Symfony is great</source> <target>J'aime Symfony</target> </trans-unit> </body> </file> </xliff>
# messages.fr.yml Symfony is great: J'aime Symfony
// messages.fr.php return array( 'Symfony is great' => 'J'aime Symfony', );
For information on where these files should be located, see 翻译文件命名规则以及存放位置.
Now, if the language of the user’s locale is French (e.g. fr_FR
or fr_BE
), the message will be translated into J'aime Symfony
. You can also translate the message inside your templates.
To actually translate the message, Symfony uses a simple process:
locale
of the current user, which is stored on the request is determined;locale
(e.g. fr_FR
). Messages from the fallback locale are also loaded and added to the catalog if they don’t already exist. The end result is a large “dictionary” of translations.When using the trans()
method, Symfony looks for the exact string inside the appropriate message catalog and returns it (if it exists).
Sometimes, a message containing a variable needs to be translated:
use SymfonyComponentHttpFoundationResponse; public function indexAction($name) { $translated = $this->get('translator')->trans('Hello '.$name); return new Response($translated); }
However, creating a translation for this string is impossible since the translator will try to look up the exact message, including the variable portions (e.g. “Hello Ryan” or “Hello Fabien”).
For details on how to handle this situation, see Message Placeholders in the components documentation. For how to do this in templates, see Twig模板.
Another complication is when you have translations that may or may not be plural, based on some variable:
There is one apple. There are 5 apples.
To handle this, use the transChoice()
method or the transchoice
tag/filter in your template.
For much more information, see Pluralization in the Translation component documentation.
Most of the time, translation occurs in templates. Symfony provides native support for both Twig and PHP templates.
Symfony provides specialized Twig tags (trans
and transchoice
) to help with message translation of static blocks of text:
{% trans %}Hello %name%{% endtrans %} {% transchoice count %} {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples {% endtranschoice %}
The transchoice
tag automatically gets the %count%
variable from the current context and passes it to the translator. This mechanism only works when you use a placeholder following the %var%
pattern.
警告
The %var%
notation of placeholders is required when translating in Twig templates using the tag.
小技巧
If you need to use the percent character (%
) in a string, escape it by doubling it: {% trans %}Percent: %percent%%%{% endtrans %}
You can also specify the message domain and pass some additional variables:
{% 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 %}
The trans
and transchoice
filters can be used to translate variable texts and complex expressions:
{{ message|trans }} {{ message|transchoice(5) }} {{ message|trans({'%name%': 'Fabien'}, "app") }} {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
小技巧
Using the translation tags or filters have the same effect, but with one subtle difference: automatic output escaping is only applied to translations using a filter. In other words, if you need to be sure that your translated message is not output escaped, you must apply the raw
filter after the translation filter:
{# text translated between tags is never escaped #} {% trans %} <h3>foo</h3> {% endtrans %} {% set message = '<h3>foo</h3>' %} {# strings and variables translated via a filter are escaped by default #} {{ message|trans|raw }} {{ '<h3>bar</h3>'|trans|raw }}
小技巧
You can set the translation domain for an entire Twig template with a single tag:
{% trans_default_domain "app" %}
Note that this only influences the current template, not any “included” template (in order to avoid side effects).
The translator service is accessible in PHP templates through the 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 looks for message files (i.e. translations) in the following locations:
app/Resources/translations
directory;app/Resources/<bundle name>/translations
directory;Resources/translations/
directory inside of any bundle.The locations are listed here with the highest priority first. That is, you can override the translation messages of a bundle in any of the top 2 directories.
The override mechanism works at a key level: only the overridden keys need to be listed in a higher priority message file. When a key is not found in a message file, the translator will automatically fall back to the lower priority message files.
The filename of the translation files is also important: each message file must be named according to the following path: domain.locale.loader
:
admin
, navigation
or the default messages
) - see Using Message Domains;en_GB
, en
, etc);xliff
, php
, yml
, etc).The loader can be the name of any registered loader. By default, Symfony provides many loaders, including:
xliff
: XLIFF file;php
: PHP file;yml
: YAML file.The choice of which loader to use is entirely up to you and is a matter of taste. The recommended option is to use xliff
for translations. For more options, see Loading Message Catalogs.
注解
You can also store translations in a database, or any other storage by providing a custom class implementing the LoaderInterface
interface. See the translation.loader tag for more information.
警告
Each time you create a new translation resource (or install a bundle that includes a translation resource), be sure to clear your cache so that Symfony can discover the new translation resources:
$ php app/console cache:clear
Imagine that the user’s locale is fr_FR
and that you’re translating the key Symfony is great
. To find the French translation, Symfony actually checks translation resources for several locales:
fr_FR
translation resource (e.g. messages.fr_FR.xliff
);fr
translation resource (e.g. messages.fr.xliff
);fallbacks
configuration parameter, which defaults to en
(see `Configuration`_).2.6 新版功能: The ability to log missing translations was introduced in Symfony 2.6.
注解
When Symfony doesn’t find a translation in the given locale, it will add the missing translation to the log file. For details, see logging.
The locale of the current user is stored in the request and is accessible via the request
object:
use SymfonyComponentHttpFoundationRequest; public function indexAction(Request $request) { $locale = $request->getLocale(); }
To set the user’s locale, you may want to create a custom event listener so that it’s set before any other parts of the system (i.e. the translator) need it:
public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); // some logic to determine the $locale $request->getSession()->set('_locale', $locale); }
Read Making the Locale “Sticky” during a User’s Session for more on the topic.
注解
Setting the locale using $request->setLocale()
in the controller is too late to affect the translator. Either set the locale via a listener (like above), the URL (see next) or call setLocale()
directly on the translator
service.
See the 多语言和URL section below about setting the locale via routing.
Since you can store the locale of the user in the session, it may be tempting to use the same URL to display a resource in different languages based on the user’s locale. For example, http://www.example.com/contact
could show content in English for one user and French for another user. Unfortunately, this violates a fundamental rule of the Web: that a particular URL returns the same resource regardless of the user. To further muddy the problem, which version of the content would be indexed by search engines?
A better policy is to include the locale in the URL. This is fully-supported by the routing system using the special _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 SymfonyComponentRoutingRouteCollection; use SymfonyComponentRoutingRoute; $collection = new RouteCollection(); $collection->add('contact', new Route( '/{_locale}/contact', array( '_controller' => 'AppBundle:Contact:index', ), array( '_locale' => 'en|fr|de', ) )); return $collection;
When using the special _locale
parameter in a route, the matched locale will automatically be set on the Request and can be retrieved via the getLocale()
method. In other words, if a user visits the URI /fr/contact
, the locale fr
will automatically be set as the locale for the current request.
You can now use the locale to create routes to other translated pages in your application.
小技巧
Read How to Use Service Container Parameters in your Routes to learn how to avoid hardcoding the _locale
requirement in all your routes.
What if the user’s locale hasn’t been determined? You can guarantee that a locale is set on each user’s request by defining a default_locale
for the framework:
# 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', ));
If you’re using validation constraints with the form framework, then translating the error messages is easy: simply create a translation resource for the validators
domain.
To start, suppose you’ve created a plain-old-PHP object that you need to use somewhere in your application:
// src/AppBundle/Entity/Author.php namespace AppBundleEntity; class Author { public $name; }
Add constraints though any of the supported methods. Set the message option to the translation source text. For example, to guarantee that the $name
property is not empty, add the following:
// src/AppBundle/Entity/Author.php use SymfonyComponentValidatorConstraints as Assert; class Author { /** * @AssertNotBlank(message = "author.name.not_blank") */ public $name; }
# src/AppBundle/Resources/config/validation.yml AppBundleEntityAuthor: properties: name: - NotBlank: { message: "author.name.not_blank" }
<!-- src/AppBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="AppBundleEntityAuthor"> <property name="name"> <constraint name="NotBlank"> <option name="message">author.name.not_blank</option> </constraint> </property> </class> </constraint-mapping>
// src/AppBundle/Entity/Author.php // ... use SymfonyComponentValidatorMappingClassMetadata; use SymfonyComponentValidatorConstraintsNotBlank; class Author { public $name; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new NotBlank(array( 'message' => 'author.name.not_blank', ))); } }
Create a translation file under the validators
catalog for the constraint messages, typically in the Resources/translations/
directory of the bundle.
<!-- validators.en.xliff --> <?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>author.name.not_blank</source> <target>Please enter an author name.</target> </trans-unit> </body> </file> </xliff>
# validators.en.yml author.name.not_blank: Please enter an author name.
// validators.en.php return array( 'author.name.not_blank' => 'Please enter an author name.', );
The translation of database content should be handled by Doctrine through the Translatable Extension or the Translatable Behavior (PHP 5.4+). For more information, see the documentation for these libraries.
2.6 新版功能: Prior to Symfony 2.6, this command was called translation:debug
.
When maintaining a bundle, you may use or remove the usage of a translation message without updating all message catalogues. The debug:translation
command helps you to find these missing or unused translation messages for a given locale. It shows you a table with the result when translating the message in the given locale and the result when the fallback would be used. On top of that, it also shows you when the translation is the same as the fallback translation (this could indicate that the message was not correctly translated).
Thanks to the messages extractors, the command will detect the translation tag or filter usages in Twig templates:
{% trans %}Symfony2 is great{% endtrans %} {{ 'Symfony2 is great'|trans }} {{ 'Symfony2 is great'|transchoice(1) }} {% transchoice 1 %}Symfony2 is great{% endtranschoice %}
It will also detect the following translator usages in PHP templates:
$view['translator']->trans("Symfony2 is great"); $view['translator']->transChoice('Symfony2 is great', 1);
警告
The extractors are not able to inspect the messages translated outside templates which means that translator usages in form labels or inside your controllers won’t be detected. Dynamic translations involving variables or expressions are not detected in templates, which means this example won’t be analyzed:
{% set message = 'Symfony2 is great' %} {{ message|trans }}
Suppose your application’s default_locale is fr
and you have configured en
as the fallback locale (see 设置 and 设置默认语言 for how to configure these). And suppose you’ve already setup some translations for the fr
locale inside an AcmeDemoBundle:
<!-- src/Acme/AcmeDemoBundle/Resources/translations/messages.fr.xliff --> <?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>Symfony2 is great</source> <target>J'aime Symfony2</target> </trans-unit> </body> </file> </xliff>
# src/Acme/AcmeDemoBundle/Resources/translations/messages.fr.yml Symfony2 is great: J'aime Symfony2
// src/Acme/AcmeDemoBundle/Resources/translations/messages.fr.php return array( 'Symfony2 is great' => 'J'aime Symfony2', );
and for the en
locale:
<!-- src/Acme/AcmeDemoBundle/Resources/translations/messages.en.xliff --> <?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>Symfony2 is great</source> <target>Symfony2 is great</target> </trans-unit> </body> </file> </xliff>
# src/Acme/AcmeDemoBundle/Resources/translations/messages.en.yml Symfony2 is great: Symfony2 is great
// src/Acme/AcmeDemoBundle/Resources/translations/messages.en.php return array( 'Symfony2 is great' => 'Symfony2 is great', );
To inspect all messages in the fr
locale for the AcmeDemoBundle, run:
$ php app/console debug:translation fr AcmeDemoBundle
You will get this output:
It indicates that the message Symfony2 is great
is unused because it is translated, but you haven’t used it anywhere yet.
Now, if you translate the message in one of your templates, you will get this output:
The state is empty which means the message is translated in the fr
locale and used in one or more templates.
If you delete the message Symfony2 is great
from your translation file for the fr
locale and run the command, you will get:
The state indicates the message is missing because it is not translated in the fr
locale but it is still used in the template. Moreover, the message in the fr
locale equals to the message in the en
locale. This is a special case because the untranslated message id equals its translation in the en
locale.
If you copy the content of the translation file in the en
locale, to the translation file in the fr
locale and run the command, you will get:
You can see that the translations of the message are identical in the fr
and en
locales which means this message was probably copied from French to English and maybe you forgot to translate it.
By default all domains are inspected, but it is possible to specify a single domain:
$ php app/console debug:translation en AcmeDemoBundle --domain=messages
When bundles have a lot of messages, it is useful to display only the unused or only the missing messages, by using the --only-unused
or --only-missing
switches:
$ php app/console debug:translation en AcmeDemoBundle --only-unused $ php app/console debug:translation en AcmeDemoBundle --only-missing
With the Symfony Translation component, creating an internationalized application no longer needs to be a painful process and boils down to just a few basic steps:
trans()
or transChoice()
methods (learn about this in Using the Translator);