Jusqu'à il y a peu, je ne me suis jamais soucié de traduire mes applications. En effet, la société dans laquelle je travaille vise essentiellement à faire des logiciels pour le secteur Français. Du coup, pas besoin de gérer la traduction.

Et un jour, j'ai développé EverLink qui est un outil pour Evernote®. Le marché français étant très peu demandeur envers Evernote, je me suis mis en tête de le traduire en langue anglaise. Bon, il s'avère que le logiciel est un flop et personne n'en a jamais fait la demande (je devrais peut-être aussi traduire mon blog pour attirer des anglophones) mais il m'a permis de poser les bases sur une méthode de traduction d'une application.

En effet, je ne suis pas fan de la méthode proposée par WinDev. Je n'aime pas l'idée de devoir traduire, fenêtre après fenêtre, langue après langue, les différents termes de mon application. Je n'aime pas non plus l'idée de gérer mes chaînes de caractères en dehors du code. Et enfin, la solution de PCSoft® (les outils WDINT et WDMSG) sont malheureusement payants.

Bref, je suis un codeur, j'ai donc codé mon propre système. Et je vais vous le présenter.

Si la traduction de votre application ne vous intéresse pas dans l'immédiat, je vous invite quand même à le lire. Je préconise une méthode que je trouve assez simple qui permet de prévoir une éventuelle traduction, sans vous ajouter beaucoup de boulot.

Traduire autour du monde

Créer un traducteur

La première étape est de créer un outil qui permet de traduire chaque chaine de caractères de notre projet.

J'ai donc créé une structure utilisant les closures (je vous invite à lire cet article que j'ai écrit si vous ne savez pas de quoi je parle : Les procédures internes et les closures : une nouvelle manière de coder en Windev). Vous pouvez utiliser une classe pour mettre en œuvre le code suivant, mais j'aime l'idée d'avoir une seule procédure globale à déplacer de projet en projet qui regroupe tous mes outils liés à la traduction.

J'ai donc créé une collection de procédure yTraduction et j'ai créé dedans la structure stTraducteur :

stTraducteur est une Structure // CréerTraducteur
    Traduire est une Procédure
    AjouterDictionnaire est une Procédure
    ChangerLangue est une Procédure
    RécupérerDictionnaire est une Procédure
FIN

Ainsi que la fonction CréerTraducteur :

PROCEDURE CréerTraducteur()
    UnTraducteur est un stTraducteur dynamique = allouer un stTraducteur
    Dictionnaire est un MembreVariant

    LangueCourante est une chaîne

    UnTraducteur.AjouterDictionnaire = iAjouterDictionnaire
    UnTraducteur.Traduire = iTraduire
    UnTraducteur.ChangerLangue = iChangerLangue
    UnTraducteur.RécupérerDictionnaire = iRécupérerDictionnaire

    RENVOYER UnTraducteur

