Backend internationalization (i18n)
Backend internationalization covers a few translated database fields. All interface text is translated in the frontend: to fix or add a translation of anything shown in the UI, see the HomePort i18n guide.
Waldur's own backend messages (API errors, notifications) are not translated and are returned in English
(see Spelling).
Messages that come from Django and Django REST framework themselves, such as This field is required., use
the translations bundled with those libraries and follow the request language.
Translated fields
These fields are stored once per language with django-modeltranslation:
| Model | Fields | Registered in |
|---|---|---|
Role |
description |
waldur_core/permissions/translation.py |
Category |
title, description |
waldur_mastermind/marketplace/translation.py |
CategoryGroup |
title, description |
waldur_mastermind/marketplace/translation.py |
The translation.py files are the source of truth if this table falls behind.
Each translated field gets one database column per language in LANGUAGES: description_en,
description_et and so on. A regional code is spelled with an underscore in the column name, so en-gb is
stored in description_en_gb. Reading description returns the column for the language of the current
request, which LocaleMiddleware takes from the Accept-Language header. An empty column falls back to
English.
Never build a column name by hand as f"{field}_{code}": it is wrong for a regional code. Use
get_localized_field_names(field) from waldur_core.core.serializers, which returns the name of every
language's column the way modeltranslation builds it. It lives there rather than in waldur_core.core.utils
because the settings modules import core.utils, and modeltranslation.settings reads LANGUAGES once,
when it is first imported: imported from there, it sees Django's default language list instead of Waldur's.
Translations are entered by staff:
- Role descriptions — in the role description dialog of the roles administration, or through the
update_descriptionsaction of the roles API, which accepts one field per language inLANGUAGES(description_et,description_en_gb, ...). - Categories and category groups — in the Django admin, which shows one input per language.
Making another field translatable
-
Register the field in the app's
translation.py:1 2
class CategoryTranslationOptions(TranslationOptions): fields = ("title", "description") -
Run
makemigrationsfor the app. The migration adds one column per language. - To accept the per-language values through the API, use
TranslatedModelSerializerMixinfromwaldur_core.core.serializers. It adds a serializer field for each language's column, named asget_localized_field_names()returns them.
Adding a language
- Add the language to
LANGUAGESinwaldur_core/server/base_settings.py. This alone makes it available: the API accepts its translated fields, and administrators can enable it under Administration -> Look and feel. - Optionally, add its code to
LANGUAGE_CHOICESinwaldur_core/server/constance_settings.py. That list is only the default set of enabled languages for a deployment that has never chosen its own.en-gb,mkandsqare deliberately not in it, so existing deployments do not suddenly offer them. - Run
makemigrationsforpermissionsandmarketplace. The migrations add the new language's column to every translated field. - In HomePort:
- add
locales/<code>.json, in canonical case (nl-BE.json). The backend sends Django's lowercase code (nl-be); the locale loader converts it. A spelling variant of the source language, such asen-GB, is not translated by hand: it is generated, see the HomePort i18n guide. - map the language code, as the backend sends it, to the country whose flag the selector shows, in both
LanguageCountry(src/navigation/header/LanguageSelectorDropdown.tsx) andLANGUAGE_COUNTRY(packages/ui/src/LanguageMenu/languageFlags.ts). - add a date-fns locale loader for the calendar in
packages/ui/src/DatePicker/useCalendarLocale.ts.
- add
Do not use modeltranslation's sync_translation_fields command: it adds the columns directly to the database,
outside the migration history.
Spelling
User-visible strings are written in US English: gettext strings, model help_text and verbose_name,
choice labels, feature descriptions, settings descriptions and API descriptions. The frontend generates its
British English locale (en-GB) from US source strings, and some of these strings reach it verbatim
(settings and feature descriptions become HomePort's SettingsDescription.ts and FeaturesDescription.ts),
so a British spelling would show to US users and never be converted for British ones.
scripts/check_us_spelling.py src/ reports British forms in these strings, and the Check US spelling CI
job runs it. Comments, docstrings, identifiers, migrations and tests are not checked; identifiers such as the
show_organisation_reporting feature key are API names and keep their spelling. A quoted single word, such
as the 'Cancelled' state, names a value and is not checked either.
Changing only the help_text, verbose_name or choice labels of a model field produces no SQL. Rather than
adding a migration that only restates the text, edit the text in the operations that already define the field:
the latest CreateModel, AddField or AlterField for it, in a squashed migration and in the migrations it
replaces alike. makemigrations --check then confirms that the state matches.