Internationalisation et localisation en Java 8

Internationalisation et localisation en Java 8

1. Vue d'ensemble

Internationalization is a process of preparing an application to support various linguistic, regional, cultural or political-specific data. C'est un aspect essentiel de toute application multilingue moderne.

Pour en savoir plus sur,, nous devons savoir qu'il existe une abréviation très populaire (probablement plus populaire que le nom réel) pour l'internationalisation -i18n en raison des 18 lettres entre «i» et «n».

Il est crucial pour les programmes d’entreprise actuels de servir des personnes de différentes régions du monde ou de plusieurs domaines culturels. Les régions culturelles ou linguistiques distinctes ne déterminent pas seulement les descriptions spécifiques à la langue, mais également la devise, la représentation numérique et même la composition divergente de la date et de l'heure.

Par exemple, concentrons-nous sur les chiffres spécifiques à chaque pays. Ils ont divers séparateurs décimaux et mille:

  • 102 300,45 (États-Unis)

  • 102 300,45 (Pologne)

  • 102.300,45 (Germany)

Il existe également différents formats de date:

  • Lundi 1 janvier 2018 15:20:34 CET (Etats-Unis)

  • lundi 1 janvier 2018 15 h 20 CET (France).

  • 2018 1 月 1 分一 03 20 分 34 秒 HEC (Chine)

De plus, différents pays ont des symboles de devise uniques:

  • £ 1,200.60 (Royaume-Uni)

  • 1.200,60 € (Italie)

  • 1 200,60 € (France)

  • $1,200.60 (United States)

Un fait important à savoir est que même si les pays ont la même devise et le même symbole monétaire - comme la France et l'Italie, la position de leur symbole monétaire pourrait être différente.

2. Localisation

Dans Java, nous avons une fonctionnalité fantastique à notre disposition appelée la classeLocale.

Cela nous permet de différencier rapidement les lieux culturels et de formater notre contenu de manière appropriée. C’est essentiel dans le cadre du processus d’internationalisation. Identique à i18n, la localisation a également son abréviation -*l10n*.

La principale raison d'utiliserLocale est que tous les formats requis spécifiques aux paramètres régionaux sont accessibles sans recompilation. Une application peut gérer plusieurs paramètres régionaux en même temps. La prise en charge d'une nouvelle langue est donc simple.

Les paramètres régionaux sont généralement représentés par une abréviation de langue, de pays et de variante séparée par un trait de soulignement:

  • de (allemand)

  • it_CH (italien, suisse)

  • en_US_UNIX (États-Unis, plate-forme UNIX)

2.1. Des champs

Nous avons déjà appris queLocale consists of language code, country code, and variant. There are two more possible fields to set: script and extensions.

Jetons un œil à une liste de champs et voyons quelles sont les règles:

  • Language peut être un codeISO 639 alpha-2 or alpha-3 ou une sous-étiquette de langue enregistrée.

  • Region (Pays) est l'indicatif du paysISO 3166 alpha-2 ou l'indicatif régionalUN numeric-3.

  • Variant est une valeur sensible à la casse ou un ensemble de valeurs spécifiant une variation deLocale.

  • Script doit être un codeISO 15924 alpha-4 valide.

  • Extensions est une carte qui se compose de clés de caractère unique et de valeursString.

LeIANA Language Subtag Registry contient des valeurs possibles pourlanguage,region,variant etscript.

Il n'y a pas de liste de valeursextension possibles, mais les valeurs doivent être des sous-étiquettesBCP-47 bien formées. Les clés et les valeurs sont toujours converties en minuscules.

2.2. Locale.Builder

Il existe plusieurs manières de créer des objetsLocale. Une manière possible utiliseLocale.Builder. Locale.Builder dispose de cinq méthodes de définition que nous pouvons utiliser pour construire l'objet et en même temps valider ces valeurs:

Locale locale = new Locale.Builder()
  .setLanguage("fr")
  .setRegion("CA")
  .setVariant("POSIX")
  .setScript("Latn")
  .build();

La représentationString desLocale ci-dessus estfr_CA_POSIX # Latn_.

Il est bon de savoir quesetting ‘variant' may be a little bit tricky as there’s no official restriction on variant values, although the setter method requires it to be BCP-47 compliant.

Sinon, il lanceraIllformedLocaleException.

Dans le cas où nous devons utiliser une valeur qui ne passe pas la validation, nous pouvons utiliser les constructeursLocale car ils ne valident pas les valeurs.

2.3. Constructeurs

