Table des matières

Système de localisation/traduction sur Ryzom Core

inlinetoc

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 :

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 :

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.

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 :

Traducteur :

Editeur :

Une fois la tâche initiale terminée, le workflow entre en mode incrémental.

Tache incrémentale

Editeur

Traducteur

Editeur

Chaînes dynamiques sur le serveur

Tâche initiale

Editeur

Traducteur

Editeur

Une fois la tâche initiale accomplie, le workflow entre en mode incrémental.

Tache incrémentale

Editeur *

Traducteur

Editeur

Fichiers de mot côté serveur

Il faut désormais recourir à des outils également, comme pour les autres:

https://bitbucket.org/ryzom/ryzomcore/src/d69a3cd7fbd0757f7acbef68649229016b0d361c/code/ryzom/tools/translation/?at=compatibility-develop

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

Traducteur

Editeur

Après cette tache initiale, il y a deux événements possibles :

Besoin d'une nouvelle colonne

Traducteur

Editeur

Nouveau contenu de feuilles

Editeur

Traducteur

Editeur

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

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 :

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 :

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 :

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 :

 

1)
N.d.T. : le texte d'origine dit “jusqu'à ce que”, “until”
2)
(NDT : each frame ?