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.
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:
getMessage()
retourne le message dans la langue par défaut de la JVM.
Dans une architecture client-serveur, c’est souvent la langue du système hébergeant le serveur.
C’est la méthode recommandée pour enregistrer des messages dans le journal des événements,
à l’intention des administrateurs systèmes.getLocalizedMessage()
retourne le message dans une langue qui dépend du contexte dans lequel l’exception s’est produite.
C’est souvent la langue qui a été configurée pour une instance particulière de Format
ou DataStore
,
que l’on peut présumer être la langue du client se connectant au serveur.
C’est la méthode recommandée pour afficher un message dans une fenêtre sur le poste de l’utilisateur.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.
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.
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:
toString()
plutôt qu’à l’aide d’un java.text.NumberFormat
.
Il en résulte des différences dans le nombre de chiffres significatifs, l’utilisation de la notation exponentielle et l’absence de séparateur des milliers.
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:
for (int i=0; i<string.length(); i++) {
char c = string.charAt(i);
if (Character.isWhitespace(c)) {
// Un espace blanc a été trouvé.
}
}
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);
}
🚉 🚥 🚧 🚫 🚯 🚸 🚺 🚹 🛄 🚭
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.
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.