DEV Community

Cover image for Localizing PHP application with FBT instead of standard i18n
Richard Dobroň
Richard Dobroň

Posted on • Edited on

4 1

Localizing PHP application with FBT instead of standard i18n

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
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

3. Language and gender settings

If you just want to change the interface language, just use:

\fbt\FbtHooks::locale('sk_SK'); // app locale
Enter fullscreen mode Exit fullscreen mode

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;
}
}
view raw UserDTO.php hosted with ❤ by GitHub

After implementation, set viewerContext:

$loggedUserDto = ...;
\fbt\FbtConfig::set('viewerContext', $loggedUserDto);
Enter fullscreen mode Exit fullscreen mode

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"
}
}
view raw composer.json hosted with ❤ by GitHub

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(); ?>
view raw example fbt.php hosted with ❤ by GitHub

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
Enter fullscreen mode Exit fullscreen mode

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
}
}
}
}
view raw sk_SK.json hosted with ❤ by GitHub

After completing the translations, we run the command that creates the translations for the application:

composer run translate-fbts
Enter fullscreen mode Exit fullscreen mode

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 🙏.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)