Como construir um aplicativo multilíngue: uma demonstração com PHP e Gettext

Publicados: 2022-03-11

Esteja você criando um site ou um aplicativo da Web completo, torná-lo acessível a um público mais amplo geralmente exige que ele esteja disponível em diferentes idiomas e localidades.

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

Considere este exemplo simples.

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

Em outras línguas, porém – como as 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 desempenham um papel importante na determinação da facilidade com que você pode localizar seu aplicativo.

A internacionalização (i18n) de 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, preferencialmente no início do projeto para evitar a necessidade de grandes mudanças no código-fonte no futuro.

Como construir um aplicativo multilíngue: uma demonstração com PHP e Gettext

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

A localização precisa ser executada toda vez 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 se torna disponível - que precisa ser localizado (ou seja, traduzido) para todas as localidades suportadas.

Neste artigo, aprenderemos como internacionalizar e localizar software escrito em PHP. Passaremos pelas várias opções de implementação e pelas diferentes ferramentas que estão à 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 consultadas nos templates:

 <h1><?=$TRANS['title_about_page']?></h1>

No entanto, essa não é uma maneira recomendada para projetos sérios, pois definitivamente apresentará problemas de manutenção no futuro. Alguns problemas podem até aparecer logo 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 tida como referência para i18n e l10n) é uma ferramenta Unix chamada Gettext.

Embora remonte a 1995, ainda é uma ferramenta abrangente para tradução de 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 usaremos 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 estruturas e bibliotecas da Web PHP que suportam Gettext

Existem os principais frameworks e bibliotecas da Web PHP que suportam Gettext e outras implementações de i18n. Alguns são mais fáceis de instalar do que outros, ou possuem recursos adicionais ou suportam diferentes formatos de arquivo i18n. 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 a Gettext com interface orientada a objetos; inclui funções auxiliares aprimoradas, extratores poderosos para vários formatos de arquivo (alguns deles nã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 o uso de XLIFFs detalhados. 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 auxiliares de visualização e filtros e validadores de entrada com reconhecimento de localidade. No entanto, ele não possui extrator de mensagens.

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

  • Laravel: Suporta arquivos array básicos; não tem extrator automático, mas inclui um auxiliar @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 o PHP 5.3, e baseado no projeto ICU. Isso permite que o Yii execute substituições poderosas, como soletrar números, formatar datas, horas, intervalos, moeda e ordinais.

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

Instalando o Gettext

Você pode precisar instalar Gettext e a biblioteca PHP relacionada usando seu gerenciador de pacotes, como apt-get ou yum. Depois de instalado, habilite-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; 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 pelo 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 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ê ficará bem com apenas arquivos PO/MO. Você terá um par de arquivos PO/MO por idioma e região, mas apenas um POT por domínio.

Separando Domínios

Existem alguns casos, em grandes projetos, em que você pode precisar separar as traduções quando as mesmas palavras transmitem significados diferentes em contextos diferentes.

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

Projetos de pequeno e médio porte costumam, por simplicidade, usar 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 de acordo com 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 identificando o país ou código regional.

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

Para alguns falantes, a parte do país pode parecer redundante. De fato, algumas línguas possuem 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 esses dialetos - quando não está presente, é tomada como uma versão “genérica” ou “híbrida” do idioma.

Estrutura de diretórios

Para usar o 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 poupa desse problema.

Ao criar um novo arquivo .po, você terá que declarar as regras de pluralização para esse idioma, e as partes traduzidas que diferenciam o plural terão uma forma diferente para cada uma dessas regras.

Ao chamar Gettext em 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 isso funcionará na forma correta usar - 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; nplurals=1; plural=0; - uma regra: não há formas plurais

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

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

Para uma explicação mais profunda, há um tutorial informativo 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 em que a pluralização precisa ser tratada, você precisará incluir no arquivo .po uma frase diferente para cada regra de plural definida.

Implementação de Amostra

Depois de toda essa teoria, vamos ser um pouco práticos. Aqui está um trecho de um arquivo .po (não se preocupe 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 "Nos estamos traduzindo algumas strings agora" msgid "Hello %1$s! Your last visit was on %2$s" msgstr "Ola %1$s! Sua ultima visita foi em %2$s" msgid "Only one unread message" msgid_plural "%d unread messages" msgstr[0] "So uma mensagem nao lida" msgstr[1] "%d mensagens nao lidas"

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

Ele descreve a codificação do arquivo, formas plurais e algumas outras coisas. A segunda seção traduz uma string simples de inglês para português do Brasil, 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 do plural).

Lá, a substituição de string também é usada, para que o número possa 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 um idioma complexo 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 da fonte. Esse msgid é o mesmo usado em todos os seus arquivos .po, o que significa que outros idiomas terão o mesmo formato e os mesmos campos msgid , mas as linhas msgstr traduzidas.

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

1. msgstr como uma sentença 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ê sabe traduzir do inglês para o espanhol, mas precisa de ajuda para traduzir para o francês, pode 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á a você l10n “grátis” para um idioma - o de origem.

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. msgstr como uma chave estruturada exclusiva

Isso descreveria a função da frase no aplicativo de maneira estruturada, incluindo o modelo ou parte em que a string está localizada em vez de seu conteúdo.

Essa é 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 para o 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 arquivo “en.po”, que os tradutores leriam para entender o que escrever em “fr.po”.

Traduções ausentes exibiriam teclas sem sentido na tela (“top_menu.welcome” 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, entretanto, 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 templates também.

Uso diário

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

Essas frases apareceriam em arquivos .po, seriam traduzidas, compiladas em arquivos .mo e usadas por Gettext ao renderizar a interface real. Dado isso, vamos juntar o que discutimos até agora em um exemplo passo a passo:

1. Um arquivo de modelo de amostra, incluindo algumas chamadas gettext diferentes

 <?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 msgstr correspondente para um determinado idioma. Há também a função abreviada _() que funciona da mesma maneira

  • ngettext() faz o mesmo, mas com regras no plural

  • Há também dgettext() e dngettext() , que permitem substituir o domínio para uma única chamada (mais sobre configuração de domínio no próximo exemplo)

2. Um arquivo de configuração de amostra (i18n_setup.php conforme usado acima), selecionando a localidade correta e configurando Gettext

Usar Gettext envolve um pouco de código clichê, mas trata-se principalmente de configurar o diretório de localidades e escolher os 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, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es'); } //setting the source/default locale, for informational purposes $lang = 'en_US'; if (isset($_GET['lang']) && valid($_GET['lang'])) { // the locale can be changed through the query-string $lang = $_GET['lang']; //you should sanitize this! setcookie('lang', $lang); //it's stored in a cookie so it can be reused } elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) { // if the cookie is present instead, let's just keep it $lang = $_COOKIE['lang']; //you should sanitize this! } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // default: look for the languages the browser says the user accepts $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); 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 language putenv("LANG=$lang"); // this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instance setlocale(LC_ALL, $lang); // this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mo bindtextdomain('main', '../locales'); // indicates in what encoding the file should be read bind_textdomain_codeset('main', 'UTF-8'); // if your application has additional domains, as cited before, you should bind them here as well bindtextdomain('forum', '../locales'); bind_textdomain_codeset('forum', 'UTF-8'); // here we indicate the default domain the gettext() calls will respond to textdomain('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 de estrutura personalizada 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 - e muito. Você pode obter o programa no site deles, é gratuito e está 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. Estaremos trabalhando aqui com a última versão, Poedit 1.8.

Veja o interior do Poedit.

Na primeira execução, você deve selecionar “Arquivo > Novo…” no menu. Você será solicitado pelo 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órios que mencionamos também. Em seguida, você deve clicar em “Extrair das fontes”, e aqui você definirá várias configurações para as tarefas de extração e tradução. Você poderá encontrá-los posteriormente em “Catálogo > Propriedades”:

  • Caminhos de origem: Inclua todas as pastas do projeto onde gettext() (e irmãos) são chamados - geralmente são suas pastas de templates/views. 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 anteriormente. 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.
    • Conjunto de caracteres do código-fonte: O conjunto de caracteres 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 aparecem 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 tarde 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 varredura, 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 pronto!, seu projeto está 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, você pode marcá-la como Fuzzy e ela será exibida em amarelo. As 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: as 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, pois 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 do mouse em uma linha de tradução, ela exibirá uma dica com os arquivos de origem e as linhas em que essa string está sendo usada.)

