Système de localisation/traduction sur Ryzom Core
Généralités
Il y a, en gros, deux parties distinctes pour la localisation dans Ryzom. La première partie (et la plus simple) concerne la localisation statique côté serveur (autrement dit, les noms d'interface, les messages d'erreur). La seconde partie est pour les textes générés dynamiquement depuis les serveurs.
Comme vous pouvez le voir sur le diagramme, il y a quatre types de fichiers qui font fonctionner le système de localisation. Chacun de ces fichiers doit venir dans chaque langue localisée. En gras, vous pouvez voir que chaque fichier inclut le code de la langue dans son nom.
Les formats de fichiers sont détaillés ci-dessous.
Code Langue
Les langues, dans Ryzom, sont identifiées par leur code langue tel que défini dans ISO 639-1 plus un code pays défini en ISO 3166 si nécessaire.
ISO 639-1 est un code langue sur deux caractères (par exemple 'en', ou 'fr'). Ce qui suffit pour la plupart des langues que nous voulons gérer.
Mais il y a quelques exceptions, comme le chinois écrit. Le chinois peut s'écrire sous deux formes : traditionnelle ou simplifiée. Cependant, il n'y a qu'un seul code pour le chinois : 'hz'.
Nous devons donc ajouter un code pays pour indiquer de quelle forme de chinois écrit nous parlons. Le code langue pour le chinois simplifié devient 'hz-CN' (c'est-à-dire Langue chinoises, Pays Chine), et pour le chinois traditionnel, c'est 'hz' tout seul parce que tous les autres pays de langue chinoise (Taiwan, Hong Kong ?) utilisent le chinois traditionnel.
Définition de l'identifiant
Les chaînes traduites sont associées à un identifiant. Les identifiants sont des chaînes de texte qui doivent suivent la contrainte de l'identifiant C, à une petite différence près.
Un identifiant C ne doit contenir que les caractères suivants : A-Z
, a-z
, 0-9
, @
et _
.
Un véritable identifiant C ne peut pas commencer par un nombre, un identifiant de chaîne le peut.
Quelques bons identifiants :
Ceci_est_un_bon_identifiant CeciEstUnBonIdentifiant _Ceci@est@unautreBonId 1234_est_un_bonId Ceci_est_un_Bon_1234
Quelques mauvais identifiants :
Ceci est un mauvais identifiant é#()|{[_IdPASBON
Formats de fichier
Il y a trois formats différents pour les fichiers de traduction. Mais on n'a besoin d'en connaître que deux.
Format 1
Ce format est utilisé pour le texte statique côté client et pour le texte à clauses côté serveur.
Ce fichier est une liste d'associations entre identifiants et chaînes (qu'on appelle aussi des chaînes de valeur). L'identifiant doit obéir à la contrainte de l'identifiant C, et la chaîne de valeur est délimitée par [
et ]
. La mise en forme du texte est lire ; vous pouvez sauter des lignes et indentités comme vous voulez.
identifiant1 [valeur de texte] identifiant2 [autre valeur de texte]
Le fichier peut inclure des commentaires de style C.
// Ceci est juste une ligne de commentaire. Continuer jusqu'à la fin de la ligne identifiant1 [valeur de texte] /* Ceci est un commentaire sur plusieurs lignes */ identifiant2 /* commentaire sur plusieurs lignes ici ! */ [autre valeur de texte]
Les valeurs de texte peuvent être mises en forme pour une meilleure lisibilité. La nouvelle ligne et les tabulations sont supprimées dans la valeur finale de la chaîne.
identifiant1 [valeur de texte avec une nouvelle ligne et des tabulations rien que pour la lisibilité] identifiant2 [autre valeur de texte]
Si vous voulez indiquer de nouvelles lignes ou des tabulations dans une chaîne de valeur, vous devez utilisers la séquence d'échappement de style C \t
pour les tabulations et \n
pour les nouvelles lignes. Pour écrire \
dans une chaîne de valeur, doublez le backslash : \\
. Pour écrire ]
dans la chaîne, faîtes le précéder d'un backslah : \]
.
identifiant1 [tabulation : \tCe texte est tabulé] identifiant2 [Nouvelle ligne \nTexte sur une nouvelle ligne] identifiant3 [Backslash : \\] identifiant4 [un crochet fermant : \] ]
Vous pouvez découper le fichier d'origine en une série de fichiers plus petits, plus simples à maintenir et à utiliser. Ceci se fait en utilisant une commande de préprocesseur de type C “#include.
#include "path/filename.txt"
Vous pouvez avoir un nombre quelconque de commandes “include”. Les fichiers inclus peuvent également contenir des commandes “include”. Le chemin peut être aussi bien absolu que relatif à l'emplacement du fichier maître.
Format 2
Ce format est utilisé pour les fichiers de phrases de traduction. Ce format a une grammaire assez compliquée qui va être décrite dans une syntaxe proche de LALR :
identifiant : [A-Za-z0-9_@]+ phrase : identifiant ‘(‘ parameterList ‘)’ ‘{‘ clauseList ‘}’ parameterList : parameterList ‘,’ parameterDesc | parameterDesc parameterDesc : parameterType parameterName parameterName : identifiant parameterType : ‘item’ | ‘place’ | ‘creature’ | ‘skill’ | ‘role’ | ‘ecosystem’ | ‘race’ | ‘brick’ | ‘tribe’ | ‘guild’ | ‘player’ | ‘int’ | ‘bot’ | ‘time’ | ‘money’ | ‘compass’ | ‘dyn_string_id’ | ‘string_id’ | ‘self’ | ‘creature_model’ | ‘entity’ | ‘bot_name’ | ‘bodypart’ | ‘score’ | ‘sphrase’ | ‘characteristic’ | ‘damage_type’ | ‘literal’ clauseList : clauseList clause | clause clause : conditionList identifiant textValue | identifiant textValue | conditionList identifiant | identifiant | textValue conditionList : conditionList condition | condition condition : ‘(‘ testList ‘)’ testList : testList ‘&’ test | test test : operand1 operator reference operand1 : parameterName | parameterName’.’propertyName propertyName : identifiant operator : ‘=’ | ‘!=’ | ‘<’ | ‘<=’ | ‘>’ | ‘<=’ reference : identifiant textValue : ‘[‘ .* ‘]’
Comme dans le format 1, vous pouvez insérer des commentaires en style C dans le text, indenter à loisir, et utiliser la commande include.
Format 3 : Export de feuille de calcul Unicode
Ce format est le résultat d'un export de feuille de calcul en texte Unicode. L'encodage doit être unicode 16 bits. Les colonnes sont séparées par des tabulations, et les lignes par de nouvelles lignes.
Vous ne devez pas les modifier manuellement, mais uniquement avec la feuille de calcul.
La première ligne contiendra le nom des colonnes.
Colonnes d'information
Si le nom d'une colonne commence par '*', alors toute la colonne sera ignorée. Ceci est utile pour ajouter une colonne d'information qui pourra aider à la traduction.
Caractère d'effacement
Il est possible d'insérer une commande 'delete' (effacer) dans un champ : '\d'. C'est utile pour la traduction des articles.
Exemple : vous avez une chaîne avec les remplacements suivants (en français) :
Rapporte moi $item.da$ $item.name$“
Et le fichier des noms d'items contient ce qui suit :
item name da marteau marteau le echelle échelle l’
Si l'item est 'marteau', pas de problème. Le remplacement donnera :
“Rapporte moi le marteau”
Mais pour 'échelle', il y a un espace supplémentaire dans le résultat :
“Rapporte moi l’ échelle”
Pour supprimer cet espace supplémentaire, vous pouvez ajouter un marqueur 'delete' dans la définition de l'article :
item name da marteau marteau le echelle échelle l’\d
Ceci donnera une chaîne correcte comme résultat :
“Rapporte moi l’échelle”
Travailler avec des fichiers de traduction, du point de vue du traducteur
Fichiers “*.uxt” sur le client
Ce fichier contient tous les textes statiques disponibles directement sur le client. Le texte doit suivre le format 1 décrit ci-dessus.
Il y a une contrainte supplémentaire : vous DEVEZ fournir le nom de la langue comme première entrée, telle qu'elle s'écrit dans la langue considérée (c'est-à-dire ‘English’ pour l'anglais, ‘Français’ pour le français…).
Par exemple, le fichier en.uxt doit commencer par :
languageName [English]
Fichiers sur le serveur
La traduction côté serveur est un peu plus compliquée. Nous allons voir comment écrire des traductions côté serveur en quatre étapes (devinez quoi : des problèmes les plus simples au plus compliqués !).
1ère étape : Une chaîne simple
Pour cela, vous n'avez besoin que du fichier de phrases.
Disons que nous voulons une chaîne qui dise “hello world!”, identifiée par HelloWorld
.
Créez une entrée de phrase dans phrase_en.txt
:
HelloWorld () { [Hello world!] }
Et voilà ! C'est tout.
Bien sûr, vous devrez aussi fournir la même phrase dans toutes les langues supportées, par exemple dans phrase_fr.txt :
HelloWorld () { [Bonjour le monde!] }
Remarquez que seule la valeur du texte a changé. L'identifiant de phrase DOIT être le même dans tous les fichiers de traduction.
2ème étape : Contournement pour "clause_<lang>.txt"
Dans la 4ème étape, nous verrons que le fichier de phrases peut devenir très complexe. Par conséquent, ce fichier n'est pas très adapté si on veut le donner à un traducteur professionnel sans compétence en grammaire complexe de fichier. Pire, la complexité du fichier peut masquer le travail à faire pour la traduction.
On peut donc séparer la grammaire de la phrase en fichiers de phrases et la valeur textuelle en fichier de clauses.
Pour cela, vous devez assigner un identifiant unique à chaque valeur de texte. Reprenons l'exemple précédent avec contournement.
Dans phrase_en.txt
, créer l'entrée de phrase comme ceci :
HelloWorld () { Hello }
Nous avons juste à mettre un identifiant dans le bloc de phrase. Ce qui signifie que la phrase se réfère à une chaîne identifiée comme Hello
dans le fichier de clause.
Maintenant, nous pouvons créer la valeur textuelle dans clause_en.txt
:
Hello [Hello world!]
Comme dans la première étape, vous devrez faire ça pour chaque langue.
Pour faciliter le travail de traduction, il est possible de spécifier l'identifiant de chaîne ET l'identifiant de valeur. Ceci peut être utile pour construire automatiquement un fichier de traduction à partir du fichier d'origine.
Exemple :
HelloWorld () { Hello [Bonjour le monde!] }
Dans un cas comme celui-ci, le système de traduction regarde toujours d'abord dans le fichier de clause, et se rabat sur la valeur textuelle dans le fichier de phrase uniquement s'il ne trouve pas de chaîne dans le fichier de clause.
L'autre avantage, c'est que la personne qui a écrit le fichier de phrase peut donner une version simplifiée de la chaîne, qu'un traducteur professionnel pourra améliorer.
3ème étape : Utiliser des paramètres – les bases
C'est là qu'on attaque la partie compliquée !
Chaque phrase peut recevoir une liste de paramètres.
Ces paramètres peuvent être de différents types :
- item, (objet)
- place, (emplacement)
- creature, (créature, animal)
- skill, (compétence)
- ecosystem, (écosystème)
- race, (race)
- brick, (brique)
- tribe, (tribu)
- guild, (guilde)
- player, (joueur)
- int, (nombre entier)
- bot, (robot)
- time, (date)
- money, (argent)
- compass, (compas)
- dyn_string_id, (identifiant de chaîne dynamique)
- string_id, (identifiant de chaîne)
- creature_model, (modèle de créature)
- entity, (entité)
- body_part, (partie du corps)
- score, (score)
- sphrase, (sous-phrase ?)
- characteristic, (caractéristique)
- damage_type, (type de dégât)
- bot_name, (nom du robot)
- literal. (?)
Chaque paramètre reçoit un nom (ou identifiant) à sa déclaration. Nous l’appelons paramName.
Chaque type de paramètre PEUT être associé avec un fichier de 'mot'. Ce fichier est une feuille excel (au format d'export de texte unicode) qui contient la traduction du paramètre : son nom, un article indéfini ou défini ('un', 'le'…), le pluriel du nom et de l'article, et toute propriété utile ou élément grammatical nécessaire pour la traduction.
La première colonne est très importante car elle associe une ligne de donnée avec une valeur particulière du paramètre.
Commençons par un exemple : nous voulons construire une phrase dynamique avec une variable de nom d'espèce de la créature.
D'abord, nous devons créer une feuille excel qui définit les mots pour l'espèce de la créature. Celle-ci sera sauvegardée en tant que race_words_<lang>.txt
en export de texte unicode depuis excel. Comme toujours, vous devrez fournir une version de ce fichier pour chaque langue.
NB : La première colonne DOIT toujours être le champ d'association et vous devrez avoir une colonne name
(nom) puisque c'est la valeur par défaut pour les paramètres. Toute autre colonne est facultative et peut varier d'une langue à l'autre pour correspondre aux diverses contraintes grammaticales spécifiques.
Voici un exemple de race_words_en.txt
:
race name ia da p pia pda kitifly Kitifly a the Kitiflys the varynx Varynx a the Varynx the etc…
Comme énoncé ci-dessus, la première colonne donne l'identifiant de la race, tel que défini dans les fiches de développement du jeu (game dev sheets). La deuxième colonne est la colonne 'hautement recommandée' pour stocker le nom de la race. La colonne 'p' est le nom au pluriel. 'ia' et 'da' indiquent les articles indéfinis et définis.
Ensuite, il faut créer une phrase avec un paramètre créature dans phrase_<lang>.txt
:
KILL_A_CREATURE (race crea) {}
Comme vous pouvez le voir, après l'identifiant de phrase KILL_THIS_CREATURE
, nous avons la liste de paramètres entre parenthèses. Nous déclarons un paramètre de type race
nommé crea
. Remarquez que vous choisissez librement le nom de votre paramètre mais que chaque paramètre doit avoir un nom unique (au moins pour une phrase).
Maintenant, nous pouvons construire la valeur de chaîne. Pour insérer le paramètre dans la chaîne, nous devons préciser le point de remplacement à l'aide du signe $
(c'est-à-dire $crea$
) directement dans la valeur de chaîne :
KILL_A_CREATURE (race crea) { [Would you please kill a $crea$ for me ?] }
Comme vous le voyez, ce n'est pas trop compliqué. $crea$
sera remplacé par le contenu de champ issu du fichier de mots dans la colonne name
et pour la ligne correspondant à l'identifier de la race.
Il est possible de rappeler n'importe quelle colonne du fichier de mots dans la chaîne de valeur. Nous pouvons par exemple dynamiser l'article indéfini :
KILL_A_CREATURE (race crea) { [Would you please kill $crea.ia$ $crea$ for me ?] }
Certains types de paramètres ont des règles de remplacement spéciales : les int
sont remplacés par leur représentation en texte, les time
sont converties en date ryzomienne, de même que les money
.
Enfin, un dernier point mais non des moindres, les règles pour les identifiants et les contournements vues aux étapes 1 et 2 sont toujours valides.
4ème étape : Utiliser les paramètres – clause conditionnelle
Il est temps maintenant de dévoiler le système de clause conditionnelle.
Disons que l'identifiant et la valeur de chaîne que nous avons mis dans une phrase à l'étape précédentes sont une clause. Et disons qu'une phrase peut contenir plus d'une clause, celle-ci pouvant être choisie par le moteur de traduction à la volée en fonction de la valeur du paramètre. C'est le système de clause conditionnelle.
Commençons par un premier exemple. Comme pour l'étape 3, nous voulons tuer une créature, mais cette fois, nous ajoutons une variable pour le nombre d'animaux à tuer, de 0 à n. Ce dont nous avons besoin, c'est la condition à partir de laquelle choisir entre les trois clauses : pas de créature à tuer, une créature à tuer, ou plusieurs.
D'abord, écrivons la phrase, ses paramètres et ses trois clauses :
KILL_A_CREATURE (race crea, int count) { // no creature to kill (Pas de créature à tuer) [There is no creature to kill today.] // 1 creature to kill (1 créature à tuer) [Would you please kill a $crea$ for me ?] // more than one (plus d'une) [Would you please kill $count$ $crea$ for me ?] }
Nous avons écrit trois versions du texte avec un sens et une structure grammaticale très différents. Maintenant, ajoutons les conditions. Les conditions sont placées avant l'identifiant et/ou la chaîne de valeur et sont indiquées par des parenthèses.
KILL_A_CREATURE (race crea, int count) { // no creature to kill (Pas de créature à tuer) (count = 0) [There is no creature to kill today.] // 1 creature to kill (1 créature à tuer) (count = 1) [Would you please kill a $crea$ for me ?] // more than one (plus d'une) (count > 1) [Would you please kill $count$ $crea$ for me ?] }
Facile, non ?
Maintenant, prenons un cas un peu plus compliqué : nous voulons écrire une phrase différente selon que le joueur soit masculin ou féminin. Voici une occasion d'introduire le paramètre self. Le paramètre Self
est un paramètre caché
, car il est toujours disponible et représente le destinataire de la phrase (celui à qui elle s'adresse).
Le paramètre Self
porte les propriétés de genre et de nom. Vous pouvez créer un fichier self_words_<lang>.txt
pour gérer les cas spéciaux (un joueur admin avec un nom traduisible, par exemple).
Réécrivons la requête pour tuer des animaux en tenant compte du genre du joueur :
KILL_A_CREATURE (race crea, int count) { // -- Joueur masculin // pas de créature à tuer, joueur masculin (count = 0 & self.gender = Male) [Hi man, there is no creature to kill today .] // 1 créature à tuer, joueur masculin (count = 1 & self.gender = Male) [Hi man, would you please kill a $crea$ for me ?] // plus d'une, joueur masculin (count > 1 & self.gender = Male) [Hi man, Would you please kill $count$ $crea$ for me ?] // -- Joueur féminin // pas de créature à tuer, joueur féminin (count = 0 & self.gender = Female) [Hi girl, There is no creature to kill today.] // 1 créature à tuer, joueur féminin (count = 1 & self.gender = Female) [Hi girl, Would you please kill a $crea$ for me ?] // plus d'une, joueur féminin (count > 1 & self.gender = Female) [Hi girl, Would you please kill $count$ $crea$ for me ?] }
Nous avons six clauses maintenant. Trois pour le nombre de créatures, multipliées par les deux genres du joueur.
Comme vous pouvez le voir, les tests conditionnels peuvent être combinés avec le caractère &
. Ce qui signifie que tous les tests doivent être valides pour sélectionner la clause.
Vous pouvez utiliser n'importe quel paramètre comme opérateur gauche pour le test. Vous pouvez également spécifier une propriété de paramètre (issue du fichier de mots) comme opérande.
De l'autre côté, l'opérande de droite du test doit être une valeur constante (textuelle ou numérique). Les opérateurs disponibles sont
= != < <= > >=
Dans certains cas, vous pourriez avoir besoin de faire des combinaisons de tests avec OR
(ou). Ceci est possible simplement en indiquant une liste de condition multiple devant une clause :
FOO_PHRASE (int c1, int c2) { (c1 = 0 & c2 = 10) (c1 = 10 & c2 = 0) [On passe dans cette clause si : c1 égal zéro et c2 égal dix ou c1 égal dix et c2 égal zéro] }
Règles détaillées de sélection des clauses :
- Une clause valide est une clause où les combinaisons conditionnelles sont vraies pour un ensemble donné de valeurs du paramètre.
- Les clauses sont évaluées dans l'ordre dans lequel elles sont écrites dans le fichier de phrase. La première clause valide sera retenue.
- Si la première clause n'a aucune condition, elle sera utilisée comme clause par défaut si aucune clause conditionnelle n'a été choisie.
- S'il n'y a pas de clause par défaut, et qu'aucune des clauses n'est retenue, alors c'est la première clause qui est choisie.
- Pour la même phrase, chaque langue peut fournir son propre ensemble de conditions et de clauses pour s'adapter à ses besoins grammaticaux.
Liste des propriétés des paramètres codés en dur
Vous trouverez ici une liste exhaustive des propriétés des paramètres codés en dur. Ces propriétés sont toujours disponbible, même quand aucun fichier de mots n'a été fourni. De plus, les propriétés codées en dur ne peuvent pas être remplacées par un fichier de nom qui utiliserait une colonne du même nom.
Paramètre | Propriété |
---|---|
Item | |
Place | |
Creature | “name : nom du modèle pour la créature |
gender : genre de la créature (tiré ?) du modèle” | |
Skill | |
Ecosystem | |
Race | |
Brick | |
Tribe | |
Guild | |
Player | “name : nom du joueur |
gender : genre du joueur dans le miroir” | |
Bot | “career : carrière du bot |
role : rôle du bot tel que défini dans creature.basics.chatProfile | |
name : nom de la créature | |
gender : genre de la créature (tiré ?) du modèle” | |
Integer | |
Time | |
Money | |
Compass | |
dyn_string_id | Seuls les tests != et == sont possibles. Essentiellement pour comparer un paramètre avec 0. |
string_id | Seuls les tests != et == sont possibles. Essentiellement pour comparer un paramètre avec 0. |
self | “name : nom du joueur |
gender : genre du joueur dans le miroir” | |
creature_model | NB : utiliser le fichier de traduction creature_words ! |
entity | ”== 0, != 0 : teste si l'entité est Unknown (inconnue) ou non |
name : nom de la créature ou nom du joueur | |
gender : genre de la créature (tiré ?) du modèle ou genre du joueur (d'après l'info du joueur)“ | |
bodypart | |
score | |
sphrase | |
characteristic | |
damage_type | |
bot_name |
Workflow de traduction
Dans le worklow de traduction ci-dessous, nous considérons que la langue de référence est l'anglais.
Il y a une série d'outils et de fichiers bat pour aider à synchroniser les traductions. Voici une description point par point de comment travailler sur un fichier de traduction, quel outil utiliser et quand.
Seuls les ajouts aux fichiers de traduction existants sont supportés par les outils de traduction. Si vous souhaitez modifier ou supprimer une chaîne ou une phrase de traduction existante, ce sera à faire 'à la main' avec un maximum d'attention dans toutes les versions linguistiques.
Dans la plupart des cas, il vaut mieux créer une nouvelle entrée de traduction, plutôt que de gérer une modification dans tous les fichiers de traduction, et il est à peu près sûr de laisser les vieilles chaînes inutilisées dans les fichiers.
En tous cas, vous ne devez JAMAIS faire de modifications quand il y a des fichiers diff en attente.
Il est fortement recommandé de respecter scrupuleusement le workflow décrit pour éviter les problèmes de traduction, les chaînes manquantes ou autres problèmes bizarres qui pourraient se produire en travaillant avec plusieurs versions linguistiques d'un ensemble de fichiers.
Le travail de traduction se fait en collaboration entre l'éditeur, qui fait la partie 'technique' du travail, et un traducteur professionnel sous-contrat qui traduit uniquement des chaînes simples en chaînes riches et de très haute qualité en respectant le contexte.
L'outil qui génère le fichier diff pour la traduction garde les commentaires de la version de référence. Ceci peut être utile pour fournir des informations supplémentaires au traducteur au sujet du contexte du texte.
De plus, pour le fichier de phrases, le fichier diff inclut automatiquement les commentaires qui décrivent la liste de paramètres.
Par défaut il semble que le système travaille avec des fichiers ISO-8859-15 etpas UTF8 donc si vous obtenez des résultats étranges avec les commandes, c'est sûrement dû à un encodage UTF8 de votre fichier.
Structure de l'entrepôt de traduction
Tous les fichiers à traduire sont stockés dans une structure de répertoire bien définie, appelée entrepôt de traduction
. Tout le travail de traduction est fait dans ce répertoire.
Des outils sont fournis pour installer le fichier traduit dans le client et le répertoire du serveur après la fin du cycle de traduction.
translation/
Répertoire racine pour la traductionlanguages.txt
Un simple fichier texte qui contient tous les codes langues (ISO 639-2) avec lesquels nous travaillons (par exemple : en, fr, etc…).work/
C'est le point de départ pour l'ajout de nouveau contenu. Ce répertoire contient tous les fichiers qui peuvent être modifiés pour ajout.diff/
Contient tous les fichiers diff générés par les outils. C'est là que se fait l'essentiel du travail. Après l'opération de fusion qui intègre les diff traduits dans les fichiers de traduction, les fichiers diff sont déplacés dans le répertoire history. Quand des fichiers diff sont générés, ils sont préfixés avec un numéro de version calculé automatiquement à partir de l'horodatage Unix courant. Ceci permet de générer de nouveaux fichiers diff sans attendre que la traduction du diff soit faite. De plus, quand vous générez un nouveau fichier diff sans fichier diff fusionné, le nouveau diff ne contient que les différences.history/
Contient tous les fichiers diff qui ont été traduits et fusionnés. Utilisé pour des raisons de sauvegarde et de sécurité.translated/
Contient tous les fichiers traduits. Le contenu de ce répertoire est installé sur les entrepôts du client et du serveur lorsque le cycle de traduction est terminé. Vous ne devez jamais rien changer à la main dans ce repertoire à moins de savoir exactement ce que vous faites.
Traduction des noms de bot
La commande extract_bot_names
analyse le fichier bot_names.txt
en fonction des Primitives et si certains noms sont utilisés plusieurs fois dans celles-ci, il doit transformer le nom du bot en un nom générique du type gn_nomgénérique
dont la correspondance est crée dans le title_words
correspondant.
Test à effectuer cf HexChatlog kerv30/12/2015 - 14:30 — Yann K 2015/12/30 14:42
Chaîne statique sur le client
Tâche initiale
Editeur :
- Créer le fichier de référence
en.uxt' dans le répertoire
work', remplir la première chaîne nécessaireLanguageName
puis ajouter toutes les chaînes pour la version anglaise. - Générer les fichiers diff de chaînes statiques avec la commande
make_string_diff
. Ceci créera les fichiersdiff
pour tous les langages dans le répertoirediff
. - Envoyer les fichiers diff au traducteur.
Traducteur :
- Traduire les fichiers diff.
- Les renvoyer à l'éditeur.
Editeur :
- Mettre les fichiers diff traduits dans le répertoire
diff
(ce qui écrasera les fichiers diff non traduits et les remplacera par les fichiers traduits). - Fusionner les fichiers diff traduits à l'aide de la commande
merge_string_diff
. Cela créera les fichiers<lang>.uxt
pour chaque langue dans le répertoiretranslated
et déplacera le diff dans le dossierhistory
.
Une fois la tâche initiale terminée, le workflow entre en mode incrémental.
Tache incrémentale
Editeur
- Ajouter des chaînes dans le fichier de référence
en.uxt
dans le répertoirework
. - Générer les fichiers diff de chaînes statiques avec la commande
make_string_diff
. Ceci créera les fichiers diff pour chaque langue dans le dossierdiff
. - Envoyer les fichiers diff au traducteur.
Traducteur
- Traduire les fichiers diff.
- Les renvoyer à l'éditeur.
Editeur
- Déposer les fichiers diff traduits dans le répertoire
diff
(ce qui écrasera les fichiers diff non traduits et les remplacera par les fichiers traduits). - Fusionner les fichiers diff avec la commande
merge_string_diff
. Ceci appliquera les fichierssdiff
aux fichiers<lang>.uxt
pour chaque langue dans le dossiertranslated
et déplacera les diff dans le dossierhistory
.
Chaînes dynamiques sur le serveur
Tâche initiale
Editeur
- Créer le fichier de référence
phrase_wk.uxt
dans le répertoirework
et ajouter toutes les entrées de phrases pour la version anglaise. - Générer les fichiers de phrase diff avec la commande
make_phrase_diff
. Ceci créera les fichiers diff pour chaque langue dans le répertoire diff. - Traduire le fichier de phrases diff. Ceci implique de bonnes connaissances de la structure grammaticale de chaque langue pour s'adapter aux règles des clauses de sélection.
- Fusionner le fichier diff traduit à l'aide de la commande
merge_phrase_diff
. Ceci créera tous les fichiersphrase_<lang>.txt
dans le répertoiretranslated
pour le fichierdiff
traduit, puis déplacera les fichiers diff dans le répertoirehistory
. - Générer le fichier de clauses diff avec la commande
make_clause_diff
. Ceci créera le fichier de clauses diff dans le répertoire diff pour toutes les langues. - Envoyer les fichiers de clauses diff au traducteur.
Traducteur
- Traduire les fichiers de clauses diff.
- Les renvoyer à l'éditeur.
Editeur
- Déposer les fichiers de clause diff traduits dans le répertoire
diff
(ce qui écrasera les fichiers de clause diff non traduits et les remplacera par les fichiers traduits). - Fusionner les fichiers de clause diff avec la commande
merge_clause_diff
. Ceci créera les fichiersclause_<lang>.txt
pour chaque langue dans le répertoiretranslated
et déplacera lesdiff
dans le répertoirehistory
.
Une fois la tâche initiale accomplie, le workflow entre en mode incrémental.
Tache incrémentale
Editeur *
- Ajouter la nouvelle phrase dans
phrase_wk.uxt
dans le dossierwork
. - Générer les fichiers de phrase diff avec la commande
make_phrase_diff
. Ceci créera les fichiers diff pour chaque langue dans le répertoirediff
. - Traduire le fichier de phrase diff. Ceci implique de bonnes connaissances de la structure grammaticale de chaque langue pour s'adapter aux règles de sélection des clauses.
- Fusionner les fichiers diff traduits à l'aide de la commande
merge_phrase_diff
. Ceci ajoutera les diff à leurs fichiersphrase_<lang>.txt
respectifs dans le répertoiretranslated
puis déplacera les fichiers diff dans le répertoirehistory
. - Générer les fichiers de clauses diff avec la commande
make_clause_diff
. Ceci créera le fichier de clause diff dans le répertoirediff
. - Envoyer les fichiers de clause diff au traducteur.
Traducteur
- Traduire les fichiers de clauses diff.
- Les renvoyer à l'éditeur.
Editeur
- Déposer les fichiers de clause diff traduits dans le répertoire
diff
(ce qui écrasera les fichiers de clause non traduits et les remplacera par les fichiers traduits). - Fusionner les fichiers de clause traduits avec la commande
merge_clause_diff
. Ceci ajoutera les fichiers diff à leurs fichiersclause_<lang>.txt
respectifs pour chaque langue dans le répertoiretranslated
et déplacera les diff dans le répertoirehistory
.
Fichiers de mot côté serveur
Il faut désormais recourir à des outils également, comme pour les autres:
How to update translations in words files :
1. Update original texts in “work” directory 2. Launch 5_make_words_diff script 3. Open files in “diff” directory 4. Replace original text with translation (separators are <tab>) 5. The 2 last lines : REMOVE THE FOLOWING TWO LINE WHEN TRANSLATION IS DONE and DIFF NOT TRANSLATED 6. Save files 7. Launch 6_merge_words_diff to merge your translations in “translated”
NB : ‘mot’ (N.d.T. 'words', dans le texte d'origine) n'a aucun rapport avec le program Word de microsoft !
Les fichiers de mots sont toujours mis à jour à la main parce qu'ils sont rarement mis à jour par l'éditeur (et, en général, uniquement pour de grosses mises à jour). De plus, quand de nouvelles phrases sont traduites, il peut être nécessaire de créer et de remplir une nouvelle colonne dans l'un des fichiers de mots pour s'adapter à la traduction.
Il y a donc juste un workflow, mais pas d'outils.
Taches initiales
Editeur
- Créer la feuille initiale pour chaque type de paramètre avec tous les identifiants possibles dans le langae de référence dans le répertoire translated.
- Créer et remplire les colonnes par défaut
name
etp
. - Créer les feuilles pour toutes les langues en copiant les feuilles de la langue de référence.
- Créer et remplir toutes les colonnes de bases dépendantes de la langue.
- Envoyer toutes les feuilles au traducteur.
Traducteur
- Traduire toutes les feuilles dans toutes les langues, en ajoutant éventuellement toutes les colonnes nécessaires.
- Renvoyer les feuilles traduites à l'éditeur.
- Garder une copie des feuilles traduites comme référence pour la traduction des phrases et des clauses.
Editeur
- Déposer les feuilles traduites dans le répertoire
translated
, ce qui écrasera les anciennes.
Après cette tache initiale, il y a deux événements possibles :
Besoin d'une nouvelle colonne
Traducteur
- Au moment de traduire un diff de phrase ou de clause, il apparaît qu'il manque une ou plusieurs nouvelles colonnes pour l'une des langues et un type de paramètre.
- Définir les colonnes nécessaires.
- Contacter l'éditeur pour vérifier qu'aucune mise à jour de feuille n'est en attente. Si oui, appliquer d'abord le workflow pour le nouveau contenu de feuilles.
- Ajouter les nouvelles colonnes et les remplir dans les feuilles / langues concernées.
- Envoyer les feuilles à l'éditeur.
Editeur
- Déposer les nouvelles feuilles dans le répertoire translated, ce qui écrasera les anciennes.
Nouveau contenu de feuilles
Editeur
- Le nouveau contenu de jeu est à intégrer dans le jeu.
- Contacter le traducteur pour vérifier qu'aucune mise à jour de feuilles n'est en cours. Si oui, appliquer d'abord le workflow pour besoin d'une nouvelle colonne.
- Créer de nouvelles feuilles pour le langage de référence, et ne contenant que le nouveau contenu.
- Ajouter et remplir les colonnes par défaut pour les nouvelles feuilles (voir Taches initiales).
- Créer les nouvelles feuilles pour toutes les langues en copiant les feuilles de la langue de référence.
- Ajouter toutes les colonnes pour respecter le format de feuille en cours, pour chaque type et chaque langue, mais NE PAS LES REMPLIR.
- Envoyer les nouvelles feuilles au traducteur.
Traducteur
- Traduire les nouvelles feuilles.
- Ajouter les nouvelles feuilles à la fin des feuilles existantes.
- Envoyer le résultat de la fusion à l'éditeur.
- Conserver le résultat de la fusion pour référence pour la traduction des phrases et des clauses, et les futurs ajouts de contenu.
Editeur
- Déposer les nouvelles feuilles dans le répertoire
translated
, ce qui écrasera les anciennes.
Installer les fichiers traduits
A la fin d'un cycle de traduction, il faut installer les fichiers traduits dans les structures d'annuaire du client et du serveur.
Pour cela, on utilise la commande install_translation
.
Les fichiers <lang>.uxt
sont copiés dans la structure du client dans Ryzom/gamedev/language
.
Tous les autres fichiers sont copiés dans Ryzom/data_shard
.
Pour appliquer la traduction sur le client, l'éditeur doit faire un patch.
Pour appliquer la traduction sur le serveur, il faut juste entrer la commande reload
sur le InputOutputService
.
Travailler avec des fichiers de traduction, du point de vue du programmeur
Les programmeurs NeL/Ryzom peuvent utiliser le système de traduction avec un nombre d'appels restreint.
Accéder aux chaînes statiques du client
Pour obtenir la chaîne unicode à partir d'une chaîne d'identifiant, utiliser la classe NLMISC::CI18N
.
Tout d'abord, il faut s'assurer que les fichiers de traduction *.uxt
sont disponibles dans le chemin de recherche.
Ensuite, il est possible de charger un ensemble de chaînes de langue en appelant NLMISC::CI18N::load(languageCode)
.
Le paramètre languageCode
est le code langue tel que défini au chapitre 2 “Code langue”.
Ensuite, appeler la méthode NLMISC::CI18N::get(identifier)
pour obtenir la chaîne unicode associée à l'identifiant.
Chaînes dynamiques
Une chaîne dynamique nécessite un peu plus de travail et une infrastructure d'instance complète.
La gestion des chaînes dynamiques implique un service de requête (RQS), le InputOutputService (IOS), le FrontEnd (FE), le client ryzom, plus les services de bases pour faire tourner les autres (nommage, tick, miroir).
RQS est un service qui nécessite d'envoyer une chaîne dynamique au client.
RQS envoie également l'identifiant de chaîne dynamique au client en utilisant la base de données ou n'importe quelle autre méthode.
Le proxy est un petit morceau de code qui construit et envoie le message de PHRASE au service IOS.
IOS a la lourde charge de parser (analyser ?) les paramètres, en choisissant la bonne clause et en construisant la chaîne résultante, puis en envoyant un minimum de données au client.
Le client reçoit la phrase et demande tout élément manquant de la chaîne à l'IOS.
Construire une liste de paramètres en envoyer la chaîne
Pour accéder au proxy, il faut inclure game_share/string_manager_sender.h
et le lier avec game_share.lib
.
Il faut d'abord construire la liste des paramètres. Ce qui se fait en remplissant un vecteur de STRING_MANAGER::TParam struture
. Pour chaque paramètre, il faut définir le champ Type
, puis écrire la donnée appropriée pour le membre.
Il FAUT respecter exactement la définition des paramètres de phrase du fichier de phrases.
On peut alors appeler :
uint32 STRING_MANAGER::sendStringToClient( NLMISC::CEntityId destClientId, const std::string &phraseIdentifier, const std::vector<STRING_MANAGER::TParam> parameters
)
destClientId
est l'identifiant du client de destination, phraseIdentifier
est l'identifiant tel qu'écrit dans le fichier de phrase, parameters
est le vecteur de paramètres que vous avez construit auparavant.
Cette fonction renvoie l'ID dynamique attribué à cette phrase.
Exemple : envoyer la phrase ‘kill a creature’ (cf. § 5.2, étape 4) :
// inclure la définition du gestionnaire de chaîne du proxy #include “game_share/string_manager_sender.h” uint32 killCreatureMessage( EntityId destClient, GSPEOPLE::EPeople race, uint32 nbToKill) { std::vector<STRING_MANAGER::TParam> params; STRING_MANAGER::TParam param; // d'abord, il nous faut la race de la créature param.Type = STRING_MANAGER::creature; param.Enum = race; params.push_back(param); // ensuite, le nombre de créatures à tuer param.Type = STRING_MANAGER::integer; param.Int = nbToKill; params.push_back(param); // et maintenant, envoyer le message uint32 dynId = STRING_MANAGER::sendStringToClient( destClient, “KILL_A_CREATURE”, params); return dynId; }
Membre à remplir dans TParam en fonction du type de paramètre
- item: Remplit le SheetId avec l'identifiant de l'item
- place: Remplit l'identifiant de la chaîne avec l'identifiant de l'emplacement
- creature: Remplit EId avec l'id de l'entité créature
- skill: Remplit Enum avec la valeur enum de SKILLS::ESkills
- ecosystem: Remplit Enum avec la valeur enum de ECOSYSTEM::EEcosystem
- race: Remplit Enum avec la valeur enum de GSPEOPLE::EPeople
- brick: Remplit SheetId avec l'identifiant de la brique
- tribe: pas encore défini
- guild: pas encore défini
- player: Remplit EId avec l'identifiant du joueur
- bot: Remplit EId avec l'identifiant du bot
- integer: Remplit Int avec la valeur integer/entière (sint32)
- time: Remplit Time avec la valeur time/temporelle (uint32)
- money: Remplit Money avec la valeur money/monétaire (uint64)
- compass: pas encore défini
- dyn_string_id: Remplit StringId avec un identifiant de chaîne dynamique
- string_id: Remplit StringId avec un identifiant de chaîne
- creature_model: Remplit SheetId avec l'identifiant de la créature
- entity: Remplit EId avec l'entité de la créature, du PNJ ou du joueur
- body_part: Remplit Enum avec la valeur enum value de BODY::TBodyPart
- score: Remplit Enum avec la valeur enum de SCORES::TScores
- sphrase: Remplit SheetId avec l'identifiant de la phrase
- characteristic: Remplit Enum avec la valeur enum de CHARACTERISTICS::TCharacteristic
- damage_type: Remplit Enum avec la valeur enum de DMGTYPE::EDamageType
- bot_name: Remplit Identifier avec le nom du bot sans la fonction
- literal: Remplit Literal avec la chaîne Unicode littérale
Accéder aux chaînes dynamiques depuis le client
Côté client, accéder aux chaînes dynamiques est assez simple. Il faut juste faire attention au délai de transmission dans certains cas.
Une fois l'identifiant de chaîne dynamique récupéré d'où que ce soit (par exemple, de la base de données), il faut juste sonder (pol ?) le getDynString method
de STRING_MANAGER::CStringManagerClient
.
La méthode renvoie faux tant que1) la chaîne demandée est incomplète ou inconnue.
Même si la méthode renvoie faux, elle peut ramener un texte partiel, avec du texte de remplacement manquant.
Les chaînes dynamiques sont stockées dynamiquement, et si le code est suffisamment intelligent, il peut libérer la mémoire des chaînes dynamiques quand celles-ci ne sont plus utiles en appelant releaseDynString
.
Pour être plus efficace, on peut appeler chaque cadre2) la méthode getDynString
jusqu'à ce que celle-ci renvoie vrai, puis stocker la chaîne et appeler releaseDynString
.
STRING_MANAGER::CStringManagerClient
est basé sur un schéma unique, donc il faut appeler STRING_MANAGER::CStringManagerClient::instance()
pour obtenir un pointeur vers l'instance unique.
Voici un exemple de code simple.
// inclure la définition du gestionnaire de chaîne du proxy #include “string_manager_client.h” using namespace STRING_MANAGER; /** Une méthode qui reçoit l'identifiant de chaîne dynamique * et qui imprime la chaîne dynamique dans le log. * L'appeler à chaque frame/cadre jusqu'à valeur vraie */ bool foo(uint32 dynStringId) { ucstring result; bool ret; CStringManagerClient *smc = CStringManagerClient::instance(); ret = smc->getDynamicString(dynStringId, result) if (!ret) nlinfo(“Incomplete string : %s”, result.toString().c_str()); else { nlinfo(“Complete string : %s”, result.toString().c_str()); // libérer la chaîne dynamique smc->releaseDynString(dynStringId); } return ret; }
Guide de création de texte pour Ryzom
Il y a beaucoup de places pour le texte dans Ryzom, cette page va clarifier les conventions d'identification de texte, le contexte disponible pour l'insertion de texte et les paramètres contextuels de texte.
Conventions pour les identifiants
Identifiants de chaînes dans en.uxt
Ces identifiants sont écrits en minuscule, avec une majuscule pour chaque nouveau mot. Exemple:
unIdentifiantSimple unAutreIdentifiant
Identifiants de phrases dans phrase_en.txt
Ces identifiants sont écrits en majuscules, et les mots sont séparés par “underscore”.
Exemple:
UN_IDENTIFIANT_SIMPLE UN_AUTRE_IDENTIFIANT
Identifiants de chaînes (ou de clauses) dans clause_en.txt
Ces identifiants sont écrits comme des identifiants de chaîne dans en.uxt
.
Mais, comme ils sont à l'intérieure d'une définition de phrase, ils doivent contenir le nom des phrases comme nom de base. Le nom des phrases est en minuscule pour respecter la convention des identifiants de chaîne.
Exemple, dans une phrase appelée UNE_PHRASE_SIMPLE
, l'identifiant doit être :
unePhraseSimple
De plus, lorsqu'il y a plus d'une clause pour une phrase donnée, l'identifiant de clause doit être suivi par quelques étiquettes qui donneront un indice au traducteur sur la signification de chaque clause.
Exemple, dans une phrase nommée UNE_PHRASE_SIMPLE
et qui contient deux clauses, une pour le singulier et une pour le pluriel, nous pourrions avoir les identifiants suivants :
unePhraseSimpleS unePhraseSimpleP
Contextes des textes
Contexte “Chat”
Le contexte “Chat” englobe tous les textes qui viennent d'un PNJ via une fenêtre de chat et des bulles de texte.
Le bot dit/crie en alentours
Il n'y a qu'un paramètre disponible : l'entité PNJ qui parle/crie.
Le nom de la phrase commence par SAY_
Exemple de phrase :
SAY_XXX (bot b) { sayXxx [Bonjour ici, je m'appelle $b$, quelqu'un m'entend ?] }
Le bot parle au chef de l'escorte (un joueur)
Deux paramètres : le bot qui parle, et le joueur.
Le nom de la phrase commence par TALK_
Exemple de phrase :
TALK_XXX (bot b, player p) { talkXxx [Bonjour $p$, je m'appelle $b$, j'ai besoin d'aide !] }
Le bot parle/crie en réponse à un clic sur lui
Deux paramètres : le bot cliqué et le joueur.
Le nom de la phrase commence par CLICK_
Exemple de phrase
CLICK_XXX (bot b, player p) { clickXxx [Bonjour $p$, je m'appelle $b$, vous avez cliqué sur moi ?] }
Contexte interactif (également appelé botchat)
Le botchat couvre tous les textes dans le dialogue interactif avec un PNJ.
Phrases liées aux missions
Missions statiques
Tous les noms de phrases liées à des missions ont une racine définie par le nom de la mission telle que positionnée dans le noeud de mision de l'éditeur de monde.
A partir de cette racine, plusieurs extensions peuvent être ajoutées pour former des noms de phrase :
- _TITLE : pour le titre de la mission
- _STEP_X : pour le texte de l'étape X (les étapes de mission commencent à 1)
- _END : pour le texte de fin de mission.
Exemple:
A partir d'une mission appelée | INSTRUCTEUR_MIS_1, |
---|---|
Le titre sera | INSTRUCTEUR_MIS_1_TITLE |
Le texte de l'étape 1 de la mission sera | INSTRUCTEUR_MIS_1_STEP_1 |
Le texte de l'étape 2 de la mission sera | INSTRUCTEUR_MIS_1_STEP_2 |
Le texte de fin de mission sera | INSTRUCTEUR_MIS_1_END |
Paramètres :
- XXXXXXX_TITLE (bot b, player p)
- b est le bot à qui le joueur parle
- p est le joueur
- XXXXXXX_STEP_X (bot giver, bot current, bot target, bot previous, player p)
- giver est le donneur de la mission
- current est le bot à qui le joueur parle
- target est le bot à aller voir pour la prochaine etape
- previous est le bot vu à l'étape précédente
- p est le joueur
- XXXXX_END (bot current, bot giver, bot previous, player p)
- giver est le donneur de la mission
- current est le bot à qui le joueur parle
- previous est le bot vu à l'étape précédente
- p est le joueur
Les paramètres des textes d'étapes du journal dépendent de la nature de l'étape (voir avec Nicolas Brigand). Pour les textes de progression de mission dans le menu contextuel, il en existe deux :
- MISSION_STEP_GIVE_ITEM_CONTEXT (bot giver, bot previous, item i)
- MISSION_STEP_TALK_CONTEXT (bot giver, bot previous)
Le premier est le texte standard, le second est affiché quand on doit donner quelque chose au bot.
Contexte supplémentaire pour les entrées de menu
Il est possible d'ajouter des entrées informatives simples dans le menu contextuel du bot. Cette partie est composée de deux phrases : le nom affiché pour l'entrée de menu et le contenu de texte affiché après avoir cliqué sur l'entrée de menu.
Deux paramètres : le bot support du menu de contexte et le joueur.
Le nom de la phrase commence par BC_MENU_
pour l'entrée de menu et BC_MENUTEXT_
Messages système (info de combat)
Les paramètres dépendent des phrases, mais il y a quelques types de phrases bien définis :
- COMBAT_
- MAGIC_
- HARVEST_
- CRAFT_
- DEATH_
- PROGRESS_