Since about 2010, I have been looking for a translation framework for PHP that can generate very complex phrases and at the same time combine options such as singular/plural and work with genders (male, female, unknown) and ideally also format numbers according to the standards of the given country or region — unsuccessfully. In 2018, Facebook released FBT — an open source localization framework that provides a more efficient way of defining content for flexible and high-quality localization. I didn’t hesitate at all. I gradually started to rewrite this JavaScript version into PHP, and even if a few small things were not entirely according to the original code, I succeeded. It was my challenge that I finished in about 40 days (better said, evenings 😊).
There are many reasons why the common i18n libraries are very inadequate. The main ones are:
- misunderstanding of the native text by the translator (does not know the context),
- insufficient/nonsensical code combinations for translation,
- do not support features like enums or pronouns,
- and many more...
Fortunately, FBT can solve all of this. However, it is not easy to use it for the first time when you are not familiar with it. But I will be happy to advise you on how to use it.
- step: install the FBT package
composer require richarddobron/fbt
- step: set your FBT configuration
<?php
require ("vendor/autoload.php");
\fbt\FbtConfig::set('author', 'your name/team');
\fbt\FbtConfig::set('project', 'project name');
\fbt\FbtConfig::set('path', '/path/to/storage/fbt');
- step: language and gender settings
- If you just want to change the interface language, just use:
\fbt\FbtHooks::locale('sk_SK'); // app locale
- If you have an application in which users log in, you can use the interface IntlViewerContextInterface:
<?php
namespace App;
use fbt\Transform\FbtTransform\Translate\IntlVariations;
use fbt\Lib\IntlViewerContextInterface;
use fbt\Runtime\Gender;
class UserDTO implements IntlViewerContextInterface
{
public function getLocale(): ?string
{
return $this->locale;
}
public static function getGender(): int
{
if ($this->gender === 'male') {
return IntlVariations::GENDER_MALE;
}
if ($this->gender === 'female') {
return IntlVariations::GENDER_FEMALE;
}
return IntlVariations::GENDER_UNKNOWN;
}
}
After implementation, set viewerContext:
$loggedUserDto = ...;
\fbt\FbtConfig::set('viewerContext', $loggedUserDto)
- step: prepare translations files
- Facebook has devised their own system of labeling languages, you can find a list of them at this link.
- From this list, choose the languages into which you want to translate your website or application.
- I usually add them to the directory /storage/fbt/
- File name will look like this: sk_SK.json
- step: add FBT scripts to composer.json
{
...,
"scripts": {
"translate-fbts": "php ./vendor/bin/fbt translate --path=./storage/fbt --translations=./storage/fbt/*.json",
"generate-translations": "php ./vendor/bin/fbt generate-translations --source=./storage/fbt/.source_strings.json --translations=./storage/fbt/*.json"
}
}
- Native phrases can also be easily translated via the app editor Swiftyper Translations.
- step: add your texts
- Native phrases are always expected in English, it might look something like this:
<?php
// simple text:
echo fbt('Save', 'Button: Save a form or settings');
// text with params:
$name = 'Patricia';
$gender = 2;
echo fbt(
\fbt\fbt::name(
'name',
'<a href="#">' . $name . '</a>',
$gender
) .
' shared a link. Tell ' . \fbt\fbt::sameParam('name') . ' you liked it.',
'Notification about sharing a link.'
);
// is same as:
?>
<?php fbtTransform(); ?>
<fbt desc="param example">
<fbt:name name="name" gender="<?=$gender?>">
<a href="#"><?=$name?></a>
</fbt:name>
shared a link. Tell
<fbt:same-param name="name" />
you liked it.
</fbt>
<?php endFbtTransform(); ?>
- step: translate collected texts
This is what the collected source strings look like in the .source_strings.json file after script execution:
{
"phrases": [
{
"hashToText": {
"77515026232eb24b14cc5e7cca878637": "Save"
},
"desc": "Button: Save a form or settings",
"project": "tutorial app",
"author": "richard",
"type": "text",
"jsfbt": "Save"
},
{
"hashToText": {
"11608ffd7ee5e79d727ab00631b2c164": "{name} shared a link. Tell you liked it."
},
"desc": "Notification about sharing a link.",
"project": "tutorial app",
"author": "richard",
"type": "table",
"jsfbt": {
"t": {
"*": "{name} shared a link. Tell you liked it."
},
"m": [
{
"token": "name", // phrase token
"type": 1 // gender = 1, number = 2, pronoun = 3
}
]
}
}
],
"childParentMappings": []
}
Now we are ready to generate a file in which we will write the translations for the collected source strings:
composer run generate-translations
The /storage/fbt/sk_SK.json file now contains the hash keys of the source phrases that we can now translate. I make translations into Slovak, so I can use the gender of the {name} token and adapt the variations of the given text precisely. This step is a bit difficult for some translations with variants, as you have to work with the variations key.
You can also find several types of translations at this link.
{
"sk_SK": {
"fb-locale": "sk_SK",
"translations": {
"77515026232eb24b14cc5e7cca878637": {
"translations": [
{
"translation": "Uložiť",
"variations": []
}
],
"tokens": [],
"types": []
},
"11608ffd7ee5e79d727ab00631b2c164": {
"translations": [
{
"translation": "{name} zdieľal odkaz. Dajte mu vedieť, že sa vám páči.",
"variations": [1] // 1 = male
},
{
"translation": "{name} zdieľala odkaz. Dajte jej vedieť, že sa vám páči.",
"variations": [2] // 2 = female
},
{
"translation": "Používateľ {name} zdieľal/a odkaz. Dajte mu vedieť, že sa vám páči.",
"variations": [3] // 3 = unknown/neutral gender
}
],
"tokens": ["name"],
"types": [3] // 3 = gender, 28 = number
}
}
}
}
After completing the translations, we run the command that creates the translations for the application:
composer run translate-fbts
Finally, this command will generate a production file with translations using Jenkins hash.
{
"sk_SK": {
"2gTzp8": [
"Uložiť",
"77515026232eb24b14cc5e7cca878637" // phrase hash
],
"16x4nE": {
"1": [ // male
"{name} zdieľal odkaz. Dajte mu vedieť, že sa vám páči.",
"11608ffd7ee5e79d727ab00631b2c164"
],
"2": [ // female
"{name} zdieľal odkaz. Dajte jej vedieť, že sa vám páči.",
"11608ffd7ee5e79d727ab00631b2c164"
],
"*": [ // fallback
"Používateľ {name} zdieľal odkaz. Dajte mu vedieť, že sa vám páči.",
"11608ffd7ee5e79d727ab00631b2c164"
]
}
}
}
Your app is now translated! Hurray!
Thanks for reading 🙏.
Comments (1)