Add lang attribute to <html> tag in page template - #5811
Conversation
Sets the HTML `lang` attribute based on the NiceGUI language setting, improving accessibility and SEO. The language code is split on '-' to use just the primary subtag (e.g. 'en' from 'en-US'). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
falkoschindler
left a comment
There was a problem hiding this comment.
Thanks, @evnchn!
One concern about the current approach:
split('-')[0] discards meaningful region/script information for several languages in the Language type. For example:
zh-CN(Simplified Chinese) andzh-TW(Traditional Chinese) both become justzh- screen readers use thelangattribute to select the correct voice/pronunciation, and these variants are meaningfully differentpt-BR(Brazilian Portuguese) andpt(European Portuguese) both becomept
Almost all Quasar language codes are already valid BCP 47 tags. The simplest fix would be to pass the full value directly:
<html lang="{{ language }}">This preserves zh-CN, pt-BR, ko-KR, etc. where the distinction matters for accessibility.
|
I agree with you. |
|
One thing to consider: the Before this change, the So for a developer with e.g. a German-language app who never touched the Could this be made opt-in instead (e.g. only emit |
|
Hmm fair. Not only that, considering cases where the user doesn't know what the language is (perhaps user-provided content dominates), the possibility of |
|
@falkoschindler do you think evnchn#85 is ready to be brought upstream? |
|
@falkoschindler I closed it to bake stuff into 1 commit and ask Claude Code to review (as usual) It is alive at https://github.com/evnchn/nicegui/tree/copilot/add-lang-attribute-html, but sure let's focus on 3.8 first. |
|
We have agreed to tackle i18n not in 3.10. I'm not sure if it will be 3.11 though (since if further work is required for the NiceGUI docs design, doing the i18n later dodges merge conflicts) |
`language` now defaults to `None`, meaning "undeclared": Quasar elements fall back to 'en-US' as before, but neither the server-rendered HTML nor Quasar's lang plugin (suppressed via its `noHtmlAttrs` config) add a `lang` attribute to the `html` tag. This way apps with default settings no longer declare `lang="en-US"` even though their content may be in another language, which would mislead screen readers, spell checkers and hyphenation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
I didn't want to let this PR wait for a broader i18n effort any longer, so I implemented the opt-in approach we discussed above and merged in the latest main:
While testing I discovered that the server-side template is only half the story: Quasar's lang plugin sets This resolves my concern from February: apps with default settings no longer declare English to screen readers, spell checkers and hyphenation, while apps that do configure a language get the full BCP 47 tag both server-side and client-side. And we keep the door open for inferring the language per request (e.g. from |
Suppressing the lang attribute via Quasar's noHtmlAttrs also drops the dir attribute, which left direction-dependent components like the carousel arrows mispositioned (both arrows stacked on the left, intercepting each other's clicks and failing test_carousel). Re-add dir="ltr" in the template for the no-language case, where the en-US fallback is always left-to-right. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Posted by Claude Code on @evnchn's behalf. TL;DR: Reviewed your opt-in implementation end to end — good to merge from my side. The one decision worth recording: the
One optional, non-blocking follow-up below. Over to you for the final approve/merge. What I verified (not just read)
Optional follow-up — release note for the default flip
Happy to add it here or as a trivial follow-up — your preference. Over to you for the approve/merge, or I'm glad to merge if you'd rather. Thanks again for unblocking this and catching the Quasar client-side half — that was the part the template alone never covered. |
The existing assertions only inspected the live DOM after Quasar's lang plugin runs. Add raw-HTML checks via httpx so the test also covers the server-rendered tag (the SEO / no-JS payload this feature targets) and catches server-template regressions that the client-side sync would mask. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
falkoschindler
left a comment
There was a problem hiding this comment.
Thanks for the thorough review, @evnchn!
I pushed one more commit (3150478) that extends test_html_lang_attribute with raw-HTML assertions via httpx, so we now also pin the server-rendered <html> tag — not just the post-JS DOM you pointed at. The two halves are complementary: the httpx checks catch a server-template regression (which Quasar's client-side sync would otherwise mask), while the existing find_by_tag checks guard the noHtmlAttrs suppression you flagged.
Approving — good to merge!
Motivation
NiceGUI's page template doesn't declare a
langattribute on the<html>tag — but Quasar's lang plugin addslang="en-US"(anddir="ltr") to the live DOM as soon as JavaScript boots, regardless of the app's actual content language. So today, non-JS consumers (search engine crawlers, reader modes) get no language information, while screen readers, spell checkers and CSS hyphenation are told "English" even for apps whose content is in another language.This PR makes the language declaration explicit and opt-in, improving accessibility and SEO for apps that configure their language, without wrongly declaring English for those that don't.
Split out from #5767 per review feedback — this is a library-wide template change affecting all NiceGUI users.
Implementation
ui.run(language=...)andrun_withnow default toNone, meaning "undeclared". Quasar elements fall back to'en-US'as before.<html lang="...">with the full BCP 47 tag (e.g.zh-CN,pt-BR), and Quasar keeps it in sync client-side.lang: {noHtmlAttrs: true}config so Quasar doesn't addlang/dirto the DOM either.ui.page(language=None)opts out per page, even when a global language is set.Progress