In questo articolo esporrò, anche grazie a diagrammi UML, come poter progettare un traduttore software, che chiamerò Translator, utilizzabile in qualsiasi applicazione, server o client non importa.
Il componente software in questione è stato da me realizzato su richiesta di un cliente che di recente ha aperto servizi web a filiali estere che, non conoscendo nulla della lingua italiana, altrimenti non avrebbero potuto utilizzare con profitto i servizi forniti.
Chiaramente, il componente non è istruito, nel senso di essere in grado di eseguire delle traduzioni di testi, e quindi bisognerà prepararlo in questo senso.
Il set di dizionari in cui cercare ed attingere i termini tradotti saranno rappresentati da una tabella del database relazionale MS SQL Server e possono essere infiniti linguaggi diversi.
Grazie al supporto fornito dalla suddetta tabella, il componente è in grado di estrarre la traduzione corretta conoscendo il relativo termine da tradurre e la lingua, o idioma, dell'utente.
Le specifiche che la classe Translator deve soddisfare (cioè le richieste avanzate dal cliente), riassunte comodamente nel caso d'uso UML "Translator Features" dicui sopra, sono le seguenti:
- Il componente deve tradurre tutte le etichette, avvisi e testi dell'applicativo nella lingua dell'utente utilizzatore;
- Il dizionario deve risiedere su un Server DB, in particolare è stato scelto MS SQL Server;
- Il componente deve soddisfare tutte le richieste di traduzione con un unica istanza di classe per tutto il ciclo di vita dell'applicativo e per ogni sessione utente (pattern Singleton);
- Deve segnalare tutti i termini di cui è stata richiesta la traduzione ma della quale non c'é traccia nel dizionario;
- Deve poter essere utilizzabile sia dalla parte di View che di Business Logic dell'applicativo (pattern MVC);
Direi che le specifiche sono tutte chiare e perseguibili.
Il quarto punto, però, ha bisogno di essere chiarito in miglior modo: come è possibile che Translator possa segnalare la mancanza di traduzione per determinati termini? Ed in che modo?
Dunque, la classe è intelligente e conoscendo lo stato dei dati di proprio dominio ogni volta che non riesce a fornire una traduzione, per mancanza del termine tradotto in tabella, eseguirà una istruzione SQL di INSERT in tabella, aggiungendo il termine richiesto; sarà poi compito di qualcun altro (una persona incaricata) aggiungere le traduzioni per i termini segnalati.
Il vantaggio di questa soluzione è che il dizionario crescerà pari passo con l'espandersi dell'applicazione, quindi il dizionario sarà sempre ottimizzato, nel senso che non conterrà mai termini e traduzioni non utilizzati.
Di contro, invece, vi è la necessità dell'intervento di una persona esterna che dovrà colmare la mancanza delle traduzioni, ma solo su termini essenziali.
Base Dati: La Tabella Dictionary
Come anticipato sopra, la base dati scelta per lo storing dei dizionari di termini necessari per rendere l'applicazione multi-lingua è MS SQL Server.
Dopo aver creato il database, al suo interno deve essere creata la tabella dictionary: è richiesta dal componente Translator per il caricamento dei dati e per l'aggiunta e la segnalazione di nuovi termini da tradurre.
Il comando SQL per generare la tabella dictionary è il seguente:
CREATE TABLE [dbo].[dictionary] (
[language_id] [char] (2)
COLLATE Latin1_General_CI_AS NOT NULL ,
[description_code] [varchar] (200)
COLLATE Latin1_General_CI_AS NOT NULL ,
[description_des] [nvarchar] (50)
COLLATE Latin1_General_CI_AS NULL ,
[context_des] [varchar] (100)
COLLATE Latin1_General_CI_AS NULL ,
[uts_code] [varchar] (50)
COLLATE Latin1_General_CI_AS NULL
) ON [PRIMARY]
I campi della sono utilizzati nella maniera seguente:
- language_id: la sigla dell'idioma con cui è stato tradotto il termine richiesto;
- description_code: il termine del quale è stata richiesta la traduzione;
- description_des: la traduzione del termine nell'idioma richiesto;
- context_des: questo è un campo accessorio di controllo che viene utilizzato per salvare l'origine di eventuali segnalazioni di traduzioni mancanti;
- uts_code: anche questo è un campo accessorio per il salvataggio del momento in cui è scattata una eventuale segnalazione, in formato UTS;
La classe Translator: design e struttura
La classe Translator incapsula un'istanza singleton di una HashTable (un componente che conserva i dati abbinando un oggetto ad una chiave univoca ed atomica) che viene caricata, con tutto il contenuto della tabella SQL dictionary, al suo primo utilizzo e che viene utilizzata e gestita attraverso una serie di metodi pubblici e privati a livello di classe (o static).
I dati estratti dalla tabella SQL vengono conservati attraverso molteplici istanze della classe Label che è fondamentalmente un javabean comune tranne per il fatto che non permette a runtime di cambiare il valore della proprietà key e che possiede un solo costruttore privato accessibile tramite il metodo pubblico di classe getInstance(String, String, String);
La struttura delle classi coinvolte nonché il loro design e le relazioni che intercorrono tra esse sono ottimamente descritte nel seguente diagramma UML di Classe:
Sebbene il diagramma UML sia chiaro ed il suo contenuto facilmente intuibile, espongo il fine di ogni proprietà della classe Translator:
- globalDictionary: è la hashmap nel quale viene organizzato il dizionario per le traduzioni. Ha l'attributo static per garantire che sia l'unica istanza alla quale accederanno i client per richiedere la traduzione dei termini richiesti. Soddisfa le condizioni imposte dal design pattern Singleton.
- SQL_LOADER: è l'istruzione SQL di select per caricare l'intero dizionario all'avvio dell'applicazione.
- SQL_SETTER: è l'istruzione SQL di insert per aggiungere nuovi termini da tradurre per i quali bisognerà intervenire a mano per abbinare la traduzione.
Passo ad illustrare la logica dei metodi di classe, che sono:
- addNonExistingLabel(String, String, String): membro pubblico e statico che la classe utilizza per segnalare un nuovo termine richiesto di cui però manca la traduzione. Accede al DB ed esegue l'istruzione SQL_SETTER per inserire in tabella il nuovo termine da tradurre. Potrebbe benissimo essere un metodo private ma in questa maniera può tornare utile se si decidesse di caricare per la prima volta il database SQL Server attraverso la classe Translator.
- init(): membro privato e statico che la classe richiama all'avvio per inizializzare e caricare il dizionario. Si preoccupa di accedere alla tabella dictionary del DB per leggere le traduzioni, di wrappare i dati in istanze della classe Label e di caricare con esse l'istanza singleton del dizionario, ovvero la proprietà globalDictionary.
- refreshDictionary(): questo metodo, anch'esso statico e pubblico, è utile per ricaricare lato server il dizionario senza stoppare l'applicazione, nel caso per esempio si trattasse di una web application.
- translate(String, String, String): è il metodo da richiamare per eseguire la traduzione di un termine. Cerca la traduzione all'interno della hashmap: l'accesso avviene tramite la chiave creata componendo la lingua, passata come primo parametro, ed il termine da tradurre, cioè il secondo parametro. Nel caso non fosse trovata la traduzione viene invocato il membro addNonExistingLabel(String, String, String) e restituito il termine richiesto originale. Il terzo parametro lo utilizzo per passare il nome della classe in cui è stata richiesta la traduzione, per tenerne traccia.
La classe Label è un semplice javabean per wrappare i record della tabella dictionary.
Alla prossima,
MA.


0 commenti:
Posta un commento
Non ti è chiaro qualcosa?
No problem, posta il tuo dubbio ;)
..... e ricordati di firmarlo!