Il existe de nombreux alphabets et de nombreuses manières d'écrire les nombres, les dates et les montants de part le monde. Chaque pays, chaque culture dispose en effet de ses propres conventions et de ses propres règles, et ce dans de nombreux domaines. Par exemple, les Anglo-saxons ont pour coutume d'utiliser le point (caractère '.') pour séparer les unités de la virgule lorsqu'ils écrivent des nombres à virgule et d'utiliser une virgule (caractère ',') entre chaque groupe de trois chiffres pour séparer les milliers des millions, les millions des milliards, etc. En France, c'est la virgule est utilisée pour séparer les unités de la partie fractionnaire des nombres à virgule, et le séparateur des milliers est simplement un espace. De même, ils ont l'habitude d'écrire les dates en mettant le mois avant les jours, alors que les Français font l'inverse.
Il va de soi que ce genre de différences rend techniquement très difficile l'internationalisation des programmes. Une solution est tout simplement de dire que les programmes travaillent dans une langue « neutre », ce qui en pratique revient souvent à dire l'anglais puisque c'est la langue historiquement la plus utilisée en informatique. Hélas, si cela convenait parfaitement aux programmeurs, ce ne serait certainement pas le cas des utilisateurs ! Il faut donc, à un moment donné ou à un autre, que les programmes prennent en compte les conventions locales de chaque pays ou de chaque peuple.
Ces conventions sont extrêmement nombreuses et portent sur des domaines aussi divers et variés que la manière d'écrire les nombres, les dates ou de coder les caractères et de classer les mots dans un dictionnaire. En informatique, il est courant d'appeler l'ensemble des conventions d'un pays la locale de ce pays. Les programmes qui prennent en compte la locale sont donc dits localisés et sont capables de s'adapter aux préférences nationales de l'utilisateur.
Note : Le fait d'être localisé ne signifie pas pour autant pour un programme que tous ses messages sont traduits dans la langue de l'utilisateur. La localisation ne prend en compte que les aspects concernant l'écriture des nombres et les alphabets utilisés. Afin de bien faire cette distinction, on dit que les programmes capables de communiquer avec l'utilisateur dans sa langue sont internationalisés. La conversion d'un programme d'un pays à un autre nécessite donc à la fois la localisation de ce programme et son internationalisation.
Si la traduction de tous les messages d'un programme ne peut pas être réalisée automatiquement, il est toutefois possible de prendre en compte les locales relativement facilement. En effet, les fonctionnalités des bibliothèques C et C++, en particulier les fonctionnalités d'entrée / sortie, peuvent généralement être paramétrées par la locale de l'utilisateur. La gestion des locales est donc complètement prise en charge par ces bibliothèques et un même programme peut donc être utilisé sans modification dans divers pays.
Note : En revanche, la traduction des messages ne peut bien évidemment pas être prise en charge par la bibliothèque standard, sauf éventuellement pour les messages d'erreur du système. La réalisation d'un programme international nécessite donc de prendre des mesures particulières pour faciliter la traduction de ces messages. En général, ces mesures consistent à isoler les messages dans des modules spécifiques et à ne pas les utiliser directement dans le code du programme. Ainsi, il suffit simplement de traduire les messages de ces modules pour ajouter le support d'une nouvelle langue à un programme existant. Le code source n'a ainsi pas à être touché, ce qui limite les risques d'erreurs.
La gestion des locales en C++ se fait par l'intermédiaire d'une classe générale,
la classe locale, qui permet de stocker tous les paramètres locaux des pays.
Cette classe est bien entendu utilisée par les flux d'entrée / sortie de la bibliothèque standard,
ce qui fait que vous n'aurez généralement qu'à initialiser cette classe au début de vos programmes
pour leur faire prendre en compte les locales. Cependant, il se peut que vous ayez à manipuler
vous-même des locales ou à définir de nouvelles conventions nationales, surtout si vous écrivez
des surcharges des opérateurs de formatage des flux operator<<
et
operator>>
. Ce chapitre présente donc les notions générales des locales,
les différentes classes mises en oeuvre au sein d'une même locale pour prendre en charge tous
les aspects de la localisation, et la manière de définir ou de redéfinir un de ces aspects afin
de compléter une locale existante.
Comme il l'a été dit plus haut, les locales comprennent différents aspects qui traitent chacun d'une des conventions nationales de la locale. Par exemple, la manière d'écrire les nombres constitue un de ces aspects, tout comme la manière de classer les caractères ou la manière d'écrire les heures et les dates.
Chacun de ces aspects constitue ce que la bibliothèque standard C++ appelle une facette. Les facettes sont gérées par des classes C++, dont les méthodes permettent d'obtenir les informations spécifiques aux données qu'elles manipulent. Certaines facettes fournissent également des fonctions permettant de formater et d'interpréter ces données en tenant compte des conventions de leur locale. Chaque facette est identifiée de manière unique dans le programme, et chaque locale contient une collection de facettes décrivant tous ses aspects.
La bibliothèque standard C++ fournit bien entendu un certain nombre de facettes prédéfinies. Ces facettes sont regroupées en catégories qui permettent de les classer en fonction du type des opérations qu'elles permettent de réaliser. La bibliothèque standard définit six catégories de facettes, auxquelles correspondent les valeurs de six constantes de la classe locale :
la catégorie ctype
, qui regroupe toutes
les facettes permettant de classifier les caractères et de les convertir d'un jeu de caractère
en un autre ;
la catégorie collate
, qui comprend
une unique facette permettant de comparer les chaînes de caractères en tenant compte des caractères de
la locale courante et de la manière de les utiliser dans les classements alphabétiques ;
la catégorie numeric
, qui comprend
toutes les facettes prenant en charge le formatage des nombres ;
la catégorie monetary
, qui comprend
les facettes permettant de déterminer les symboles monétaires et la manière d'écrire les montants ;
la catégorie time
, qui comprend les facettes
capables d'effectuer le formatage et l'écriture des dates et des heures ;
la catégorie message
, qui contient une unique
facette permettant de faciliter l'internationalisation des programmes en traduisant les messages destinés
aux utilisateurs.
Bien entendu, il est possible de définir de nouvelles facettes et de les inclure dans une locale existante. Ces facettes ne seront évidemment pas utilisées par les fonctions de la bibliothèque standard, mais le programme peut les récupérer et les utiliser de la même manière que les facettes standards.
Les mécanismes de définition, d'identification et de récupération des facettes, sont tous pris en charge au niveau de la classe locale. La déclaration de cette classe est réalisée de la manière suivante dans l'en-tête locale :
class locale { public: // Les types de données : // Les catégories de facettes : typedef int category; static const category // Les valeurs de ces constantes { // sont spécifiques à chaque implémentation none = VAL0, ctype = VAL1, collate = VAL2, numeric = VAL3, monetary = VAL4, time = VAL5, messages = VAL6, all = collate | ctype | monetary | numeric | time | messages; }; // La classe de base des facettes : class facet { protected: explicit facet(size_t refs = 0); virtual ~facet(); private: // Ces méthodes sont déclarées mais non définies : facet(const facet &); void operator=(const facet &); }; // La classe d'identification des facettes : class id { public: id(); private: // Ces méthodes sont déclarées mais non définies : void id(const id &); void operator=(const id &); }; // Les constructeurs : locale() throw() explicit locale(const char *nom); locale(const locale &) throw(); locale(const locale &init, const char *nom, category c); locale(const locale &init1, const locale &init2, category c); template <class Facet> locale(const locale &init, Facet *f); template <class Facet> locale(const locale &init1, const locale &init2); // Le destructeur : ~locale() throw(); // Opérateur d'affectation : const locale &operator=(const locale &source) throw(); // Les méthodes de manipulation des locales : basic_string<char> name() const; bool operator==(const locale &) const; bool operator!=(const locale &) const; template <class charT, class Traits, class Allocator> bool operator()( const basic_string<charT, Traits, Allocator> &s1, const basic_string<charT, Traits, Allocator> &s2) const; // Les méthodes de sélection des locales : static locale global(const locale &); static const locale &classic(); };
Comme vous pouvez le constater, outre les constructeurs, destructeur et méthodes générales, la classe locale contient la déclaration de deux sous-classes utilisées pour la définition des facettes : la classe facet et la classe id.
La classe facet est la classe de base de toutes les facettes. Son rôle est essentiellement d'éviter que l'on puisse dupliquer une facette ou en copier une, ce qui est réalisé en déclarant en zone privée le constructeur de copie et l'opérateur d'affectation. Comme ces méthodes ne doivent pas être utilisées, elles ne sont pas définies non plus, seule la déclaration est fournie par la bibliothèque standard. Notez que cela n'est pas dérangeant pour l'utilisation de la classe facet, puisque comme ces méthodes ne sont pas virtuelles et qu'elles ne seront jamais utilisées dans les programmes, l'éditeur de liens ne cherchera pas à les localiser dans les fichiers objets du programme. Ainsi, les instances de facettes ne peuvent être ni copiées, ni dupliquées. En fait, les facettes sont destinées à être utilisées au sein des locales, qui prennent en charge la gestion de leur durée de vie. Toutefois, si l'on désire gérer soi-même la durée de vie d'une facette, il est possible de le signaler lors de la construction de la facette. Le constructeur de base de la classe facet prend en effet un paramètre unique qui indique si la durée de vie de la facette doit être prise en charge par la locale dans laquelle elle se trouve ou si elle devra être explicitement détruite par le programmeur (auquel cas ce paramètre doit être fixé à 1). En général, la valeur par défaut 0 convient dans la majorité des cas et il n'est pas nécessaire de fournir de paramètre au constructeur des facettes.
La classe id quant à elle est utilisée pour définir des identifiants uniques pour chaque classe de facette. Ces identifiants permettent à la bibliothèque standard de distinguer les facettes les unes des autres, à l'aide d'une clef unique dont le format n'est pas spécifié, mais qui est déterminée par la classe id automatiquement lors de la première création de chaque facette. Cet identifiant est en particulier utilisé pour retrouver les facettes dans la collection des facettes gérées par la classe locale.
Pour que ce mécanisme d'enregistrement fonctionne, il faut que chaque classe
de facette définisse une donnée membre statique id
en zone publique,
dont le type est la sous-classe id de la classe locale. Cette donnée membre
étant statique, elle appartient à la classe et non à chaque instance, et permet donc bien d'identifier
chaque classe de facette. Lors du chargement du programme, les variables statiques sont initialisées à 0,
ce qui fait que les facettes disposent toutes d'un identifiant nul. Ceci permet aux méthodes
de la bibliothèque standard de déterminer si un identifiant a déjà été attribué à la classe d'une facette
ou non lorsqu'elle est utilisée pour la première fois. Si c'est le cas, cet identifiant est utilisé tel
quel pour référencer cette classe, sinon, il est généré automatiquement et stocké dans la donnée membre
id
de la classe.
Ainsi, pour définir une facette, il suffit simplement d'écrire une classe
dérivant de la classe locale::facet et contenant une donnée membre statique et publique
id
de type locale::id. Dès lors, cette facette se verra attribuer
automatiquement un identifiant unique, qui permettra d'utiliser les fonctions de recherche de facettes
dans les locales. Ces fonctions utilisent bien entendu la donnée membre id
du type de la facette à rechercher et s'en servent en tant qu'index dans la collection des facettes
de la locale à utiliser. La manière de réaliser ces opérations n'est pas décrite par la norme du C++,
mais le principe est là.
Les fonctions de recherche des facettes sont également déclarées dans l'en-tête locale. Ce sont des fonctions template paramétrées par le type des facettes, qui prennent en paramètre la locale dans laquelle la facette doit être recherchée :
template <class Facet> const Facet &use_facet(const locale &); template <class Facet> bool has_facet(const locale &);
La fonction use_facet
permet de récupérer l'instance
d'une facette dans la locale passée en paramètre. Comme la signature de cette fonction
template ne permet pas de déterminer le type de la facette, et donc l'instance
à utiliser pour l'appel, il est nécessaire de spécifier explicitement ce type entre crochets après
le nom de la fonction. Par exemple, pour récupérer la facette num_put<char>
de la locale classique de la bibliothèque C, on fera l'appel suivant :
Les facettes fournies par la bibliothèque standard sont généralement disponibles
et peuvent être utilisées avec les locales. Les méthodes spécifiques à chacune de ces facettes peuvent
donc être appelées sur la référence de la facette retournée par la fonction use_facet
.
En revanche, les facettes définies par l'utilisateur peuvent ne pas être présentes dans la locale
fournie en paramètre à la fonction use_facet
. Dans ce cas, cette fonction lance
une exception de type bad_cast. Comme il peut être utile en certaines circonstances
de déterminer dynamiquement si une locale contient ou non une facette, la bibliothèque standard met
à disposition la fonction globale has_facet
. Cette fonction s'utilise de la même
manière que la fonction use_facet
, mais au lieu de renvoyer la facette demandée,
elle retourne un booléen indiquant si la locale fournie en paramètre contient ou non cette facette.
Les programmes peuvent créer plusieurs locales afin de prendre en compte
plusieurs jeux de paramètres internationaux s'ils le désirent, mais ils doivent dans ce cas manipuler
ces locales eux-mêmes dans toutes les opérations susceptibles d'utiliser la notion de locale. Par exemple,
ils doivent spécifier la locale à utiliser avant chaque opération d'entrée / sortie en appelant
la méthode imbue
des flux utilisés. Comme cela n'est pas très pratique, la bibliothèque
standard définit une locale globale, qui est la locale utilisée par défaut lorsqu'un programme
ne désire pas spécifier explicitement la locale à utiliser. Cette locale peut être récupérée à
tout moment en créant un simple objet de type locale, en utilisant le constructeur par
défaut de la classe locale. Ce constructeur initialise en effet la locale en cours
de construction avec tous les paramètres de la locale globale. Ainsi, pour récupérer la facette
num_put<char> de la locale globale, on fera l'appel suivant :
use_facet
n'est plus, contrairement à l'exemple précédent, la locale renvoyée par la méthode statique
classic
de la classe locale, mais une copie de la locale globale.
Il est possible de construire une locale spécifique explicitement avec le constructeur de la classe locale qui prend le nom de la locale à utiliser en paramètre. Ce nom peut être l'un des noms standards "C", "", ou toute autre valeur dont la signification n'est pas normalisée. Le nom de locale vide ("") permet de construire une locale dont les paramètres sont initialisés en fonction de l'environnement d'exécution du programme. C'est donc la valeur que l'on utilisera en général, car cela permet de paramétrer le comportement des programmes facilement, sans avoir à les modifier et à les recompiler.
Note : La manière de définir la locale dans l'environnement d'exécution des programmes est spécifique à chaque système d'exploitation et n'est normalisé ni par la norme C++, ni par la norme C. La norme POSIX précise cependant que cela peut être réalisé par l'intermédiaire de variables d'environnement. Par exemple, si la variable d'environnement LANG n'est pas définie, la locale utilisée sera la locale de la bibliothèque C. En revanche, si cette variable d'environnement contient la valeur "fr_FR", la locale utilisée sera celle des francophones de France. Les formats des nombres, des dates, etc. utilisés seront donc ceux qui sont en vigueur en France.
Les autres constructeurs de la classe locale permettent de créer de nouvelles locales en recopiant les facettes d'une locale existante, éventuellement en ajoutant de nouvelles facettes non standards ou en redéfinissant certaines facettes de la locale modèle. Bien entendu, le constructeur de copie recopie toutes les facettes de la locale source dans la locale en cours de construction. Les deux constructeurs suivants permettent de recopier toutes les facettes d'une locale, sauf les facettes identifiées par la catégorie spécifiée en troisième paramètre. Pour cette catégorie, les facettes utilisées sont celles de la locale spécifiée en deuxième paramètre. Il est possible ici d'identifier cette deuxième locale soit par son nom, soit directement par l'intermédiaire d'une référence. Enfin, les deux constructeurs template permettent de créer une locale dont toutes les facettes sont initialisées à partir d'une locale fournie en paramètre, sauf la facette dont le type est utilisé en tant que paramètre template. Pour cette facette, la valeur à utiliser peut être spécifiée directement en deuxième paramètre ou extraite d'une autre locale, elle aussi spécifiée en deuxième paramètre. Nous verrons plus en détail dans la Section 16.3 la manière de procéder pour insérer une nouvelle facette ou remplacer une facette existante.
Enfin, la classe locale dispose de deux méthodes statiques permettant
de manipuler les locales du programme. La méthode classic
que l'on a utilisée
dans l'un des exemples précédents permet d'obtenir la locale représentant les options de la bibliothèque
C standard. Cette locale est la locale utilisée par défaut par les programmes qui ne définissent pas
les locales utilisées. La méthode global
quant à elle permet de spécifier la locale
globale du programme. Cette méthode prend en paramètre un objet de type locale à partir
duquel la locale globale est initialisée. Il est donc courant de faire un appel à cette méthode
dès le début des programmes C++.
Exemple 16-1. Programme C++ prenant en compte la locale de l'environnement
#include <ctime> #include <iostream> #include <locale> using namespace std; int main(void) { // Utilise la locale définie dans l'environnement // d'exécution du programme : locale::global(locale("")); // Affiche la date courante : time_t date; time(&date); struct tm *TL = localtime(&date); use_facet<time_put<char> >(locale()).put( cout, cout, ' ', TL, 'c'); cout << endl; // Affiche la date avec la fonction strftime // de la bibliothèque C : char s[64]; strftime(s, 64, "%c", TL); cout << s << endl; return 0; }
La méthode global
de la classe global appelle
automatiquement la méthode setlocale
si la locale fournie en paramètre a un nom.
Cela signifie que les locales de la bibliothèque standard C++ et celles de la bibliothèque standard C sont
compatibles et utilisent les mêmes conventions de nommage. En particulier, les programmes qui veulent
utiliser la locale définie dans leur environnement d'exécution peuvent utiliser la locale anonyme
"". C'est ce que fait le programme de l'exemple précédent, qui affiche la date
au format de la locale définie par l'utilisateur en passant par les mécanismes du C++ (via la facette
time_put, qui sera décrite en détail dans la Section 16.2.6) et
par la fonction strftime
de la bibliothèque C.
Note : Les fonctions
time
,localtime
etstrftime
sont des fonctions de la bibliothèque C standard. Elles permettent respectivement d'obtenir une valeur de type time_t représentant la date courante, de la convertir en une structure contenant les différentes composantes de la date en temps local, et de formater cette date selon les conventions de la locale courante. Ces fonctions ne seront pas décrites plus en détail ici. Vous pouvez consulter la bibliographie si vous désirez obtenir plus de détails sur la bibliothèque C et les fonctions qu'elle contient.
Précédent | Sommaire | Suivant |
Les flux d'entrée / sortie sur fichiers | Niveau supérieur | Les facettes standards |