интернационализация в JSF с записями ResourceBundle, которые загружаются из базы данных - PullRequest
7 голосов
/ 21 декабря 2010

Я работаю над проектом Java EE6 с использованием JPA / EJB / JSF, и у меня возникли некоторые проблемы при разработке поддержки нескольких языков для сущностей.Существует три соответствующих объекта:

Язык (имеет идентификатор)
Компетенция (имеет идентификатор)
Имя Competence (имеет ссылку на Компетентность, Ссылка на язык и строку)

Компетенция имеет единицу-to-many ссылка на CompetenceName, реализованная с помощью Map, содержащей один объект для каждого языка, для которого существует имя для Competence.Обратите внимание, что компетенции создаются динамически , и поэтому их имена не могут существовать в пакете ресурсов.

При перечислении компетенций на веб-странице я хочу, чтобы они отображались на языке текущеговошедший в систему пользователь, он хранится в управляемом компоненте Session Scoped.

Есть ли какой-нибудь хороший способ сделать это, не нарушая хороший дизайн MVC?Моя первая идея состояла в том, чтобы получить сессионный бин области действия непосредственно из метода «getName» в сущности Competence через FacesContext и посмотреть на карту CompetenceNames следующим образом:

public class Competence
{
...
@MapKey(name="language")
@OneToMany(mappedBy="competence", cascade=CascadeType.ALL, orphanRemoval=true)
private Map<Language, CompetenceName> competenceNames;

public String getName(String controller){
    FacesContext context = FacesContext.getCurrentInstance();
    ELResolver resolver = context.getApplication().getELResolver();
    SessionController sc = (SessionController)resolver.getValue(context.getELContext(), null, "sessionController");
    Language language = sc.getLoggedInUser().getLanguage();
    if(competenceNames.get(language) != null)
        return competenceNames.get(language).getName();
    else
        return "resource missing";
}

Это решение кажется чрезвычайно грубымпоскольку объект опирается на уровень контроллера и должен извлекать контроллер сеанса каждый раз, когда мне нужно его имя.Более совместимым с MVC решением было бы использование параметра Language, но это означает, что каждый отдельный вызов из JSF должен включать язык, извлекаемый из управляемого bean-объекта сессионной области, который тоже не является хорошим решением.

Есть ли у кого-нибудь мысли или шаблоны проектирования по этому вопросу?

Ответы [ 2 ]

17 голосов
/ 21 декабря 2010

Интернационализация / локализация должна быть полностью сделана в виде обзора. Модель не должна знать об этом.

В JSF запись <resource-bundle> в faces-config.xml и <f:loadBundle> в XHTML также могут указывать на полноценный класс ResourceBundle вместо базового имени файлов .properties. В Java SE 6 доступен новый API ResourceBundle.Control, который позволяет полностью контролировать загрузку и заполнение пакета.

Зная эти факты, должна быть возможность загружать пакетные сообщения из БД с пользовательскими ResourceBundle и Control. Вот начальный пример:

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

    }

}

Таким образом, вы можете объявить это следующим образом в faces-config.xml:

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

Или следующим образом в Facelet:

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

В любом случае, вы можете использовать его обычным способом:

<h:outputText value="#{competenceBundle.name}" />
0 голосов
/ 21 декабря 2010

Я бы переместил специфичную для языка деталь из вашей модели в наборы ресурсов. Просто модель компетенции, языка и пользователя. Если пользователь запрашивает страницу, вы отображаете его компетенцию и ищите компетенцию для конкретного языка (CompetenceName) из пакета ресурсов.

Я искал пример кода, чтобы начать работу, и нашел this , см. Листинг 19.16 customerDetails.jsp.

Что-то вроде:

<fmt:message key="${competence}" />
...