Localização em C

Alguém me perguntou sobre locale em C. Pra você que não sabe o que locale significa, é somente a maneira como algumas strings relacionadas a formatação se apresentam de acordo com a localidade onde o usuário se encontra. Por exemplo, no Brasil usamos ‘,’ (vírgula) ao invés de ‘.’ (ponto) decimal, quando queremos expressar valores fracionários, como em 3,141593. Mas, parece, que as rotinas das bibliotecas lidam apenas com o formato americano, usando o ‘.’. Bem… não é bem assim.

O padrão ISO 9899, que rege a linguagem C e a biblioteca padrão, nos fornece o header locale.h com estruturas e funções para lidarmos com localização. A localização default é chamada, simplesmente, de “C” e segue o padrão americano. Mas, podemos ajustar a localidade para a nossa língua, nosso país e o charset que usamos, desde que o sistema operacional o tenha acessível. Para isso usamos a função setlocale(), que tem o protótipo:

char *setlocale(int category, const char *locale);

A “categoria” é uma das constantes LC_ALL, LC_ADDRESS, LC_COLLATE, LC_CTYPE, LC_IDENTIFICATION, LC_MEASUREMENT, LC_MESSAGES, LC_MONETARY, LC_NAME, LC_NUMERIC, LC_PAPER, LC_TELEPHONE ou LC_TIME. Cada uma dessas características endereça um aspecto da sua localidade… LC_NUMERIC, por exemplo, é usada para ajustar como o “ponto decimal” e o “separador de milhar” serão usados. LC_MONETARY, a mesma coisa, mas também onde a string da moeda será colocada e se como valores negativos são expressos… A categoria LC_CTYPE tem a ver com as macros contidas no header ctype.h. E por ai vai…

O argumento “locale” é uma string com um formato específico. Pro Brasil, por exemplo, pode-se usar “pt_BR” ou “pt_BR.utf8” (no caso do Linux/BSD/MacOS). O formato segue o padrão “língua[_país][.charset]”, onde “língua” segue a abreviação especificada na ISO 639, enquanto “país” segue a especificação ISO 3166… Mas, é importante notar que apenas os locales disponíveis no sistema operacional são aceitáveis e eles podem ser obtidos assim:

$ locale -a
C
C.UTF-8
en_AG
en_AG.utf8
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IN
en_IN.utf8
en_NG
en_NG.utf8
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZM
en_ZM.utf8
en_ZW.utf8
POSIX
pt_BR.utf8

Note que, no meu caso, “pt_BR” não está disponível, apenas “pt_BR.utf8”.

O programinha abaixo mostra locale em funcionamento:

#include <stdio.h>
#include <locale.h>

void main(void)
{
  float f;

  setlocale(LC_ALL, "pt_BR.utf8");
  fputs("Entre com um valor float (usando ',' como separador decimal): ", stdout);
  fflush(stdout);
  scanf("%f", &f);
  printf("Valor: %f\n", f);
}

Compilando e executando:

$ cc -o test test.c
$ ./test
Entre com um valor float (usando ',' como separador decimal): 3,14
Valor: 3,140000

Tanto o scanf() quanto o printf() obedeceram a localização ajustada por setlocale()! Mas, note, não é que essas funções respeitem a localização, mas as funções que elas usam o fazem (atof(), por exemplo). Outro ponto interessante é sobre os “separadores de milhar”. Eles não são respeitados nessas funções! Faça float f = atof("32.768,9"); com o locale pt_BR.utf8 ativo e você, provavelmente, obterá 0.0f como resposta, indicando falha da conversão. Não espere que scanf() e printf() vão entender valores monetários, por exemplo… O locale LC_MONETARY existe somente para que você possa pegar as regras correspondentes na estrutura lconv.

Para saber quais são as regras do locale selecionado, use a função localeconv() que retorna um ponteiro para uma estrutura lconv contendo um monte de informações (disponiveis na especificação da linguagem C, aqui). Você não deve modificar os valores da estrutura…

Uma maneira simples de livrar-se dos separadores de milhar, levando-se em conta o indicado em lconv, é usando uma rotina como essa aqui:

#include <string.h>
#include <locale.h>

char *strip_thousands_sep(char *s)
{
  struct lconv *pl;

  pl = localeconv();
  if (*pl->mon_thousands_sep)
  {
    char *p = s;

    while (p = strchr(p, *pl->mon_thousands_sep))
      strcpy(p, p + 1);  // Não há problema em fazer
                         // essa cópia. Não há overlapping
                         // aqui e ambos os ponteiros
                         // são válidos.
  }

  return s;
}

É claro, a localidade terá que ser ajustada antes de chamar essa rotina. E, como já dito, para retornar a localidade default, basta usar “C”, ao invés de “pt_BR.utf8”.

