No. 103

accentiTitolo originale: Accent Folding for Auto-Complete

Scritto da Carlos Bueno

Pubblicato in: Scripting, Industria, Tipografia, User Interface Design, Accessibilità, Usabilità

E' appena passata un'altra generazione di tecnologie e Unicode è ormai supportato praticamente ovunque. Il prossimo passo è quindi quello di scrivere software che non sia solo “internazionalizzato” ma veramente multi-lingua. In questo articolo passeremo rapidamente in rassegna un po' di storia e di teoria, per poi illustrare un hack efficace chiamato accent-folding. L'accent-folding ha i suoi limiti ma può comunque aiutare a rendere migliori alcuni aspetti dell'interazione utente, importanti sebbene spesso sottovalutati.

Nel processo di internazionalizzazione si dà per assunto che qualunque utente rientri perfettamente in una singola locale, come ad esempio “English, United States” o “French, France.”. Si tratta di un retaggio del passato, quando per il PC era già parecchio riuscire a mostrare i giusti bit. Un byte equivaleva ad un carattere solo, senza eccezioni, e si poteva caricare un solo alfabeto alla volta. Questo andava bene perché era meglio che niente e perché gli utenti passavano la maggior parte del loro tempo con dei documenti che avevano creato essi stessi o i loro collaboratori.

Oggi gli utenti hanno a che fare ogni giorno con dati provenienti da tutto il mondo, in più lingue e locali. La locale che io in quanto utente preferisco è solo debolmente correlata con le locali che le applicazioni mi aspetto processino.

Prendete per esempio questa rubrica:

  • Fulanito López
  • Erik Lørgensen
  • Lorena Smith
  • James Lö

Se compongo un nuovo messaggio ed inserisco “lo” nel campo “A:”, cosa può succedere? In molte applicazioni, solo il nome Lorena verrà mostrato nell'elenco dei suggerimenti. Queste applicazioni “supportano Unicode,” nel senso che non lo sporcano e non vi pasticciano, ma questo è tutto.

Screenshot dei suggerimenti automatici della rubrica indirizzi di Microsoft Entourage, che non applica l'accent-folding.

Fig 1. Ehi Entourage, dove sono i miei contatti?

Questo problema non si trova esclusivamente nelle rubriche indirizzi. Pensate alle inbox, ai social bookmark, ai feed dei commenti, agli utenti che parlano più lingue, agli internet café in paesi stranieri, perfino agli URLs. Provate a cercare il giornalista Ryszard Kapuściński e guardate come differenti siti web trattino il suo nome:

Non ci sono scuse: il vostro software non può comportarsi in modo così ottuso quando l'utente digita “caffe” invece di “caffé.”

Áçčềñṭ-Ḟøłɖǐṅg

In specifiche applicazioni di ricerca che preferiscono richiamare un maggior numero di risultati piuttosto che essere più precisi, come nel nostro esempio della rubrica indirizzi, á, a, å, e â possono essere trattate come equivalenti. Gli accenti (altresì noti come segni diacritici) sono suggerimenti di come deve essere la pronuncia, ma non influenzano il significato della parola. Digitare le lettere accentate può rivelarsi complicato, soprattutto sui dispositivi mobili.

Esempio di una widget YUI Autosuggest a cui è aggiunto l'accent-folding.

Fig 2. Accent-folding nella widget di suggerimento automatico.

Una funzione accent-folding sostanzialmente mappa i caratteri Unicode al loro equivalente ASCII. Ovunque applichiate il case-folding, dovreste considerare di applicare anche l'accent-folding, esattamente per le stesse ragioni. Con l'accent-folding, non importa se l'utente cerca caffe, caffé o perfino çåFFé;: i risultati saranno gli stessi.

Attenzione, però! Ci sono milioni di problemi nelle regole degli accenti. Quasi sicuramente sbaglierete qualcosa da qualche parte. Quasi tutti gli alfabeti hanno dei segni super-speciali che influiscono anche sul significato e ovviamente tutti gli alfabeti non Occidentali hanno regole totalmente diverse da quelle a cui siamo abituati.

