Como Criar um Aplicativo Multilíngue: Uma Demo Com PHP e Gettext

Se você está construindo um site ou um completo aplicativo web, tornando-a acessível a um público mais amplo, muitas vezes, requer que ele esteja disponível em diferentes idiomas e localidades.

diferenças fundamentais entre a maioria das línguas humanas tornam isso tudo menos fácil. As diferenças nas regras gramaticais, nuances de linguagem, formatos de data e muito mais se combinam para tornar a localização um desafio único e formidável.

considere este exemplo simples.

regras de pluralização em inglês são bastante simples: você pode ter uma forma singular de uma palavra ou uma forma plural de uma palavra.

em outras línguas, porém – como línguas eslavas-existem duas formas plurais além da singular. Você pode até encontrar idiomas com um total de quatro, cinco ou seis formas plurais, como em Esloveno, irlandês ou árabe.

a maneira como seu código é organizado e como seus componentes e interface são projetados, desempenha um papel importante na determinação da facilidade com que você pode localizar seu aplicativo.

internacionalização (i18n) da sua base de código, ajuda a garantir que ela possa ser adaptada a diferentes idiomas ou regiões com relativa facilidade. A internacionalização geralmente é feita uma vez, de preferência no início do projeto para evitar a necessidade de grandes mudanças no código-fonte no futuro.

como criar um Aplicativo Multilíngue: uma demonstração com PHP e Gettext

depois que sua base de código for internacionalizada, a localização (l10n) se torna uma questão de traduzir o conteúdo de seu aplicativo para um idioma/localidade específico.

a localização precisa ser realizada sempre que um novo idioma ou região precisa ser suportado. Além disso, sempre que uma parte da interface (contendo texto) é atualizada, um novo conteúdo fica disponível – o que precisa ser localizado (ou seja, traduzido) para todos os locais suportados.

neste artigo, aprenderemos como internacionalizar e localizar software escrito em PHP. Passaremos pelas várias opções de implementação e pelas diferentes ferramentas disponíveis à nossa disposição para facilitar o processo.

Ferramentas para internacionalização

a maneira mais fácil de internacionalizar o software PHP é usando arquivos array. Os Arrays serão preenchidos com strings traduzidas, que podem ser pesquisadas de dentro dos modelos:

<h1><?=$TRANS?></h1>

esta é, no entanto, dificilmente uma maneira recomendada para projetos sérios, pois definitivamente colocará problemas de manutenção no futuro. Alguns problemas podem até aparecer no início, como a falta de suporte para interpolação de variáveis ou pluralização de substantivos e assim por diante.

uma das ferramentas mais clássicas (muitas vezes tomadas como referência para i18n e l10n) é uma ferramenta Unix chamada Gettext.

embora datando de 1995, ainda é uma ferramenta abrangente para traduzir software que também é fácil de usar. Embora seja muito fácil começar, ele ainda possui ferramentas de suporte poderosas.

Gettext é o que vamos usar neste post. Apresentaremos um ótimo aplicativo GUI que pode ser usado para atualizar facilmente seus arquivos de origem l10n, evitando assim a necessidade de lidar com a linha de comando.

Bibliotecas Para facilitar as Coisas

Principais web PHP frameworks e bibliotecas que suportam Gettext

Existem grandes web PHP frameworks e bibliotecas que suportam Gettext e outras implementações de i18n. Alguns são mais fáceis de instalar do que os outros, ou esporte, ou recursos adicionais de suporte diferentes i18n formatos de arquivo. Embora neste documento, nos concentremos nas ferramentas fornecidas com o núcleo PHP, aqui está uma lista de algumas outras que vale a pena mencionar:

  • oscarotero / Gettext: suporte Gettext com uma interface orientada a objetos; inclui funções auxiliares aprimoradas, Extratores poderosos para vários formatos de arquivo (alguns deles não são suportados nativamente pelo comando gettext). Também pode exportar para formatos além de apenas arquivos. mo/. po, o que pode ser útil se você precisar integrar seus arquivos de tradução em outras partes do sistema, como uma interface JavaScript.

  • symfony / translation: suporta muitos formatos diferentes, mas recomenda usar verbose XLIFF’s. Não inclui funções auxiliares ou um extrator embutido, mas suporta espaços reservados usando strtr() internamente.

  • zend / i18n: Suporta arquivos array e INI ou formatos Gettext. Implementa uma camada de cache para evitar a necessidade de ler o sistema de arquivos Todas as vezes. Também inclui ajudantes de visualização e filtros de entrada e validadores com reconhecimento de localidade. No entanto, não tem extrator de mensagens.

