Technick.com S.r.l. - Open Source Software

Localizzazione in Java tramite lo standard TMX

Nicola Asuni, 2004-10-14 / 2006-06-04

Introduzione

Uno degli aspetti fondamentali dell'internazionalizzazione consiste nel separare dal nucleo del codice sorgente i testi, le etichette, i messaggi ed altre informazioni sensibili alla localizzazione. Questo consente di mantenere la stessa base di codice sorgente per tutte le versioni del prodotto nelle diverse lingue e facilita il processo di traduzione perchè tutte le risorse sensibili al contesto locale sono ben identificate ed isolate.

Fin dalla JDK 1.1 Java offre un vasto supporto all'internazionalizzazione (i18n) attraverso una molteplicità di strumenti che consentono diversi approcci. In quest'ottica rivestono una particolare importanza il supporto all'Unicode 2.0, gli ambienti multilingua e la localizzazione degli oggetti, giusto per citarne alcuni.
Tuttavia questi strumenti possono risultare insufficienti quando il nostro mercato di riferimento è quello globale in cui i costi di traduzione e manutenzione dei testi (che comprendono le etichette, i messaggi, gli elementi di menù, ...) possono assumere dimensioni consistenti.
In quest'ottica, l'adozione dello standard TMX consente di introdurre nella gestione dei testi e delle traduzioni i concetti di riuso, aumento della consistenza, riduzione della durata del ciclo produttivo e riduzione dei costi.

TMX - Translation Memory eXchange

http://www.lisa.org/tmx/

TMX è un open standard che utilizza l'XML per l'archiviazione e l'interscambio di memorie di traduzione (TM - Translation Memory) create attraverso l'ausilio di strumenti software di traduzione e localizzazione (CAT - Computer Aided Translation).
TMX è il risultato di un'iniziativa presa da uno degli Special Interest Group di LISA conosciuto come OSCAR (Open Standards for Container/Content Allowing Re-use).

Lo scopo del TMX è quello di offrire un sistema neutrale per lo scambio di dati tra diversi sistemi di traduzione che minimizzi o elimini la perdita di dati critici.
Il formato TMX è supportato dalla maggior parte dei software di traduzione.

Le specifiche dello standard TMX sono liberamente disponibili nel sito http://www.lisa.org/tmx/ dove potrete trovare numerosi riferimenti, documenti, articoli e strumenti software.

Esempio di codice TMX

sample_tmx.xml
<?xml version="1.0" ?>
<tmx version="1.4">
	<header
		creationtool="XYZTool"
		creationtoolversion="1.01-023"
		datatype="PlainText"
		segtype="sentence"
		adminlang="en-us"
		srclang="EN"
		o-tmf="ABCTransMem">
	</header>
	<body>
		<tu tuid="hello" datatype="plaintext">
			<tuv xml:lang="en">
				<seg>hello</seg>
			</tuv>
			<tuv xml:lang="it">
				<seg>ciao</seg>
			</tuv>
		</tu>
		<tu tuid="world" datatype="plaintext">
			<tuv xml:lang="en">
				<seg>world</seg>
			</tuv>
			<tuv xml:lang="it">
				<seg>mondo</seg>
			</tuv>
		</tu>
	</body>
</tmx>
dove:

TM - Translation Memory

http://www.opentag.com/tm.htm

Translation Memories (TM), conosciute anche come Translation Databases, sono essenzialmente costituite da un database (archivio) dove le frasi del testo di riferimento sono associate alle corrispondenti traduzioni in una o più lingue.
L'insieme della frase di riferimento con le sue traduzioni è detto translation memory unit.
Le applicazioni che fanno uso delle TM, sono strumenti di ausilio alla traduzione linguistica, progettate per migliorare la qualità e l'efficenza del processo di traduzione umana e non per sostituirlo.
Ogni nuova frase inserita attraverso l'applicazione TM verrà cercata tra le frasi di riferimento nel database e ne verrà calcolato un valore di corrispondenza.
Quando il valore di corrispondenza sarà del 100% (exact match) la traduzione presente nel database verrà direttamente inserita nel testo da tradurre. Quando il valore di corrispondenza sarà inferiore al 100% ma superiore ad una certa soglia (fuzzy match), la frase tradotta verrà inserita come traduzione proposta che dovrà essere verificata ed eventualmente corretta da traduttore umano. Le frasi con un margine di corrispondenza sotto il margine di soglia andranno tradotte manualmente per intero. Le nuove frasi inserite verranno registrate nel database per esser utilizzate per future traduzioni.

Diverse aziende offrono complessi prodotti commerciali in questo settore.