Un problema minore è l'alfabeto Unicode "fullwidth" Roman: si tratta di un insieme semplici caratteri ASCII di larghezza fissa pensati per allinearsi con i caratteri Cinesi/Giapponesi/Koreani (e.g., "1979年8月15日"). Si trovano tra 0xff00 e 0xff5e e dovrebbero essere trattati come equivalenti dei loro corrispettivi ASCII.

Ehi, sono qui solo per fare copia/incolla!

Ho messo su GitHub degli esempi più completi, ma per illustrare la tecnica, ecco un esempio base di accent-folding utilizzando Javascript:

(Il codice che prosegue su una nuova riga è segnato con » —ndr)

  
var accentMap = {
'á':'a', 'é':'e', 'í':'i','ó':'o','ú':'u'
};

function accent_fold (s) {
if (!s) { return ''; }
var ret = '';
for (var i = 0; i < s.length; i++) {
ret += accentMap[s.charAt(i)] || s.charAt(i);
}
return ret;
};

Espressioni regolari

Le espressioni regolari sono difficili da gestire se si vuole che si “rendano conto degli accenti”. Notate come in Fig. 2 solo i caratteri non accentati siano in grassetto. Il problema è che il layout dei caratteri Unicode non si presta ai pattern trasversali alle lingue. L'espressione regolare appropriata per “lo” sarebbe qualcosa di pazzesco come:

  [LlĹ弾ĻļḶḷḸḹḼḽḺḻŁłŁłĿŀȽƚɫ][OoÓóÒòŎŏÔôỐốỒồỖöȪȫŐőÕõṌȭȮȯǾǿ...ǬǭŌ]

Non fate mai una cosa del genere! Mai! Al momento in cui scriviamo, ci sono pochi motori per espressioni regolari che supportano shortcuts per le classi di caratteri Unicode. I più avanzati sembrano essere PCRE e Java. Probabilmente non dovreste forzare questo meccanismo, ma al contrario cercare di evidenziare una versione accent-folded [indipendente dagli accenti, ndr] della stringa e poi usare la posizione del carattere per sottolineare l'originale, come in questo esempio:

  
// accent_folded_hilite("Fulanilo López", 'lo')
// --> "Fulanilo pez"
//


function accent_folded_hilite(str, q) {
var str_folded = accent_fold(str).toLowerCase() »
.replace(/[]+/g, '');
var q_folded = accent_fold(q).toLowerCase() »
.replace(/[]+/g, '');

// Creazione di una stinga intermedia con i suggerimenti evidenziati
// Esempio: fulani pez

var re = new RegExp(q_folded, 'g');
var hilite_hints = str_folded.replace(re, »

'');

// Puntatore all'indice della stringa originale
var spos = 0;
// Accumulatore per la nostra stringa finale
var highlighted = '';

// Scorriamo in parallelo la stringa originale e la stringa
// con i suggerimenti evidenziati.
// Quando nella stringa con i suggerimenti incontriamo un
// appendiamo nella stringa finale un carattere
// di tag aperto o chiuso e incrementiamo il contatore della
// stringa con i suggerimenti. Se incontriamo un carattere che
// non è un simbolo di suggerimento appendiamo il carattere
// della stringa originale nella stringa finale e incrementiamo
// entrambe i contatori.


for (var i = 0; i< hilite_hints.length; i++) {
var c = str.charAt(spos);
var h = hilite_hints.charAt(i);
if (h === '<') {
highlighted += '';
} else if (h === '>') {
highlighted += '
';
} else {
spos += 1;
highlighted += c;
}
}
return highlighted;
}

L'esempio precedente è probabilmente troppo semplificato per poter essere usato nel codice in produzione. Ad esempio, non si possono sottolineare termini multipli. Alcuni caratteri speciali potrebbero espandersi su due caratteri, come “æ” --> “ae” con la conseguenza di mandare a gambe all'aria spos. Inoltre, elimina i segni di maggiore e minore () presenti nella stringa originale. Ma va comunque bene come primo passo.

Accent-folding nella libreria Autocomplete di YUI

La libreria Autocomplete di YUI [Yahoo! User Interface, ndr] ha molti spunti ed opzioni con cui giocare. Oggi vedremo due metodi riscrivibili: filterResults() e formatMatch(). Il metodo filterResults ci permette di scrivere la nostra funzione di confronto. Il metodo formatMatch ci permette di cambiare l'HTML di una entry nella lista di accoppiamenti suggeriti. Potete inoltre scaricare un esempio completo su cui lavorare, compreso tutto il codice sorgente.

