Skip to content

Commit 40b8096

Browse files
committed
docs: document localization features
1 parent 0d19239 commit 40b8096

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
```

src/Web/Documentation/content/main/2-features/15-datetime.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ You may format a {b`Tempest\DateTime\DateTime`} instance in a specific format us
107107

108108
```php
109109
use Tempest\DateTime\FormatPattern;
110-
use Tempest\DateTime\Locale;
110+
use Tempest\Intl\Locale;
111111

112112
$date->format(); // 19 Sept 2025, 02:00:00
113113
$date->format(pattern: FormatPattern::COOKIE); // Monday, 19-Sept-2025 02:00:00 BST

0 commit comments

Comments
 (0)