jueves, 24 de julio de 2008

Aplicaciones Java (V): Creación de una interfaz multiidioma

Supongamos que queremos implementar la interfaz de nuestro programa de modo que se pueda elegir el idioma. La opción que presentamos a continuación nos va a permitir implementar este cambio de idioma sin tener que hardcodear cada una de las posibles combinaciones de componentes e idiomas en el código.

La clave la tenemos en utilizar un objeto del tipo Properties. Ésta es una clase de Java que nos permitirá, por medio de su método getProperty, acceder a un fichero de texto que contiene pares propiedad - valor. El nombre de la propiedad y su valor pueden estar separados por dos puntos ":", igual "=", espacios " ", tabuladores, o una combinación de varios de estos símbolos. Veamos a continuación el fichero languages.cfg utilizado en el ejemplo. Utilizaremos este fichero desde un objeto Properties.

languages.ES.Menu.Tools:        Herramientas
languages.ES.Menu.Language:     Idioma
languages.ES.Menu.Help:         Ayuda
languages.ES.Menu.About:        Acerca de
languages.ES.Label.label:       Etiqueta
languages.ES.Button.boton:      Botón
languages.ES.Table.tabla.0:     primera columna
languages.ES.Table.tabla.1:     segunda columna
languages.ES.Table.tabla.2:     tercera columna

languages.EN.Menu.Tools:        Tools
languages.EN.Menu.Language:     Language
languages.EN.Menu.Help:         Help
languages.EN.Menu.About:        About
languages.EN.Label.label:       Label
languages.EN.Button.boton:      Button
languages.EN.Table.tabla.0:     first column
languages.EN.Table.tabla.1:     second column
languages.EN.Table.tabla.2:     third column

Observamos que para cada componente, hemos definido una propiedad para cada idioma, que sólo cambia en el segundo token (ES/EN). Esto nos permitirá implementar una función para traducir la interfaz, tal que le pasemos el idioma, y buscaremos automáticamente el conjunto de propiedades correspondientes al idioma seleccionado.

En primer lugar cargaremos el fichero con las traducciones en el objeto Properties. Intentaremos leer el fichero del filesystem (mediante FileReader), y si no encontramos el fichero, intentaremos leerlo desde dentro del fichero jar (Class.getResourceAsStream).

A continuación leeremos la propiedad correspondiente getProperty("languages."+language+"."+tipo_compomente+"."+nombre_componente), y cambiaremos ese texto en el componente correspondiente.

private void translateInterface(String language)
{
    // read the properties file that will contain the strings for every language
    
    if (languagescfg == null)
    {
        languagescfg = new Properties();

        try {
            // We will try to open the file from a resource, in case it is embedded in a jar file
            InputStream is = this.getClass().getClassLoader().getResourceAsStream("languages.cfg");
            
            if (is != null)
                languagescfg.load(new InputStreamReader(is));
            else languagescfg.load(new FileReader("languages.cfg"));
        } catch (FileNotFoundException e)
        {        
            System.err.println("File or resource \"languages.cfg\" not found");
        } catch (IOException e)
        {
            System.err.println("Error while reading file \"languages.cfg\"");
        }
    }

    // menus
    toolsmenu.setText(languagescfg.getProperty("languages."+language+".Menu.Tools"));
    languagemenu.setText(languagescfg.getProperty("languages."+language+".Menu.Language"));
    helpmenu.setText(languagescfg.getProperty("languages."+language+".Menu.Help"));
    aboutitem.setText(languagescfg.getProperty("languages."+language+".Menu.About"));
    
    // labels
    label.setText(languagescfg.getProperty("languages."+language+".Label.label"));
            
    // buttons
    boton.setText(languagescfg.getProperty("languages."+language+".Button.boton"));
    
    // Headers de tablas
    tabla.getColumnModel().getColumn(0).setHeaderValue(languagescfg.getProperty("languages."+language+".Table.tabla.0"));
    tabla.getColumnModel().getColumn(1).setHeaderValue(languagescfg.getProperty("languages."+language+".Table.tabla.1"));
    tabla.getColumnModel().getColumn(2).setHeaderValue(languagescfg.getProperty("languages."+language+".Table.tabla.2"));
}

Finalmente sólo queda llamar al método translateInterface, tanto al final de la función que genera el interfaz (para asignar inicialmente los valores por defecto), como en la escucha de los eventos que saltarán cuando el usuario seleccione el idioma correspondiente desde el menú Herramientas->Idioma.

public void generaInterfaz()
{
    // creamos los componentes deseados
    // ...
    
    // Translations of the interface
    
    translateInterface("ES");
}

public void actionPerformed(ActionEvent event)
{
    Object source = event.getSource();
    if (source.getClass().getName().equals("javax.swing.JRadioButtonMenuItem"))
    {
        if (source == languageitemES)
        {
            translateInterface("ES");
        }
        else if (source == languageitemEN)
        {
            translateInterface("EN");
        }
    }
}

Material

El código fuente mostrando el uso de estos componentes se puede descargar bajo licencia GPLv3:
Download

Ejecutar ejemplo, como applet o como Java Web Start (recomendado):
Start Applet Launch via Java Web Start

No hay comentarios: