Skip to content

English · Español

Teoría 06 — Política bilingüe en el portal

🇪🇸 La política bilingüe del repo (§0.6 de CLAUDE.md) tiene cuatro capas en el portal: contenido (teoría), interfaz (chrome), entrada del usuario (notas, diario), y assessment (quizzes y exámenes). Cada capa elige independientemente: contenido es bilingüe siempre, chrome es localizable, entrada es libre, assessment puede ser monolingüe o pareado. La regla central: el inglés es el canónico; el español es el complemento. Ninguna cadena del sistema obliga a leer en un idioma que no controlas.

Las cuatro capas

La superficie bilingüe del portal se descompone en cuatro capas independientes:

  1. Contenido de teoría. Lo que enseña el currículo (docs/phase-NN-*/theory/*.md).
  2. Chrome de la UI. Etiquetas, botones, navegación, mensajes de error.
  3. Entrada del usuario. Notas, entradas de journal, respuestas de examen.
  4. Contenido de assessment. Prompts de quiz, preguntas de examen, answer keys.

Cada capa tiene su propia política. Mezclarlas — por ejemplo, tratar los prompts de quiz y el chrome de la UI bajo la misma pipeline i18n — produce comportamientos extraños; un botón etiquetado "Next" en chrome inglés no debería cambiar a "Siguiente" en español solo porque el quiz esté en español, y viceversa.

Capa 1: Contenido de teoría

Según CLAUDE.md §0.6 (que codifica el addendum §A2), las páginas de teoría son bilingües por fuente:

  • El documento completo está escrito en inglés (canónico).
  • Un blockquote > 🇪🇸 de 1 a 3 frases de resumen aparece en la parte superior, en español.
  • Pueden aparecer glosas en español inline junto a términos conceptuales densos, con moderación.
  • Las cabeceras de sección, identificadores de código, nombres de archivo y mensajes de commit permanecen solo en inglés.

El portal renderiza estas páginas tal cual — sin pipeline de traducción. El bloque 🇪🇸 es parte de la prosa, no un artefacto de la UI. El toggle de locale (teoría 04) no afecta a esto; cambiar el locale de inglés a español no oculta el bloque 🇪🇸 ni expone una variante solo en español. No hay variante de teoría solo en español.

Esta es una elección deliberada. Mantener dos traducciones paralelas del currículo es el anti-objetivo de §A2 — duplica el coste de mantenimiento del currículo sin duplicar su valor pedagógico. El resumen en español al principio de cada página cubre el 80% de mayor valor (orientar al lector hispanohablante a las partes densas) al 5% del coste.

Por qué el canónico es inglés

Tres razones, en orden de prioridad:

  1. Reutilizabilidad. Otros learners (por addendum §A3) pueden no ser hispanohablantes. Un currículo escrito canónicamente en español obligaría a cualquier fork no hispanohablante a traducir primero; el canónico en inglés mantiene la barrera mínima.
  2. Vocabulario técnico. El vocabulario de aprendizaje automático (machine learning) y de ingeniería es canónicamente en inglés. "Backpropagation", "gradient descent", "attention head" — existen traducciones al español pero su adopción es desigual y la carga cognitiva de un término técnico traducido no es trivial.
  3. Identificadores de código. Los identificadores del código fuente deben ser ASCII en inglés por política del repo. La teoría que usa nombres de identificador (p. ej., softmax_with_temperature(logits)) se lee más natural cuando la prosa que la rodea coincide.

El contrato del resumen en español

El bloque de resumen al inicio de cada página de teoría tiene un contrato estricto:

  • Longitud: 1 a 3 frases. Ni un párrafo. Ni un único fragmento.
  • Voz: activa, declarativa, sin hedging.
  • Contenido: la idea central de la página, en español cotidiano (forma está bien, vosotros no es apropiado para lectores latinoamericanos).
  • Formato: un blockquote markdown con prefijo > 🇪🇸.

El contrato se impone mediante un hook de docs-linting: cada archivo docs/phase-NN-*/theory/*.md se chequea por una línea ^> 🇪🇸 dentro de las primeras 5 líneas no en blanco. Los resúmenes faltantes hacen fallar el build de docs.

Capa 2: Chrome de la UI

El chrome de la UI del portal se localiza — los strings en inglés y español viven en src/miniportal/locale/{en,es}.yaml y un filtro Jinja2 {% trans %} los resuelve frente a students.locale.

La superficie localizada es pequeña: ~50 strings cubriendo etiquetas de navegación (Curriculum, Journal, Notes, Quizzes, Progress, Admin, Logout), etiquetas de botones (Save entry, Submit quiz, Reveal answer, Reset), cabeceras de tabla (username, display_name, last login), y avisos de error / éxito (Saved., Password updated., Login required.).

Una cadena de fallback de locale maneja strings faltantes: si es.yaml carece de una clave, el renderer cae a en.yaml. Faltar en ambos es un error de arranque, atrapado en CI.

El toggle de locale es un control de UI a un clic (botón 🇪🇸/🇬🇧 en el nav superior) que:

  1. Escribe el nuevo valor en students.locale.
  2. Pone una cookie lc=<new locale> para que la capa de renderizado la lea en el siguiente request.
  3. Refresca la página.

El ajuste es por estudiante, no por sesión. El mismo navegador con sesión en dos cuentas de estudiante respetará el locale preferido de cada cuenta de forma independiente.

Por qué localizar el chrome, dado el contenido canónico en inglés

Una pregunta razonable: si el contenido de teoría es inglés con un resumen en español, ¿por qué localizar los botones?

Respuesta: la localización del chrome es barata (~50 strings) y elimina un impuesto de fricción. Un estudiante hispanohablante que lee la teoría en inglés (con el resumen en español) sigue beneficiándose de botones etiquetados Guardar y Siguiente. El coste cognitivo de re-traducir el chrome en cada clic no es cero y se acumula.

La asimetría es deliberada: localizar 50 strings de chrome tiene un coste de mantenimiento bajo; traducir 40 fases de teoría tiene un coste de mantenimiento alto. El portal escoge el barato.

Capa 3: Entrada del usuario

El contenido generado por el usuario — notas, entradas de journal, respuestas de examen — es completamente sin restricciones. Borja puede escribir notas en inglés, en español, en párrafos mezclados con code-switching, o en cualquier otro idioma. El portal no detecta, valida, ni transforma el idioma del input.

Almacenamiento: las columnas body_markdown son UTF-8 TEXT; la collation por defecto de SQLite unicode_string aplica. Indexación sobre estas columnas es full-text vía FTS5 (aplazada a una fase futura si hace falta); el modo unicode del tokenizer de FTS5 maneja correctamente contenido multilingüe.

El filtro de inyección de la Fase 37 (teoría 03) se aplica uniformemente sin importar el idioma del input. El filtro opera sobre clases de caracteres y patrones, no sobre tokens de idioma.

La convención de archivo del journal (learners/<name>/journal/YYYY-MM-DD.md) es igualmente sin restricciones. El nombre del archivo es ASCII (la fecha), el cuerpo es cualquier cosa que Borja quiera escribir.

Por qué no validar el input

Tres razones:

  1. El usuario sabe mejor. Forzar un idioma sobre las notas personales es paternalista y contraproducente. Un learner hispanohablante nativo puede tomar notas en español porque así piensa.
  2. El code-switching es la norma. Borja rutinariamente escribe // Esto evita un buffer overflow — mitad español, mitad inglés. Una validación que rechazara esto frustraría sin beneficio.
  3. A ningún consumidor aguas abajo le importa. La tabla de notas solo la lee el learner que la escribió. No hay pipeline automatizado que necesite una etiqueta de idioma normalizada.

Capa 4: Contenido de assessment

Los prompts de quiz y las preguntas de examen reciben un tratamiento especial: pueden autorearse bilingües.

Los schemas de quizzes.body_yaml y exam_questions.prompt soportan campos pareados por idioma:

slug: past-simple-irregular-eat
phase_n: 32
prompt_en: |
  Conjugate "eat" in the past simple, 3rd person singular.
prompt_es: |
  Conjuga "eat" en pasado simple, 3ª persona del singular.
answer_key: ate
rubric:
  match: exact_ci
  normalize: trim

La capa de renderizado elige prompt_en o prompt_es según el locale activo; cae a prompt_en si falta prompt_es.

La answer key no se traduce. Para el currículo del tutor de gramática, la respuesta es por definición una forma inglesa (p. ej., ate). Traducir el prompt mientras se mantiene la answer key en el idioma objetivo preserva la intención de aprendizaje — "quiero el past simple en inglés de eat" — sin importar en qué idioma se lea el prompt.

Para la maquinaria de respuestas estructuradas de la Fase 30, la especificación de la rúbrica es agnóstica al idioma (regex, exact-match, JSON-shape match); no hace falta traducción.

Respuestas de examen

exam_responses.response_text es la respuesta de texto libre del learner. Para preguntas del tutor de gramática, la respuesta es una sola palabra en inglés (ate, went, was). El matcher es comparación insensible a mayúsculas con trim-normalizado; nada específico de idioma.

Para respuestas estructuradas de mayor longitud de la Fase 30, el learner envía una estructura JSON siguiendo la rúbrica. Las claves del JSON son canónicamente en inglés (p. ej., {"verb": "...", "tense": "...", "person": "..."}); los valores pueden estar en cualquiera de los dos idiomas si la rúbrica lo permite. Esto está documentado por pregunta en la rúbrica.

El lab de quiz bilingüe

El lab de la Fase 41 incluye un smoke test para renderizado bilingüe de quizzes:

  1. Pone students.locale = 'en'. Renderiza el quiz. Asegura que el prompt coincide con prompt_en.
  2. Pone students.locale = 'es'. Re-renderiza. Asegura que el prompt coincide con prompt_es.
  3. Envía la misma respuesta en ambos locales. Asegura que ambos puntúan igual.

El lab también atrapa el caso "falta prompt_es": un quiz con solo prompt_en definido renderiza correctamente en cualquier locale (fallback al inglés), sin error.

Lo que esta política NO hace

  • Sin traducción automática. El portal nunca llama a un servicio de MT. Las traducciones las suministra el autor y están versionadas.
  • Sin autodetección de locale. No se parsea la cabecera Accept-Language. Por defecto es inglés; cambiar es explícito.
  • Sin soporte RTL. Hebreo, árabe, etc. están fuera de alcance. Añadir RTL implica trabajo de CSS, layout y tokenización que el currículo no presupuesta.
  • Sin detección del idioma del contenido. El idioma de una nota no se etiqueta. La búsqueda trata todo el input como una única bolsa de tokens.
  • Sin memoria de traducción. Los pares EN/ES de los quizzes de la Fase 41 se autorean a mano cada vez; sin glosario compartido entre quizzes (el corpus es demasiado pequeño).

Camino hacia adelante

Dos extensiones naturales están documentadas pero pospuestas:

  1. Un tercer idioma. Si un futuro learner es lusohablante, la misma maquinaria de campos pareados (prompt_pt) se extiende sin cambios de schema. El enum del locale y la UI del toggle de locale necesitan actualizaciones; ambos acotados.
  2. Un modelo de nota etiquetada por idioma. Un futuro learner podría querer filtrar "muéstrame solo mis notas en español". Una columna notes.language (TEXT nullable, puesto por el learner) bastaría; no en el MVP.

El workflow bilingüe para el propio Claude Code

Una nota sobre las propias salidas de Claude en este repo: según §0.6, Claude responde a Borja en inglés con resúmenes opcionales en español para material conceptual denso. El portal no cambia este contrato — el comportamiento de Claude está gobernado por CLAUDE.md y el addendum, no por el ajuste de locale del portal. El portal localiza la UI; no localiza el workflow de autoría subyacente.

La pipeline de pre-write que produjo este mismo archivo es bilingüe por fuente: el contenido del archivo es inglés, el resumen en español al principio está en español. El portal sirve este archivo sin cambios sin importar quién lo lea.

Recapitulación en un párrafo

La política bilingüe del portal se descompone en cuatro capas, cada una con su propia regla: la teoría es canónica-inglesa con resumen en español, el chrome de la UI se alterna por locale entre inglés/español, la entrada del usuario es libre sin restricciones, el contenido de assessment puede autorearse bilingüe con answer keys compartidas. El toggle de locale solo afecta al chrome y a los prompts de assessment; la teoría se queda tal como está escrita. El diseño trata al inglés como el canónico que soporta la carga y al español como un complemento — nunca al revés — porque el currículo está pensado para que learners no hispanohablantes hagan fork mientras sirve al estilo de aprendizaje de Borja.

Fin del pre-write de teoría para la Fase 41. Labs y BLUEPRINTs se cubren con agentes paralelos.