This is an old revision of the document!
The System localization/translation of the Ryzom Core
General Information
There are basically “two” distinct parts for localization in Ryzom. The first part(and the simplest one) concerns the server-side “static localization”(that is, interface names, error messages). The second part is for the dynamically generated server texts.
As you can see in the diagram, there are four types of files that operate the location-system. Each of these files must come in there own “localized(Locale)” language. If you look closely, you can see(In bold text) that each file includes the “language-code” embedded in the name.
Detail about the file formats follow below:
Code Language
The languages in Ryzom are identified by their “language-code” as defined in ISO 639-1 PLUS a country code defined in ISO 3166 if necessary.
ISO 639-1 is a two-character language-code(eg 'en', or 'fr'). This is(for the most part) enough for most languages that we want to manage.
But.. there are some exceptions, as for ex the Chinese writing. The Chinese language can be written in two forms: “traditional” or “simplified”. However, there is only one language-code for Chinese: 'hz'.
Therefor we need to add a country code to indicate what form of Chinese writing we are talking about. The language-code for simplified Chinese becomes: 'hz-CN'(ie Chinese language, Country China), and for traditional Chinese, it is: 'hz', this happens because of all other Asian languages, like: (Taiwan, Hong Kong?) only uses “traditional” Chinese as the spoken language.
Definitions of identifiers
The translated strings are associated with an identifier. The identifiers are strings of text that must follow the constraint of “<wrap hi> the identifier C </ wrap>”, with a small difference.
An identifier C must contain only the following characters: A-Z , a-z , 0-9 , @ and _ .
A real identifier C can not start with a number, but an string identifier can.
Example of “GOOD” identifiers:
These are regarded as good identifiers: CeciEstUnBonIdentifiant _Ceci@est@unautreBonId 1234_est_un_bonId Ceci_est_un_Bon_1234
Example of “BAD” identifiers:
These are regarded as bad identifiers:
é#()|{[_IdPASBON
File Formats
There are three different formats for translation files. But we do only need to know two of them:
Format 1
This format is used for the static text on the client side and also for text with server-side clauses.
This file is a list of associations between identifiers and strings(also called “value chains”). The identifier MUST obey the constraint of the identifier C, and the value chain is also delimited by [ 'and' ']' '. The formatting of the text is “read;”. But you do have complete freedom when it comes to skipping “lines and indents”.
identifier1 [text value] identifier2 [The other text value]
Note: It is permitted to include C-style comments.
// This is just a comment line. Continue to the end of the line identifier1 [text value] /* this is a comment on several lines */ identifier2 /* Comment on several lines here! */ [Other text value]
Text values can be formatted for better legibility. The new “line and tabs” are deleted in the final value of the string.
identifier [value text with a new line And tabs for the readability only] identifier [Other text value]
If you want to specify new rows or tabs in a string-value, you must use the C \ t escape sequence for tabs and \ n for new lines. To write \ in a string-value, double the backslash: . To write
] in the string, precede it with a backslash : \].
identifier1 [tabulation : \This text is tabulated] identifier2 [New line \ nText on a new line] identifier3 [Backslash : \\] identifier4 [A closing hook : \] ]
For easier use and maintaining, you can split up the original file into a “series of smaller files”. This is done by using a pre-processor command in the C-programing language: “#include.
#include "path/filename.txt"
You can have any number of “include”-commands, you can even have “include”-commands in include files. The “path” can be either “absolute” or “relative” to the location of the “master file”.
Format 2
This format is used for the translation of “phrase files”. This format has a rather complicated grammar that could be described as a syntax close to LALR :
identifier: [A-Za-z0-9_@]+
phrase : identifier ‘(‘ parameterList ‘)’
‘{‘
clauseList
‘}’
parameterList : parameterList ‘,’ parameterDesc
| parameterDesc
parameterDesc : parameterType parameterName
parameterName : identifier
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 identifier textValue
| identifier textValue
| conditionList identifier
| identifier
| textValue
conditionList : conditionList condition
| condition
condition : ‘(‘ testList ‘)’
testList : testList ‘&’ test
| test
test : operand1 operator reference
operand1 : parameterName
| parameterName’.’propertyName
propertyName : identifier
operator : ‘=’
| ‘!=’
| ‘<’
| ‘<=’
| ‘>’
| ‘<=’
reference : identifier
textValue : ‘[‘ .* ‘]’
As in format 1, you can insert “C style” comments in the text, identifier with enclosure marks, and use the include command.
Format 3 : Export the Unicode Worksheet
This format is the result of a “spreadsheet export” to Unicode text. The encoding MUST be 16-bit unicode format. The columns are separated by tabs, and the lines simply by new lines.
IMPORTANT: You must NOT modify them manually, use only the worksheet for this task.
The first row will/should always contain the column names.
Information Columns
For ex: If the name of a column starts with '*', then the entire column will be ignored. This is very useful when you want to add a “column of information” that can help with the translation.
Erase characters
It is possible to insert a 'delete' command in a field: '\ d'. This is useful for translating articles.
Example: You have a string with the following replacements(in English):
Report me $ item.da $ $ item.name $ ”
And the file will contain the following names:
Item name da Hammer hammer L’ échelle l’ échelle
If the item is 'hammer', there is no problem. The replacement will give the following result:
“Report me hammer ”
But for 'l’ échelle', there is an extra space in the result:
“Report me l’ échelle ”
To remove this extra space, you can add a 'delete' marker in the article definition, like this:
item name da Hammer hammer echelle échelle l’\d
This will give a correct string, as the result shows below:
“Rapporte moi l’échelle”
Working with the translation files with the point of view from the translator
File types of "* .uxt" in the client
These files contain all the “static texts” available directly in the client. The text should follow the format 1(as described above).
There is an additional constraint to consider: you MUST provide the name of the language as the “first entry”, as it is written in the considered language(ie English for English, 'French' for French …).
For example, the en.uxt file should start with:
LanguageName [English]
The Server-side Files
The server-side translation is a bit more complicated. We will see how the server-side translations is done in four steps (We will start with the simplest to the most complicated problems, Yay!).
Step 1: A single string
To “pull this of”, you only need the sentence file.
Let's say we want a string that says “hello world!”, Identified by HelloWorld .
Let's create a phrase entry in phrase_en.txt :
HelloWorld ()
{
[Hello world!]
}
And there you go! That's all there is to it.
Of course, you will also have to provide the same sentence in all supported languages, for example in file phrase_en.txt:
HelloWorld ()
{
[Bonjour le monde!]
}
Notice that only the value of the text has changed. The sentence identifier MUST be the same in all translation files.
Step 2: Workaround for "clause_ <lang> .txt"
In the 4th step, we will see that the file-sentences can become very complex. Therefore is these kind of files not very suitable if you(for example) want to give it to a professional translator without the proficiency in complex file grammatics. Even worse, the complexity of the file may hide the translation work itself.
One can therefore separate the grammar of the sentence into sentence-files and the textual value into a file of clauses.
To do this, you must assign a unique identifier to each text value. Let's go back to the previous, above example.. with a “workaround”.
In phrase_en.txt , create the sentence entry like this:
HelloWorld ()
{
Hello
}
As you can see, we only need to put an identifier in the sentence block. This means that the phrase refers to a string identified as Hello in the clause file.
Maintenant, nous pouvons créer la valeur textuelle dans clause_en.txt :
Hello [Hello world!]
As in the first step, you will have to do this for each language.
To facilitate the translation work, it is possible to specify the string identifier AND the value identifier. This can be useful for automatically building a translation file from the original file. Exemple :
HelloWorld ()
{
Hello [Bonjour le monde!]
}
In a case like this, the translation system always looks first in the clause file, and then falls back on the text value in the sentence file ONLY if it does NOT find a string in the clause file.
The other advantage is that the person who wrote the phrase file can give a simplified version of the channel which a professional translator can improve.
The 3rd step: Using parameters - the basics
This is where we are gonna attack the complicated part!
Each sentence can receive a list of parameters.
These parameters can be of different types :
- item, (object)
- place, (emplacement)
- creature, (creature, animal)
- skill, (competence)
- ecosystem, (ecosystem)
- race, (race)
- brick, (brick)
- tribe, (tribe)
- guild, (guild)
- player, (player)
- int, (number integer)
- bot, (robot)
- time, (date)
- money, (money)
- compass, (compass)
- dyn_string_id, (Dynamic string identifier)
- string_id, (String identifier)
- creature_model, (Creature model)
- entity, (entity)
- body_part, (body parts)
- score, (score)
- sphrase, (sphrase)
- characteristic, (characteristic)
- damage_type, (damage type)
- bot_name, (robot name)
- literal. (literaly)
Each parameter receives a name (or id) from its declaration. We call it paramName.
Each type of parameter MAY be associated with a 'word' file. This file is an excel sheet (in unicode text export format) containing the translation of the parameter: its name, an indefinite or defined article ( 'a', 'le' …), the plural of the name and The article, and any useful property or grammatical element necessary for translation.
The first column is very important because it associates a data line with a particular value of the parameter.
Let's start with an example: we want to build a dynamic sentence with a variable of species name of the creature.
First, we need to create an excel sheet that defines the words for the species of the creature. This will be saved as race_words_ <lang> .txt in exporting unicode text from excel. As always, you will need to provide a version of this file for each language.
NB: The first column MUST always be the association field and you will need to have a name column as it is the default value for the parameters. Any other column is optional and can vary from one language to another to match the various specific grammatical constraints.
Here is an example of race_words_en.txt :
<Code> race name ia da p pia pda
Kitifly Kitifly has the Kitiflys the
Varynx in the Varynx
Etc … </ code>
As stated above, the first column gives the identifier of the race, as defined in the game dev sheets. The second column is the 'highly recommended' column to store the name of the breed. The column 'p' is the plural name. 'Ia' and 'da' indicate the indefinite and defined items.
Next, create a sentence with a creature parameter in sentence_ <lang> .txt :
KILL_A_CREATURE (race crea)
{}
As you can see, after the phrase identifier KILL_THIS_CREATURE , we have the list of parameters in parentheses. We declare a race parameter named crea . Note that you freely choose the name of your parameter, but each parameter must have a unique name (at least for the sentence).
Now we can build the string value. To insert the parameter in the string, we must specify the replacement point using the $ sign (ie $ crea $ ) directly in the string value:
KILL_A_CREATURE (race crea)
{
[Would you please kill a $crea$ for me ?]
}
As you can see, it's not too complicated. $ Crea $ will be replaced by the field content from the word file in the name column and the row corresponding to the race identifier.
It is possible to recall any column of the word file in the value chain. For example, we can dynamize the indefinite article:
KILL_A_CREATURE (race crea)
{
[Would you please kill $crea.ia$ $crea$ for me ?]
}
Some types of parameters have special replacement rules: int are replaced by their text representation, time are converted to a ryzomian date, as are money .
Finally, last but not least, the rules for identifiers and workarounds seen in steps 1 and 2 that are still valid.
Step 4: Using Parameters - Conditional Clause
It is now time to unveil the conditional clause system.
Let's say that the identifier and the string value that we used in a sentence in the previous step are a clause. And let's even say that a sentence can contain more than one clause, which can be chosen by the translation engine on the fly according to the value of the parameter. This is what the conditional clause system is all about!
Let's start with a first example. As for step 3, we want to kill a creature, but this time we add a variable for the number of animals to kill, from 0 to n. What we need is the condition from which to choose between the three clauses: no creature to kill, one creature to kill, or several.
First, let us write the sentence, its parameters and its three clauses:
KILL_A_CREATURE (race crea, int count)
{
// no creature to kill (No creature to kill)
[There is no creature to kill today.]
// 1 creature to kill (1 creature to kill)
[Would you please kill a $crea$ for me ?]
// more than one (More than one)
[Would you please kill $count$ $crea$ for me ?]
}
We have written three versions of the text with a very different meaning and grammatical structure. Now let's add the conditions. The conditions are placed before the identifier and / or the value chain, and are indicated by parentheses.
KILL_A_CREATURE (race crea, int count)
{
// no creature to kill (No creature to kill)
(count = 0) [There is no creature to kill today.]
// 1 creature to kill (1 creature to kill)
(count = 1) [Would you please kill a $crea$ for me ?]
// more than one (More than one)
(count > 1) [Would you please kill $count$ $crea$ for me ?]
}
Easy, right?
Now let's take a more complicated case: Let's say we want to write a different sentence depending on whether the player is male or female. This is a perfect opportunity to introduce the self parameter. The Self parameter is a hidden parameter, because it is always available and represents the recipient of the sentence(the one to whom it is addressed).
The Self parameter carries the gender and name properties. You can create a self_words_ <lang> .txt file to handle special cases (an admin player with a translatable name, for example).
Rewrite the query to kill animals taking into account the kind of player:
KILL_A_CREATURE (race crea, int count)
{
// -- Male Player
// No creature to kill, male player
(count = 0 & self.gender = Male)
[Hi man, there is no creature to kill today .]
// 1 creature to kill, male player
(count = 1 & self.gender = Male)
[Hi man, would you please kill a $crea$ for me ?]
// More than one, male player
(count > 1 & self.gender = Male)
[Hi man, Would you please kill $count$ $crea$ for me ?]
// -- Female Player
// No creature to kill, female player
(count = 0 & self.gender = Female)
[Hi girl, There is no creature to kill today.]
// 1 creature to kill, female player
(count = 1 & self.gender = Female)
[Hi girl, Would you please kill a $crea$ for me ?]
// More than one, female player
(count > 1 & self.gender = Female)
[Hi girl, Would you please kill $count$ $crea$ for me ?]
}
As you can see, we now have six clauses. Three for the number of creatures, multiplied by the two genres of the player.
As you can see, conditional tests can be combined with the & character. This means that all tests must be valid to select the clause.
You can use any parameter as the left operator for the test. You can also specify a parameter property (from the word file) as an operand.
On the other hand, the right-hand operand of the test must be a constant value (textual or numerical). The available operators are:
= != < <= > >=
In some cases, you may need to do combinations of tests with OR (or). This is possible simply by indicating a multiple condition list before a clause:
FOO_PHRASE (int c1, int c2)
{
(c1 = 0 & c2 = 10)
(c1 = 10 & c2 = 0)
[One passe in this clause if :
c1 Equal to zero and c2 equal ten
or
c1 Equal ten and c2 equal zero]
}
Detailed rules for selecting clauses:
- A valid clause is a clause where the conditional combinations are true for a given set of parameter values.
- Clauses are evaluated in the order in which they are written in the sentence file. The first valid clause will be retained.
- If the first clause has no condition, it will be used as the default clause if no conditional clause has been chosen.
- If there is no default clause, and none of the clauses are retained, then this is the first clause that is chosen.
- For the same sentence, each language can provide its own set of conditions and clauses to adapt to its grammatical needs.
List of properties of hard-coded parameters
Here you will find an exhaustive list of the properties of the hard-coded parameters. These properties are always available even when no word file has been provided. In addition, hard-coded properties can not be replaced by a name file that would use a column with the same name.
| Parameter | Property |
|---|---|
| Item | |
| Place | |
| Creature | “name : Name of the model for the creature |
| gender : Creature type (drawn?) From the model” | |
| 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.txtUn 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épertoirework', remplir la première chaîne nécessaireLanguageNamepuis 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 fichiersdiffpour 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>.uxtpour chaque langue dans le répertoiretranslatedet 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.uxtdans 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 fichierssdiffaux fichiers<lang>.uxtpour chaque langue dans le dossiertranslatedet 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.uxtdans le répertoireworket 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>.txtdans le répertoiretranslatedpour le fichierdifftraduit, 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>.txtpour chaque langue dans le répertoiretranslatedet déplacera lesdiffdans 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.uxtdans 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>.txtrespectifs dans le répertoiretranslatedpuis 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>.txtrespectifs pour chaque langue dans le répertoiretranslatedet déplacera les diff dans le répertoirehistory.
Fichiers de mot côté serveur
<fs x-large>Cette partie est obsolète.</fs>
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
nameetp. - 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_






