The project has three translation layers that work together:
django-modeltranslation adds per-language database fields for dynamic content such as event titles and static-page navigation labels.Swedish (sv) is the default language. The exact non-default languages exposed at runtime depend on the active association settings module.
svsv, en, fisv, enficore/settings/common.py, core/settings/date.pyImportant settings:
LANGUAGE_CODE = "sv"ALL_LANGUAGES = (("sv", "Svenska"), ("en", "English"), ("fi", "Suomi"))DATE_LANGUAGES = (("sv", "Svenska"), ("en", "English")) overrides DaTe’s active language listENABLE_LANGUAGE_FEATURES controls whether the project exposes the active association’s full language set or only SwedishLOCALE_PATHS = ("locale",) points Django to the .po and .mo filesWhen ENABLE_LANGUAGE_FEATURES=False:
LANGUAGES is reduced to Swedish onlysvUse Django’s normal i18n system for strings that live in code or templates.
Typical sources:
{% trans %} or {% blocktrans %}gettext, gettext_lazy, or _()gettext, _(), and variantsLocale files live here:
locale/sv/LC_MESSAGES/django.polocale/en/LC_MESSAGES/django.polocale/fi/LC_MESSAGES/django.polocale/sv/LC_MESSAGES/djangojs.polocale/en/LC_MESSAGES/djangojs.polocale/fi/LC_MESSAGES/djangojs.poCommon workflow:
django-admin makemessages -l sv -l en -l fi
django-admin makemessages -l sv -l en -l fi -d djangojs -i "**/vendor/**" -i "core/static/**"
django-admin compilemessages
What this does:
makemessages scans templates and Python files for translatable strings and updates the .po filesmakemessages with the domain set to djangojs scans JavaScript source files for translatable strings and updates the djangojs.po files, vendored and generated static files should be ignoredcompilemessages converts .po files into .mo files that Django actually loads at runtimeFor gettext and its variants to be usable in JavaScript code the Django translation catalog has to be loaded first.
This can be done by including a script tag in the head of a page:
<script src="{% url 'javascript-catalog' %}"></script>
Static UI strings are not enough for this project because editors need translated versions of database content. That is handled by django-modeltranslation.
The project registers translated fields in these files:
events/translation.pynews/translation.pypolls/translation.pystaticpages/translation.pyExamples of translated model fields:
Event.title, Event.contentPost.title, Post.contentCategory.nameQuestion.question_text, Choice.choice_textStaticPageNav.category_nameStaticUrl.titleWhen a field is registered for translation, django-modeltranslation creates language-specific columns such as:
title_svtitle_entitle_fiThe shared schema still includes *_fi columns for associations that use Finnish. Keep the modeltranslation field registration language set stable, even if an association exposes a smaller runtime language set.
translation.py.TranslationOptions.fields = (...).Example:
from core.modeltranslation import get_translation_languages
from modeltranslation.translator import register, TranslationOptions
from myapp.models import Thing
TRANSLATION_LANGUAGES = get_translation_languages()
@register(Thing)
class ThingTranslationOptions(TranslationOptions):
fields = ("title", "content")
languages = TRANSLATION_LANGUAGES
After adding a new translated field to an existing model:
*_sv, *_en, and *_fi columns existpython manage.py update_translation_fields to copy the current base-field values into the translated columns when appropriateWithout that backfill step, old content may exist only in the original field and appear missing in translated admin tabs.
This project uses CKEditor5-backed content in places that are also translated. django-modeltranslation needs custom-field support enabled for that to work cleanly.
The setting already exists in core/settings/common.py:
MODELTRANSLATION_CUSTOM_FIELDS = (
"CKEditor5Field",
)
If you introduce another non-standard field type that should be translated, update that setting and verify the admin/widget behavior.
Routing and language state are handled by Django plus a few project-specific helpers.
Key files:
core/urls/common.pydate/views.pydate/middleware.pydate/language_utils.pystaticpages/templatetags/localized_urls.pyHow it works:
set_language stores the user’s choice in Django’s language cookieLangMiddleware activates the language resolved from the cookie, then the default languageUSE_ACCEPT_LANGUAGE_HEADER=True lets an association use Accept-Language when no supported cookie value is setlocalize_url() normalizes internal paths to the canonical unprefixed formPrecedence rules:
Accept-Language is used only when USE_ACCEPT_LANGUAGE_HEADER=TrueFor internal links:
reverse(...) in Python{% url %} in templateslocalized_url template filter for internal paths stored in the database or hardcoded as plain stringsExamples:
<a href="{% url 'events:index' %}">...</a>
<a href="{{ '/news/'|localized_url }}">...</a>
<a href="{{ page.url|localized_url }}">{{ page.title }}</a>
For external links:
localized_url on https://..., mailto:..., tel:..., anchors, or JavaScript URLsdate.language_utils.localize_url() already skips those cases.
The admin adapts based on ENABLE_LANGUAGE_FEATURES.
When enabled:
TabbedTranslationAdmin or TranslationTabularInlineWhen disabled:
Relevant files:
events/admin.pynews/admin.pypolls/admin.pystaticpages/admin.pytemplates/common/admin/base_site.htmlThe test settings explicitly enable language features and ensure locale files are compiled.
Relevant files:
core/settings/test.pycore/translation_compiler.pydate/tests.pycore.translation_compiler.ensure_compiled_translations() exists so tests can run without depending on an external compilemessages binary at test time.
For translation-content QA outside the test suite, you can also run:
python scripts/validate_translations.py
That script fails if a required locale catalog is missing, contains fuzzy entries, or still has untranslated strings.
When you change translation behavior, cover at least these cases:
Accept-Language headerlocalized_urldjango-admin makemessages -l sv -l en -l fi -d djangojs -i "**/vendor/**" -i "core/static/**" for JavaScript codedjango-admin makemessages -l sv -l en -l fi for Python code/templates.po files.django-admin compilemessages.translation.py.python manage.py update_translation_fields./news/.localized_url.compilemessages after editing .po filesENABLE_LANGUAGE_FEATURES=False