Gestion du multilingue / traductions sur Unity

Niveau de difficulté : intermédiaire

Si vous vous demandez comment ça se passe dans ma ptite tête, j’ai cherché de longues heures sans trouver de réponse pour une solution  »built-in » de gestion de traduction. Du coup je me suis servi de mon expérience sur Symfony pour créer ma solution maison de la gestion des différentes langues dans mon jeu. Je tiens à préciser qu’elle est sans doute améliorable, et d’ailleurs je serai heureux d’apprendre comment. Voici tout de même ma proposition à base de Dictionnary , Manager et Enum :

Mon Lang enum. C’est tout con : une liste finie, mais plus robuste qu’une chaine de caractère.

Creuser le problème :

La problématique est la suivante : comment gérer qu’un texte soit affiché soit en Anglais, soit en Français dans un jeux ? Cela soulève déjà la question : comment afficher du texte en jeu ? Cette manière dont les jeux et les programmes ont de le faire naturellement ne laisse pas imaginer une complexité folle. Pourtant il faut un brin de logique pour se rendre compte que c’est pas si évident. Certains problèmes sont purement techniques, tandis que d’autres seront juste un choix partial à prendre. La première question de cet ordre serait : quel langue afficher par défaut ?

  • Choix partial : On affiche l’anglais par défaut car langue la plus utilisée dans le monde : Simple et efficace, mais nécessite que l’utilisateur comprenne à minima l’anglais.
  • Choix plus « simple » mais aléatoire : on affiche la langue par défaut de l’appareil de l’utilisateur. Risque : tomber sur du mandarin ou quelque chose d’exotique, ou une langue non supportée. Je pense pas trop m’avancer et dire que si vous êtes ici, vous parlez au moins le français.
  • Choix batard : sa langue natale, même si on est un inuit ou alsacien comme moi. Risque : on vous comprendra pas, et donc si il faut un minimum de texte, on ne jouera pas au jeu. Godferdomme !!

Vous voyez l’idée, il faut partir d’un choix par défaut pour laisser le choix à l’utilisateur de la langue. Il faut peut être donc intégrer un système de sauvegarde de choix de l’utilisateur. Passons ce détail. Pour se faire, j’ai donc listé les langues supportées dans un enum (liste finie qui contient En et Fr, et d’autres encore si j’ai la motive de traduire plus), j’ai donné le choix de la langue dans un OptionManager qui écoute les changements d’options du menu des options, et je serialize / deserialize (sauvegarder en fichier) les choix utilisateurs pour les restituer. Youpi, on a un choix de langue ! Mais on a pas encore de traduction …

Unity ne propose pas d’outil pour manager les traductions par défaut. Ici les textes sont écris sur les objets directement dans le champ texte.

Les choses à ne surtout pas faire !

Des conditions partout :

Pour gérer la traduction, il faut donc déjà connaitre la langue. Connaitre la langue ne suffit pas, il ne faut surtout pas faire une simple condition du style :

if (options.lang == langEnum.fr) {
return « Bonjour tous le monde »
} else {
return « Hello everyone » }

dans son code. Imaginez : vous devriez répéter ça pour chaque texte que vous voudrez afficher à l’écran. Pas pratique. Quand vous reflechissez à votre code, gardez DRY à l’esprit : Don’t Repeat Yourself.

Ecrire Shakespear dans le / les scripts lié au texte à rendre

Autre chose à ne SURTOUT pas faire : écrire les textes en dur dans le code. Pourquoi ? Si vous deviez gérer un dialogue entre des personnages, mais que les dialogues changent au fur et à mesure de l’histoire, que vous écrivez des pancartes, des sous titre de cinématiques (etc) vous allez vraiment écrire tout le text dans les scripts de controle des personnages / objets ? Non non non, pas pratique, difficile à maintenir, difficile de s’y retrouver, cela ne répond pas à la problématique de la condition du if répété partout, pas performant non plus … L’avantage de la POO est justement de permettre un code « non séquentiel » (c’est à dire que ce n’est pas juste une succession de lignes qui s’exécutent de haut en bas, mais des objets qui interagissent entre eux à la manière de nos relations au monde). Il faut donc éviter à tout prix d’écrire dans son code une succession de dialogues en dur, décousu, éparpillé partout, et qui respecte pour seule logique l’ordre d’exécution. En plus si vous voulez répéter un dialogue, avec cette logique il faudrait copier coller un pan entier de texte ! Gardez SOLID à lesprit : cela doit être SIMPLE à maintenir.

Ecrire 2 textes en dur sur un champ string ou longtext de GameObject d’Unity

