Skip to content

Translations

Messages that reach API clients are translated with Django's gettext, in the language Django activates for the request (LocaleMiddleware, Accept-Language, translation.override). That covers permission denials, domain errors, validation messages from pagination, shaping, bulk and import, and contrib responses. ninja-devx ships English defaults and no catalog; add a catalog for each language you support.

MIDDLEWARE = [..., "django.middleware.locale.LocaleMiddleware", ...]
LANGUAGES = [("en", "English")]

Your own messages

  • Permissions. message is looked up with gettext when the denial is sent, so mark it for extraction and translate it in your project's catalog:

    from django.utils.translation import gettext_noop
    
    
    class IsWorkspaceAdmin(BasePermission[object]):
        message = gettext_noop("Workspace admins only.")
    
  • Domain errors. default_message works the same way. A message passed explicitly (NotFound(_("Order %(id)s is gone") % {...})) is used as it is, so translate it where you raise it.

  • Ninja's own responses ("Unauthorized", "Not Found" for Http404) come from Ninja and are not translated.

Adding a language

Catalogs live in src/ninja_devx/locale/<language>/LC_MESSAGES/. They are managed without GNU gettext:

uv run python tools/messages.py --language de   # creates django.po + django.mo
# fill the msgstr lines, run it again, and check:
uv run python tools/messages.py --check         # discovers existing catalogs and verifies them

--language is repeatable. Without it, tools/messages.py updates the catalogs that already exist; --check fails on stale or untranslated ones.

For an application's own strings, use Django's standard makemessages/compilemessages and point LOCALE_PATHS at your project's locale directory. The messages the package translates are listed in the settings guide and the errors reference.