Multilingual sitemap alternates design
Status
This is the accepted design for multilingual sitemap alternates. It extends the existing sitemap and explicit page-translation contracts without adding template-side markup.
Decision
Sitemap alternates are an opt-in extension of sitemap generation:
[sitemap]
hreflang = truehreflang accepts only true. Omitting it retains the existing sitemap
output, and false is rejected as a redundant restatement of that default.
As with every sitemap setting, [sitemap] requires site.origin.
When enabled, Heine adds xmlns:xhtml="http://www.w3.org/1999/xhtml" to each
generated sitemap URL-set. It adds <xhtml:link rel="alternate" .../>
children to an authored page's sitemap entry only when that page has at least
one sitemap-included counterpart in the same explicit translationid group.
For example, two counterparts produce identical alternate sets:
<url>
<loc>https://example.org/guide/index.html</loc>
<xhtml:link rel="alternate" hreflang="en"
href="https://example.org/guide/index.html"/>
<xhtml:link rel="alternate" hreflang="de"
href="https://example.org/guide/de/index.html"/>
</url>
<url>
<loc>https://example.org/guide/de/index.html</loc>
<xhtml:link rel="alternate" hreflang="en"
href="https://example.org/guide/index.html"/>
<xhtml:link rel="alternate" hreflang="de"
href="https://example.org/guide/de/index.html"/>
</url>The <loc> remains the entry's own canonical URL. Every alternate set is
locale-sorted and includes that entry itself. Locale codes are the configured,
validated BCP-47 locale codes. URLs come from the same checked origin,
base-path, and literal output-path construction used by the existing sitemap.
Relationship boundary
translationid is the sole source of alternate relationships. Heine never
infers a counterpart from matching page IDs, paths, titles, locale prefixes, or
directory structure.
The resolved sitemap relationship contains only rendered authored pages that
are included in the sitemap. A page excluded with sitemap = false is not
named as an alternate by another locale, because that would undermine its
explicit sitemap exclusion. The remaining sitemap members form a complete,
reciprocal subset. Partial groups are normal.
A translationid group with fewer than two sitemap-included pages has no
alternate relationship, so its entry has no xhtml:link children. A self-only
annotation would add bytes without identifying an alternate page.
An authored paginated listing may participate through its first, authored URL. Its later pager URLs are generated views rather than translated authored pages, so they never receive alternates. Taxonomy, Series, collection, search, feed, and other generated resources likewise do not participate.
This feature does not add x-default. That value requires an authored policy
for a locale-neutral fallback target, which translationid does not express.
It also does not inject HTML-head annotations or HTTP Link headers. Sitemap
annotations are the one selected representation; templates remain responsible
for any later HTML-head contract.
Resolution and output
After page relationships and pagination are resolved, sitemap generation builds one locale-sorted alternate catalog from sitemap-included authored task URLs. It retains only the first URL of an authored paginated listing. The sitemap module receives resolved output identities, not raw paths or template values.
Each alternate URL is checked against the existing 2,048-character canonical
URL limit. XML text and attributes use the existing XML escaping operation.
Alternate elements count toward serialized sitemap bytes, but not toward the
Sitemap Protocol's URL-entry count. The XHTML namespace is emitted whenever
hreflang = true, which keeps the size calculation independent of the
contents of an individual split child sitemap. If one URL entry, including its
alternate set, cannot fit within the conservative byte limit, Heine reports
that canonical URL rather than emitting an oversized sitemap.
Quick builds do not update sitemaps and therefore do not update alternate annotations. A full build is required after any change that can affect a page, its translation membership, locale, output path, or sitemap inclusion.
Diagnostics
The existing translation checks remain the only relationship validation:
- an empty
translationidis invalid; - a locale may declare a translation ID at most once; and
sitemap = falseretains its existing source-located checks.
No warning or error is emitted for a partial translation group, a group with
only one sitemap member, or an excluded counterpart. Those are ordinary,
authored publication choices. A malformed sitemap.hreflang value, including
redundant false, is a checked configuration error at its exact TOML span.
Documentation and acceptance criteria
The sitemap guide introduces the opt-in before explaining its boundaries. The
internationalization guide links the page-counterpart model to sitemap output.
The reference records sitemap.hreflang, and the starter site enables it so
its English and German counterpart pages produce a real example.
- Omitted
hreflangpreserves existing sitemap bytes exactly. - Enabled
hreflangemits the XHTML namespace and canonical, locale-sorted, reciprocal sets containing self for each eligible multi-member group. - A missing locale and a sitemap-excluded counterpart leave a valid partial set, without naming the excluded URL.
- A single surviving member has no alternate element.
- Generated resources and later pagination URLs never receive alternates, but the first URL of an authored translated listing can receive them.
- XML escaping, non-root
base_path, URL-length checks, deterministic sitemap splitting, full-build behavior, and source-located configuration diagnostics remain covered by focused and fixture-site tests.