DEV Community

Cover image for npx i18next-cli instrument
Adriano Raiano
Adriano Raiano

Posted on • Originally published at locize.com

npx i18next-cli instrument

Internationalizing a React application used to mean a tedious weekend of hunting down every hardcoded string, manually wrapping each one in a t() call, injecting useTranslation() hooks into every component, and praying you didn't miss a corner case. The reality is that most apps ship without i18n simply because the upfront cost feels too high.

The new instrument command in i18next-cli changes that equation entirely. In this post, we'll take a React + Vite + TypeScript project — Taskly — from zero i18n to a cloud-synced, multi-language application in just a few steps.


The Starting Point: A Standard React App

Taskly is a typical modern project: Vite, React, and TypeScript. It has no i18n dependencies and no translation files—just English strings scattered throughout the JSX:

// DashboardPage.tsx (before)
<h1>Here's your overview</h1>
<p>You have {activeCount} tasks left to complete.</p>
Enter fullscreen mode Exit fullscreen mode

This is the "Before" state. Let's start the transformation.


Step 1 — Instrument: Let the CLI Do the Heavy Lifting

Instead of manual search-and-replace, we run:

npx i18next-cli instrument
Enter fullscreen mode Exit fullscreen mode

If no i18next.config.ts exists, the CLI opens an interactive setup wizard first:

Welcome to the i18next-cli setup wizard!
✔ What kind of configuration file do you want? TypeScript (i18next.config.ts)
✔ What locales does your project support? en,de,fr,it,es,ja
✔ What is the glob pattern for your source files? src/**/*.{js,jsx,ts,tsx}
✔ What is the path for your output resource files? public/locales/{{language}}/{{namespace}}.json
✔ Configuration file created at: i18next.config.ts
Enter fullscreen mode Exit fullscreen mode

Then the instrumentation begins. The tool performs a deep static analysis of your codebase and:

  • Wraps hardcoded user-facing strings in t() calls, generating a key from the content
  • Injects useTranslation() hooks into React components that need them
  • Adds import { useTranslation } from 'react-i18next' where missing
  • Falls back to import i18next from 'i18next' with i18next.t() in non-component files
  • Generates a ready-to-use src/i18n.ts initialization file
  • Automatically injects import './i18n' into your entry file (src/main.tsx)
  • Detects language-switcher patterns and injects i18n.changeLanguage() calls

After the run you'll see a summary like:

✔ Scanned complete: 101 candidates, 101 approved, 1 language-change site(s)

Instrumentation Summary:
  Total candidates:     101
  Approved:            101
  Skipped:               0
  Language-change sites: 1

✔ 5 file(s) ready for instrumentation

▶  Next step: run 'i18next-cli extract' to extract the translation keys into your locale files.
Enter fullscreen mode Exit fullscreen mode

The generated src/i18n.ts is already wired up to load your translation files using lazy dynamic imports, and i.e. src/main.tsx now imports it automatically:

// src/main.tsx — modified by the instrument command
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App'
import './i18n'          // ← injected automatically

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
Enter fullscreen mode Exit fullscreen mode
// src/i18n.ts — generated by the instrument command
import i18next from 'i18next'
import { initReactI18next } from 'react-i18next'
import resourcesToBackend from 'i18next-resources-to-backend'

i18next
  .use(initReactI18next)
  .use(resourcesToBackend((language: string, namespace: string) =>
    import(`../public/locales/${language}/${namespace}.json`)))
  .init({
    returnEmptyString: false,
    fallbackLng: 'en',
    defaultNS: 'translation'
  })

export default i18next
Enter fullscreen mode Exit fullscreen mode

Step 2 — The Human Touch: Manual Adjustments

⚠️ First-Step Tool: The instrument command uses heuristic-based detection and is designed as a first pass to identify and suggest transformation candidates. It will not catch 100% of cases, and you should expect both false positives and false negatives. Always review the suggested transformations carefully before committing them to your codebase. Think of it as an intelligent code assistant, not an automated compiler.

Static analysis is powerful, but some strings need a guiding hand. The CLI provides the // i18next-instrument-ignore comment to opt individual strings out of instrumentation.

Brand names and UI chrome

In Sidebar.tsx for example, "Taskly" is a brand name — it must never be translated:

{/* i18next-instrument-ignore */}
<span className={styles.brandName}>Taskly</span>
Enter fullscreen mode Exit fullscreen mode

Dynamic labels mapped from data

The navigation labels are stored in a NAV_ITEMS array. The CLI instruments each label literal independently, but the right approach is a single t() call keyed by item.page. You can place comment-hints to guide the CLI:

