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

@translation-cms/sync

v1.2.41

Published

Scan translation keys in your codebase and sync them to the Translations CMS

Readme

@translation-cms/sync

Automatically scan translation keys from your codebase, sync with the Translations CMS, and fetch translations as local JSON files. Built for Next.js + i18next.

Integration Methods

Choose the approach that fits your workflow:

  • Next.js Plugin (Recommended) — Automatic sync on next dev and next build
  • CLI (Manual) — pnpm sync-translations for on-demand control
  • Programmatic API — Call sync/pull directly in your code or scripts

Method 1: Next.js Plugin (Automatic)

Edit next.config.ts:

import withTranslationsCMS from '@translation-cms/sync/next';

export default withTranslationsCMS(
    {
        // your Next.js config
    },
    {
        pullOnBuild: true, // auto pull on `next build`
        pullOnDev: true, // auto pull on `next dev` startup
        watchInterval: 10000, // poll CMS for updates every 10s in dev (0 to disable)
    }
);

Your translations sync automatically — and in dev mode, the JSON files are re-pulled automatically whenever you publish new translations in the CMS. No extra commands needed.

Method 2: CLI (Manual)

# Scan code + upload to CMS
pnpm sync-translations sync

# Download translations
pnpm sync-translations pull

# Watch mode — auto-sync on file changes
pnpm sync-translations watch

# Interactive setup
pnpm sync-translations init

Method 3: Programmatic API

Use in scripts, build tools, or a custom next.config.ts:

import type { NextConfig } from 'next';
import {
    PHASE_DEVELOPMENT_SERVER,
    PHASE_PRODUCTION_BUILD,
} from 'next/constants';
import {
    syncTranslations,
    pullTranslations,
    watchTranslations,
} from '@translation-cms/sync/api';

const pathMappings = { '@/*': ['src/*'] }; // mirror your tsconfig paths

export default async function config(phase: string): Promise<NextConfig> {
    if (phase === PHASE_DEVELOPMENT_SERVER) {
        // Scan keys and upload, then pull translations
        await syncTranslations({
            projectRoot: '.',
            pathMappings,
            verbose: true,
        });
        await pullTranslations({ projectRoot: '.', verbose: true });
        // Poll the CMS in the background — re-pulls whenever you publish
        watchTranslations({ projectRoot: '.' });
    }

    if (phase === PHASE_PRODUCTION_BUILD) {
        await syncTranslations({ projectRoot: '.', pathMappings });
        await pullTranslations({ projectRoot: '.' });
    }

    return {
        // ... your Next.js config
    };
}

Or use syncAndPull in scripts / CI:

import { syncAndPull } from '@translation-cms/sync/api';
await syncAndPull({ projectRoot: './my-app' });

Choose Your Method

| Method | When to Use | Setup Time | | -------------------- | --------------------------------- | ---------- | | Next.js Plugin | Team projects, automatic workflow | 5 min | | CLI | Manual control, debugging, CI/CD | 10 min | | Programmatic API | Custom workflows, scripts | 5 min |

Recommended: Start with the Next.js Plugin for automatic updates during development, then add CLI or API commands as needed.

What is this?

Hybrid translation management system with three integration methods:

  • Scans your code for translation keys (React-i18next pattern)
  • Syncs new keys to the Translations CMS
  • Fetches translations as JSON files
  • Automatic or manual — choose what works for your team
  • Programmatic API — bring your own CI/CD or tooling
Your Code → scan → CMS → pull → JSON files → i18next → Your App

Programmatic API Reference

Use these functions when you need fine-grained control or integration with custom workflows. Perfect for CI/CD pipelines, build tools, or custom scripts.

syncTranslations(options?)

Scans your codebase for translation keys and uploads them to the CMS.

import { syncTranslations } from '@translation-cms/sync/api';

const result = await syncTranslations({
    projectRoot: './apps/web', // auto-detect if omitted
    dryRun: false, // preview without uploading
    force: false, // ignore cache
    reportPath: './sync-report.json', // optional report output
    verbose: true, // detailed logging
});