(Il codice che prosegue su una nuova riga è segnato con » ndr)



<!-- questo è importante per dire a javascript di trattare le stringhe
come UTF-8 -->

<meta http-equiv="content-type" content="text/html;charset=utf-8" />

<!-- YUI stylesheets -->

<link rel="stylesheet" type="text/css"
href="http://yui.yahooapis.com/2.7.0/build/fonts/fonts-min.css" />
<link rel="stylesheet" type="text/css"
href="http://yui.yahooapis.com/2.7.0/build/autocomplete/assets »
/skins/sam/autocomplete.css" />


<!-- librerie YUI: eventi, sorgenti e completamento automatico -->

<script type="text/javascript"
src="http://yui.yahooapis.com/2.7.0/build/yahoo-dom-event »
/yahoo-dom-event.js"></script>
<script type="text/javascript"
src="http://yui.yahooapis.com/2.7.0/build/datasource »
/datasource-min.js"></script>

<script type="text/javascript"
src="http://yui.yahooapis.com/2.7.0/build/autocomplete »
/autocomplete-min.js"></script>


<!-- contiene accent_fold() e accent_folded_hilite() -->

<script type="text/javascript" src="/accent-fold.js"></script>


<!-- Assegna a <body> la "skin" YUI -->

<body class="yui-skin-sam">
<b>To:</b>
<div style="width:25em">


<!-- il nostro campo "to" -->

<input id="to" type="text" />


<!-- Un <div> vuoto per contenere l'autocomplete -->

<div id="ac"></div>

</div>
</body>

<script>


// La nostra rubrica indirizzi statica resa come lista
di hash tables

var addressBook = [
{name:'Fulanito López', email:'fulanito@example.com'},
{name:'Erik Lørgensen', email:'erik@example.com'},
{name:'Lorena Smith', email:'lorena@example.com'},
{name:'James Lö', email:'james@example.com'}
];
{emailcloak=on}
/*
Cicla sulla nostra rubrica indirizzi e aggiunge un nuovo campo ad ogni
riga chiamato "search." Questo contiene una versione accent-folded del
campo "name".
*/

for (var i = 0; i< addressBook.length; i++) {
addressBook[i]['search'] = accent_fold(addressBook[i]['name']);
}

// Creazione di un oggetto datasource YUI dalla nostra rubrica grezza
var datasource = new YAHOO.util.LocalDataSource(addressBook);

/*
Un datasource è una tabella, ma il nostro array di hash tables non ho il
concetto dell'ordinamento in colonna. Quindi bisogna dire al datasource
come ordinare le colonne
*/

datasource.responseSchema = {fields : ["email", "name", "search"]};

/*
Istanziare la widget di autocompletamento con il riferimento al campo di
input, al div vuoto e all'oggetto datasource.
*/

var autocomp = new YAHOO.widget.AutoComplete("to", "ac", datasource);

// Permette l'inserimento di più entri specificando come delimitatori
//lo spazio e la virgola

autocomp.delimChar = [","," "];

/*
Aggiunge un nuovo metodo filterResults() all'oggetto "autocomplete":
cicla sul datasource e cerca "q" all'interno del campo "search".
Questo metodo viene richiamato ogni volta che l'utente inserisce un
nuovo carattere nel campo di input.
*/

autocomp.filterResults = function(q, entries, resultObj, cb) {
var matches = [];
var re = new RegExp('\\b'+accent_fold(q), 'i');
for (var i = 0; i < entries.length; i++) {
if (re.test(entries[i]['search'])) {
matches.push(entries[i]);
}
}
resultObj.results = matches;
return resultObj;
};

/*
Aggiunge un metodo formatResult(). Viene richiamato su
ciascun risultato ritornato da formatResult(). Ritorna
una buona rappresentazione HTML del confronto.
In questo metodo abbiamo eseguito la funzione di evidenziazione
accent-folded sul nome e sull'e-mail.
*/

autocomp.formatResult = function (entry, q, match) {
var name = accent_folded_hilite(entry[1], q);
var email = accent_folded_hilite(entry[0], q);
return name + ' ';
};

//fine