Viennent ensuite les procédures internes (à mettre dans le code de la fonction CréerTraducteur.

Tout d'abord la procédure iTraduire qui est la méthode principale. Elle permet de recevoir une chaine de caractère au format ChaineConstruit et de renvoyer sa traduction si elle a été trouvée. Si elle n'a pas été trouvée, on renvoie la chaîne initiale.

Le code de iTraduire :

PROCEDURE INTERNE iTraduire(*)
    ChaineOriginale est une chaîne = MesParamètres[1]
    ChaineFinale est une chaîne

    SI PAS LangueCourante ~= "" ALORS
        Traduction est un Variant = Dictionnaire[ChaineOriginale]

        SI Traduction = Null _OU_ Traduction[LangueCourante]..Valeur = Null ALORS
            Traduction[LangueCourante] = Null
            Dictionnaire[ChaineOriginale] = Traduction
            ChaineFinale = WL.ChaîneConstruit(MesParamètres)
        SINON
            ChaineFinale = WL.ChaîneConstruit(Traduction[LangueCourante], MesParamètres[2 A] )
        FIN
    SINON
        ChaineFinale = WL.ChaîneConstruit(MesParamètres)
    FIN

    RENVOYER ChaineFinale
FIN

Ensuite, on définit la méthode iChangerLangue. Elle permet de définir la langue utilisée. La langue est une simple chaîne de caractère, vous pouvez donc utiliser la codification que vous souhaitez pour chaque langue.

Le code la procédure iChangerLangue :

PROCEDURE INTERNE iChangerLangue(NouvelleLangue est chaîne)
    LangueCourante = NouvelleLangue
FIN

On continue avec la méthode iAjouterDictionnaire. Elle permet d'ajouter un dictionnaire de traduction. Dans mon cas, j'utilise un variant (ou MembreVariant) pour gérer le dictionnaire. L'intérêt de cette méthode, c'est que le nouveau dictionnaire ne remplace pas le précédent mais le complète.

Le code de iAjouterDictionnaire:

PROCEDURE INTERNE iAjouterDictionnaire(DonnéesDeTraduction est un MembreVariant)
    SI DonnéesDeTraduction <> Null ALORS
        POUR TOUT UnMembreExpression DE DonnéesDeTraduction..Membre
            SI Dictionnaire[UnMembreExpression..Nom]..Existe ALORS
                NouveauMembre est un MembreVariant = Dictionnaire[UnMembreExpression..Nom]
                POUR TOUT UnMembreLangue DE UnMembreExpression..Membre
                    NouveauMembre[UnMembreLangue..Nom] = UnMembreLangue
                FIN

                Dictionnaire[UnMembreExpression..Nom] = NouveauMembre
            SINON
                Dictionnaire[UnMembreExpression..Nom] = UnMembreExpression
            FIN
        FIN
    FIN
FIN

Et on finit avec la méthode iRécupérerDictionnaire qui permet de récupérer le dictionnaire. Cela sera utilisé pour sauvegarder le dictionnaire.

PROCEDURE INTERNE iRécupérerDictionnaire()
    RENVOYER Dictionnaire    
FIN

Et enfin, un petit exemple qui permet d'utiliser et de voir ce qu'il se passe :

// Définition des codifications par langue
soit _FRANCAIS_ = LangueVersNom(langueFrançais)
soit _ANGLAIS_ = LangueVersNom(langueAnglais)

Traducteur est un stTraducteur dynamique = CréerTraducteur()

// Premier exemple, on n'a pas défini le dictionnaire, que se passe-t-il ?
Traducteur.ChangerLangue(_FRANCAIS_)
Trace("En français : ", Traducteur.Traduire("Salut %1", "John"))

Traducteur.ChangerLangue(_ANGLAIS_)
Trace("En anglais : ", Traducteur.Traduire("Salut %1", "John"))

// On définit un dictionnaire et on utilise le même exemple
DictionnaireJSON est une chaîne = [
    { "Salut %1":{ "Fran\u00e7ais":"Salut %1", "Anglais":"Hello %1" } }
]

Traducteur.AjouterDictionnaire(JSONVersVariant(DictionnaireJSON))

Traducteur.ChangerLangue(_FRANCAIS_)
Trace("En français : ", Traducteur.Traduire("Salut %1", "John"))

Traducteur.ChangerLangue(_ANGLAIS_)
Trace("En anglais : ", Traducteur.Traduire("Salut %1", "John"))

L'exemple suivant est basé sur les variants et JSon. Je vous invite à lire l'article Windev, les variants et JSON pour en savoir plus.

J'ai utilisé JSon pour stocker le dictionnaire pour avoir un code simple. Vu que le programme n'est pas très lourd, cela ne pose aucun problème de performance. Libre à vous d'utiliser le format de votre choix pour stocker vos dictionnaires (HFSQL, SQlite, CSV, XML, etc...).

Cachez ce traducteur que je ne saurais voir

Si vous ne devez lire qu'une seule chose de ce billet, c'est ce chapitre.

Maintenant que le plus gros du travail est fait, on va chercher à le rendre discret. Nous allons utiliser pour cela l'astuce introduite dans l'article WinDev - Rendre ChaineConstruit plus élégant.

Le simple fait d'utiliser la fonction _ vous permet de préparer votre logiciel à la traduction. Vous pouvez faire cela sans même avoir à mettre en place le traducteur. Cette étape peut-être effectuée beaucoup plus tard dans le cycle de vie de votre logiciel. Vous ne perdrez pas de temps si vous le faîte dès le départ, et plus tard, cela peut s'avérer gagnant.

Voici un exemple qui met en évidence l'utilisation de cette astuce avant la mise en place du traducteur et après.

// Définition de la procédure _
_ est une Procédure

// Premier exemple, on n'a pas encore mis en place la traduction, on utilise donc ChaineConstruit
_ = yChaine.ChaîneConstruit
iExemple()

// Mise en place du traducteur

// Définition des codifications par langue
soit _FRANCAIS_ = LangueVersNom(langueFrançais)
soit _ANGLAIS_ = LangueVersNom(langueAnglais)

// Création et initialisation du dictionnaire avec un dictionnaire
DictionnaireJSON est une chaîne = [
    { "Salut %1":{ "Fran\u00e7ais":"Salut %1", "Anglais":"Hello %1" } }
]
Traducteur est un stTraducteur dynamique = CréerTraducteur()
Traducteur.AjouterDictionnaire(JSONVersVariant(DictionnaireJSON))

_ = Traducteur.Traduire

// Second exemple, le traducteur a été mis en place, on décide de traduire l'application en français
Traducteur.ChangerLangue(_FRANCAIS_)
iExemple()

// Troisième exemple, le traducteur a été mis en place, on décide de traduire l'application en anglais
Traducteur.ChangerLangue(_ANGLAIS_)
iExemple()


PROCEDURE INTERNE iExemple()
    Trace(_("Salut %1", "John"))    
FIN

Sans changer le code de la procédure iExemple, elle a été traduite dès la mise en place du traducteur. Le code de iExemple correspond à 99% de votre code. La mise en place du traducteur, ce n'est même pas 1%.

Que vous utilisiez ou non la traduction, je vous invite à mettre en place ma méthode et à utiliser la procédure _ devant chacune de vos chaînes de caractères. Même si ces dernières n'utilisent pas de paramètre.

Code source et exemple complet de ce billet

Comme d'habitude, je met à disposition de mes abonnés le projet complet qui m'a permis de mettre en place l'exemple.

Ce projet contient :

  • la collection de procédures yTraduction qui contient l'exemple complet ainsi que des procédures permettant de charger / sauvegarder un dictionnaire ;
  • la collection de procédures yInterfaceGraphique qui contient un exemple de code permettant d'énumérer les champs d'une fenêtre via une closure ;
  • un exemple qui montre comment traduire à la volée tous les champs d'une fenêtre ;
  • un exemple pour gérer le chargement d'un dictionnaire interne au projet ainsi que le dictionnaire utilisateur ;
  • un exemple de tests unitaires qui m'ont permis de développer la structure stTraducteur.

Le projet est stocké dans le dossier :

  • 2017-11-07_Traduction.

Si ce n'est pas encore le cas, abonnez-vous et récupérez l'exemple gratuitement.

Résumé du billet

Pour résumer, nous avons vu dans ce billet une méthode qui permet de traduire une application entièrement par programmation et qui permet de se passer des utilitaires de WinDev.

La mise en place en place de la traduction n'est pas obligatoire, mais il est vivement conseillé d'utiliser la fonction _ avec toutes les chaînes de caractères, qu'elles soient avec ou sans paramètres.

Enfin, je vous mets à disposition un exemple complet facile à réutiliser dans vos projets.

Conclusion

J'espère que ce billet vous a intéressé. N'hésitez pas à partager via les commentaires vos différentes astuces ou les problèmes que vous rencontrez avec les exemples. Si vous avez des suggestions d'améliorations à apporter, n'hésitez pas, je suis preneur.

Sachez que vous pouvez me suivre sur Twitter @Johjo07 et que vous pouvez vous abonnez à la lettre d'info.

J'ai mis en place un sondage, j'aimerai avoir votre avis : Sondage - WinDev et vous.

Merci pour votre lecture et bon dev à tous !

Jonathan Laurent

Read more posts about this author.

Comments