Skip to content

Commit 33fb626

Browse files
committed
TASK: Add documentation for @neos-project/neos-ui-i18n package
1 parent 1c5a56c commit 33fb626

File tree

1 file changed

+244
-0
lines changed

1 file changed

+244
-0
lines changed

packages/neos-ui-i18n/README.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# @neos-project/neos-ui-i18n
2+
3+
> I18n utilities for Neos CMS UI.
4+
5+
This package connects Flow's Internationalization (I18n) framework with the Neos UI.
6+
7+
In Flow, translations are organized in [XLIFF](http://en.wikipedia.org/wiki/XLIFF) files that are stored in the `Resources/Private/Translations/`-folder of each Flow package.
8+
9+
The Neos UI does not load all translation files at once, but only those that have been made discoverable explicitly via settings:
10+
```yaml
11+
Neos:
12+
Neos:
13+
userInterface:
14+
translation:
15+
autoInclude:
16+
'Neos.Neos.Ui':
17+
- Error
18+
- Main
19+
// ...
20+
'Vendor.Package':
21+
- Main
22+
// ...
23+
```
24+
25+
At the beginning of the UI bootstrapping process, translations are loaded from an enpoint (see: [`\Neos\Neos\Controller\Backend\BackendController->xliffAsJsonAction()`](https://neos.github.io/neos/9.0/Neos/Neos/Controller/Backend/BackendController.html#method_xliffAsJsonAction)) and are available afterwards via the `translate` function exposed by this package.
26+
27+
## API
28+
29+
### `translate`
30+
31+
```typescript
32+
function translate(
33+
fullyQualifiedTranslationAddressAsString: string,
34+
fallback: string | [string, string],
35+
parameters: Parameters = [],
36+
quantity: number = 0
37+
): string;
38+
```
39+
40+
`translate` will use the given translation address to look up a translation from the ones that are currently available (see: [`initializeI18n`](#initializeI18n)).
41+
42+
To understand how the translation address maps onto the translations stored in XLIFF files, let's take a look at the structure of the address:
43+
```
44+
"Neos.Neos.Ui:Main:errorBoundary.title"
45+
└────┬─────┘ └─┬┘ └───────────┬─────┘
46+
Package Key Source Name trans-unit ID
47+
```
48+
49+
Each translation address consists of three Parts, one identifying the package (Package Key), one identifying the XLIFF file (Source Name), and one identifying the translation itself within the XLIFF file (trans-unit ID).
50+
51+
Together with the currently set `Locale`, Package Key and Source Name identify the exact XLIFF file for translation thusly:
52+
```
53+
resource://{Package Key}/Private/Translations/{Locale}/{Source Name}.xlf
54+
```
55+
56+
So, the address `Neos.Neos.Ui:Main:errorBoundary.title` would lead us to:
57+
```
58+
resource://Neos.Neos.Ui/Private/Translations/de/Main.xlf
59+
```
60+
61+
Within the XLIFF-file, the trans-unit ID identifies the exact translation to be used:
62+
```xml
63+
<?xml version="1.0" encoding="UTF-8"?>
64+
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
65+
<file original="" product-name="Neos.Neos.Ui" source-language="en" target-language="de" datatype="plaintext">
66+
<body>
67+
<!-- ... -->
68+
<!-- ↓ This is the one -->
69+
<trans-unit id="errorBoundary.title" xml:space="preserve">
70+
<source>Sorry, but the Neos UI could not recover from this error.</source>
71+
<target>Es tut uns leid, aber die Neos Benutzeroberfläche konnte von diesem Fehler nicht wiederhergestellt werden.</target>
72+
</trans-unit>
73+
<!-- ... -->
74+
</body>
75+
</file>
76+
</xliff>
77+
```
78+
79+
If no translation can be found, `translate` will return the given `fallback` string.
80+
81+
Translations (and fallbacks) may contain placeholders, like:
82+
```
83+
All changes from workspace "{0}" have been discarded.
84+
```
85+
86+
Placeholders may be numerically indexed (like the one above), or indexed by name, like:
87+
```
88+
Copy {source} to {target}
89+
```
90+
91+
For numerically indexed placeholders, you can pass an array of strings to the `parameters` argument of `translate`. For named parameters, you can pass an object with string values and keys identifying the parameters.
92+
93+
Translations may also have plural forms. `translate` uses the [`Intl` Web API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) to pick the currect plural form for the current `Locale` based on the given `quantity`.
94+
95+
Fallbacks can also provide plural forms, but will always treated as if we're in locale `en-US`, so you can only provide two different plural forms.
96+
97+
#### Arguments
98+
99+
| Name | Description |
100+
|-|-|
101+
| `fullyQualifiedTranslationAddressAsString` | The translation address for the translation to use, e.g.: `"Neos.Neos.Ui:Main:errorBoundary.title"` |
102+
| `fallback` | The string to return, if no translation can be found under the given address. If a tuple of two strings is passed here, these will be treated as singular and plural forms of the translation. |
103+
| `parameters` | Values to replace placeholders in the translation with. This can be passed as an array of strings (to replace numerically indexed placeholders) or as a `Record<string, string>` (to replace named placeholders) |
104+
| `quantity` | The quantity is used to determine which plural form (if any) to use for the translation |
105+
106+
#### Examples
107+
108+
##### Translation without placeholders or plural forms
109+
110+
```typescript
111+
translate('Neos.Neos.Ui:Main:insert', 'insert');
112+
// output (en): "insert"
113+
```
114+
115+
##### Translation with a numerically indexed placeholder
116+
117+
```typescript
118+
translate(
119+
'Neos.Neos:Main:workspaces.allChangesInWorkspaceHaveBeenDiscarded',
120+
'All changes from workspace "{0}" have been discarded.',
121+
['user-admin']
122+
);
123+
124+
// output (en): All changes from workspace "user-admin" have been discarded.
125+
```
126+
127+
##### Translation with a named placeholder
128+
129+
```typescript
130+
translate(
131+
'Neos.Neos.Ui:Main:deleteXNodes',
132+
'Delete {amount} nodes',
133+
{amount: 12}
134+
);
135+
136+
// output (en): "Delete 12 nodes"
137+
```
138+
139+
##### Translations with placeholders and plural forms
140+
141+
```typescript
142+
translate(
143+
'Neos.Neos.Ui:Main:changesPublished',
144+
['Published {0} change to "{1}".', 'Published {0} changes to "{1}".']
145+
[1, "live"],
146+
1
147+
);
148+
// output (en): "Published 1 change to "live"."
149+
150+
translate(
151+
'Neos.Neos.Ui:Main:changesPublished',
152+
['Published {0} change to "{1}".', 'Published {0} changes to "{1}".']
153+
[20],
154+
20
155+
);
156+
// output (en): "Published 20 changes to "live"."
157+
```
158+
159+
### `initializeI18n`
160+
161+
```typescript
162+
async function initializeI18n(): Promise<void>;
163+
```
164+
165+
> [!NOTE]
166+
> Usually you won't have to call this function yourself. The Neos UI will
167+
> set up I18n automatically.
168+
169+
This function loads the translations from the translations endpoint and makes them available globally. It must be run exactly once before any call to `translate`.
170+
171+
The exact URL of the translations endpoint is discoverd via the DOM. The document needs to have a link tag with the id `neos-ui-uri:/neos/xliff.json`, with the following attributes:
172+
```html
173+
<link
174+
id="neos-ui-uri:/neos/xliff.json"
175+
href="https://mysite.example/neos/xliff.json?locale=de-DE"
176+
data-locale="de-DE"
177+
data-locale-plural-rules="one,other"
178+
>
179+
```
180+
181+
The `ApplicationView` PHP class takes care of rendering this tag.
182+
183+
### `setupI18n`
184+
185+
```typescript
186+
function setupI18n(
187+
localeIdentifier: string,
188+
pluralRulesAsString: string,
189+
translations: TranslationsDTO
190+
): void;
191+
```
192+
193+
This function can be used in unit tests to set up I18n.
194+
195+
#### Arguments
196+
197+
| Name | Description |
198+
|-|-|
199+
| `localeIdentifier` | A valid [Unicode Language Identifier](https://www.unicode.org/reports/tr35/#unicode-language-identifier), e.g.: `de-DE`, `en-US`, `ar-EG`, ... |
200+
| `pluralRulesAsString` | A comma-separated list of [Language Plural Rules](http://www.unicode.org/reports/tr35/#Language_Plural_Rules) matching the locale specified by `localeIdentifier`. Here, the output of [`\Neos\Flow\I18n\Cldr\Reader\PluralsReader->getPluralForms()`](https://neos.github.io/flow/9.0/Neos/Flow/I18n/Cldr/Reader/PluralsReader.html#method_getPluralForms) is expected, e.g.: `one,other` for `de-DE`, or `zero,one,two,few,many` for `ar-EG` |
201+
| `translations` | The XLIFF translations in their JSON-serialized form |
202+
203+
##### `TranslationsDTO`
204+
205+
```typescript
206+
type TranslationsDTO = {
207+
[serializedPackageKey: string]: {
208+
[serializedSourceName: string]: {
209+
[serializedTransUnitId: string]: string | string[]
210+
}
211+
}
212+
}
213+
```
214+
215+
The `TranslationDTO` is the payload of the response from the translations endpoint (see: [`\Neos\Neos\Controller\Backend\BackendController->xliffAsJsonAction()`](https://neos.github.io/neos/9.0/Neos/Neos/Controller/Backend/BackendController.html#method_xliffAsJsonAction)).
216+
217+
###### Example:
218+
219+
```jsonc
220+
{
221+
"Neos_Neos_Ui": { // <- Package Key with "_" instead of "."
222+
"Main": { // <- Source name with "_" instead of "."
223+
224+
// Example without plural forms
225+
"errorBoundary_title": // <- trans-unit ID with "_" instead of "."
226+
"Sorry, but the Neos UI could not recover from this error.",
227+
228+
// Example with plural forms
229+
"changesDiscarded": [ // <- trans-unit ID with "_" instead of "."
230+
"Discarded {0} change.",
231+
"Discarded {0} changes."
232+
]
233+
}
234+
}
235+
}
236+
```
237+
238+
### `teardownI18n`
239+
240+
```typescript
241+
function teardownI18n(): void;
242+
```
243+
244+
This function must be used in unit tests to clean up when `setupI18n` has been used.

0 commit comments

Comments
 (0)