I. Présentation▲
L'internationalisation consiste à rendre un site utilisable dans différentes langues et selon différentes cultures. Ceci a toute son importance, car toutes les cultures n'ont pas la même manière de formater les nombres, la justification du texte diffère d'une zone géographique à l'autre. Il existe d'autres paramètres tels que l'unité monétaire.
Il existe toute une série de solutions pour internationaliser un site. On peut effectivement créer différentes pages en différentes langues et, selon le choix de l'utilisateur, rediriger vers ces pages. Une autre solution est d'utiliser une page dont on modifie le texte selon la langue et la région choisie. Ce texte peut provenir d'une base de données, d'un fichier XML ou d'une tout autre source de données.
Microsoft a instauré dans .NET différentes techniques pour effectuer ces opérations. Ce sont ces techniques qui sont décrites dans ce chapitre.
II. Objets nécessaires pour l'implémentation des solutions Microsoft▲
Quelle que soit la solution adoptée, le namespace à utiliser afin de détecter la culture du client est System.Globalization. On retrouvera ainsi dans notre code :
using
System.
Globalization ;
Pour détecter la culture du client, on utilise l'objet Request, instancié implicitement lors de toute requête HTTP et qui permet d'accéder aux entêtes HTTP et d'en récupérer les valeurs. Il contient ainsi diverses informations quant au client et à son navigateur par exemple sa langue ainsi que son pays d'origine. À l'aide de ces informations, il est possible d'instancier un objet CultureInfo. Cet objet a pour but de retenir la langue ainsi que la culture de l'utilisateur.
Ainsi lors d'une requête au serveur, le client envoie une série d'informations telles que celles présentées dans l'exemple suivant. On notera la présence du paramètre Accept-Language. C'est celui-ci qui est utilisé pour détecter la langue du navigateur qui a effectué la requête.
GET /page.aspx HTTP/1.0
User-Agent : Mozilla/4.0
Accept : image/gif, image/jpeg, text/* */*
Accept-Language: fr, en-gb;q=0.8, en;q=0.7
<infos supplémentaires éventuelles - surtout pour POST>
L'instanciation d'un objet CultureInfo est donc à la base de toutes les solutions. Celle-ci s'effectue de la manière suivante :
string
Lang =
Request.
UserLanguages[
0
];
// Principal Language
CultureInfo CurrentCulture =
new
CultureInfo
(
Lang);
III. Solutions possibles pour internationaliser une application web▲
Il existe diverses solutions afin d'internationaliser une application web qui sont :
- détection et redirection
Cette solution utilise différentes pages, chaque page comportant les textes d'une langue particulière. Il peut également y avoir des séparations de page selon les cultures ou tout autre besoin spécifique ; - modification à l'exécution
Il n'existe qu'une version de la page, le texte étant mis à jour dynamiquement, à l'exécution ; - utilisation du Web.Config
En modifiant la valeur de la langue et de la culture directement dans le Web.Config, les textes, dates et autres sont mis en forme selon cette configuration. Ceci implique que tous les utilisateurs auront le même résultat, quelles que soient leurs cultures ; - utilisation de fichiers ressources satellites
Cette solution est semblable à la deuxième solution si ce n'est que les fichiers ressources sont optimisés, c'est pourquoi on l'utilise pour de grandes quantités de textes.
III-A. Détection et redirection▲
La solution la plus simple est la suivante : une page de base détecte la culture à utiliser et redirige vers la page correspondante. Ceci ne doit être utilisé que s’il y a beaucoup de texte à traduire (pour des raisons de performances).
Le désavantage de cette technique est évidemment un nombre accru de pages ainsi qu'un risque d'incohérence entre les pages correspondantes, ce qui peut être très contraignant si cette cohérence a de l'importance.
Par contre, la redirection vers la page correspondante peut se faire vers un site dont la localisation est plus proche du client, diminuant ainsi les temps de réponse.
Il faut également noter qu'un langage peut ne pas être reconnu par .NET. C'est le cas de Kyrgys (cyrillique) par exemple. Il est donc préférable d'utiliser des try/catch pour éviter toute exception non désirée. Dans la gestion de l'exception, on peut simplement spécifier une langue par défaut.
Le code correspondant est le suivant :
string
Lang =
Request.
UserLanguages[
0
];
// Principal Language
CultureInfo CurrentCulture =
new
CultureInfo
(
Lang);
Response.
Redirect
(
"detectAndRedirect"
+
CurrentCulture.
Name.
Substring
(
0
,
2
) +
".aspx"
);
III-B. Modifications à l'exécution▲
La détection de la langue et la réponse se font dans la même page. À l'inverse du point précédent, il est bon d'utiliser cette technique uniquement dans le cas d'un nombre peu élevé de traductions à effectuer.
Les avantages de cette technique sont qu'il n'existe qu'une seule version du code compilé et déployé et qu'il n'y a aucune redirection rendant l'application centralisée. Suivant les besoins, on peut également y trouver d'autres avantages.
Il existe deux types principaux de modifications dynamiques : les traductions et la modification de la langue sur laquelle se baser.
Traductions
À l'aide de la langue préalablement récupérée, il suffit de différencier les cas comme dans l'exemple suivant :
string
Lang =
Request.
UserLanguages[
0
];
// Principal Language
CultureInfo CurrentCulture =
new
CultureInfo
(
Lang);
switch
(
CurrentCulture.
Name.
Substring
(
0
,
2
).
ToUpper
(
))
{
case
"FR"
:
LaText.
Text =
"Texte en français..."
;
break
;
case
"EN"
:
default
:
LaText.
Text =
"Text written in English..."
;
break
;
}
Cependant, cela peut être assez lourd dans le cas où beaucoup d'informations doivent être traduites. Si le nombre d'informations est élevé, il serait préférable d'utiliser les satellites. Cette technique est présentée dans la suite du document.
Modification de la langue de l'utilisateur
Il est possible, de manière dynamique, de mettre à jour la culture à utiliser en utilisant la propriété statique CurrentCulture de la classe Thread.
string
lang =
Request.
UserLanguages
(
0
) ;
Thread.
CurrentThread.
CurrentCulture =
new
CultureInfo
(
lang) ;
Cependant, il est nécessaire de se souvenir la langue d'un appel de page à l'autre. Effectivement, la modification de la culture du thread courant n'est active que pour la durée du thread. À chaque requête étant associé un thread, le thread est arrêté dès l'affichage de la page en question. On utilisera une variable de session à cet effet.
Cette technique sera souvent associée à un choix dans une liste déroulante.
private
void
Page_Load
(
object
sender,
System.
EventArgs e)
{
if
(!
Page.
IsPostBack ||
Session[
"User-Language"
]
==
null
)
{
CultureInfo CurrentCulture =
new
CultureInfo
(
Request.
UserLanguages[
0
]
);
Session[
"User-Language"
]
=
Request.
UserLanguages[
0
];
}
LaText.
Text =
CurrentCulture.
Name;
}
private
void
BuChangeLanguage_Click
(
object
sender,
EventArgs e)
{
Thread.
CurrentThread.
CurrentCulture =
new
CultureInfo
(
DdlLanguage.
SelectedValue);
LaText.
Text =
Thread.
CurrentThread.
CurrentCulture.
Name;
Session[
"User-Language"
]
=
Thread.
CurrentThread.
CurrentCulture.
Name;
}
III-C. Utilisation du Web.Config▲
Lorsque le serveur asp.NET, qui est généralement IIS, mais cela pourrait être Apache ou n'importe quel autre serveur web, doit formater des dates par exemple, il vérifie la configuration de l'application dans le Web.Config. Si il trouve l'information sur la langue et la culture à utiliser, il formate les divers éléments selon cette information.
Ainsi, il s'agit d'utiliser l'attribut culture de l'élément globalization qui se trouve dans ce fichier Web.Config.
<globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="FR-BE">
Dans le cas d'un Web.Config non existant, le serveur se servira de la valeur présente dans le machine.Config, le fichier de configuration générale du serveur. Généralement, la valeur est neutral. Avec cette configuration, le formatage dépendra de la langue installée sur le système hôte du serveur.
III-D. Utilisation de fichiers ressources satellites▲
Les fichiers .dll sont des fichiers que l'on retrouve dans la plupart des applications .NET (il peut être encapsulé dans le fichier .exe). Le but principal de ces fichiers est de contenir le code MSIL (Microsoft Intermediate Language) de l'application, mais ils peuvent également contenir des ressources telles que :
- les traductions de textes ;
- des images ;
- des fichiers textes ;
- tout autre élément nécessaire au bon fonctionnement de l'application.
Cette encapsulation de fichiers permet d'éviter tout risque de suppression ou de modification du fichier.
Bien entendu, plusieurs fichiers .dll satellites (c'est-à-dire qui ne contiennent pas le point d'entrée de l'application) peuvent cohabiter au sein d'une même application, ce qui permet le principe de la réutilisabilité.
Dans le cadre de ce chapitre, les ressources qui nous intéressent sont les traductions. Celles-ci se trouvent dans des fichiers .resources.
Le principe de cette technique est de détecter la culture à utiliser et de charger le satellite correspondant. Cette technique doit être utilisée pour des applications générant le contenu à l'exécution ou qui ont un nombre élevé de composants exécutables.
Les avantages sont semblables à ceux du paragraphe sur la modification à l'exécution si ce n'est que la mise en place est plus aisée.
C'est grâce à la valeur de CurrentUICulture de la classe Thread que .NET est capable de déterminer quel fichier .resources est à utiliser. C'est donc cette propriété qui doit être modifiée si l'on souhaite modifier la langue à utiliser.
Schématiquement, voici le principe :
Pour utiliser cette technique, il est nécessaire que tous les éléments asp.NET de la page .aspx à traduire possèdent les attributs « runat » (dont la valeur est « server ») et « id ». Il en va de même pour les HTMLControls dont le contenu doit être traduit sans quoi il sera impossible au serveur de modifier les valeurs correspondantes.
- Les étapes du développement sont alors :
Création d'un fichier ressource contenant les valeurs par défaut dans le cas où la culture ne serait pas reconnue ou non supportée. Ce fichier a une extension .resx, par exemple developpez.resx. Les valeurs de ce fichier auront la forme :
nomPage.
nomControle =
valeurDeLaLangue
Pour être précis, il suffit que la clé soit unique. Le fait de prendre nomPage.nomControle comme clé est une convention et permet de retrouver plus facilement les valeurs dans les fichiers ressources.
La création de ce fichier ne se fait pas « à la dure », mais bien dans l'environnement de développement. Prenons l'exemple de Visual Studio .NET 2003 :
- Création d'un fichier ressource par culture supportée. Ceci donnera, par exemple, les fichiers developpez.en.resx et developpez.fr.resx.
- Création d'un fichier ressource pour la région si l'on souhaite être plus précis (en France, le « GSM » belge s'appelle « portable » et en Suisse « Natel »), il est possible de créer des fichiers ressources dont les noms seront developpez.fr-FR.resx et developpez.fr-BE.resx.
- Écriture du code permettant le chargement à l'aide de la classe ResourceManager. Celle-ci se trouve dans le namespace Resources. On trouvera donc dans notre code :
using
System.
Resources ;
…
protected
ResourceManager manager =
new
ResourceManager
(
" NomDuNamespace.developpez "
,
typeof
(
Satellite).
Assembly) ;
- « developpez » est le nom du satellite dans cet exemple. Le fait de passer comme argument le nom du namespace permet de pointer le fichier .dll qui contient le fichier ressources.
Cette classe peut avoir d'autres arguments. Pour de plus amples informations, je vous conseille de lire la documentation MSDN :
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemresourcesresourcemanagermemberstopic.asp
Écriture du code permettant de détecter la culture de l'utilisateur afin d'assigner la propriété CurrentCulture de la classe Thread, propriété qui est utilisée pour déterminer le formatage des dates et autres…
Nous ne reviendrons pas dessus, ceci a déjà été présenté dans les paragraphes précédents.
- Écriture du code permettant de récupérer les valeurs des différents satellites afin de les afficher dans la page Web
manager.
GetString
(
" nomPage.nomControle "
) ;
Il est préférable de lire « Best Practices for Developping World-Ready Applications » sur MSDN Online à l'adresse
protected
Label LaText;
protected
ResourceManager manager =
new
ResourceManager
(
"Tests_Globalization.TestsGlobalization"
,
typeof
(
satellite).
Assembly);
private
void
Page_Load
(
object
sender,
System.
EventArgs e)
{
if
(
Session[
"User-Language"
]
!=
null
)
Thread.
CurrentThread.
CurrentUICulture =
new
CultureInfo
(
Session[
"User-Language"
].
ToString
(
));
LaText.
Text =
manager.
GetString
(
"satellite2.LaText"
);
}
III-E. Inconvient du système▲
Les fichiers ressources créés se trouvent dans les fichiers dll comme nous l'avons vu. Il est donc nécessaire de recompiler l'application, ou tout du moins une partie de celle-ci, lorsque l'on souhaite modifier le contenu de ces fichiers.
En fait, lorsque l'on récupère des informations dans ces fichiers ressources, .NET récupère au préalable le fichier correspondant et qui est encapsulé dans la dll.
Pour éviter cette recompilation, il suffit donc de spécifier au manager qu'il doit utiliser un fichier .resources qui n'est pas inclus dans une dll. Cela se fait par :
ResourceManager manager =
ResourceManager.
CreateFileBasedResourceManager
(
value
,
path,
null
);
Il est possible de générer dynamiquement ce fichier, ce qui peut avoir diverses utilités comme nous le verrons dans le paragraphe suivant.
Cette génération se fait à l'aide des lignes de code suivantes :
FileStream fs =
new
FileStream
(
" items.resources "
,
FileMode.
OpenOrCreate,
FileAccess.
Write);
IResourceWriter writer =
new
ResourceWriter
(
fs);
writer.
AddResource
(
" clé "
,
" valeur "
);
writer.
Generate
(
);
writer.
Close
(
);
Pour pouvoir utiliser les différentes classes présentes dans le code précédent, il est nécessaire d'utiliser
using
System.
IO ;
using
System.
Resources ;
Il est également possible de générer des fichiers .resources à partir de fichiers texte ou de fichiers XML. Pour cela, je vous renvoie vers MSDN :