Locale a trois constructeurs:

  • nouvelle locale (langue de chaîne)

  • nouvelle locale (langue de chaîne, pays de chaîne)

  • nouvelle locale (langue de chaîne, pays de chaîne, variante de chaîne)

Un constructeur à 3 paramètres:

Locale locale = new Locale("pl", "PL", "UNIX");

Unvariant valide doit être unString de 5 à 8 alphanumériques ou un seul numérique suivi de 3 alphanumériques. Nous ne pouvons appliquer «UNIX» au champvariant que via le constructeur car il ne répond pas à ces exigences.

Cependant, l’utilisation de constructeurs pour créer des objetsLocale présente un inconvénient: nous ne pouvons pas définir d’extensions ni de champs de script.

2.4. Constantes

C'est probablement le moyen le plus simple et le plus limité d'obtenir desLocales. La classeLocale a plusieurs constantes statiques qui représentent le pays ou la langue la plus populaire:

Locale japan = Locale.JAPAN;
Locale japanese = Locale.JAPANESE;

2.5. Tags de langue

Une autre façon de créerLocale consiste à appeler la méthode de fabrique statiqueforLanguageTag(String languageTag). Cette méthode nécessite unString qui répond à la normeIETF BCP 47.

Voici comment créer lesLocale britanniques:

Locale uk = Locale.forLanguageTag("en-UK");

2.6. Locales disponibles

Même si nous pouvons créer plusieurs combinaisons d'objetsLocale, nous ne pourrons peut-être pas les utiliser.

Une note importante à prendre en compte est que lesLocalesur une plate-forme dépendent de ceux qui ont été installés dans Java Runtime.

Comme nous utilisonsLocales pour le formatage, les différents formateurs peuvent avoir un ensemble encore plus petit deLocales disponibles qui sont installés dans le Runtime.

Voyons comment récupérer des tableaux de paramètres régionaux disponibles:

Locale[] numberFormatLocales = NumberFormat.getAvailableLocales();
Locale[] dateFormatLocales = DateFormat.getAvailableLocales();
Locale[] locales = Locale.getAvailableLocales();

Après cela, nous pouvons vérifier si notreLocale réside parmi lesLocales. disponibles

Nous devons nous rappeler quethe set of available locales is different for various implementations of the Java Platformand various areas of functionality.

La liste complète des paramètres régionaux pris en charge est disponible surthe Oracle’s Java SE Development Kit webpage.

2.7. Paramètres régionaux par défaut

Tout en travaillant avec la localisation, nous pourrions avoir besoin de savoir quelle est la valeur par défautLocale sur notre instanceJVM. Heureusement, il existe un moyen simple de le faire:

Locale defaultLocale = Locale.getDefault();

De plus, nous pouvons spécifier unLocale par défaut en appelant une méthode setter similaire:

Locale.setDefault(Locale.CANADA_FRENCH);

C'est particulièrement pertinent lorsque nous souhaitons créer des testsJUnit qui ne dépendent pas d'une instanceJVM.

3. Chiffres et monnaies

Cette section fait référence aux formateurs de nombres et de devises devant se conformer à différentes conventions spécifiques aux paramètres régionaux.

Pour formater les types de nombres primitifs (int,double) ainsi que leurs équivalents objet (Integer,Double), nous devrions utiliser la classeNumberFormat et sa valeur statique méthodes d'usine.

Deux méthodes sont intéressantes pour nous:

  • NumberFormat.getInstance(Locale locale)

  • NumberFormat.getCurrencyInstance(Locale locale)

Examinons un exemple de code:

Locale usLocale = Locale.US;
double number = 102300.456d;
NumberFormat usNumberFormat = NumberFormat.getInstance(usLocale);

assertEquals(usNumberFormat.format(number), "102,300.456");

Comme nous pouvons le voir, c’est aussi simple que de créerLocale et de l’utiliser pour récupérer l’instance deNumberFormat et formater un numéro d’échantillon. On peut remarquer quethe output includes locale-specific decimal and thousand separators.

Voici un autre exemple:

Locale usLocale = Locale.US;
BigDecimal number = new BigDecimal(102_300.456d);

NumberFormat usNumberFormat = NumberFormat.getCurrencyInstance(usLocale);
assertEquals(usNumberFormat.format(number), "$102,300.46");

Le formatage d'une devise implique les mêmes étapes que le formatage d'un nombre. La seule différence est que le formateur ajoute le symbole monétaire et la partie décimale arrondie à deux chiffres.

4. Date et l'heure

Nous allons maintenant en savoir plus sur le formatage des dates et des heures, probablement plus complexe que celui des nombres.

