Engineering Notes

Note 06 / Internationalisation

Django i18n: check language, URLs and metadata

Translated text is not enough. Language switching, internal links and SEO metadata must describe the same page edition.

One language, several conflicting signals

An Italian page can show translated text while retaining German internal links, a German canonical and code comments in another language. A screen reader may also use the wrong pronunciation if lang does not match the content. A correctly compiled catalogue says little about these problems.

First define which pages are fully translated. Only those editions belong in language switching, hreflang and the sitemap. A German fallback on an English list needs a clear label and lang="de" on the German text; it is not an English article translation.

Let Django resolve the route

For fully translated pages, a named route inside i18n_patterns can be the URL source. With prefix_default_language=False, German keeps the original path while Italian and English receive their prefixes. Run reverse() inside the corresponding override() context. The mapping remains correct if the URL structure changes.

Python · URL metadata for fully translated pages
from django.urls import reverse
from django.utils.translation import override

LANGUAGES = ("de", "it", "en")


def page_metadata(route_name, language, host="https://codlab.de"):
    if language not in LANGUAGES:
        raise ValueError("unsupported language")
    # Fully translated pages, using real Django routes.
    urls = {}
    for code in LANGUAGES:
        with override(code):
            urls[code] = host + reverse(route_name)
    return {
        "canonical": urls[language],
        "alternates": {**urls, "x-default": urls["de"]},
        "inLanguage": language,
    }

The served page as a contract

For each language, check HTTP status, html lang, a single main heading, description, canonical and og:url. Every hreflang must point to an existing translation of the same page and link back reciprocally. x-default may identify the German edition. TechArticle inLanguage must match the edition.

The local test executes this function with the clone’s real Engineering routes. It checks the three URLs, an unsupported language and restoration of the previously active language. A separate browser run checks rendered metadata and internal links. None of these checks replaces reading the translation.

Catalogues and complete editions

gettext works well for the shared interface. Long editorial content can have its own versioned editions, provided a missing edition fails visibly instead of silently falling back to German. Compile changed PO files before testing; a stale MO file can hide correct source text.

The example assumes the same named route for three complete editions. A partly translated blog needs a registry of available translations and, where necessary, language-specific slugs. Caches must also account for language. Check code comments and displayed dates too: both are visible content.