本地化资源包中的枚举值

发布于 2024-10-06 06:23:50 字数 2740 浏览 3 评论 0原文

我的 JSF 应用程序中的 i18n 枚举有问题。当我开始时,我有枚举,其中定义了文本。但现在,我在枚举中将密钥绑定到消息包。

我的枚举示例之一:

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

}

在视图层中,我使用类似:

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

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

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

它运行良好,但是当我需要输出 #{order.status} 时,我需要对其进行转换。 因此,我实现了一个转换器,但在 getAsObject() 方法中将 String 转换为 Object 时遇到了麻烦。

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

}

我现在对此很纠结。有人知道如何有效地国际化多个枚举吗?

I have a problem with i18n enums in my JSF application. When I started, I had enums with the text defined inside. But now, I have keys tied to message bundles in the enum.

Example one of my enums:

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

}

In the view layer, I use something like:

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

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

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

It worked well, but when I needed to output #{order.status}, I needed to convert it.
So I implemented a converter, but got in trouble with conversion of String to Object in the getAsObject() method.

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

}

I'm entangled now with that. Anybody know how to internationalize multiple Enums efficiently?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

安穩 2024-10-13 06:23:50

通过转换器传递的值不是您期望的选项标签,而是选项值。最佳实践是不要在模型端执行此操作,而是在视图端执行此操作,因为模型不需要了解 i18n。

至于方法,你基本上不必要地使事情变得过于复杂。从 JSF 1.2 开始,有一个内置的 EnumConverter 会自动启动,从 JSF 2.0 开始,您可以通过新的 varf:selectItems 中的通用数组或 List code> 属性,无需在模型中的 List 上复制值。

bean 的外观如下:

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

    // ...
}

视图的外观如下(假设 msg 引用您在 中定义的 faces-config.xml 中的):

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

仅此而已。


与问题无关,您在枚举名称和消息键中输入了拼写错误,它应该是:

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

并且,更干净的方法是将捆绑包密钥保留在枚举之外,并使用枚举本身作为捆绑包密钥的一部分。例如

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

The value which is passed through the converter is not the option label as you seem to expect, but the option value. The best practice is to not do this in the model side, but in the view side, because the model shouldn't need to be i18n aware.

As to the approach, you're basically unnecessarily overcomplicating things. Since JSF 1.2 there's a builtin EnumConverter which will kick in automatically and since JSF 2.0 you can iterate over a generic array or List in f:selectItems by the new var attribute without the need to duplicate the values over a List<SelectItem> in the model.

Here's how the bean can look like:

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

    // ...
}

And here's how the view can look like (assuming that msg refers to the <var> as you've definied in <resource-bundle> in faces-config.xml):

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

That's all.


Unrelated to the problem, you've typos in the enum name and message keys, it should be:

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

And, more clean would be to keep the bundle keys out the enum and use enum itself as part of bundle key. E.g.

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
枫以 2024-10-13 06:23:50

我在这里发布了我的解决方案: 多个枚举的国际化(枚举值的翻译) - 但仍希望进一步增强。

编辑:在@Joop Eggen的帮助下,我们提出了一个非常酷的解决方案:

再次编辑:完整且随时可用的解决方案:

创建一个类

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

使其成为自定义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>

将taglib添加到您的web.xml

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

中像这样的属性文件 enum_en.properties 和 enum_yourlanguage.properties

TransferStatus.NOT_TRANSFERRED = Not transferred
TransferStatus.TRANSFERRED = Transferred

将属性文件作为资源包添加到 faces-config.xml

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

将自定义标记库添加到 xhtml 文件

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

然后 - 瞧 - 您现在可以在 jsf 中访问翻译后的枚举值:

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

I have posted my solution here: Internationalization of multiple enums (translation of enum values) - but still hoping for further enhancement.

EDIT: with the help of @Joop Eggen, we have come up with a really cool solution:

EDIT again: complete and ready-to-use solution:

Make a class

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

Make it a custom EL function

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

Add the taglib to your web.xml

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

Have properties files enum_en.properties and enum_yourlanguage.properties like this

TransferStatus.NOT_TRANSFERRED = Not transferred
TransferStatus.TRANSFERRED = Transferred

Add the properties files as resource bundles to your faces-config.xml

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

Add the custom taglib to your xhtml files

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

And - voilà - you can now access the translated enum values in jsf:

<h:outputText value="#{enum[l:xlate(order.transferStatus)]}" />
憧憬巴黎街头的黎明 2024-10-13 06:23:50

嗯,枚举只是另一个类。没有什么可以阻止您添加解析和字符串转换方法来解析和输出区域设置敏感的消息。

也许它违反了单一责任原则(是吗?),但我相信让枚举负责解析和返回区域设置感知值是正确的做法。

只需添加两个这样的方法:

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

我希望我的代码是正确的,因为我现在没有使用 IDE 检查它...这是您正在寻找的吗?

Well, enum is just another class. There is nothing stopping you from adding parsing and to-string conversion methods that will parse and output locale-sensitive messages.

Maybe it violates Single Responsible Principle (does it?), but I believe making enum responsible for parsing and returning locale-aware values is the right thing to do.

Just add two methods like this:

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

I hope I got the code right, as I am not checking it with IDE now... Is this what you were looking for?

无所谓啦 2024-10-13 06:23:50

我计算枚举中的消息键,如下所示;所以不需要在枚举上维护具有附加属性的键

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

然后我

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

在应用程序上下文中配置了 org.springframework.context.support.ReloadableResourceBundleMessageSource

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

I calculate the message key in the enum like as shown below; so no need to maintain the keys with additional attributes on the enum

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

Then I use it like this

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

with having configured a org.springframework.context.support.ReloadableResourceBundleMessageSource in the app context

<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>
墨落成白 2024-10-13 06:23:50

如果有人正在寻找一个简单的实用程序库来处理枚举国际化,请查看 https:/ /github.com/thiagowolff/litefaces-enum-i18n

该工件也可以在 Maven Central 中找到:

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

基本上,您只需将工件添加到您的项目中,并按照所描述的枚举命名约定定义枚举各自的键即可。可以使用提供的 EL 函数检索翻译(以及 CSS 类名称)。

In case anyone is looking for a simple utility library to handle enum internationalization, please take a look at https://github.com/thiagowolff/litefaces-enum-i18n

The artifact is also available in Maven Central:

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

Basically, you just need to add the artifact to your project and define the enum respective keys following the described enum naming conventions. The translations (and also CSS class names) can be retrieved using the provided EL functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文