Conteúdo


O que há de novo em Unicode em PHP V5.3?

Suporte de i18N e Unicode

Comments

A Web é uma plataforma ideal para desenvolver aplicativos e serviços com alcance mundial. Para criar um aplicativo que tenha verdadeiro apelo global, é necessário adaptá-lo ao processo e exibir dados em diversos idiomas e sistemas de escrita.

Um aplicativo é adaptado para outro idioma em diversas fases, a primeira das quais é a chamada internacionalização, frequentemente abreviada como i18n. O objetivo da internacionalização é garantir que os usuários possam utilizar seu idioma e notações no aplicativo, incluindo caracteres especiais para entrada e exibição de dados, exibindo números e datas no formato adequado e classificando listas de acordo com as regras específicas do idioma.

A abordagem mais avançada também inclui localização (abreviada como l10n). Durante a localização, o aplicativo é adaptado para dar suporte a hábitos culturais, linguísticos e locais específicos. Esse processo envolve tradução para o idioma local; a configuração adequada de formatos de datas, números e moedas; regras de classificação; etc.

Este artigo apresenta os novos recursos do PHP V5.3 que aperfeiçoam sua habilidade de criar aplicativos internacionalizados em PHP. O artigo não lida com o problema da localização em geral — especialmente com tradução; essa tarefa é mais bem abordada por bibliotecas PHP adicionais, como gettext do GNU (consulte Recursos).

Suporte a Unicode em PHP

Um aplicativo adequadamente internacionalizado deve ser capaz de processar dados escritos em diferentes sistemas de escrita. Inglês e outros idiomas usados na Europa Ocidental são baseados no sistema de script latino e usam somente caracteres latinos — algumas vezes com acentos adicionados (sinais diacríticos). Conforme avançamos para o oriente, encontramos os alfabetos cirílicos, os sistemas hebraico e árabe no Oriente Médio e diversos alfabetos indianos. Então há o chinês, o japonês e uma dúzia de outros sistemas de script orientais. A maioria dos sistemas de caracteres usados mais ou menos comumente estão inclusos no conjunto de caracteres Unicode (consulte Recursos para obter mais informações).

Entretanto, os caracteres Unicode são apenas abstrações. Sistemas de computador precisam codificar caracteres Unicode quando armazenados na memória ou em disco ou quando transferidos pela rede. Diversas codificações são usadas para Unicode: as duas mais populares são UTF-8 e UTF-16. Ambientes de desenvolvimento modernos como a tecnologia Java™ e o Microsoft® .NET Framework usam Unicode e possuem tipos de dados para caracteres e sequências de caracteres Unicode. Trabalhar com texto que usa caracteres Unicode é, então, completamente transparente para os desenvolvedores. É responsabilidade das funções da biblioteca lidar corretamente com todas as entradas e saídas (UI, formulários HTML, o banco de dados, XML) e, se necessário, transformá-las para codificação interna usada para a representação de sequências de caracteres Unicode.

Infelizmente, a linguagem PHP ainda carece de suporte Unicode adequado. Embora desenvolvedores de PHP central estejam pensando sobre adicionar suporte Unicode ao PHP desde 2001, nem sequer PHP V5.3 o inclui. Entretanto, esse suporte está planejado para o próximo importante release — PHP V6.

Superando o suporte Unicode ausente em PHP

A falta de suporte Unicode em PHP é desagradável, mas há soluções alternativas que permitem desenvolver aplicativos internacionalizados adequados mesmo em PHP. O primeiro problema que precisa ser solucionado é a representação adequada de dados Unicode. PHP usa as chamadas cadeias binárias de caracteres— em PHP, uma cadeia de caracteres não é uma sequência de caracteres Unicode, mas uma sequência de bytes. É possível armazenar internamente todas as sequências de caracteres em codificação UTF-8 e certificar-se de que todas as entradas e saídas do script sejam adequadamente codificadas e decodificadas.

Em teoria, é possível usar outras codificações além de UTF-8, mas UTF-8 cria menos problema que os outros sistemas. Muitas bibliotecas PHP já esperam que sequências de caracteres sejam codificadas em UTF-8, incluindo todas as funções que funcionam com XML e a recentemente adicionada biblioteca intl. Para trabalhar de maneira mais tranquila com sequências de caracteres codificadas em UTF-8, é melhor codificar caracteres em UTF-8 e enviar saída de scripts em UTF-8.

