Pular para o conteúdo

toLocaleUpperCase() sem locale erra 6 em cada 10 nomes de cidade brasileira

"dia".toLocaleUpperCase(), sem nada entre parênteses, devolve DIA — na sua máquina. Num aparelho configurado em turco, a mesma linha devolve DİA, com o I pontilhado. Não é exemplo de manual: rodamos as 5.571 cidades do Brasil pelas duas réguas, e 60,6% dos nomes saem com pelo menos uma letra diferente — Rio de Janeiro vira RİO DE JANEİRO.

As 27 capitais, pelas duas réguas

Nenhum número desta tabela foi digitado: as duas colunas são capital.toLocaleUpperCase("pt-BR") e capital.toLocaleUpperCase("tr"), rodadas de verdade neste build. 13 das 27 saem diferentes — quase a metade.

CapitalMaiúscula em pt-BRMaiúscula em turco
Rio Branco (AC)RIO BRANCORİO BRANCO
Maceió (AL)MACEIÓMACEİÓ
Macapá (AP)MACAPÁMACAPÁ
Manaus (AM)MANAUSMANAUS
Salvador (BA)SALVADORSALVADOR
Fortaleza (CE)FORTALEZAFORTALEZA
Brasília (DF)BRASÍLIABRASÍLİA
Vitória (ES)VITÓRIAVİTÓRİA
Goiânia (GO)GOIÂNIAGOİÂNİA
São Luís (MA)SÃO LUÍSSÃO LUÍS
Cuiabá (MT)CUIABÁCUİABÁ
Campo Grande (MS)CAMPO GRANDECAMPO GRANDE
Belo Horizonte (MG)BELO HORIZONTEBELO HORİZONTE
Belém (PA)BELÉMBELÉM
João Pessoa (PB)JOÃO PESSOAJOÃO PESSOA
Curitiba (PR)CURITIBACURİTİBA
Recife (PE)RECIFERECİFE
Teresina (PI)TERESINATERESİNA
Rio de Janeiro (RJ)RIO DE JANEIRORİO DE JANEİRO
Natal (RN)NATALNATAL
Porto Alegre (RS)PORTO ALEGREPORTO ALEGRE
Porto Velho (RO)PORTO VELHOPORTO VELHO
Boa Vista (RR)BOA VISTABOA VİSTA
Florianópolis (SC)FLORIANÓPOLISFLORİANÓPOLİS
São Paulo (SP)SÃO PAULOSÃO PAULO
Aracaju (SE)ARACAJUARACAJU
Palmas (TO)PALMASPALMAS

As capitais que mudam são exatamente as que têm um "i" minúsculo na grafia — nenhuma outra letra do alfabeto se comporta diferente entre os dois locales. Conferido em 13 de agosto de 2026.

A pergunta que ninguém faz: locale de quem?

toLocaleUpperCase() existe para fazer a coisa certa com acento — é o método que a maioria dos tutoriais recomenda no lugar do toUpperCase() cru, porque em teoria ele "sabe o idioma". O que ninguém avisa é: sabe o idioma de QUEM? Não é o idioma da página, não é o lang="pt- BR" do HTML, não é nada que quem escreveu o código decidiu. É o do APARELHO de quem está lendo.

Isso não é comportamento de navegador nem acidente de implementação — está na especificação da linguagem. O ECMA-402, que é o padrão do Intl, lido na origem em tc39.es/ecma402, define toLocaleUpperCase em cima de uma operação chamada TransformCase, e o primeiro passo dela diz: "If locales is undefined, let locales be DefaultLocale()". Chamar sem argumento não é pedir o comportamento neutro — é entregar a decisão para a máquina.

E o que é DefaultLocale()? A própria especificação diz que é "implementation-defined" e devolve "the well-formed and canonicalized language tag for the host environment's current locale" . A nota anexa a essa definição fecha a dúvida para o caso do navegador: "it should match navigator.language" — o idioma que a pessoa escolheu no celular ou no navegador dela, não o que o site pediu.

Por que justamente o turco

O português, o inglês, o espanhol — a imensa maioria dos idiomas do mundo — concordam numa coisa que parece óbvia demais para precisar de regra: a maiúscula de "i" é "I". O turco e o azeri não concordam. As duas línguas têm duas letras onde o alfabeto latino comum só tem uma: um par pontilhado (i minúsculo, İ maiúsculo) e um par sem ponto (ı minúsculo, I maiúsculo). Maiusculizar o "i" do teclado comum, em turco, dá o pontilhado — nunca o "I" que qualquer outro idioma esperaria.

A fonte é o próprio Unicode, lida na origem em unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt, que é o arquivo que todo motor de Intl — V8, ICU, qualquer um — usa para saber quais letras fogem da regra padrão. As linhas, copiadas sem alterar nada:

# When uppercasing, i turns into a dotted capital I
0069; 0069; 0130; 0130; tr; # LATIN SMALL LETTER I
0069; 0069; 0130; 0130; az; # LATIN SMALL LETTER I