outras estruturas também incluem módulos i18n, mas não estão disponíveis fora de suas bases de código:

  • Laravel: suporta arquivos de matriz básicos; não tem Extrator automático, mas inclui um ajudante @lang para arquivos de modelo.

  • Yii: Suporta array, Gettext e tradução baseada em banco de dados e inclui um extrator de mensagens. Apoiado pela extensão Intl, disponível desde PHP 5.3, e com base no projeto ICU. Isso permite que o Yii execute substituições poderosas, como soletrar números, formatar datas, horários, intervalos, moeda e ordinais.

Se você decidir ir para uma das bibliotecas que fornecem nenhuma extratores, você pode querer usar o Gettext formatos, então você pode usar o original Gettext ferramentas de programação (incluindo o Poedit), conforme descrito no resto do capítulo.

instalando Gettext

você pode precisar instalar Gettext e a biblioteca PHP relacionada usando seu Gerenciador de pacotes, como apt-get ou yum. Depois de instalado, ative-o adicionando extension=gettext.so (Linux/Unix) ou extension=php_gettext.dll (Windows) ao seu arquivo php.ini.

aqui também usaremos o Poedit para criar arquivos de tradução. Você provavelmente o encontrará no Gerenciador de pacotes do seu sistema; ele está disponível para Unix, Mac e Windows e também pode ser baixado gratuitamente em seu site.

tipos de arquivos Gettext

existem três tipos de arquivos com os quais você costuma lidar enquanto trabalha com Gettext.

os principais são os arquivos PO (Portable Object) e MO (Machine Object), sendo o primeiro uma lista de “objetos traduzidos” legíveis e o segundo o binário correspondente (a ser interpretado por Gettext ao fazer a localização). Há também um arquivo POT (po Template), que simplesmente contém todas as chaves existentes de seus arquivos de origem e pode ser usado como um guia para gerar e atualizar todos os arquivos PO.

os arquivos de modelo não são obrigatórios; dependendo da ferramenta que você está usando para fazer l10n, você vai ficar bem com apenas arquivos PO/MO. Você terá um par de arquivos PO / MO por idioma e região, mas apenas um pote por domínio.

separando domínios

existem alguns casos, em grandes projetos, onde você pode precisar separar traduções quando as mesmas palavras transmitem um significado diferente em contextos diferentes.

nesses casos, você precisará dividi-los em diferentes “domínios”, que são basicamente chamados de grupos de arquivos POT/PO/MO, onde o nome do arquivo é o referido domínio de tradução.

projetos de pequeno e médio porte geralmente, por simplicidade, usam apenas um domínio; seu nome é arbitrário, mas usaremos “main” para nossos exemplos de código.

em projetos Symfony, por exemplo, domínios são usados para separar a tradução para mensagens de validação.

código de localidade

uma localidade é simplesmente um código que identifica uma versão de um idioma. É definido seguindo as especificações ISO 639-1 e ISO 3166-1 alpha-2: duas letras minúsculas para o idioma, opcionalmente seguidas por um sublinhado e duas letras maiúsculas que identificam o país ou o código regional.

para idiomas raros, três letras são usadas.

para alguns falantes, a parte do país pode parecer redundante. Na verdade, algumas línguas têm dialetos em diferentes países, como o alemão austríaco (de_at) ou o Português Brasileiro (pt_BR). A segunda parte é usada para distinguir entre esses dialetos-quando não está presente, é considerada uma versão “genérica” ou “híbrida” do idioma.

estrutura de diretórios

para usar Gettext, precisaremos aderir a uma estrutura específica de pastas.Primeiro, você precisará selecionar uma raiz arbitrária para seus arquivos l10n em seu repositório de origem. Dentro dele, você terá uma pasta para cada localidade necessária e uma pasta fixa “LC_MESSAGES” que conterá todos os seus pares PO/MO.

