# Idiomas y localización — games2 **Estado:** contrato de producto v1, revisado el 6 de octubre de 2026. La portada y el catálogo base ya permiten español e inglés; los flujos futuros deben cumplir esta política antes de activarse. El idioma de interfaz es una preferencia de presentación y nunca un permiso. La lengua del contenido de un juego puede formar parte de su configuración versionada, según la distinción siguiente. ## Alcance y prioridades El primer conjunto completo de publicación es `es` y `en`. Se usarán etiquetas [BCP 47](https://www.rfc-editor.org/info/rfc5646/) para añadir variantes regionales, otras escrituras y lenguas nuevas. Solo aparece un idioma en el selector si navegación, permisos, ayuda, seguridad, condiciones, errores, avisos y juegos publicados cumplen la revisión de esa lengua. Una traducción automática sin revisión humana no abre un nuevo idioma de producto. Al entrar, la preferencia explícita de la cuenta prevalece sobre la preferencia local del navegador; después se consideran los idiomas configurados en el dispositivo y, por último, `es`. Mientras no haya cuentas, `games2` guarda la elección local y usa `navigator.languages` como pista. Cambiar el idioma de interfaz es inmediato y persistente, sin cambiar reglas, permisos o plaza. El selector nombra cada idioma en su propia lengua (`Español`, `English`). El documento actualiza `lang`; una futura lengua RTL actualizará también `dir` y exigirá revisar diseños con propiedades CSS lógicas. Se distingue `uiLocale` de `contentLocale`. Juegos de palabras, bancos de preguntas y estímulos cuya dificultad depende de la lengua fijan `contentLocale`, versión del contenido y variantes admitidas al configurar la sala/ronda. Cambiar el selector de interfaz no permite elegir otra pregunta, traducción más favorable o diccionario durante una respuesta. En rondas temporizadas, la variante del estímulo permanece fijada hasta el punto seguro; menús y mensajes de ayuda pueden cambiar inmediatamente. Traducciones equivalentes se validan antes de publicar; si no lo son, se declaran variantes de reglas/contenido en lugar de tratarlas como un cambio cosmético. Antes de indexar el catálogo público, cada página pública traducida tendrá URL propia (`/es/...`, `/en/...`), `canonical` correcto y enlaces `hreflang` entre equivalentes. La raíz actual solo demuestra la selección cliente en la fase inicial; no se considerará una estrategia SEO multilingüe terminada. Los enlaces privados a salas mantienen identificadores estables y no incorporan una lengua al token de invitación. El idioma de interfaz es distinto de la zona horaria. El servidor conserva instantes UTC y valores/identificadores independientes del idioma; los intervalos de ronda tienen su dominio monotónico explícito. La web usa `Intl` y el futuro cliente nativo un formateador compatible con CLDR, con fixtures comunes de números, fechas, horas, plurales y listas. Compartir semántica no exige usar la API JavaScript en Dart. El juego no concatena fragmentos traducidos para construir frases. Para mensajes complejos se adoptará un catálogo de claves con formas plurales/select tipadas antes de introducirlos. La moneda, si alguna vez existe, se trata por sus propios contratos y no se infiere de la lengua. Los catálogos de traducción se tratan como datos, sin HTML ejecutable ni interpolación de marcado libre. Variables y enlaces permitidos se tipan y revisan junto a la plantilla para que una traducción no altere permisos ni introduzca contenido activo. ## Contratos por capa | Capa | Obligación | | --- | --- | | Frontend | Claves estables para navegación, estados, formularios, reglas, `aria-label`, `alt`, errores y ayuda. Carga de catálogos por idioma; fallback visible y controlado. `lang` en fragmentos con lengua distinta a la página. Ningún texto de seguridad oculto en una imagen. | | API Go | Acepta `Accept-Language` en lecturas localizadas y devuelve `Content-Language` y `Vary: Accept-Language` cuando corresponda. Códigos de error y campos estructurales permanecen invariantes; la explicación humana se traduce. La negociación no cambia la autorización ni la respuesta privada que se permite recibir. | | Servidor de dominio | Almacena claves y argumentos estructurados para avisos, eventos, moderación y resultados; no persiste una frase como dato autoritativo. Los envíos diferidos fijan idioma, versión de plantilla y datos permitidos en el momento de composición. | | Paquete de juego | `metadata.defaultLocale` y `metadata.locales` enumeran catálogos versionados. Título, descripción, reglas, acciones, resultados, ayudas, alternativas de imagen, subtítulos y textos de portada se resuelven por claves. El arte con texto requiere variante localizada. | | Publicación interna | Valida cobertura de todos los idiomas de producto, variables/plurales, enlaces, límites de longitud, accesibilidad y revisión humana antes de activar un juego. Los recursos y traducciones publicados quedan fijados por digest/versión junto al paquete. | La API de catálogo inicial acepta las preferencias `es`/`en` (incluidas variantes como `en-GB`) y responde con `locale` por ficha. Si faltase una traducción en esta fase, la ficha usa su idioma por defecto y lo indica en `locale`; una publicación real **no** debe depender de ese fallback para textos críticos. El frontend envía `Accept-Language` al cambiar el selector. El catálogo público podrá almacenarse en caché solo separando idioma y proyección de seguridad. Estas cabeceras siguen la semántica de [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-content-language). Los mensajes del protocolo conservan `type`, `code`, identificadores y argumentos. El [modelo compartido](shared-data-models.md) usa `LocalizedMessage { key, args }`: web y Flutter traducen la misma clave con argumentos seguros; `code` identifica la causa y no sustituye el texto traducible. Las formas v1 se normalizan mediante adaptador. Si una respuesta incluye texto humano del servidor, declara su lengua. El cambio de `uiLocale` en una conexión en tiempo real actualiza su localización, sin transición del juego ni cambio de la variante de contenido fijada para la ronda. En invitaciones y avisos se conserva la lengua elegida por quien recibe, sin filtrar su preferencia a terceros. El [perfil multidispositivo](../game-engine/multi-device-profile.md) exige textos de `TVBoard`, `Desktop` y `Mobil`, preparación/entrada QR de mesa, enlace personal tras invitación y «Jugar solo en el móvil». El idioma de la TV común lo elige quien puede gestionarla; no reemplaza la preferencia de cada mando. TV personal y móvil negocian sus textos sin difundir preferencias privadas. Cambiar idioma o presentación conserva plaza/recibos. En juegos temporizados, el [perfil de sincronización](../game-engine/synchronization-profile.md) fija el punto seguro: una traducción no reinicia el plazo ni revela antes una pregunta; el contenido necesita equivalencia de dificultad/lectura además de reloj común. La futura app Flutter **solo de controles** cumple el [perfil de comunicación común](client-communication-profile.md): mismas claves/códigos y contratos localizados del mando, con catálogos compatibles con la versión de sus controles. Se verifica `es`/`en` en web y app, incluidos controles declarativos/propios, QR, autenticación, recuperación y el paso a móvil completo en la web. Compartir datos de traducción no presupone compartir componentes Svelte/Flutter ni representar el tablero en la app. ## Contenido sensible y límites No se traduce automáticamente el chat, los nombres escritos por usuarios, los reportes, las pruebas de edad ni los datos personales. Se conserva la lengua original cuando importa para moderación y se aplican las mismas prohibiciones de comunicación en todos los idiomas. Una persona menor o de edad desconocida no obtiene chat al cambiar de lengua. Ayuda, denuncia y textos de protección se revisan por personas competentes en cada idioma; si falta un texto esencial, la capacidad no se activa para ese idioma o se ofrece una ruta de ayuda comprensible. El buscador tratará acentos, mayúsculas y orden según la lengua sin alterar identificadores. Los límites de entrada se expresan en caracteres/gráficos y bytes según el contrato, nunca se justifican con la longitud media española. Las plantillas de SMS y correo se prueban con expansión, enlaces y accesibilidad antes de enviarse. ## Pruebas de aceptación 1. Una preferencia explícita sobrevive a recarga y sesión; un dispositivo sin preferencia negocia `es`/`en`; un idioma no soportado cae en `es` sin error ni mezcla silenciosa. 2. Todo el recorrido de catálogo, sala, partida, ayuda, cuenta y protección se revisa en ambos idiomas con teclado y lector de pantalla. Se verifica `html[lang]` y `lang` en textos con fallback. 3. La API devuelve ficha y cabeceras coherentes para `es`, `en-GB`, pesos `q`, idioma desconocido y fallback. Los códigos y reglas son idénticos entre lenguas. 4. El publicador rechaza claves ausentes, variables incompatibles, plurales incompletos, alternativas de imagen esenciales sin traducir y portadas con texto sin variante. 5. Diseño revisado a 320 CSS px, zoom 200 % y texto 40 % más largo; no hay recortes, botones ilegibles ni contenido fuera de pantalla. **Brecha conocida:** el esquema actual del paquete solo admite códigos sencillos como `es` y `en-US`. Antes de publicar una lengua con script o subetiquetas más complejas se debe ampliar el validador de paquetes, probar canonicalización BCP 47 y versionar el cambio de formato si altera compatibilidad.