Bandiere nei widget: perché i codici ISO dei Paesi sono meglio dei nomi localizzati
Il bug era difficile da individuare. Un widget iOS mostrava il nome corretto del Paese in inglese, ma l’emoji della bandiera sbagliata in tedesco. Con le impostazioni locali francesi, un altro Paese mostrava la bandiera sbagliata. Le bandiere non erano casuali: l’errore seguiva uno schema che abbiamo impiegato un momento a riconoscere. La ricerca restituiva la bandiera del Paese il cui nome, nelle impostazioni locali correnti, assomigliava al nome visualizzato memorizzato.
La causa era una ricerca dei Paesi che usava nomi localizzati come chiavi del dizionario. Nomi che sono stringhe stabili in una lingua diventano stringhe diverse in un’altra. Per correggere il problema abbiamo dovuto ricostruire l’intera ricerca attorno ai codici ISO 3166-1 alpha-2. La lezione va ben oltre le emoji delle bandiere.
Come si manifestava il bug
Il widget mostrava un elenco di Paesi con le rispettive emoji delle bandiere, insieme ad altri dati. L’implementazione originale memorizzava i nomi visualizzati dei Paesi come identificativi, cioè le stringhe «Germany» o «France», e usava un dizionario con quei nomi come chiavi per cercare le emoji corrispondenti.
Con le impostazioni locali inglesi funzionava correttamente. Il nome memorizzato era «Germany», la chiave del dizionario era «Germany» e la ricerca restituiva la bandiera corretta. Ma impostando il dispositivo sul tedesco, NSLocale.current.localizedString(forRegionCode: "DE") restituisce «Deutschland». Il widget chiamava questo metodo per determinare dinamicamente i nomi da visualizzare. Il nome diventava quindi «Deutschland», corretto nel contesto, ma la ricerca nel dizionario con chiavi inglesi falliva, ricadeva su un valore predefinito e mostrava la bandiera sbagliata.
La variante più insidiosa riguardava le corrispondenze parziali. In alcune lingue, il nome localizzato di un Paese condivide un prefisso o assomiglia al nome inglese di un altro Paese. Senza controlli di uguaglianza esatta, le ricerche approssimative restituivano risultati plausibili ma errati. Un processo di verifica della qualità eseguito solo con le impostazioni locali inglesi non avrebbe mai rilevato il problema.
Perché i nomi dipendenti dalle impostazioni locali sono una trappola
Il problema fondamentale è usare una stringa dipendente dalle impostazioni locali come identificativo. Gli identificativi devono essere stabili: avere lo stesso valore indipendentemente da chi li legge, da quando lo fa o dal contesto linguistico. Il nome di un Paese nelle impostazioni locali correnti è un valore di presentazione. È corretto e appropriato mostrarlo all’utente. Usarlo come chiave di ricerca, identificativo di database, chiave di cache o qualsiasi altro riferimento stabile è invece una scelta errata e fragile.
Non riguarda soltanto i nomi dei Paesi. I nomi delle valute, delle lingue, delle impostazioni locali e le descrizioni dei fusi orari hanno tutti la stessa proprietà: sono stringhe dipendenti dalle impostazioni locali che il sistema genera per la visualizzazione, non per l’identificazione. NSLocale.current.localizedString(forCurrencyCode: "EUR") restituisce «Euro» in inglese e «Euro» in tedesco, una coincidenza; localizedString(forCurrencyCode: "GBP"), invece, restituisce «British Pound» in inglese e «Britisches Pfund» in tedesco. Usare uno dei due valori come chiave produce la stessa categoria di bug.
La regola è semplice: usa il codice ISO come identificativo in tutta la logica e chiama il metodo di localizzazione solo quando costruisci una stringa da mostrare all’utente. Non memorizzare mai la stringa localizzata. Non usarla mai come chiave. Non confrontarla mai programmaticamente tra impostazioni locali diverse.
Generare le emoji delle bandiere dai codici ISO
Le emoji delle bandiere sono codificate in Unicode come coppie di simboli indicatori regionali. La sequenza di caratteri di una bandiera corrisponde al codice ISO 3166-1 alpha-2 del Paese, con ogni lettera sostituita dal simbolo indicatore regionale equivalente. I simboli indicatori regionali vanno da U+1F1E6 (per A) a U+1F1FF (per Z).
In Swift, basta una breve funzione di utilità per generare l’emoji di una bandiera da un codice ISO:
func flagEmoji(for isoCode: String) -> String {
let base: UInt32 = 0x1F1E6 - 65 // offset from A
return isoCode.uppercased().unicodeScalars.compactMap {
UnicodeScalar(base + $0.value)
}.map { String($0) }.joined()
}
Questa funzione riceve «DE» e restituisce 🇩🇪. Riceve «SG» e restituisce 🇸🇬. Non richiede dizionari, file di corrispondenze o manutenzione. La corrispondenza è definita dallo standard Unicode e segue direttamente i codici ISO 3166-1 alpha-2.
L’unico caso limite da gestire è che non tutti i codici di due lettere hanno un’emoji della bandiera corrispondente. Codici come UN ed EU, riservati in ISO 3166-1 e non assegnati a Paesi, producono emoji della bandiera che non tutte le piattaforme visualizzano allo stesso modo. Inoltre, la funzione precedente non esegue alcuna validazione: converte qualunque carattere riceva, quindi prima della conversione occorre verificare l’input rispetto all’elenco dei codici ISO 3166-1 alpha-2 assegnati (esattamente due lettere da A a Z). Limitare l’input ai codici assegnati a Paesi e territori ed effettuare test sulla piattaforma di destinazione permette di coprire questo caso.
La mappatura di oltre 150 Paesi che ci serviva comunque
Generare algoritmicamente le emoji delle bandiere dai codici ISO risolve la parte di visualizzazione. Dovevamo però anche memorizzare e interrogare i Paesi in modo strutturato: il widget li filtrava per regione, li ordinava secondo vari criteri e associava a ciascuno metadati aggiuntivi. Serviva quindi una mappatura adeguata.
La mappatura che abbiamo costruito associa i codici ISO 3166-1 alpha-2 a un nome inglese canonico, per uso interno e log, a un codice numerico ISO 3166-1, per i sistemi che richiedono identificativi numerici, e a un raggruppamento regionale. Il nome inglese non viene mai usato come chiave di ricerca: serve soltanto al debug e alla visualizzazione amministrativa in contesti non localizzati. Tutti i nomi destinati agli utenti vengono generati al momento del rendering tramite NSLocale.current.localizedString(forRegionCode:).
Questa separazione è la decisione architetturale fondamentale. Il livello dei dati usa i codici ISO. Il livello di presentazione li traduce secondo le impostazioni locali correnti. I due livelli restano separati. La ricerca di una bandiera, il raggruppamento regionale e l’ordinamento operano tutti sui codici ISO. Solo il testo finale mostrato all’utente passa attraverso la localizzazione.
Estendere la correzione ad altri dati
Dopo aver corretto il bug delle bandiere, abbiamo esaminato il resto del codice alla ricerca dello stesso schema. Il problema compariva in altri tre punti: un modulo di visualizzazione delle valute che usava i nomi localizzati come chiavi di cache, un selettore di lingua che memorizzava i nomi localizzati nelle preferenze dell’utente e un modulo di formattazione delle date che confrontava nomi dei mesi dipendenti dalle impostazioni locali.
La correzione ha seguito lo stesso schema in ogni caso: sostituire la stringa dipendente dalle impostazioni locali con il suo equivalente stabile ISO o IETF. Codici valuta ISO 4217 al posto dei nomi delle valute, tag di lingua BCP 47 al posto dei nomi localizzati delle lingue, valori numerici dei mesi al posto delle stringhe con i loro nomi. Le stringhe localizzate diventano una responsabilità esclusiva del livello di visualizzazione.
Valeva la pena svolgere questa verifica sistematicamente. Tutti questi bug sarebbero rimasti invisibili nei test con impostazioni locali inglesi e sarebbero emersi solo in produzione, su dispositivi configurati per altre lingue. Dato che l’app si rivolgeva a un pubblico multilingue, l’impatto sarebbe stato significativo.
La portata del problema in produzione
Uno dei motivi per cui questa classe di bug persiste è la sua invisibilità negli ambienti di sviluppo con un’unica impostazione locale. Uno sviluppatore che lavora con impostazioni inglesi su un’app destinata a un pubblico internazionale può scrivere e testare l’intera funzione di visualizzazione dei Paesi senza mai attivare il bug. Il bug si manifesta solo quando le impostazioni locali del dispositivo non corrispondono a quelle con cui è stato costruito il dizionario o la mappatura: proprio la condizione in cui si trova la maggior parte degli utenti di riferimento.
Nei widget, il divario è ancora più ampio rispetto all’app principale perché vengono eseguiti nel contesto limitato di un’estensione. L’estensione del widget usa le impostazioni locali correnti del sistema, che possono differire da quelle usate nei test dello sviluppatore. Anche il debug è meno diretto rispetto all’app principale: esegui lo schema dell’estensione del widget in Xcode (oppure usi Debug > Attach to Process), e il codice della timeline e della vista viene eseguito secondo la pianificazione di WidgetKit anziché in risposta alle tue interazioni. Individuare i bug di rendering dipendenti dalle impostazioni locali nei widget richiede di eseguire l’estensione con le impostazioni locali rilevanti, usare le anteprime di Xcode con voci di esempio della timeline e verificare attentamente il contesto effettivo di rendering dell’estensione: un passaggio facile da saltare finché un utente non segnala il problema in produzione.
La risposta pratica è considerare tutta la logica di visualizzazione dipendente dalle impostazioni locali come una categoria di rischio, non come un singolo bug da correggere. La revisione del codice dovrebbe verificare l’uso di stringhe dipendenti dalle impostazioni locali come identificativi. La domanda è semplice: questa stringa passa attraverso un metodo di localizzazione prima di essere usata come chiave o valore di confronto? Se sì, il progetto deve cambiare. Codici ISO, identificativi numerici e altri riferimenti indipendenti dalle impostazioni locali devono sostituire le stringhe localizzate in ogni livello, tranne nella visualizzazione finale.
Test con diverse impostazioni locali
La lezione più diretta di questo bug è che i test con diverse impostazioni locali devono far parte della matrice di test standard, non essere un’aggiunta tardiva. iOS lo rende relativamente semplice: il simulatore permette di cambiare le impostazioni locali senza cambiare la lingua del sistema e i test dell’interfaccia possono essere parametrizzati in base a queste impostazioni.
Per la visualizzazione delle bandiere e dei Paesi, abbiamo aggiunto test di istantanea eseguiti con un insieme fisso di impostazioni locali del dispositivo, inglesi, tedesche, francesi, giapponesi e arabe. Verificano che le emoji delle bandiere per un insieme fisso di codici ISO restino corrette in tutti i casi. Questi test rilevano subito le regressioni se qualcuno introduce una ricerca dipendente dalle impostazioni locali nel percorso di visualizzazione.
Il principio generale è che qualsiasi funzione che coinvolga localizzazione, internazionalizzazione o visualizzazione dipendente dalla regione dovrebbe avere test automatici con più impostazioni locali. Una verifica manuale della qualità in un’unica impostazione locale non basta. I bug dovuti a ipotesi dipendenti dalle impostazioni locali sono comuni e restano sistematicamente invisibili nei test condotti con una sola impostazione locale.
Approfondimenti correlati
- Dynamic Island per le app con timer: implementazione e lezioni apprese — un altro contesto di estensione iOS in cui il comportamento della piattaforma differisce da quanto emerge nei test all’interno dell’app
- Tabelle di chiusura per il calcolo delle relazioni — usare identificativi stabili, ID anziché nomi, per costruire una logica affidabile di ricerca e attraversamento