DEV Community

Lumora
Lumora

Posted on

«es» no es un mercado: el bug de i18n que nos costó reescribir una campaña entera

Nota: en Lumora construimos libros infantiles personalizados con IA, y escribimos en diez idiomas.
Este artículo cuenta un problema de i18n con el que chocamos de frente y cómo lo resolvimos. Lo contamos
desde dentro, con nuestro nombre, porque el error nos costó reescribir una campaña entera.

Casi todos los equipos tratamos el idioma como si fuera el país. Ponemos es en el selector, sacamos las cadenas a un JSON y damos el problema por cerrado. Funciona hasta el día en que tu producto tiene algo que ver con una fecha.

Y entonces descubres que es no es un mercado, son varios calendarios distintos que comparten vocabulario.

El día en que el regalo no llega el 25

Teníamos una campaña de regalos escrita en español. Correcta gramaticalmente, revisada, sin errores de traducción. Y aun así estaba mal para una parte grande de quien la leía:

  • En España, el gran momento de regalo infantil no es el 25 de diciembre: son los Reyes Magos, el 6 de enero. Un mensaje que dice "pídelo antes del 24" le está dando a una familia española una fecha límite equivocada por casi dos semanas.
  • En México conviven las dos cosas: Navidad y Reyes, con la rosca del 6 de enero.
  • En Argentina, Chile o Uruguay, la Navidad es el 25 de diciembre… en pleno verano. A 30 grados. Con las vacaciones escolares largas empezando, no terminando.

Fíjate en lo incómodo del asunto: los tres casos hablan español. Los tres pasan el mismo test de traducción. Y los tres necesitan un mensaje distinto, una imagen distinta y una fecha límite distinta.

El mismo golpe existe en portugués. En Brasil la Navidad también cae en pleno verano, enero es mes de vacaciones, y el día en que de verdad se reinicia la rutina familiar no es el 1 de enero: es la volta às aulas, en febrero. Si tu calendario de contenidos asume "año nuevo, hábitos nuevos" en enero, en Brasil llegas un mes antes de que a nadie le importe.

Por qué el código de locale no te salva

La respuesta obvia es "usa es-ES y es-AR". Es correcta y casi nunca es suficiente, por dos razones prácticas:

1. La cadena se colapsa. En cuanto el locale viaja por un Accept-Language, un navigator.language, una URL /es o un CDN, casi todo el mundo acaba normalizando a los dos primeros caracteres. Nuestra propia web sirve /es y /pt. El subtag de región existe en teoría y ha desaparecido en la práctica.

2. Estás mezclando dos ejes que no son el mismo. El idioma responde a en qué palabras te hablo. El calendario y el hemisferio responden a en qué momento vives. Un argentino que navega en español desde Madrid y una familia española son el mismo es-ES por cabecera y dos realidades distintas. Y al revés: alguien puede querer el libro en español para un sobrino en Chile.

El bug de fondo es que estábamos derivando una fecha cultural a partir de una etiqueta de idioma. Son dos ejes independientes y los teníamos acoplados.

Lo que cambiamos

Separamos las dos cosas. El idioma decide las palabras. Un concepto aparte, que llamamos "momento de regalo", decide la fecha, la estación y las imágenes:

type Hemisphere = "north" | "south";

interface GiftMoment {
  key: "reyes" | "navidad" | "both";
  giftDate: string;      // la fecha que de verdad importa
  orderBy: string;       // el deadline que mostramos
  hemisphere: Hemisphere;
}

// El idioma NO entra en esta tabla. Solo el territorio.
const GIFT_MOMENTS: Record<string, GiftMoment> = {
  ES: { key: "reyes",   giftDate: "01-06", orderBy: "12-28", hemisphere: "north" },
  MX: { key: "both",    giftDate: "12-25", orderBy: "12-15", hemisphere: "north" },
  AR: { key: "navidad", giftDate: "12-25", orderBy: "12-12", hemisphere: "south" },
  CL: { key: "navidad", giftDate: "12-25", orderBy: "12-12", hemisphere: "south" },
  BR: { key: "navidad", giftDate: "12-25", orderBy: "12-12", hemisphere: "south" },
};

// Dos entradas, no una. Y el idioma nunca se usa para inferir el territorio.
function resolveCampaign(locale: string, country: string) {
  const moment = GIFT_MOMENTS[country] ?? GIFT_MOMENTS.ES;
  return {
    copy: t(locale, `campaign.${moment.key}`),
    season: moment.hemisphere === "south" ? "summer" : "winter",
    orderBy: moment.orderBy,
  };
}
Enter fullscreen mode Exit fullscreen mode

Tres consecuencias que no habíamos anticipado:

  • La nieve es un bug de contenido. Nuestras ilustraciones navideñas por defecto llevaban nieve y bufandas. Para diciembre en el hemisferio sur eso no es "genérico", es sencillamente otra estación. La estación pasó a ser un parámetro del prompt, no una constante.
  • El deadline es parte del producto. Si lo que entregas se imprime, la fecha límite real depende de la fecha de regalo local. Un único "pide antes del X" global es incorrecto para la mitad de tu audiencia.
  • Cuando no sabes el país, no adivines por idioma. Preferimos preguntar, o mostrar un texto neutro respecto a la fecha, antes que asumir que es significa España.

La parte que más nos sorprendió: traducir arrastra la cultura de origen

Relacionado con esto, y quizá lo más útil que hemos aprendido: escribimos cada idioma de forma nativa en lugar de traducir desde el inglés.

No fue una decisión estética. Un cuento escrito en inglés y traducido al español llega con las asunciones del original metidas dentro de la estructura: la cena de Nochebuena en el sitio equivocado, el invierno como telón de fondo, los nombres, el ritmo de las frases, incluso qué se considera una travesura. La traducción arregla las palabras y deja intacto el marco cultural. Es exactamente el mismo error que el de las fechas, una capa más arriba.

Si estás construyendo algo multiidioma, esta es la pregunta que nos habría ahorrado más trabajo: ¿esta cadena depende del idioma, o depende del lugar? Si depende del lugar, sácala del sistema de traducción hoy. En cuanto vive en el i18n.json ya está acoplada al idioma y desacoplarla después es mucho más caro.


Por transparencia: esto salió de trabajar en Lumora, donde generamos libros infantiles ilustrados y personalizados. Subes una foto de tu peque, la IA escribe e ilustra el cuento, y el niño o la niña es el protagonista con el mismo aspecto en todas las páginas. Escribimos en diez idiomas de forma nativa, no traducida, y el primer libro es gratis y sin tarjeta.

Si has peleado con esto en otro idioma, nos interesa mucho saber cómo lo resolviste. El caso del árabe nos dio otro problema completamente distinto —de capa de texto en el PDF— y esa es otra historia.

Top comments (1)

Collapse
 
swapnoneel123 profile image
Swapnoneel Saha

la separación entre idioma, región y momento de regalo es una buena decisión. yo añadiría pruebas con el mismo idioma y varios países, además de una revisión visual para fechas, imágenes y estaciones. también conviene conservar la región completa desde la url o el perfil, sin depender solo de accept language. así se puede detectar una pérdida de contexto antes de publicar una campaña.