====== 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. {{ :fr:rc_loc.jpg |}} 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 [[wp>List_of_ISO_639-1_codes|ISO 639-1]] plus un code pays défini en [[wp>ISO_3166|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_.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_.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_.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_.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 traduction * ''languages.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 --- //[[user:yannk|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écessaire ''LanguageName'' 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 fichiers ''diff'' pour tous les langages dans le répertoire ''diff''. * 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 ''.uxt'' pour chaque langue dans le répertoire ''translated'' et déplacera le diff dans le dossier ''history''. 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épertoire ''work''. * 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 dossier ''diff''. * 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 fichiers ''sdiff'' aux fichiers ''.uxt'' pour chaque langue dans le dossier ''translated'' et déplacera les diff dans le dossier ''history''. ==== Chaînes dynamiques sur le serveur ==== === Tâche initiale === **Editeur** * Créer le fichier de référence ''phrase_wk.uxt'' dans le répertoire ''work'' 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 fichiers ''phrase_.txt'' dans le répertoire ''translated'' pour le fichier ''diff'' traduit, puis déplacera les fichiers diff dans le répertoire ''history''. * 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 fichiers ''clause_.txt'' pour chaque langue dans le répertoire ''translated'' et déplacera les ''diff'' dans le répertoire ''history''. 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 dossier ''work''. * 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 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 fichiers ''phrase_.txt'' respectifs dans le répertoire ''translated'' puis déplacera les fichiers diff dans le répertoire ''history''. * 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épertoire ''diff''. * 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 fichiers ''clause_.txt'' respectifs pour chaque langue dans le répertoire ''translated'' et déplacera les diff dans le répertoire ''history''. ==== Fichiers de mot côté serveur ==== Cette partie est obsolète. 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 ) 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'' et ''p''. * 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 ''.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). {{ :fr:rc_loc2.jpg |}} 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 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 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 que((N.d.T. : le texte d'origine dit “jusqu'à ce que”, “until”)) 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 cadre(((NDT : each frame ?)) 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_   {{tag>traduction Ryzom_Core Serveur Client Tutoriel client_rc_obsolete Obsolète}}