Volver al blog
Terminal output showing a translation script writing de.json and fr.json, alongside a folder tree and a browser rendering the German locale route.

Cómo traducir una aplicación Next.js con PolyLingo en menos de 30 minutos

By Robert

Cómo traducir una aplicación Next.js con PolyLingo en menos de 30 minutos

Al final de este tutorial tendrás un proyecto funcional multilingüe de Next.js App Router: cadenas extraídas en messages/en.json, archivos de locales traducidos para cada idioma que necesites, next-intl sirviendo el archivo correcto por ruta, y un único script de Node que puedes ejecutar cada vez que cambie tu contenido.

No necesitas registrarte en ninguna plataforma de traducción. No hay tarifas fijas por idioma. Una llamada API maneja todos tus idiomas objetivo a la vez.

Lo que necesitarás:

  • Un proyecto Next.js usando App Router (Next.js 14 o 15)
  • Node.js 18 o superior
  • Una cuenta gratuita de PolyLingo y clave API

Paso 1: Obtén tu clave API de PolyLingo (5 minutos)

Crea una cuenta gratuita en usepolylingo.com. El nivel gratuito incluye 100,000 tokens por mes, suficiente para traducir un archivo de locales de tamaño medio a más de 10 idiomas varias veces.

Una vez dentro, ve a API keys en el panel y crea una clave. Solo verás el valor completo una vez, así que cópialo inmediatamente.

Agrégalo a tu proyecto como una variable de entorno. Nunca lo subas al control de versiones ni lo expongas en código del cliente:

# .env.local
POLYLINGO_API_KEY="pl_tu_clave_aqui"

Verifica que la API sea accesible antes de continuar:

curl -sS "https://api.usepolylingo.com/v1/health"

Deberías recibir un pequeño payload JSON con "status": "ok".


Paso 2: Instala next-intl y configura el enrutamiento (10 minutos)

Instala la librería:

npm install next-intl

Crea un archivo i18n.ts en la raíz de tu proyecto. Esto indica a next-intl qué locales soportas y cómo cargar el archivo de mensajes correcto para cada solicitud:

// i18n.ts
import { getRequestConfig } from 'next-intl/server'

export const locales = ['en', 'de', 'fr'] as const
export type Locale = (typeof locales)[number]
export const defaultLocale: Locale = 'en'

export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale
  if (!locale || !locales.includes(locale as Locale)) {
    locale = defaultLocale
  }
  return {
    locale,
    messages: (await import(`./messages/${locale}.json`)).default,
  }
})

Agrega middleware para prefijar las rutas con el locale:

// middleware.ts
import createMiddleware from 'next-intl/middleware'
import { locales, defaultLocale } from './i18n'

export default createMiddleware({
  locales: [...locales],
  defaultLocale,
  localePrefix: 'as-needed',
})

export const config = {
  matcher: ['/((?!api|_next|.*\..*).*)'],
}

Mueve tus archivos de página bajo app/[locale]/. Actualiza tu layout raíz para recibir el parámetro locale y envolver los hijos con NextIntlClientProvider:

// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params
  const messages = await getMessages()
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  )
}

Paso 3: Extrae tus cadenas en un archivo JSON de mensajes (10 minutos)

Crea una carpeta messages/ en la raíz de tu proyecto. Añade un archivo en.json con tus cadenas fuente. next-intl usa una estructura de claves anidadas:

{
  "Home": {
    "title": "Bienvenido",
    "cta": "Comenzar"
  }
}

Actualiza tus páginas para usar useTranslations en lugar de cadenas codificadas:

// app/[locale]/page.tsx
import { useTranslations } from 'next-intl'

export default function HomePage() {
  const t = useTranslations('Home')
  return (
    <main>
      <h1>{t('title')}</h1>
      <button type="button">{t('cta')}</button>
    </main>
  )
}

Ahora escribe el script de traducción. Este lee messages/en.json, lo envía a la API de PolyLingo con format: "json", y escribe un archivo de salida por cada locale objetivo. La bandera format: "json" indica a la API que preserve la estructura de claves y traduzca solo los valores de cadena — claves anidadas, arrays y tipos no cadena permanecen intactos.

// scripts/translate-messages.mjs
// Ejecutar con: node scripts/translate-messages.mjs

import fs from 'node:fs'
import path from 'node:path'

const API_KEY = process.env.POLYLINGO_API_KEY
const API_URL = (process.env.POLYLINGO_API_URL || 'https://api.usepolylingo.com/v1').replace(/\/$/, '')
const TARGETS = ['de', 'fr'] // extiende este array para añadir más locales

const enPath = path.join('messages', 'en.json')
const en = JSON.parse(fs.readFileSync(enPath, 'utf8'))

const res = await fetch(`${API_URL}/translate`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    content: JSON.stringify(en),
    format: 'json',
    targets: TARGETS,
    model: 'standard',
  }),
})

if (!res.ok) {
  const err = await res.text()
  throw new Error(`PolyLingo ${res.status}: ${err}`)
}

const { translations } = await res.json()

for (const locale of TARGETS) {
  const out = path.join('messages', `${locale}.json`)
  fs.writeFileSync(out, JSON.stringify(JSON.parse(translations[locale]), null, 2) + '\n')
  console.log('Wrote', out)
}

Ejecuta el script:

node scripts/translate-messages.mjs

Deberías ver una salida como:

Wrote messages/de.json
Wrote messages/fr.json

Abre esos archivos y revísalos. Tus claves serán idénticas a en.json. Solo los valores de cadena habrán cambiado.


Paso 4: Prueba las rutas (5 minutos)

Inicia tu servidor de desarrollo:

npm run dev

Visita http://localhost:3000 y http://localhost:3000/de. El encabezado y el botón deberían mostrarse en inglés y alemán respectivamente. Añade más locales extendiendo el array TARGETS en el script y el array locales en i18n.ts, luego vuelve a ejecutar el script.

Revisa tu uso de tokens en el panel de PolyLingo bajo Usage. Para un archivo pequeño traducido a dos idiomas, habrás usado unos pocos cientos de tokens de tu cuota mensual.


Qué hacer a continuación

Añade más locales. El script envía una solicitud sin importar cuántas entradas haya en TARGETS. Añadir japonés, español y árabe cuesta una llamada API, no tres.

Integrarlo en CI. Añade POLYLINGO_API_KEY como secreto del repositorio en GitHub Actions y ejecuta el script como parte de tu pipeline de construcción. Tus archivos de locales se mantienen sincronizados automáticamente cada vez que cambia en.json.

Traduce otros formatos. El mismo patrón de script funciona para páginas de documentación Markdown (format: "markdown") y plantillas de email HTML (format: "html"). La API preserva la estructura en todos los casos.

Usa el endpoint batch para proyectos más grandes. Si tienes varios archivos JSON separados (uno por área funcional, por ejemplo), POST /translate/batch acepta hasta 100 ítems en una sola solicitud, cada uno con su propio id y format.


Pruébalo gratis

El nivel gratuito de PolyLingo incluye 100,000 tokens por mes. No se requiere tarjeta de crédito.

curl -sS -X POST "https://api.usepolylingo.com/v1/translate" \
  -H "Authorization: Bearer $POLYLINGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "{\"Home\":{\"title\":\"Bienvenido\",\"cta\":\"Comenzar\"}}",
    "format": "json",
    "targets": ["de", "fr", "es", "ja"]
  }'

Obtén tu clave API