Guide: hreflang and multilingual

django-seo-suite integrates with Django's i18n_patterns to produce consistent hreflang alternates on the page and in the sitemap. Both use the same LANGUAGES configuration as their source of truth.


How it works

build_hreflang_alternates calls Django's translate_url for every language in settings.LANGUAGES and produces a list of HreflangAlt(lang, absolute_url) named tuples. Pass this list to SeoMetadata.partial(hreflang=...) and the {% seo_hreflang %} tag renders the <link rel="alternate" hreflang="..."> elements.


Setup

Ensure your URL configuration uses i18n_patterns:

# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path

urlpatterns = i18n_patterns(
    path("articles/<slug:slug>/", ArticleDetail.as_view(), name="article-detail"),
    path("articles/", ArticleList.as_view(), name="article-list"),
)

Set LANGUAGES and LANGUAGE_CODE in settings:

LANGUAGES = [
    ("en", "English"),
    ("fr", "French"),
    ("de", "German"),
]
LANGUAGE_CODE = "en"
USE_I18N = True

Adding hreflang to a detail view

from django.views.generic import DetailView
from seo_suite.mixins import SeoViewMixin
from seo_suite.hreflang import build_hreflang_alternates
from seo_suite.metadata import SeoMetadata

from .models import Article


class ArticleDetail(SeoViewMixin, DetailView):
    model = Article

    def get_seo_metadata(self, context=None):
        alternates = build_hreflang_alternates(
            self.request.path,
            self.request,
        )
        return SeoMetadata.partial(hreflang=alternates)

For a request at /en/articles/my-article/ with LANGUAGES = [("en", …), ("fr", …)] and LANGUAGE_CODE = "en", this emits:

<link rel="alternate" hreflang="en" href="https://example.com/en/articles/my-article/">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/articles/my-article/">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/articles/my-article/">

Controlling x-default

The x-default alternate is added automatically when SEO_SUITE["HREFLANG_X_DEFAULT"] is True (the default). It points to the URL for settings.LANGUAGE_CODE.

To disable it globally:

SEO_SUITE = {
    "HREFLANG_X_DEFAULT": False,
}

To disable it for a single call:

alternates = build_hreflang_alternates(
    self.request.path,
    self.request,
    include_x_default=False,
)

Custom language list

Pass a languages argument to restrict or reorder the alternates:

alternates = build_hreflang_alternates(
    self.request.path,
    self.request,
    languages=["en", "fr"],  # only these two, regardless of LANGUAGES
)

Adding hreflang to a list view

from django.views.generic import ListView
from seo_suite.mixins import SeoListViewMixin
from seo_suite.hreflang import build_hreflang_alternates
from seo_suite.metadata import SeoMetadata


class ArticleList(SeoListViewMixin, ListView):
    model = Article
    seo_title = "Articles"

    def get_seo_metadata(self, context=None):
        alternates = build_hreflang_alternates(self.request.path, self.request)
        return SeoMetadata.partial(hreflang=alternates)

Note

hreflang is a replace-wins field: when a higher-priority layer provides a hreflang list, it replaces any list from lower layers entirely. Only set hreflang in one layer per page (usually the view).


Template rendering

{% seo_head %} renders hreflang automatically. The tag used internally is {% seo_hreflang %}, which you can use standalone:

{% load seo_suite %}
<head>
  {% seo_hreflang %}
</head>

Sitemap hreflang

For hreflang in sitemaps, use Django's built-in sitemap i18n support together with SeoSitemap. See the Sitemaps guide for a complete example.