Internacionalização, um CakePHP Multi-idiomas Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 4 anos, 8 meses atrás.

Para quem nunca tentou criar um aplicativo multi-idiomas e não travou em algum momento, uma salva de palmas, é uma tarefa para poucos, já que não é só na programação que precisa ser um ninja, mas ter algum conhecimento de outros idiomas (é... o Google Translate não basta, não é só traduzir, tem que escrever para aquele país) e saber como funcionam as datas, moedas e até mesmo o horário local em cada lugar é de vital importância para uma internacionalização de qualidade, trocando em miúdos, o que torna uma "tradução" em um "um site multi-idiomas" é nada mais nada menos que cultura.

Pelo menos com a parte da programação, não precisamos nos preocupar, o Cake já faz tudo, é só saber aonde mexer, o resto você pesquisa na Google, combinado?

Preparando a tradução no CakePHP

Antes de começar a sair por ai traduzindo a torto e a direito, precisamos que o CakePHP reconheça os textos que queremos que sejam internacionalizados, e isso é fácil:

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

__('Eu não falo inglês muito bem!')

É só isso? Bom, é um começo, mas tem mais, sim, veremos isso a frente.

Sempre que você escrever algo que precisa ser traduzido, tando no seu Model, Controller ou View, escreva desta forma (dois underlines seguidos do termo entre parenteses), lembrando que na View você precisa colocar entre as tags do php e dar um echo para aparecer:

<?php echo __('Eu não falo inglês muito bem!');  ?>

E isso não se resumo as views apenas, mas em qualquer lugar que caiba um texto a ser exibido, quer ver um exemplo, imagine um Controller "Paginas" com a action mostrar e que irá exibir uma página de um site caso ela exista no banco de dados, caso contrário:

$this->Session->setFlash('Está pagina não existe!');

Para que o CakePHP entenda que aqui tem algo a ser traduzido, apenas coloque como já vimos:

$this->Session->setFlash(__('Está pagina não existe!'));

É realmente muito simples, então vamos dar um passo além.

Passando variáveis

Existem casos em que você precisa passar uma variável ali no meio, como por exemplo:

__('Você tem 4 produtos no seu carrinho de compras')

Note que 4 é algo variável, você precisa que o Cake conte quantos produtos tenha no carrinho e adicione alí, isto é muito simples de se fazer na verdade:

__('Você tem %s produtos no seu carrinho de compras', array(4))

Entendeu? Não! Então vou explicar: Troquei o 4 por %s e isso faz com que o Cake saiba que alí será passada uma variável, então informei que o valor de %s é 4 no array do segundo parâmetro, que poderia ser algo vindo do controller: __('Você tem %s produtos no seu carrinho de compras',array($qtdprodutoscarrinho))

É realmente muito simples, então vou complicar mais ainda:

__('Erik Figueiredo, você tem 4 produtos no seu carrinho de compras')

Agora temos duas variáveis, uma que exibirá o Usuário atual (que é Erik Figueiredo) e outra que exibirá a quantidade de produtos (que é 4).

__('%s, você tem %s produtos no seu carrinho de compras',array('Erik Figueiredo',4))

Fazendo assim, na ordem em que colocou no texto e for adicionando na mesma ordem no array, você pode ter quantas variáveis precisar alí, 3, 4, 5... entendeu. O mesmo funciona para plugins, porém, mantendo o padrão __d('Eu não falo inglês muito bem!'), ou seja, adicionando um d na string.

Criando os arquivos de tradução

O CakePHP organiza suas traduções dentro da pasta Locale (app/Locale) através de arquivos .po, mas calma lá que ele já cria esses arquivos pra gente. Para gerar estes arquivos, é só dar um comando no console.

Aqui um artigo sobre configurar o console do CakePHP no Windows, Linux ou Mac na documentação oficial (em inglês, mas facilmente compreendido com GoogleTranslator)

Console/cake i18n extract --extract-core no

Lembrando que para Windows usamos a barra invertida "". Isso vai fazer com que o Cake navegue por todos os arquivos do seu aplicativo extraindo todas as linhas __() em um arquivo .pot (que será transformado em .po)  na pasta Locale, porém excluindo as bibliotecas e mensagens padrões do CakePHP, porque vou passar isso já pronto pra vocês, mas se quiserem também o Core do Cake, é só fazer assim:

Console/cake i18n extract

Ou seja, remova o --extract-core que já resolve.

De qualquer forma ele vai fazer uma série de perguntas (aqui você tem que ter um banco de dados configurado, caso contrário ele vai tentar configurar um pra você).

  1. A primeira pergunta quer o caminho da pasta app, se o exemplo entre colchetes '[]' estiver correto confirme com enter
  2. A segunda pergunta que saber aonde fica a pasta Locale, novamente se o que estiver entre colchetes '[]' estiver correto, apenas confirme.
  3. E a terceira quer saber se você quer todas as strings do aplicativo no arquivo default.pot, digite y e confirme.

Pronto, ele vai gerar seu arquivo de tradução, note que sempre que executar uma alteração, você deve voltar aqui e executar a shell novamente.

Traduzindo a aplicação CakePHP

Ok, já temos nossos textos corretamente escritos e nosso arquivo .pot com todas as traduções, agora vamos realmente traduzir nossa aplicação, mas tem um porém, os arquivos .pot não são usados pelo CakePHP (????), calma lá, vou explicar, na verdade ele é um modelo para ser traduzido, um arquivo editável, assim como .fla é do Flash, que no fim vira .swf (ta bom, essa eu puxei do fundo do baú) ou um arquivo psd que posteriormente virá a ser um jpeg (ou outro zilhão de extensões).

