Adicionar comportamento de i18n em um Entity

Olá pessoal.

Tenho um enum conforme segue abaixo:

[code]
public enum StatusProjetoEnum {

CADASTRADO ("CD", "Cadastrado"),
PUBLICADO ("PB", "Publicado"),
CANCELADO ("CN", "Cancelado"),
FINALIZADO ("FN", "Finalizado"),
FECHADO ("FC", "Fechado");

private String codigo;
private String descricao;

private StatusProjetoEnum(String codigo, String descricao) {
	this.codigo = codigo;
	this.descricao = descricao;
}

public String getCodigo() {
	return codigo;
}

public String getDescricao() {
	return descricao;
}

}[/code]

Como posso adicionar suporte a internacionalização num cenário como este?

Tentei as seguintes abordagens:

  • Filter: através de filter eu não consegui pois não possuo acesso aos métodos getters utilizados para gerar uma possível resposta, e como estou usando JSF, isto precisaria ser tratado no momento em que um texto é atribuído ao valor de um componente (por exemplo um outputLabel)
  • PhaseListener: tentei criar um listener para obter acesso ao contexto do faces durante uma fase específica ( por exemplo Render Response ) para modificar o resultado obtido após a execução de um getter do meu enum, porém a operação teria um custo alto ao tentar navegar por todos os componentes da árvore e buscar os métodos getters que devem ser traduzidos.
  • Spring AOP: como estou usando o spring, tentei usar Aspectos p/ interceptar uma chamada a um getter que deva ser traduzido e então modificar o resultado. Porém não quero manter meus Entities no contexto do spring (desta forma toda utilização das entities deveriam ser obtidas do contexto do spring e como o hibernate quem cria os objetos pra mim… não poderei usar aspectos).
  • Implementar a tradução no getter do próprio enum: estou pensando em implementar desta forma, porém meu enum ficará acoplado a uma instância do FacesContext, o que pode não estar disponível dependendo do contexto.

Alguém poderia me ajudar com alguma idéia melhor do que as que citei acima? Ou até mesmo ajudar a escolher entre uma destas opções?

Qualquer opinião será de grande ajuda.

Obrigado.

Adolfo,
A primeira chave do Enum “CD” é a chave no seu arquivo bundle?

Eu utilizo de vez em quando uma classe utilitária na minha aplicação para carregar mensagens dos meus resource bundles.

Talvez te ajude.

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

/**
 * Utility Class to hold the messages of the system.
 *
 */
public class Messages {

    public static FacesMessage getMessage(String bundleName, String resourceId,
            Object[] params) {

        FacesContext context = FacesContext.getCurrentInstance();
        Application application = context.getApplication();
        String bundle = application.getMessageBundle();
        Locale locale = getAppLocale(context);
        ClassLoader classloader = getClassLoader();

        String sumary = getString(bundle, bundleName, resourceId, locale, classloader, params);

        if (sumary == null) {
            sumary = "???" + resourceId + "???";
        }

        String detail = getString(bundle, bundleName, resourceId + "_detail", locale, classloader, params);

        return new FacesMessage(sumary, detail);
    }

    public static String getString(String bundle, String resourceId, Object[] params) {
        FacesContext context = FacesContext.getCurrentInstance();
        Application application = context.getApplication();
        String appBundle = application.getMessageBundle();
        Locale locale = getAppLocale(context);
        ClassLoader loader = getClassLoader();
        return getString(appBundle, bundle, resourceId, locale, loader, params);
    }

    public static String getString(String bundle1, String bundle2,
            String resourceId, Locale locale, ClassLoader loader,
            Object[] params) {
        String resource = null;
        ResourceBundle bundle;

        if (bundle1 != null) {
            bundle = ResourceBundle.getBundle(bundle1, locale, loader);

            try {
                resource = bundle.getString(resourceId);
            }
            catch (MissingResourceException ex) {
                //todo logar o erro
            }
        }

        if (resource == null) {
            bundle = ResourceBundle.getBundle(bundle2, locale, loader);
            if (bundle != null) {
                try {
                    resource = bundle.getString(resourceId);
                }
                catch (MissingResourceException ex) {
                    //todo logar o erro
                }
            }
        }

        if (resource == null) {
            return null; //Não há correspondencia
        }
        if (params == null) {
            return resource;
        }

        MessageFormat formartter = new MessageFormat(resource, locale);
        return formartter.format(params);
    }

    public static Locale getAppLocale(FacesContext context) {
        Locale locale = null;
        UIViewRoot viewRoot = context.getViewRoot();
        if (viewRoot != null) {
            locale = viewRoot.getLocale();
        }
        if (locale == null) {
            locale = Locale.getDefault();
        }
        return locale;
    }

    public static ClassLoader getClassLoader() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = ClassLoader.getSystemClassLoader();
        }
        return loader;
    }
}

Eu copiei essa classe do JSF Core dai algumas vezes utilizo implementações parecidas dela para resgatar Mensagens específicas.

Obrigado pela resposta Pedro.

A classe que você passou é bem interessante e vou acabar seguindo esta linha mesmo.
Uma coisa que eu não gostaria a princípio, era que meu Entity fiizesse referência ao faces mas acho que não terei como fugir disso.

Então Adolfo,

Internacionalization ao meu ver é uma funcionalidade (Comportamento) da prórpria camada de visão.

De alguma forma você vai ter que mapear as infromações do seu modelo de domínio para essa camada.

Essa classe que te passei contém métodos estáticos por isso, dai você poderia usar uma abordagem do tipo:


@ManagedBean
public class MeuManagedBean {
	
	public String facaAlgo(ActionEvent event) {
		
		/*** ....etc ... */	
		
		FacesMesssage message = Messages.getMessage(bundleName, statusProjetoEnum.getDescricao(), context)
	}
}

Dai você teria os properties do tipo:


messages.properties:
cadastrado = Cadastrado
publicado = Publicado
.... etc ....

messages_en.properties:
cadastrado = Joined
publicado = Published
... etc ...