Pregunta

Estoy trabajando en un proyecto Java EE6 usando JPA/EJB/JSF y tengo algunos problemas para diseñar múltiples soporte de lenguaje para entidades. Hay tres entidades relevantes:

Lenguaje (tiene identificación)
Competencia (tiene identificación)
CompetenceName (tiene referencia de competencia, referencia de idioma y una cadena)

La competencia tiene una referencia de uno a muchos a CompetenceName implementado con un mapa, que contiene un objeto para cada lenguaje que existe un nombre para una competencia. Tenga en cuenta que se crean competencias dinamicamente y sus nombres no pueden existir en un paquete de recursos.

Al enumerar las competencias en una página web, quiero que se muestren con el lenguaje del usuario que se registra actualmente, esto se almacena en una sesión de bean administrada en una sesión.

¿Hay alguna buena manera de lograr esto sin romper un buen diseño de MVC? Mi primera idea fue obtener la sesión de la sesión directamente de un método de "GetName" en la entidad de competencia a través de Facescontext, y mirar en el mapa de Competencenames para él como lo sigue:

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

Esta solución se siente extremadamente cruda ya que la entidad se basa en la capa del controlador y tiene que buscar un controlador de sesión cada vez que quiera su nombre. Una solución más compatible con MVC sería tomar un parámetro de idioma, pero esto significa que cada llamada de JSF tendrá que incluir el lenguaje obtenido del frijol administrado con el alcance de la sesión que tampoco se siente como una buena solución.

¿Alguien tiene algún pensamiento o patrones de diseño para este problema?

¿Fue útil?

Solución

La internacionalización/localización debe hacerse preferiblemente en el lado de la vista. El modelo no debe ser consciente de esto.

En JSF, el <resource-bundle> ingresar faces-config.xml y el <f:loadBundle> en xhtml también puede señalar un ResourceBundle clase en lugar de nombre de base de .properties archivos. En Java se 6 hay un nuevo ResourceBundle.Control API disponible que permite el control total sobre la carga y el llenado del paquete.

Conociendo esos hechos, debería ser posible cargar los mensajes del paquete de la base de datos con una personalización ResourceBundle y Control. Aquí hay un ejemplo de inicio:

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

    }

}

De esta manera puedes declararlo de la siguiente manera en faces-config.xml:

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

O como sigue en la facelé:

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

De cualquier manera, puede usarlo de la manera habitual:

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

Otros consejos

Movería la parte específica del lenguaje de su modelo a paquetes de recursos. Simplemente modele la competencia, el idioma y el usuario. Si un usuario solicita una página, muestra su competencia y busca la competencia específica del idioma (CompetenceName) desde el paquete de recursos.

Busqué un código de muestra para comenzar y encontrar este, Ver Listado 19.16 CustomerDetails.jsp.

Algo como:

<fmt:message key="${competence}" />
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top