Documentação do Symfony - versão 3.1
Renderizada do repositório symfony-docs-pt-BR no Github
O termo “internacionalização” se refere ao processo de abstrair strings e outras peças com localidades específicas para fora de sua aplicação e dentro de uma camada onde eles podem ser traduzidos e convertidos baseados na localização do usuário (em outras palavras, idioma e páis). Para o texto, isso significa englobar cada um com uma função capaz de traduzir o texto (ou “messagem”) dentro do idioma do usuário:
// 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');
Note
O termo localidade se refere rigorosamente ao idioma e país do usuário. Isto
pode ser qualquer string que sua aplicação usa então para gerenciar traduções
e outras diferenças de formato (ex: formato de moeda). Nós recomendamos o código
de linguagem ISO639-1 , um underscore (_
), então o código de país
ISO3166 (ex: fr_FR
para Francês/França).
Nesse capítulo, nós iremos aprender como preparar uma aplicação para suportar múltiplas localidades e então como criar traduções para localidade e então como criar traduções para múltiplas localidades. No geral, o processo tem vários passos comuns:
Translation
do Symfony;Translator
;Traduções são suportadas por um Translator
service que usa o
localidadedo usuário para observar e retornar mensagens traduzidas. Antes de usar isto,
abilite o Translator
na sua configuração:
1 2 3 | # app/config/config.yml
framework:
translator: { fallback: en }
|
1 2 3 4 | <!-- app/config/config.xml -->
<framework:config>
<framework:translator fallback="en" />
</framework:config>
|
1 2 3 4 | // app/config/config.php
$container->loadFromExtension('framework', array(
'translator' => array('fallback' => 'en'),
));
|
A opção fallback
define a localidade alternativa quando uma tradução
não existe no localidadedo usuário.
Tip
Quando a tradução não existe para uma localidade, o tradutor primeiro tenta
encontrar a tradução para o idioma (fr
se o localidade é
fr_FR
por exemplo). Se isto também falhar, procura uma tradução
usando a localidade alternativa.
A localidade usada em traduções é a que está armazenada no pedido. Isto é
tipicamente definido através do atributo _locale
em suas rotas (veja A localidade e a URL).
Tradução do texto é feita done através do serviço translator
(Translator
). Para traduzir um bloco
de texto (chamado de messagem), use o
método trans()
. Suponhamos,
por exemplo, que estamos traduzindo uma simples mensagem de dentro do controller:
1 2 3 4 5 6 | public function indexAction()
{
$t = $this->get('translator')->trans('Symfony2 is great');
return new Response($t);
}
|
Quando esse código é executado, Symfony2 irá tentar traduzir a mensagem
“Symfony2 is great” baseada na localidade
do usuário. Para isto funcionar,
nós precisamos informar o Symfony2 como traduzir a mensagem por um “translation resource”,
que é uma coleção de traduções de mensagens para um localidade especificada.
Esse “dicionário” de traduções pode ser criado em diferentes formatos variados,
sendo XLIFF o formato recomendado:
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- 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>
|
1 2 3 4 | // messages.fr.php
return array(
'Symfony2 is great' => 'J\'aime Symfony2',
);
|
1 2 | # messages.fr.yml
Symfony2 is great: J'aime Symfony2
|
Agora, se o idioma do localidade do usuário é Francês (ex: fr_FR
ou fr_BE
),
a mensagem irá ser traduzida para J'aime Symfony2
.
Para realmente traduzir a mensagem, Symfony2 usa um processo simples:
localidade
do usuário atual, que está armazenada no pedido (ou
armazenada como _locale
na sessão), é determinada;locale
(ex: fr_FR
). Messagens da localidade alternativa são
também carregadas e adiconadas ao catalogo se elas realmente não existem. O resultado
final é um grande “dicionário” de traduções. Veja Catálogo de Mensagens
para mais detalhes;Quando usa o método trans()
, Symfony2 procura pelo string exato dentro
do catálogo de mensagem apropriada e o retorna (se ele existir).
Às vezes, uma mensagem conteúdo uma variável precisa ser traduzida:
1 2 3 4 5 6 | public function indexAction($name)
{
$t = $this->get('translator')->trans('Hello '.$name);
return new Response($t);
}
|
Entretanto criar uma tradução para este string é impossível visto que o tradutor
irá tentar achar a mensagem exata, incluindo porções da variável
(ex: “Hello Ryan” ou “Hello Fabien”). Ao invés escrever uma tradução
para toda interação possível da mesma variável $name
, podemos substituir a
variável com um “espaço reservado”:
1 2 3 4 5 6 | public function indexAction($name)
{
$t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name));
new Response($t);
}
|
Symfony2 irá procurar uma tradução da mensagem pura (Hello %name%
)
e então substitui o espaço reservado com os valores deles. Criar uma tradução
é exatamente como foi feito anteriormente:
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- 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>Hello %name%</source>
<target>Bonjour %name%</target>
</trans-unit>
</body>
</file>
</xliff>
|
1 2 3 4 | // messages.fr.php
return array(
'Hello %name%' => 'Bonjour %name%',
);
|
1 2 | # messages.fr.yml
'Hello %name%': Hello %name%
|
Note
Os espaços reservados podem suportar qualquer outro forma já que a mensagem inteira é reconstruída
usando a função PHP `strtr function`_. Entretanto, a notação %var%
é
requerida quando traduzir em templates Twig, e é no geral uma convenção
sensata a seguit.
Como podemos ver, criar uma tradução é um processo de dois passos:
Translator
.O segundo passo é feito mediante criar catálogos de mensagem que definem as traduções para qualquer número de localidades diferentes.
Quando uma mensagem é traduzida, Symfony2 compila um catálogo de mensagem para a localidade do usuário e investiga por uma tradução da mensagem. Um catálogo de mensagens é como um dicionário de traduções para uma localidade específica. Por exemplo, o catálogo para a localidade``fr_FR`` poderia conter a seguinte tradução:
Symfony2 is Great => J’aime Symfony2
É responsabilidade do desenvolvedor (ou tradutor) de uma aplicação internacionalizada criar essas traduções. Traduções são armazenadas no sistema de arquivos e descoberta pelo Symfony, graças a algumas convenções.
Tip
Cada vez que você criar um novo translation resource (ou instalar um pacote que inclua o translation resource), tenha certeza de limpar o cache então aquele Symfony poderá detectar o novo translation resource:
1 | php app/console cache:clear
|
O Symfony2 procura por arquivos de mensagem (em outras palavras, traduções) nas seguintes localizações:
<kernel root directory>/Resources/translations
;<kernel root directory>/Resources/<bundle name>/translations
;Resources/translations/
do bundle.Os locais são apresentados com a prioridade mais alta em primeiro lugar. Isso significa que você pode sobrescrever as mensagens de tradução de um bundle em qualquer um dos 2 diretórios no topo.
O mecanismo de substituição funciona em um nível chave: apenas as chaves sobrescritas precisam ser listadas em um arquivo de mensagem de maior prioridade. Quando a chave não é encontrada em um arquivo de mensagem, o tradutor automaticamente alternará para os arquivos de mensagem menos prioritários.
O nome de arquivo das traduções é também importante, já que Symfony2 usa uma convenção
para determinar detalhes sobre as traduções. Cada arquivo de messagem deve ser nomeado
de acordo com o seguinte padrão: domínio.localidade.carregador
:
admin
,
navigation
ou o padrão messages
) - veja Usando Domínios de Mensagem;en_GB
, en
, etc);xliff
,
php
or yml
).O carregador poder ser o nome de qualquer carregador registrado. Por padrão, Symfony providencia os seguintes carregadores:
xliff
: arquivo XLIFF;php
: arquivo PHP;yml
: arquivo YAML.A escolha de qual carregador é inteiramente tua e é uma questão de gosto.
Note
Você também pode armazenar traduções em um banco de dados, ou outro armazenamento
ao providenciar uma classe personalizada implementando a interface
LoaderInterface
.
Veja Custom Translation Loaders
abaixo para aprender como registrar carregadores personalizados.
Cada arquivo consiste de uma série de pares de tradução de id para um dado domínio e locale. A id é o identificador para a tradução individual, e pode ser a mensagem da localidade principal (ex: “Symfony is great”) de sua aplicaçãp ou um identificador único (ex: “symfony2.great” - veja a barra lateral abaixo):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!-- src/Acme/DemoBundle/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>
<trans-unit id="2">
<source>symfony2.great</source>
<target>J'aime Symfony2</target>
</trans-unit>
</body>
</file>
</xliff>
|
1 2 3 4 5 | // src/Acme/DemoBundle/Resources/translations/messages.fr.php
return array(
'Symfony2 is great' => 'J\'aime Symfony2',
'symfony2.great' => 'J\'aime Symfony2',
);
|
1 2 3 | # src/Acme/DemoBundle/Resources/translations/messages.fr.yml
Symfony2 is great: J'aime Symfony2
symfony2.great: J'aime Symfony2
|
Symfony2 irá descobrir esses arquivos e usá-los quando ou traduzir
“Symfony2 is great” ou “symfony2.great” no localidade do idioma Francês (ex:
fr_FR
ou fr_BE
).
Como nós vimos, arquivos de mensagem são organizados em diferentes localidades que
eles traduzem. Os arquivos de mensagem podem também ser organizados além de “domínios”.
O domínio padrão é messages
. Por exemplo, suponha que, para organização,
traduções foram divididas em três domínios diferentes: messages
, admin
e navigation
. A tradução Francesa teria os seguintes arquivos de
mensagem:
messages.fr.xliff
admin.fr.xliff
navigation.fr.xliff
Quando traduzir strings que não estão no domínio padrão (messages
),
você deve especificar o domínio como terceiro argumento de trans()
:
1 | $this->get('translator')->trans('Symfony2 is great', array(), 'admin');
|
Symfony2 irá pesquisar pela mensagem no domínio``admin`` da localidade do usuário.
A localidade do usuário atual é armazenada no pedido e é acessível através do objeto ``request`:
1 2 3 4 5 6 | // access the reqest object in a standard controller
$request = $this->getRequest();
$locale = $request->getLocale();
$request->setLocale('en_US');
|
Também é possível armazenar a localidade na sessão em vez do pedido. Se você fizer isso, cada pedido posterior terá esta localidade.
1 | $this->get('session')->set('_locale', 'en_US');
|
Veja a seção A localidade e a URL abaixo sobre como setar a localidade através de roteamento.
Se a localidade não foi fixada explicitamente na sessão , o parâmetro de
configuração fallback_locale
será usada pelo Translator
. O parâmetro
é padronizado para en
(veja Configuração).
Alternativamente, você pode garantir que uma localidade é definida em cada pedido do usuário
definindo um default_locale
para o framework:
1 2 3 | # app/config/config.yml
framework:
default_locale: en
|
1 2 3 4 | <!-- app/config/config.xml -->
<framework:config>
<framework:default-locale>en</framework:default-locale>
</framework:config>
|
1 2 3 4 | // app/config/config.php
$container->loadFromExtension('framework', array(
'default_locale' => 'en',
));
|
New in version 2.1: O parâmetro default_locale
foi definido debaixo da chave session
originalmente, entretanto, com o 2.1 isto foi movido. Foi movido porque a
localidade agora é definida no pedido ao invés da sessão.
Uma vez que você pode armazenar a localidade do usuário na sessão, pode ser tentador
usar a mesma URL para mostrar o recurso em muitos idiomas diferentes baseados
na localidade do usuário.Por exemplo, http://www.example.com/contact
poderia
mostrar conteúdo em Inglês para um usuário e Francês para outro usuário. Infelizmente,
isso viola uma regra fundamental da Web: que um URL particular retorne
o mesmo recurso independente do usuário. Para complicar ainda o problema, qual
versão do conteúdo deveria ser indexado pelas ferramentas de pesquisa ?
Uma melhor política é incluir a localidade na URL. Isso é totalmente suportado
pelo sistema de roteamnto usando o parâmetro especial _locale
:
1 2 3 4 5 | contact:
pattern: /{_locale}/contact
defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
requirements:
_locale: en|fr|de
|
1 2 3 4 5 | <route id="contact" pattern="/{_locale}/contact">
<default key="_controller">AcmeDemoBundle:Contact:index</default>
<default key="_locale">en</default>
<requirement key="_locale">en|fr|de</requirement>
</route>
|
1 2 3 4 5 6 7 8 9 10 11 12 | use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('contact', new Route('/{_locale}/contact', array(
'_controller' => 'AcmeDemoBundle:Contact:index',
'_locale' => 'en',
), array(
'_locale' => 'en|fr|de'
)));
return $collection;
|
Quando usar o parâmetro especial _locale numa rota, a localidade encontrada
será automaticamente estabelecida na sessão do usuário. Em outras palavras, se um usuário
visita a URI /fr/contact
, a localidade``fr`` será automaticamente estabelecida
como a localidade para a sessão do usuário.
Você pode agora usar a localidade do usuário para criar rotas para outras páginas traduzidas na sua aplicação.
Pluralização de mensagem é um tópico difícil já que as regras podem ser bem complexas. Por convenção, aqui está a representação matemática das regrad de pluralização Russa:
(($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
Como você viu, em Russo, você pode ter três formas diferentes de plural, cada uma com index de 0, 1 ou 2. Para cada forma, o plural é diferente, e então a tradução também é diferente.
Quando uma tradução tem formas diferentes devido à pluralização, você pode providenciar
todas as formas como string separadas por barra vertical (|
):
'There is one apple|There are %count% apples'
Para traduzir mensagens pluralizadas, use o método:
transChoice()
1 2 3 4 5 | $t = $this->get('translator')->transChoice(
'There is one apple|There are %count% apples',
10,
array('%count%' => 10)
);
|
O segundo argumento (10
neste exemplo), é o número de objetos sendo
descritos e é usado para determinar qual tradução usar e também para preencher
o espaço reservado %count%
.
Baseado em certo número, o tradutor escolhe a forma correta do plural.
Em Inglês, muitas palavras tem uma forma singular quando existe exatamente um objeto
e uma forma no plural para todos os outros números (0, 2, 3...). Então, se count
é
1
, o tradutor usará a primeira string (There is one apple
)
como tradução. Senão irá usar There are %count% apples
.
Aqui está a tradução Francesa:
'Il y a %count% pomme|Il y a %count% pommes'
Mesmo se a string parecer similar (é feita de duas substrings separadas por
barra vertical), as regras Francesas são diferentes: a primeira forma (sem plural) é usada quando
count
is 0
or 1
. Então, o tradutor irá automaticamente usar a
primeira string (Il y a %count% pomme
) quando count
é 0
ou 1
.
Cada localidade tem sua própria lista de regras, com algumas tendo tanto quanto seis formas diferentes de plural com regras complexas por trás de quais números de mapas de quais formas no plural. As regras são bem simples para Inglês e Francês, mas para Russo, você poderia querer um palpite para conhecer quais regras combinam com qual string. Para ajudar tradutores, você pode opcionalmente “atribuir uma tag” a cada string:
'one: There is one apple|some: There are %count% apples'
'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'
As tags são realmente as únicas pistas para tradutores e não afetam a lógica
usada para determinar qual forma plural usar. As tags podem ser qualquer string
descritiva que termine com dois pontos (:
). As tags traduzidas também não são necessariamente a mesma
que as da mensagem original.
A maneira mais fácil de pluralizar uma mensagem é deixar o Symfony2 usar lógica interna
para escolher qual string usar, baseando em um número fornecido. Às vezes, você irá
precisar de mais controle ou querer uma tradução diferente para casos específicos (para
0
, ou quando o contador é negativo, por exemplo). Para certos casos, você pode
usar intervalos matemáticos explícitos:
'{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are many apples'
Os intervalos seguem a notação `ISO 31-11`_. A string acima especifica
quatro intervalos diferentes: exatamente 0
, exatamente 1
, 2-19
, e 20
e mais altos.
Você também pode misturar regras matemáticas explícitas e regras padrão. Nesse caso, se o contador não combinar com um intervalo específico, as regras padrão, terão efeito após remover as regras explícitas:
'{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% apples'
Por exemplo, para 1
maçã, a regra padrão There is one apple
será
usada. Para 2-19
apples, a segunda regra padrão There are %count%
apples
será selecionada.
Uma classe Interval
pode representar um conjunto finito
de números:
{1,2,3,4}
Ou números entre dois outros números:
[1, +Inf[
]-1,2[
O delimitador esquerdo pode ser [
(inclusivo) or ]
(exclusivo). O delimitador
direito pode ser [
(exclusivo) or ]
(inclusivo). Além de números, você
pode usar -Inf
e +Inf
para infinito.
A maior parte do tempo, traduções ocorrem em templates. Symfony2 providencia suporte nativo para ambos os templates PHP e Twig.
Symfony2 providencia tags Twig especializadas (trans
e transchoice
) para
ajudar com tradução de mensagem de blocos estáticos de texto:
1 2 3 4 5 | {% trans %}Hello %name%{% endtrans %}
{% transchoice count %}
{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
{% endtranschoice %}
|
A tag transchoice
automaticamente obtém a variável %count%
do
contexto atual e a passa para o tradutor. Esse mecanismo só funciona
quando você usa um espaço reservado seguindo o padrão %var%
.
Tip
Se você precisa usar o caractere de percentual (%
) em uma string, escape dela ao
dobrá-la: {% trans %}Percent: %percent%%%{% endtrans %}
Você também pode especificar o domínio da mensagem e passar algumas variáveis adicionais:
1 2 3 4 5 6 7 | {% 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} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples
{% endtranschoice %}
|
Os filtros trans
e transchoice
podem ser usados para traduzir textos de
variáveis e expressões complexas:
1 2 3 4 5 6 7 | {{ message | trans }}
{{ message | transchoice(5) }}
{{ message | trans({'%name%': 'Fabien'}, "app") }}
{{ message | transchoice(5, {'%name%': 'Fabien'}, 'app') }}
|
Tip
Usando as tags de tradução ou filtros que tenham o mesmo efeito, mas com uma diferença sutil: saída para escape automático só é aplicada para variáveis traduzidas por utilização de filtro. Em outras palavras, se você precisar estar certo que sua variável traduzida não é uma saída para escape, você precisa aplicar o filtro bruto após o filtro de tradução.
1 2 3 4 5 6 7 8 9 10 11 12 | {# text translated between tags is never escaped #}
{% trans %}
<h3>foo</h3>
{% endtrans %}
{% set message = '<h3>foo</h3>' %}
{# a variable translated via a filter is escaped by default #}
{{ message | trans | raw }}
{# but static strings are never escaped #}
{{ '<h3>foo</h3>' | trans }}
|
New in version 2.1: Agora você pode definir o domínio de tradução para um template Twig inteiro com uma única tag:
1 | {% trans_default_domain "app" %}
|
Note que isso somente influencia o template atual, e não qualquer template “incluído” (para evitar efeitos colaterais).
O serviço tradutor é acessível em templates PHP através do
helper translator
:
1 2 3 4 5 6 7 | <?php echo $view['translator']->trans('Symfony2 is great') ?>
<?php echo $view['translator']->transChoice(
'{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
10,
array('%count%' => 10)
) ?>
|
Quando traduzir a mensagem, Symfony2 usa a localidade do pedido atual
ou a localidade alternativa
se necessária. Você também pode especificar manualmente a
localidade a usar para a tradução:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $this->get('translator')->trans(
'Symfony2 is great',
array(),
'messages',
'fr_FR',
);
$this->get('translator')->trans(
'{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
10,
array('%count%' => 10),
'messages',
'fr_FR',
);
|
Quando a tradução do conteúdo do banco de dados deveria ser manipulada pelo Doctrine através do Translatable Extension. Para mais informações, veja a documentação para aquela biblioteca.
Com o componente Translation do Symfony2, criar uma aplicação internacionalizada não precisa mais ser um processo dolorido e desgastante para somente uns passos básicos:
trans()
ou
transChoice()
;