Il miglior approccio per Java Enum multilingue

7

Sto incontrando problemi con un approccio che sto prendendo e ora mi chiedo se ho appena iniziato il percorso sbagliato e dovrei ripensare al mio approccio. Ecco cosa sto tentando.

Vorrei utilizzare un Enum per rappresentare diversi possibili stati dell'account (ci sono altri set di dati finiti che imposterò usando un approccio duplice):

  • A, attivo
  • I, non attivo
  • P, in sospeso

Questi account status devono essere presentati all'utente nella loro lingua, se preferisci.

Esempio: lo stato dell'account è A, lo mostra come attivo per gli anglofoni inglesi e Activo per lo spagnolo.

Ci sono anche situazioni in cui mi piacerebbe mostrare un elenco a discesa di tutti i possibili stati dell'account nella preferenza locale appropriata.

Approccio attuale

Utilizza una classe AccountStatusEnum con valori di (A, I, P).

In una variabile membro privata statica, memorizza un EnumMap di Account Status localizzati all'interno di una EnumMap di possibili Locales.

Esempio:

private static EnumMap<LocaleEnum, EnumMap<AccountStatusEnum, String>> localeAccountStatuses = new EnumMap<LocaleEnum, EnumMap<AccountStatusEnum, String>>(LocaleEnum.class);

Questo verrebbe caricato all'interno di un blocco di inizializzazione statico.

Potrei quindi utilizzare i seguenti metodi per ottenere tutti i valori per un elenco a discesa:

public static EnumMap<AccountStatusEnum, String> getAccountStatuses(LocaleEnum locale) {
        return localeAccountStatuses.get(locale);
    }

Oppure potrei usare questo metodo per ottenere una singola descrizione per un dato Enum:

public String getDescription(LocaleEnum locale){
            return getAccountStatuses(locale).get(this);
        }

Esempio di utilizzo: AccountStatusEnum.A.getDescription (LocaleEnum.es_MX);

Sono curioso dei tuoi pensieri su questo approccio e di possibili approcci migliori per realizzare la stessa cosa.

Modifica La logica per ottenere i valori per popolare gli Enum sarebbe centralizzata all'interno di una singola classe helper. Pertanto, se la fonte delle descrizioni cambia a venire da una nuova fonte, la modifica del codice è ridotta a icona.

Grazie

    
posta forevernewb123 20.09.2014 - 18:35
fonte

3 risposte

14

Mi avvicinerei a questo problema poiché mi avvicinerei a qualsiasi problema di localizzazione: ResourceBundle . Io uso una classe chiamata I18n che ha un metodo statico chiamato getMessage che accetta una chiave e opzionalmente un elenco di argomenti. La chiave viene cercata in una ResourceBundle configurata per utilizzare il Locale predefinito o quello che preferisci (i dettagli qui non sono importanti, purché chi chiama a I18n non debba sapere cosa è Locale è).

La classe I18n è la seguente:

public final class I18n {
    private I18n() {
    }

    private static ResourceBundle bundle;

    public static String getMessage(String key) {
        if(bundle == null) {
            bundle = ResourceBundle.getBundle("path.to.i18n.messages");
        }
        return bundle.getString(key);
    }

    public static String getMessage(String key, Object ... arguments) {
        return MessageFormat.format(getMessage(key), arguments);
    }
}

Nel tuo progetto, avresti nel pacchetto path.to.i18n, i file contenenti messages.properties (predefinito), messages_en.properties (Locale en), messages_it.properties (Locale esso), ecc.

Quando devi tradurre un valore enum, non dovresti trattarlo in modo diverso rispetto a qualsiasi altra chiave, tranne che la chiave è il termine enum.

In altre parole, hai:

public enum AccountStatusEnum {
  Active,
  Inactive,
  Pending
}

Quando chiami I18n.getMessage , passi con AccountStatusEnum.Active.toString() e la traduzione sarà trovata in un file di proprietà con la chiave "Attiva". Se sei come me e preferisci utilizzare le lettere minuscole, devi eseguire toLowerCase() sulla stringa. Ancora meglio, si crea un nuovo I18n.getMessage che prende un Enum piuttosto che una chiave e quel metodo chiama la versione String di I18n.getMessage con il valore enum convertito in stringa e in lettere minuscole.

Quindi dovresti aggiungere questo alla classe I18n :

    public static String getMessage(Enum<?> enumVal) {
        return getMessage(enumVal.toString().toLowerCase());
    }

Preferisco utilizzare questo metodo perché incorpora la stessa logica di traduzione nella stessa parte del programma senza influire sul design. Potresti anche voler differenziare le chiavi enum dalle tue altre chiavi, nel qual caso puoi iniziare ciascuna chiave enum con "enum", quindi dovresti anteporre "enum". quando chiami getMessage . Ricordati di nominare le tue chiavi nei tuoi file di proprietà di conseguenza.

Spero che ti aiuti!

    
risposta data 20.09.2014 - 19:19
fonte
1

ResourceBundle è la strada da percorrere, ma vorrei approfittare del fatto che le enumerazioni sono oggetti e nascondere i dettagli di i19n all'interno

public enum MyEnum {
  A,
  I,
  P;

  private String localeText = null;

  private MyEnum() {
    // Load resource bundle
    this.localeText = // get message;
  }

  public String getLocaleText() {
    return this.localeText();
  }
}

Questo ti permetterebbe di fare:

MyEnum.A.getLocaleText();

direttamente, ovunque

Un ottimizzazione potrebbe caricare il pacchetto di risorse come un attributo statico, in un inizializzatore statico.

E in JEE, punti bonus per rendere il bundle di risorse iniettabile (quindi è possibile modificare facilmente le stringhe di localizzazione).

    
risposta data 21.09.2014 - 12:22
fonte
1

Riassumendo i pro e i contro delle risposte proposte:

Il problema con il file helper globale è la mancanza di flessibilità, ad esempio, in un'applicazione ho varie enumerazioni con una costante enum specifica che mi piacerebbe avere una chiave molto specifica, non seguendo il formato comune.

Il problema con la localizzazione della logica nell'enumerazione è la replica del codice, che a sua volta renderebbe più difficile mantenere a lungo termine

Con Java 8 abbiamo a nostra disposizione una funzionalità molto utile che può essere molto utile per risolvere i problemi discussi sopra: metodi predefiniti.

La mia proposta è di avere un'interfaccia con un singolo metodo per ottenere le chiavi:

public interface I18nEnum {
   default String getMessageKey(Enum<?> e) {
      return e.getClass().getSimpleName() + '.' + e.name().toLowerCase();
   }
}

In questo modo le nostre enumerazioni devono semplicemente dichiarare di implementare quell'interfaccia, senza implementarla effettivamente, e abbiamo i vantaggi di avere il nostro codice in un unico posto, ma abbiamo ancora la flessibilità di gestire casi specifici sovrascrivendo il metodo predefinito quando necessario, ad esempio:

public enum Sex implements I18nEnum {
    MALE,
    FEMALE,
    UNKNOWN {
        @Override
        public String getMessageKey(Enum<?> e) {
            return "Common.UNKNOWN";
        }
    }
}
    
risposta data 25.07.2016 - 23:26
fonte

Leggi altre domande sui tag