As strings de forma 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 string com forma plural no Poedit, mostrando uma aba 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á redigitalizar o código, removendo entradas inexistentes, mesclando as que foram alteradas e adicionando novas.

O Poedit também pode tentar adivinhar algumas traduções, com base em outras que você fez. Esses palpites e as entradas alteradas receberão um marcador “Fuzzy”, indicando que precisam de revisão, exibidos 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 marcar como Fuzzy e alguém o revisará mais tarde.

Por fim, é aconselhável deixar marcado “Visualizar > Entradas não traduzidas primeiro”, pois isso ajudará a evitar o esquecimento de entradas. Nesse menu, você também pode abrir partes da interface do usuário que permitem deixar informações contextuais para tradutores, se necessário.

dicas e truques

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

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. Isso acontece na primeira vez que é lido e, para atualizá-lo, talvez seja necessário reiniciar o servidor.

No Nginx e no 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.

As 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 também usam algo semelhante a t() , para tornar o código traduzido mais curto. No entanto, essa é a única função que possui um atalho.

Você pode querer adicionar em seu projeto alguns outros, como __() ou _n() para ngettext() , ou talvez um _r() sofisticado que juntaria as chamadas gettext() e sprintf() . Outras bibliotecas, como a 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 fica dentro de “Catálogo > Propriedades > Palavras-chave de Fontes”).

Lembre-se: Gettext já conhece as funções padrão para muitos idiomas, então não se preocupe se essa lista parecer vazia. Você precisa incluir nessa lista as especificações das novas funções, seguindo este formato específico:

  • Se você criar algo como t() , que simplesmente retorna a tradução para uma string, você pode especificá-la como t . Gettext saberá que o único argumento da função é a string a ser traduzida;

  • Se a função tiver mais de um argumento, você pode especificar em qual deles está 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 , significando que o primeiro formulário é o primeiro argumento e o segundo formulário é 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 tão facilmente quanto 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 uso com PHP para outras linguagens como Python, Java ou C#.

Além disso, o Poedit pode ajudar a facilitar o caminho entre o código e as strings traduzidas, tornando o processo mais direto e fácil de seguir. Ele também pode simplificar os esforços de tradução compartilhada com sua integração Crowdin.

Sempre que possível, considere outros idiomas que seus usuários possam falar. Isso é importante principalmente para projetos que não sejam em inglês: você pode aumentar o acesso do usuário se liberá-lo em inglês, bem como em seu idioma nativo.

É claro que 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 posteriormente, 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 acabou