Documentação do Symfony2
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:
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:
# app/config/config.yml
framework:
translator: { fallback: en }
<!-- app/config/config.xml -->
<framework:config>
<framework:translator fallback="en" />
</framework:config>
// 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 (Symfony\Component\Translation\Translator). Para traduzir um bloco de texto (chamado de messagem), use o método :method:`Symfony\\Component\\Translation\\Translator::trans`. Suponhamos, por exemplo, que estamos traduzindo uma simples mensagem de dentro do controller:
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:
<!-- 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>
// messages.fr.php
return array(
'Symfony2 is great' => 'J\'aime Symfony2',
);
# 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:
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:
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”:
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:
<!-- 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>
// messages.fr.php
return array(
'Hello %name%' => 'Bonjour %name%',
);
# 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:
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:
php app/console cache:clear
O Symfony2 procura por arquivos de mensagem (em outras palavras, traduções) nas seguintes localizações:
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:
O carregador poder ser o nome de qualquer carregador registrado. Por padrão, Symfony providencia os seguintes carregadores:
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 Symfony\Component\Translation\Loader\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):
<!-- 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>
// src/Acme/DemoBundle/Resources/translations/messages.fr.php
return array(
'Symfony2 is great' => 'J\'aime Symfony2',
'symfony2.great' => 'J\'aime Symfony2',
);
# 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:
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():
$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`:
// 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.
$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:
# app/config/config.yml
framework:
default_locale: en
<!-- app/config/config.xml -->
<framework:config>
<framework:default-locale>en</framework:default-locale>
</framework:config>
// 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:
contact:
pattern: /{_locale}/contact
defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
requirements:
_locale: en|fr|de
<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>
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: :method:`Symfony\\Component\\Translation\\Translator::transChoice`
$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 Symfony\Component\Translation\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:
{% 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:
{% 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:
{{ 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.
{# 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:
{% 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:
<?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:
$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: