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)