const NAV_ITEMS = [
  {
    // t('navLabel.dashboard', 'Dashboard')
    page: 'dashboard',
    // i18next-instrument-ignore
    label: 'Dashboard',
    // ...
  },
  // ...
]

// Then in JSX, adjust to use the data-driven key:
<span className={styles.navLabel}>
  {t(`navLabel.${item.page}`, item.label)}
</span>
Enter fullscreen mode Exit fullscreen mode

The ignore comment keeps the literal in the array (needed at runtime as the fallback), while the comment-hint registers the key for extraction. A quick manual edit connects the two.

Language switcher — detected automatically

The CLI detects patterns that change the application language and injects i18n.changeLanguage() alongside them. In SettingsPage.tsx, the original onClick only called updateSettings:

// Before
onClick={() => updateSettings({ language: lang.code })}

// After — instrument detects the language-change pattern and injects:
onClick={() => { i18n.changeLanguage(lang.code); updateSettings({ language: lang.code }); }}
Enter fullscreen mode Exit fullscreen mode

No manual change needed here — the CLI handled it.


Step 3 — Extract: Generate Your Translation Files

With the code instrumented, now run:

npx i18next-cli extract
Enter fullscreen mode Exit fullscreen mode

This parses every source file, collects all t() calls or Trans component keys and their default values, and writes them into your locale files:

Updated: public/locales/en/translation.json
Updated: public/locales/de/translation.json
Updated: public/locales/fr/translation.json
Updated: public/locales/it/translation.json
Updated: public/locales/es/translation.json
Updated: public/locales/ja/translation.json
✔ Extraction complete!
Enter fullscreen mode Exit fullscreen mode

Your public/locales/en/translation.json now contains every key with its English default value:

