Application multilingue
Date de publication : 02 juillet 2008
Par
Freak
Comtois
Cet article présente la méthode utilisée pour gérer les langues dans l'EDI de PureBasic. Il s'agit pour l'essentiel d'une traduction d'un article écrit par Freak l'auteur de l'EDI, que j'ai complétée en détaillant un peu plus certains passages.
I. Introduction
II. Exemples fichiers
III. DataSection
IV. Procédure LoadLanguage
IV-1. Comptage
IV-2. Chargement de la langue par défaut
IV-3. Tri et indexation groupe
IV-4. Chargement langue externe
V. Procédure Language
V-1. Contrôle Groupe
V-2. Recherche mot
VI. Exemple d'utilisation
VII. Lien
VIII. Remerciements
I. Introduction
Cet article présente la méthode utilisée dans l'EDI de PureBasic pour gérer les langues.
L'EDI utilise un peu plus de code pour mémoriser d'autres informations contenues dans les fichiers (traducteurs, etc),
mais le code de base est identique.
L'ancienne méthode utilisée était un simple tableau de chaînes de caractères appelées par leur index.
Il s'agit bien sûr d'une bonne solution pour un petit programme, mais cela est devenu un problème à mesure que le projet grandissait.
Le tableau des index n'est pas descriptif de ce qu'il contient, et il est donc difficile de dire quelle chaîne de caractères
sera insérée dans le code. En fait, c'est aussi un cauchemar pour ajouter de nouvelles chaînes, en particulier avec la mise à jour de fichiers externes de traduction.
Le code que vous voyez ici (celui qui est en service depuis 3,94) a prouvé que la gestion des langues est très simple et il permet de résoudre les problèmes mentionnés ci-dessus.
Le code se compose de deux procédures :
- LoadLanguage() pour charger une langue.
- Language() pour utiliser cette langue dans le programme.
Bénéfice de cette solution :
- Les chaînes de caractères sont identifiées par un groupe et un nom clé, ce qui permet de mieux les organiser avec leurs propres descriptions et de développer plus facilement.
- Les chaînes de caractères sont indexées et triées lors de leur chargement, ce qui permet un accès rapide malgré qu'elles soient appelées par leur nom.
- Une langue par défaut est définie dans le code (DataSection), ce qui permet d'avoir toujours un texte par défaut dans le cas ou un fichier externe est manquant ou périmé.
- La liste de mots est facile à élargir. Il suffit d'ajouter une nouvelle entrée dans la DataSection ainsi que le fichier de langue et d'utiliser le nouveau groupe et nom clé.
- Les fichiers de langues additionnels sont dans le format "PB Preference" qui permet de les maintenir facilement.
Utilisation :
- Définir le langage par défaut dans la DataSection comme montré plus bas
- Utiliser LoadLanguage() au départ pour charger le langage par défaut ou le fichier externe.
- Utiliser Language(Group$, Name$) pour accéder aux mots des langages.
II. Exemples fichiers
Exemples de fichiers langue issus directement de l'EDI de PureBasic.
Le nom d'un groupe se trouve entre crochets, par exemple [Compiler]. Le nom clé est identique quelque soit le fichier langue, c'est la description de ce mot qui sera traduite. et enfin c'est ce mot clé que vous utiliserez dans votre programme. Considérez le comme un mnémonique sur le texte que vous souhaitez afficher.
| Anglais |
Français |
[MenuTitle]
File = &File
Edit = &Edit
Compiler = &Compiler
Debugger = &Debugger
Tools = &Tools
Help = &Help |
[MenuTitle]
File = &Fichier
Edit = &Edition
Compiler = &Compilateur
Debugger = &Débogueur
Tools = &Outils
Help = &Aide |
[Compiler]
OptionsTitle = Compiler Options
MainFile = Main source file
UseIcon = Use Icon
EnableDebugger = Enable Debugger
EnableASM = Enable inline ASM support
EnableXP = Enable XP skin support |
[Compiler]
OptionsTitle = Options du compilateur
MainFile = Fichier principal
UseIcon = Utiliser une icône
EnableDebugger = Activer le débogueur
EnableASM = Activer l'assembleur en ligne
EnableXP = Activer le support des thèmes XP |
III. DataSection
C'est ici que la langue par défaut est définie. C'est une liste de Groupe,
suivi d'une liste de noms, par exemple dans le groupe [MenuItem], on trouve le nom 'Open', avec une description plus complète à droite, c'est cette description qu'il est possible de traduire dans un fichier et qui sera affichée dans votre programme
Avec quelques mot-clés spéciaux pour le groupe :
- "_GROUP_" indique un nouveau groupe dans la DataSection, la seconde valeur est le nom du groupe.
- "_END_" indique la fin de la liste de la langue (Il n'y a pas de nombre de mots prédéfinis, le comptage est effectué dans la procédure LoadLanguage()).
 |
Les index de mot sont insensibles à la casse pour se faciliter la vie.
|
DataSection
Language:
Data$ "_GROUP_", "MenuTitle"
Data$ "File", "File"
Data$ "Edit", "Edit"
Data$ "_GROUP_", "MenuItem"
Data$ "New", "New"
Data$ "Open", "Open..."
Data$ "Save", "Save"
Data$ "_END_", ""
EndDataSection
|
IV. Procédure LoadLanguage
Cette procédure charge le langage par défaut ou une langue à partir d'un fichier.
Elle doit être appelée au moins une fois avant d'utiliser n'importe quel mot.
Cette procédure permet de :
- Charger et trier le langage par défaut inclus dans le programme.
- Charger n'importe quel langage à partir d'un fichier.
Cette méthode vous permet d'obtenir un mot d'une langue, même si le fichier n'est pas
trouvé, ou qu'un mot référencé par un index est introuvable dans le fichier.
Vous récupérerez la langue par défaut en utilisant la procédure Language().
Cette procédure peut être appelée plusieurs fois pour changer la langue utilisée durant l'exécution de votre programme.
IV-1. Comptage
Dans un premier temps, la procédure effectue un comptage du nombre de groupes, et du nombre de chaînes (Chaque ligne est composée d'un nom clé (mnémonique) et d'une description) contenus dans la langue par défaut incluse dans la
DataSection.
NbLanguageGroups = 0
NbLanguageStrings = 0
Restore Language
Repeat
Read Name$
Read String$
Name$ = UCase(Name$)
If Name$ = "_GROUP_"
NbLanguageGroups + 1
ElseIf Name$ = "_END_"
Break
Else
NbLanguageStrings + 1
EndIf
ForEver
|
Une fois le comptage terminé, les tableaux sont déclarés.
Global Dim LanguageGroups.LanguageGroup(NbLanguageGroups)
Global Dim LanguageStrings.s(NbLanguageStrings)
Global Dim LanguageNames.s(NbLanguageStrings)
|
IV-2. Chargement de la langue par défaut
La procédure charge la langue standard (par défaut) se trouvant dans la
DataSection.
Group = 0
StringIndex = 0
Restore Language
Repeat
Read Name$
Read String$
Name$ = UCase(Name$)
If Name$ = "_GROUP_"
LanguageGroups(Group)\GroupEnd = StringIndex
Group + 1
LanguageGroups(Group)\Name$ = UCase(String$)
LanguageGroups(Group)\GroupStart = StringIndex + 1
For i = 0 To 255
LanguageGroups(Group)\IndexTable[i] = 0
Next i
ElseIf Name$ = "_END_"
Break
Else
StringIndex + 1
LanguageNames(StringIndex) = Name$ + Chr(1) + String$
EndIf
ForEver
LanguageGroups(Group)\GroupEnd = StringIndex
|
IV-3. Tri et indexation groupe
La procédure effectue l'indexation et un tri pour chaque groupe.
La bibliothèque
String est grandement utilisée ici.
SortArray() permet de trier le tableau pour chaque groupe, selon les valeurs début et fin d'index du groupe en cours de traitement.
Pour accélérer les recherches dans la procédure
Language(), chaque groupe possède une table permettant de retrouver un index rapidement en fonction de la première lettre du nom clé,
LanguageGroups(Group)\IndexTable[char] = StringIndex.
For Group = 1 To NbLanguageGroups
If LanguageGroups(Group)\GroupStart <= LanguageGroups(Group)\GroupEnd
SortArray(LanguageNames(), 0, LanguageGroups(Group)\GroupStart, LanguageGroups(Group)\GroupEnd)
char = 0
For StringIndex = LanguageGroups(Group)\GroupStart To LanguageGroups(Group)\GroupEnd
LanguageStrings(StringIndex) = StringField(LanguageNames(StringIndex), 2, Chr(1))
LanguageNames(StringIndex) = StringField(LanguageNames(StringIndex), 1, Chr(1))
If Asc(Left(LanguageNames(StringIndex), 1)) <> char
char = Asc(Left(LanguageNames(StringIndex), 1))
LanguageGroups(Group)\IndexTable[char] = StringIndex
EndIf
Next StringIndex
EndIf
Next Group
|
IV-4. Chargement langue externe
La procédure tente de charger un fichier de langue externe.
La bibliothèque
Preference rend de grands services,
notamment ici avec la fonction
ReadPreferenceString()
qui permet de gérer simplement les valeurs par défaut. En effet, LanguageStrings(StringIndex) contiendra toujours une valeur valide, qui sera issue soit du fichier demandé, soit de la langue par défaut en DataSection.
If FileName$ <> ""
If OpenPreferences(FileName$)
For Group = 1 To NbLanguageGroups
If LanguageGroups(Group)\GroupStart <= LanguageGroups(Group)\GroupEnd
PreferenceGroup(LanguageGroups(Group)\Name$)
For StringIndex = LanguageGroups(Group)\GroupStart To LanguageGroups(Group)\GroupEnd
LanguageStrings(StringIndex) = ReadPreferenceString(LanguageNames(StringIndex), LanguageStrings(StringIndex))
Next StringIndex
EndIf
Next Group
ClosePreferences()
ProcedureReturn #True
EndIf
EndIf
|
V. Procédure Language
Cette procédure retourne un mot pour la langue en cours d'utilisation.
Chaque mot est identifié par un groupe et un nom clé(les deux sont insensibles à la casse).
Si le nom n'est pas trouvé (ou non inclus dans la langue par défaut) le
retour sera "##### String not found! #####". Cela aide à trouver les erreurs dans le code de la langue facilement.
V-1. Contrôle Groupe
Dans un premier temps la procédure vérifie que le groupe demandé existe.
S'il existe son numéro est mémorisé dans la variable 'Group' qui est statique afin de conserver sa valeur au prochain appel de la procédure.
Static Group.l
Protected String$, StringIndex, Result
Group$ = UCase(Group$)
Name$ = UCase(Name$)
String$ = "##### String not found! #####"
If LanguageGroups(Group)\Name$ <> Group$
For Group = 1 To NbLanguageGroups
If Group$ = LanguageGroups(Group)\Name$
Break
EndIf
Next Group
If Group > NbLanguageGroups
Group = 0
EndIf
EndIf
|
V-2. Recherche mot
Si le groupe demandé existe, la recherche de la chaîne correspondant au nom clé peut commencer.
Pour réduire la recherche, les noms clés sont
triés, ce qui fait que la boucle de recherche débute au premier index correspondant à la première lettre du nom recherché.
If Group <> 0
StringIndex = LanguageGroups(Group)\IndexTable[ Asc(Left(Name$, 1)) ]
If StringIndex <> 0
Repeat
Result = CompareMemoryString(@Name$, @LanguageNames(StringIndex))
If Result = #PB_String_Equal
String$ = LanguageStrings(StringIndex)
Break
ElseIf Result = #PB_String_Lower
Break
EndIf
StringIndex + 1
Until StringIndex > LanguageGroups(Group)\GroupEnd
EndIf
EndIf
ProcedureReturn String$
|
VI. Exemple d'utilisation
Vous trouverez le détail du fichier
german.prefs dans la page
source.
LoadLanguage()
Debug Language("MenuTitle", "Edit")
Debug Language("MenuItem", "Save")
|
VII. Lien
VIII. Remerciements
Je tiens à remercier tout particulièrement
Timo 'fr34k' Harter pour m'avoir autorisé à utiliser tout ses articles et notamment celui ci,
CORBASE pour sa participation à la traduction de cet article, et
gorgonite pour la relecture de l'article, sans oublier l'équipe PureBasic qui travaille sans relâche pour améliorer ce fabuleux langage.

