Añadimos japonés y portugués de Brasil a toda nuestra web por 31 €
El 29 de septiembre lanzamos globalize.now en japonés y portugués brasileño. Todas las páginas, todas las tablas de precios y las 61 entradas del blog. La traducción en sí tardó ocho minutos, y la app facturó unos 31 €. globalize.now es infraestructura de localización impulsada por IA, y así fue ejecutarla en nuestra propia web, con cada cifra tomada de la página del job y del historial de Git, no de memoria.
Nuestro artículo sobre lo que costó la estrategia de crecimiento por localización en 2017 termina diciendo que todavía no publicamos nuestras propias cifras. Esta es la primera. Es una cifra de coste, no de crecimiento: todavía nadie ha tenido tiempo de visitar /ja/.
¿Qué tradujimos exactamente?
Toda la web, dos veces. Aquí está el inventario, contado directamente del repositorio:
- Interfaz: 1782 cadenas en inglés repartidas en cinco catálogos de mensajes (el sitio principal, precios, páginas de comparación, integraciones y ejemplos de traducción), unas 21.000 palabras.
- Blog: 61 artículos, unas 110.000 palabras.
- Idiomas de destino: japonés (
ja) y portugués de Brasil (pt-BR).
Son aproximadamente 130.000 palabras de origen distribuidas en dos idiomas. El pull request modificó 170 archivos y añadió 34.681 líneas. Después de la fusión, el sitemap lista 811 URL en 11 idiomas, y el sitio genera 962 páginas estáticas.
¿Cuánto tardó?
Ocho minutos de traducción, y después una pasada de calidad más larga. Arturs, nuestro CTO, añadió los dos idiomas al proyecto y el job empezó el main a las 08:56 UTC. La página del job lo desglosa:
| Etapa | Duración |
|---|---|
| Traducción (11 264 cadenas nuevas, 5632 por idioma) | 8 min 1 s |
| Revisión de QA automatizada | 27 min 11 s |
| Entrega a un pull request | 5 min 29 s |
El commit con ambos idiomas quedó registrado a las 09:29 UTC. Las otras 45 106 cadenas del job vinieron de la memoria de traducción, porque los ocho idiomas ya existentes no habían cambiado.
Arturs calculó diez minutos cuando lo mencionó en nuestro chat de equipo. No se equivocó con la traducción, pero fue optimista con el job completo, y la diferencia está en la pasada de QA. Preferimos gastar 27 minutos de tiempo de máquina revisando que saltárnoslo.
¿Qué encontró la QA automatizada?
72 avisos de 11 264 cadenas nuevas, todos clasificados como menores. 11 192 pasaron limpias, y la QA aplicó 182 correcciones automáticas por el camino.
Los avisos que abrimos fueron sobre todo la comprobación de longitud siendo cautelosa con el japonés. «Pricing» se convierte en 料金, dos caracteres, y una proporción de longitud de 0,29 queda justo por debajo del umbral de 0,3. Eso es una traducción correcta que activa una heurística pensada para idiomas alfabéticos, no un error. Aun así dejamos los avisos visibles: una comprobación que nunca se activa no es una comprobación.
¿Cuánto costó?
Sobre los 31 €: la vista de coste por idioma del proyecto muestra 15,72 € para japonés y 16,15 € para portugués brasileño. Eso es lo que cobra la app, lo mismo que vería un cliente traduciendo el mismo volumen. No es nuestro coste interno de modelo ni un descuento.
En unas 260 000 palabras traducidas, eso son unos 12 céntimos por cada 1000. No se facturó nada más encima. No hay cuota por idioma ni cuota por asiento, así que añadir el duodécimo idioma cuesta lo mismo que añadir el tercero. Los planes actuales están en la página de precios.
¿Qué implicó realmente la ingeniería?
Un pull request, fusionado esa misma tarde, y nada de eso fue traducción. Al abrirse, el job de traducción se ejecutó de nuevo sobre él y tomó 56 370 de las 56 390 cadenas directamente de la memoria de traducción, así que los catálogos ya traducidos llegaron a los archivos nuevos en siete minutos. La traducción es la parte que se automatizó. Registrar un locale nuevo en un sitio Next.js sigue siendo tu código, y conviene saber qué significa «tu código» antes de planificarlo.
Añadir un locale a next-intl es una línea en la configuración de enrutamiento. Todo lo que construye una URL, lee un locale o enumera tus idiomas es el resto del diff:
- Enrutamiento: la lista de locales, más una sobrescritura del prefijo de URL para
pt-BR(siguiente sección). - Metadatos SEO: clústeres de hreflang, canonicals,
og:locale(ja_JP,pt_BR) y la lista de locales sobre la que iteran los helpers de metadatos. - Formato: las etiquetas
Intlja-JPypt-BR, para que las fechas y los números se rendericen correctamente. - Selector de idioma: enlaza a cada locale con un
<a href hreflang>real, para que los rastreadores puedan seguirlo. - Sitemap y comprobaciones: el generador del sitemap, la comprobación de lastmod, la comprobación de enlaces internos y el verificador de traducciones tuvieron que aprender los dos códigos nuevos.
- Catálogos: archivos
.povacíos, solo con cabecera, para los dos locales, rellenados en el mismo pull request desde la memoria de traducción.
Si tu sitio es menos a medida que el nuestro, la mayor parte de esa lista es configuración. Si construyes URL a mano en algún sitio, y la mayoría de los sitios lo hacen en algún punto, encontrarás cada uno de esos sitios la primera vez que un enlace salga mal. La guía para desarrolladores cubre la configuración, y lo que hace realmente un agente de localización con IA cubre la división entre lo que se automatiza y lo que no.
¿Por qué el portugués de Brasil está en /pt-br/ y no en /pt-BR/?
Porque una etiqueta de idioma y un segmento de URL son cosas distintas, con funciones distintas.
La etiqueta correcta es pt-BR, según BCP 47. La usamos en todos los sitios donde una máquina lee el idioma: nombres de archivo, <html lang>, hreflang, og:locale y Intl. Solo la URL va en minúsculas:
// i18n/routing.ts (trimmed)
const prefixes = {
"pt-BR": "/pt-br",
} as const;
export const routing = defineRouting({
locales: ["en", "it", "de", "fr", "es", "lv", "hi", "lt", "et", "ja", "pt-BR"],
defaultLocale: "en",
localePrefix: { mode: "always", prefixes },
});
/** The URL path segment for a locale: `pt-BR` → `pt-br`, `de` → `de`. */
export function localeUrlSegment(locale: string): string {
const prefix = (prefixes as Record<string, string | undefined>)[locale];
return prefix ? prefix.slice(1) : locale;
}
Las rutas con mayúsculas y minúsculas mezcladas se escriben mal, y un sitio que responde tanto en /pt-BR/ como en /pt-br/ tiene dos copias de cada página compitiendo entre sí. Por eso /pt-BR/ redirige a /pt-br/, y localeUrlSegment() construye cada URL hecha a mano a partir del mapa de prefijos. Una sola fuente de verdad implica que el selector de idioma, los canonicals y el sitemap no pueden desincronizarse.
¿Por qué japonés y portugués de Brasil?
Son idiomas grandes de la web que todavía no cubríamos. Según el recuento de W3Techs el día que lanzamos, el japonés es el idioma de contenido del 4,9 % de los sitios web, y el portugués del 4,1 %.
El argumento a favor de hacerlo siquiera es más antiguo que nosotros. CSA Research encuestó a 8709 consumidores en 29 países en 2020, y el 76 % dijo que prefiere comprar productos con información en su propio idioma. Eso es el lado de la demanda. Lo que cambió es el lado de la oferta: antes, dos idiomas eran una relación con un proveedor, y ahora cuestan 31 €.
Nuestra propia regla para elegir idiomas es la del artículo sobre la estrategia de crecimiento. Mira de dónde viene ya tu tráfico, añade dos idiomas, no cambies nada más y mide durante un trimestre. Eso es exactamente lo que haremos con estos dos.
¿Qué nos queda por revisar?
La revisión nativa. Todavía nadie que lea japonés o portugués de Brasil de forma nativa ha revisado el resultado.
Lo que hemos comprobado: QA automatizada en cada cadena, el build pasa todas las comprobaciones previas al build, y las páginas que revisamos puntualmente en staging devuelven 200 con el lang, el canonical y el hreflang correctos, y se renderizan por completo. Lo que un build no puede comprobar es si una frase suena como si la hubiera escrito una persona. Lo aprendimos por las malas cuando auditamos nuestro propio alemán y francés y encontramos el registro equivocado en páginas enteras.
Así que la revisión viene a continuación, y actualizaremos este artículo con lo que encuentre, incluido cualquier error.
¿Podrías hacer lo mismo en tu web?
Si tus cadenas ya viven en catálogos, sí, y la parte de traducción tardará minutos. Si todavía están codificadas directamente en los componentes, primero hay que hacer la conversión. Eso se ejecuta una sola vez, en el flujo de conexión dentro de la app, y a partir de ahí los trabajos por push traducen las cadenas nuevas. La guía para vibe coders cubre apps creadas con Lovable, Bolt o Cursor, donde las cadenas codificadas son la norma.
El coste escala con las palabras que traduces, no con los idiomas ni con las personas del equipo. Por eso cambió la respuesta a «¿merece la pena añadir un idioma?». Ahora medirlo sale más barato que la reunión para hablar de ello.
Pruébalo en tu propio repositorio
Dos idiomas nos costaron ocho minutos de traducción y unos 31 €. Conecta tu repositorio, elige dos idiomas hacia los que ya apunte tu analítica, y comprueba cómo es tu propio recibo.
globalize.now convierte el texto codificado de tu app en archivos de traducción listos para localizar y los mantiene actualizados en cada despliegue.
Prueba globalize.now gratis