<html lang="xx"> Language Attribute Validation
Issue No: 105
Category: Html Head Tags
Issue type: Issue
Priority: Critical
Description
The lang attribute on the <html> element declares the page's primary human language using a BCP 47 language tag. Missing, empty, invalid, or mismatched values cause search engines to mis-classify page language, prevent language-targeted serving via hreflang, break screen reader pronunciation, and fail WCAG 3.1.1.
How do we capture it
-
Parse the
<html>element and extract thelangattribute value from the raw HTML response.- Record the presence and exact string value of the attribute.
- Normalise to lowercase for comparison.
-
Validate the value against BCP 47 (IETF RFC 5646).
- Split on
-into subtags: language, script, region, variant. - Check the primary language subtag against the IANA Language Subtag Registry.
- Flag subtags separated by
_(underscore) instead of-(hyphen) — a common error. - Flag deprecated primary language codes (
iw→he,ji→yi,in→id). - Flag ambiguous Chinese without a script subtag (
zhalone — should bezh-Hansorzh-Hant).
- Split on
-
Detect language mismatch using a language detection library (e.g.
langdetect,franc).- Extract the visible text content of the page (strip HTML tags, scripts, styles).
- Run language detection; record the detected language code and confidence score.
- If the detected language differs from
langwith confidence ≥ 0.85, flagis_mismatch = true.
-
Check the HTTP
Content-Languageheader in the response.- If present, compare to the
langattribute value. - Flag a conflict if the two values contradict each other.
- If present, compare to the
-
Check for hreflang consistency (cross-reference with the hreflang audit):
- If hreflang annotations are present, verify that the declared
langcode appears in at least one hreflang entry for the same page.
- If hreflang annotations are present, verify that the declared
What to store
| Field | Type | Comment |
|---|---|---|
site_id | integer | FK → sites(id) |
page_id | integer | FK → pages(id) |
lang_attribute | text | Raw value of the lang attribute; NULL if attribute absent |
is_present | boolean | Whether the attribute exists on <html> |
is_empty | boolean | Whether the attribute value is an empty string |
is_valid_bcp47 | boolean | Whether the value is a valid BCP 47 tag |
language_code | text | Primary language subtag (e.g. en, fr, zh) |
script_code | text | Script subtag (e.g. Hans, Latn); NULL if absent |
region_code | text | Region subtag (e.g. US, GB); NULL if absent |
is_deprecated | boolean | Whether the language code is deprecated in the IANA registry |
uses_underscore_separator | boolean | Whether _ is used instead of - in the tag |
is_ambiguous_chinese | boolean | Whether lang="zh" is present without a script subtag |
detected_language | text | Language code detected from page text content |
detection_confidence | numeric | Confidence score of language detection (0.0–1.0) |
is_mismatch | boolean | Whether declared lang differs from detected lang (confidence ≥ 0.85) |
http_content_language | text | Value of the HTTP Content-Language response header; NULL if absent |
has_header_conflict | boolean | Whether Content-Language header contradicts the lang attribute |
checked_at | timestamp | When this check was last performed |
Condition for trigger
The following rules evaluate stored fields and produce diagnostics in the audit report.
-
Trigger Rule 1: Missing
langAttribute- Target Field:
is_present - Evaluation Logic:
is_present = false - Severity: CRITICAL
- Diagnostic Message: "The
<html>element has nolangattribute. Declare the page language (e.g.lang=\"en\") so search engines can correctly classify the page and serve it to users in the right locale."
- Target Field:
-
Trigger Rule 2: Empty
langAttribute- Target Field:
is_empty - Evaluation Logic:
is_present = true AND is_empty = true - Severity: CRITICAL
- Diagnostic Message: "The
<html>element has an emptylang=\"\"attribute, which is invalid. Replace it with a valid BCP 47 language tag."
- Target Field:
-
Trigger Rule 3: Invalid BCP 47 Tag
- Target Field:
is_valid_bcp47 - Evaluation Logic:
is_present = true AND is_empty = false AND is_valid_bcp47 = false - Severity: WARNING
- Diagnostic Message: "The value
[lang_attribute]is not a valid BCP 47 language tag. Use a registered IANA subtag (e.g.en,en-US,fr-FR,zh-Hans). Check https://www.iana.org/assignments/language-subtag-registry."
- Target Field:
-
Trigger Rule 4: Underscore Separator in Language Tag
- Target Field:
uses_underscore_separator - Evaluation Logic:
uses_underscore_separator = true - Severity: WARNING
- Diagnostic Message: "The
langattribute value uses an underscore separator ([lang_attribute]). BCP 47 requires a hyphen (-). Change to the correctly hyphenated form."
- Target Field:
-
Trigger Rule 5: Deprecated Language Code
- Target Field:
is_deprecated - Evaluation Logic:
is_deprecated = true - Severity: WARNING
- Diagnostic Message: "The language code
[language_code]is deprecated in the IANA registry. Replace it with the current canonical code."
- Target Field:
-
Trigger Rule 6: Language Mismatch (Detected vs Declared)
- Target Field:
is_mismatch - Evaluation Logic:
is_mismatch = true AND detection_confidence >= 0.85 - Severity: CRITICAL
- Diagnostic Message: "The declared language is
[lang_attribute]but automated detection identified the page content as[detected_language]with[detection_confidence]confidence. Correct thelangattribute to match the actual page language."
- Target Field:
-
Trigger Rule 7: HTTP Header Conflicts with
langAttribute- Target Field:
has_header_conflict - Evaluation Logic:
has_header_conflict = true - Severity: WARNING
- Diagnostic Message: "The HTTP
Content-Languageheader ([http_content_language]) conflicts with thelangattribute ([lang_attribute]). Align both to the same BCP 47 value, preferring thelangattribute as the primary declaration."
- Target Field:
-
Trigger Rule 8: Ambiguous Chinese Without Script Subtag
- Target Field:
is_ambiguous_chinese - Evaluation Logic:
is_ambiguous_chinese = true - Severity: WARNING
- Diagnostic Message: "The
lang=\"zh\"attribute does not specify a script subtag. Usezh-Hansfor Simplified Chinese orzh-Hantfor Traditional Chinese so search engines can serve the correct script variant."
- Target Field:
Sources
- W3C — BCP 47 Language Tags: https://www.w3.org/International/articles/language-tags/
- IETF RFC 5646 — Tags for Identifying Languages: https://www.rfc-editor.org/rfc/rfc5646
- IANA Language Subtag Registry: https://www.iana.org/assignments/language-subtag-registry
- Google — International SEO (hreflang): https://developers.google.com/search/docs/specialty/international
- MDN — The
langglobal attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang - WCAG 2.1 Success Criterion 3.1.1 — Language of Page: https://www.w3.org/WAI/WCAG21/Understanding/language-of-page.html
- W3C I18n Checker: https://validator.w3.org/i18n-checker/
Long description
The HTML lang attribute provides the single most important language signal in a web page. While the HTTP Content-Language header and hreflang annotations also carry language information, the lang attribute on <html> is the authoritative in-document declaration that all four major rendering engines, all major search engine crawlers, and all screen readers rely on first.
Why the lang attribute matters for SEO
Search engines use the lang value to:
- Assign the page to the correct language cluster for query matching.
- Validate hreflang annotations (the
langcode must align with the hreflangx-defaultor self-referencing entry). - Apply language-appropriate stemming, tokenisation, and stop-word filtering during indexing.
- Qualify or disqualify the page for locale-targeted features such as rich results in specific language regions.
Why it matters for accessibility
WCAG 2.1 Success Criterion 3.1.1 requires the page language to be programmatically determined. Screen readers change pronunciation engines based on the lang value — a missing or wrong value means French pages are read with English phonology, making the content incomprehensible to visually impaired users.
BCP 47 tag structure
A valid tag is composed of IANA-registered subtags separated by hyphens:
language[-script][-region][-variant]
en Latn US
zh Hans
fr FR
The primary language subtag (en, fr, zh) is mandatory. The script subtag (Hans, Hant, Latn) and region subtag (US, GB, FR) are optional but recommended when the meaning would be ambiguous without them.
Common failure patterns:
langattribute missing entirely (most common on CMS-generated pages where the template is not locale-aware).lang=""— attribute present but value is an empty string (often a placeholder left by a developer).lang="en_US"— underscore used instead of hyphen (common copy-paste error from locale strings in programming languages).lang="iw"— deprecated code for Hebrew (current code:he).lang="zh"— ambiguous Chinese, does not distinguish Simplified from Traditional script.langset to the developer's language, not the content language (e.g. template defaultenon a Spanish-language page).Content-Language: frHTTP header whilelang="en"on the page — contradictory signals confuse crawlers.
How search engines handle missing lang
Google and Bing will attempt to auto-detect the page language from the visible text content when lang is absent or invalid. Auto-detection is generally accurate for high-resource languages but unreliable for low-resource languages, mixed-language pages, and short pages with limited text. Relying on auto-detection rather than an explicit declaration is always an unnecessary risk.
How to Fix
-
Add
langto the<html>element with the correct BCP 47 tag:<html lang="en">— English<html lang="en-US">— American English<html lang="zh-Hans">— Simplified Chinese- Set it in the base layout/template so all pages inherit it automatically.
-
Validate the tag against the IANA Language Subtag Registry at https://www.iana.org/assignments/language-subtag-registry or using the W3C I18n Checker at https://validator.w3.org/i18n-checker/.
-
Replace deprecated codes with current equivalents:
iw→he,ji→yi,in→id. -
Replace underscores with hyphens:
en_US→en-US,zh_CN→zh-CN. -
Disambiguate Chinese: replace
lang="zh"withlang="zh-Hans"(Simplified) orlang="zh-Hant"(Traditional) based on the actual script used. -
Align the HTTP
Content-Languageheader with thelangattribute. SetContent-Language: en(language only, no region) at the server level if a single language is served from the origin; remove it if hreflang handles all language targeting. -
Audit CMS templates and locale settings: for multilingual sites, ensure the CMS outputs the correct
langvalue per language variant and that no template hard-codes a single language code for all locales.
Additional technical notes
BCP 47 subtag hierarchy
A BCP 47 tag is composed of subtags separated by hyphens. Each position has a specific meaning:
language - script - region - variant - extension
en Latn US
zh Hans
sr Cyrl RS
zh Hant TW
- The language subtag is mandatory. It is a 2- or 3-letter ISO 639 code.
- The script subtag (4 letters, title-case) is required when the language can be written in multiple scripts (Chinese:
Hans/Hant; Serbian:Cyrl/Latn). - The region subtag (2-letter ISO 3166-1 country code) distinguishes regional variants (
en-USvsen-GB).
Deprecated subtag examples to watch for
| Deprecated | Current | Language |
|---|---|---|
iw | he | Hebrew |
ji | yi | Yiddish |
in | id | Indonesian |
mo | ro | Moldovan Romanian |
sh | sr-Latn | Serbo-Croatian |
Impact on Google hreflang
Google's hreflang annotation uses the same BCP 47 format. If the lang attribute is invalid or different from the hreflang declarations, Google may fail to match alternate language versions and show the wrong locale variant to users in different countries. Alignment between lang, hreflang, and Content-Language is a prerequisite for a functional international SEO setup.