Internationalisation et localisation

Cela est resté finalement assez discret jusqu’ici, mais en y regardant de plus près, les programmes que nous avons réalisés sont en fait destinés à un environnement anglophone. En effet, prenez par exemple les entrées : si nous souhaitons fournir un nombre flottant à notre programme, nous devons utiliser le point comme séparateur entre la partie entière et décimale. Or, dans certains pays, on pourrait vouloir utiliser la virgule à la place. Cela nous paraît moins étrange étant donné que les constantes flottantes sont écrites de cette manière en C, mais il n’en va pas de même pour nos utilisateurs.

Ce qu’il faudrait finalement, c’est que nos programmes puissent s’adapter aux usages, coutumes et langues de notre utilisateur et c’est que nous allons voir (partiellement) dans ce chapitre. :)

Définitions

Avant toute chose, il nous est nécessaire de définir deux concepts afin de bien cerner de quoi nous allons parler.

L’internationalisation

L'internationalisation (parfois abrégée « i18n ») est un procédé par lequel un programme est rendu capable de s’adapter aux préférences linguistiques et régionales d’un utilisateur.

La localisation

La localisation (parfois abrégée « l10n ») est une opération par laquelle un programme internationalisé se voit fournir les informations nécessaires pour s’adapter aux préférences linguistiques et régionales d’un utilisateur.

La fonction setlocale

De manière générale, les programmes que nous avons conçus jusqu’ici étaient déjà partiellement internationalisés, car la bibliothèque standard du langage C l’est dans une certaine mesure. Toutefois, nous n’avons jamais recouru à un processus de localisation pour que ceux-ci s’adaptent à nos usages. Nous vous le donnons en mille : la localisation en C s’effectue à l’aide de… la fonction setlocale().

char *setlocale(int categorie, char *localisation);

Cette fonction attends deux arguments : une catégorie et la localisation qui doit être employée pour cette catégorie. Elle retourne la localisation demandée si elle a pu être appliquée, un pointeur nul sinon.

Les catégories

La bibliothèque standard du C divise la localisation en plusieurs catégories, plus précisément cinq :

  1. La catégorie LC_COLLATE qui modifie le comportement des fonctions strcoll() et strxfrm() ;
  2. La catégorie LC_CTYPE qui adapte le comportement des fonctions de traduction entre chaîne de caractères et chaînes de caractères larges (nous y viendrons bientôt), ainsi que les fonctions de l’en-tête <ctype.h> ;
  3. La catégorie LC_MONETARY qui influence le comportement de la fonction localeconv() ;
  4. La catégorie LC_NUMERIC qui altère le comportement des fonctions *printf() et *scanf() ainsi que des fonctions de conversions de chaînes de caractères en ce qui concerne les nombres flottants ;
  5. La catégorie LC_TIME qui change le comportement de la fonction strftime().

Enfin, la catégorie LC_ALL (qui n’en est pas vraiment une) représente toutes les catégories en même temps.

Nous ne nous attarderons que sur les catégories LC_NUMERIC et LC_TIME dans la suite de ce chapitre.

Les localisations

La bibliothèque standard prévoit deux localisations possibles :

  1. La localisation "C" qui correspond à celle par défaut. Celle-ci utilise les usages anglophones ;
  2. La localisation "" (une chaîne vide) qui correspond à celle utilisée par votre système.

Il est également possible de fournir un pointeur nul comme localisation, auquel cas la localisation actuelle est retournée.

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
	char *s;

	s = setlocale(LC_ALL, NULL);
	puts(s);

	if (setlocale(LC_ALL, "") == NULL)
	{
		perror("setlocale");
		return EXIT_FAILURE;
	}

	s = setlocale(LC_ALL, NULL);
	puts(s);
	return 0;
}
Résultat
C
fr_BE.UTF-8

Comme vous le voyez, la localisation de départ est bien C.