LISA - Localization Industry Standards Association

http://www.lisa.org

Fondata nel 1990, LISA è la principale organizzazione mondiale no-profit per il GILT (Globalizzazione, Internazionalizzazione, Localizzazione e Traduzione). LISA si rivolge ad individui, aziende, associazioni ed organizzazioni per gli standard, che si occupano di lingue e tecnologie per le lingue.

Oltre 400 aziende leader nel settore IT, service providers e professionisti che rappresentano le grandi aziende con mercati internazionali, hanno contribuito a stabilire le linee guida di LISA basate sul best practice e gli standard tecnologici per le lingue a supporto della globalizzazione.

LISA mette in contatto fra loro numerosi soggetti che contribuiscono al processo di internazionalizzazione delle aziende e che comprendono: consumatori, governi, organizzazioni per gli standard, istituti di ricerca e consulenza, fornitori di servizi e di tecnologie per la taduzione e localizzazione.

LISA offre servizi sotto forma di iniziative per la standardizzazione, Special Interest Groups, conferenze e programmi di apprendimento di supporto GILT alle imprese.

I soci ed i gruppi affiliati a LISA comprendono l'International Organization for Standardization (ISO Liaison Category A Members of TC 37 and TC 46), World Bank, OASIS, IDEAlliance, AIIM, Advisory Council (TAC), Fort-Ross, &euro;TTEC, Japan Technical Communicators Association, Society of Automotive Engineers (SAE), European Union, Canadian Translation Bureau, TermNet, American Translators Association (ATA), IWIPS, Fédération Internationale des Traducteurs (FIT), Termium, JETRO, Institute of Translating and Interpreting (ITI), Unicode Consortium, OpenI18N, professionisti ed organizzazioni per il commercio.

Tra i membri ed i co-fondatori di LISA spiccano alcune tra le aziende più grandi e conosciute a livello mondiale: Adobe, Avaya, Cisco Systems, CLS Communication, EMC, Hewlett Packard, IBM, Innodata Isogen, Fuji Xerox, Microsoft, Oracle, Nokia, Logitech, SAP, Siebel Systems, Standard Chartered Bank, FileNet, LionBridge Technologies, Lucent, Sun Microsystems, WH&P, PeopleSoft, Philips Medical Systems, Rockwell Automation, The RWS Group, Xerox Corporation and Canon Research.

Bridge Java per TMX

Attraverso la java.util.ResourceBundle class, Java dispone di una soluzione pratica per la localizzazione. È infatti possibile estrarre gli elementi testuali dal codice sorgente originale ed isolarli in un componente ResourceBundle quale ad esempio la classe ListResourceBundle o un file di proprietà.
Questa soluzione offre dei sicuri vantaggi al programmatore ma può risultare piuttosto complessa dal punto di vista del traduttore, sopratutto in termini di riusabilità della traduzione.

Un eccellente alternativa consiste nell'archiviazione delle risorse testuali nel formato di interscambio TMX (file XML) che consente ai traduttori di importare ed esportare le traduzioni da o verso i loro strumenti di traduzione (ne esistono diversi in commercio compatibili con TMX) in maniera totalmente indipendente dal linguaggio di programmazione utilizzato.

Così come suggerito dall'articolo Use XML as a Java Localization Solution di Masaki Itagaki, la soluzione ottimale per implementare lo standard TMX nelle applicazioni Java, consiste nell'estendere la classe ResourceBundle in modo che possa leggere direttamente i dati da file XML conformi allo standard TMX (classe Java ==> file TMX <== programma di traduzione).
Cio consente di sfruttare tutti i vantaggi offerti dalla classe ResourceBundle e di rendere più semplice il porting verso TMX delle applicazioni esistenti.

Gli svantaggi offerti da questa tecnica sono principalmente dovuti al tempo ed alla memoria necessaria per il caricamento dell'intero file TMX.

Al fine di semplificare l'esposizione, prenderemo in considerazione solo quegli elementi TMX necessari a tradurre un testo semplice (vedi sample_tmx.xml): per esempio:
	<tu tuid="hello" datatype="plaintext">
		<tuv xml:lang="en">
			<seg>hello</seg>
		</tuv>
		<tuv xml:lang="it">
			<seg>ciao</seg>
		</tuv>
	</tu>

La classe TMXResourceBundle.java