Ainda assim, transformar tudo em UTF-8 não resolve nada. Se você codificar um caractere latino com um acento ou caractere não latino em UTF-8, obterá dois, três ou quatro bytes, o que confunde as funções de cadeia de caracteres do PHP que calculam o comprimento da sequência de caracteres ou trabalham com subsequências de caracteres. A Listagem 1 demonstra esse problema.

Listagem 1. Problemas relacionados a suporte inadequado a Unicode em PHP
<?php

Header("Content-type: text/plain;charset=utf-8");
 
$text["en"] = "The Hitchhiker's Guide to the Galaxy";
$text["es"] = "Guía del autoestopista galáctico";
$text["cs"] = "Stopařův průvodce po Galaxii";
$text["ru"] = "Путеводитель хитч-хайкера по Галактике";
$text["ja"] = "銀河ヒッチハイク・ガイド";

foreach($text as $lang => $t)
{
 echo $lang, ": ", $t, " (", strlen($t), " vs. ", mb_strlen($t, "utf-8"), ")\n";
}
?>

A saída dessa listagem é mostrada na Figura 1.

Figura 1. Funções de cadeia de caracteres PHP simples retornam resultados inadequados para texto codificado em UTF-8
Image shows output from plain PHP string functions
Image shows output from plain PHP string functions

Como se pode ver, o comprimento das sequências de caracteres escritas em diversos sistemas de escrita é calculado incorretamente. Somente para texto contendo letras do alfabeto latino um resultado correto é retornado. Nesse caso, é possível solucionar o problema usando funções da biblioteca mbstring (consulte Recursos). Assim, para obter o comprimento correto da cadeia de caracteres codificada em UTF-8, é necessário usar mb_strlen(string, "utf-8"), em vez de somente strlen(string).

Quando seus scripts estão processando dados em UTF-8, você está pronto para adicionar mais recursos de internacionalização com a biblioteca intl.

Instalando a biblioteca intl

A biblioteca intl é um wrapper de PHP da famosa biblioteca International Components for Unicode (ICU) (consulte Recursos). Muitos aplicativos usam ICU para implementar suporte Unicode e de localização adequado.

A biblioteca intl é uma parte padrão do PHP desde a V5.3. Se você possui o PHP V5.3 ou posterior, a biblioteca deve estar disponível para uso. Para versões mais antigas do PHP, ainda é possível usar a biblioteca através de uma extensão PECL.

Trabalhando com códigos do idioma

A combinação de idioma, região, sistema de escrita e outros parâmetros que controlam a localização é conhecida como código do idioma. O código do idioma normalmente é identificado por um tag de idioma como definido em IETF Best Current Practices (BCP) 47 (consulte Temas relacionados para obter mais informações). Por exemplo, inglês será identificado pela tag simples en. Alguns idiomas historicamente evoluíram em diferentes regiões e hoje possuem diferenças significativas. Para lidar com essa situação, é possível anexar um identificador de país após o identificador de idioma. Por exemplo, pt_PT identifica português como usado em Portugal, enquanto pt_BR denota o português como usado no Brasil. O BCP 47 oferece um controle muito mais refinado, mas para fins de brevidade, este artigo não fornece mais detalhes sobre identificadores de códigos do idioma.

Todas as funções e métodos intl que reconhecem o código do idioma aceitam um tag de idioma como o identificar do código do idioma. Ainda, a biblioteca intl fornece uma interface dupla — funcional e orientada a objeto. É possível escolher a interface adequada dependendo do seu estilo de codificação PHP. Por exemplo, há uma função/um método que retorna o nome do idioma para um código do idioma no idioma escolhido. Usando a notação funcional, é possível chamar o código usando o seguinte:

// return name of language used for "en" locale in French (fr)
echo locale_get_display_language("en", "fr"); // Anglais

Se você prefere a abordagem orientada a objeto, é possível usar o método estático correspondente:

// return name of language used for "en" locale in French (fr)
echo Locale::getDisplayLanguage("en", "fr"); // Anglais

A classe Locale fornecida na biblioteca intl define métodos de utilitário úteis. Alguns exemplos são fornecidos na Listagem 2.

