|
| 1 | +--- |
| 2 | +title: Localization |
| 3 | +description: "Tempest provides convenient utilities for localizing applications, including a translator built on the MessageFormat 2.0 specification." |
| 4 | +--- |
| 5 | + |
| 6 | +## Overview |
| 7 | + |
| 8 | +Tempest provides a simple {b`Tempest\Intl\Translator`} interface for localizing applications. It allows you to translate messages into different languages and formats them according to the current or specified locale. |
| 9 | + |
| 10 | +The translator implements the [MessageFormat 2.0](https://messageformat.unicode.org/) specification, which provides a flexible syntax for defining translation messages. This specification is [maintained by the Unicode project](https://github.com/unicode-org/message-format-wg) and is widely used in internationalization libraries. |
| 11 | + |
| 12 | +## Translating messages |
| 13 | + |
| 14 | +To translate messages, you may [inject](../1-essentials/05-container.md) the {`Tempest\Intl\Translator`} interface and use its `translate()` method. If the translation message accepts variables, you may pass them as named parameters. |
| 15 | + |
| 16 | +```php |
| 17 | +$translator->translate('cart.expire_at', expire_at: $expiration); |
| 18 | +// Your cart is valid until 1:30 PM |
| 19 | +``` |
| 20 | + |
| 21 | +To translate a message in a specific locale, you may use the `translateForLocale()` instead and provide the {b`Tempest\Intl\Locale`} as the first parameter. |
| 22 | + |
| 23 | +```php |
| 24 | +$translator->translateForLocale(Locale::FRENCH, 'cart.expire_at', expire_at: $expiration); |
| 25 | +// Votre panier expire à 12h30 |
| 26 | +``` |
| 27 | + |
| 28 | +Alternatively, you may use the `translate` or the `translate_for_locale` function in the `Tempest\Intl` namespace. |
| 29 | + |
| 30 | +### Configuring the locale |
| 31 | + |
| 32 | +The current locale is stored in the `currentLocale` property of the {`Tempest\Intl\IntlConfig`} [configuration object](../1-essentials/06-configuration.md). You may configure another default locale by creating a dedicated configuration file: |
| 33 | + |
| 34 | +```php intl.config.php |
| 35 | +return new IntlConfig( |
| 36 | + currentLocale: Locale::FRENCH, |
| 37 | + fallbackLocale: Locale::ENGLISH, |
| 38 | +); |
| 39 | +``` |
| 40 | + |
| 41 | + By default, Tempest uses the [`intl.default_locale`](https://www.php.net/manual/en/locale.getdefault.php) ini value for the current locale. |
| 42 | + |
| 43 | +### Changing the locale |
| 44 | + |
| 45 | +You may update the current locale at any time by mutating the {b`Tempest\Intl\IntlConfig`} configuration object. For instance, this could be done in a [middleware](../1-essentials/01-routing.md#route-middleware): |
| 46 | + |
| 47 | +```php |
| 48 | +final readonly class SetLocaleMiddleware implements HttpMiddleware |
| 49 | +{ |
| 50 | + public function __construct( |
| 51 | + private Authenticator $authenticator, |
| 52 | + private IntlConfig $intlConfig, |
| 53 | + ) {} |
| 54 | + |
| 55 | + public function __invoke(Request $request, HttpMiddlewareCallable $next): Response |
| 56 | + { |
| 57 | + $this->intlConfig->currentLocale = $this->authenticator |
| 58 | + ->currentUser() |
| 59 | + ->preferredLocale; |
| 60 | + |
| 61 | + return $next($request); |
| 62 | + } |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +## Defining translation messages |
| 67 | + |
| 68 | +Translation messages are usually stored in translation files. Tempest automatically [discovers](../4-internals/02-discovery.md) 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](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code. |
| 69 | + |
| 70 | +For instance, you may store translation files in a `lang` directory: |
| 71 | + |
| 72 | +``` |
| 73 | +src/ |
| 74 | +└── lang/ |
| 75 | + ├── messages.fr.yaml |
| 76 | + └── messages.en.yaml |
| 77 | +``` |
| 78 | + |
| 79 | +Alternatively, you may call the `add()` method on a {`Tempest\Intl\Catalog\Catalog`} instance to add a translation message at runtime. |
| 80 | + |
| 81 | +```php |
| 82 | +$catalog->add(Locale::FRENCH, 'order.continue_shopping', 'Continuer vos achats'); |
| 83 | +``` |
| 84 | + |
| 85 | +### Message syntax |
| 86 | + |
| 87 | +Tempest implements the [MessageFormat 2.0](https://messageformat.unicode.org/) specification, which provides a flexible syntax for defining translation messages. The syntax allows for variables, [pluralization](#pluralization), and [custom formatting functions](#custom-formatting-functions). |
| 88 | + |
| 89 | +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](https://messageformat.unicode.org/docs/reference/variables/), a [function](https://messageformat.unicode.org/docs/reference/functions/) and a function [parameter](https://messageformat.unicode.org/docs/reference/functions/#options): |
| 90 | + |
| 91 | +```yaml messages.en.yaml |
| 92 | +today: |
| 93 | + Today is {$today :datetime pattern=|yyyy/MM/dd|} |
| 94 | +``` |
| 95 | +
|
| 96 | +:::info |
| 97 | +You may learn more about this syntax in the [MessageFormat documentation](https://messageformat.unicode.org/docs/translators/). |
| 98 | +::: |
| 99 | +
|
| 100 | +### Pluralization |
| 101 | +
|
| 102 | +Pluralizing messages may be done using [matchers](https://messageformat.unicode.org/docs/reference/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: |
| 103 | + |
| 104 | +```php messages.pl.yaml |
| 105 | +cart: |
| 106 | + items_count: |
| 107 | + .input {$count :number} |
| 108 | + .match $count |
| 109 | + one {{Masz {$count} przedmiot.}} |
| 110 | + few {{Masz {$count} przedmioty.}} |
| 111 | + many {{Masz {$count} przedmiotów.}} |
| 112 | + other {{Masz {$count} przedmiotów.}} |
| 113 | +``` |
| 114 | + |
| 115 | +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. |
| 116 | + |
| 117 | +```php messages.pl.yaml |
| 118 | +cart: |
| 119 | + items_by_type_count: |
| 120 | + .input {$type :string} |
| 121 | + .input {$count :number} |
| 122 | + .match $type $count |
| 123 | + product one {{Masz {$count} produkt w koszyku.}} |
| 124 | + product few {{Masz {$count} produkty w koszyku.}} |
| 125 | + product many {{Masz {$count} produktów w koszyku.}} |
| 126 | + product * {{Masz {$count} produktów w koszyku.}} |
| 127 | + service one {{Masz {$count} usługę w koszyku.}} |
| 128 | + service few {{Masz {$count} usługi w koszyku.}} |
| 129 | + service many {{Masz {$count} usług w koszyku.}} |
| 130 | + service * {{Masz {$count} usług w koszyku.}} |
| 131 | + * one {{Masz {$count} element w koszyku.}} |
| 132 | + * few {{Masz {$count} elementy w koszyku.}} |
| 133 | + * many {{Masz {$count} elementów w koszyku.}} |
| 134 | + * * {{Masz {$count} elementów w koszyku.}} |
| 135 | +``` |
| 136 | + |
| 137 | +### Custom formatting functions |
| 138 | + |
| 139 | +The [MessageFormat 2.0](https://messageformat.unicode.org/) 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. |
| 140 | + |
| 141 | +You may define a custom formatting function by implementing the {b`Tempest\Intl\MessageFormat\FormattingFunction`} interface. For instance, the function for formatting dates is implemented as follows: |
| 142 | + |
| 143 | +```php |
| 144 | +final class DateTimeFunction implements FormattingFunction |
| 145 | +{ |
| 146 | + public string $name = 'datetime'; |
| 147 | +
|
| 148 | + public function evaluate(mixed $value, array $parameters): FormattedValue |
| 149 | + { |
| 150 | + $datetime = DateTime::parse($value); |
| 151 | + $formatted = $datetime->format(Arr\get_by_key($parameters, 'pattern')); |
| 152 | +
|
| 153 | + return new FormattedValue($value, $formatted); |
| 154 | + } |
| 155 | +} |
| 156 | +``` |
0 commit comments