# Translate
# Summary
Translator is built on top of gettext (opens new window) and it provides a set of tools to manage translations in PHP applications.
# Installing
Translate is available through Packagist (opens new window) and the repository source is at chevere/translate (opens new window).
composer require chevere/translate
# Quick Start
Mark translatable strings in your code using the translation functions.
// /app/src/hello.php
use function Chevere\Translate\__;
echo __('Hello, world!');
Extract the strings into a .po file, translate it, compile it to PHP and load it at runtime.
use Chevere\Translate\PoMaker;
use Chevere\Translate\TranslatorBuilder;
use Chevere\Translate\TranslatorInstance;
use Chevere\Translate\TranslatorLoader;
use function Chevere\Filesystem\directoryForPath;
$source = directoryForPath('/app/src/');
$locales = directoryForPath('/app/locales/');
$compiled = directoryForPath('/app/compiled/');
// 1. Scan source and create /app/locales/es-CL/messages.po
(new PoMaker('es-CL', 'messages'))
->withScanFor($source)
->make($locales);
// 2. Translate messages.po using any PO editor
// 3. Compile to /app/compiled/es-CL/messages.php
(new TranslatorBuilder($locales, $compiled))
->withBuild('es-CL', 'messages');
// 4. Load the translator and register it
$translator = (new TranslatorLoader())
->withLoad($compiled, 'es-CL', 'messages')
->translator();
new TranslatorInstance($translator);
echo __('Hello, world!');
// ¡Hola, mundo!
Steps 1 to 3 are build-time tasks, only step 4 is needed at runtime.
When using a domain files are stored at {directory}/{locale}/{domain}. Pass an empty domain to store files at {directory}/{locale} instead.
| Domain | PO file | PHP file |
|---|---|---|
'messages' | /app/locales/es-CL/messages.po | /app/compiled/es-CL/messages.php |
'' | /app/locales/es-CL.po | /app/compiled/es-CL.php |
# PoMaker
PoMaker scans .php and .js files for translatable strings and generates a PO file (opens new window) for a given locale.
# Create PoMaker
Pass the locale and optionally the domain. Use writer to log the scan progress (defaults to NullWriter).
use Chevere\Translate\PoMaker;
use Chevere\Writer\StreamWriter;
use function Chevere\Writer\streamFor;
$poMaker = new PoMaker(
locale: 'es-CL',
domain: 'messages',
writer: new StreamWriter(streamFor('php://stdout', 'w')),
);
# Scan directory
Use withScanFor to recursively scan a directory. It returns a new instance.
use function Chevere\Filesystem\directoryForPath;
$poMaker = $poMaker->withScanFor(
directoryForPath('/app/src/')
);
By default it scans for __ and __n calls. Use functions to map additional function names to its gettext handler. For example, to also extract strings with context and domain, or calls to your own helpers:
$poMaker = $poMaker->withScanFor(
directory: directoryForPath('/app/src/'),
functions: [
'__p' => 'pgettext',
'__np' => 'npgettext',
'__d' => 'dgettext',
'__dn' => 'dngettext',
'__dp' => 'dpgettext',
'__dnp' => 'dnpgettext',
'myTranslate' => 'gettext',
],
);
Available handlers are gettext, ngettext, pgettext, npgettext, dgettext, dngettext, dpgettext and dnpgettext.
Strings passed to domain functions are only extracted when their domain matches the PoMaker domain. For example, __d('admin', 'Dashboard') is extracted by new PoMaker('es-CL', 'admin') but not by new PoMaker('es-CL', 'messages').
# Make PO file
Use make to generate the .po file at the target directory, which is created if it doesn't exist.
$poMaker->make(
directoryForPath('/app/locales/')
);
make overwrites any existing .po file at the target path, including translations already in it. Generate into a separate directory and merge the result with your translated files (for example, using msgmerge (opens new window)).
Calling make before withScanFor throws BadMethodCallException.
# TranslatorBuilder
TranslatorBuilder compiles .po files to .php files, which is the format used at runtime by TranslatorLoader.
# Create TranslatorBuilder
Pass the sourceDirectory containing the .po files and the targetDirectory for the generated .php files. Use writer to log the generated files (defaults to NullWriter).
use Chevere\Translate\TranslatorBuilder;
use function Chevere\Filesystem\directoryForPath;
$builder = new TranslatorBuilder(
sourceDirectory: directoryForPath('/app/locales/'),
targetDirectory: directoryForPath('/app/compiled/'),
);
The source directory must exist or DirectoryNotExistsException is thrown.
# Build translations
Use withBuild to compile the .po file for a locale and domain. It returns a new instance, so calls can be chained.
$builder = $builder
->withBuild(locale: 'es-CL', domain: 'messages')
->withBuild(locale: 'es-CL', domain: 'admin')
->withBuild(locale: 'pt-BR', domain: 'messages');
It throws DirectoryNotExistsException if the locale directory doesn't exist and FileNotExistsException if the .po file doesn't exist.
# TranslatorLoader
TranslatorLoader loads compiled .php translation files into a Translator.
# Load translations
Use withLoad to load the translation file for a locale and domain from a directory. It returns a new instance, so calls can be chained to load multiple domains.
use Chevere\Translate\TranslatorLoader;
use function Chevere\Filesystem\directoryForPath;
$compiled = directoryForPath('/app/compiled/');
$loader = (new TranslatorLoader())
->withLoad($compiled, 'es-CL', 'messages')
->withLoad($compiled, 'es-CL', 'admin');
The first loaded domain becomes the default domain, used by functions that don't take a domain argument.
It throws DirectoryNotExistsException if the locale directory doesn't exist and FileNotExistsException if the .php file doesn't exist.
# Get Translator
Use translator to get the Translator.
$translator = $loader->translator();
# Translator
Translator extends gettext/translator (opens new window), so it provides all gettext methods such as gettext, ngettext, pgettext and dgettext.
$translator->gettext('Hello, world!');
// ¡Hola, mundo!
Use withAdd to add or override messages at runtime. Messages are indexed by context, with '' for no context. Plural translations are passed as a list of forms.
$translator = $translator->withAdd(
messages: [
'' => [
'Hello, world!' => '¡Hola, mundo!',
'%d file' => ['%d archivo', '%d archivos'],
],
'menu' => [
'Open' => 'Abrir',
],
],
pluralForms: 'nplurals=2; plural=(n != 1);',
domain: 'messages',
);
Use withLoad to load compiled translation files directly.
use function Chevere\Filesystem\filePhpReturnForPath;
$translator = $translator->withLoad(
filePhpReturnForPath('/app/compiled/es-CL/messages.php')
);
Use domain, dictionary, pluralForms and toArray to read the translator state.
# Functions
Translation functions are provided at the Chevere\Translate namespace.
# Register Translator
Functions use the Translator registered with TranslatorInstance.
use Chevere\Translate\TranslatorInstance;
new TranslatorInstance($translator);
If no Translator is registered, functions return the original string. Plural functions return $singular when $count is 1 and $plural otherwise.
Use translator to get the registered Translator.
use function Chevere\Translate\translator;
$translator = translator();
# Available functions
| Function | Description |
|---|---|
__($message) | Translate a string |
__n($singular, $plural, $count) | Translate a plural string |
__p($context, $message) | Translate a string with context |
__np($context, $singular, $plural, $count) | Translate a plural with context |
__d($domain, $message) | Translate a string in a domain |
__dn($domain, $singular, $plural, $count) | Translate a plural in a domain |
__dp($domain, $context, $message) | Translate a string in a domain with context |
__dnp($domain, $context, $singular, $plural, $count) | Translate a plural in a domain with context |
use function Chevere\Translate\__;
use function Chevere\Translate\__d;
use function Chevere\Translate\__p;
__('Open');
// Abierto
__p('menu', 'Open');
// Abrir
__d('admin', 'Dashboard');
// Panel
Functions return the translated string as-is, use sprintf (opens new window) or strtr (opens new window) to replace placeholders.
use function Chevere\Translate\__n;
$count = 3;
sprintf(__n('%d file', '%d files', $count), $count);
// 3 archivos
Remember to pass __p, __np and domain functions to PoMaker so their strings are extracted.