Localization
Overview
Tempest provides a simple Translator
interface for localizing applications. It allows you to translate messages into different languages and formats them according to the current or specified locale.
The translator implements the MessageFormat 2.0 specification, which provides a flexible syntax for defining translation messages. This specification is maintained by the Unicode project and is widely used in internationalization libraries.
Translating messages
To translate messages, you may inject the Tempest\Intl\Translator
interface and use its translate()
method. If the translation message accepts variables, you may pass them as named parameters.
$translator->translate('cart.expire_at', expire_at: $expiration); // Your cart is valid until 1:30 PM
To translate a message in a specific locale, you may use the translateForLocale()
instead and provide the Locale
as the first parameter.
$translator->translateForLocale(Locale::FRENCH, 'cart.expire_at', expire_at: $expiration); // Votre panier expire à 12h30
Alternatively, you may use the translate
or the translate_for_locale
function in the Tempest\Intl
namespace.
Configuring the locale
The current locale is stored in the currentLocale
property of the Tempest\Intl\IntlConfig
configuration object. You may configure another default locale by creating a dedicated configuration file:
return new IntlConfig( currentLocale: Locale::FRENCH, fallbackLocale: Locale::ENGLISH, );
By default, Tempest uses the intl.default_locale
ini value for the current locale.
Changing the locale
You may update the current locale at any time by mutating the IntlConfig
configuration object. For instance, this could be done in a middleware:
final readonly class SetLocaleMiddleware implements HttpMiddleware { public function __construct( private Authenticator $authenticator, private IntlConfig $intlConfig, ) {} public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { $this->intlConfig->currentLocale = $this->authenticator ->currentUser() ->preferredLocale; return $next($request); } }
Defining translation messages
Translation messages are usually stored in translation files. Tempest automatically discovers YAML and JSON translation files that use the <name>.<locale>.{yaml,json}
naming format, where <name>
may be any string, and <locale>
must be an ISO 639-1 language code.
For instance, you may store translation files in a lang
directory:
src/ └── lang/ ├── messages.fr.yaml └── messages.en.yaml
Alternatively, you may call the add()
method on a Tempest\Intl\Catalog\Catalog
instance to add a translation message at runtime.
$catalog->add(Locale::FRENCH, 'order.continue_shopping', 'Continuer vos achats');
Message syntax
Tempest implements the MessageFormat 2.0 specification, which provides a flexible syntax for defining translation messages. The syntax allows for variables, pluralization, and custom formatting functions.
Since most translation messages are multiline, YAML is the recommended format for defining them. Here is an example of a translation message that uses a variable, a function and a function parameter:
today: Today is {$today :datetime pattern=|yyyy/MM/dd|}
You may learn more about this syntax in the MessageFormat documentation.
Pluralization
Pluralizing messages may be done using matchers and the number
function. This syntax supports languages that have more than two plural categories. For instance, you may translate this sentence in Polish:
cart: items_count: .input {$count :number} .match $count one {{Masz {$count} przedmiot.}} few {{Masz {$count} przedmioty.}} many {{Masz {$count} przedmiotów.}} other {{Masz {$count} przedmiotów.}}
For more complex translation messages, you may also use multiple variables in a matcher. In this example, we use a type
and a count
variable in the same matcher.
cart: items_by_type_count: .input {$type :string} .input {$count :number} .match $type $count product one {{Masz {$count} produkt w koszyku.}} product few {{Masz {$count} produkty w koszyku.}} product many {{Masz {$count} produktów w koszyku.}} product * {{Masz {$count} produktów w koszyku.}} service one {{Masz {$count} usługę w koszyku.}} service few {{Masz {$count} usługi w koszyku.}} service many {{Masz {$count} usług w koszyku.}} service * {{Masz {$count} usług w koszyku.}} * one {{Masz {$count} element w koszyku.}} * few {{Masz {$count} elementy w koszyku.}} * many {{Masz {$count} elementów w koszyku.}} * * {{Masz {$count} elementów w koszyku.}}
Using markup
Markup may be added to translation messages using a dedicated syntax defined in the MessageFormat specification. Tempest provides a markup implementation that renders HTML tags and Iconify icons.
bold_text: "This is {#strong}bold{/strong}." ui: open_menu: "{#icon-tabler-menu/} Open menu"
It is possible to implement your own markup by implementing the MarkupFormatter
or StandaloneMarkupFormatter
interfaces. Classes implementing these interfaces are automatically discovered by Tempest.
Custom formatting functions
The MessageFormat 2.0 specification allows for defining custom formatting functions that can be used in translation messages. By default, Tempest provides formatting functions for strings, numbers and dates.
You may define a custom formatting function by implementing the FormattingFunction
interface. For instance, the function for formatting dates is implemented as follows:
final class DateTimeFunction implements FormattingFunction { public string $name = 'datetime'; public function evaluate(mixed $value, array $parameters): FormattedValue { $datetime = DateTime::parse($value); $formatted = $datetime->format(Arr\get_by_key($parameters, 'pattern')); return new FormattedValue($value, $formatted); } }