2010-12-07 21 views
12

Tengo un problema con i18n enumeraciones en mi aplicación JSF. Cuando comencé, tenía enumeraciones con el texto definido en el interior. Pero ahora, tengo claves atadas a paquetes de mensajes en la enumeración.Localizando valores de enumeración en el paquete de recursos

Ejemplo uno de mis enumeraciones:

public enum OrderStatus implements CustomEnum { 
    PENDING("enum.orderstatus.pending"), 
    CANCELED("enum.orderstatus.canceled"); 

    /** 
    * key in message bundle 
    */ 
    private String name; 

    OrderStatus(String name) { 
     this.name = name; 
    } 

    @Override 
    public String getName() { 
     return name; 
    } 

} 

En la capa vista, usar algo como:

<!-- input --> 
<h:selectOneMenu value="#{order.status}"> 
    <f:selectItems value="#{flowUtils.orderStatuses}"/> 
</h:selectOneMenu> 

<!-- output --> 
<h:outputText value="#{order.status}"/> 

y en Java:

public class FlowUtils { 
    public List<SelectItem> getOrderStatuses() { 
     ArrayList<SelectItem> l = new ArrayList<SelectItem>(); 
     for(OrderStatus c: OrderStatus.values()) { 
      // before i18n 
      // l.add(new SelectItem(c, c.getName())); 

      // after i18n 
      l.add(new SelectItem(c, FacesUtil.getMessageValue(c.getName()))); 
     } 
     return l;    
    } 
} 

public class FacesUtil { 
    public static String getMessageValue(String name) { 
     FacesContext context = FacesContext.getCurrentInstance(); 
     return context.getApplication().getResourceBundle(context, "m").getString(name); 
    } 
} 

funcionó bien, pero cuando necesitaba dar salida a #{order.status}, necesitaba convertirlo. Así que implementé un convertidor, pero tuve problemas con la conversión de String a Object en el método getAsObject().

web.xml:

<converter> 
    <converter-for-class>model.helpers.OrderStatus</converter-for-class> 
    <converter-class>model.helpers.EnumTypeConverter</converter-class> 
</converter> 

Java:

public class EnumTypeConverter implements Converter { 

    @Override 
    public Object getAsObject(FacesContext context, UIComponent comp, 
      String value) throws ConverterException { 
     // value = localized value :(
     Class enumType = comp.getValueBinding("value").getType(context); 
     return Enum.valueOf(enumType, value); 
    } 

    @Override 
    public String getAsString(FacesContext context, UIComponent component, 
      Object object) throws ConverterException { 
     if (object == null) { 
      return null; 
     } 
     CustomEnum type = (CustomEnum) object; 
     ResourceBundle messages = context.getApplication().getResourceBundle(context, "m"); 
     String text = messages.getString(type.getName()); 
     return text; 
    } 

} 

estoy enredado ahora con eso. ¿Alguien sabe cómo internacionalizar múltiples Enums de manera eficiente?

Respuesta

25

El valor que se pasa a través del convertidor no es la etiqueta de opción como parece esperar, sino el valor de la opción. La mejor práctica es no hacer esto en el lado del modelo, sino en el lado de la vista, porque el modelo no debería ser consciente.

En cuanto al enfoque, básicamente se trata de cosas innecesariamente complicadas. Desde JSF 1.2 hay un built-in EnumConverter que se iniciará automáticamente y desde JSF 2.0 puede iterar sobre una matriz genérica o List en f:selectItems por el nuevo atributo var sin la necesidad de duplicar los valores sobre un List<SelectItem> en el modelo.

Así es como el grano puede verse como:

public class Bean { 
    private OrderStatus orderStatus; 
    private OrderStatus[] orderStatuses = OrderStatus.values(); 

    // ... 
} 

Y aquí es como la vista puede parecer (suponiendo que msg se refiere a la <var> como has definied en <resource-bundle> en faces-config.xml):

<h:selectOneMenu value="#{bean.orderStatus}"> 
    <f:selectItems value="#{bean.orderStatuses}" var="orderStatus" 
     itemValue="#{orderStatus}" itemLabel="#{msg[orderStatus.name]}" /> 
</h:selectOneMenu> 

Eso es todo.


Sin relación con el problema, que haya errores tipográficos en las teclas de nombre y enumeración de mensajes, debe ser:

PENDING("enum.orderstatus.pending"), 
CANCELLED("enum.orderstatus.cancelled"); 

Y, más limpia sería mantener las claves de paquetes fuera de la enumeración y use enum como parte de la clave del paquete. P.ej.

PENDING, 
CANCELLED; 
<h:selectOneMenu value="#{bean.orderStatus}"> 
    <f:selectItems value="#{bean.orderStatuses}" var="orderStatus" 
     itemValue="#{orderStatus}" itemLabel="#{msg['enum.orderstatus.' += orderStatus]}" /> 