console.log(`Added ${result.keysAdded} keys`);

pullTranslations(options?)

Fetches translations from the CMS and writes them as JSON files.

import { pullTranslations } from '@translation-cms/sync/api';

const result = await pullTranslations({
    projectRoot: './apps/web', // auto-detect if omitted
    outputDir: './src/i18n/locales', // custom output location
    force: false, // ignore cache
    ttl: 300000, // cache duration in ms
    environment: 'production', // pull from specific env
    verbose: true, // detailed logging
});

watchTranslations(options?)

Starts a polling loop that detects CMS publishes and re-pulls automatically. Non-blocking — call it after pullTranslations and it runs in the background.

import { watchTranslations } from '@translation-cms/sync/api';

watchTranslations({
    projectRoot: './apps/web', // auto-detect if omitted
    outputDir: './src/i18n/locales', // must match pull outputDir
    interval: 10000, // poll every 10s (default)
});

When you publish translations in the CMS, the JSON files are updated within interval milliseconds — no manual pull needed.

syncAndPull(options?)

Convenience function: sync keys then pull translations in one call.

import { syncAndPull } from '@translation-cms/sync/api';

const { synced, pulled } = await syncAndPull({
    projectRoot: './apps/web',
    verbose: true,
});

Setup Guide — From Zero to Working

Step 1: Prerequisites

Check what you need:

  • Next.js project (v14+)
  • pnpm (recommended) or npm
  • Translations CMS account + project created
  • CMS project credentials (URL, project ID, API key)

Step 2: Installation

pnpm add @translation-cms/sync

Plus the peer dependencies:

pnpm add i18next react-i18next i18next-resources-to-backend

Step 3: Set Up CMS Credentials

Create .env.local in your project root:

NEXT_PUBLIC_CMS_URL=https://cms.example.com
NEXT_PUBLIC_CMS_PROJECT_ID=your-project-id
CMS_SYNC_API_KEY=your-jwt-api-key

The CMS_SYNC_API_KEY is a JWT token. You can find it in the CMS under Project Settings → Environments → your environment → API Key. If the key there still looks like a UUID, use "Regenerate" to generate a JWT.

Note: use CMS_SYNC_API_KEY (without NEXT_PUBLIC_). This prevents the JWT from accidentally ending up in the browser. The old name NEXT_PUBLIC_CMS_ANON_KEY still works as a fallback, but is discouraged.

Step 4: Automatic Setup (Recommended)

Run the interactive setup wizard:

pnpm sync-translations init

This automatically creates:

src/lib/i18n/
  ├── settings.ts           # i18next configuration
  ├── types.ts              # TypeScript types for translations
  ├── client.ts             # 'use client' hook + static imports
  ├── server.ts             # Server component support
  ├── provider.tsx          # Provider wrapper
  └── dictionaries/
      ├── en.json           # English (empty, filled by pull)
      └── nl.json           # Dutch (empty, filled by pull)

Also .translationsrc.json is generated:

{
    "outputDir": "./src/lib/i18n/dictionaries",
    "pullTtlMs": 300000,
    "excludedDirs": ["e2e", "node_modules", "fixtures"],
    "routeParams": {}
}

Note: routeParams can stay empty! The scanner automatically generates sensible defaults for all dynamic routes.

Step 5: First Sync — Upload Keys

Scan your code and upload detected keys to the CMS:

pnpm sync-translations sync

This will:

  1. Scan your code for translation keys (t('namespace:key') pattern)
  2. Detect routes where each key is used
  3. Upload new keys to CMS
  4. Show report (which keys added/changed)

Check in the CMS if your keys appeared.

On first sync: you might get an error that no translations can be downloaded — this is normal! The keys were just uploaded but don't have translations yet. Go to your CMS, fill in translations, then run pull again.

Step 5b: Add Translations in CMS

  1. Open your Translations CMS project
  2. Your keys are now listed (without translations)
  3. Fill in translations per locale (English, Dutch, etc.)
  4. Click Save

Step 6: Fetch Translations