Tout d'abord, nous devons savoir que le formatage de la date et de l'heure a considérablement changé dans Java 8 car il contient une toute nouvelle APIDate/Time. Par conséquent, nous allons examiner différentes classes de formateurs.

4.1. DateTimeFormatter

Since Java 8 was introduced, the main class for localizing of dates and times is the DateTimeFormatter class. Il fonctionne sur les classes qui implémentent l'interfaceTemporalAccessor, par exemple,LocalDateTime,LocalDate, LocalTime ouZonedDateTime.  Pour créer unDateTimeFormatter, nous devons fournir au moins un modèle, puis Locale.  Voyons un exemple de code:

Locale.setDefault(Locale.US);
LocalDateTime localDateTime = LocalDateTime.of(2018, 1, 1, 10, 15, 50, 500);
String pattern = "dd-MMMM-yyyy HH:mm:ss.SSS";

DateTimeFormatter defaultTimeFormatter = DateTimeFormatter.ofPattern(pattern);
DateTimeFormatter deTimeFormatter = DateTimeFormatter.ofPattern(pattern, Locale.GERMANY);

assertEquals(
  "01-January-2018 10:15:50.000",
  defaultTimeFormatter.format(localDateTime));
assertEquals(
  "01-Januar-2018 10:15:50.000",
  deTimeFormatter.format(localDateTime));

Nous pouvons voir qu'après avoir récupéréDateTimeFormatter, tout ce que nous avons à faire est d'appeler la méthodeformat().

Pour une meilleure compréhension, nous devrions nous familiariser avec les lettres de modèle possibles.

Examinons les lettres par exemple:

Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   y       year-of-era                 year              2004; 04
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978

Toutes les lettres de modèle possibles avec explication peuvent être trouvées danshttps://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html.It’s worth to know that final value depends on the number of symbols. Il y a «MMMM» dans l’exemple qui affiche le nom complet du mois, alors qu’une seule lettre «M» donnerait le numéro du mois sans le 0 initial.

Pour terminer surDateTimeFormatter, voyons comment nous pouvons formaterLocalizedDateTime:

LocalDateTime localDateTime = LocalDateTime.of(2018, 1, 1, 10, 15, 50, 500);
ZoneId losAngelesTimeZone = TimeZone.getTimeZone("America/Los_Angeles").toZoneId();

DateTimeFormatter localizedTimeFormatter = DateTimeFormatter
  .ofLocalizedDateTime(FormatStyle.FULL);
String formattedLocalizedTime = localizedTimeFormatter.format(
  ZonedDateTime.of(localDateTime, losAngelesTimeZone));

assertEquals("Monday, January 1, 2018 10:15:50 AM PST", formattedLocalizedTime);

Afin de formaterLocalizedDateTime, nous pouvons utiliser la méthodeofLocalizedDateTime(FormatStyle dateTimeStyle) et fournir unFormatStyle. prédéfini

Pour un examen plus approfondi de l'API Java 8Date/Time, nous avons un articlehere existant.

4.2. DateFormat etSimpleDateFormatter

Comme il est encore courant de travailler sur des projets qui utilisentDates etCalendars, nous allons brièvement présenter les fonctionnalités de mise en forme des dates et des heures avec les classesDateFormat etSimpleDateFormat.

Analysons les capacités du premier:

GregorianCalendar gregorianCalendar = new GregorianCalendar(2018, 1, 1, 10, 15, 20);
Date date = gregorianCalendar.getTime();

DateFormat ffInstance = DateFormat.getDateTimeInstance(
  DateFormat.FULL, DateFormat.FULL, Locale.ITALY);
DateFormat smInstance = DateFormat.getDateTimeInstance(
  DateFormat.SHORT, DateFormat.MEDIUM, Locale.ITALY);

assertEquals("giovedì 1 febbraio 2018 10.15.20 CET", ffInstance.format(date));
assertEquals("01/02/18 10.15.20", smInstance.format(date));

DateFormat fonctionne avecDates et dispose de trois méthodes utiles:

  • getDateTimeInstance

  • getDateInstance

  • getTimeInstance

Tous prennent des valeurs prédéfinies deDateFormat comme paramètre. Chaque méthode est surchargée, il est donc également possible de passerLocale. Si nous voulons utiliser un modèle personnalisé, comme c'est le cas dansDateTimeFormatter, nous pouvons utiliserSimpleDateFormat. Voyons un court extrait de code:

GregorianCalendar gregorianCalendar = new GregorianCalendar(
  2018, 1, 1, 10, 15, 20);
Date date = gregorianCalendar.getTime();
Locale.setDefault(new Locale("pl", "PL"));