pasta LC_MESSAGES

formas plurais

Como dissemos na introdução, diferentes idiomas podem ostentar diferentes regras de pluralização. No entanto, Gettext nos salva esse problema.

ao criar um novo .arquivo po, você terá que declarar as regras de pluralização para esse idioma, e as peças traduzidas que são sensíveis ao plural terão uma forma diferente para cada uma dessas regras.

ao chamar Gettext no código, você terá que especificar um número relacionado à frase (por exemplo, para a frase “você tem n mensagens.”, você precisará especificar o valor de n), e ele funcionará a forma correta a ser usada – mesmo usando substituição de string, se necessário.

as regras plurais são compostas pelo número de regras necessárias com um teste booleano para cada regra (o teste para no máximo uma regra pode ser omitido). Por exemplo:

  • Japonês: nplurals=1; plural=0; – uma regra: não existem formas plurais

  • inglês: nplurals=2; plural=(n != 1); – duas regras: use a forma plural apenas quando n não é 1, caso contrário, use a forma singular.

  • Português Brasileiro: nplurals=2; plural=(n > 1); – duas regras, use a forma plural somente quando n for maior que 1, caso contrário, use a forma singular.

para uma explicação mais profunda, há um tutorial Informativo do LingoHub disponível online.

Gettext determinará qual Regra usar com base no número fornecido e usará a versão localizada correta da string. Para strings onde a pluralização precisa ser tratada, você precisará incluir no .po arquiva uma frase diferente para cada regra plural definida.

exemplo de implementação

depois de toda essa teoria, vamos ficar um pouco prático. Aqui está um trecho de um.arquivo po (não se preocupe ainda muito com a sintaxe, mas apenas tenha uma noção do conteúdo geral):