If you've done the first sync AND filled in translations in the CMS:

pnpm sync-translations pull

This fetches translations from the CMS and writes them to local JSON:

  • dictionaries/en.json → English translations
  • dictionaries/nl.json → Dutch translations
  • etc. (one per locale)

Step 7: Integration in Your Project

A. Root Layout Setup

Add the provider to your src/app/layout.tsx:

import { TranslationProvider } from '@/lib/i18n/provider';

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html>
            <body>
                <TranslationProvider>{children}</TranslationProvider>
            </body>
        </html>
    );
}

B. Server Components (Recommended)

import { getTranslation } from '@/lib/i18n/server';

export default async function Page() {
    const { t } = await getTranslation('common');

    return (
        <div>
            <h1>{t('common:app.title')}</h1>
            <p>{t('common:app.description')}</p>
        </div>
    );
}

C. Client Components

'use client';
import { useTranslation } from '@/lib/i18n/client';

export function MyButton() {
    const { t } = useTranslation('common');

    return <button>{t('common:button.click')}</button>;
}

D. Multiple Namespaces (With Type-Checking)

const { t } = useTranslation(['common', 'auth']);

// This works
t('common:nav.home');
t('auth:login.email');

// This gives a type error
t('payments:amount'); // namespace not added

Step 8: Automatic Sync in Your Workflow

Add this to package.json:

{
    "scripts": {
        "dev": "sync-translations pull && next dev",
        "build": "sync-translations pull && next build"
    }
}

Now you automatically pull the latest translations on startup.

Step 9 (Optional): Set Up Preview Mode

Enable live in-context preview — editors can live see how their text looks in your app.

Add this to your root layout:

'use client';
import { useEffect } from 'react';
import { initPreviewListener } from '@translation-cms/sync';

export function CMSPreview() {
    useEffect(() => {
        if (process.env.NODE_ENV === 'development') {
            initPreviewListener({
                onLocaleSwitch: locale => {
                    // change language if CMS selects a different locale
                    window.location.href = `/${locale}`;
                },
            });
        }
    }, []);
    return null;
}

And add to layout:

<TranslationProvider>
    <CMSPreview />
    {children}
</TranslationProvider>

Add data-cms-key to elements for precise highlighting:

<h1 data-cms-key="common:page.title">{t('common:page.title')}</h1>

CLI Commands Reference

Basic Commands

# Scan code + upload to CMS
pnpm sync-translations sync

# Download translations to local JSON
pnpm sync-translations pull

# See what would change (without upload)
pnpm sync-translations sync --dry-run

# See difference from previous sync
pnpm sync-translations status

# Watch mode — auto-sync on file changes
pnpm sync-translations watch

# Interactive setup
pnpm sync-translations init

Advanced Flags

| Flag | Description | | ----------------- | ---------------------------------------- | | --dry-run | Show changes, don't execute | | --force | Ignore cache, force refresh | | --output <dir> | Custom output directory for JSON | | --env <name> | Pull from staging/production environment | | --ttl <ms> | Override cache TTL (ms) | | --project-id | Override project ID | | --api-key | Override API key | | --cms-url | Override CMS URL | | --report <file> | Write sync report to JSON file |

Examples

# Force refresh, ignore cache
pnpm sync-translations pull --force

# Custom output directory
pnpm sync-translations pull --output ./locales

# Pull from staging environment
pnpm sync-translations pull --env staging

# Write report of changes
pnpm sync-translations sync --report ./sync-report.json

Key Scanner — How It Works

The tool recognizes these patterns in your code:

// React-i18next hook
const { t } = useTranslation('blog');
t('blog:post.title');

// Server-side helper
const { t } = await getTranslation('blog');
t('blog:post.title');

// CMS client helper
const t = await client.getTranslations(locale, 'blog');
t('blog:post.title');

// Trans component
<Trans i18nKey="blog:post.title" />

// I18nKey-typed config (only if the file imports `I18nKey`)
import type { I18nKey } from '@/lib/i18n/types';
const label: I18nKey = 'sidebar:settings';

