date-website

Translation System Notes

Overview

The project has three translation layers that work together:

  1. Django locale files translate static UI strings from templates, forms, Python code and JavaScript code.
  2. django-modeltranslation adds per-language database fields for dynamic content such as event titles and static-page navigation labels.
  3. Language-aware routing and cookies decide which language a request should render in.

Swedish (sv) is the default language. The exact non-default languages exposed at runtime depend on the active association settings module.

Supported Languages

Important settings:

When ENABLE_LANGUAGE_FEATURES=False:

Layer 1: Static UI Translations

Use Django’s normal i18n system for strings that live in code or templates.

Typical sources:

Locale files live here:

Common 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:

Using translations in JavaScript

For 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>

Layer 2: Dynamic Content Translations

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:

Examples of translated model fields:

When a field is registered for translation, django-modeltranslation creates language-specific columns such as:

The 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.

Adding Translation Support to a New Model

  1. Add or update the app’s translation.py.
  2. Register the model with TranslationOptions.
  3. List the base field names in fields = (...).
  4. Create and commit the migration that adds the translated database columns.
  5. Backfill existing values into the new language-specific fields if needed.

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:

Without that backfill step, old content may exist only in the original field and appear missing in translated admin tabs.

CKEditor5 and Other Custom Field Types

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.

Layer 3: Language Selection and Canonical URLs

Routing and language state are handled by Django plus a few project-specific helpers.

Key files:

How it works:

Precedence rules:

  1. Language cookie wins when present
  2. Accept-Language is used only when USE_ACCEPT_LANGUAGE_HEADER=True
  3. The project falls back to Swedish

Linking Correctly in Templates and Stored URLs

For internal links:

Examples:

<a href="{% url 'events:index' %}">...</a>
<a href="{{ '/news/'|localized_url }}">...</a>
<a href="{{ page.url|localized_url }}">{{ page.title }}</a>

For external links:

date.language_utils.localize_url() already skips those cases.

Admin Behavior

The admin adapts based on ENABLE_LANGUAGE_FEATURES.

When enabled:

When disabled:

Relevant files:

Tests and Local Development

The test settings explicitly enable language features and ensure locale files are compiled.

Relevant files:

core.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:

Translating static strings

  1. Mark strings with Django translation helpers.
  2. Depending on what you are translating, run one or both of:
    • django-admin makemessages -l sv -l en -l fi -d djangojs -i "**/vendor/**" -i "core/static/**" for JavaScript code
    • django-admin makemessages -l sv -l en -l fi for Python code/templates
  3. Edit the .po files.
  4. Run django-admin compilemessages.
  5. Smoke-test pages in Swedish and at least one non-default language on the same unprefixed URL.

Translating existing model content

  1. Register the model fields in translation.py.
  2. Create and apply migrations.
  3. Run python manage.py update_translation_fields.
  4. Review the translated admin UI.
  5. Add or update test coverage.

Adding localized navigation or stored URLs

  1. Store internal paths as relative URLs when possible, for example /news/.
  2. Render them through localized_url.
  3. Verify switching languages keeps the target on the same canonical unprefixed path.

Common Pitfalls