Un guide sur le ResourceBundle

1. Vue d’ensemble

Au cours de leur carrière professionnelle, de nombreux développeurs de logiciels ont la possibilité de développer des applications ou des systèmes multilingues. Celles-ci sont généralement destinées à des utilisateurs finaux de différentes régions ou de différentes langues.

Il est toujours difficile de maintenir et d’étendre ces applications. Une capacité à exploiter simultanément différentes données spécifiques à la localisation est généralement cruciale. La modification des données de l’application doit être aussi simple que possible, sans nécessiter de recompilation. C’est pourquoi nous évitons généralement de coder en dur les noms d’étiquettes ou de boutons.

Heureusement, nous pouvons faire confiance à Java qui nous fournit cette classe, ce qui nous aide à résoudre tous les problèmes susmentionnés.

  • En termes simples, le ResourceBundle permet à notre application de charger des données à partir de fichiers distincts contenant des données spécifiques aux paramètres régionaux. **

1.1. ResourceBundles

La première chose à savoir est que tous les fichiers d’un groupe de ressources doivent figurer dans le même package/répertoire et avoir un nom de base commun . Ils peuvent avoir des suffixes spécifiques aux paramètres régionaux indiquant la langue, le pays ou la plate-forme, séparés par un symbole de soulignement.

Il est important que nous puissions ajouter un code de pays s’il existe déjà un code de langue ou une plate-forme si des codes de langue et de pays sont présents.

Regardons des exemples de noms de fichiers:

  • ExampleResource

  • ExampleResource en__

  • ExampleResource en US

  • ExampleResource en US UNIX__

Le fichier par défaut pour chaque ensemble de données est toujours un sans suffixe - ExampleResource . Comme il existe deux sous-classes de ResourceBundle :

  • PropertyResourceBundle et ListResourceBundle ** , nous pouvons conserver de manière interchangeable des données dans des fichiers de propriétés ainsi que des fichiers java.

Chaque fichier doit avoir un nom spécifique aux paramètres régionaux et une extension de fichier appropriée, par exemple, ExampleResource en US.properties ou Example en.java__.

1.2. Fichiers de propriétés - PropertyResourceBundle

Les fichiers de propriétés sont représentés par PropertyResourceBundle. Ils stockent les données sous forme de paires clé-valeur sensibles à la casse.

Analysons un exemple de fichier de propriétés:

# Buttons
continueButton continue
cancelButton=cancel

! Labels
helloLabel:hello

Comme on peut le constater, il existe trois styles différents pour définir des paires clé-valeur.

Tous sont équivalents, mais le premier est probablement le plus populaire parmi les programmeurs Java . Cela vaut la peine de savoir que nous pouvons également mettre des commentaires dans les fichiers de propriétés. Les commentaires commencent toujours par # ou ! .

1.3. Fichiers Java - ListResourceBundle

Tout d’abord, afin de stocker nos données spécifiques à une langue, nous devons créer une classe qui étend ListResourceBundle et remplace la méthode getContents () . La convention de nom de classe est la même que pour les fichiers de propriétés.

Pour chaque Locale, nous devons créer une classe Java distincte.

Voici un exemple de classe:

public class ExampleResource__pl__PL extends ListResourceBundle {

    @Override
    protected Object[][]getContents() {
        return new Object[][]{
          {"currency", "polish zloty"},
          {"toUsdRate", new BigDecimal("3.401")},
          {"cities", new String[]{ "Warsaw", "Cracow" }}
        };
    }
}

Les fichiers Java présentent un avantage majeur par rapport aux fichiers de propriétés, à savoir la possibilité de contenir tout objet souhaité - pas uniquement Strings.

D’autre part, chaque modification ou introduction d’une nouvelle classe Java spécifique aux paramètres régionaux nécessite la recompilation d’une application, tandis que les fichiers de propriétés peuvent être étendus sans aucun effort supplémentaire.

2. Utiliser des ensembles de ressources

Nous savons déjà comment définir des ensembles de ressources, nous sommes donc prêts à l’utiliser.

Prenons l’extrait de code court:

Locale locale = new Locale("pl", "PL");
ResourceBundle exampleBundle = ResourceBundle.getBundle("package.ExampleResource", locale);

assertEquals(exampleBundle.getString("currency"), "polish zloty");
assertEquals(exampleBundle.getObject("toUsdRate"), new BigDecimal("3.401"));
assertArrayEquals(exampleBundle.getStringArray("cities"), new String[]{"Warsaw", "Cracow"});

Tout d’abord, nous pouvons définir notre Locale , à moins de ne pas vouloir utiliser celui par défaut.

Ensuite, appelons une méthode de fabrique statique de ResourceBundle . Nous devons passer le nom du paquet avec son paquet/répertoire et les paramètres régionaux en tant que paramètres.

Il existe également une méthode de fabrication qui ne requiert un nom d’ensemble que si les paramètres régionaux par défaut sont corrects. Dès que nous avons l’objet, nous pouvons récupérer les valeurs par leurs clés.

