<p>I'd like to use a database to store i18n key/value pairs so we can modify / reload the i18n data at runtime. Has anyone done this? Or does anyone have an idea of how to implement this? I've read several threads on this, but I haven't seen a workable solution.</p>

<p>I'm specifically refering to something that would work with the jstl tags such as</p>

<pre><code>&lt;fmt:setlocale&gt;
&lt;fmt:bundle&gt;
&lt;fmt:setBundle&gt;
&lt;fmt:message&gt;
</code></pre>

<p>I think this will involve extending ResourceBundle, but when I tried this I ran into problems that had to do with the way the jstl tags get the resource bundle.</p>

[{'answer_id': 19308, 'author': 'danb', 'author_id': 2031, 'author_profile': 'https://Stackoverflow.com/users/2031', 'pm_score': 3, 'selected': True, 'text': '<p>Are you just asking how to store UTF-8/16 characters in a DB? in mysql it\'s just a matter of making sure you build with UTF8 support and setting that as the default, or specifying it at the column or table level. I\'ve done this in oracle and mysql before. Create a table and cut and paste some i18n data into it and see what happens... you might be set already..</p>\n\n<p>or am I completely missing your point? </p>\n\n<p>edit: </p>\n\n<p>to be more explicit... I usually implement a three column table... language, key, value... where "value" contains potentially foreign language words or phrases... "language" contains some language key and "key" is an english key (i.e. login.error.password.dup)... language and key are indexed...  </p>\n\n<p>I\'ve then built interfaces on a structure like this that shows each key with all its translations (values)... it can get fancy and include audit trails and "dirty" markers and all the other stuff you need to enable translators and data entry folk to make use of it..</p>\n\n<p>Edit 2:</p>\n\n<p>Now that you added the info about the JSTL tags, I understand a bit more... I\'ve never done that myself.. but I found this old info on <a href="http://www.theserverside.com/discussions/thread.tss?thread_id=27390" rel="nofollow noreferrer">theserverside</a>...</p>\n\n<pre><code>HttpSession session = .. [get hold of the session] \nResourceBundle bundle = new PropertyResourceBundle(toInputStream(myOwnProperties)) [toInputStream just stores the properties into an inputstream] \nLocale locale = .. [get hold of the locale]\njavax.servlet.jsp.jstl.core.Config.set(session, Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle ,locale));\n</code></pre>\n'}, {'answer_id': 19377, 'author': 'dlinsin', 'author_id': 198, 'author_profile': 'https://Stackoverflow.com/users/198', 'pm_score': 1, 'selected': False, 'text': '<p>We have a database table with key/language/term where key is a n integer and is a combined primary key together with language.</p>\n\n<p>We are using Struts, so we ended up writing our own <em>PropertyMessageResources</em> implementation which allows us to do something like <code>&lt;bean:message key="impressum.text" /&gt;</code>.</p>\n\n<p>It works very well and gives us the flexibility to do dynamically switch languages in the front-end as well as updating the translations on the fly.</p>\n'}, {'answer_id': 32325, 'author': 'ScArcher2', 'author_id': 1310, 'author_profile': 'https://Stackoverflow.com/users/1310', 'pm_score': 4, 'selected': False, 'text': '<p>I finally got this working with danb\'s help above.</p>\n\n<p>This is my resource bundle class and resource bundle control class.</p>\n\n<p>I used this code from @[danb]\'s.</p>\n\n<pre><code>ResourceBundle bundle = ResourceBundle.getBundle("AwesomeBundle", locale, DbResourceBundle.getMyControl());\njavax.servlet.jsp.jstl.core.Config.set(actionBeanContext.getRequest(), Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle, locale));\n</code></pre>\n\n<p>and wrote this class.</p>\n\n<pre><code>public class DbResourceBundle extends ResourceBundle\n{\n    private Properties properties;\n\n    public DbResourceBundle(Properties inProperties)\n    {\n        properties = inProperties;\n    }\n\n    @Override\n    @SuppressWarnings(value = { "unchecked" })\n    public Enumeration&lt;String&gt; getKeys()\n    {\n        return properties != null ? ((Enumeration&lt;String&gt;) properties.propertyNames()) : null;\n    }\n\n    @Override\n    protected Object handleGetObject(String key)\n    {\n        return properties.getProperty(key);\n    }\n\n    public static ResourceBundle.Control getMyControl()\n    {\n        return new ResourceBundle.Control()\n        {\n\n            @Override\n            public List&lt;String&gt; getFormats(String baseName)\n            {\n                if (baseName == null)\n                {\n                    throw new NullPointerException();\n                }\n                return Arrays.asList("db");\n            }\n\n            @Override\n            public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException,\n                  InstantiationException, IOException\n            {\n                if ((baseName == null) || (locale == null) || (format == null) || (loader == null))\n                    throw new NullPointerException();\n                ResourceBundle bundle = null;\n                if (format.equals("db"))\n                {\n                    Properties p = new Properties();\n                    DataSource ds = (DataSource) ContextFactory.getApplicationContext().getBean("clinicalDataSource");\n                    Connection con = null;\n                    Statement s = null;\n                    ResultSet rs = null;\n                    try\n                    {\n                        con = ds.getConnection();\n                        StringBuilder query = new StringBuilder();\n                        query.append("select label, value from i18n where bundle=\'" + StringEscapeUtils.escapeSql(baseName) + "\' ");\n\n                        if (locale != null)\n                        {\n                            if (StringUtils.isNotBlank(locale.getCountry()))\n                            {\n                                query.append("and country=\'" + escapeSql(locale.getCountry()) + "\' ");\n\n                            }\n                            if (StringUtils.isNotBlank(locale.getLanguage()))\n                            {\n                                query.append("and language=\'" + escapeSql(locale.getLanguage()) + "\' ");\n\n                            }\n                            if (StringUtils.isNotBlank(locale.getVariant()))\n                            {\n                                query.append("and variant=\'" + escapeSql(locale.getVariant()) + "\' ");\n\n                            }\n                        }\n                        s = con.createStatement();\n                        rs = s.executeQuery(query.toString());\n                        while (rs.next())\n                        {\n                            p.setProperty(rs.getString(1), rs.getString(2));\n                        }\n                    }\n                    catch (Exception e)\n                    {\n                        e.printStackTrace();\n                        throw new RuntimeException("Can not build properties: " + e);\n                    }\n                    finally\n                    {\n                        DbUtils.closeQuietly(con, s, rs);\n                    }\n                    bundle = new DbResourceBundle(p);\n                }\n                return bundle;\n            }\n\n            @Override\n            public long getTimeToLive(String baseName, Locale locale)\n            {\n                return 1000 * 60 * 30;\n            }\n\n            @Override\n            public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime)\n            {\n                return true;\n            }\n\n        };\n    }\n</code></pre>\n'}, {'answer_id': 12210837, 'author': 'krsnik', 'author_id': 776464, 'author_profile': 'https://Stackoverflow.com/users/776464', 'pm_score': 0, 'selected': False, 'text': "<p>Actuly what ScArcher2 needed is davids response which is not marked a correct or helpfull.</p>\n\n<p>The solution ScArcher2 chose to use is imo terrible mestake:) Loading ALL the translations at one time... in any bigger application its gonna kill it. Loading thousends of translations each request...</p>\n\n<p>david's method is more commonly used in real production environments. \nSometimes to limit db calls, which is with every message translated, you can create groups of translations by topic, functionality etc. to preload them. But this is little bit more complex and can be substituted with good cache system.</p>\n"}]
['https://Stackoverflow.com/questions/19295', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/1310/']