// Object properties ending in "Key"
const config = { titleKey: 'blog:post.title' };

Format: namespace:key

All keys must follow this format. Otherwise you'll get a warning:

// Good
t('common:button.save');

// Wrong — namespace missing
t('save');

Dynamic Routes — Auto-Generated routeParams

How it works:

At each pnpm sync-translations sync:

  1. Scanner automatically detects all routes with dynamic parameters (e.g. /[locale]/blog/[slug])
  2. For each [param] a sensible default is automatically generated:
    • locale / lang"en"
    • id, postId, productId"123"
    • slug"demo"
    • username"demo-user"
    • email"[email protected]"
  3. This is saved in .cms-sync-cache-meta.json (you can ignore it)
  4. The CMS uses this to generate working preview URLs

You don't have to do anything! — everything works out-of-the-box.

Manual override (optional):

If you want to use different test values, fill in .translationsrc.json:

{
    "routeParams": {
        "/[locale]/products/[id]": { "id": "prod-999" },
        "/[locale]/blog/[slug]": { "slug": "my-custom-post" }
    }
}

This overrides the auto-generated values. Everything you add here takes priority.

Cache Files

At each sync two cache files are created:

  • .cms-sync-cache.json — Uploaded keys and their routes (for diff on next sync)
  • .cms-sync-cache-meta.json — Auto-generated route params (you can add to .gitignore)

Scanner Options

Customize scan behavior in .translationsrc.json:

{
    "excludedDirs": ["e2e", "node_modules", "fixtures"],
    "sourceExtensions": [".ts", ".tsx", ".js", ".jsx"],
    "reservedCssNamespaces": ["after", "before"]
}

Advanced Features

Preview Mode — Live Highlighting

Editors can click "Show in app" in the CMS and your app opens in an iframe with live highlighting of the element.

Setup

'use client';
import { initPreviewListener } from '@translation-cms/sync';

useEffect(() => {
    initPreviewListener({
        highlightStyles: {
            outline: '3px solid #3b82f6',
            outlineOffset: '2px',
            backgroundColor: 'rgba(59, 130, 246, 0.1)',
        },
    });
}, []);

Element Targeting

Use data-cms-key for precise targeting:

<h1 data-cms-key="blog:title">{t('blog:title')}</h1>
<p data-cms-key="blog:excerpt">{t('blog:excerpt')}</p>

JSON Output Format

After sync-translations pull these kinds of files are generated:

{
    "common": {
        "app.title": "My App",
        "button.save": "Save",
        "nav.home": "Home"
    },
    "auth": {
        "login.email": "Email",
        "login.password": "Password"
    }
}

Structure:

  • Top-level keys = namespaces
  • Nested keys = translation strings
  • 1 file per language (en.json, nl.json, etc.)

Troubleshooting

No keys found

# Check scanner configuration
pnpm sync-translations sync --dry-run

Make sure your keys use namespace:key format.

Environment variables not found

Check .env.local:

grep -E 'CMS_URL|CMS_PROJECT_ID|CMS_SYNC_API_KEY' .env.local

Must contain NEXT_PUBLIC_CMS_URL, NEXT_PUBLIC_CMS_PROJECT_ID and CMS_SYNC_API_KEY. The value of CMS_SYNC_API_KEY is a JWT token — get it from the CMS Project Settings.

Pull doesn't work

# Force refresh, ignore cache
pnpm sync-translations pull --force

# Check configuration
cat .translationsrc.json

Type errors in TypeScript

Make sure src/lib/i18n/types.ts is generated correctly and that your namespaces are in .translationsrc.json.


Best Practices

  1. Use server components where possible — better for performance
  2. Add data-cms-key on visually important elements
  3. Commit .translationsrc.json to git — not .env.local
  4. Ignore cache files: add to .gitignore:
    .cms-sync-cache.json
    .cms-sync-cache-meta.json
    .last-pulled
  5. Run sync in CI/CD on main branch — catch missing translations
  6. Add pull to build script — always fresh translations

License

MIT