Ver contenido
Introducción
El atributo lang de HTML indica a los navegadores y tecnologías asistivas en qué idioma humano está tu contenido. WCAG 3.1.1 (Idioma de la Página) requiere que cada página web declare su idioma principal. Este atributo aparentemente simple tiene impactos profundos en la accesibilidad, pronunciación y experiencia de usuario.
Los lectores de pantalla usan el atributo de idioma para seleccionar las reglas de pronunciación y voz correctas. Sin una identificación adecuada del idioma, un lector de pantalla podría leer texto en francés con pronunciación en inglés, haciendo el contenido incomprensible para los usuarios.
Qué Requiere WCAG
3.1.1 Idioma de la Página (Nivel A)
El idioma humano predeterminado de cada página web puede determinarse programáticamente.
Esto significa:
- Cada página HTML debe tener un atributo
langen el elemento<html> - El valor debe ser una etiqueta de idioma BCP 47 válida (ej., “es”, “en”, “fr”)
- Esto permite que las tecnologías asistivas carguen reglas de pronunciación apropiadas
3.1.2 Idioma de las Partes (Nivel AA)
El idioma humano de cada pasaje o frase en el contenido puede determinarse programáticamente.
Esto significa:
- El contenido en un idioma diferente al predeterminado de la página debe tener su propio atributo
lang - Esto aplica a citas, frases o secciones en idiomas extranjeros
- Excepciones: nombres propios, términos técnicos, palabras que se han convertido en parte del idioma circundante
Quién Se Beneficia
| Tipo de Usuario | Beneficio |
|---|---|
| Usuarios de lectores de pantalla | Pronunciación correcta del contenido |
| Usuarios de herramientas de traducción | Traducción precisa y dirigida |
| Usuarios con discapacidades de lectura | Separación silábica y procesamiento de texto apropiados |
| Motores de búsqueda | Mejor comprensión del idioma del contenido |
| Todos los usuarios | Corrección ortográfica y auto-corrección correctas |
Implementación Básica
Idioma de la Página
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Título de Página</title>
</head>
<body>
<!-- Contenido de página en español -->
</body>
</html>
Idioma de las Partes
<p>
La frase en inglés <span lang="en">that's life</span> significa "así es la vida".
</p>
<blockquote lang="en">
<p>To be or not to be, that is the question.</p>
<cite>— William Shakespeare</cite>
</blockquote>
Códigos de Idioma Comunes
| Idioma | Código | Variantes Extendidas |
|---|---|---|
| Español | es |
es-ES, es-MX, es-AR |
| Inglés | en |
en-US, en-GB, en-AU |
| Francés | fr |
fr-FR, fr-CA |
| Alemán | de |
de-DE, de-AT, de-CH |
| Portugués | pt |
pt-BR, pt-PT |
| Chino | zh |
zh-CN, zh-TW, zh-Hans, zh-Hant |
| Japonés | ja |
ja-JP |
| Coreano | ko |
ko-KR |
| Árabe | ar |
ar-SA, ar-EG |
| Ruso | ru |
ru-RU |
Problemas Comunes
1. Atributo lang Faltante
<!-- MAL: Sin idioma especificado -->
<!DOCTYPE html>
<html>
<head>...</head>
<body>...</body>
</html>
<!-- BIEN: Idioma declarado -->
<!DOCTYPE html>
<html lang="es">
<head>...</head>
<body>...</body>
</html>
2. Código de Idioma Incorrecto
<!-- MAL: Códigos de idioma inválidos -->
<html lang="espanol">
<html lang="spa">
<html lang="es_MX">
<!-- BIEN: Códigos BCP 47 válidos -->
<html lang="es">
<html lang="es-MX">
3. Frases Extranjeras Sin Etiquetas de Idioma
<!-- MAL: Inglés pronunciado con reglas del español -->
<p>El término marketing es muy usado en negocios.</p>
<!-- BIEN: Palabra inglesa marcada para pronunciación correcta -->
<p>El término <span lang="en">marketing</span> es muy usado en negocios.</p>
4. Desajuste de Idioma
<!-- MAL: Página en español pero marcada como inglés -->
<html lang="en">
<body>
<h1>Bienvenido a nuestro sitio</h1>
<!-- Contenido en español... -->
</body>
</html>
<!-- BIEN: Declaración de idioma correcta -->
<html lang="es">
<body>
<h1>Bienvenido a nuestro sitio</h1>
</body>
</html>
Cuándo Marcar el Idioma de las Partes
Sí Marcar
- Palabras extranjeras que podrían pronunciarse mal
- Citas en otros idiomas
- Pasajes o secciones en diferentes idiomas
- Términos técnicos de otros idiomas (cuando la pronunciación importa)
<!-- Cita extranjera -->
<blockquote lang="en">
<p>To be or not to be.</p>
</blockquote>
<!-- Frase extranjera -->
<p>El concepto de <span lang="de">Weltanschauung</span> es fundamental.</p>
No Necesitas Marcar
- Nombres propios (como “François” o “München”)
- Términos técnicos ampliamente usados (como “software” o “internet”)
- Palabras adoptadas al español (como “fútbol” o “restaurante”)
- Nombres de marcas
<!-- Estos no necesitan atributos lang -->
<p>Conoce a nuestro CEO, François Martin.</p>
<p>Actualiza el software del sistema.</p>
<p>El internet cambió el mundo.</p>
Impacto en Lectores de Pantalla
Cuando un lector de pantalla encuentra texto, verifica el atributo lang para seleccionar reglas de pronunciación:
| Sin lang | Con lang |
|---|---|
| “mar-ket-ing” (pronunciación española) | “már-ke-ting” (pronunciación inglesa) |
| El lector de pantalla adivina el idioma | El lector de pantalla sabe el idioma exacto |
| Potencialmente incomprensible | Pronunciación clara y correcta |
Probando Atributos de Idioma
Prueba Manual Rápida
- Verifica que
<html>tenga un atributolang - Confirma que el código de idioma coincida con el contenido de la página
- Busca frases extranjeras y verifica los atributos
lang - Usa un lector de pantalla para verificar la pronunciación
Qué Verificar
- [ ] La página tiene atributo
langen el elemento<html> - [ ] El código de idioma tiene formato BCP 47 válido
- [ ] El idioma coincide con el contenido real de la página
- [ ] Las frases extranjeras tienen atributos
langapropiados - [ ] El lector de pantalla pronuncia el contenido correctamente
Herramientas para Pruebas
- axe DevTools - Señala idioma de página faltante
- WAVE - Muestra atributos de idioma
- DevTools del navegador - Inspeccionar elemento
<html> - Validador W3C - Valida códigos de idioma
Resumen de Mejores Prácticas
| Hacer | No Hacer |
|---|---|
Siempre incluir lang en <html> |
Dejar el atributo lang faltante |
| Usar códigos BCP 47 válidos | Usar nombres completos de idioma o códigos inválidos |
| Marcar frases extranjeras | Dejar contenido extranjero sin marcar |
| Hacer coincidir lang con el contenido real | Usar código de idioma incorrecto |
| Probar con lectores de pantalla | Asumir que la pronunciación es correcta |
Implementación
Implementación HTML Completa
Plantilla de Idioma de Página
<!DOCTYPE html>
<html lang="es" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Declaración de idioma en meta (suplementario) -->
<meta http-equiv="content-language" content="es">
<!-- Versiones en idiomas alternativos (SEO y accesibilidad) -->
<link rel="alternate" hreflang="en" href="https://ejemplo.com/en/pagina">
<link rel="alternate" hreflang="es" href="https://ejemplo.com/es/pagina">
<link rel="alternate" hreflang="x-default" href="https://ejemplo.com/es/pagina">
<title>Título de Página</title>
</head>
<body>
<!-- Contenido en idioma principal (Español) -->
<!-- Sección en diferente idioma -->
<section lang="en">
<h2>Section in English</h2>
<p>This content is in English.</p>
</section>
<!-- Cambios de idioma en línea -->
<p>
El concepto de <span lang="de">Weltanschauung</span> se refiere a una
visión del mundo.
</p>
</body>
</html>
Estructura de Etiqueta de Idioma BCP 47
idioma-extlang-script-region-variante-extension-uso-privado
Ejemplos:
es → Español (general)
es-ES → Español (España)
es-MX → Español (México)
zh-Hans → Chino (script Simplificado)
zh-Hant → Chino (script Tradicional)
zh-CN → Chino (región China, implica Simplificado)
zh-TW → Chino (región Taiwán, implica Tradicional)
pt-BR → Portugués (Brasil)
sr-Latn → Serbio (script Latino)
sr-Cyrl → Serbio (script Cirílico)
Referencia Completa de Etiquetas de Idioma
const ETIQUETAS_IDIOMA = {
// Idiomas Principales
'es': 'Español',
'es-ES': 'Español (España)',
'es-MX': 'Español (México)',
'es-AR': 'Español (Argentina)',
'es-CO': 'Español (Colombia)',
'es-CL': 'Español (Chile)',
'en': 'Inglés',
'en-US': 'Inglés (Estados Unidos)',
'en-GB': 'Inglés (Reino Unido)',
'en-AU': 'Inglés (Australia)',
'en-CA': 'Inglés (Canadá)',
'fr': 'Francés',
'fr-FR': 'Francés (Francia)',
'fr-CA': 'Francés (Canadá)',
'fr-BE': 'Francés (Bélgica)',
'de': 'Alemán',
'de-DE': 'Alemán (Alemania)',
'de-AT': 'Alemán (Austria)',
'de-CH': 'Alemán (Suiza)',
'pt': 'Portugués',
'pt-BR': 'Portugués (Brasil)',
'pt-PT': 'Portugués (Portugal)',
// Idiomas Asiáticos
'zh': 'Chino',
'zh-Hans': 'Chino (Simplificado)',
'zh-Hant': 'Chino (Tradicional)',
'zh-CN': 'Chino (China)',
'zh-TW': 'Chino (Taiwán)',
'zh-HK': 'Chino (Hong Kong)',
'ja': 'Japonés',
'ja-JP': 'Japonés (Japón)',
'ko': 'Coreano',
'ko-KR': 'Coreano (Corea del Sur)',
// Idiomas RTL
'ar': 'Árabe',
'ar-SA': 'Árabe (Arabia Saudita)',
'ar-EG': 'Árabe (Egipto)',
'he': 'Hebreo',
'fa': 'Persa/Farsi',
// Otros Europeos
'it': 'Italiano',
'nl': 'Holandés',
'ru': 'Ruso',
'pl': 'Polaco',
'sv': 'Sueco',
'da': 'Danés',
'no': 'Noruego',
'fi': 'Finlandés',
'el': 'Griego',
'tr': 'Turco',
// Clásicos/Históricos
'la': 'Latín',
'grc': 'Griego Antiguo',
};
Implementaciones en Frameworks
React con i18n
import React, { createContext, useContext, useEffect } from 'react';
// Contexto de Idioma
interface LanguageContextType {
language: string;
setLanguage: (lang: string) => void;
dir: 'ltr' | 'rtl';
}
const LanguageContext = createContext<LanguageContextType | null>(null);
// Idiomas RTL
const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur'];
export function LanguageProvider({
children,
defaultLanguage = 'es'
}: {
children: React.ReactNode;
defaultLanguage?: string;
}) {
const [language, setLanguageState] = React.useState(defaultLanguage);
const dir = RTL_LANGUAGES.some(l => language.startsWith(l)) ? 'rtl' : 'ltr';
const setLanguage = (lang: string) => {
setLanguageState(lang);
// Actualizar atributos del documento
document.documentElement.lang = lang;
document.documentElement.dir = RTL_LANGUAGES.some(l => lang.startsWith(l))
? 'rtl'
: 'ltr';
};
// Establecer idioma inicial del documento
useEffect(() => {
document.documentElement.lang = language;
document.documentElement.dir = dir;
}, []);
return (
<LanguageContext.Provider value={{ language, setLanguage, dir }}>
{children}
</LanguageContext.Provider>
);
}
export function useLanguage() {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage debe usarse dentro de LanguageProvider');
}
return context;
}
// Componente para cambios de idioma en línea
interface LangSpanProps {
lang: string;
children: React.ReactNode;
className?: string;
}
export function LangSpan({ lang, children, className }: LangSpanProps) {
const isRtl = RTL_LANGUAGES.some(l => lang.startsWith(l));
return (
<span
lang={lang}
dir={isRtl ? 'rtl' : undefined}
className={className}
>
{children}
</span>
);
}
// Componente para cambios de idioma a nivel de bloque
interface LangBlockProps {
lang: string;
children: React.ReactNode;
as?: 'div' | 'section' | 'article' | 'blockquote';
className?: string;
}
export function LangBlock({
lang,
children,
as: Component = 'div',
className
}: LangBlockProps) {
const isRtl = RTL_LANGUAGES.some(l => lang.startsWith(l));
return (
<Component
lang={lang}
dir={isRtl ? 'rtl' : 'ltr'}
className={className}
>
{children}
</Component>
);
}
// Ejemplo de Uso
function ContenidoMultilingue() {
return (
<div>
<p>
La palabra alemana <LangSpan lang="de">Schadenfreude</LangSpan> describe
el placer derivado de la desgracia de otros.
</p>
<LangBlock lang="en" as="blockquote">
<p>To be or not to be, that is the question.</p>
<cite>— William Shakespeare</cite>
</LangBlock>
<LangBlock lang="ar" as="section">
<h2>مقطع باللغة العربية</h2>
<p>هذا النص مكتوب باللغة العربية.</p>
</LangBlock>
</div>
);
}
Implementación en Vue 3
<script setup lang="ts">
import { ref, computed, watch, onMounted, provide } from 'vue'
// Composable de idioma
const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur']
export function useLanguage(defaultLanguage = 'es') {
const language = ref(defaultLanguage)
const dir = computed(() =>
RTL_LANGUAGES.some(l => language.value.startsWith(l)) ? 'rtl' : 'ltr'
)
function setLanguage(lang: string) {
language.value = lang
document.documentElement.lang = lang
document.documentElement.dir = RTL_LANGUAGES.some(l => lang.startsWith(l))
? 'rtl'
: 'ltr'
}
onMounted(() => {
document.documentElement.lang = language.value
document.documentElement.dir = dir.value
})
watch(language, (newLang) => {
document.documentElement.lang = newLang
document.documentElement.dir = RTL_LANGUAGES.some(l => newLang.startsWith(l))
? 'rtl'
: 'ltr'
})
return { language, dir, setLanguage }
}
// Usando en componente
const { language, dir, setLanguage } = useLanguage('es')
// Proporcionar a componentes hijos
provide('language', { language, dir, setLanguage })
</script>
<template>
<div>
<!-- Selector de idioma -->
<select @change="setLanguage(($event.target as HTMLSelectElement).value)">
<option value="es">Español</option>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="ar">العربية</option>
</select>
<!-- Cambio de idioma en línea -->
<p>
La frase en inglés <span lang="en">that's life</span> significa "así es la vida".
</p>
<!-- Cambio de idioma en bloque con soporte RTL -->
<blockquote lang="ar" dir="rtl">
<p>السلام عليكم</p>
</blockquote>
<!-- Sección en diferente idioma -->
<section lang="en">
<h2>Section in English</h2>
<p>This content is completely in English.</p>
</section>
</div>
</template>
Componente LangSpan para Vue
<!-- components/LangSpan.vue -->
<script setup lang="ts">
import { computed } from 'vue'
const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur']
interface Props {
lang: string
tag?: 'span' | 'div' | 'section' | 'article' | 'blockquote' | 'p'
}
const props = withDefaults(defineProps<Props>(), {
tag: 'span'
})
const isRtl = computed(() =>
RTL_LANGUAGES.some(l => props.lang.startsWith(l))
)
const direction = computed(() => isRtl.value ? 'rtl' : undefined)
</script>
<template>
<component
:is="tag"
:lang="lang"
:dir="direction"
>
<slot />
</component>
</template>
<!-- Uso -->
<LangSpan lang="de">Kindergarten</LangSpan>
<LangSpan lang="ja" tag="blockquote">
<p>日本語のテキスト</p>
</LangSpan>
CSS para Estilos Específicos por Idioma
/* Selección de fuentes base por idioma */
:lang(es) {
font-family: 'Inter', system-ui, sans-serif;
}
:lang(zh) {
font-family: 'Noto Sans SC', 'PingFang SC', sans-serif;
}
:lang(ja) {
font-family: 'Noto Sans JP', 'Hiragino Sans', sans-serif;
}
:lang(ko) {
font-family: 'Noto Sans KR', 'Apple SD Gothic Neo', sans-serif;
}
:lang(ar) {
font-family: 'Noto Sans Arabic', 'Tahoma', sans-serif;
}
/* Soporte para idiomas RTL */
:lang(ar),
:lang(he),
:lang(fa),
:lang(ur) {
direction: rtl;
text-align: right;
}
/* Comillas por idioma */
:lang(es) q {
quotes: '«' '»' '"' '"';
}
:lang(en) q {
quotes: '"' '"' "'" "'";
}
:lang(fr) q {
quotes: '« ' ' »' '‹ ' ' ›';
}
:lang(de) q {
quotes: '„' '"' '‚' ''';
}
:lang(ja) q {
quotes: '「' '」' '『' '』';
}
/* Estilo de frases extranjeras en línea */
[lang]:not(:lang(es)) {
/* Indicación sutil de cambio de idioma */
font-style: italic;
}
/* Excepción: No italizar cambios de idioma a nivel de bloque */
section[lang],
article[lang],
blockquote[lang],
div[lang] {
font-style: normal;
}
/* Aislamiento de texto bidireccional */
[lang] {
unicode-bidi: isolate;
}
/* Separación silábica por idioma */
:lang(es) {
hyphens: auto;
-webkit-hyphens: auto;
}
:lang(de) {
/* El alemán tiene reglas estrictas de separación silábica */
hyphens: auto;
-webkit-hyphens: auto;
}
:lang(zh),
:lang(ja),
:lang(ko) {
/* Los idiomas CJK no usan separación silábica */
hyphens: none;
word-break: keep-all;
}
Detección de Idioma del Lado del Servidor
Implementación Next.js/Node.js
// lib/language.ts
import { NextRequest } from 'next/server';
const SUPPORTED_LANGUAGES = ['es', 'en', 'fr', 'de', 'ar'];
const DEFAULT_LANGUAGE = 'es';
export function detectLanguage(request: NextRequest): string {
// 1. Verificar parámetro de URL
const urlLang = request.nextUrl.searchParams.get('lang');
if (urlLang && SUPPORTED_LANGUAGES.includes(urlLang)) {
return urlLang;
}
// 2. Verificar prefijo de ruta
const pathLang = request.nextUrl.pathname.split('/')[1];
if (SUPPORTED_LANGUAGES.includes(pathLang)) {
return pathLang;
}
// 3. Verificar cookie
const cookieLang = request.cookies.get('preferred-language')?.value;
if (cookieLang && SUPPORTED_LANGUAGES.includes(cookieLang)) {
return cookieLang;
}
// 4. Verificar encabezado Accept-Language
const acceptLanguage = request.headers.get('accept-language');
if (acceptLanguage) {
const languages = acceptLanguage
.split(',')
.map(lang => {
const [code, q = '1'] = lang.trim().split(';q=');
return { code: code.split('-')[0], quality: parseFloat(q) };
})
.sort((a, b) => b.quality - a.quality);
for (const { code } of languages) {
if (SUPPORTED_LANGUAGES.includes(code)) {
return code;
}
}
}
// 5. Por defecto
return DEFAULT_LANGUAGE;
}
// middleware.ts
export function middleware(request: NextRequest) {
const language = detectLanguage(request);
// Establecer encabezado de idioma para SSR
const response = NextResponse.next();
response.headers.set('x-detected-language', language);
return response;
}
Pruebas Automatizadas
Tests de Playwright para Atributos de Idioma
import { test, expect } from '@playwright/test';
test.describe('Atributos de Idioma', () => {
test('la página tiene atributo lang válido en elemento html', async ({ page }) => {
await page.goto('/');
// Obtener atributo lang del elemento html
const lang = await page.locator('html').getAttribute('lang');
// Debe existir
expect(lang).not.toBeNull();
expect(lang).not.toBe('');
// Debe tener formato BCP 47 válido (verificación básica)
expect(lang).toMatch(/^[a-z]{2,3}(-[A-Za-z]{2,4})?(-[A-Za-z]{2})?$/);
});
test('el atributo lang coincide con el idioma del contenido de la página', async ({ page }) => {
// Probar página en español
await page.goto('/es/nosotros');
const esLang = await page.locator('html').getAttribute('lang');
expect(esLang).toMatch(/^es/);
// Probar página en inglés
await page.goto('/en/about');
const enLang = await page.locator('html').getAttribute('lang');
expect(enLang).toMatch(/^en/);
});
test('las frases extranjeras tienen atributos lang', async ({ page }) => {
await page.goto('/contenido-multilingue');
// Encontrar spans/elementos con contenido extranjero
const foreignPhrases = page.locator('[lang]:not(html)');
const count = await foreignPhrases.count();
// Si la página tiene contenido extranjero, debe estar marcado
if (count > 0) {
for (let i = 0; i < count; i++) {
const lang = await foreignPhrases.nth(i).getAttribute('lang');
expect(lang).not.toBeNull();
expect(lang).toMatch(/^[a-z]{2,3}/);
}
}
});
test('los idiomas RTL tienen atributo dir correcto', async ({ page }) => {
await page.goto('/ar/about');
// HTML debe tener dir="rtl" para árabe
const dir = await page.locator('html').getAttribute('dir');
expect(dir).toBe('rtl');
// El contenido árabe en línea también debe tener dir
const arabicContent = page.locator('[lang^="ar"]');
const count = await arabicContent.count();
for (let i = 0; i < count; i++) {
const element = arabicContent.nth(i);
const isBlock = await element.evaluate(el =>
['DIV', 'SECTION', 'ARTICLE', 'BLOCKQUOTE'].includes(el.tagName)
);
if (isBlock) {
const dir = await element.getAttribute('dir');
expect(dir).toBe('rtl');
}
}
});
test('los atributos hreflang están presentes para idiomas alternativos', async ({ page }) => {
await page.goto('/es/nosotros');
// Verificar enlaces de idiomas alternativos
const alternateLinks = page.locator('link[rel="alternate"][hreflang]');
const count = await alternateLinks.count();
// Debe tener al menos 2 (propio + uno alternativo)
expect(count).toBeGreaterThanOrEqual(2);
// Cada uno debe tener hreflang válido
for (let i = 0; i < count; i++) {
const hreflang = await alternateLinks.nth(i).getAttribute('hreflang');
expect(hreflang).toMatch(/^[a-z]{2,3}(-[A-Za-z]{2})?$|^x-default$/);
}
});
test('el selector de idioma cambia el atributo lang', async ({ page }) => {
await page.goto('/es/nosotros');
// Verificar idioma inicial
let lang = await page.locator('html').getAttribute('lang');
expect(lang).toMatch(/^es/);
// Hacer clic en selector de idioma
await page.click('[data-testid="lang-switch-en"]');
// Esperar navegación
await page.waitForURL(/\/en\//);
// Verificar que el idioma cambió
lang = await page.locator('html').getAttribute('lang');
expect(lang).toMatch(/^en/);
});
});
Auditoría de Accesibilidad para Idioma
// language-audit.js
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.describe('Auditoría de Accesibilidad de Idioma', () => {
test('pasa las reglas de idioma de axe', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withRules([
'html-has-lang', // elemento html debe tener lang
'html-lang-valid', // lang debe ser válido
'html-xml-lang-mismatch', // lang y xml:lang deben coincidir
'valid-lang' // atributos lang deben ser válidos
])
.analyze();
expect(results.violations).toHaveLength(0);
});
test('todas las páginas tienen declaración de idioma consistente', async ({ page }) => {
const pages = ['/es/', '/es/nosotros', '/es/servicios', '/es/contacto'];
for (const url of pages) {
await page.goto(url);
const lang = await page.locator('html').getAttribute('lang');
expect(lang).toMatch(/^es/);
// Ejecutar axe en cada página
const results = await new AxeBuilder({ page })
.withRules(['html-has-lang', 'html-lang-valid'])
.analyze();
expect(results.violations).toHaveLength(0);
}
});
});
Checklist de Resumen
- [ ] Cada página tiene atributo
langen el elemento<html> - [ ] Los códigos de idioma siguen el formato BCP 47
- [ ] El idioma coincide con el contenido real de la página
- [ ] Las frases extranjeras tienen atributos
langapropiados - [ ] Los idiomas RTL tienen atributo
dir="rtl" - [ ] CSS proporciona configuraciones tipográficas específicas por idioma
- [ ] Las versiones en idiomas alternativos tienen enlaces
hreflang - [ ] La detección de idioma usa una cadena de respaldo apropiada
- [ ] Los componentes soportan cambio dinámico de idioma
- [ ] Las pruebas automatizadas verifican los atributos de idioma
Artículos Relacionados
- Guía de HTML Semántico - Estructura de documento
- Hub de Cumplimiento WCAG - Todos los evaluadores de accesibilidad
Referencias
- W3C - WCAG 2.2 SC 3.1.1 Language of Page
- W3C - WCAG 2.2 SC 3.1.2 Language of Parts
- W3C - H57 Using lang attribute
- MDN - lang attribute
- IETF - BCP 47: Tags for Identifying Languages
- W3C - Choosing language tags