language - I18n respaldado por base de datos para la aplicación web java
java i18n properties file (4)
Me gustaría usar una base de datos para almacenar pares de 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.
Me refiero específicamente a algo que funcionaría con las etiquetas jstl como
<fmt:setlocale>
<fmt:bundle>
<fmt:setBundle>
<fmt:message>
Creo que esto implicará la extensión de ResourceBundle, pero cuando lo intenté encontré problemas que tenían que ver con la forma en que las etiquetas jstl obtienen el paquete de recursos.
¿Estás preguntando cómo almacenar los caracteres UTF-8/16 en un DB? 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 de i18n en ella y vea lo que sucede ... es posible que ya esté configurado ...
o estoy completamente perdido tu punto?
editar:
para ser más explícito ... Por lo general, implemento una tabla de tres columnas ... idioma, clave, valor ... donde "valor" contiene palabras o frases potencialmente de lengua extranjera ... "idioma" contiene alguna clave de idioma y "clave" es una clave en inglés (es decir, login.error.password.dup) ... el idioma y la clave están indexados ...
Luego construí 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 todo lo demás que necesita para habilitar traductores y datos gente de entrada para hacer uso de ella ..
Editar 2:
Ahora que agregó la información sobre las etiquetas JSTL, comprendo un poco más ... Nunca lo hice yo mismo ... pero encontré esta información antigua en el servidor ...
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));
Tenemos una tabla de base de datos con clave / idioma / término donde clave es un número entero y es una clave primaria combinada junto con el lenguaje.
Estamos utilizando Struts, así que terminamos escribiendo nuestra propia implementación de PropertyMessageResources que nos permite hacer algo como <bean:message key="impressum.text" />
.
Funciona muy bien y nos brinda la flexibilidad de cambiar dinámicamente los idiomas en el front-end y de actualizar las traducciones sobre la marcha.
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.
Usé 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;
}
};
}
Realmente, lo que ScArcher2 necesitaba era una respuesta davids que no estaba marcada como correcta o útil.
La solución que ScArcher2 eligió usar 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 real. A veces, para limitar las llamadas a través de Internet, 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é.