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:
“Report me 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.
Now we can create the text value in 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.
Property-list 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 : Player name |
gender : Kind of the player in the mirror” | |
Bot | “career : Career of bot |
role : Role of the bot as defined in creature.basics.chatProfile | |
name : Name of the creature | |
gender : Creature type (drawn?) From the model” | |
Integer | |
Time | |
Money | |
Compass | |
dyn_string_id | Only tests != and == Are possible. Essentially to compare a parameter with 0. |
string_id | Only tests != and == Are possible. Essentially to compare a parameter with 0. |
self | “name : Player name |
gender : Kind of the player in the mirror” | |
creature_model | NB : Use the creature_words translation file ! |
entity | “== 0, != 0 : Tests whether the entity is Unknown (unknown) or not |
name : Name of the creature or name of player | |
gender : Creature type (drawn?) Of the player's model or gender (based on player info)” | |
bodypart | |
score | |
sphrase | |
characteristic | |
damage_type | |
bot_name |
Translation Workflow
In the translation workflow below, we consider that the reference language is English.
There is a series of tools and beats files to help synchronize translations. Here is a step-by-step description of how to work on a translation file, which tool to use and when.
Only additions to existing translation files are supported by the translation tools. If you want to edit or delete an existing translation string or phrase, it will be done 'by hand' with maximum attention in all language versions.
In most cases, it is better to create a new translation entry, rather than manage a change in all translation files, and it is almost safe to leave the old unused strings in the files.
In any case, you must NEVER make changes when there are pending diff files.
It is strongly recommended that you strictly follow the described workflow to avoid translation problems, missing strings, or other bizarre problems that might occur when working with multiple language versions of a set of files.
The translation work is done in collaboration between the publisher, who is the 'technical' part of the work, and a professional translator subcontracts that translates only simple strings into rich chains and of very high quality while respecting the context.
The tool that generates the diff file for translation keeps the comments of the reference version. This can be useful to provide additional information to the translator about the context of the text.
In addition, for the sentences file, the diff file automatically includes comments that describe the parameter list.
By default it seems that the system works with ISO-8859-15 files and not UTF8 so if you get strange results with commands, it's probably due to a UTF8 encoding of your file.
Structure of stored translation
All files to be translated are stored in a well-defined directory structure called translation warehouse
. All translation work is done in this directory.
Tools are provided to install the translated file in the client and the server directory after the end of the translation cycle.
translation/
Root directory for translationlanguages.txt
A simple text file that contains all the language codes (ISO 639-2) with which we work (for example: en, fr, etc …).work/
This is the starting point for adding new content. This directory contains all the files that can be modified to add.diff/
Contains all the diff files generated by the tools. That is where most of the work is done. After the merge operation that integrates the translated diff into the translation files, the diff files are moved to the history directory. When diff files are generated, they are prefixed with a version number automatically calculated from the current Unix timestamp. This allows you to generate new diff files without waiting for the diff to be translated. In addition, when you generate a new diff file without a merged diff file, the new diff only contains the differences.history/
Contains all diff files that have been translated and merged. Used for backup and security reasons.translated/
Contains all translated files. The contents of this directory are installed on the client and server warehouses when the translation cycle is complete. You should never change anything by hand in this directory unless you know exactly what you are doing.
Name translation of bots
The command extract_bot_names
parses the file bot_names.txt
according to the Primitives and if some names are used several times in it, it must transform the name of the bot into a generic name of the type gn_genericname
Whose match is created in the corresponding title_words
.
Test to perform cf HexChatlog kerv30/12/2015 - 14:30 — Yann K 2015/12/30 14:42
Static string on the client
Initial task
Editor :
- Create the reference file
en.uxt
in thework
directory, fill in the first necessary stringLanguageName
and add all strings for the English version. - Generate diff files from static strings with the command
make_string_diff
. This will creatediff
files for all languages in the' 'diff' 'directory. - Send diff files to the translator.
Translator:
- Translate diff files.
- Return them to the editor.
Editor:
- Put the translated diff files in the
diff
directory(which will overwrite the untranslated diff files and replace them with the translated files). - Merge diff files translated using the
merge_string_diff
command. This will create the<lang> .uxt
files for each language in thetranslated
directory and will move the diff to thehistory
folder.
After the initial task is completed, the workflow enters incremental mode.
Incremental spot
Editor
* Add strings to the reference file en.uxt
in the work
directory.
* Generate diff files from static strings with the command make_string_diff
. This will create the diff files for each language in the diff
folder.
* Send diff files to the translator.
Translator
- Translate diff files.
- Return them to the editor.
Editor
- Deposit the translated diff files to the
diff
directory(which will overwrite the untranslated diff files and replace them with the translated files). - Merge diff files with the command
merge_string_diff
. This will apply thesdiff
files to thelang '.uxt
files for each language in thetranslated
folder and will move the diff into thehistory
folder.
Dynamic strings on the server
Initial task
Editor
* Create the reference file phrase_wk.uxt
in the work
directory and add all sentence entries for the English version.
- Generate diff phrase files with the command
make_phrase_diff
. This will create the diff files for each language in the diff directory. - Translate sentences file diff. This implies good knowledge of the grammatical structure of each language to adapt to the rules of the selection clauses.
- Merge the translated diff file with the command
merge_phrase_diff
. This will create all the filesphrase_ <lang>.txt
in thetranslated
directory for the translated diff file, and then move the diff files to thehistory
directory. - Generate the diff clauses file with the command
make_clause_diff
. This will create the diff clauses file in the diff directory for all languages. - Send the diff clauses files to the translator.
Translator
- Translate clauses files diff.
- Return them to the editor.
Editor
* Deposit translated diff files in the diff
directory(which will overwrite the untranslated diff clause files and replace them with the translated files).
- Merge the diff clause files with the command
merge_clause_diff
. This will create theclause_<lang>.txt
files for each language in thetranslated
directory and move thediff
into thehistory
directory.
Once the initial task has been completed, the workflow enters incremental mode.
Incremental spot
Editor *
* Add the new sentence to phrase_wk.uxt
in the work
folder.
- Generate diff phrase files with the command
make_phrase_diff
. This will create the diff files for each language in thediff
directory. - Translate the diff file. This implies a good knowledge of the grammatical structure of each language to adapt to the rules of selection of the clauses.
- Merge diff files translated using the command
merge_phrase_diff
. This will add the diff to their respectivephrase_<lang>.txt
files in thetranslated
directory and then move the diff files to thehistory
directory. - Generate the diff clauses files with the command
make_clause_diff
. This will create the diff clause file in thediff
directory. - Send the diff clause files to the translator.
Translator
- Translate clauses files diff.
- Return them to the editor.
Editor
- Deposit translated diff files in the
diff
directory(which will overwrite the untranslated clause files and replace them with the translated files). - Merge the translated clause files with the command
merge_clause_diff
. This will add the diff files to their respectiveclause_lang.txt
files for each language in thetranslated
directory and will move the diff to thehistory
directory.
Server-side word files
We must now also use tools, as for others:
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 FOLLOWING TWO LINES WHEN TRANSLATION IS DONE and DIFF NOT TRANSLATED 6. Save files 7. Launch 6_merge_words_diff to merge your translations in “translated”
NB: 'word' (N.d.T. 'words', in the original text) has no relation to the Microsoft Word program!
Word files are always updated manually because they are rarely updated by the editor(and usually only for large updates). In addition, when new sentences are translated, it may be necessary to create and fill a new column in one of the word files to fit the translation.
So there's just a workflow, but no tools.
Initial tasks
Editor * Create the initial sheet for each parameter type with all possible identifiers in the reference language in the translated directory.
- Create and fill in the default columns
name
andp
. - Create sheets for all languages by copying the sheets of the reference language.
- Create and populate all language-dependent database columns.
- Send all sheets to the translator.
Translator
- Translate all sheets in all languages, possibly adding all necessary columns.
- Return the translated sheets to the editor.
- Keep a copy of the translated sheets as a reference for translating sentences and clauses.
Editor
- Place the translated sheets in the
translated
directory, which will overwrite the old ones.
After this initial task, there are two possible events:
Need a new column
Translator
- When translating a sentence or clause diff, it appears that one or more new columns are missing for one of the languages and one parameter type.
- Define required columns.
- Contact the publisher to verify that no sheet updates are pending. If so, first apply the workflow for the new sheet content .
- Add the new columns and fill them in the relevant sheets/languages.
- Send the sheets to the editor.
Editor
- Place the new sheets in the translated directory, which will overwrite the old ones.
New sheet contents
Editor
- The new game content is to be integrated into the game.
- Contact the translator to verify that no sheet updates are in progress. If so, first apply the workflow to need a new column.
- Create new sheets for the reference language, and containing only the new content.
- Add and populate the default columns for new sheets(see Initial Tasks).
- Create new sheets for all languages by copying the sheets of the reference language.
- Add all columns to respect the current sheet format, for each type and language, but DO NOT COMPLETE THEM.
- Send the new sheets to the translator.
Translator
- Translate the new sheets.
- Add new sheets to the end of existing sheets.
- Send the merge result to the editor.
- Keep the result of the merge for reference for the translation of sentences and clauses, and future additions of content.
Editor
- Place new sheets in the
translated
directory, which will overwrite the old ones.
Install translated filess
At the end of a translation cycle, the translated files must be installed in the client and server directory structures.
To do this, we use the command install_translation
.
The <lang>.uxt
files are copied into the client structure in Ryzom/gamedev/language
.
All other files are copied to Ryzom/data_shard
.
To apply the translation to the client, the publisher must make a patch.
To apply the translation to the server, just enter the reload
command on the InputOutputService
.
Working with translation files, from the point of view of the programr
NeL / Ryzom programmers can use the translation system with a restricted number of calls.
Accessing static client chains
To get the unicode string from an identifier string, use the class NLMISC::CI18N
.
First, make sure that the translation files * .uxt
are available in the search path.
Then, it is possible to load a set of language strings by calling NLMISC :: CI18N :: load (languageCode)
.
The languageCode
parameter is the language code as defined in Chapter 2 'Language code'.
Then, call the method NLMISC::CI18N::get(identifier)
to get the unicode string associated with the identifier.
Dynamic Chains
A dynamic string requires a little more work and a complete instance infrastructure.
Dynamic String Management involves a query service (RQS), the InputOutputService (IOS), the FrontEnd (FE), the ryzom client, plus the basic services to run The others(naming, tick, mirror).
RQS is a service that requires sending a dynamic string to the client.
RQS also sends the dynamic string identifier to the client using the database or any other method.
The proxy is a small piece of code that builds and sends the PHRASE message to the IOS service.
IOS has the heavy burden of parsing(parsing) the parameters, choosing the right clause and constructing the resulting string, and then sending a minimum amount of data to the client.
The client receives the phrase and requests any missing element from the string to the IOS.
Build a list of string sending parameters
To access the proxy, you must include game_share/string_manager_sender.h
and link it with game_share.lib
.
The list of parameters must first be constructed. This is done by filling a vector of STRING_MANAGER::TParam struture
. For each parameter, define the “Type” field and write the appropriate data for the member.
You must respect exactly the definition of the sentence parameters of the sentence file.
We can then call:
uint32 STRING_MANAGER::sendStringToClient( NLMISC::CEntityId destClientId, const std::string &phraseIdentifier, const std::vector<STRING_MANAGER::TParam> parameters
)
DestClientId
is the identifier of the destination client, phraseIdentifier
is the identifier as written in the phrase file, parameters
is the parameter vector you built before.
This function returns the dynamic ID assigned to this phrase.
Example: send the phrase 'kill a creature'(see § 5.2, step 4):
<Code> include the definition of the proxy string manager #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; First, we need the race of the creature param.Type = STRING_MANAGER::creature; param.Enum = race; params.push_back(param);
Then the number of creatures to kill param.Type = STRING_MANAGER::integer; param.Int = nbToKill; params.push_back(param); And now send the message uint32 dynId = STRING_MANAGER::sendStringToClient( destClient, “KILL_A_CREATURE”, params);
return dynId; }</code>
Member to fill in TParam depending on the parameter type
- Item: Fills the SheetId with the identifier of the item
- Place: Fills the identifier of the string with the identifier of the location
- Creature: Fills EId with the id of the creature entity
- Skill: Fills Enum with the enum value of SKILLS::ESkills
- Ecosystem: Fills Enum with the enum value of ECOSYSTEM::EEcosystem
- Race: Fills Enum with the enum value of GSPEOPLE::EPeople
- Brick: Fills SheetId with brick ID
- Tribe: not defined yet
- Guild: not yet defined
- Player: Fills EId with player ID
- Bot: Fills EId with the identifier of the bot
- Integer: Fills Int with the value integer/integer(sint32)
- Time: Fills time with time/time(uint32)
- Money: Fills money with money/monetary value(uint64)
* Compass: not yet defined
- Dyn_string_id: Fills StringId with a dynamic string identifier
- String_id: Fills StringId with string identifier
- Creature_model: Fills SheetId with credential ID
- Entity: Fills EId with the entity of the creature, NPC or player
- Body_part: Fills Enum with the enum value of BODY::TBodyPart
- Score: Fills Enum with the enum value of SCORES::TScores
- Sphrase: Fills SheetId with the phrase id
- Characteristic: Fills Enum with the enum value of CHARACTERISTICS::TCharacteristic
- Damage_type: Fills Enum with the enum value of DMGTYPE::EDamageType
- Bot_name: Fill in Identifier with the name of the bot without the function
- Literal: Fills Literal with the Unicode string literal
Accessing dynamic strings from the client
On the client side, accessing dynamic strings is quite simple. You just have to watch out for the delay in some cases.
Once the dynamic string identifier is retrieved from anywhere(for example, from the database), you just have to probe(pol?) The getDynString method
of STRING_MANAGER::CStringManagerClient
.
The method returns false as long as 1) the requested string is incomplete or unknown.
Even if the method returns false, it can bring back a partial text, with missing replacement text.
Dynamic strings are dynamically stored, and if the code is smart enough, it can free memory from dynamic strings when they are no longer needed by calling releaseDynString
.
To be more efficient, it is possible to call each frame() the getDynString method until it returns true, then store the string and call releaseDynString
.
STRING_MANAGER::CStringManagerClient
is based on a single scheme, so you need to call STRING_MANAGER::CStringManagerClient::instance()
to get a pointer to the single instance.
Voici un exemple de code simple.
// Include the definition of the proxy string manager #include “string_manager_client.h” using namespace STRING_MANAGER; / ** A method that receives the dynamic string identifier * And prints the dynamic string in the log. * Call it to each frame / frame until true value * / 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; }
Text creation guide for Ryzom
There are plenty of places for text in Ryzom, this page will clarify the text identification conventions, the context available for text insertion and contextual text settings.
Conventions for identifiers
String identifiers in en.uxt
These identifiers are written in lowercase, with a capital letter for each new word. Exemple:
unIdentifierSimple unOtherIdentifier
Identifiers of sentences in phrase_en.txt
These identifiers are written in uppercase, and the words are separated by “underscore”.
Example:
UN_IDENTIFIER_SIMPLE UN_OTHER_IDENTIFIER
Identifiers of strings(or clauses) in clause_en.txt
These identifiers are written as string identifiers in en.uxt
.
But, as they are inside a sentence definition, they must contain the name of the phrases as the base name. The name of the sentences is in lower case to respect the convention of the string identifiers.
Example, in a sentence called AN_PHRASE_SIMPLE
, the identifier must be:
anPhraseSimple
In addition, when there is more than one clause for a given sentence, the clause identifier must be followed by a few labels that will give the translator a clue as to the meaning of each clause.
Example, in a sentence named AN_PHRASE_SIMPLE
and containing two clauses, one for the singular and one for the plural, we could have the following identifiers:
anPhraseSimpleS anPhraseSimpleP
Contexts of the texts
Context "Chat"
The “Chat” context encompasses all texts that come from an NPC via a chat window and text bubbles.
The bot says / shouts in the neighborhood
There is only one parameter available: the NPC entity that speaks/cries.
The sentence name starts with SAY_
Sample sentence:
SAY_XXX (bot b) { sayXxx [Hello here, my name is $ b $, someone hears me ?] }
The bot talks to the escort leader (one player)
Two parameters: the talking bot, and the player.
The sentence name starts with TALK_
Sample sentence:
TALK_XXX (bot b, player p) { talkXxx [Hi $ p $, my name is $ b $, I need help !] }
The bot speaks/cries in response to a "click" on him
Two parameters: the bot clicked and the player.
The sentence name starts with CLICK_
Sample sentence
CLICK_XXX (bot b, player p) { clickXxx [Hi $ p $, my name is $ b $, you clicked on me ?] }
Interactive context (also called botchat)
The botchat covers all the texts in the interactive dialogue with an NPC.
Sentences related to missions
Static missions
All mission-related sentence names have a root defined by the name of the mission as positioned in the mision node of the world editor.
From this root, several extensions can be added to form sentence names:
- _TITLE: for the title of the mission
- _STEP_X: for the text of step X(the mission steps start at 1)
- _END: for the end-of-mission text.
Exemple:
From a mission called | INSTRUCTOR_MIS_1 |
---|---|
The title will be | INSTRUCTOR_MIS_1_TITLE |
The text of Stage 1 of the mission will be | INSTRUCTOR_MIS_1_STEP_1 |
The text of Stage 2 of the Mission will be | INSTRUCTOR_MIS_1_STEP_2 |
The end-of-mission text will be | INSTRUCTOR_MIS_1_END |
Settings :
- XXXXXXX_TITLE (bot b, player p)
- B is the bot to which the player speaks
- P is the player
- XXXXXXX_STEP_X (bot giver, bot current, bot target, bot previous, player p)
- Giver is the donor of the mission
- Current is the bot to which the player speaks
- Target is the bot to go for the next step
- Previous is the bot seen in the previous step
- P is the player
- XXXXX_END (bot current, bot giver, bot previous, player p)
- Giver is the donor of the mission
- Current is the bot to which the player speaks
- Previous is the bot seen in the previous step
- P is the player
The parameters of the log's step texts depend on the nature of the stage(see Nicolas Brigand).
For mission progress texts in the context menu, there are two:
- MISSION_STEP_GIVE_ITEM_CONTEXT(bot giver, bot previous, item i)
- MISSION_STEP_TALK_CONTEXT(bot giver, bot previous)
The first is the standard text, the second is displayed when you have to give something to the bot.
Additional context for menu entries
It is possible to add informative simple entries in the context menu of the bot. This part consists of two sentences: the name displayed for the menu entry and the text content displayed after clicking on the menu entry.
Two parameters: the context menu bot bot and the player.
The sentence name starts with BC_MENU_
for the menu entry and BC_MENUTEXT_
System Messages(combat info)
The parameters depend on the sentences, but there are a few well defined types of sentences:
- COMBAT_
- MAGIC_
- HARVEST_
- CRAFT_
- DEATH_
- PROGRESS_