0069 é o ponto de código do "i" comum; 0130 é "İ", a letra que o Unicode chama de LATIN CAPITAL LETTER I WITH DOT ABOVE. A própria MDN documenta o mesmo achado com o exemplo de sempre dessa área: "istanbul".toLocaleUpperCase("tr") devolve İSTANBUL, nunca ISTANBUL.

A magnitude: 60,6% das cidades brasileiras

Uma letra parece pouco. Uma cidade parece anedota. A pergunta que decide se isso é curiosidade ou defeito de verdade é: quantos nomes reais têm um "i" minúsculo no meio? Rodamos o catálogo inteiro de município do IBGE — o mesmo baixado pelos artigos irmãos de acento, ordem alfabética e duplicata, de servicodados.ibge.gov.br/api/v1/localidades/municipios, baixado em 2026-08-13 — e comparamos toLocaleUpperCase("tr") contra toLocaleUpperCase("pt-BR"), caractere por caractere, nos 5.298 nomes distintos (tirando os 273 que se repetem entre estados, mesma conta do artigo de new Set()).

3.209 de 5.298 60,6% — saem com pelo menos uma letra diferente. Não é a cauda rara da distribuição: é a maioria. Qualquer sistema que colete nome de cidade e maiusculize com toLocaleUpperCase() sem locale — etiqueta de correio, recibo, relatório — está numa moeda cara-ou-coroa em que cara é a maioria dos nomes saindo com uma letra errada, tudo porque o celular de quem gerou o documento estava no idioma errado.

O achado mais visual da medição grande não é uma capital — é Piripiri, cidade real do Piauí, com quatro "i": em turco ela sai PİRİPİRİ, com os quatro pontilhados.

Já mordeu gente de verdade — só que não em JavaScript

Este não é um defeito hipotético de linguagem de programação: é uma exceção de idioma que qualquer API de maiúscula sensível a locale herda, em qualquer linguagem. Um post técnico sobre o mesmo problema (Onur Gümüş, "Does Your Code Pass The Turkey Test?", lido em 2026-08-13) descreve um banco cujo sistema em ODP.NET quebrou depois de um deploy: ToUpper(), rodando sob cultura turca, transformava DESCRIPTION em DESCRİPTİON, e uma comparação que esperava a primeira forma parava de bater — uma exceção "cryptic" para quem estava vendo o erro sem saber que a causa era a letra i. A Oracle corrigiu trocando o método por ToUpperInvariant, a versão que ignora o locale do sistema de propósito.

Não é o mesmo motor, não é a mesma linguagem, e este artigo não afirma que o caso do banco aconteceu em JavaScript — seria inventar gravidade que a fonte não tem. O que os dois casos compartilham é a MESMA linha do Unicode citada acima: qualquer API de case-mapping que siga o locale do sistema por padrão pode cair na mesma exceção, porque a exceção não é da API — é da língua turca.

Por que esta ferramenta não erra o mesmo jeito

O conversor de texto desta casa nunca chama toLocaleUpperCase() sem argumento — as quatro chamadas do módulo (em texto.ts) passam "pt-BR" sempre, travado por escrito. Não foi decisão tomada para este artigo: o comentário no topo do arquivo já citava o caso do turco antes de qualquer texto nomear a fonte — a ferramenta sabia do risco antes de virar matéria.

A mesma armadilha, do lado contrário, já está documentada em localizar e substituir: quando a opção "manter caixa" precisa decidir se um termo achado está em maiúscula, ela também passa "pt-BR" explícito pelo mesmo motivo. Ferramenta nenhuma desta casa deixa o Intl escolher sozinho.

Onde isso morde de verdade

Não é preciso um usuário turco para o defeito aparecer — precisa só de um AMBIENTE configurado errado. Uma imagem de contêiner de CI com LANG=tr_TR deixada por engano, um servidor de teste configurado por outra equipe, um desenvolvedor testando internacionalização e esquecendo de trocar de volta — qualquer um desses transforma toda maiusculização sem locale explícito do sistema inteiro, silenciosamente, sem uma linha de erro. O defeito não avisa: o código continua rodando, os testes que não cobrem essa letra continuam verdes, e o texto sai de produção com uma letra errada que ninguém pediu.

O que este artigo não mede

Não medimos nenhum navegador real com o sistema operacional configurado em turco — o ambiente onde este site é escrito não tem essa máquina, e os números vêm do Intl do Node, que usa o mesmo ICU que Chrome e Safari. Não afirmamos quantos usuários turcos visitam sites brasileiros, nem citamos nenhum incidente real em produção de um site em português — o caso do banco, citado acima, é de outra linguagem e está identificado como tal. E não testamos o Lituano, que o Unicode também trata como exceção de case-mapping por um motivo diferente (acento que desaparece depois de certas combinações) — está fora do escopo deste artigo, que é sobre a letra i.