PLATFORM
  • Tails

    Create websites with TailwindCSS

  • Wave

    Start building the next great SAAS

  • Pines

    Alpine & Tailwind UI Library

  • Auth

    Plug'n Play Authentication for Laravel

  • Designer comingsoon

    Create website designs with AI

  • DevBlog comingsoon

    Blog platform for developers

  • Static

    Build a simple static website

  • SaaS Adventure

    21-day program to build a SAAS

Written By
Views

Localizing PHP application with FBT instead of standard i18n

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. step: install the FBT package
composer require richarddobron/fbt
  1. 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');
  1. 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)
  1. 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
  1. 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"
    }
}
  1. 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(); ?>
  1. 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)

loading comments