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) ; ils sont tous là
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$) ; insensible à la casse
If
Name$ =
"_GROUP_"
LanguageGroups(Group)\GroupEnd =
StringIndex
Group +
1
LanguageGroups(Group)\Name$ =
UCase(String$) ; insensible à la casse
LanguageGroups(Group)\GroupStart =
StringIndex +
1
For
i =
0
To
255
LanguageGroups(Group)\IndexTable[i] =
0
Next
i
ElseIf
Name$ =
"_END_"
Break
; Il n'y a plus de données à lire
Else
StringIndex +
1
LanguageNames(StringIndex) =
Name$ +
Chr(1
) +
String$ ; Garde le nom de l'index et la valeur ensemble pour trier plus facilement
EndIf
ForEver
LanguageGroups(Group)\GroupEnd =
StringIndex ; Configure la fin pour le dernier groupe !
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
)) ; Sépare la valeur de l'index
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 ; Pour un accès plus rapide quand on utilise le même nom de groupe plusieurs fois
Protected
String$, StringIndex, Result
Group$ =
UCase(Group$)
Name$ =
UCase(Name$)
String$ =
"##### String not found! #####"
; Pour aider à trouver les erreurs
If
LanguageGroups(Group)\Name$ <>
Group$ ; Contrôle si c'est le même groupe à chaque fois
For
Group =
1
To
NbLanguageGroups
If
Group$ =
LanguageGroups(Group)\Name$
Break
EndIf
Next
Group
If
Group >
NbLanguageGroups ; Le groupe demandé n'est pas trouvé
Group =
0
; Initialise le numéro de groupe
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
; Mot non trouvé
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() ; charge la langue par défaut
;LoadLanguage("german.prefs") ; décommentez pour charger le fichier de langue allemand
; Récupère quelques mots de la langue
;
Debug
Language("MenuTitle"
, "Edit"
)
Debug
Language("MenuItem"
, "Save"
)
VII. Lien▲
Consultez le code source complet.
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.