Локализация значений перечисления в наборе ресурсов
У меня проблема с 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?
Ответы
Ответ 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.