La forme que prend la localisation dépend de votre système. Sous unixoïdes et dans notre exemple, elle prend la forme de la langue en minuscule (au format ISO 639) suivie d’un tiret bas et du pays en majuscule (au format ISO 3166–1) et, éventuellement, terminée par un point et par l’encodage utilisé.

La catégorie LC_NUMERIC

La catégorie LC_NUMERIC permet de modifier le comportement des fonctions scanf() et printf() afin qu’elles adaptent leur gestion et leur affichage des nombres flottants.

La catégorie LC_NUMERIC affecte également les fonctions atoi(), atol(), atoll(), strtol(), strtoll(), strtoul(), strtoull(), strtoimax(), strtoumax(), atof(), strtof(), strtod() et strtold() qui forment une suite de fonctions dédiées à la conversion de chaînes de caractères vers des nombres. Toutefois, nous ne nous étendrons pas sur ces dernières.

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
	double f;

	if (setlocale(LC_NUMERIC, "") == NULL)
	{
		perror("setlocale");
		return EXIT_FAILURE;
	}

	printf("Veuillez entrer un nombre flottant : ");

	if (scanf("%lf", &f) != 1)
	{
		perror("scanf");
		return EXIT_FAILURE;
	}

	printf("Vous avez entré : %f.\n", f);
	return 0;
}
Résultat (localisation francophone)
Veuillez entrer un nombre flottant : 45,5
Vous avez entré : 45,500000.

Veuillez entrer un nombre flottant : 45.5
Vous avez entré : 45,000000.

Comme vous le voyez, avec une locale francophone et après l’appel à setlocale(), seule la virgule est considérée comme séparateur de la partie entière et de la partie décimale.

La catégorie LC_TIME

La catégorie LC_TIME modifie le comportement de la fonction strftime().

size_t strftime(char *chaine, size_t taille, char *format, struct tm *date);

Cette fonction, déclarée dans l’en-tête <time.h>, écrit dans la chaîne de caractères chaine différents éléments décrivant la date date en suivant la chaîne de format format (à l’image de la fonction snprintf()). S’il y a assez de place dans la chaîne chaine pour écrire l’entièreté des données, la fonction retourne le nombre de caractères écrits, sinon elle retourne zéro.

Structure tm

La date doit être sous la forme d’une structure tm (déclarée dans l’en-tête <time.h>), structure qui peut être obtenue via la fonction localtime().

struct tm *localtime(time_t *date);

Cette fonction attend simplement l’adresse d’un objet de type time_t et retourne l’adresse d’une structure tm.

La fonction strftime() supporte un grand nombre d’indicateurs de conversion pour construire la chaîne de format. En voici une liste non exhaustive.

Indicateur de conversion Signification
A Nom du jour de la semaine
B Nom du mois de l’année
d Jour du mois
Y Année sur quatre chiffres
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


int
main(void)
{
	char buf[255];
	time_t t;

	if (setlocale(LC_TIME, "") == NULL)
	{
		perror("setlocale");
		return EXIT_FAILURE;
	}
	if (time(&t) == (time_t)-1)
	{
		perror("time");
		return EXIT_FAILURE;
	}

	struct tm *tm = localtime(&t);

	if (tm == NULL)
	{
		perror("localtime");
		return EXIT_FAILURE;
	}
	if (!strftime(buf, sizeof buf, "%A %d %B %Y", tm))
	{
		perror("strftime");
		return EXIT_FAILURE;
	}

	printf("%s\n", buf);
	return 0;
}
Résultat
Wednesday 07 November 2018
mercredi 07 novembre 2018

À nouveau, comme vous pouvez le voir, suivant la locale utilisée la chaîne produite n’est pas la même.


En résumé
  1. L’internationalisation permet de rendre un programme sensible aux préférences régionales d’un utilisateur ;
  2. La localisation permet à un programme d’appliquer les préférences régionales d’un utilisateur ;
  3. La fonction setlocale() permet de modifier la localisation de certaines fonctions de la bibliothèque standard ;
  4. La localisation par défaut (notée C) correspond aux usages anglophones.