A localização também é útil para comparação de strings. A função strcoll() leva em consideração a localidade para comparar duas strings. O exemplo contido no site cppreference (exemplo, aqui) ilustra bem. Note que strcmp() e derivadas não lidarão com localização… Outra função para lidar com strings e localização útil é strxfrm(). Ela transforma (daí a abreviação xfrm) uma string numa representação que pode ser usada em strcmp(), ou derivadas, para ter o mesmo efeito de strcoll(). Isso pode ser útil em línguas como hebraico e árabe, que são escritas “de trás para frente”…

Instalando novas localidades no Linux:

Nada mais simples. Linux disponibiliza pacotes para linguagens específicas. Para listá-las todas (Debian/Ubuntu):

$ apt-cache search language-pack | grep 'pack-.. '
language-pack-af - translation updates for language Afrikaans
language-pack-am - translation updates for language Amharic
language-pack-an - translation updates for language Aragonese
language-pack-ar - translation updates for language Arabic
language-pack-as - translation updates for language Assamese
language-pack-az - translation updates for language Azerbaijani
language-pack-be - translation updates for language Belarusian
language-pack-bg - translation updates for language Bulgarian
language-pack-bn - translation updates for language Bengali
language-pack-bo - translation updates for language Tibetan
language-pack-br - translation updates for language Breton
language-pack-bs - translation updates for language Bosnian
language-pack-ca - translation updates for language Catalan; Valencian
language-pack-cs - translation updates for language Czech
language-pack-cy - translation updates for language Welsh
language-pack-da - translation updates for language Danish
language-pack-de - translation updates for language German
language-pack-dz - translation updates for language Dzongkha
language-pack-el - translation updates for language Greek, Modern (1453-)
language-pack-en - translation updates for language English
language-pack-eo - translation updates for language Esperanto
language-pack-es - translation updates for language Spanish; Castilian
language-pack-et - translation updates for language Estonian
language-pack-eu - translation updates for language Basque
language-pack-fa - translation updates for language Persian
language-pack-fi - translation updates for language Finnish
language-pack-fr - translation updates for language French
language-pack-ga - translation updates for language Irish
language-pack-gd - translation updates for language Gaelic; Scottish Gaelic
language-pack-gl - translation updates for language Galician
language-pack-gu - translation updates for language Gujarati
language-pack-he - translation updates for language Hebrew
language-pack-hi - translation updates for language Hindi
language-pack-hr - translation updates for language Croatian
language-pack-hu - translation updates for language Hungarian
language-pack-ia - translation updates for language Interlingua (International Auxiliary Language Association)
language-pack-id - translation updates for language Indonesian
language-pack-is - translation updates for language Icelandic
language-pack-it - translation updates for language Italian
language-pack-ja - translation updates for language Japanese
language-pack-ka - translation updates for language Georgian
language-pack-kk - translation updates for language Kazakh
language-pack-km - translation updates for language Central Khmer
language-pack-kn - translation updates for language Kannada
language-pack-ko - translation updates for language Korean
language-pack-ku - translation updates for language Kurdish
language-pack-lt - translation updates for language Lithuanian
language-pack-lv - translation updates for language Latvian
language-pack-mk - translation updates for language Macedonian
language-pack-ml - translation updates for language Malayalam
language-pack-mn - translation updates for language Mongolian
language-pack-mr - translation updates for language Marathi
language-pack-ms - translation updates for language Malay
language-pack-my - translation updates for language Burmese
language-pack-nb - translation updates for language Bokmål, Norwegian; Norwegian Bokmål
language-pack-ne - translation updates for language Nepali
language-pack-nl - translation updates for language Dutch; Flemish
language-pack-nn - translation updates for language Norwegian Nynorsk; Nynorsk, Norwegian
language-pack-oc - translation updates for language Occitan (post 1500)
language-pack-or - translation updates for language Oriya
language-pack-pa - translation updates for language Panjabi; Punjabi
language-pack-pl - translation updates for language Polish
language-pack-pt - translation updates for language Portuguese
language-pack-ro - translation updates for language Romanian
language-pack-ru - translation updates for language Russian
language-pack-si - translation updates for language Sinhala; Sinhalese
language-pack-sk - translation updates for language Slovak
language-pack-sl - translation updates for language Slovenian
language-pack-sq - translation updates for language Albanian
language-pack-sr - translation updates for language Serbian
language-pack-sv - translation updates for language Swedish
language-pack-ta - translation updates for language Tamil
language-pack-te - translation updates for language Telugu
language-pack-tg - translation updates for language Tajik
language-pack-th - translation updates for language Thai
language-pack-tr - translation updates for language Turkish
language-pack-ug - translation updates for language Uighur; Uyghur
language-pack-uk - translation updates for language Ukrainian
language-pack-uz - translation updates for language Uzbek
language-pack-vi - translation updates for language Vietnamese
language-pack-xh - translation updates for language Xhosa

Se adicionar language-pack-ja, por exemplo, a localidade jp_JP.utf8 será adicionada nas tabelas de localidade do sistema e ficará disponível para uso de seus programas.

Anúncios