De plus, l’exemple montre que nous pouvons utiliser getString (clé String) , getObject (clé String), et getStringArray (clé String) pour obtenir les valeurs souhaitées.

3. Sélection de la ressource Bundle appropriée

  • Si nous voulons utiliser une ressource bundle, il est important de savoir comment Java sélectionne les fichiers

Imaginons que nous travaillions avec une application qui a besoin d’étiquettes en polonais mais que la locale JVM par défaut est Locale.US .

Au début, l’application cherchera les fichiers dans le chemin de classe correspondant aux paramètres régionaux que vous demandez. Il commence par le nom le plus spécifique, c’est-à-dire celui qui contient une plate-forme, un pays et une langue.

Ensuite, cela passe à plus général. S’il n’y a pas de correspondance, les paramètres régionaux par défaut ne sont pas vérifiés par la plateforme.

  • En l’absence de correspondance, il essaiera de lire le paquet par défaut. ** Tout devrait être clair quand on regarde l’ordre des noms de fichiers sélectionnés:

  • Label pl PL UNIX__

  • Label pl PL

  • Label pl__

  • Label en US

  • Label en__

  • Marque

Nous devons garder à l’esprit que chaque nom représente à la fois les fichiers .java et .properties , mais le premier a la priorité sur le second.

Lorsqu’il n’y a pas de fichier approprié, une exception MissingResourceException est émise.

4. Héritage

Un autre avantage du concept de regroupement de ressources est l’héritage de propriété. Cela signifie que les paires clé-valeur incluses dans des fichiers moins spécifiques sont héritées par celles situées plus haut dans l’arbre d’héritage.

Supposons que nous ayons trois fichiers de propriétés:

#resource.properties
cancelButton = cancel

#resource__pl.properties
continueButton = dalej

#resource__pl__PL.properties
backButton = cofnij

Le groupe de ressources récupéré pour Locale («pl», «PL») renverrait les trois clés/valeurs du résultat. Il est à noter que il n’est pas possible de revenir au groupe de paramètres régionaux par défaut dans la mesure où l’héritage de propriété est pris en compte.

De plus, ListResourceBundles et PropertyResourceBundles ne sont pas dans la même hiérarchie.

Ainsi, si un fichier de propriétés est trouvé dans le chemin d’accès aux classes, les paires clé-valeur sont héritées uniquement des fichiers de propriétés. La même règle s’applique aux fichiers Java.

5. Personnalisation

Tout ce que nous avons appris ci-dessus concerne l’implémentation par défaut de ResourceBundle . Cependant, il existe un moyen de modifier son comportement.

Nous faisons cela en étendant ResourceBoundle.Control et en surchargeant ses méthodes.

Par exemple, nous pouvons modifier la durée de conservation des valeurs dans le cache ou déterminer la condition dans laquelle le cache doit être rechargé.

Pour une meilleure compréhension, préparons une méthode succincte à titre d’exemple:

public class ExampleControl extends ResourceBundle.Control {

    @Override
    public List<Locale> getCandidateLocales(String s, Locale locale) {
        return Arrays.asList(new Locale("pl", "PL"));
    }
}

Le but de cette méthode est de changer le mode de sélection des fichiers dans le classpath. Comme nous pouvons le constater, ExampleControl ne renverra que polonais Locale , quel que soit le type par défaut ou défini Locale .

6. UTF-8

Comme il existe encore de nombreuses applications utilisant JDK 8 ou des versions plus anciennes, il est utile de savoir que avant Java 9 ListResourceBundles avait encore un avantage sur PropertyResourceBundles . Les fichiers Java pouvant stocker des objets String, ils peuvent contenir n’importe quel caractère pris en charge par le codage UTF-16 .

Au contraire, PropertyResourceBundle charge les fichiers par défaut en utilisant le codage ISO 8859-1 , qui contient moins de caractères que UTF-8 (ce qui pose des problèmes pour nos exemples de langue polonaise).

Afin de sauvegarder les caractères dépassant UTF-8 , nous pouvons utiliser le convertisseur Native-To-ASCII - native2ascii . Il convertit tous les caractères non conformes à la norme ISO 8859-1 en les codant en notation \ uxxxx .

Voici un exemple de commande:

native2ascii -encoding UTF-8 utf8.properties nonUtf8.properties

Et voyons à quoi ressemblent les propriétés avant et après un changement d’encodage:

#Before
polishHello=cześć

#After
polishHello=cze\u015b\u0107

Heureusement, cet inconvénient n’existe plus en Java 9. JVM lit les fichiers de propriétés en codage UTF-8 et l’utilisation de caractères non latins ne pose aucun problème.

7. Conclusion

BundleResource contient beaucoup de ce dont nous avons besoin pour développer une application multilingue. Les fonctionnalités que nous avons abordées facilitent la manipulation de différents lieux.

Nous évitons également les valeurs codées en dur, ce qui nous permet d’étendre les Locales prises en charge en ajoutant simplement de nouveaux fichiers Locale , ce qui permet à notre application d’être modifiée et gérée de manière fluide.

Comme toujours, l’exemple de code est disponible sur over sur GitHub .