Changelog
-
I’ve moved the notice outside of the
<main>
element and translated it to the secondary language. -
I’ve added concrete HTML output examples to clarify what the page methods produce for translated vs. untranslated pages. I’ve also added a separate section explaining the mixed-language problem this solution solves.
I recently stumbled over a post in the Kirby forums about the HTML lang
attribute for multilingual sites.
It made me realise that I never even thought about how to handle the lang
attribute when content isnāt fully translated. What happens when a visitor views a German page that falls back to English content? Let me share what I learned.
What the lang
attribute does
The lang
attribute helps screen readers pronounce words correctly and enables translation tools to work more reliably. While both screen readers and translation tools have automatic language detection, theyāre often unreliable. Thatās why we need to explicitly set this:
<html lang="en">
When dealing with mixed-language content, itās helpful to put an additional lang
attribute on specific elements. For example, letās say youāre writing a web dev article and suddenly thereās an Eichhƶrnchen šæļø:
there's an <span lang="de">Eichhƶrnchen</span> šæļø
Without that lang="de"
attribute, a screen reader would try to pronounce āEichhƶrnchenā using English pronunciation rules. Not ideal.
How Kirby handles translations
Kirbyās language handling is quite straightforward. Once you create a translation of a page (by adding a file like default.de.txt
next to your default.en.txt
), the pageās content can be returned in that language. If thereās no translation, the default language will be used. You can read more about this in Kirbyās docs.
Setting the lang attribute with Kirby looks like this:
<html lang="<?= $kirby->language()->code() ?>">
This sets the lang
attribute to the current languageās code. If the current language is German, youāll get lang="de"
.
The mixed-language problem
Hereās where it gets tricky. What happens when a page isnāt fully translated?
Letās say a visitor is viewing your German site, but a specific article hasnāt been translated yet. The navigation and UI elements might be in German, but the actual content falls back to English. You end up with a page thatās partially German and partially English.
The document still has <html lang="de">
, but the main content is actually in English. This mismatch confuses screen readers and translation tools.
A clean solution with custom page methods
What we need is a way to set a lang
attribute on our content wrapper that reflects the actual language of the content. To keep our templates clean, we can create some custom page methods.
First, a method to check if the current page is translated:
'isTranslated' => function () {
return $this->translation(kirby()->language()->code())->exists();
}
Then, a method to return a lang
attribute for the fallback language:
'fallbackLang' => function () {
if ($this->isTranslated()) return null;
return 'lang="' . kirby()->defaultLanguage()->code() . '"';
}
The fallbackLang()
method returns null
when the page is fully translated (no extra lang
attribute needed), or a complete lang
attribute with the default languageās code when using fallback content.
Using it in templates
Now we can use these methods in our templates:
<?php if($page->isTranslated() === false): ?>
<p class="notice">
<?= t('fallback.lang.notice') ?>
</p>
<?php endif ?>
<main <?= $page->fallbackLang() ?>>
<?= $page->text()->kt() ?>
</main>
This code produces different HTML depending on the translation status. Letās break it down:
When viewing a fully translated German page:
<main>
<p>Der Inhalt dieser Seite...</p>
</main>
The <main>
element has no lang
attribute because the content language matches the document language (<html lang="de">
).
When viewing an untranslated page on the German site:
<p class="notice">
Der Inhalt dieser Seite ist noch nicht übersetzt.
</p>
<main lang="en">
<p>The content of this page...</p>
</main>
Here, the <main>
element gets lang="en"
to indicate that this section contains English content, even though the rest of the page is in German. This helps screen readers switch pronunciation rules and allows translation tools to handle the content correctly.
Final thoughts
To be honest, I havenāt worked on a multilingual site for some time, but I wanted to publish this article anyway as itās been gathering dust in my drafts. Thereās a good chance Iāll update it with some practical learnings in the future.
Have you implemented something similar on your multilingual sites? Iād love to hear about your approaches over on Mastodon.