Automating i18n Translations with Next.js and GitHub
Managing translations manually is one of the most common pain points in multilingual Next.js projects. Out-of-sync JSON files, forgotten keys, Pull Requests piling up for text strings — this time generates no value.
This guide shows how to set up a continuous localization pipeline: every change to your source file automatically triggers a translation PR, in all your languages, with no manual intervention.
Why Manual Translation Management Doesn't Scale
Q: What are the real problems with a manual i18n workflow?
Three problems accumulate systematically:
Slowness. Between adding a key in the code and having its translation available in production, several days can pass. On fast deployment cycles, this is a bottleneck.
The human bottleneck. One person centralizes exports, sends to translators, handles imports. This is fragile and doesn't scale beyond one or two languages.
Translation debt. Untranslated keys accumulate silently. In some languages, entire sections of the interface remain in the source language without anyone noticing.
Q: Can this workflow be fully automated?
Yes. The complete chain — detecting new keys, translating, creating the PR — can be automated. This is exactly what Jason does.
Which Library to Choose for Next.js?
Q: next-intl or i18next for a Next.js project in 2025?
For Next.js 13+ with App Router, next-intl is the reference choice. It's designed for SSR and Server Components: no client-side provider needed for server components, built-in locale routing, native TypeScript support.
For React apps outside Next.js (Vite, CRA) or Next.js Pages Router, i18next + react-i18next remains the most flexible and well-documented solution.
Setting Up with next-intl
Installation
pnpm add next-intl
Recommended Structure
messages/
fr.json ← source language (maintained manually)
en.json ← automatically generated by Jason
es.json ← automatically generated
de.json ← automatically generated
src/
i18n.ts
middleware.ts
Configuration src/i18n.ts
import { getRequestConfig } from 'next-intl/server'
import { cookies } from 'next/headers'
export default getRequestConfig(async () => {
const locale = cookies().get('locale')?.value ?? 'fr'
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
}
})
next.config.js
const createNextIntlPlugin = require('next-intl/plugin')
const withNextIntl = createNextIntlPlugin('./src/i18n.ts')
module.exports = withNextIntl({})
Usage in Components
// Server component
import { getTranslations } from 'next-intl/server'
export default async function PricingPage() {
const t = await getTranslations('pricing')
return <h1>{t('title')}</h1>
}
// Client component
'use client'
import { useTranslations } from 'next-intl'
export default function NavBar() {
const t = useTranslations('nav')
return <a href="/pricing">{t('pricing')}</a>
}
Connecting GitHub for Continuous Localization
Q: How does GitHub synchronization work with Jason?
In three steps:
- Connection: connect your GitHub repository in Jason and specify the path to your source file (e.g.
messages/fr.json) - Detection: on every push, Jason identifies new or modified keys
- Automatic PR: Jason generates translations and opens a Pull Request on your repo, ready to merge
Q: How do you configure the GitHub webhook?
In Jason: Settings → Integrations → Generate webhook token.
In GitHub: Settings → Webhooks → Add webhook
| Field | Value |
|-------|-------|
| Payload URL | URL provided by Jason |
| Content type | application/json |
| Secret | Jason token |
| Events | "Just the push event" |
Click Add webhook. The pipeline is active.
Q: Is it compatible with an existing CI/CD pipeline?
Yes. Jason provides a REST API and CLI to trigger translations from your deployment scripts, in addition to the GitHub webhook.
# CLI example
jason translate --project my-project --source messages/fr.json --langs en,es,de
Variables, Plurals and Edge Cases
Q: How does Jason handle variables in keys?
Jason automatically detects and preserves all common formats:
{ "welcome": "Welcome, {name}!" }
{ "cart": "{{count}} item(s) in cart" }
{ "keys": "{count, plural, =0 {No keys} =1 {1 key} other {# keys}}" }
The structure and variable identifiers are reproduced identically in all target languages, without modification.
Q: Does Jason preserve nested JSON structure?
Yes. The hierarchy of your source file is preserved. If your fr.json has a namespace structure (auth.login.submit), the generated files have exactly the same structure.
Result: Continuous Localization Without Friction
With this pipeline in place, translation debt becomes structurally impossible. Every code PR that modifies the source file triggers a translation PR. Your languages are always up to date.
This model — continuous localization integrated into the development workflow — is what differentiates teams that manage their translations well from those that accumulate missing keys sprint after sprint.