Listagem 2. Uso dos métodos na classe Locale
<?php
Header("Content-type: text/plain; charset=utf-8");
 
// display name of Portuguese as used in Brazil in different languages
echo Locale::getDisplayName("pt_BR", "en"), "\n";
echo Locale::getDisplayName("pt_BR", "de"), "\n";
echo Locale::getDisplayName("pt_BR", "ru"), "\n";
echo Locale::getDisplayName("pt_BR", "ja"), "\n";
 
// return preferred locale set in user's browser
echo "Preferred locale from browser: ", 
     Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
?>

O script produz o nome do idioma (Português) e o nome do país (Brasil) em diversos códigos do idioma diferentes. Ainda, o método acceptFromHttp(), que pode ser usado para ler o código do idioma preferido que um usuário definiu, é mostrado. A Figura 2 mostra a saída dessa listagem.

Figura 2. Saída do código da Listagem 2
Image shows Locale class output
Image shows Locale class output

Formatar números

Em um primeiro relance, formatar números pode parecer uma tarefa fácil. Mas quando é preciso lidar com todos os detalhes entediantes — os diferentes separadores e agrupadores de decimais usados em diferentes idiomas, por exemplo— você gostará de poder usar o intl para fazer isso por você. Além de formatação de números e moedas, o intl lida com tarefas mais sofisticadas, como escrever números corretamente —novamente, não apenas para o inglês, mas para muitos códigos do idioma suportados.

Formatação está disponível como funções, iniciando com o prefixo numfmt_, ou como métodos, como na classe NumberFormatter. Os exemplos neste artigo usam o estilo orientado a objeto, mas é possível obter os mesmos resultados usando uma abordagem funcional.

Antes de formatar, é preciso criar uma nova instância do NumberFormatter. É preciso fornecer um identificador de código do idioma e um estilo de formatador como parâmetros para o método de construção. É possível especificar o estilo usando diversas constantes predefinidas, como NumberFormatter::DECIMAL (formato decimal), NumberFormatter::CURRENCY (formato de moeda), NumberFormatter::SCIENTIFIC (formato científico) e NumberFormatter::SPELLOUT (o número será escrito corretamente). Por exemplo:

$fmt = new NumberFormatter("en", NumberFormatter::SPELLOUT);

Em um formatador recentemente criado, é possível salvar diversos métodos. Os mais úteis provavelmente são format() para formatar números e formatCurrency() para formatar quantias. O último método aceita um código de moeda como um segundo parâmetro. O uso dos métodos é mostrado na Listagem 3.

Listagem 3. Uso dos métodos na classe FormatNumber
<?php
Header("Content-type: text/plain; charset=utf-8");
 
// Locale-aware number formatting
$fmt = new NumberFormatter("en", NumberFormatter::DECIMAL);
echo $fmt->format(19841984.123456), "\n";
 
// Spelling out numbers in English
$fmt = new NumberFormatter("en", NumberFormatter::SPELLOUT);
echo $fmt->format(1984), "\n";
 
// Spelling out numbers in Russian
$fmt = new NumberFormatter("ru", NumberFormatter::SPELLOUT);
echo $fmt->format(1984), "\n";
 
// Formatting Euro and Czech crowns in German
$fmt = new NumberFormatter("de", NumberFormatter::CURRENCY);
echo $fmt->formatCurrency(123456.789, "EUR"), "\n";
echo $fmt->formatCurrency(123456.789, "CZK"), "\n";
 
// Formatting Euro and Czech crowns in Czech
$fmt = new NumberFormatter("cs", NumberFormatter::CURRENCY);
echo $fmt->formatCurrency(123456.789, "EUR"), "\n";
echo $fmt->formatCurrency(123456.789, "CZK"), "\n";

?>

Como a saída na Figura 3 mostra, separadores decimais e agrupamento adequado são usados ao formatar o número decimal. Se você conhece os idiomas inglês e russo, é possível verificar se o número está escrito corretamente. E, por fim, é possível ver que alguns códigos de moedas como EUR estão formatados como , enquanto outros estão presentes em formas localizadas — por exemplo, a Coroa Tcheca (CZK) está escrita em tcheco como .

Figura 3. Saída do script da Listagem 3
Image shows FormatNumber class output
Image shows FormatNumber class output

