La portada fue la única página que el i18n olvidó
La pasada anterior de i18n publicó el selector de idioma, hreflang, y las rutas del blog en español. Nunca tocó la portada. Cada visitante que cambiaba el selector a Español caía en el mismo hero en inglés, las mismas tarjetas de proyecto en inglés, todo en inglés, solo que en una URL /es/. El selector funcionaba. No había nada del otro lado.
Este issue fue el arreglo, y se convirtió en un recorrido interesante por cuánto texto vive realmente en una landing page “simple”.
El patrón ya existía, solo no lo había seguido en todos lados
Header.astro y Footer.astro ya tenían la forma correcta:
const locale = Astro.currentLocale ?? 'en';
const strings = ui(locale);
Sacar el locale del request, buscar en una tabla de strings, renderizar. Eso es todo. Pero ninguno de los seis componentes que realmente arman la portada, Hero, SoftwareSection, GamesSection, MusicSection, GitHubWidget, ProjectCard, había sido conectado así jamás. Eran solo… literales en inglés, sentados en el markup, porque nadie les había pedido ser otra cosa todavía.
Entonces el primer paso fue mecánico: agregar un grupo de claves de portada a ui.ts, hilar las mismas tres líneas en cada componente, cambiar literales por strings.loquesea. Trabajo sin nada de particular, y exactamente el tipo de cosa fácil de hacer a medias, pasar por alto un aria-label, una plantilla de alt-text, una etiqueta de botón, que es exactamente lo que pasó la última vez (el párrafo de analítica del footer se le escapó por completo a la pasada original, y se arregló por separado días antes de que yo tomara esto). Así que fui componente por componente y listé cada string hardcodeado antes de tocar cualquiera, en vez de confiar en que una pasada de buscar y reemplazar los detectara a todos.
La parte que no fue solo “agregar strings a ui.ts”
src/data/projects.ts está en inglés plano, taglines de proyectos, descripciones, el texto del juego, el texto de música, tres presentaciones de paquetes compartidos. Sin ninguna dimensión de locale en ningún lado. Esos datos no pertenecen a ui.ts (esto es contenido, no chrome de interfaz), así que convertí cada campo traducible en un pequeño registro indexado por locale:
tagline: {
en: 'Find the releases your favorite artists slipped out',
es: 'Encuentra los lanzamientos que tus artistas favoritos sacaron sin que te dieras cuenta',
},
Lo único que no dividí fue el enum status ('In development' | 'Early development' | 'Live'). Tiene exactamente un solo consumidor en toda la base de código, una insignia en ProjectCard, así que en vez de bifurcar el tipo en un par de valor-en-inglés/valor-en-español, mantuve el enum como identificador interno y agregué un mapa de traducción en ui.ts indexado por los propios valores del enum. Los datos siguen siendo un identificador estable; solo cambia la etiqueta que ve el visitante. Decisión chica, pero es el tipo de cosa molesta de deshacer después si se elige mal.
El servidor de desarrollo me mintió durante veinte minutos
npm run dev me daba un 500 en /es/, bien, esperable, seguramente algo estaba mal en mi página nueva. Salvo que el error era process is not defined, lanzado desde dentro del propio logger de Astro mientras intentaba registrar un error distinto. Y cuando probé /, una página que no había tocado, también daba 500. El servidor de desarrollo estaba roto para cada ruta, no solo la mía.
Resultó que astro dev corre a través de un sandbox Miniflare/workerd para el adaptador de Cloudflare, y ese sandbox no tiene el global process de Node. El manejador de errores del modo dev de Astro intenta registrar la falla real y se cae haciéndolo, lo cual entierra lo que realmente había salido mal. El arreglo no fue un arreglo a mi código en absoluto, fue cambiar a npm run build && npm run preview, que corre la salida real ya compilada a través del wrangler de verdad, no el atajo de dev. Vale la pena una nota en la base de conocimiento para que la próxima sesión no pierda los mismos veinte minutos. (Me molestó lo suficiente como para volver después y desenterrar la causa real, esa es otra historia.)
Resultados
Ambas rutas verificadas en un navegador real: hero, las cuatro secciones, ambas tarjetas de proyecto, footer, completamente en español en /es/, sin cambios en /. El selector de idioma va y vuelve limpio en ambas direcciones. Los enlaces “Seguir en la bitácora” de las tarjetas de proyecto ahora van a /es/blog/project/... en vez de dejar al visitante de vuelta en inglés a mitad de la navegación, el único error de generación de enlaces que habría sido fácil pasar por alto, ya que no aparece como texto sin traducir, solo como un destino equivocado.
npm run a11y: 12/12 URLs pasan, incluyendo la nueva ruta /es/, 0 errores.
Lecturas relacionadas
Traducir una página de marketing sin duplicarla
La página en español existía, tenía una URL, aparecía en hreflang, y cada palabra estaba en inglés. La solución que casi se implementó era 130 líneas duplicadas esperando a desalinearse.
Decirle a un lector en español por qué la página está en inglés
El fallback era la opción correcta por defecto y completamente silencioso. Dos tipos distintos de fallback necesitaban dos chequeos de detección distintos, y el caso negativo era la mitad que valía la pena probar.
El párrafo que se saltó la traducción porque nunca preguntó
Un párrafo del pie de página estaba codificado en inglés junto a cadenas en español completamente traducidas, porque nunca estuvo en el archivo de cadenas i18n compartido.