Skip to content

By John Martin

  • i18n
  • ux
Cover illustration: two locale paths joining into one shared page layout

Locale switching that never lies

The worst thing a language switcher can do is offer a translation that does not exist. The reader clicks, lands on a 404, and learns the switcher lies. So the rule here is simple:

A switcher link is only rendered when the translation actually exists.

Availability comes from content

For every page, the app asks the content collection which locales have a document at the same resource path. The switcher renders from that answer - not from the full registry. If a page exists in English only, English readers see no Vietnamese link, because there is nothing behind it.

The slug is the join key

Translations share one slug. /en/blog/locale-switching and /vi/blog/locale-switching are the same article, so switching means replacing exactly one segment:

/en/blog/locale-switching
       |
       v
/vi/blog/locale-switching

No per-page mapping table, no redirects chained behind the switcher - the path is rebuilt by the platform's own builder, which rejects malformed output at compile time rather than serving it.

Metadata follows the same rule

The hreflang alternates in the page head are built from the same availability answer. A page never advertises a translation to search engines that it would not offer to a reader.

SurfaceSource of truth
Switcher linkscontent query
hreflang linkscontent query
canonicalcurrent path, production origin

One query, three surfaces, no divergence.


The registry that defines which locales exist is documented in the build-time content post.

← All posts