Chapitre 15. Les locales

Table des matières
15.1. Notions de base et principe de fonctionnement des facettes
15.2. Les facettes standards
15.3. Personnalisation des mécanismes de localisation

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 chiffre 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 fracionnaire 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 que, à un moment donné ou à un autre, 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 que 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 qui les utilisent.

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 librairies C et C++, et 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 librairies, 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 pris en charge par les librairies 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, et 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 toutes les paramètres locaux des pays. Cette classe est bien entendu utilisée par les flux d'entrée / sortie de la librairie 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 ayiez à manipuler vous-même des locales ou à en 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.

15.1. Notions de base et principe de fonctionnement des facettes

Comme il l'a été dit plus haut, les locales comprennent différents aspects qui traitent chacun d'une des conventions nationale 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 librairie 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 librairie 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 données qu'elles permettent de manipuler. La librairie standard définit six catégories de facettes, auxquelles correspondent les valeurs de six constantes de la classe locale :

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 librairie 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
        none     = VAL0,    // sont spécifiques à chaque implémentation
        ctype    = VAL1, collate  = VAL2,
        numeric  = VAL3, monetary = VAL4,
        time     = VAL5, messages = VAL6,
        all = collate | ctype | monetary | numeric | time  | messages;

    // La classe 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 et destructeur :
    locale() throw()
    locale(const locale &) throw()
    explicit locale(const char *nom);
    locale(const locale &init, const char *nom, category c);

    template <class Facet>
    locale(const locale &init, Facet *f);

    template <class Facet>
    locale(const locale &init1, const locale &init2);

    locale(const locale &init1, const locale &init2, category c);
    ~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 librairie 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 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és, 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 sa valeur est fixée à 1, 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étruites par le programmeur. 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 quand à elle est utilisée pour définir des identifiants uniques pour chaque classe de facette. Ces identifiant permettent à la librairie 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ée 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, et 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 librairie standard de déterminer si un identifiant a déjà été attribué à la classe d'une facette ou non. 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 celui-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, et 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 librairie C, on fera l'appel suivant :

use_facet<num_put<char> >(locale::classic());

Si la facette demandée n'existe pas ou n'est pas définie dans la locale spécifiée en paramètre, la fonction use_facet 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 librairie 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.