Pergunta

Gostaria de usar um banco de dados para armazenar pares chave/valor i18n para que possamos modificar/recarregar os dados i18n em tempo de execução.Alguém já fez isso?Ou alguém tem uma idéia de como implementar isso?Li vários tópicos sobre isso, mas não vi uma solução viável.

Estou me referindo especificamente a algo que funcionaria com tags jstl, como

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

Acho que isso envolverá a extensão do ResourceBundle, mas quando tentei fazer isso, tive problemas relacionados à maneira como as tags jstl obtêm o pacote de recursos.

Foi útil?

Solução

Você está apenas perguntando como armazenar caracteres UTF-8/16 em um banco de dados?no mysql é apenas uma questão de construir com suporte UTF8 e defini-lo como padrão, ou especificá-lo no nível da coluna ou tabela.Já fiz isso no Oracle e no MySQL antes.Crie uma tabela e recorte e cole alguns dados i18n nela e veja o que acontece...você já pode estar definido ..

ou estou perdendo completamente o seu argumento?

editar:

para ser mais explícito...Normalmente implemento uma tabela de três colunas ...idioma, chave, valor...onde "valor" contém palavras ou frases potencialmente estrangeiras..."idioma" contém alguma chave de idioma e "chave" é uma chave em inglês (ou seja,login.error.senha.dup)...idioma e chave são indexados...

Em seguida, construí interfaces em uma estrutura como esta que mostra cada chave com todas as suas traduções (valores)...pode ser sofisticado e incluir trilhas de auditoria e marcadores "sujos" e todas as outras coisas que você precisa para permitir que tradutores e pessoas que digitam dados possam usá-lo.

Editar 2:

Agora que você adicionou as informações sobre as tags JSTL, entendo um pouco mais...Eu nunca fiz isso sozinho..mas encontrei esta informação antiga em lado do 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));

Outras dicas

Finalmente consegui fazer isso funcionar com a ajuda do danb acima.

Esta é minha classe de pacote de recursos e classe de controle de pacote de recursos.

Usei esse código do @[danb].

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));

e escreveu esta aula.

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;
            }

        };
    }

Temos uma tabela de banco de dados com chave/idioma/termo onde chave é um número inteiro e é uma chave primária combinada com o idioma.

Estamos usando Struts, então acabamos escrevendo nosso próprio PropriedadeMessageResources implementação que nos permite fazer algo como <bean:message key="impressum.text" />.

Funciona muito bem e nos dá a flexibilidade de alternar idiomas dinamicamente no front-end, bem como atualizar as traduções em tempo real.

Na verdade, o que o ScArcher2 precisava é da resposta de David, que não está marcada como correta ou útil.

A solução que o ScArcher2 escolheu usar foi um erro terrível :) Carregando TODAS as traduções de uma vez...em qualquer aplicação maior, isso vai matá-lo.Carregando milhares de traduções a cada solicitação...

o método de David é mais comumente usado em ambientes de produção reais.Às vezes, para limitar as chamadas de banco de dados, que ocorrem com cada mensagem traduzida, você pode criar grupos de traduções por tópico, funcionalidade etc.para pré-carregá-los.Mas isso é um pouco mais complexo e pode ser substituído por um bom sistema de cache.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top