Antes de mais nada crie uma estrutura de pastas desta forma:

app/Locale/<locale>/LC_MESSAGES/

Sendo que será o código do país de acordo com o padrão iso-639 (de 3 letras - veja a lista completa no site da W3C) que por sinal é um padrão obsoleto, mas o CakePHP também trabalha com o locales mais atuais (achei esse link na internet, quem tiver outro mais confiável pode enviar). Dentro da pasta LC_MESSAGES, vai o arquivo .po que vamos criar abaixo.

Existem duas formas de criar um arquivo .po:

Usando seu editor/ide favorito

Basta abrir seu arquivo default.pot no seu editor favorito, traduzir e salvar como arquivo default.po, seguindo o seguinte padrão:

msgid  "Mensagem original."
msgstr "Mensagem traduzida."

Exemplo:

msgid "I don't speak english very well!"
msgstr "Eu não falo inglês muito bem!"

Um detalhe é que desta forma você pode editar o próprio arquivo .po em vez do pot, porém isso aumenta a chance de alguma coisa dar errada, você esquecer uma aspa dupla, trocar por simples, salvar em outro tipo de codificação (se não sabe o que é codificação de arquivo, pare de ler agora mesmo e estude isso), porém eu garanto que com um pouco de atenção, Dreamweaver (ta... peguei pesado) e paciência da pra fazer sim.

Usando o PoEdit

Então baixe o programa que vamos usar para traduzir, ele não tem nem 6mb.

Após instalar, abra o arquivo com ele (você é programador, imagino que não precise que eu ensine isso), logo de cara tem a lista de linhas a traduzir, basta clicar na que quer e em baixo tem Texto fonte e Tradução (em baixo, não em cima), clique nem Tradução e ... bem... traduza, quando terminar vá em Arquivo/Salvar Como e salve como default.po na pasta que criamos acima.

Definindo o idioma

Como você deve ser uma pessoa estudada em diversos idiomas já deve ter traduzido pelo menos para umas 8 línguas o seu app, agora como definimos o idioma a ser usado? Fácil:

Configure::write('Config.language', 'por');

Isso no seu app/Config/core.php ou bootstrap.php (embora possa ser usado em qualquer lugar, aqui é o mais indicado), lembrando que já vem como padrão o inglês.

Alterando o idioma em tempo de execução

Agora, se você precisa alterar o idioma do seu site:

  • Pelo idioma padrão do navegador
  • Pelo subdomínio (exemplo: eng.seusite.com.br, por.seusite.com.br...)
  • Por um prefixo de url (como na documentação do CakePHP)
  • Por um sistema de geolocalização através de ip
  • Outras maneiras que não pensei

Você pode usar seção entro de qualquer controller ou action, mas imagino que vá usar no beforeFilter do seu appController, claro:

$this->Session->write('Config.language', 'por');

Note que o valor passado foi 'por' de português. Obs.: Penso em dar um exemplo disso na série de tutoriais sobre como criar um CMS, traduzindo nosso painel de controle através de um botão que armazena um valor na seção e através do idioma do navegador.

Core do CakePHP em português do Brasil

Como prometido, aqui segue o core do CakePHP traduzido no nosso idioma. É só salvar o arquivo em:

app/Locale/<locale>/LC_MESSAGES/core.po

E caso queria usar em outros idiomas você pode copiar e traduzir para cada um (ou pesquisar no Github ou Google, hehehe).

Considerações importantes

  1. Para realmente internacionalizar sua aplicação você ainda precisar ver coisas como datas e moeda local, eu já escrevi aqui como usar um Behavior para traduzir e organizar as datas automaticamente, quem sabe eu escreva algo sobre moedas também.
  2. Ainda vão existir informações no Banco de dados a se traduzir, e isso é feito com o TranslateBehavior, mas isso é assunto para outro artigo.
  3. Esta técnica foi feita para se traduzir pequenos trechos de texto e não é interessante para muitos parágrafos, para isso existe outra técnica (também usada na documentação do CakePHP) que é a de traduzir os arquivos da View inteirinhos, veja:

Como traduzir vários parágrafos de texto

Se você tem View/Paginas/empresa.ctp, para criar a versão em Francês, basta criar uma pasta fra em View, assim:

View/fra/Paginas/empresa.ctp

E no beforeFilter (no appController ou no controller atual que no nosso exemplo é PaginasController), mas não use ainda, continue lendo:

public function beforeFilter() {
    $locale = Configure::read('Config.language');
    if ($locale && file_exists(VIEWS . $locale' . DS . $this->viewPath)) {
        $this->viewPath = $locale . DS . $this->viewPath;
    }
}

Aqui vem uma correção pra quem usou o código acima ou direto da documentação do CakePHP, a constant VIEWS do código acima não existe mais na versão 2.x, o correto é usar: APP."Views", deve ficar assim:

public function beforeFilter() { $locale = Configure::read('Config.language'); if ($locale && file_exists(APP."Views".DS. $locale . DS . $this->viewPath)) { $this->viewPath = $locale . DS . $this->viewPath; } }

Com isso você traduz uma view inteira, ou todas as views de um controller, ou todas as views de todos os controllers, ou... ta bom.. você entendeu. Qualquer dúvida sobre o assunto é só perguntar nos comentários.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!