L’autre TRES mauvaise idée, est de mettre un champ long texte sur ses prefabs ou GameObject Unity. Pourquoi ? Si vous modifiez le nom du champ, vous perdez le texte écris définitivement, encore plus si vous avez des textes différents par prefab : vous perdrez tous les textes écris dans des variables. Les Text des GUI Element ou TextMesh_Pro ne sont pas assez robuste non plus pour permettre une bonne gestion de la traduction. Enfin ce n’est que mon avis hein.

N’oublions pas : Réfléchir Objet.

Et donc pour éviter de devoir taper des string (chaîne de caractères) à rallonge de partout, je vais me servir d’un système de clef valeur : la clef sera un identifiant pour X traductions ! Imaginez une fonction qui accepte une clef comme entrée, et une traduction en sortie, comme un dictionnaire français anglais. Cette fonction est agnostique (elle s’en fout) de la « langue », elle veut juste une chaine de caractère en entrée , la chercher dans le dictionnaire, pour donner une sortie sous forme de string, mais elle dispose que d’un seul dictionnaire de clef valeurs, attribué en fonction de la langue choisie. Ici ce dictionnaire est attribué en fonction de la langue des options : nous avons donc qu’à remplir une seule fois la condition de vérification de langue vue plus haut ! Si la langue est lang.fr , alors je lui attribue le dictionnaire Fr, autrement le dictionnaire En.

Voici juste un petit exemple de morceaux de textes différents. Imaginez les éparpillé sur des dizaines de fichiers différents, dans plusieurs langues ! Vous pouvez aussi voir deux dictionnaires différents : j’utilise le premier pour traduire les actions du jeu en fonction de leur enum.

Un dictionnaire en C# est un objet de classe Dictionnary<TypeEntrée,TypeSortie>() , auquel on peut définir quel est le type d’entrée et le type de sortie. Celui ci peut être écrit en dur (dans notre cas cela sera ce choix : je veux pouvoir écrire mes dialogues à l’avance et changer le jeu comme je veux) ou calculé à la volée, et a l’avantage de pouvoir être facilement maintenable (il faut le voir comme un « simple » fichier texte un peu plus poussé). Cela peut par exemple permettre de convertir un enum en chaîne de caractère, ou encore un texte en autre texte. Si il y a correspondance entre la clef d’entrée et une valeur du dictionnaire, il réstitue la valeur associée ! Pratique non ?

Ne faite pas attention au Dictionnary resolutions, je m’en sers pour lister les résolutions supportées, et restituer la valeur textuelle de celles ci. Dans cet exemple, dans la methode Awake, nous pouvons voir que si la lang de OptionManager est lang.fr, le dictionnary sera le new Translation().fr, où Translation est ma classe qui contient mes dictionnaires.

Imaginons maintenant un texte d’accueil. Je contextualise à minima cette clef pour des soucis de simplicité et de maintenabilité. Par exemple main.description.welcom (clef d’entrée) , la traduction Anglaise sera « Welcome to the Main Page » et Française « Bienvenu sur la page d’accueil ». J’aurai donc 2 dictionnaires : un public static Dictionnary<string,string> Fr et un public Dictionnary<string,string> En avec chacun {« main.description.welcom », »la traduction !! »}. Vous pouvez l’étendre à de multiples dictionnaires ! La seule contrainte est d’avoir les mêmes clefs dans les dictionnaires, où sinon faire un try catch de bourrin de la traduction au cas où la clef est absente (et s’en servir pour debugger tant qu’à faire).

« Au moins, y’a un try catch » #DavidGoodEnough

Cette solution offre pour moi une stabilité et une robustesse que je n’ai pas réussi à remettre en question. Je peux grâce à cela, créé un script que je colle sur les objets qui restituent du texte (les textmesh pro). Ils contiennent la clef en claire, et au lancement du jeu c’est traduit car mon script récupère la clef et restitue la traduction.

Voilà le script réduit à sa plus simple utilité : il récupère le TextMeshPro sur lequel il est placé, il récupère l’instance de mon OptionManager, et utilise la methode de traduction Translation(text.text) pour restituer la traduction

Pour conclure, je dirai qu’utiliser des clefs valeurs grâce aux dictionnaires permet de gagner pas mal de temps : on centralise la logique de traduction, on facilite le maintient et la propreté du code, sans être réellement dépendant des composants d’Unity. Cela permet d’éviter de perdre des dialogues entiers car ils sont enregistrés en dur dans un seul fichier, et non pas sur des objets modifiables. Au final c’est assez simple à mettre en place. On pourra reprocher que mon implémentation est un peu complexe (Elle est pas très KISS : Keep It Simple Stupid) car elle fait intervenir beaucoup de notions (gestion des options, des sauvegardes, utilisation des enums au lieu d’un boolean, utilisation des dictionnaires), mais elle n’est la résultante que de mon expérience de developpeur web. Si vous avez une autre solution plus robuste, je suis preneur !

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s