msgid ""msgstr """Language: pt_BR\n""Content-Type: text/plain; charset=UTF-8\n""Plural-Forms: nplurals=2; plural=(n > 1);\n"msgid "We're now translating some strings"msgstr "Nós estamos traduzindo algumas strings agora"msgid "Hello %1$s! Your last visit was on %2$s"msgstr "Olá %1$s! Sua última visita foi em %2$s"msgid "Only one unread message"msgid_plural "%d unread messages"msgstr "Só uma mensagem não lida"msgstr "%d mensagens não lidas"

a primeira seção funciona como um cabeçalho, tendo o msgid e msgstr vazio.

descreve a codificação do arquivo, formas plurais e algumas outras coisas. A segunda seção traduz uma string simples do Inglês para o português brasileiro, e a terceira faz o mesmo, mas aproveita a substituição de string de sprintf, permitindo que a tradução contenha o nome de usuário e a data de visita.

a última seção é uma amostra de formas de pluralização, exibindo a versão singular e plural como msgid em inglês e suas traduções correspondentes como msgstr 0 e 1 (seguindo o número dado pela regra plural).

lá, a substituição de string também é usada, então o número pode ser visto diretamente na frase, usando %d. As formas plurais sempre têm dois msgid (singular e plural), por isso é aconselhável não usar uma linguagem complexa como fonte de tradução.

Chaves de localização

como você deve ter notado, estamos usando a frase em inglês real como o ID de origem. Que msgid é o mesmo usado em todo o seu .arquivos po, o que significa que outros idiomas terão o mesmo formato e os mesmos campos msgid, mas traduzidos msgstr linhas.

falando de chaves de tradução, existem duas abordagens “filosóficas” padrão aqui:

1. msgid como uma frase real

as principais vantagens desta abordagem são:

  • se houver partes do software não traduzidas em qualquer idioma, a chave exibida ainda manterá algum significado. Por exemplo, se você souber traduzir do Inglês para o espanhol, mas precisar de Ajuda para traduzir para o francês, poderá publicar a nova página com frases em francês ausentes e partes do site serão exibidas em inglês.

  • é muito mais fácil para o Tradutor entender o que está acontecendo e fazer uma tradução adequada com base no msgid.

  • dá-lhe l10n “livre” para uma língua – a fonte.

por outro lado, a principal desvantagem é que, se você precisar alterar o texto real, precisará substituir o mesmo msgid em vários arquivos de idioma.

2. msgid como uma chave estruturada única

isso descreveria a função de frase no aplicativo de forma estruturada, incluindo o modelo ou parte onde a string está localizada em vez de seu conteúdo.

esta é uma ótima maneira de organizar o código, separando o conteúdo do texto da lógica do modelo. No entanto, isso poderia apresentar problemas ao tradutor que perderia o contexto.

um arquivo de idioma de origem seria necessário como base para outras traduções. Por exemplo, o desenvolvedor idealmente teria um ” en.po “arquivo, que os tradutores leriam para entender o que escrever em” fr.PO”.

traduções ausentes exibiriam chaves sem sentido na tela (“top_menu.bem-vindo “em vez de” Olá, Usuário!”na referida página Francesa Não traduzida).

isso é bom, pois forçaria a tradução a ser concluída antes da publicação-mas ruim, pois os problemas de tradução seriam realmente terríveis na interface. Algumas bibliotecas, no entanto, incluem uma opção para especificar um determinado idioma como “fallback”, tendo um comportamento semelhante ao da outra abordagem.

o manual Gettext favorece a primeira abordagem, pois, em geral, é mais fácil para tradutores e usuários em caso de problemas. Essa é a abordagem que usaremos aqui também.

deve-se notar, no entanto, que a documentação do Symfony favorece a tradução baseada em palavras-chave, para permitir mudanças independentes de todas as traduções sem afetar os modelos também.

uso diário

em um aplicativo comum, você usaria algumas funções Gettext enquanto escrevia texto estático em suas páginas.

essas frases apareceriam então .arquivos po, são traduzidos, compilados em arquivos. mo e, em seguida, usados por Gettext ao renderizar a interface real. Dado isso, vamos amarrar o que discutimos até agora em um exemplo passo a passo:

1. Um exemplo de arquivo de modelo, incluindo algumas chamadas gettext

<?php include 'i18n_setup.php' ?><div> <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1> <!-- code indented this way only for legibility → <?php if ($unread): ?> <h2> <?=sprintf( ngettext('Only one unread message', '%d unread messages', $unread), $unread )?> </h2> <?php endif ?></div><h1><?=gettext('Introduction')?></h1><p><?=gettext('We\'re now translating some strings')?></p>
  • gettext() simplesmente traduz um msgid em seu correspondente msgstr para um determinado idioma. Há também o atalho de função _() que funciona da mesma maneira

  • ngettext() faz o mesmo, mas com a regra do plural

  • Há também dgettext() e dngettext(), que permite a você substituir o domínio de uma única chamada (para mais informações sobre configuração de domínio no seguinte exemplo)

2. Um exemplo de arquivo de configuração (i18n_setup.php como usado acima), selecionar a localidade correta e configurar Gettext

usando Gettext envolve um pouco de um código clichê, mas trata-se principalmente de configurar o diretório locales e escolher parâmetros apropriados (uma localidade e um domínio).

<?php/** * Verifies if the given $locale is supported in the project * @param string $locale * @return bool */function valid($locale) { return in_array($locale, ) && valid($_GET)) { // the locale can be changed through the query-string $lang = $_GET; //you should sanitize this! setcookie('lang', $lang); //it's stored in a cookie so it can be reused} elseif (isset($_COOKIE) && valid($_COOKIE)) { // if the cookie is present instead, let's just keep it $lang = $_COOKIE; //you should sanitize this!} elseif (isset($_SERVER)) { // default: look for the languages the browser says the user accepts $langs = explode(',', $_SERVER); array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ); }); foreach ($langs as $browser_lang) { if (valid($browser_lang)) { $lang = $browser_lang; break; } }}// here we define the global system locale given the found languageputenv("LANG=$lang");// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instancesetlocale(LC_ALL, $lang);// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mobindtextdomain('main', '../locales');// indicates in what encoding the file should be readbind_textdomain_codeset('main', 'UTF-8');// if your application has additional domains, as cited before, you should bind them here as wellbindtextdomain('forum', '../locales');bind_textdomain_codeset('forum', 'UTF-8');// here we indicate the default domain the gettext() calls will respond totextdomain('main');// this would look for the string in forum.mo instead of main.mo// echo dgettext('forum', 'Welcome back!');?>

3. Preparando a tradução para a primeira execução

uma das grandes vantagens que o Gettext tem sobre os pacotes custom framework i18n é seu formato de arquivo extenso e poderoso.

talvez você esteja pensando “Oh cara, isso é muito difícil de entender e editar à mão, uma matriz simples seria mais fácil!”Não se engane, aplicativos como o Poedit estão aqui para ajudar – muito. Você pode obter o programa de seu site, é gratuito e disponível para todas as plataformas. É uma ferramenta muito fácil de se acostumar e muito poderosa ao mesmo tempo – usando todos os recursos que o Gettext tem disponível. Vamos trabalhar aqui com a versão mais recente, Poedit 1.8.

vista dentro do Poedit.

Na primeira execução, você deve selecionar “Arquivo > Novo…” no menu. Você será solicitado para o idioma; selecione / filtre o idioma para o qual deseja traduzir ou use o formato mencionado anteriormente, como en_US ou pt_BR.

selecionando o idioma.

Agora, salve o arquivo-usando essa estrutura de diretório que mencionamos também. Em seguida, você deve clicar em “Extrair de fontes” e aqui você configurará várias configurações para as tarefas de extração e tradução. Você poderá encontrar todos eles mais tarde por meio de “Catalog > Properties”:

  • caminhos de origem: Inclua todas as pastas do projeto onde gettext() (e irmãos) são chamados-isso geralmente é sua(s) Pasta (s) de modelos/visualizações. Esta é a única configuração obrigatória.

  • propriedades de Tradução:

    • nome e versão do projeto, equipe e endereço de E-mail da equipe: informações úteis que vão no .cabeçalho do arquivo po.
    • formas plurais: estas são as regras que mencionamos antes. Você pode deixá-lo com a opção padrão na maioria das vezes, pois o Poedit já inclui um banco de dados útil de regras plurais para muitos idiomas.
    • Charsets: UTF-8, de preferência.
    • charset de código-fonte: o charset usado por sua base de código – provavelmente UTF – 8 também, certo?
  • palavras-chave de origem: o software subjacente sabe como gettext() e chamadas de função semelhantes parecem em várias linguagens de programação, mas você também pode criar suas próprias funções de tradução. Será aqui que você adicionará esses outros métodos. Isso será discutido mais adiante na seção” Dicas”.

depois de definir essas propriedades, o Poedit executará uma varredura em seus arquivos de origem para encontrar todas as chamadas de localização. Após cada verificação, o Poedit exibirá um resumo do que foi encontrado e do que foi removido dos arquivos de origem. Novas entradas estarão vazias na tabela de tradução, permitindo que você insira as versões localizadas dessas strings. Salve-o e um arquivo. mo será (re)compilado na mesma pasta e, presto!, seu projeto é internacionalizado!

projeto internacionalizado.

o Poedit também pode sugerir traduções comuns da web e de arquivos anteriores. É útil, então você só precisa verificar se eles fazem sentido e aceitá-los. Se você não tiver certeza sobre uma tradução, poderá marcá-la como difusa e ela será exibida em amarelo. Entradas azuis são aquelas que não têm tradução.

4. Traduzindo strings

como você deve ter notado, existem dois tipos principais de strings localizadas: simples e aquelas com formas plurais.

os simples têm apenas duas caixas: fonte e string localizada. A string de origem não pode ser modificada, já que Gettext/Poedit não inclui a capacidade de alterar seus arquivos de origem; em vez disso, você precisará alterar a própria fonte e verificar novamente os arquivos. (Dica: Se você clicar com o botão direito em uma linha de Tradução, ela exibirá uma dica com os arquivos de origem e as linhas onde essa string está sendo usada.)

as strings de formulário Plural incluem duas caixas para mostrar as duas strings de origem e guias para que você possa configurar as diferentes formas finais.

configurando formulários finais.

exemplo de uma string com uma forma plural no Poedit, mostrando uma guia de tradução para cada um.

sempre que você alterar seus arquivos de código-fonte e precisar atualizar as traduções, basta clicar em Atualizar e o Poedit irá verificar novamente o código, removendo entradas inexistentes, mesclando as que mudaram e adicionando novas.

Poedit também pode tentar adivinhar algumas traduções, com base em outras que você fez. Essas suposições e as entradas alteradas receberão um marcador “difuso”, indicando que precisam de revisão, exibido em amarelo na lista.

também é útil se você tiver uma equipe de tradução e alguém tentar escrever algo sobre o qual não tem certeza: basta marcá-lo confuso e outra pessoa irá revisá-lo mais tarde.

Finalmente, é aconselhável deixar” ver > entradas não traduzidas primeiro ” marcado, pois ajudará você a evitar esquecer quaisquer entradas. Nesse menu, você também pode abrir partes da interface do usuário que permitem que você deixe informações contextuais para tradutores, se necessário.

dicas &Truques

os servidores da Web podem acabar armazenando em cache seus arquivos. mo.

se você estiver executando o PHP como um módulo no Apache( mod_php), poderá enfrentar problemas com o arquivo .mo sendo armazenado em cache. Acontece na primeira vez que é lido e, para atualizá-lo, pode ser necessário reiniciar o servidor.

no Nginx e PHP5, geralmente são necessárias apenas algumas atualizações de página para atualizar o cache de tradução e, no PHP7, raramente é necessário.

bibliotecas fornecem funções auxiliares para manter o código de localização curto.

como preferido por muitas pessoas, é mais fácil usar _() em vez de gettext(). Muitas bibliotecas i18n personalizadas de frameworks usam algo semelhante a t() também, para tornar o código traduzido mais curto. No entanto, essa é a única função que ostenta um atalho.

Você pode querer adicionar em seu projeto de alguns outros, como __() ou _n() para ngettext(), ou talvez uma fantasia _r() que gostaria de participar do gettext() e sprintf() chamadas. Outras bibliotecas, como Gettext do oscarotero, também fornecem funções auxiliares como essas.

nesses casos, você precisará instruir o utilitário Gettext sobre como extrair as strings dessas novas funções. Não tenha medo, é muito fácil. É apenas um campo no .arquivo po ou uma tela de Configurações no Poedit (no editor, essa opção está dentro de “Catalog > Properties > Sources keywords”).

lembrar: Gettext já conhece as funções padrão para muitos idiomas, portanto, não se preocupe se essa lista parecer vazia. Você precisa incluir na lista as especificações das novas funções, a seguir este formato específico:

  • Se você criar algo como t(), que simplesmente retorna a tradução de uma seqüência de caracteres, você pode especificar como t. Gettext saberá que o único argumento de função é a string a ser traduzida;

  • se a função tiver mais de um argumento, você pode especificar em qual é a primeira string e, se necessário, a forma plural também. Por exemplo, se nossa assinatura de função for __('one user', '%d users', $number), a especificação seria __:1,2, o que significa que a primeira forma é o primeiro argumento e a segunda forma é o segundo argumento. Se o seu número vier como o primeiro argumento, a especificação seria __:2,3, indicando que a primeira forma é o segundo argumento e assim por diante.

depois de incluir essas novas regras no .arquivo po, uma nova varredura trará suas novas strings com a mesma facilidade de antes.

torne seu aplicativo PHP multilíngue com Gettext

Gettext é uma ferramenta muito poderosa para internacionalizar seu projeto PHP. Além de sua flexibilidade que permite suporte para um grande número de linguagens humanas, seu suporte para mais de 20 linguagens de programação permite que você transfira facilmente seu conhecimento de usá-lo com PHP para outras linguagens como Python, Java ou C#.Além disso, o Poedit pode ajudar a suavizar o caminho entre o código e as strings traduzidas, tornando o processo mais simples e fácil de seguir. Ele também pode agilizar os esforços de tradução compartilhados com sua integração Crowdin.Sempre que possível, considere outros idiomas que seus usuários possam falar. Isso é importante principalmente para projetos não ingleses: você pode aumentar o acesso do Usuário se liberá-lo em inglês e em seu idioma nativo.

claro, nem todos os projetos precisam de internacionalização, mas é muito mais fácil iniciar o i18n durante a infância de um projeto, mesmo que não seja inicialmente necessário, do que fazê-lo mais tarde no futuro, caso se torne um requisito. E, com ferramentas como Gettext e Poedit, é mais fácil do que nunca.

relacionado: Introdução ao PHP 7: O que há de novo e o que se foi

Leave a Reply

O seu endereço de email não será publicado.