FBT in action
Write the text once in English. Translators get whole sentences with the variations their language needs, and FBT picks the right one at runtime.
- Plurals
- Names & gender
- Enums
- Pronouns
fbt(
'You have ' .
fbt::plural('unread message', $count, [
'many' => 'unread messages',
'showCount' => 'yes',
'name' => 'count',
]) . '.',
'Unread messages in the inbox',
);
- $count = 1Máš 1 neprečítanú správu.
- $count = 3Máš 3 neprečítané správy.
- $count = 5Máš 5 neprečítaných správ.
- $count = 21Máš 21 neprečítaných správ.
English and German have two forms. Slovak has three (1, 2–4, 5+), Russian uses the form of 1 also for 21, Turkish keeps the noun singular after a number, and Japanese has no plural at all. Translators just fill in the forms of their language.
fbt(
fbt::name('name', $user->name, $user->gender) .
' shared a photo with you.',
'Notification about a shared photo',
);
- Peter · malePeter s tebou zdieľal fotku.
- Jana · femaleJana s tebou zdieľala fotku.
- Alex · unknownAlex s tebou zdieľal(a) fotku.
In Slovak and Russian, the verb changes with the gender of the person (zdieľal / zdieľala, поделился / поделилась). English, German, Turkish and Japanese keep one sentence.
fbt(
'Your order has been ' .
fbt::enum($order->status, [
'shipped' => 'shipped',
'delivered' => 'delivered',
'cancelled' => 'cancelled',
]) . '.',
'Status of an order',
);
- shippedTvoja objednávka bola odoslaná.
- deliveredTvoja objednávka bola doručená.
- cancelledTvoja objednávka bola zrušená.
Every value becomes a whole sentence for translators, so they can change the word order and the grammar, not just a single word.
fbt(
fbt::param('name', $user->name) . ' updated ' .
fbt::pronoun('possessive', $user->pronounGender, ['human' => true]) .
' profile.',
'Notification about an updated profile',
);
- Jana · femaleJana aktualizovala svoj profil.
- Peter · malePeter aktualizoval svoj profil.
- Alex · unknownAlex aktualizoval(a) svoj profil.
English and German pick his / her / their. Slovak and Russian use the same reflexive pronoun for everyone (svoj, свой), but the verb changes instead. Turkish and Japanese have no gendered pronouns at all.
The same works with <fbt> in HTML templates. The outputs above are rendered by fbt.
Why FBT?
Getting grammatically correct translations in dynamic applications is hard. Let FBT do the hard work.
Inline translations
Write texts right where they're used, in PHP with fbt() or in HTML templates with <fbt>. No translation keys, no language files to keep in sync.
Grammar done right
Plurals, genders and names are variations translators fill in. Every language gets correct sentences, even with three plural forms or gendered verbs.
Proven at Facebook
A port of fbt, the framework Facebook built to translate its apps, and of fbtee, its maintained fork. Same source strings, same translation formats.
Easy setup
One Composer package and CLI commands to collect texts, generate translation files and build the translations.
Translate in context
Inline translating lets translators edit texts right on the page, where they see how they're used.
Works with your stack
Plain PHP, HTML templates and Latte, with hooks to store phrases and translations anywhere. For Laravel, use FBT for Laravel.
Get started in minutes
In any PHP application, or in Laravel.
- PHP
- Laravel
composer require richarddobron/fbt
# Wrap texts with fbt(...), then collect them
php ./vendor/bin/fbt collect-fbts --path=./storage/fbt/ --src=./src/
php ./vendor/bin/fbt generate-translations --src=./storage/fbt/.source_strings.json --translations=./translations/*.json
# Translate translations/*.json, then
php ./vendor/bin/fbt translate --path=./storage/fbt/ --translations=./translations/*.json
composer require richarddobron/laravel-fbt
php artisan vendor:publish --tag=fbt-config
# Wrap texts with @fbt(...) in Blade, then collect them
php artisan fbt:collect-fbts
php artisan fbt:generate-translations
# Translate storage/fbt/translation_input.json, then
php artisan fbt:translate
See FBT for Laravel for Blade directives, artisan commands and storing translations in the database.
Let your coding agent set it up
Paste this prompt into Claude Code, Cursor or Copilot, it installs fbt and wraps the texts of your app.
Add internationalization to this PHP app with fbt (docs: https://richarddobron.github.io/fbt/). For a Laravel app,
use laravel-fbt instead (https://richarddobron.github.io/laravel-fbt/).
1. Install it with `composer require richarddobron/fbt`.
2. Configure it at the start of the application: `btFbtConfig::set('author', ...)`, `'project'`, `'path'` (storage
of the collected texts and translations) and `'locale'` (the locale of the current request).
3. Make the user class implement `fbtLibIntlViewerContextInterface` (`getLocale()` and `getGender()`) and set it
as the viewer context with `btFbtConfig::set('viewerContext', $user)`, so that texts use the locale and the
gender of the signed-in user.
4. Wrap every user-facing text in `fbt('Text', 'Description')`, or in `<fbt desc="Description">Text</fbt>` between
`fbtTransform()` and `endFbtTransform()` in templates. Use `fbs()` for plain text (e.g. HTML attributes).
Every text needs a description which explains its context to translators.
5. Never concatenate translated fragments, keep whole sentences in one fbt. Use `fbt::param()` for values,
`fbt::plural()` for counts, `fbt::name()` for names of people (with their gender), `fbt::enum()` and
`fbt::pronoun()`.
6. Collect the texts with `php ./vendor/bin/fbt collect-fbts --path=<path> --src=<source directory>`, generate the
missing translations with `php ./vendor/bin/fbt generate-translations` and build the translations with
`php ./vendor/bin/fbt translate --path=<path> --translations=<translation files>`.
Based on Facebook's fbt
and its maintained fork fbtee