Ordenação

Em muitos aplicativos, os dados podem ser classificados antes de serem exibidos. Mas regras de ordenação para idiomas que não o inglês podem ser complexas. Caracteres com acentos normalmente são tratados de uma maneira especial; alguns idiomas tratam sequências selecionadas de dois caracteres como uma letra para classificar (por exemplo, ch em tcheco e espanhol tradicional). Felizmente, a biblioteca intl fornece a classe Collator (e funções de sombra com nomes iniciando com collator_), que você pode usar para comparar e classificar sequências de caracteres com relação ao seu código do idioma selecionado.

Antes de comparar ou classificar, é preciso criar o novo intercalador e especificar um código do idioma para ele: $coll = new Collator("en_US");.

Agora á possível chamar diversos métodos no objeto criado. Por exemplo, o método compare() compara duas sequências de caracteres; os métodos sort() e asort() classificam arrays ou arrays associativas de maneira semelhante às funções de array do PHP correspondentes. O código na Listagem 4 mostra como é possível classificar uma array de palavras em tcheco baseadas de maneira diferente no código do idioma usado para o intercalador.

Listagem 4. Ordenando com diferentes códigos do idioma
<?php

Header("Content-type: text/plain; charset=utf-8");
 
// words to sort
$words = array("čočka", "čekanka", "cena", "chalupa",
               "ťululum", "dálnopis", "tyfus", "traktor");
 
// sort using built-in PHP sort function
sort($words);
echo "Words sorted using built-in sort function:\n";
var_export($words);

// sort according to English rules
$coll = new Collator("en_US");
$coll->sort($words);
echo "\n\nWords sorted according to English rules:\n";
var_export($words);

// sort according to Czech rules
$coll = new Collator("cs");
$coll->sort($words);
echo "\n\nWords sorted according to Czech rules:\n";
var_export($words);

?>

Se você classificar palavras usando a função integrada sort() do PHP, as palavras iniciando com letras acentuadas estão no final, porque o PHP compara sequências de caracteres como valores binários e não trata letras acentuadas de uma maneira especial. Entretanto, se você classificar as mesmas palavras usando um intercalador criado para inglês, as palavras são classificadas como se os acentos fossem ignorados — comportamento desejado se você possui algumas poucas palavras estrangeiras em um texto em inglês. No final, um intercalador tcheco é usado. Como se pode ver, regras estranhas agora são aplicadas: alguns caracteres acentuados são tratados como não acentuados (por exemplo, ť), alguns são tratados como caracteres separados (por exemplo, c e č) e ch é tratado como um caractere especial. A Figura 4 mostra o resultado do código na Listagem 4.

Figura 4. Saída do script da Listagem 4
Image showing output of the previous script
Image showing output of the previous script

Tópicos avançados

Unicode e internacionalização é um amplo tópico, mas você deve conhecer pelo menos mais um elemento importante. Por motivos históricos, Unicode permite representações alternativas dos mesmos caracteres. Por exemplo, á pode ser escrito tanto como um caractere pré-composto á com o ponto de código Unicode U+00E1 quanto como uma sequência decomposta da letra a (U+0061) combinada com o acento ´ (U+0301). Para fins de comparação e classificação, essas duas representações devem ser consideradas iguais.

Para solucionar isso, a biblioteca intl fornece a classe Normalizer. Essa classe, por sua vez, fornece o método normalize(), que pode ser usado para converter uma cadeia de caracteres em uma forma normalizada composta ou decomposta. Seu aplicativo deve transformar de maneira consistente todas as sequências de caracteres em uma ou outra forma antes de realizar comparações.

echo Normalizer::normalize("a´, Normalizer::FORM_C); // á 
echo Normalizer::normalize("á", Normalizer::FORM_D);      // a´

Conclusão

Este breve artigo apresentou a funcionalidade mais importante e útil que a biblioteca intl fornece. Essa biblioteca tornou-se uma parte padrão do PHP V5.3. A biblioteca oferece muito mais funcionalidade que este artigo poderia abordar—por exemplo, formatação de data e análise de valores armazenados em formatos localizados. Para mais informações, consulte o manual do PHP em PHP.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=477923
ArticleTitle=O que há de novo em Unicode em PHP V5.3?
publish-date=03262010