Internationalisation

Dans une architecture où un programme exécuté sur un serveur fournit ses données à plusieurs clients, les conventions locales du serveur ne sont pas nécessairement les mêmes que celles des clients. Les conventions peuvent différer par la langue, mais aussi par la façon d’écrire les valeurs numériques (même entre deux pays parlant la même langue) ainsi que par le fuseau horaire. Pour produire des messages conformes aux conventions du client, SIS emploie deux approches qui diffèrent par leur niveau de granularité: au niveau des messages eux-mêmes, ou au niveau des objets produisant les messages. L’approche utilisée détermine aussi s’il est possible de partager une même instance d’un objet pour toutes les langues.

Chaînes de caractères distinctes pour chaque conventions locales

Certaines classes ne sont conçues que pour fonctionner selon une convention locale à la fois. C’est évidemment le cas des implémentations standards de java.text.Format, puisqu’elles sont entièrement dédiées au travail d’internationalisation. Mais c’est aussi le cas de d’autres classes moins évidentes comme javax.imageio.ImageReader et ImageWriter. Lorsque une de ces classes est implémentée par SIS, nous l’identifions en implémentant l’interface org.apache.sis.util.Localized. La méthode getLocale() de cette interface permet alors de déterminer selon quelles conventions locales l’instance produira ses messages.

Une autre classe qui fournit différentes méthodes pour différentes langues est java.lang.Throwable. L’API standard du Java définie deux méthodes pour obtenir un message d’erreur: getMessage() et getLocalizedMessage(). Habituellement, ces deux méthodes retournent la même chaîne de caractères. Toutefois certaines exceptions lancées par Apache SIS peuvent utiliser différentes langues. La politique que SIS tente d’appliquer autant que possible est:

Exemple: Si une erreur s’est produite alors qu’un client japonais s’est connecté à un serveur européen, le message fournit par getLocalizedMessage() pourra être envoyé à l’utilisateur au Japon alors que le message fournit par getMessage() pourra être enregistré dans le journal des événements du serveur. Ainsi, l’administrateur système pourra plus facilement analyser l’erreur même s’il ne connaît pas la langue du client.

La classe utilitaire org.apache.sis.util.Exceptions fournit des méthodes de commodité pour obtenir des messages selon des conventions locales spécifiées lorsque cette information est disponible.

Instance unique pour toutes les conventions locales

Les API définit par SIS ou hérités de GeoAPI privilégient plutôt l’utilisation du type InternationalString là où une valeur de type String serait susceptible d’être localisée. Cette approche permet de différer le processus d’internationalisation au moment d’obtenir une chaîne de caractères plutôt qu’au moment de construire l’objet qui les contient. C’est particulièrement utile pour les classes immuables qui serviront à créer des instances uniques indépendamment des conventions locales.

Exemple: Il existe dans SIS une seule instance de type OperationMethod représentant la projection de Mercator, quelle que soit la langue du client. Mais sa méthode getName() fournit (indirectement) une instance de InternationalString telle que toString(Locale.ENGLISH) retourne Mercator Projection alors que toString(Locale.FRENCH) retourne Projection de Mercator.

En définissant des objets spatiaux indépendemment des conventions locales, on réduit les risques de sur-coûts de calculs. Par exemple il est plus facile de détecter que deux cartes emploient la même projection cartographique si cette dernière est représentée par la même instance de CoordinateOperation, même si la projection porte différents noms selon les pays. En outre, certain types de CoordinateOperation peuvent nécessiter des grilles de transformation de coordonnées, ce qui accroît l’intérêt de partager une instance unique pour des raisons d’économie de mémoire.

Convention Locale.ROOT

Toutes les méthodes SIS recevant ou retournant une valeur de type Locale acceptent la valeur Locale.ROOT. Cette valeur est interprétée comme signifiant de ne pas localiser le texte. La notion de texte non-localisé est un peu fausse, puisqu’il faut bien choisir une convention de format. Mais cette convention, bien que très proche de l’anglais, sera généralement légèrement différente. Par exemple:

Traitement des caractères

Les chaînes de caractères en Java utilisent l’encodage UTF-16. Il existe une correspondance directe entre les valeurs de type char et la très grande majorité des caractères, ce qui facilite l’utilisation des chaînes lorsque ces caractères suffisent. Mais certains caractères Unicode ne sont pas représentables par un seul char. Ces caractères supplémentaires comprennent certains idéogrammes, mais aussi des symboles routiers et géographiques dans la plage 1F680 à 1F700. Le support de ces caractères supplémentaires nécessite des itérations un peu plus complexes que le cas classique où l’on supposait une correspondance directe. Ainsi, au lieu de la boucle de gauche ci-dessous, les applications internationales devraient généralement utiliser la boucle de droite:

Boucle à éviter
for (int i=0; i<string.length(); i++) {
    char c = string.charAt(i);
    if (Character.isWhitespace(c)) {
        // Un espace blanc a été trouvé.
    }
}
Boucle recommandée
for (int i=0; i<string.length();) {
    int c = string.codePointAt(i);
    if (Character.isWhitespace(c)) {
        // Un espace blanc a été trouvé.
    }
    i += Character.charCount(c);
}
Exemples de caractères supplémentaires
(l’affichage dépend des capacités du navigateur)

🚉 🚥 🚧 🚫 🚯 🚸 🚺 🚹 🛄 🚭

SIS supporte les caractères supplémentaires en utilisant la boucle de droite lorsque nécessaire. Mais la boucle de gauche reste occasionnellement utilisée lorsqu’il est connu que les caractères recherchés ne sont pas des caractères supplémentaires, même si la chaîne dans laquelle on fait la recherche peut en contenir.

Interprétation des espaces blancs

Le Java standard fournit deux méthodes pour déterminer si un caractères est un espace blanc: Character.isWhitespace(…) et Character.isSpaceChar(…). Ces deux méthodes diffèrent dans leurs interprétations des espaces insécables, des tabulations et des retours à la ligne. La première méthode est conforme à l’interprétation couramment utilisée dans des langages telles que le Java, C/C++ et XML, qui considère les tabulations et retours à la ligne comme des espaces blancs, alors que les espaces insécables sont interprétés comme des caractères non-blanc. La seconde méthode — strictement conforme à la définition Unicode — fait l’interprétation inverse.

SIS emploie ces deux méthodes dans des contextes différents. isWhitespace(…) est utilisée pour séparer les éléments d’une liste (nombres, dates, mots, etc.), tandis que isSpaceChar(…) est utilisée pour ignorer les espaces blancs à l’intérieur d’un seul élément.

Exemple: Supposons une liste de nombres représentés selon les conventions françaises. Chaque nombre peut contenir des espace insécables comme séparateurs des milliers, tandis que les différents nombres de la liste peuvent être séparés par des espaces ordinaires, des tabulations ou des retours à la ligne. Pendant l’analyse d’un nombre, on veut considérer les espaces insécables comme faisant partie du nombre, alors qu’une tabulation ou un retour à la ligne indique très probablement une séparation entre ce nombre et le nombre suivant. On utilisera donc isSpaceChar(…). Inversement, lors de la séparation des nombres de la liste, on veut considérer les tabulations et les retours à la ligne comme des séparateurs mais pas les espaces insécables. On utilisera donc isWhitespace(…). Le rôle des espaces ordinaires, qui pourraient s’appliquer aux deux cas, doit être décidé en amont.

Dans la pratique, cette distinction se traduit pas une utilisation de isSpaceChar(…) dans les implémentations de java.text.Format, et une utilisation de isWhitespace(…) dans pratiquement tout le reste de la bibliothèque SIS.