I18n respaldado por base de datos para la aplicación web java


6

Me gustaría usar una base de datos para almacenar pares clave/valor i18n para que podamos modificar/recargar los datos i18n en tiempo de ejecución. ¿Alguien ha hecho esto? ¿O alguien tiene una idea de cómo implementar esto? He leído varios hilos sobre esto, pero no he visto una solución viable.

estoy refiriendo específicamente a algo que funcione con las etiquetas JSTL como

<fmt:setlocale> 
<fmt:bundle> 
<fmt:setBundle> 
<fmt:message> 

creo que esto implicará que se extiende ResourceBundle, pero cuando he intentado esto me encontré con los problemas que tenían que ver con el forma en que las etiquetas jstl obtienen el paquete de recursos.

2

¿Estás preguntando cómo almacenar los caracteres UTF-8/16 en una base de datos? en mysql, solo es cuestión de asegurarse de que compila con soporte UTF8 y configurarlo como valor predeterminado, o especificarlo a nivel de columna o tabla. He hecho esto en oráculo y mysql antes. Cree una tabla y corte y pegue algunos datos i18n en ella y vea lo que sucede ... es posible que ya esté configurado ...

o ¿me falta completamente su punto?

edición:

ser más explícito ... normalmente tres implementar una tabla de columna ... idioma, clave, valor ... donde "valor" contiene palabras de la lengua extranjera potencialmente o frases ... " idioma "contiene algunas teclas de idioma y" clave "es una clave en inglés (es decir, login.error.password.dup) ... el lenguaje y la clave están indexados ...

He creado interfaces en una estructura como esta que muestra cada clave con todas sus traducciones (valores) ... puede ser elegante e incluir pistas de auditoría y marcadores "sucios" y todas las demás cosas que necesita para permitir que los traductores y personas que ingresan datos lo usen ..

Edición 2:

Ahora que ha agregado la información sobre las etiquetas JSTL, entiendo un poco más ... que nunca he hecho yo mismo .. pero me encontré con este viejo información sobre theserverside ...

HttpSession session = .. [get hold of the session] 
ResourceBundle bundle = new PropertyResourceBundle(toInputStream(myOwnProperties)) [toInputStream just stores the properties into an inputstream] 
Locale locale = .. [get hold of the locale] 
javax.servlet.jsp.jstl.core.Config.set(session, Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle ,locale)); 

1

Tenemos una tabla de base de datos con clave/idioma/término donde la clave es un número entero y es una combinación de clave principal junto con el idioma.

Estamos utilizando Struts, así que terminamos de escribir nuestros propios PropertyMessageResources aplicación que nos permite hacer algo como <bean:message key="impressum.text" />.

Funciona muy bien y nos da la flexibilidad de cambiar dinámicamente los idiomas en el front-end y de actualizar las traducciones sobre la marcha.


13

Finalmente conseguí esto trabajando con la ayuda de danb anterior.

Esta es mi clase de paquete de recursos y clase de control de paquete de recursos.

He usado este código de @ [danb] 's.

ResourceBundle bundle = ResourceBundle.getBundle("AwesomeBundle", locale, DbResourceBundle.getMyControl()); 
javax.servlet.jsp.jstl.core.Config.set(actionBeanContext.getRequest(), Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle, locale)); 

y escribió esta clase.

public class DbResourceBundle extends ResourceBundle 
{ 
    private Properties properties; 

    public DbResourceBundle(Properties inProperties) 
    { 
     properties = inProperties; 
    } 

    @Override 
    @SuppressWarnings(value = { "unchecked" }) 
    public Enumeration<String> getKeys() 
    { 
     return properties != null ? ((Enumeration<String>) properties.propertyNames()) : null; 
    } 

    @Override 
    protected Object handleGetObject(String key) 
    { 
     return properties.getProperty(key); 
    } 

    public static ResourceBundle.Control getMyControl() 
    { 
     return new ResourceBundle.Control() 
     { 

      @Override 
      public List<String> getFormats(String baseName) 
      { 
       if (baseName == null) 
       { 
        throw new NullPointerException(); 
       } 
       return Arrays.asList("db"); 
      } 

      @Override 
      public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, 
        InstantiationException, IOException 
      { 
       if ((baseName == null) || (locale == null) || (format == null) || (loader == null)) 
        throw new NullPointerException(); 
       ResourceBundle bundle = null; 
       if (format.equals("db")) 
       { 
        Properties p = new Properties(); 
        DataSource ds = (DataSource) ContextFactory.getApplicationContext().getBean("clinicalDataSource"); 
        Connection con = null; 
        Statement s = null; 
        ResultSet rs = null; 
        try 
        { 
         con = ds.getConnection(); 
         StringBuilder query = new StringBuilder(); 
         query.append("select label, value from i18n where bundle='" + StringEscapeUtils.escapeSql(baseName) + "' "); 

         if (locale != null) 
         { 
          if (StringUtils.isNotBlank(locale.getCountry())) 
          { 
           query.append("and country='" + escapeSql(locale.getCountry()) + "' "); 

          } 
          if (StringUtils.isNotBlank(locale.getLanguage())) 
          { 
           query.append("and language='" + escapeSql(locale.getLanguage()) + "' "); 

          } 
          if (StringUtils.isNotBlank(locale.getVariant())) 
          { 
           query.append("and variant='" + escapeSql(locale.getVariant()) + "' "); 

          } 
         } 
         s = con.createStatement(); 
         rs = s.executeQuery(query.toString()); 
         while (rs.next()) 
         { 
          p.setProperty(rs.getString(1), rs.getString(2)); 
         } 
        } 
        catch (Exception e) 
        { 
         e.printStackTrace(); 
         throw new RuntimeException("Can not build properties: " + e); 
        } 
        finally 
        { 
         DbUtils.closeQuietly(con, s, rs); 
        } 
        bundle = new DbResourceBundle(p); 
       } 
       return bundle; 
      } 

      @Override 
      public long getTimeToLive(String baseName, Locale locale) 
      { 
       return 1000 * 60 * 30; 
      } 

      @Override 
      public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) 
      { 
       return true; 
      } 

     }; 
    } 

0

Realmente lo que ScArcher2 necesitaba es davids response que no está marcado como correcto o útil.

La solución que eligió ScArcher2 es imo terrible mestake :) Cargando TODAS las traducciones a la vez ... en una aplicación más grande, la va a matar. Cargando miles de traducciones cada solicitud ...

El método de david es más comúnmente utilizado en entornos de producción reales. A veces, para limitar las llamadas a bases de datos, que es con cada mensaje traducido, puede crear grupos de traducciones por tema, funcionalidad, etc. para precargarlos. Pero esto es un poco más complejo y puede ser sustituido por un buen sistema de caché.

  0

Acepto que cargar todas las traducciones en aplicaciones más grandes podría causar problemas. En nuestro caso, no fue un problema en absoluto y satisfizo nuestra necesidad. Mi código podría ser modificado para usar un mecanismo de caché que solo mantiene un subconjunto de las traducciones en la memoria en un momento dado. El código era solo un ejemplo de lo que funcionó, no lo que sería mejor en todas las situaciones. 05 sep. 122012-09-05 15:01:31