Powrót do bloga
Terminal output showing a translation script writing de.json and fr.json, alongside a folder tree and a browser rendering the German locale route.

Jak przetłumaczyć aplikację Next.js za pomocą PolyLingo w mniej niż 30 minut

By Robert

Jak przetłumaczyć aplikację Next.js za pomocą PolyLingo w mniej niż 30 minut

Pod koniec tego samouczka będziesz mieć działający wielojęzyczny projekt Next.js App Router: ciągi znaków wyodrębnione do messages/en.json, przetłumaczone pliki lokalizacji dla każdego potrzebnego języka, next-intl serwujący odpowiedni plik dla każdej trasy oraz pojedynczy skrypt Node, który możesz uruchomić ponownie za każdym razem, gdy zmieni się Twoja zawartość.

Nie musisz rejestrować się na żadnej platformie tłumaczeniowej. Brak stałych opłat za język. Jedno wywołanie API obsługuje wszystkie docelowe języki jednocześnie.

Co będzie Ci potrzebne:

  • Projekt Next.js korzystający z App Router (Next.js 14 lub 15)
  • Node.js 18 lub nowszy
  • Darmowe konto PolyLingo i klucz API

Krok 1: Pobierz swój klucz API PolyLingo (5 minut)

Utwórz darmowe konto na usepolylingo.com. Darmowy plan obejmuje 100 000 tokenów miesięcznie, co wystarczy, aby przetłumaczyć średniej wielkości plik lokalizacji na ponad 10 języków wielokrotnie.

Po zalogowaniu przejdź do API keys w panelu i utwórz klucz. Pełną wartość zobaczysz tylko raz, więc skopiuj ją od razu.

Dodaj go do swojego projektu jako zmienną środowiskową. Nigdy nie commituj go do kontroli wersji i nigdy nie ujawniaj w kodzie po stronie klienta:

# .env.local
POLYLINGO_API_KEY="pl_your_key_here"

Sprawdź, czy API jest dostępne, zanim przejdziesz dalej:

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

Powinieneś otrzymać mały ładunek JSON z "status": "ok".


Krok 2: Zainstaluj next-intl i skonfiguruj routing (10 minut)

Zainstaluj bibliotekę:

npm install next-intl

Utwórz plik i18n.ts w katalogu głównym projektu. Informuje on next-intl, które lokalizacje obsługujesz i jak załadować odpowiedni plik z wiadomościami dla każdego żądania:

// 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,
  }
})

Dodaj middleware, aby poprzedzać trasy prefiksem lokalizacji:

// 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|.*\..*).*)'],
}

Przenieś pliki stron do app/[locale]/. Zaktualizuj swój główny layout, aby odbierał parametr locale i opakowywał dzieci w 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>
  )
}

Krok 3: Wyodrębnij swoje ciągi do pliku wiadomości JSON (10 minut)

Utwórz folder messages/ w katalogu głównym projektu. Dodaj plik en.json z ciągami źródłowymi. next-intl używa zagnieżdżonej struktury kluczy:

{
  "Home": {
    "title": "Welcome",
    "cta": "Get started"
  }
}

Zaktualizuj swoje strony, aby używały useTranslations zamiast twardo zakodowanych ciągów:

// 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>
  )
}

Teraz napisz skrypt tłumaczący. Odczytuje on messages/en.json, wysyła go do API PolyLingo z format: "json" i zapisuje jeden plik wyjściowy na każdą docelową lokalizację. Flaga format: "json" mówi API, aby zachowało strukturę kluczy i tłumaczyło tylko wartości tekstowe — zagnieżdżone klucze, tablice i typy inne niż tekstowe pozostają niezmienione.

// scripts/translate-messages.mjs
// Run with: 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'] // extend this array to add more 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)
}

Uruchom go:

node scripts/translate-messages.mjs

Powinieneś zobaczyć wyjście takie jak:

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

Otwórz te pliki i sprawdź je. Twoje klucze będą identyczne jak w en.json. Zmienią się tylko wartości tekstowe.


Krok 4: Testowanie tras (5 minut)

Uruchom serwer deweloperski:

npm run dev

Odwiedź http://localhost:3000 i http://localhost:3000/de. Nagłówek i przycisk powinny być wyświetlane odpowiednio po angielsku i niemiecku. Dodaj więcej lokalizacji, rozszerzając tablicę TARGETS w skrypcie oraz tablicę locales w i18n.ts, a następnie uruchom skrypt ponownie.

Sprawdź zużycie tokenów w panelu PolyLingo w sekcji Usage. Dla małego pliku lokalizacji przetłumaczonego na dwa języki zużyjesz kilkaset tokenów z miesięcznego limitu.


Co dalej

Dodaj więcej lokalizacji. Skrypt wysyła jedno żądanie niezależnie od liczby wpisów w TARGETS. Dodanie japońskiego, hiszpańskiego i arabskiego kosztuje jedno wywołanie API, a nie trzy.

Zintegruj to z CI. Dodaj POLYLINGO_API_KEY jako sekret repozytorium w GitHub Actions i uruchamiaj skrypt jako część procesu budowania. Twoje pliki lokalizacji będą automatycznie synchronizowane za każdym razem, gdy zmieni się en.json.

Tłumacz inne formaty. Ten sam wzorzec skryptu działa dla stron dokumentacji Markdown (format: "markdown") oraz szablonów e-mail HTML (format: "html"). API zachowuje strukturę we wszystkich przypadkach.

Użyj punktu końcowego batch dla większych projektów. Jeśli masz wiele oddzielnych plików JSON (np. po jednym na obszar funkcji), POST /translate/batch akceptuje do 100 elementów w jednym żądaniu, każdy z własnym id i format.


Wypróbuj za darmo

Darmowy plan PolyLingo obejmuje 100 000 tokenów miesięcznie. Nie jest wymagana karta kredytowa.

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\":\"Welcome\",\"cta\":\"Get started\"}}",
    "format": "json",
    "targets": ["de", "fr", "es", "ja"]
  }'

Pobierz swój klucz API