Migliaia di complicazioni...

La soluzione dell'accent-folding funziona principalmente per le lingue dell'Europa occidentale, ma non va bene per tutte. Sfrutta le peculiarità di questa famiglia di lingue ed i problemi limitati al dominio dei nostri esempi, dove è meglio avere qualche risultato piuttosto che nessun risultato. In Germania, sarebbe più corretto che Ü venisse mappato con Ue invece che solo a U. Un francese che cerchi sul web la parola thé (tea) sarebbe infastidito da tutti i risultati irrilevanti in inglese che darebbe la ricerca.

Fino ad ora, si può solo mappare un singolo carattere. Sarebbe molto complicato ricondurre “Marc Chagall” scritto con caratteri latini all'equivalente in alfabeto cirillico “Марк Шагал” o in Yiddish “מאַרק שאַגאַל.” Ci sono molte interessanti analogie tra i caratteri, ma un mapping bidirezionale svincolato dal contesto è probabilmente impossibile.

In capo a ciò c'è un altro problema: una lingua può avere più di un sistema di scrittura. La translitterazione consiste nello scrivere una lingua in un diverso alfabeto. Non è esattamente come la trascrizione, che mappa i suoni come in “hola, que paso” in “oh-la, keh pah-so.”. La translitterazione cerca di mappare i simboli scritti in un altro alfabeto, idealmente in maniera reversibile.

Queste quattro frasi dicono tutte “Ai bambini piace guardare la televisione” in Giapponese:

  • Kanji: 子供はテレビを見るのが好きです。
  • Hiragana: こども は てれび を みる の が すき です 。
  • Romaji: kodomo wa terebi o miru noga suki desu.
  • Cyrillic: кодомо ва тэрэби о миру нога суки дэсу.

Per secoli le persone si sono inventate modi per scrivere diverse lingue con qualunque tastiera o con qualsiasi sistema tipografico avessero a disposizione. Così, anche se l'utente legge solo una lingua, può leggerla in schemi di translitterazione multipli. Alcuni schemi sono logici ed accademici, ma spesso sono sistemi organici disordinati che dipendono da accenti regionali o da codice di scarsa qualità utilizzato da molto tempo. L'era del computer ha favorito una nuova esplosione di sistemi dovuta al fatto che le persone hanno imparato a chattare e mandare email con il semplice ASCII.

Molto lavoro è già stato fatto su questo problema e ci sono due strade già delineate tra cui scegliere: la strada giusta e quella che forse potrebbe essere una buona strada. Nessuna di queste ha la semplicità e l'ingenuità delle nostre hash tables, ma saranno meno fastidiose per i vostri utenti nelle applicazioni in generale.

La prima strada è la International Components for Unicode (ICU), un progetto che ha avuto inizio nei primi anni '90 nell'Unione Europea. Ha come scopo quello di essere una libreria per translitterazioni completa, che riconosce la lingua, che utilizza Unicode e con capacità di formattazione... Insomma, fa tutto. E' enorme ed è scritta in C++/Java; richiede una conoscenza contestuale degli input e degli output per poter lavorare.

La seconda strada è Unidecode, una libreria per translitterazione libera dal contesto disponibile per Perl e per Python. Cerca di fare la translitterazione di tutti i caratteri Unicode verso un alfabeto latino di base. Non cerca di essere reversibile, per uno specifico linguaggio e nemmeno generalmente corretta: è un hack rapido e sporco ma ciò nonostante piuttosto comprensibile.

L'accent-folding messo nei posti giusti risparmia agli utenti un po' di tempo e rende il vostro software più intelligente ed elegante. La stretta segregazione delle lingue e delle locali è parzialmente un artefatto delle limitazioni della tecnologia che non ha più ragion d'essere. Dipende da voi quanto far avanzare le cose, ma anche un minimo sforzo può farvi fare molta strada. Buona fortuna!

Share/Save/Bookmark
 

Discutiamone

Ti sembra interessante? Scrivi tu il primo commento


Cenni sull'autore

Carlos Bueno

Carlos Bueno

Carlos Bueno è un software engineer in Xoopit / Photos for Yahoo! Mail. Occasionalmente scrive su dell'internazionalizzazione, della performance e della sicurezza su cui spesso si sorvola. Vive a San Francisco.