2010-12-07 34 views
12

J'ai un problème avec les énumérations i18n dans mon application JSF. Quand j'ai commencé, j'avais enums avec le texte défini à l'intérieur. Mais maintenant, j'ai des clés liées à des paquets de messages dans l'énumération.Localisation des valeurs enum dans le regroupement de ressources

Exemple un de mes énumérations:

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

} 

Dans la couche de vue, j'utiliser quelque chose comme:

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

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

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

Il a bien fonctionné, mais quand j'avais besoin de sortir #{order.status}, j'avais besoin de le convertir. J'ai donc implémenté un convertisseur, mais j'ai eu des problèmes avec la conversion de String en Object dans la méthode 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; 
    } 

} 

Je suis empêtré maintenant avec ça. Quelqu'un sait-il comment internationaliser plusieurs Enums efficacement?

Répondre

25

La valeur transmise par le convertisseur n'est pas l'étiquette de l'option, contrairement à la valeur de l'option. La meilleure pratique consiste à ne pas le faire du côté du modèle, mais du côté de la vue, car le modèle ne devrait pas avoir besoin d'être averti. En ce qui concerne l'approche, vous compliquez inutilement les choses. Depuis JSF 1.2 il y a un EnumConverter intégré qui démarrera automatiquement et puisque JSF 2.0 vous pouvez parcourir un tableau générique ou List en f:selectItems par le nouvel attribut var sans avoir besoin de dupliquer les valeurs sur un List<SelectItem> dans le modèle.

Voilà comment le haricot peut ressembler à:

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

    // ... 
} 

Et voici comment la vue peut ressembler (en supposant que msg fait référence à la <var> que vous avez DÉFINIES dans <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> 

C'est tout.


Rien à voir avec le problème, vous avez des fautes de frappe dans les clés de nom de ENUM et le message, il devrait être:

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

Et, serait plus propre pour garder les clés de faisceau sur l'ENUM et utilisez enum lui-même dans le cadre de la clé de regroupement. Par exemple.

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

Eh bien, enum est juste une autre classe. Rien ne vous empêche d'ajouter des méthodes d'analyse et de conversion de chaînes qui analysent et affichent les messages sensibles aux paramètres régionaux. Peut-être viole-t-il le principe du seul responsable (n'est-ce pas?), Mais je crois que faire une énumération responsable de l'analyse et du renvoi des valeurs tenant compte des paramètres régionaux est la bonne chose à faire.

Il suffit d'ajouter deux méthodes comme ceci:

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

J'espère que je suis le bon code, comme je ne suis pas le vérifier avec IDE maintenant ... Est-ce que vous cherchez?

+0

Le code est bon, mais cela ne fonctionnera pas dans le cas d'OP car ce n'est pas l'étiquette d'option qui est transmise au convertisseur. – BalusC

+0

Merci, je n'étais pas au courant de ça. –

1

Je calcule la clé du message dans le ENUM comme ci-après; donc pas besoin de maintenir les clés avec des attributs supplémentaires sur le ENUM

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

Puis-je utiliser comme ça

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

d'avoir configuré un org.springframework.context.support.ReloadableResourceBundleMessageSource dans le contexte de l'application

<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

J'ai posté ici ma solution: Internationalization of multiple enums (translation of enum values) - mais toujours dans l'espoir d'améliorer encore.

EDIT: avec l'aide de @Joop Eggen, nous avons mis au point une solution vraiment cool:

EDIT à nouveau: solution complète et prête à l'emploi:

Faire une classe

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

Faites une fonction EL personnalisée

<?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> 

Ajouter le taglib à votre web.xml

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

ont des propriétés et des fichiers enum_en.properties enum_yourlanguage.properties comme celui-ci

TransferStatus.NOT_TRANSFERRED = Not transferred 
TransferStatus.TRANSFERRED = Transferred 

Ajouter les fichiers de propriétés sous forme de faisceaux de ressources à votre visage-config.xml

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

Ajouter le taglib personnalisé à vos fichiers xhtml

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

et - voilà - vous pouvez désormais accéder aux valeurs enum traduites dans jsf:

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

Au cas où quelqu'un est à la recherche d'une bibliothèque utilitaire simple pour gérer l'internationalisation enum, s'il vous plaît jeter un oeil à https://github.com/thiagowolff/litefaces-enum-i18n

L'artefact est également disponible dans Maven Central:

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

Fondamentalement, il vous suffit d'ajouter l'artefact à votre projet et définir les ENUM clés respectives suivant les conventions de nommage enum décrites. Les traductions (et aussi les noms de classe CSS) peuvent être récupérées en utilisant les fonctions EL fournies.