SimpleDateFormat fullMonthDateFormat = new SimpleDateFormat(
  "dd-MMMM-yyyy HH:mm:ss:SSS");
SimpleDateFormat shortMonthsimpleDateFormat = new SimpleDateFormat(
  "dd-MM-yyyy HH:mm:ss:SSS");

assertEquals(
  "01-lutego-2018 10:15:20:000", fullMonthDateFormat.format(date));
assertEquals(
  "01-02-2018 10:15:20:000" , shortMonthsimpleDateFormat.format(date));

5. Personnalisation

En raison de quelques bonnes décisions de conception, nous ne sommes pas liés à un modèle de formatage spécifique aux paramètres régionaux et nous pouvons configurer presque tous les détails pour être pleinement satisfaits d'une sortie.

Pour personnaliser la mise en forme des nombres, nous pouvons utiliserDecimalFormat etDecimalFormatSymbols.

Prenons un petit exemple:

Locale.setDefault(Locale.FRANCE);
BigDecimal number = new BigDecimal(102_300.456d);

DecimalFormat zeroDecimalFormat = new DecimalFormat("000000000.0000");
DecimalFormat dollarDecimalFormat = new DecimalFormat("$###,###.##");

assertEquals(zeroDecimalFormat.format(number), "000102300,4560");
assertEquals(dollarDecimalFormat.format(number), "$102 300,46");

The DecimalFormat documentation affiche tous les caractères de motif possibles. Tout ce que nous devons savoir, c’est que "000000000.000" détermine les zéros de tête ou de fin, "," est un séparateur de mille et "." est un nombre décimal.

Il est également possible d’ajouter un symbole de devise. Nous pouvons voir ci-dessous que le même résultat peut être obtenu en utilisant la classeDateFormatSymbol:

Locale.setDefault(Locale.FRANCE);
BigDecimal number = new BigDecimal(102_300.456d);

DecimalFormatSymbols decimalFormatSymbols = DecimalFormatSymbols.getInstance();
decimalFormatSymbols.setGroupingSeparator('^');
decimalFormatSymbols.setDecimalSeparator('@');
DecimalFormat separatorsDecimalFormat = new DecimalFormat("$###,###.##");
separatorsDecimalFormat.setGroupingSize(4);
separatorsDecimalFormat.setCurrency(Currency.getInstance(Locale.JAPAN));
separatorsDecimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);

assertEquals(separatorsDecimalFormat.format(number), "$10^[email protected]");

Comme nous pouvons le voir, la classeDecimalFormatSymbols nous permet de spécifier n'importe quel formatage de nombre que nous pouvons imaginer.

To customize SimpleDataFormat, we can use DateFormatSymbols.

Voyons à quel point le changement de nom de jour est simple:

Date date = new GregorianCalendar(2018, 1, 1, 10, 15, 20).getTime();
Locale.setDefault(new Locale("pl", "PL"));

DateFormatSymbols dateFormatSymbols = new DateFormatSymbols();
dateFormatSymbols.setWeekdays(new String[]{"A", "B", "C", "D", "E", "F", "G", "H"});
SimpleDateFormat newDaysDateFormat = new SimpleDateFormat(
  "EEEE-MMMM-yyyy HH:mm:ss:SSS", dateFormatSymbols);

assertEquals("F-lutego-2018 10:15:20:000", newDaysDateFormat.format(date));

6. Bundles de ressources

Enfin, la partie cruciale de l'internationalisation dans leJVM est le mécanismeResource Bundle.

Le but d'unResourceBundle est de fournir à une application des messages / descriptions localisés qui peuvent être externalisés dans les fichiers séparés. Nous couvrons l'utilisation et la configuration de l'ensemble de ressources dans l'un de nos articles précédents -guide to the Resource Bundle.

7. Conclusion

Locales and les formateurs qui les utilisent sont des outils qui nous aident à créer une application internationalisée. Ces outils nous permettent de créer une application qui peut s’adapter dynamiquement aux paramètres linguistiques ou culturels de l’utilisateur sans plusieurs builds ou même sans se soucier de savoir si Java prend en charge lesLocale.

Dans un monde où un utilisateur peut être n'importe où et parler n'importe quelle langue, la possibilité d'appliquer ces modifications signifie que nos applications peuvent être plus intuitives et compréhensibles par plus d'utilisateurs dans le monde.

Lorsque vous travaillez avec des applications Spring Boot, nous avons également un article pratique pourSpring Boot Internationalization.

Le code source de ce tutoriel, avec des exemples complets, se trouve àover on GitHub.