Add translator documentation

This commit is contained in:
Nolway 2021-12-08 20:12:18 +01:00 committed by Alexis Faizeau
parent 8a2767ef40
commit 77f8426788
3 changed files with 180 additions and 4 deletions

View file

@ -36,6 +36,23 @@ WA.onInit().then(() => {
}) })
``` ```
### Get the player language
```
WA.player.language: string;
```
The current language of player is available from the `WA.player.language` property.
{.alert.alert-info}
You need to wait for the end of the initialization before accessing `WA.player.language`
```typescript
WA.onInit().then(() => {
console.log('Player language: ', WA.player.language);
})
```
### Get the tags of the player ### Get the tags of the player
``` ```

View file

@ -10,6 +10,10 @@ type LanguageObject = {
[key: string]: string | LanguageObject; [key: string]: string | LanguageObject;
}; };
type TranslationParams = {
[key: string]: string | number
};
class Translator { class Translator {
public readonly fallbackLanguage: Language = this.getLanguageByString(FALLBACK_LANGUAGE) || { public readonly fallbackLanguage: Language = this.getLanguageByString(FALLBACK_LANGUAGE) || {
language: "en", language: "en",
@ -18,7 +22,14 @@ class Translator {
private readonly fallbackLanguageObject: LanguageObject = FALLBACK_LANGUAGE_OBJECT as LanguageObject; private readonly fallbackLanguageObject: LanguageObject = FALLBACK_LANGUAGE_OBJECT as LanguageObject;
/**
* Current language
*/
private currentLanguage: Language; private currentLanguage: Language;
/**
* Contain all translation keys of current language
*/
private currentLanguageObject: LanguageObject; private currentLanguageObject: LanguageObject;
public constructor() { public constructor() {
@ -28,6 +39,11 @@ class Translator {
this.defineCurrentLanguage(); this.defineCurrentLanguage();
} }
/**
* Get language object from string who respect the RFC 5646
* @param {string} languageString RFC 5646 formatted string
* @returns {Language|undefined} Language object who represent the languageString
*/
public getLanguageByString(languageString: string): Language | undefined { public getLanguageByString(languageString: string): Language | undefined {
const parts = languageString.split("-"); const parts = languageString.split("-");
if (parts.length !== 2 || parts[0].length !== 2 || parts[1].length !== 2) { if (parts.length !== 2 || parts[0].length !== 2 || parts[1].length !== 2) {
@ -41,10 +57,19 @@ class Translator {
}; };
} }
/**
* Get a string who respect the RFC 5646 by a language object
* @param {Language} language A language object
* @returns {string|undefined} String who represent the language object
*/
public getStringByLanguage(language: Language): string | undefined { public getStringByLanguage(language: Language): string | undefined {
return `${language.language}-${language.country}`; return `${language.language}-${language.country}`;
} }
/**
* Add the current language file loading into Phaser loader queue
* @param {Phaser.Loader.LoaderPlugin} pluginLoader Phaser LoaderPLugin
*/
public loadCurrentLanguageFile(pluginLoader: Phaser.Loader.LoaderPlugin) { public loadCurrentLanguageFile(pluginLoader: Phaser.Loader.LoaderPlugin) {
const languageString = this.getStringByLanguage(this.currentLanguage); const languageString = this.getStringByLanguage(this.currentLanguage);
pluginLoader.json({ pluginLoader.json({
@ -53,6 +78,11 @@ class Translator {
}); });
} }
/**
* Get from the Phase cache the current language object and promise to load it
* @param {Phaser.Cache.CacheManager} cacheManager Phaser CacheManager
* @returns {Promise<void>} Load current language promise
*/
public loadCurrentLanguageObject(cacheManager: Phaser.Cache.CacheManager): Promise<void> { public loadCurrentLanguageObject(cacheManager: Phaser.Cache.CacheManager): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const languageObject: Object = cacheManager.json.get( const languageObject: Object = cacheManager.json.get(
@ -68,6 +98,11 @@ class Translator {
}); });
} }
/**
* Get the language for RFC 5646 2 char string from availables languages
* @param {string} languageString Language RFC 5646 string
* @returns {Language|undefined} Language object who represent the languageString
*/
public getLanguageWithoutCountry(languageString: string): Language | undefined { public getLanguageWithoutCountry(languageString: string): Language | undefined {
if (languageString.length !== 2) { if (languageString.length !== 2) {
return undefined; return undefined;
@ -87,6 +122,9 @@ class Translator {
return languageFound; return languageFound;
} }
/**
* Define the current language by the navigator or a cookie
*/
private defineCurrentLanguage() { private defineCurrentLanguage() {
const navigatorLanguage: string | undefined = navigator.language; const navigatorLanguage: string | undefined = navigator.language;
const cookieLanguage = getCookie("language"); const cookieLanguage = getCookie("language");
@ -116,8 +154,14 @@ class Translator {
this.currentLanguage = currentLanguage; this.currentLanguage = currentLanguage;
} }
private getObjectValueByPath(path: string, object: LanguageObject): string | undefined { /**
const paths = path.split("."); * Get value on object by property path
* @param {string} key Translation key
* @param {LanguageObject} object Language object
* @returns {string|undefined} Found translation by path
*/
private getObjectValueByPath(key: string, object: LanguageObject): string | undefined {
const paths = key.split(".");
let currentValue: LanguageObject | string = object; let currentValue: LanguageObject | string = object;
for (const path of paths) { for (const path of paths) {
@ -135,7 +179,13 @@ class Translator {
return currentValue; return currentValue;
} }
private formatStringWithParams(string: string, params: { [key: string]: string | number }): string { /**
* Replace {{ }} tags on a string by the params values
* @param {string} string Translation string
* @param {{ [key: string]: string | number }} params Tags to replace by value
* @returns {string} Formatted string
*/
private formatStringWithParams(string: string, params: TranslationParams): string {
let formattedString = string; let formattedString = string;
for (const param in params) { for (const param in params) {
@ -146,7 +196,13 @@ class Translator {
return formattedString; return formattedString;
} }
public _(key: string, params?: { [key: string]: string | number }): string { /**
* Get translation by a key and formatted with params by {{ }} tag
* @param {string} key Translation key
* @param {{ [key: string]: string | number }} params Tags to replace by value
* @returns {string} Translation formatted
*/
public _(key: string, params?: TranslationParams): string {
const currentLanguageValue = this.getObjectValueByPath(key, this.currentLanguageObject); const currentLanguageValue = this.getObjectValueByPath(key, this.currentLanguageObject);
if (currentLanguageValue) { if (currentLanguageValue) {

View file

@ -0,0 +1,103 @@
# How to translate WorkAdventure
## How the translation files work
In the translations folder, all json files of the form [namespace].[language code].json are interpreted to create languages by the language code in the file name.
The only mandatory file that is the entry point is the index.[language code].json which must contain the properties language, country and default so that the language can be taken into account.
Example:
```json
{
"language": "English", # Language that will be used
"country": "United States", # Country specification (e.g. British English and American English are not the same thing)
"default": true # In some cases WorkAdventure only knows the language, not the country language of the browser. In this case it takes the language with the default property at true.
}
```
It does not matter where the file is placed in this folder. However, we have set up the following architecture in order to have a simple reading:
- translations/
- [language code]/
- [namepace].[language code].json
Example:
- translations/
- en-US/
- index.en-US.json
- main-menu.en-US.json
- chat.en-US.json
- fr-FR/
- index.fr-FR.json
- main-menu.fr-FR.json
- chat.fr-FR.json
## Add a new language
It is very easy to add a new language!
First, in the translations folder create a new folder with the language code as name (the language code according to [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646)).
In the previously created folder, add a file named as index.[language code].json with the following content:
```json
{
"language": "Language Name",
"country": "Country Name",
"default": true
}
```
See the section above (*How the translation files work*) for more information on how this works.
BE CAREFUL if your language has variants by country don't forget to give attention to the default language used. If several languages are default the first one in the list will be used.
## Add a new key
### Add a simple key
The keys are searched by a path through the properties of the sub-objects and it is therefore advisable to write your translation as a JavaScript object.
Please use [kebab-case](https://en.wikipedia.org/wiki/Letter_case#Kebab_case) to name your keys!
Example:
```json
{
"messages": {
"coffe-machine":{
"start": "Coffe machine has been started!"
}
}
}
```
In the code you can use it like this:
```js
translator._('messages.coffe-machine.start');
```
### Add a key with parameters
You can also use parameters to make the translation dynamic.
Use the tag {{ [parameter name] }} to apply your parameters in the translations
Example:
```json
{
"messages": {
"coffe-machine":{
"player-start": "{{ playerName }} started the coffee machine!"
}
}
}
```
In the code you can use it like this:
```js
translator._('messages.coffe-machine.player-start', {
playerName: "John"
});
```