3
\$\begingroup\$

I'm working on spring web application which has to be accessible both in polish and english. So generally, it's all about internationalization. Of course, internationalization is based on getting values by keys from properties files, that are named according to locale id e.g messages_pl. As I'm taking advantage of enums in context of status, categories etc. used by most of my domain entities, I want to display to user enum's constant name according to mentioned above languages.

I've already implemented some solution which is based on interface and default method within. I'd like to know your opinion on my approach. Basically I'm trying to make my code to speak for itself (be readable) and be reusable (not to mention about good design).

PreferedContactForm:

public enum PreferedContactForm implements EnumResourceBundleAware {

EMAIL,
MOBILE,
BOTH;

public String handleDisplayedMessage() {
    return this.handleDisplayedMessage(this);
}

@Override
public String retrieveMessagePattern() {
    return "prefered.contact.form.{0}";
}}

EnumResourceBundleAware:

public interface EnumResourceBundleAware {

default <E extends Enum<E>> String handleDisplayedMessage(E value) {
    ResourceBundle resourceBundle = ResourceBundleLoader.load();
    String formattedValueAsStr = value.name().toLowerCase().replace('_', '.');
    String displayedMessageCode = MessageFormat.format(retrieveMessagePattern(), formattedValueAsStr);
    return resourceBundle.getString(displayedMessageCode);
}

String retrieveMessagePattern();}

I hope my english is not too bad.

Thanks in advance for any opinion or other prefered solutions.

@UP (Second solution)

Thanks slowy for your opinion and tips (it was helpful). I implemented second solution, where i separated translation responsibility from enums by implementing some sort of service class, which is called EnumTranslator. Of course, I can call above mentioned serivce class from my views (i'm using thymeleaf template engine for rendering my html views). As with first solution, i look forward to your opinion.

EnumTranslator interface:

public interface EnumTranslator {

    public String searchTranslationForEnum(Enum enm);

    public String loadTranslationKeyForEnum(Enum enm);
}

EnumTranslatorSupport (it's my approach to call classes that implment interfaces):

    public class EnumTranslatorSupport implements EnumTranslator {

    @Override
    public String searchTranslationForEnum(Enum enm) {
        ResourceBundleLoader resourceBundleLoader = ResourceBundleLoader.create();
        ResourceBundle resourceBundle = resourceBundleLoader.load();

        String enumTranslationKey = loadTranslationKeyForEnum(enm); 
        String foundTranslationForEnum =  resourceBundle.getString(enumTranslationKey);
        if(StringUtils.isBlank(foundTranslationForEnum)) {
            throw new TranslationForEnumNotFoundException("There is no translation for enum");
        }

        return foundTranslationForEnum;
    }

    @Override
    public String loadTranslationKeyForEnum(Enum enm) {
        String enumNamespace = enm.getClass().getSimpleName();
        String enumNamePreparedForTranslationKey = enm.name().replace('_', ' ').toLowerCase();
        String translationKey = "";

        for(int i = 0, max = enumNamespace.length(); i < max; i++) {
            char ch = enumNamespace.charAt(i);
            translationKey += (Character.isUpperCase(ch)) ? ((i == 0) ? Character.toLowerCase(ch) : "." + Character.toLowerCase(ch)) : ch;    
        }

        translationKey = translationKey.concat(".").concat(enumNamePreparedForTranslationKey);
        return translationKey;
    }
}

And i would like also to refer to data layer implemented for my web application in context of enum persist. So basically, i'm saving my enums with a little help from my custom converter for enums. The point is, that i want to save lowercased enum constant name along with underscore replaced with single space. To depict it better, here is an example: Enum: IN_PROGRESS Enum constant name, which is saved in database: in progress I know it can look different, but for the time being it works as expected.

\$\endgroup\$

closed as off-topic by Toby Speight, Graipher, t3chb0t, IEatBagels, kraskevich Aug 26 '17 at 23:23

This question appears to be off-topic. The users who voted to close gave this specific reason:

If this question can be reworded to fit the rules in the help center, please edit the question.

1
\$\begingroup\$

Well, when I read it first, it made sense. But when I take a closer look, man there's some fancy stuff going on there!

public String handleDisplayedMessage() {
    return this.handleDisplayedMessage(this);
}

Dude, that's an endless loop. You just Stack-Overflow-Errored my VM :P


When I use the API, I do something like this:

    String msg = PreferedContactForm.MOBILE.handleDisplayedMessage(PreferedContactForm.EMAIL);
    System.out.println(msg);

Now, that's some dubious stuff, isn't it? Why can I call the method on 'MOBILE' and pass 'EMAIL'? This made me literally laugh xD. And what displayed message? Displayed where? Where do I get this display message? What does it do with it? 'getTranslationForEnum' or something would make much more sense.


I can also do that:

String msgPattern = PreferedContactForm.EMAIL.retrieveMessagePattern();

Now what? What can I do with that? Do I need that? Is this ... important? I would have chosen 'getMessageKeyPrefix' or something like that. Why must this be exposed anyway?


And this part:

default <E extends Enum<E>> String handleDisplayedMessage(E value) {

With the generic, misses the whole point of having the 'EnumResourceBundleAware', you can pass any enum! it should be this:

default String handleDisplayedMessage(EnumResourceBundleAware value) {

Why didn't you just made a simple class with a static method, which takes a prefix and an enum value? If you chose to save the message key as 'class.name' + "." + enumValue, for instance 'pack.age.PreferedContactForm.MOBILE=Mobile', you wouldn't even use the messageKeyPrefix! And you can get rid of MessageFormat. Why not something like this:

public static String getTranslationForEnum(EnumResourceBundleAware value)

And the API would be something like

TranslationThingerThing.getTranslationForEnum(PreferedContactForm.MOBILE);

When it comes to design, I wouldn't put the functionality of translation into an enum, even if it's inherited. And to have a default implementation in an interface still bothers me, it feels like an oxymoron to me. The functionality of translation itself is a 'concern', and should be separated (See what I did there?). Also, let's assume the enum value can be saved in a backend, you then also pass translation functionality to the persistency layer. Not a big fan of that.

And considering testing: Here's the usual 'having an abstract class'-problem. How do I test abstraction? You could use either a implementation, and call it good, or write an implementation for your test case only. It's not too bad, but it's not sexy either. My main problem with abstraction is, if you mix abstraction with implementation, in your case: your abstract method calls a method from the implementation, so the method you want to test is not tested cohesive.


The 'having an interface for enums for indicating that it is translatable'-thing I have seen before. But with the main goal, to have a test case which crawls the classpath for those enums, to verify, all enums are translated. Another variant I've seen was to have those enums annotated.


Hope this helps, slowy

\$\endgroup\$
  • \$\begingroup\$ Re: first paragraph - it's not an infinite loop. PreferedContactForm.handleDisplayedMessage() calls the handleDisplayedMessage(E value) inherited from EnumResourceBundleAware. \$\endgroup\$ – BenC Aug 25 '17 at 22:03
  • \$\begingroup\$ I appreciate your help. I will bear it in mind while I implement better solution. It's my first approach to solve this kind of problem. I hope, my next solution will be better. \$\endgroup\$ – MateuszMateusz Aug 26 '17 at 8:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.