Skip to content

5. Translating

Kewos contains no hard-coded visible string, neither on the server nor in the interface. Everything goes through a key and one translation file per language.

Your module follows the same rule. Without it the engine falls back on the code’s technical names: a column called published_year, a value shown as worn, a section titled section.work. Nothing is broken, it is simply unreadable.

The file

i18n/en.json:

{
"module.library": "Library",
"model.library_title": "Catalogue",
"model.library_copy": "Copies",
"model.library_member": "Members",
"model.library_loan": "Loans",
"section.work": "The work",
"section.reference": "Reference",
"library_title.name": "Title",
"library_title.author": "Author",
"library_title.isbn": "ISBN",
"library_title.published_year": "Published",
"library_title.genre": "Genre",
"library_title.genre.fiction": "Fiction",
"library_title.genre.science": "Science",
"library_loan.state": "State",
"library_loan.state.out": "Out",
"library_loan.state.returned": "Returned",
"library_loan.state.lost": "Lost",
"workflow.check_in": "Check in",
"workflow.declare_lost": "Declare lost",
"group.library_reader": "Reader",
"group.library_librarian": "Librarian"
}

Then i18n/fr.json with exactly the same keys and other values.

The keys, once and for all

ShapeWhat it names
module.<module>The module in the launcher.
model.<model>The model in menus and screen titles.
<model>.<field>A field, everywhere it appears.
<model>.<field>.<value>A selection value.
section.<key>A form section title.
workflow.<transition>A state transition button.
group.<group>A permission group.
<model>.dashboard.<key>A dashboard indicator or chart.

What it changes

The loan kanban, fully translated: columns, labels and values

The kanban columns read “Out”, “Returned”, “Lost”; the module is called “Library”; the cards say “Member” and “Due back”. You touched no screen: one mapping file, and everything falls into place.

The other languages

Add i18n/de.json, i18n/es.json with the same keys. The user’s language decides, and a key missing from one file falls back to the default language.

The second directory, the one for server messages

i18n/ translates the interface: what the engine renders from your declarations, the field names, the values, the buttons. It does not translate the sentences your code produces.

A business error message, an email subject, a line on a PDF: those go through t("key") on the server, and the server does not read i18n/. It reads a second directory:

modules/library/
├── i18n/ <- the interface (the view engine)
│ ├── fr.json
│ └── en.json
└── translations/ <- your messages, resolved by t()
├── fr.json
└── en.json
translations/en.json
{ "library.error.already_out": "This copy is already out." }
raise BusinessRuleError(t("library.error.already_out"))

Same rule as above: the same keys in every language, and a test comparing the key sets.

The same file, in KM

A KM module carries its translations inside its file, one block per language:

translate en {
model.library_title "Catalogue"
library_title.name "Title"
library_title.genre.fiction "Fiction"
section.work "The work"
workflow.check_in "Check in"
}
translate fr {
model.library_title "Catalogue"
library_title.name "Titre"
library_title.genre.fiction "Fiction"
section.work "L'œuvre"
workflow.check_in "Rendre"
}

The keys are exactly the same, and this step’s rule does not move: a key present in one language and absent from another is a defect, whatever the format.

Next

The rights.