npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

next-intl-auto-translate

v0.1.0

Published

Next.js plugin that auto-translates next-intl message files into multiple locales at dev/build time via the OpenAI chat API.

Readme

next-intl-auto-translate

CI npm version npm downloads license

A Next.js plugin that auto-translates your next-intl messages into every configured locale on every dev rebuild and production build. Edit en.json, save, and ru.json / fr.json / … appear next to it — translated through the OpenAI chat API and cached in a vocabulary.json so the same string is never re-translated twice.

Why?

Maintaining next-intl message files by hand is the slow part of i18n. Most tools either ship a heavyweight runtime, gate behind a vendor SDK, or break the build when the model misbehaves. next-intl-auto-translate keeps it minimal:

  • One main locale file (en.json) is the source of truth. You only edit that.
  • The plugin runs at dev/build time, batches every new string into one OpenAI call, and writes the translated <locale>.json files atomically next to the source.
  • A local vocabulary.json caches every text → { locale → translation } pair, so subsequent runs only translate new strings.
  • Works with both Webpack and Turbopack (file watcher fallback).
  • Never breaks the build: when the API fails or returns garbage, missing translations fall back to the original English text.

Translation itself is delegated to json-i18n-auto-translate — this package is the Next.js integration around it.

Installation

npm install -D next-intl-auto-translate

Requires Node.js >=18 and Next.js >=13. Set your API key:

export OPENAI_API_KEY=sk-...

Quick start

As a Next.js plugin

next.config.ts:

import type { NextConfig } from "next"
import { createNextIntlAutoTranslatePlugin } from "next-intl-auto-translate"

const withAutoTranslate = createNextIntlAutoTranslatePlugin({
  inputFile: "./messages/en.json",
  locales: ["en", "ru", "fr", "de"],
})

const nextConfig: NextConfig = {
  // ...your config
}

export default withAutoTranslate(nextConfig)

On next dev and next build, the plugin reads messages/en.json, batch-translates anything new, and writes messages/ru.json, messages/fr.json, messages/de.json plus a sibling messages/vocabulary.json.

From a config file

Create next-intl-auto-translate.config.json next to your messages:

{
  "inputFile": "./messages/en.json",
  "locales": ["en", "ru", "fr", "de"],
  "notes": "Friendly, informal tone. Keep product names like 'Acme' untranslated."
}

Then either pass configPath:

const withAutoTranslate = createNextIntlAutoTranslatePlugin({
  configPath: "./next-intl-auto-translate.config.json",
})

…or run the CLI as a one-shot:

npx next-intl-auto-translate --config ./next-intl-auto-translate.config.json

API

createNextIntlAutoTranslatePlugin(options)

Returns a (nextConfig) => nextConfig wrapper. Accepts either inline options or a configPath (mutually exclusive).

| Option | Type | Required | Default | Description | | ------------ | ------------------- | -------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------- | | inputFile | string | yes | — | Path to the source-of-truth messages file. The basename (without extension) is the main locale. | | locales | readonly string[] | yes | — | All locales, including the main locale. Files are written for every locale except the main one. | | notes | string | no | — | Translator notes (tone, glossary, audience) appended to the OpenAI system prompt. | | vocabFile | string | no | <inputFile-dir>/vocabulary.json | Where to cache text → { locale → translation } pairs. | | debug | boolean | no | false | Verbose logs. | | configPath | string | no | — | Use a JSON config file instead of inline options. Cannot be combined with the other fields. |

createWebpackPlugin(options)

If you wire Webpack manually (without the Next.js wrapper), this returns a plain WebpackPluginInstance:

import { createWebpackPlugin } from "next-intl-auto-translate"

const plugin = createWebpackPlugin({
  inputFile: "./messages/en.json",
  locales: ["en", "ru"],
})

loadMessages(options)

Programmatic single-run entry point used by the CLI. Reads inputFile, translates anything missing, and writes target-locale files + the vocabulary.

import { loadMessages } from "next-intl-auto-translate"

await loadMessages({
  inputFile: "/abs/path/messages/en.json",
  locales: ["en", "ru", "fr"],
  mainLocale: "en",
  pathToVocabFile: "/abs/path/messages/vocabulary.json",
  notes: "informal tone",
})

| Option | Type | Required | Default | Description | | ----------------------- | ------------------- | -------- | ------- | --------------------------------------------------------------------------------- | | inputFile | string | yes | — | Absolute path to the main-locale JSON file. | | locales | readonly string[] | yes | — | All locales (target locales = locales minus mainLocale). | | mainLocale | string | yes | — | The source-of-truth locale; its file is never overwritten. | | pathToVocabFile | string | yes | — | Absolute path to the vocabulary cache. | | notes | string | no | — | Translator notes. | | pruneStaleVocabulary | boolean | no | true | Remove vocabulary entries whose source strings no longer exist in the main file. |

CLI

npx next-intl-auto-translate --config ./next-intl-auto-translate.config.json

| Flag | Required | Default | Description | | ---------------- | -------- | ---------------------------------------- | -------------------------------------- | | -c, --config | no | ./next-intl-auto-translate.config.json | Path to the JSON config. | | -h, --help | no | — | Show usage. |

OPENAI_API_KEY is read from the environment.

How it works

  1. The plugin reads inputFile and extracts every string value (recursively, across nested objects and arrays).
  2. Strings not present in vocabulary.json are batched into a single call to json-i18n-auto-translate, which preserves {placeholders} and returns a { text → { locale → translation } } map.
  3. The new translations are merged into vocabulary.json, then every target locale file is rebuilt from the main content + the vocabulary.
  4. Locale files are only rewritten when their content actually changes — no unnecessary file system churn for hot reloads.
  5. Stale vocabulary entries (whose source strings no longer exist in inputFile) are pruned by default.

Dev mode (Webpack + Turbopack)

  • Webpack: the plugin hooks watchRun and re-translates only when inputFile or vocabFile change. Runs are debounced and de-duplicated across client/server compilations.
  • Turbopack: Webpack hooks don't fire, so the plugin sets up a node:fs.watch on the same files (debounced by 1s) to get the same behavior.

In both cases, an initial translation is kicked off via setImmediate when the plugin is constructed, so the first next dev boot is already up to date.

Interop with next-intl-merge

If you also use next-intl-merge to split your messages into per-feature files, its locales array must contain only the main locale. If both plugins write ru.json to the same directory, they race during dev shutdown and the merge plugin will clobber the translated output silently.

next-intl-auto-translate checks for this at config load and throws a clear error if a sibling next-intl-merge.config.json lists any target locale.

Fallback behavior

json-i18n-auto-translate is designed to never throw — on network/API/JSON failures, every requested (text, locale) cell falls back to the original text. That means your build never breaks because of a flaky API:

  • Missing translations land in vocabulary.json as { "<text>": { ru: "<text>" } }.
  • On the next successful run, those entries are retried (they're still considered untranslated only if absent; if you want to force a retry, delete the row in vocabulary.json).

See also

License

MIT