Istanziare la classe TMXResourceBundle è come istanziare la classe PropertyResourceBundle.
Attraverso il costruttore specifichiamo il nome ed il percorso del file in formato TMX dove sono contenute le traduzioni ed il codice ISO della lingua di cui vogliamo estrarre le traduzioni.
Quando la classe è istanziata, il metodo parseXmlFile che fa uso del parser XML di Sun (javax.xml.parsers.DocumentBuilder.parse), provvede a caricare i dati TMX in un oggetto di tipo org.w3c.dom.Document (DOM - Document Object Model).
A questo punto i nodi del documento vengono analizzati e le coppie chiave-valore vengono aggiunte ad hashcontents (un oggetto di tipo java.util.Hashtable). Le coppie chiave-valore sono costituite rispettivamente dall'attributo tuid dell'elemento tu e dal valore del nodo seg contenuto all'interno del nodo tuv in cui il valore dell'attributo xml:lang è identico a quello della lingua specificata.
L'estensione della classe ResourceBundle richiede la ridefinizione (overriding) dei metodi astratti handleGetObject e getKeys così da poter ricavare l'elemento associato ad una determinata chiave attraverso i metodi ereditati da ResourceBundle: getObject(String key), getString(String key), getStringArray(String key).
L'overload getString(String key, String def) del metodo getString(String key) di ResourceBundle consente di ottenere la stringa associata ad una determinata chiave o un valore di default in caso di errore.

Codice Sorgente

