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.
1. Install the FBT package
composer require richarddobron/fbt
2. 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');
3. 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);
4. 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
5. 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.
6. 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(); ?> |
7. 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 🙏.
Top comments (0)