---
name: project-i18n-web-2026-07
description: "Multi-idioma de la web pública (es/en, de listo pero apagado) — modelo, qué está traducido y qué falta; punto de retomada del 2026-07-26"
metadata: 
  node_type: memory
  type: project
  originSessionId: 0db5f437-b468-4735-a68f-19f91144a0f1
  modified: 2026-07-26T21:36:58.889Z
---

Arco activo desde 2026-07-26 (rama `main`, todo pusheado): la web pública tiene que estar completa en **español e inglés**; el **alemán está traducido pero apagado** hasta que Manu lo revise (es nativo) y el francés está previsto. Parte de [[project-refactor-web-2026-07]].

**El modelo, decidido y construido.** Prefijo de camino + **slugs traducidos** (`/en/tools/kombucha-tuner`), el español en la raíz y como `x-default`. Fuente única `config/idiomas.php`: `idiomas` (activo sí/no), `traducidas` (qué páginas existen en cada idioma) y `rutas` (nombre de ruta → camino por idioma). Regla dura: **lo que no está traducido no existe en ese idioma** — la ruta no se registra, devuelve 404 y el hreflang no la declara. `routes/publico.php` registra las páginas en un bucle por idioma; añadir un idioma es una entrada en config + una carpeta en `lang/`, cero líneas de enrutado. `App\Support\Idiomas` + helpers `ruta()`, `marcaIdioma()`, `atributoIdioma()`.

**El idioma se recuerda pero no secuestra** (`FijarIdiomaWeb`): sesión + cookie `fm_idioma` (un año, sin cifrar → está en la lista `except` de `encryptCookies`), y solo se usa en la **puerta de entrada** (`/`). Un enlace compartido de `/pro` se lee en español aunque tengas guardado el inglés. El conmutador manda `?hl=xx` para que el clic cuente como decisión y el middleware limpia el parámetro; sin eso, quien viene de /en y pincha ES rebota a /en por su propia preferencia.

**Traducido y en línea (es/en, de escrito):** app (245 claves), home, /pro, /mentor, /faq, /herramientas, afinador, y el armazón (menú y pie) en `lang/*/armazon.php`.

**Muerto y retirado:** el `applyLang()` de `public/landing/site.js` con `[data-en]` + localStorage. Cambiaba el texto en cliente y, con las páginas ya traducidas en servidor, dejaba mezclas (menú inglés, cuerpo español). Fuera el JS y los 35 `data-en`. **No volver a introducir traducción en cliente.**

**PUNTO DE RETOMADA (2026-07-26, tras 3 commits).** El mecanismo para traducir el contenido de `/fermentos` está montado y **verificado inerte** (el español pasa por él sin cambiar un byte), pero **sin contenido todavía**:
- `App\Support\ContenidoFermentos` — aplica la traducción **sobre el modelo en memoria** después de cargarlo, así las vistas siguen pintando `$guide->intro` y no saben nada. Lee `lang/{en,de}/contenido_fermentos.php`, que **hay que escribir**: `guias` por slug español (name/description/intro/slug/faqs por posición/pasos por `step_number` con acciones y consejos por posición), `categorias` igual (+ web_title/chooser), `ingredientes` por nombre español y `papeles`. Lo que falte se queda en español, no se rompe.
- Slugs traducidos también en las fichas (`/en/ferments/sauerkraut`): el slug español es la clave canónica, `slugCanonico()` traduce la entrada y `Idiomas::parametrosPorIdioma()` (gancho nuevo) hace que hreflang y conmutador emitan el slug de cada idioma.
- El material de partida está volcado: **4.780 palabras** en español (4 clasificaciones + 5 fichas publicadas: kombucha, kefir-agua, chucrut, kimchi, vinagre). Se regenera con el volcado por tinker que hay en el historial, o directo de la BBDD.

**Falta, en este orden:** (1) `lang/*/pagina_fermentos.php` con el armazón de index/categoria/show y reescribir esas tres vistas (hoy tienen el copy en español dentro; `horquilla()` del modelo lleva « días» hardcodeado); (2) escribir `contenido_fermentos.php` en EN y DE; (3) salmuera y contacto; (4) legales como **traducción de cortesía** con nota de que prevalece la española (decisión mía, pendiente de que Manu lo valide con su asesoría); (5) meter `fermentos.index`, `fermentos.show`, `salmuera`, `contacto` y las legales en `traducidas` y verificar rutas + hreflang + sitemap.

**El blog NO se traduce** (decisión del propietario): `blog.index` solo tiene camino en `es` y los enlaces desde otro idioma llevan «(ES)» que pone `marcaIdioma()` — desaparece solo el día que la página se traduzca.

**El precio del modelo, dicho para que no sorprenda:** el español del contenido de fermentos se edita en el admin y los demás idiomas en ficheros `lang/`, así que un cambio de copy en español no arrastra al inglés. La alternativa era migración/tabla de traducciones sin pantalla en el admin donde editarla.

Gotcha Blade encontrada esta sesión: `@if` en la misma línea que `@class(...)` **no compila** («unexpected token endif») — construir la etiqueta en una variable antes del ancla. Relacionado: [[feedback-documentos-como-fichero]].
