tva
← Insights

Flags em Widgets: Por Que Códigos ISO de País Superam Nomes Dependentes de Localidade

O bug era sutil. Um widget iOS exibia o nome do país correto em inglês, mas mostrava o emoji de bandeira errado em alemão. Na localidade francesa, um país diferente mostrava a bandeira errada. As bandeiras não eram aleatórias — eram consistentemente erradas em um padrão que levou um momento para reconhecer: a pesquisa estava retornando a bandeira de qualquer país que, na localidade atual, tivesse um nome que se assemelhasse ao nome de exibição armazenado.

A causa raiz era uma pesquisa de país que usava nomes de países localizados como chaves de dicionário. Nomes que são strings estáveis em uma localidade se tornam strings diferentes em outra. Corrigi-lo exigiu reconstruir a pesquisa em torno de códigos ISO 3166-1 alpha-2 por toda parte, e a lição vai muito além de emojis de bandeira.


Como o Bug Apareceu

O widget exibia uma lista de países com seus emojis de bandeira correspondentes ao lado de outros dados. A implementação original armazenava os nomes de exibição dos países como identificador — a string “Germany” ou “France” — e usava um dicionário com chaves nesses nomes para pesquisar o emoji de bandeira correspondente.

Na localidade inglesa, isso funcionava corretamente. O nome armazenado era “Germany”, a chave do dicionário era “Germany”, e a pesquisa retornava a bandeira correta. Mas quando a localidade do dispositivo era definida como alemão, NSLocale.current.localizedString(forRegionCode: "DE") retorna “Deutschland”. O widget estava chamando esse método para resolver os nomes de exibição dinamicamente. Então o nome exibido se tornava “Deutschland” — correto, em contexto — mas a pesquisa no dicionário com chaves em inglês falhava, voltava para um padrão e exibia a bandeira errada.

A variante mais insidiosa eram correspondências parciais. Em algumas localidades, o nome localizado de um país coincide com um prefixo ou correspondência aproximada do nome em inglês de um país diferente. Sem verificações de igualdade estrita, pesquisas aproximadas retornavam resultados plausíveis, mas incorretos. Um processo de QA rodando apenas na localidade inglesa nunca detectaria isso.

Por Que Nomes Dependentes de Localidade São uma Armadilha

O problema fundamental é usar uma string dependente de localidade como identificador. Identificadores precisam ser estáveis — o mesmo valor independentemente de quem os lê, quando, ou em que contexto de linguagem. O nome de um país na localidade atual é um valor de apresentação. É correto e apropriado exibi-lo para um usuário. É incorreto e frágil usá-lo como chave de pesquisa, identificador de banco de dados, chave de cache ou qualquer outra forma de referência estável.

Isso não é limitado a nomes de países. Nomes de moedas, nomes de idiomas, nomes de exibição de localidade e descrições de fuso horário compartilham a mesma propriedade: são strings dependentes de localidade que o sistema gera para exibição, não para identidade. NSLocale.current.localizedString(forCurrencyCode: "EUR") retorna “Euro” em inglês e “Euro” em alemão, o que por acaso coincide — mas localizedString(forCurrencyCode: "GBP") retorna “British Pound” em inglês e “Britisches Pfund” em alemão. Usar qualquer um como chave produz a mesma categoria de bug.

A regra é simples: use o código ISO como identificador em toda a lógica e chame o método de localização apenas ao construir uma string para exibição ao usuário. Nunca armazene a string localizada. Nunca a use como chave. Nunca a compare programaticamente entre localidades.

Gerando Emojis de Bandeira a Partir de Códigos ISO

Os emojis de bandeira são codificados no Unicode como pares de Símbolos Indicadores Regionais. A sequência de caracteres para a bandeira de um determinado país corresponde ao código ISO 3166-1 alpha-2 do país, com cada letra substituída pelo seu Símbolo Indicador Regional equivalente. Os Símbolos Indicadores Regionais vão de U+1F1E6 (para A) até U+1F1FF (para Z).

Em Swift, gerar um emoji de bandeira a partir de um código ISO é uma função utilitária curta:

func flagEmoji(for isoCode: String) -> String {
    let base: UInt32 = 0x1F1E6 - 65 // deslocamento a partir de A
    return isoCode.uppercased().unicodeScalars.compactMap {
        UnicodeScalar(base + $0.value)
    }.map { String($0) }.joined()
}

Essa função pega “DE” e retorna 🇩🇪. Pega “SG” e retorna 🇸🇬. Não requer dicionário, arquivo de mapeamento ou manutenção. O mapeamento é definido pelo padrão Unicode e rastreia os códigos ISO 3166-1 alpha-2 diretamente.

O único caso extremo que vale a pena tratar é o fato de que nem todos os códigos de duas letras têm emojis de bandeira correspondentes. ONU, UE e alguns outros códigos regionais produzem emojis de bandeira que nem todas as plataformas renderizam de forma consistente. Para um widget que lida com dados de Estado-nação, restringir a entrada a códigos válidos de Estados membros da ONU e testar na plataforma alvo é suficiente.

O Mapeamento de 150+ Países Que Ainda Precisávamos

