Translations¶
Two distinct mechanisms are involved in i18n (internationalisation):
-
translation of the values when depending on the language used for storing the Model;
-
translation of the fields labels when objects are rendered within a View;
As naming convention, language identification uses the ISO codes for language and country, separated by a dash. The country code being optional. Each code is two letters long, lower case for language and uppercase for country.
Syntax: {ISO639-1 lang}[-{ISO-3166-1 country}]
Examples :
fr
: standard frenchfr-CA
: french Canadianzh-CN
: simplified chinese
Model - Translation of fields values¶
Any non-relational field can be translated (see Fields types).
The ORM uses a dedicated Model core\Translation
to store the terms translations.
When a field is marked as multilang
, the values of its translations can be retrieved with a SQL query.
'Translation' object fields:
<?php
/*[...]*/
class Translation extends Model {
public static function getColumns() {
return [
'language' => [
'type' => 'string',
'usage' => 'language/iso-639:2'
],
'object_class' => [
'type' => 'string'
],
'object_field' => [
'type' => 'string'
],
'object_id' => [
'type' => 'integer'
],
'value' => [
'type' => 'binary'
]
];
}
}
For the value field, the SQL MEDIUMBLOB type is used (overhead of 3 bytes, max size of 16,7 Mo).
Indeed, the size of the 'value' column may vary greatly from one type to another (the binary type may represent a document, a picture, a video, …). And, most of the time, fields that must be translated are texts (string, short_text or text). In any case, a 3 characters overhead is acceptable (and set a 16 Mo limit should not be a problem).
Note : as the only condition for the SQL type is to be compatible with the associated eQual type, in order for a binary field to be translated (for instance a PDF doc available in different languages), the size of the PDF should remain smaller than the SQL MEDIUMBLOB type (16 MB with MySQL).
Using multilang in CRUD¶
i18n is applied on a field basis.
To mark a field for i18n support and allow to store several translations for it, the multilang property must be set to true in the related class definition file.
Example :
<?php
'field' => [
'type' => 'string',
'multilang' => true
]
To understand how it works, let's compare with a normal http UPDATE controller (which is also valid for CREATE) :
<?php
// [...]
MyClass::ids($id)
->update($fields)
->read(['field1', 'field2'])
->first();
Now let's use the multilang
field by adding a parameter in our request :
<?php
// [...]
$lang = 'fr';
MyClass::ids($id)
->update($fields, $lang)
->read(['field1', 'field2'], $lang)
->first();
Using the $lang
parameter (DEFAULT_LANG
by default) indicates the ORM which value to return for fields set as multilang
.
Note: you don't need to specify language for a DELETE request since the whole object gets deleted
And it's the same process for a GET request, for instance :
<?php
// [...]
$lang = 'en';
MyClass::search()
->read(['field1', 'field2'], $lang)
->get(true);
Now there is a special case you're very likely to encounter : CREATE an object with multiple translations at once.
Here is how you should proceed :
<?php
// [...]
$lang1 = 'fr';
$lang2 = 'en';
MyClass::create($fields, $lang1)
->read(['field1', 'field2'])
->first();
MyClass::ids($id)->update($fields, $lang2);
What it does, is updating your initial object with the secondary language
If you want to add more than 2 languages, you can repeat that last line for as many languages needed.
Like so :
<?php
// [...]
MyClass::ids($id)->update($fields, $lang2); // $lang2 = 'en'
MyClass::ids($id)->update($fields, $lang3); // $lang3 = 'es'
View - Translation of the terms related to a class¶
Each package can have an optional folder named i18n
.
If such folder exists, it must contain a subfolder for each language used by the backend (according to naming convention and as defined in the configuration).
Inside those folders, translations are stored in .json
files which prefixes are identical to the class they refer to (ex. : User.json
).
Those translation files use UTF-8 encoding and JSON format, and contain the translation of all terms that might be translated (attributes label
, description
, selection
, and help
).
The translation file has the name
and the plural
of the name of the class, as well as a description
for it.
The model section which consist of a map of all the field names that are present in the class, to which is associated a list of attributes with their related translations.
Possible attributes are :
-
label
attribute is the name of the field that we want the user to see and it must start with a capital letter. -
description
attribute is considered an additional information or a small brief for the field, to assist users in understanding the use of it. This attribute should start with a capital letter and end with either ".", "?" or "!". -
selection
attribute is usually added for the "type" field of the class, which allows the translation of the available options of the field. -
help
attribute is going to be used as a more detailed explanation about the view's fields, in case the user didn't properly understand thedescription
. For the moment, this attribute is going to be an empty string, but should be added to all the fields.
The view section that translates all the section names of the tabs or the groups, if present in the form view. Since some classes may have multiple types of views, like "Partner.form.default.json"
and "Partner.form.payer.json"
, the view
section will contain the name of the type (in this case "form.default"
and "form.payer"
).
Inside each object a name
, description
and layout
containing the "id" of the view's sections and assigning a label to it, will be displayed as shown below.
"view": {
"form.default": {
"name": "Partenaires",
"description": "Formulaire de base pour afficher les partenaires",
"layout": {}
},
"list.default": {
"name": "Partneraires",
"description": "Liste de base des partenaires pré-existants",
"layout": {}
},
"form.payer": {
"name": "Client",
"description": "Formulaire de base pour les clients pré-existants",
"layout": {}
},
"list.customer": {
"name": "Clients",
"description": "Liste de base des clients pré-existants",
"layout": {}
},
"list.contact": {
"name": "Contacts",
"description": "List des contacts pré-existants",
"layout": {}
}
}
In this example, multiple view type exists but all the layouts are empty because none of them were divided into sections. A none empty layout, which means that the view is actually divided into sections or tabs will look like the following example, where "section.identity_info", "section.identity_main_address", "section.identity_addresses", and "section.identity_description" are the sections ids and the labels
are the name we want to be shown on the user side. Usually "list.default" type have an empty layout because it is a table, so it will not be divided into sections.
"view": {
"form.default": {
"name": "Identité",
"description": "Formulaire de base pour afficher des groupes",
"layout": {
"section.identity_info": {
"label": "Informations générales"
},
"section.identity_main_address": {
"label": "Adresse principale"
},
"section.identity_addresses": {
"label": "Autres adresses"
},
"section.identity_description": {
"label": "Description"
}
}
},
"list.default": {
"name": "Liste d'identités",
"description": "Liste de base d'identités pré-existantes",
"layout": {}
}
}
The error section is used for translating the form validation (Usually added for the Login/Sign Up form).
In every i18n (translation) folder in each package, the user has the ability to create a Markdown (.md) file to better explain what the view does and how it works. For example, to explain more about Booking list view, we can create a "Booking.list.default.md"
file inside the i18n folder where this view is present and write down more explanation about it. We can write that this view represents all the booking history related to a center of the logged user, and that they should be displayed in descending order which means that the most recent posts are the ones shown first.
Example of french (fr) translation using¶
Example of french (fr) translation using i18n
{
"name": "Règle de Tva",
"plural": "Règles de Tva",
"description": "Les règles comptables permettent de préciser sur quel compte une opération doit être imputée.",
"model": {
"name":{
"label":"Nom",
"description": "Nom de la règle comptable.",
"help": ""},
"description":{
"label":"Description",
"description": "Brève description de la règle pour servir de mémo.",
"help": ""},
"type":{
"label":"Type",
"description": "Type d'opération auquel cette règle se rapporte.",
"selection": {
"purchase": "achat",
"sale": "vente"},
"help": ""},
"vat_rule_id":{
"label":"Règle de TVA",
"description": "Règle de TVA à laquelle cette ligne est liée.",
"help": ""
},
"accounting_rule_line_ids":{
"label":"Lignes liées à cette règle",
"description": "Lignes liées à cette règle.",
"help": ""}
},
"view": {
"form.default": {
"name": "Règles de Tva",
"description": "Vue par défaut des règles comptables avec tous les champs",
"layout": {
"section.accounting_rules_section": {
"label": "Détails"
},
"section.accounting_rulelines": {
"label": "Ligne de règle comptable"
}
}
},
"list.default": {
"name": "Liste des Règles de Tva",
"description": "Cette vue est destinée à afficher les règles comptables.",
"layout": {}
}
},
"error": {
}
}
Multiple translation files¶
Classes may be used in different packages (extending parent classes with the possibility of adding new fields) and therefore, they also may have different translation files.
Here is an example of an extended class.
<?php
namespace lodging\sale\booking;
class Contact extends \sale\booking\Contact {
public static function getName() {
return "Contact";
}
public static function getDescription() {
return "Booking contacts are persons involved in the organisation of
a booking.";
}
public static function getColumns() {
return [
'owner_identity_id' => [
'type' => 'many2one',
'foreign_object' => 'lodging\identity\Identity',
'description' => 'The organisation which the targeted
identity is a partner of.',
'default' => 1
],
'partner_identity_id' => [
'type' => 'many2one',
'foreign_object' => 'lodging\identity\Identity',
'description' => 'The targeted identity (the partner).',
'onupdate' => 'identity\Partner::onupdatePartnerIdentityId',
'required' => true
],
'booking_id' => [
'type' => 'many2one',
'foreign_object' => 'lodging\sale\booking\Booking',
'description' => 'Booking the contact relates to.',
'required' => true
],
'owner_identity_id' => [
'type' => 'many2one',
'foreign_object' => 'lodging\identity\Identity',
'description' => 'The organisation which the targeted
identity is a partner of.',
'default' => 1
]
];
}
}
If multiple translation files exist, the child class (extending the parent) gets overloaded with the parent (being extended) translation files. Also, if the same fields are translated, the translation used is the lowest/furthest one in the translation hierarchy (the translation file of the child class).
For example, if the class Contact inside the package sale and the one inside the package lodging both have inside the i18n folder a file named Contact.json
in which the field booking_id is translated, the translation inside the package lodging (extending the class inside the package sale) will be the one used.
On the other hand, if different fields are translated, the child class (inside the package lodging in the above example) will inherit the translations from the parent classes.
This scenario goes on until we reach the root-parent class "equal\orm\Model"
.
Translation of Menus¶
Even though Menu views
are different than Form Views
, their translation is the same. However, menu's don't contain anything in there model
section since it has no fields, which is why it won't be displayed in the translation of it. The view
section is similar to the ones shown above, containing the "id" of the section, its "label" and an empty "layout" object because all items
in the menu are considered as a new view type even if it's a children item in the view. Below is the structured menu translation:
{
"description": "",
"view": {
"item.bookings": {
"label": "Réservations",
"description": "",
"layout": {}
},
"item.customers": {
"label": "Clients",
"description": "",
"layout": {}
},
"item.planning": {
"label": "Calendrier",
"description": "",
"layout": {}
}
}
}
Translation files are stored in a specific i18n
folders, in the packages directory which entities belong to.
Generic filename format is : packages/{package_name}/i18n/{lang}/{class_name}.json
Translation files are requested using the config_i18n controller:
Example : ```bash $ ./equal.run --get=config_i18n --entity=core\User --lang=fr