{
  "heresYourOverview": "Here's your overview",
  "highPriority": "High priority",
  "howAreYou": "How are you today?",
  "navLabel": {
    "dashboard": "Dashboard",
    "tasks": "My Tasks",
    "settings": "Settings",
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

You now have a complete English baseline ready for the world.

The other locale files have the same keys but empty values — ready to be translated.

Bonus: TypeScript Definitions

If you're using TypeScript, one extra command gives you fully typed translation keys — so typos in t() calls and Trans components become compile errors:

npx i18next-cli types
Enter fullscreen mode Exit fullscreen mode
✔ TypeScript definitions generated successfully.
  ✓ Resources interface written to src/@types/resources.d.ts
  ✓ TypeScript definitions written to src/@types/i18next.d.ts
Enter fullscreen mode Exit fullscreen mode

Run this after every extract and your editor will autocomplete translation keys and flag any that don't exist.


Step 4 — Migrate: Push to Locize

At this point, you have a fully extracted English baseline.

You could manually share these JSON files with translators, wait for them to finish, and try to merge their changes back into your codebase. But dealing with new keys, deleted keys, and merge conflicts quickly becomes a maintainability nightmare. Instead, it's time to streamline the process and move your localization workflow to the cloud.

Add your Locize credentials to i18next.config.ts (you can also use environment variables or just follow the interactive prompt):

// i18next.config.ts
export default {
  locales: ['en', 'de', 'fr', 'it', 'es', 'ja'],
  extract: {
    input: 'src/**/*.{js,jsx,ts,tsx}',
    output: 'public/locales/{{language}}/{{namespace}}.json'
  },
  locize: {
    projectId: 'your-project-id',
    apiKey: process.env.LOCIZE_API_KEY,
    version: 'latest'
  }
}
Enter fullscreen mode Exit fullscreen mode

Then push everything to Locize in one command:

npx i18next-cli locize-migrate
Enter fullscreen mode Exit fullscreen mode

If necessary, the CLI prompts for your credentials on the first run, offers to save them securely, then migrates all locale files:

✔ Retry successful!
added language de...
added language es...
added language fr...
added language it...
added language ja...
transfering latest/en/translation...
transfered 94 keys latest/en/translation...
...
downloading translations after migration...
✔ 'locize migrate' completed successfully.
Enter fullscreen mode Exit fullscreen mode

💡 Automatic Translation: If you have Locize's Machine / AI Translation feature enabled, your target languages will be translated automatically after migration — asynchronously.

Pro Tip: In case the translated files are not ready yet, wait a moment after migrating and run npx i18next-cli locize-download to pull those new translations back into your local project.

npx i18next-cli locize-download

Step 5 — Iterate: Adding New Keys

Development doesn't stop after the initial migration. When you add a new feature:

// DashboardPage.tsx
<p>{t('howAreYou', 'How are you today?')}</p>
Enter fullscreen mode Exit fullscreen mode

Just run two commands:

npx i18next-cli extract      # picks up the new key locally
npx i18next-cli locize-sync  # syncs new keys to Locize, downloads translations
Enter fullscreen mode Exit fullscreen mode

The new key appears in public/locales/en/translation.json immediately after extract, and after locize-sync all other languages will have it translated (instantly if Auto-Translation is on, or pending your translators' review).

Command reference

Command When to use
extract After every code change — keeps local JSON in sync with your source
types After extract — generates TypeScript definitions for type-safe translation keys
locize-migrate Once, for the initial bulk push of local resources to Locize
locize-sync Ongoing — two-way sync between local files and Locize
locize-download Pull the latest translations from Locize without pushing changes

Step 6 — Going Further: Dynamic Translation Loading

Bundling JSON files works well, but i18next-locize-backend lets you decouple your translations entirely from your deployment cycle.

npm install i18next-locize-backend
Enter fullscreen mode Exit fullscreen mode
// src/i18n.ts
import i18next from 'i18next'
import { initReactI18next } from 'react-i18next'
import LocizeBackend from 'i18next-locize-backend'

i18next
  .use(initReactI18next)
  .use(LocizeBackend)
  .init({
    fallbackLng: 'en',
    backend: {
      projectId: 'your-project-id',
      version: 'latest'
    }
  })

export default i18next
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • No re-deploys for copy changes. Fix a typo in the Locize editor and it appears in your live app at the next page load — no build, no deploy.
  • Smaller bundles. Only the user's language is fetched, on demand.
  • Phased rollouts. Use Locize versioning to promote translations independently of code versions.

Step 7 — Power Features: saveMissing and the InContext Editor

saveMissing — Zero-friction key registration

During development, enable saveMissing to automatically push newly written keys to Locize the moment they appear in the running app — no extract step needed:

i18next.use(LocizeBackend).init({
  saveMissing: true,        // push unknown keys to Locize immediately
  backend: { projectId: 'your-project-id', apiKey: 'your-api-key' }
})
Enter fullscreen mode Exit fullscreen mode

This pairs beautifully with Locize AI: as soon as a new key arrives, Locize translates it into every configured language automatically.

InContext Editor — Edit translations in place

Add the Locize InContext Editor to your development build for a visual, in-page translation workflow — click any string, edit it directly in the browser, and save it back to Locize without ever leaving the app.

npm install locize-lastused locize
Enter fullscreen mode Exit fullscreen mode
import LastUsed from 'locize-lastused'
import { locizePlugin } from 'locize'

i18next
  .use(LastUsed)    // tracks which keys are actually used in production
  .use(locizePlugin) // enables the InContext Editor
  // ...
Enter fullscreen mode Exit fullscreen mode

locize-lastused also flags keys in your Locize dashboard that haven't been used recently — making it easy to clean up stale translations alongside your code.


The Full Journey at a Glance

1.  npx i18next-cli instrument       → Wrap strings, inject hooks, generate i18n.ts
2.  [manual tweaks]                  → Ignore brand names, fix dynamic key patterns
3.  npx i18next-cli extract          → Generate local translation JSON
    npx i18next-cli types            → Generate TypeScript definitions (optional)
4.  npx i18next-cli locize-migrate   → Push everything to Locize (first time)
    [Locize AI translates async]
    npx i18next-cli locize-download  → Pull auto-translated files back locally
5.  [code new feature]
    npx i18next-cli extract          → Pick up new keys
    npx i18next-cli locize-sync      → Two-way sync with Locize

Optional upgrades:
    → i18next-locize-backend         → Fetch translations dynamically (no re-deploy)
    → saveMissing: true              → Auto-register new keys from running app
    → locize InContext Editor        → Edit translations visually in the browser
    → locize-lastused                → Track and clean up stale keys
Enter fullscreen mode Exit fullscreen mode

We went from a standard React app with 100+ hardcoded strings to a fully instrumented, cloud-synced, multi-language application in minutes — not a weekend.

Summary: The Workflow

Phase Commands
Setup instrumentextractlocize-migrate
Daily Dev extractlocize-sync
Live Updates Use i18next-locize-backend for instant publishing

By leveraging the i18next-cli, we've turned what used to be a grueling manual task into a streamlined, automated pipeline.

Want to see if your project is ready? Run:

npx i18next-cli status
Enter fullscreen mode Exit fullscreen mode

It gives you an instant overview of your current i18n health.


Top comments (0)