Gerar emojis de bandeira a partir de códigos ISO algoritmicamente lida com o lado de exibição. Mas também precisávamos armazenar e consultar países de forma estruturada — o widget filtrava países por região, os ordenava por vários critérios e associava cada um a metadados adicionais. Isso exigiu um mapeamento adequado.

O mapeamento que construímos emparelha códigos ISO 3166-1 alpha-2 com um nome canônico em inglês (para uso interno e registro), um código numérico ISO 3166-1 (para sistemas que exigem identificadores numéricos) e um agrupamento de região. O nome em inglês neste mapeamento nunca é usado como chave de pesquisa — existe apenas para depuração e exibição administrativa em contextos não localizados. Todos os nomes voltados ao usuário são gerados no momento da renderização via NSLocale.current.localizedString(forRegionCode:).

Essa separação é a decisão arquitetônica chave. A camada de dados fala em códigos ISO. A camada de apresentação traduz para a localidade atual. Elas nunca se encontram no meio. Uma pesquisa de bandeira, um agrupamento de região, uma ordem de classificação — tudo isso opera em códigos ISO. Apenas o texto final renderizado ao usuário passa pela localização.

Estendendo a Correção para Outros Dados

Uma vez que o bug da bandeira foi corrigido, auditamos o restante da base de código para o mesmo padrão. O mesmo problema apareceu em três outros lugares: um módulo de exibição de moedas que usava nomes de moedas localizados como chaves de cache, um seletor de idiomas que armazenava nomes de idiomas localizados nas preferências do usuário e um módulo de formatação de datas que comparava nomes de meses dependentes de localidade.

A correção em cada caso seguiu o mesmo padrão. Substitua a string dependente de localidade pelo seu equivalente ISO ou IETF estável: códigos de moeda (ISO 4217) em vez de nomes de moedas, tags de idioma BCP 47 em vez de nomes de idiomas localizados, valores de mês numéricos em vez de strings de nome de mês. As strings localizadas se tornam preocupações apenas da camada de view.

A auditoria valeu a pena fazer sistematicamente. Cada um desses bugs seria invisível nos testes de localidade inglesa e teria surgido apenas em produção, em dispositivos configurados para outros idiomas. Dado que o aplicativo tinha como alvo um público multilíngue, o impacto teria sido significativo.

O Escopo do Problema em Produção

Uma razão pela qual essa classe de bug persiste é que é invisível em ambientes de desenvolvimento de localidade única. Um desenvolvedor trabalhando na localidade inglesa, construindo um aplicativo para um público internacional, pode escrever e testar o recurso inteiro de exibição de países sem nunca acionar o bug. O bug só se manifesta quando a localidade do dispositivo não corresponde à localidade em que o dicionário ou mapeamento foi originalmente construído — que é precisamente a condição que descreve a maioria dos usuários alvo.

Em um widget especificamente, a lacuna é maior do que em um aplicativo principal porque os widgets executam em um contexto de extensão restrito. A extensão do widget roda com a localidade do sistema atual, que pode diferir da localidade que o desenvolvedor testou. Não há depuração em processo para renderizações de widget — você não pode anexar um debugger a uma extensão de widget e inspecionar o estado em tempo de execução da mesma forma que pode com um processo de aplicativo principal. Identificar bugs de renderização dependentes de localidade em widgets requer o depurador de linha do tempo do Xcode e atenção cuidadosa ao contexto de renderização real da extensão, que a maioria dos desenvolvedores encontra apenas depois que um usuário relata o problema em produção.

A resposta prática é tratar toda a lógica de exibição dependente de localidade como uma categoria de risco, não apenas um bug individual para corrigir. A revisão de código deve incluir uma verificação de strings dependentes de localidade usadas como identificadores. A pergunta de revisão é simples: essa string está passando por algum método de localização antes de ser usada como chave ou valor de comparação? Se sim, o design precisa mudar. Códigos ISO, identificadores numéricos e outras referências independentes de localidade devem substituir strings localizadas em cada camada, exceto na exibição final.

Testando Através de Localidades

A lição mais direta deste bug é que o teste de localidade precisa fazer parte da matriz de teste padrão, não ser uma reflexão tardia. O iOS torna isso relativamente direto: o simulador suporta a troca de localidade sem mudar o idioma do sistema, e os testes de UI podem ser parametrizados por localidade.

Para exibição de bandeiras e países especificamente, adicionamos testes de snapshot que rodam em relação a um conjunto fixo de localidades de dispositivo — inglês, alemão, francês, japonês, árabe — e verificam que o emoji de bandeira para um conjunto fixo de códigos ISO permanece correto em todos eles. Esses testes detectam regressões imediatamente se alguém introduzir uma pesquisa dependente de localidade no caminho de exibição.

O princípio mais amplo é que qualquer recurso que toque localização, internacionalização ou exibição dependente de região deve ter testes automatizados que exercitem múltiplas localidades. O QA manual em uma única localidade não é substituto. A categoria de bugs causados por suposições dependentes de localidade é tanto comum quanto consistentemente invisível para testes de localidade única.

Insights Relacionados

Artigos relacionados