# Page language set wrong, so screen readers mispronounce it

Source: https://easeweb.dev/learn/page-language
Topics: Page structure > The page language is set (WCAG 3.1.1, 3.1.2)
The fix: Set lang on the html element to the language each page is written in, and give any passage in another language its own lang. Either render the html lang from the page's locale in the server template, or, in an app that changes language in the browser, set document.documentElement.lang every time the locale changes. Both pass; which one applies depends on how the site switches language.
Test with: screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html https://www.w3.org/WAI/WCAG22/Techniques/html/H57 https://www.w3.org/WAI/WCAG22/Techniques/html/H58 https://www.w3.org/International/questions/qa-choosing-language-tags https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/lang

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

An outdoor shop has an English site and a Spanish one. A screen reader user opens the Spanish product page for a pair of hiking boots. The heading says "Botas de montaña", but the screen reader reads it as "BOH-tass dee mon-TANNA", with English vowels and stresses, and the description that follows is no easier. They know Spanish well; they cannot follow it read like this. Further down, a customer review is quoted in English. Once the page is fixed to Spanish, that quote would be read by the Spanish voice instead, unless it is marked too.

## Why it fails

The site's shared layout starts with `<html lang="en">`, written once when the site was English only. When the Spanish version was added, the translation library swapped the words on each page, but nothing touched the `lang` attribute. Screen readers, read-aloud tools and braille translators use that attribute to pick a voice and pronunciation rules, so they read every Spanish page as English. WCAG 3.1.1 asks that the default human language of each page can be programmatically determined; here it can be determined, and it is wrong, which fails the same way as missing. WCAG 3.1.2 asks the same for each passage in a language other than the page's, such as the English review. Proper names, technical terms and words that have become part of the surrounding language are exempt.

## Who is affected

The failure follows the translation: the English pages are fine, so a team that tests only in its own language never hears it. It also reaches beyond assistive technology. Browsers use `lang` to offer translation, choose fonts and hyphenation, and apply quotation marks, so a Spanish page marked as English may be offered a translation "from English" and hyphenate wrongly.

## What automation and AI miss

axe's `html-has-lang` and `html-lang-valid` check that the attribute exists and that its value is a real language tag. `lang="en"` on a Spanish page passes both, so the most common form of this failure, the wrong language rather than a missing one, goes unreported. A tool would have to detect the language of the text and compare it, and the usual page checks do not. No tool tells whether a quote, a review or a product name is in another language and needs its own `lang`, or whether it is a name that should be left alone. A coding assistant asked to "add a lang attribute" often writes `lang="en"` into the shared layout, which fixes the scan and keeps the failure on every translated page. Listen to each language version, or at least compare its `lang` with its text.

## Before: one lang for every language

```html
<!-- app.html, the shared layout -->
<html lang="en">
```

```html
<!-- the Spanish product page, words from the translation file -->
<h1>Botas de montaña</h1>
<p>Impermeables y ligeras, para todo el año.</p>
<blockquote>Kept me dry for three days of rain.</blockquote>
```

The Spanish page says it is English, so it is read with English pronunciation. The English review has no `lang` of its own, so it will take whatever the page says.

## After: the template sets lang from the page's locale

```html
<!-- app.html, rendered on the server for each request -->
<html lang="{locale}">  <!-- "es" on /es/ pages, "en" on /en/ -->
```

```html
<h1>Botas de montaña</h1>
<p>Impermeables y ligeras, para todo el año.</p>
<blockquote lang="en">Kept me dry for three days of rain.</blockquote>
```

Each page arrives with the language it is written in, before any script runs. The review keeps English pronunciation because it says so itself, whatever the page around it is set to.

## After: an app that switches language in the browser updates the html lang

```js
function setLocale(locale) {
  i18n.locale = locale;                       // swaps the words
  document.documentElement.lang = locale;     // and says which language they are in
}
```

```html
<blockquote lang="en">Kept me dry for three days of rain.</blockquote>
```

When the language switcher changes the words without loading a new page, the `lang` on the html element changes in the same step, so screen readers switch voice with it. This works only while every change of language goes through this one function; set the same value on the server too, if the app renders there, so the first load is right.

## Which option to use

Both pass 3.1.1 and 3.1.2. When each language has its own URL and the server renders the page, set `lang` in the template from the locale in the URL: there is no script to forget. When a language switcher changes the words in the browser, the server's value is only right for the first load, so the code that changes the locale must set `document.documentElement.lang` too; most i18n libraries have a hook or an option for it. Many sites need both, a server value for the first load and a client update for the switch.

## Implementation decisions

Use the shortest tag that is right: `en`, `es`, `ja`. Add a region only when the text differs by region, such as `pt-BR` or `zh-Hant` for traditional Chinese. Some codes that look right but are not valid, and that `html-lang-valid` reports:

- `english` or `spanish`: use the code, not the name;
- `es_ES`: tags use a hyphen, not the underscore that some locale systems print;
- `jp` or `cn`: these are country codes; the language codes are `ja` and `zh`.

Mark passages, not single borrowed words. A full sentence, a quote or a list of reviews in another language gets `lang` on its element; "café" in English text, a brand name, or a product called "Cumbre" does not. Content you do not write, such as reviews, comments and posts, often comes in many languages: if the source records the language, render it as `lang`; if not, leave the page's language rather than guessing.

A language switcher's own links, each written in its own language ("Español", "English"), need `lang` on each link as well, or "Español" is read by the English voice on the English page. Use `hreflang` too, if the link goes to that language's version. When content is translated by a service in the browser, the service usually updates `lang` itself; check that it does.

## Verify the fix

1. Load a page in each language directly by URL and check that the html `lang` matches the language of its text.
2. Change language with the site's own switcher, and check that `document.documentElement.lang` follows, without a reload and after one.
3. Find each passage in a language other than the page's, such as quotes, reviews and the language switcher's links, and check that each has a matching `lang`.
4. With a screen reader, read a paragraph and one of those passages on each language version, and check that the voice and pronunciation fit the text.
5. Check every `lang` value against a list of valid tags, or run axe's `html-lang-valid` and `valid-lang` rules.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat when a language is added.

## Limits

This is a starting pattern, not a guarantee for every application. Screen readers switch voices only when a voice for that language is installed, and some ignore `lang` on short passages; that is outside your control, but the markup must still be right. Whether a passage is a name or a phrase in another language is a judgement no tool makes. Test each language version of your real pages, with someone who reads that language if you can.