</h:selectOneMenu> 
enum.orderstatus.PENDING = Pending 
enum.orderstatus.CANCELLED = Cancelled 
1

Bueno, enum es solo otra clase. No hay nada que le impida agregar métodos de conversión de análisis y secuencia que analizarán y emitirán mensajes sensibles a la configuración regional.

Tal vez viola el Principio Responsable único (¿verdad?), Pero creo que hacer que enum sea responsable de analizar y devolver los valores conscientes de la configuración regional es lo correcto.

Sólo tiene que añadir dos métodos de esta manera:

public String toString(FacesContext context) { 
    // need to modify the method 
    FacesUtil.getMessageValue(context, name); 
} 

public OrderStatus parse(FacesContext context, String theName) { 
    for (OrderStatus value : values()) { 
    if (value.toString(context).equals(theName) { 
     return value; 
    } 
    } 
    // think of something better 
    return null; 
} 

espero que me dieron el código correcto, ya que no estoy comprobando con IDE ahora ... ¿Es esto lo que estabas buscando?

+0

El código es correcto, pero esto no funcionará en el caso de OP ya que no es la etiqueta de opción que se pasa al convertidor. – BalusC

+0

Gracias, no estaba al tanto de eso. –

1

que calcular la clave de mensaje en la enumeración como se muestra a continuación; así que no hay necesidad de mantener las claves con atributos adicionales en la enumeración

public String getMessageKey() { 
    return String.format("enum_%s_%s", this.getClass().getSimpleName(), 
      this.name()); 
} 

Entonces lo uso como esto

 <p:selectOneMenu id="type" 
     value="#{xyzBean.type}" required="true"> 
      <f:selectItems 
       value="#{xyzBean.possibleTypes}" 
       var="type" itemLabel="#{msg[type.messageKey]}"> 
      </f:selectItems> 
    </p:selectOneMenu> 

de haber configurado un org.springframework.context.support.ReloadableResourceBundleMessageSource en el contexto de aplicaciones

<bean id="msg" 
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> 
    <property name="basename" value="/resources/locale/messages" /> 
    <property name="useCodeAsDefaultMessage" value="true" /> 
    <property name="cacheSeconds" value="1" /> 
</bean> 
2

he publicado mi solución a este problema: Internationalization of multiple enums (translation of enum values) - pero todavía con la esperanza de una mejora adicional.

EDIT: con la ayuda de @Joop Eggen, hemos llegado a una solución genial:

nuevo EDIT: solución completa y lista para su uso:

hacer una clase

public final class EnumTranslator { 
    public static String getMessageKey(Enum<?> e) { 
    return e.getClass().getSimpleName() + '.' + e.name(); 
    } 
} 

que sea una costumbre función eL

<?xml version="1.0" encoding="UTF-8"?> 
<facelet-taglib 
xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" 
version="2.0"> 
<namespace>http://example.com/enumi18n</namespace> 
<function> 
    <function-name>xlate</function-name> 
    <function-class>your.package.EnumTranslator</function-class> 
    <function-signature>String getMessageKey(java.lang.Enum)</function-signature> 
</function> 
</facelet-taglib> 

Añadir taglib a su web.xml

<context-param> 
    <param-name>javax.faces.FACELETS_LIBRARIES</param-name> 
    <param-value>/WEB-INF/enumi18n.taglib.xml</param-value> 
</context-param> 

tienen propiedades archivos enum_en.properties y enum_yourlanguage.properties como este

TransferStatus.NOT_TRANSFERRED = Not transferred 
TransferStatus.TRANSFERRED = Transferred 

Añadir los archivos de propiedades como paquetes de recursos a su caras-config.xml

<resource-bundle> 
     <base-name>kk.os.obj.jsf.i18n.enum</base-name> 
     <var>enum</var> 
    </resource-bundle> 

Añadir taglib encargo a sus archivos XHTML

<html ... xmlns:l="http://example.com/enumi18n"> 

Y - voilà - ahora se puede acceder a los valores de enumeración traducidas en JSF:

<h:outputText value="#{enum[l:xlate(order.transferStatus)]}" /> 
0

En caso de que alguien está buscando una biblioteca de utilidad simple para manejar la internacionalización de enum, por favor eche un vistazo a https://github.com/thiagowolff/litefaces-enum-i18n

El artefacto también está disponible en Maven central:

<dependency> 
    <groupId>br.com.litecode</groupId> 
    <artifactId>litefaces-enum-i18n</artifactId> 
    <version>1.0.1</version> 
</dependency> 

Básicamente, sólo tiene que añadir el artefacto a su proyecto y definir las claves respectivas enumeración siguiendo las convenciones de nombres de enumeración descritos. Las traducciones (y también los nombres de las clases de CSS) se pueden recuperar utilizando las funciones EL provistas.

Cuestiones relacionadas