Module Bundles
Every text your module declares lives in its own bundle: an app's i18n/i18n.properties, a library's messagebundle.properties, and the locale files beside them (i18n_de.properties, i18n_en.properties, …). You ship the bundle with the module. Nothing else holds the text: the PHP side declares the thing, the bundle names it.
The keys
The SDK fixes the key for every kind of declared text, so it can find the text without being told. The base bundle must carry every required key; a locale file that lacks one shows the English text.
| What | Key | Required | Checked by |
|---|---|---|---|
| The app | appTitle, appDescription | yes (Core) | ui5:sync |
Any other artifact whose TITLE or DESCRIPTION is {i18n>key} | that key | no; without it the literal stays | ui5:sync |
An Access, Act or Read ability | abilities.<type>.<ability>.title / .description | yes | ui5:sync · Permission Levels |
A See ability | the view's keys | — | View Visibility |
| A role | roles.<role>.title / .description | yes | ui5:sync · Permission Levels |
| A setting | settings.<key>.title / .description | yes | ui5:sync · Reading & Writing |
| A Customizing row | customizing.<catalog>.<code>.<column> | yes, for each translatable column that has a literal | ui5:sync · Customizing |
| A navigation entry | route.<key>.label (and .title, .description, .icon) | the label | ui5:nav · Navigation |
| A Weave doorway | weave.<concept>.label | yes | the registry build · LUX Weave |
| A message | any key you name in a MessageText | — | at runtime: an unknown key shows as the key · Actions & Messages |
A field in a 422 | fields.<field>, validation.<field>.<rule> | no | Mutating Actions |
The texts sit in the bundle of the module that declares the thing: an ability in the bundle of the app that carries the #[Act], a Weave label in the bundle of the app that declares the #[Weave], not in the hub's.
What stops when a key is missing
None of the required keys falls back silently. A missing one stops the step that needs it and names the key and the file it belongs in:
ui5:syncstops at the first missing synced text: app titles, abilities, roles, settings, Customizing rows.ui5:navstops on a missingroute.<key>.labeland writes no cache.- The registry stops on a missing
weave.<concept>.labelwhile it is built.
To see every missing synced text at once, before a sync:
php artisan ui5:i18nIt writes nothing, lists each missing key with its file and exits non-zero, so a CI step fails before a deploy would. It runs the same check ui5:sync runs, but collects every missing key instead of stopping at the first.
Which copy of the bundle is read
The server reads what the app ships. A packaged app is read from its published resources/ui5/i18n/. An app under a workspace source is read from webapp/ in the browser but from dist/ on the command line, so a key you just typed into webapp/ is invisible to ui5:sync, ui5:nav and ui5:i18n until the app is built. The order is fixed:
# 1. author the keys in webapp/i18n/*.properties
npm run build # 2. webapp/ -> dist/
php artisan ui5:app <Name> --refresh # 3. dist/ -> the module's resources/
php artisan ui5:sync # 4. the synced texts
php artisan ui5:nav # 5. the navigation cacheTranslators' files
The base bundle is yours and must be complete. The locale files are your translators', and each may be partial: a key it lacks shows in English, and, failing that, from the base bundle. Ship i18n_en.properties even when your base bundle is English, so the English link of the chain exists.
Apostrophes
UI5 reads a bundle value as a MessageFormat pattern. A lone ' opens a literal section that swallows every placeholder after it: Can't save {0} shows Cant save {0}. Write '' for an apostrophe in any value that carries a {0}, and prefer ’ (U+2019) in prose, which needs no escaping.