Подтвердить что ты не робот

Локализация значений перечисления в наборе ресурсов

У меня проблема с i18n перечислениями в моем приложении JSF. Когда я начал, у меня были перечисления с текстом, определенным внутри. Но теперь у меня есть ключи, связанные с пакетами сообщений в перечислении.

Пример одного из моих перечислений:

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}, мне нужно было его преобразовать. Поэтому я реализовал конвертер, но получил проблемы с преобразованием String в Object в метод 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;
    }

}

Теперь я запутался. Кто-нибудь знает, как эффективно интернационализировать несколько Enums?

4b9b3361

Ответ 1

Значение, которое передается через конвертер, не является меткой параметра, как вы ожидаете, но значением параметра. Лучшая практика заключается в том, чтобы не делать этого на стороне модели, но со стороны зрения, потому что модель не должна быть осведомлена о ней.

Что касается подхода, вы в основном излишне преувеличиваете вещи. Поскольку JSF 1.2 содержит встроенный EnumConverter, который будет автоматически запускаться, а с JSF 2.0 вы можете выполнять итерацию по универсальному массиву или List в f:selectItems с помощью нового атрибута var без необходимости дублировать значения по сравнению с List<SelectItem> в модели.

Вот как выглядит bean:

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

    // ...
}

И вот как выглядит представление (при условии, что msg относится к <var>, как вы определили в <resource-bundle> в 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");

И более чистым было бы сохранить ключи пакета из перечисления и использовать enum самостоятельно как часть ключа пакета. Например.

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

Ответ 2

Я разместил здесь свое решение: Интернационализация множественных перечислений (перевод значений перечисления) - но все же надеемся на дальнейшее совершенствование.

EDIT: с помощью @Joop Eggen мы разработали действительно классное решение:

EDIT снова: полное и готовое к использованию решение:

Сделать класс

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>

Добавить пользовательский taglib в ваши xhtml файлы

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

И - voilà - теперь вы можете получить доступ к переведенным значениям перечисления в jsf:

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

Ответ 3

Ну, перечисление - это еще один класс. Нет ничего, что мешало бы вам добавлять методы анализа и преобразования строк, которые будут анализировать и выводить сообщения, чувствительные к языку.

Может быть, это нарушает принцип одиночной ответственности (не так ли?), но я считаю, что сделать перечисление, ответственное за разбор и возвращение значений, соответствующих языковым стандартам, - это правильная вещь.

Просто добавьте два метода:

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... Это то, что вы искали?

Ответ 4

Я вычисляю ключ сообщения в перечислении, как показано ниже; поэтому нет необходимости поддерживать ключи с дополнительными атрибутами в перечислении

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>

Ответ 5

Если кто-то ищет простую библиотеку утилиты для обработки интернационализации enum, посмотрите 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>

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