Customizing Texts
A Customizing catalog's human-readable columns, the ones the entry lists in translatable(), are stored as their source-language literal. That keeps them queryable, sortable and always available as a fallback. What the user sees comes from the bundle.
The key
customizing.<catalog>.<code>.<column>For example customizing.document_types.invoice.name. The key lives in the bundle of the module that declares the row: its app's i18n.properties, or its library's messagebundle.properties, shipped with your package, base file and every locale:
# i18n/i18n.properties
customizing.document_types.invoice.name=Invoice
# i18n/i18n_de.properties
customizing.document_types.invoice.name=RechnungWhen two modules add rows to one catalog, each brings the keys of its own rows. A field whose literal is null needs no key. A key missing from the base bundle stops ui5:sync; php artisan ui5:i18n lists every missing one beforehand (Module Bundles).
The table reads in the user's language
ui5:sync reads the keys with the rows and writes the texts, in every language of the bundle, to sdk_texts, beside the catalog projection. Name the Eloquent model of your catalog once, with the entry class that declares its rows:
// config/ui5.php
'i18n' => [
'customizing' => [
\App\Models\DocumentType::class => \App\Customizing\DocumentType::class,
],
],From then on the catalog's columns arrive translated, with no reader code: the request's language, then English, then the base bundle, and the literal where a row has no text. An OData set behind discoverModel translates too, and $filter, $orderby and $search run on the text: name eq 'Rechnung' finds the invoice under German.
A query without the model reads the literals. How a raw-SQL entity set joins the text is on Synced Texts.
The SDK's own catalogs
The SDK's partner catalogs, relationship types and partner roles, are wired this way already. Their texts ship with the SDK; ui5:sync writes them with the rows.