internationalization in JSF with ResourceBundle entries which are loaded from database

Internationalization/localization should preferably be entirely done in the view side. The model shouldn't be aware of this.

In JSF, the <resource-bundle> entry in faces-config.xml and the <f:loadBundle> in XHTML can also point to a fullworthy ResourceBundle class instead of basename of .properties files. In Java SE 6 there's a new ResourceBundle.Control API available which allows full control over loading and filling the bundle.

Knowing those facts, it should be possible to load the bundle messages from the DB with a custom ResourceBundle and Control. Here's a kickoff example:

public class CompetenceBundle extends ResourceBundle {

    protected static final String BASE_NAME = "Competence.messages"; // Can be name of @NamedQuery
    protected static final Control DB_CONTROL = new DBControl();

    private Map<String, String> messages;

    public CompetenceBundle() {
        setParent(ResourceBundle.getBundle(BASE_NAME, 
            FacesContext.getCurrentInstance().getViewRoot().getLocale(), DB_CONTROL));
    }

    protected CompetenceBundle(Map<String, String> messages) {
        this.messages = messages;
    }

    @Override
    protected Object handleGetObject(String key) {
        return messages != null ? messages.get(key) : parent.getObject(key);
    }

    @Override
    public Enumeration<String> getKeys() {
        return messages != null ? Collections.enumeration(messages.keySet()) : parent.getKeys();
    }

    protected static class DBControl extends Control {

        @Override
        public ResourceBundle newBundle
            (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                throws IllegalAccessException, InstantiationException, IOException
        {
            String language = locale.getLanguage();
            Map<String, String> messages = getItSomehow(baseName, language); // Do your JPA thing. The baseName can be used as @NamedQuery name.
            return new CompetenceBundle(messages);
        }

    }

}

This way you can declare it as follows in faces-config.xml:

<resource-bundle>
    <base-name>com.example.i18n.CompetenceBundle</base-name>
    <var>competenceBundle</var>
</resource-bundle>

Or as follows in the Facelet:

<f:loadBundle basename="com.example.i18n.CompetenceBundle" var="competenceBundle" />

Either way, you can use it the usual way:

<h:outputText value="#{competenceBundle.name}" />