(download the full project from Sourceforge).
package com.tecnick.tmxjavabridge;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * <p>
 * Reads resource text data directly from a TMX (XML) file.
 * </p>
 * <p>
 * First, the TMXResourceBundle class instantiates itself with two parameters: a
 * TMX file name and a target language name. Then, using a DOM parser, it reads
 * all of a translation unit's properties for the key information and specified
 * language data and populates a hashtable with them.
 * </p>
 * <p>
 * <b>TMX info: </b> http://www.lisa.org/tmx/
 * </p>
 * 
 * <h4>Implementation notes</h4>
 * <p>
 * You instantiate the TMXResourceBundle class in a program to read data from a
 * TMX file. Once the class is instantiated, it reads all the data in a TMX file
 * and loads into a DOM tree. Then it populates a hashtable so the
 * handleGetObject() method can be called to find text information based on a
 * key just as a standard ResourceBundle class does. <br>
 * Instantiating the TMXResouceBundle class is the same as instantiating the
 * PropertyResourceBundle class. First you obtain a system language code (e.g.:
 * from a locale's information). In TMX the value of the attribute must be one
 * of the ISO language identifiers (a two- or three-letter code) or one of the
 * standard locale identifiers (a two- or three-letter language code, a dash,
 * and a two-letter region code).
 * </p>
 * 
 * Copyright (c) 2004-2006 Tecnick.com S.r.l (www.tecnick.com) Via Ugo Foscolo
 * n.19 - 09045 Quartu Sant'Elena (CA) - ITALY www.tecnick.com -
 * info@tecnick.com <br/> Project homepage: <a
 * href="http://tmxjavabridge.sourceforge.net"
 * target="_blank">http://tmxjavabridge.sourceforge.net</a><br/> License:
 * http://www.gnu.org/copyleft/lesser.html LGPL
 * 
 * @author Nicola Asuni [www.tecnick.com].
 * @version 1.1.008
 */

public class TMXResourceBundle extends ResourceBundle implements Serializable {

	/**
	 * Serial Version UID
	 */
	private static final long serialVersionUID = -2098421084432070017L;

	/**
	 * The hastable that will contain data loaded from XML
	 */
	protected Hashtable hashcontents = null;

	/**
	 * Number of translation units (tu) items
	 */
	protected int numberOfItems = 0;

	/**
	 * Vector to store tu items keys
	 */
	protected Vector vectOfItems;
	
	/**
	 * TMX to Hashtable conversion. Reads XML and store data in HashTable.
	 * 
	 * @param xmlfile
	 *            the TMX (XML) file to read, supports also URI resources or JAR
	 *            resources
	 * @param language
	 *            ISO language identifier (a two- or three-letter code)
	 */
	public TMXResourceBundle(String xmlfile, String language) {
		this(xmlfile, language, "");
	}

	/**
	 * Copy object data to this object.
	 * @param obj object to copy.
	 */
	private void copyTMXResourceBundle(TMXResourceBundle obj) {
		this.hashcontents = obj.hashcontents;
		this.numberOfItems = obj.numberOfItems;
		this.vectOfItems = obj.vectOfItems;
	}
	
	/**
	 * TMX to Hashtable conversion. Reads XML and store data in HashTable. NOTE:
	 * you must manually delete the cachefile to refresh its content.
	 * 
	 * @param xmlfile
	 *            the TMX (XML) file to read, supports also URI resources or JAR
	 *            resources
	 * @param language
	 *            ISO language identifier (a two- or three-letter code)
	 * @param cachefile
	 *            name of the file used to store cache data for the specified
	 *            language
	 */
	public TMXResourceBundle(String xmlfile, String language, String cachefile) {
		
		// try to get data from cachefile (if any)
		if (cachefile.length() > 0) {
			try {
				FileInputStream fis = new FileInputStream(cachefile);
				ObjectInputStream in = new ObjectInputStream(fis);
				copyTMXResourceBundle((TMXResourceBundle)in.readObject());
				in.close();
				return;
			} catch (Exception e) {
				System.err.println("Exception:" + e);
			}
		}
		
		String temp_key = null; // store hashtable key names
		String temp_value = null; // store hashtable values
		NamedNodeMap temp_list = null; // list of <tu> attributes
		Attr temp_attr = null; // <tu> attribute
		NodeList listOfTUVs = null; // list of <tuv> elements
		NodeList listOfSEG = null; // list of <seg> elements
		Element SEGElements = null; // <seg> element
		int numberOfTUVs = 0; // number of <tuv> elements

		// Create Document with parser
		Document document = parseXmlFile(xmlfile, false);

		// handle document error
		if (document == null) {
			hashcontents = new Hashtable(); // initialize a void hashtable
			return;
		}

		// Make a list of Term Units and count the number of items
		NodeList listOfTermUnits = document.getElementsByTagName("tu");
		numberOfItems = listOfTermUnits.getLength();

		// set tu keys vector size
		vectOfItems = new Vector(numberOfItems);

		// set hash size
		hashcontents = new Hashtable(numberOfItems);
		for (int i = 0; i < numberOfItems; i++) {
			temp_value = null;

			// set a key
			temp_list = listOfTermUnits.item(i).getAttributes();
			temp_attr = (Attr) temp_list.getNamedItem("tuid");
			temp_key = temp_attr.getValue();

			vectOfItems.add(temp_key); // store key on vector

			// get a value
			// Make a TUV list => "listOfTUVs"
			Node TUVs = listOfTermUnits.item(i);
			if (TUVs.getNodeType() == Node.ELEMENT_NODE) {
				Element TUVElements = (Element) TUVs;
				listOfTUVs = TUVElements.getElementsByTagName("tuv");
				numberOfTUVs = listOfTUVs.getLength();
			}

			// Check each TUV. If it's a specified lang, then get a SEG value
			for (int j = 0; j < numberOfTUVs; j++) {
				temp_list = listOfTUVs.item(j).getAttributes();
				temp_attr = (Attr) temp_list.getNamedItem("xml:lang");
				if (temp_attr.getValue().equalsIgnoreCase(language)) {
					// -- Get a SEG value
					SEGElements = (Element) listOfTUVs.item(j);
					listOfSEG = SEGElements.getElementsByTagName("seg");
					try {
						temp_value = listOfSEG.item(0).getFirstChild()
								.getNodeValue();
					} catch (Exception e) {
						// in case of error print error message and set value to
						// void string
						System.err.println(this.getClass().getName() + "(\""
								+ xmlfile + "\", \"" + language + "\") :: "
								+ "Void <seg> value on <tu tuid=\"" + temp_key
								+ "\"> key");
						temp_value = "";
					}
				}
			}

			// Populate hashtable
			if ((temp_key != null) && (temp_value != null)) {
				hashcontents.put(temp_key, temp_value);
			}
		} // for loop
		// try to save this object on cache file
		if (cachefile.length() > 0) {
			try {
				FileOutputStream fos = new FileOutputStream(cachefile);
				ObjectOutputStream out = new ObjectOutputStream(fos);
				out.writeObject(this);
				out.close();
			} catch (Exception e) {
				System.err.println("Exception:" + e);
			}
		}
	}

	/**
	 * Parses an XML file and returns a DOM document.
	 * 
	 * @param filename
	 *            the name of XML file
	 * @param validating
	 *            If true, the contents is validated against the DTD specified
	 *            in the file.
	 * @return the parsed document
	 */
	public Document parseXmlFile(String filename, boolean validating) {
		Document doc = null;
		DocumentBuilderFactory factory = null;
		// Create a builder factory
		try {
			factory = DocumentBuilderFactory.newInstance();
		} catch (FactoryConfigurationError e) {
			System.err.println(e);
			return null;
		}
		factory.setValidating(validating);
		// Create the builder and parse the file
		try {
			try {
				// try to get the file from jar
				InputStream instream = getClass().getResourceAsStream(filename);
				doc = factory.newDocumentBuilder().parse(instream);
			} catch (Exception ejar) {
				try {
					// try to get the file as external URI
					doc = factory.newDocumentBuilder().parse(filename);
				} catch (IOException euri) {
					try {
						// try to get the file as local filename
						doc = factory.newDocumentBuilder().parse(new File(filename));
					} catch (IOException efile) {
						try {
							// try to resolve the path as relative to local
							// class folder
							String[] classPath = System.getProperties().getProperty("java.class.path", ".").split(";");
							String newpath = classPath[0] + "/" + filename;
							doc = factory.newDocumentBuilder().parse(
									new File(newpath));
						} catch (IOException epath) {
							// unable to get the input file
							System.err.println("IOException:" + epath);
						}
					}
				}
			}
		} catch (ParserConfigurationException e) {
			System.err.println("[" + filename + "] ParserConfigurationException:" + e);
		} catch (SAXException e) {
			System.err.println("[" + filename + "] SAXException:" + e);
		}
		return doc;
	}

	/**
	 * Get key value, return default if void.
	 * 
	 * @param key
	 *            name of key
	 * @param def
	 *            default value
	 * @return parameter value or default
	 */
	public String getString(String key, String def) {
		String param_value = "";
		try {
			param_value = this.getString(key);
			if ((param_value != null) && (param_value.length() > 0)) {
				return param_value;
			}
		} catch (Exception e) {
			// for any exception return the default value
			return def;
		}
		return def;
	}

	/**
	 * handleGetObject implementation
	 * 
	 * @param key
	 *            the resource key
	 * @return the content associated to the specified key
	 * @throws MissingResourceException
	 */
	public final Object handleGetObject(String key) throws MissingResourceException {
		return hashcontents.get(key);
	}

	/**
	 * Returns the number of translation units
	 * 
	 * @return number of Items
	 */
	public int getNumberOfItems() {
		return numberOfItems;
	}

	/**
	 * Define getKeys method
	 * 
	 * @return item elements
	 */
	public Enumeration getKeys() {
		return vectOfItems.elements();
	}

}

Classe di esempio

Questa classe mostra come istanziare la classe TMXResourceBundle con il file di esempio sample_tmx.xml.
Nell'esempio il codice della lingua (en = inglese) viene specificato in maniera esplicita ma può essere anche ricavato dall'ambiente locale.

Codice Sorgente

package com.tecnick.tmxjavabridge.sample;

import com.tecnick.tmxjavabridge.TMXResourceBundle;

/**
 * Sample class for TMXResourceBundle class.
 * <br/><br/>
 * Copyright (c) 2004-2006
 * Tecnick.com S.r.l (www.tecnick.com) 
 * Via Ugo Foscolo n.19 - 09045 Quartu Sant'Elena (CA) - ITALY
 * www.tecnick.com - info@tecnick.com<br/>
 * License: http://www.gnu.org/copyleft/lesser.html LGPL
 * 
 * @author Nicola Asuni [www.tecnick.com].
 * @version 1.1.008
 */
public class TMXJBSample {
	
	/**
	 * loads TMX data
	 */
	final static TMXResourceBundle res_en = new TMXResourceBundle("tmx/sample_tmx.xml", "en");
	// test cache system
	final static TMXResourceBundle res_it = new TMXResourceBundle("tmx/sample_tmx.xml", "it", "src/com/tecnick/tmxjavabridge/test/test_tmx_it.obj");
	
	/**
	 * Prints 2 strings on System.out
	 * @param args String[]
	 */
	public static void main(String[] args) {
		System.out.println(res_en.getString("hello", ""));
		System.out.println(res_en.getString("world", ""));
		System.out.println(res_it.getString("hello", ""));
		System.out.println(res_it.getString("world", ""));
	}
}

Riferimenti


nome: password: [registrati]

| TXT | TXT+
Il contenuto di questo sito è protetto da copyright © Tecnick.com s.r.l. Tutti i diritti riservati.
Tecnick.com s.r.l. - Sede Legale: Via Della Pace, 11 – 09044 – Quartucciu (CA) – ITALY - Capitale Sociale € 10.000,00 i.v. - P. IVA e C.F.: 02574420929 - C.C.I.A.A.: CA-2000-19195 - R.E.A.: 208980

Powered by Tecnick.com AIOCP (All In One Control Panel) Tecnick.com is Java.net Silver Partner

Valid XHTML 1.0! Valid CSS1! Level Double-A conformance icon, W3C-WAI Web Content Accessibility Guidelines 1.0





Bookmark and Share


English Italian