<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Adriano Raiano</title>
    <description>The latest articles on Forem by Adriano Raiano (@adrai).</description>
    <link>https://forem.com/adrai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F614344%2F8a97d74c-d54f-4a3a-a201-2cc216321546.jpeg</url>
      <title>Forem: Adriano Raiano</title>
      <link>https://forem.com/adrai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/adrai"/>
    <language>en</language>
    <item>
      <title>i18next in Production: Managing Translations at Scale</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Mon, 13 Apr 2026 06:19:34 +0000</pubDate>
      <link>https://forem.com/adrai/i18next-in-production-managing-translations-at-scale-lkb</link>
      <guid>https://forem.com/adrai/i18next-in-production-managing-translations-at-scale-lkb</guid>
      <description>&lt;p&gt;i18next scales from a weekend project to a production app with millions of users — but the way you manage translations needs to evolve as your project grows. At prototype scale, a &lt;code&gt;locales/en/translation.json&lt;/code&gt; file in your repo is all you need. At production scale — 10 languages, multiple teams, thousands of keys — manual file management becomes the bottleneck, not i18next itself.&lt;/p&gt;

&lt;p&gt;This post covers the practical problems that appear when managing translations at scale, and the i18next ecosystem patterns that solve them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: Translation files in the repo become a bottleneck
&lt;/h2&gt;

&lt;p&gt;At prototype scale, translation JSON files live in your repo. A developer adds a key, translates it, commits, done. At production scale, this becomes a problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge conflicts.&lt;/strong&gt; Two developers add keys to the same namespace. Every PR that touches translations conflicts with every other one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translators need repo access.&lt;/strong&gt; Your German translator shouldn't need to know how to create a Git branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translations block deploys.&lt;/strong&gt; A feature is done but the French translations aren't ready. Do you ship with missing translations or wait?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pattern: Decouple translation delivery from code delivery
&lt;/h3&gt;

&lt;p&gt;Instead of bundling translations in your build, load them at runtime from a CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Backend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-http-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;loadPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.example.com/locales/{{lng}}/{{ns}}.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this pattern, translations update independently of your app. Fix a typo, add a language, adjust tone — all without touching code.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/locize/i18next-locize-backend" rel="noopener noreferrer"&gt;i18next-locize-backend&lt;/a&gt; takes this further: translations are served from a managed global CDN with built-in cache invalidation and versioning. No CDN configuration needed on your side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Backend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-locize-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problem 2: Nobody knows which keys are missing
&lt;/h2&gt;

&lt;p&gt;At prototype scale, one developer manages all the keys. At production scale, different teams own different namespaces, and nobody has a complete picture of what's translated and what's not.&lt;/p&gt;

&lt;p&gt;Common symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users see untranslated keys in production (the &lt;code&gt;checkout.confirmButton&lt;/code&gt; incident)&lt;/li&gt;
&lt;li&gt;A new feature ships with 12 languages but only 8 have translations&lt;/li&gt;
&lt;li&gt;Nobody knows that the &lt;code&gt;emails&lt;/code&gt; namespace hasn't been touched in 3 months&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pattern: saveMissing + coverage tracking
&lt;/h3&gt;

&lt;p&gt;i18next's &lt;code&gt;saveMissing&lt;/code&gt; feature detects untranslated keys at runtime and reports them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;saveMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// dev only&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a missing key is encountered, it's automatically sent to your translation management system. No manual extraction step, no "did someone add this key?" questions.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://locize.com" rel="noopener noreferrer"&gt;Locize&lt;/a&gt;, missing keys appear in the dashboard immediately. Combined with &lt;a href="https://www.locize.com/docs/automatic-translation" rel="noopener noreferrer"&gt;automatic translation&lt;/a&gt;, new keys can be translated into all target languages without any manual intervention — using your configured AI or MT provider with &lt;a href="https://www.locize.com/docs/styleguide" rel="noopener noreferrer"&gt;styleguide&lt;/a&gt; and &lt;a href="https://www.locize.com/docs/glossary" rel="noopener noreferrer"&gt;glossary&lt;/a&gt; context.&lt;/p&gt;

&lt;p&gt;For build-time key extraction (SSG, SSR, or when saveMissing isn't suitable for your runtime), the &lt;a href="https://www.locize.com/blog/i18next-cli" rel="noopener noreferrer"&gt;i18next-cli&lt;/a&gt; scans your source code and extracts all &lt;code&gt;t()&lt;/code&gt; calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 3: Translation quality drifts over time
&lt;/h2&gt;

&lt;p&gt;At prototype scale, one person writes all translations and they're internally consistent. At production scale, different people translate different parts at different times. "Cancel" becomes "Dismiss" in one dialog and "Abort" in another. Your brand's name for a feature is translated three different ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Glossary + Translation Memory + Styleguide
&lt;/h3&gt;

&lt;p&gt;These three features work together to maintain consistency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.locize.com/docs/glossary" rel="noopener noreferrer"&gt;Glossary&lt;/a&gt;:&lt;/strong&gt; Define approved and forbidden terminology. "Drive" means cloud storage, not driving. "Workspace" is always "Espace de travail" in French, never "Zone de travail."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.locize.com/docs/translation-memory" rel="noopener noreferrer"&gt;Translation Memory&lt;/a&gt;:&lt;/strong&gt; When a translator works on a new key, they see how similar strings were translated before. Fuzzy matching surfaces past translations with character-level diffs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.locize.com/docs/styleguide" rel="noopener noreferrer"&gt;Styleguide&lt;/a&gt;:&lt;/strong&gt; Define tone, formality level, target audience, and usage rules once. Applied automatically to all AI translation providers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Locize, all three are injected into AI translation prompts automatically — so even AI-generated first drafts are consistent with your established terminology and brand voice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 4: No review process for translations
&lt;/h2&gt;

&lt;p&gt;At prototype scale, translations go straight to production. At production scale, a wrong translation in a legal disclaimer or a payment screen is a business problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Review workflow
&lt;/h3&gt;

&lt;p&gt;Enable per-language review so changes go through approval before going live:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A translator (or AI) creates or modifies a translation&lt;/li&gt;
&lt;li&gt;The change is marked as "pending review"&lt;/li&gt;
&lt;li&gt;A reviewer accepts or rejects the change&lt;/li&gt;
&lt;li&gt;Only accepted translations are promoted to production quality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Locize, the &lt;a href="https://www.locize.com/docs/review-workflow" rel="noopener noreferrer"&gt;review workflow&lt;/a&gt; is configurable per language. Enable it for customer-facing languages (German, French, Japanese) while keeping it off for internal/developer languages where speed matters more than perfection.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 5: No way to test translations before releasing
&lt;/h2&gt;

&lt;p&gt;At prototype scale, "testing" means refreshing the page. At production scale, you need to verify translations in a staging environment without affecting production users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Branches and versions
&lt;/h3&gt;

&lt;p&gt;Translation branches work like code branches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a translation branch when you create a feature branch&lt;/li&gt;
&lt;li&gt;Translate new strings in isolation&lt;/li&gt;
&lt;li&gt;Test in staging against the branch translations&lt;/li&gt;
&lt;li&gt;Merge the translation branch when the feature ships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Locize, &lt;a href="https://www.locize.com/docs/general-questions/should-i-use-versions-or-branches" rel="noopener noreferrer"&gt;branches&lt;/a&gt; mirror your Git workflow. Each branch has its own CDN endpoint, so your staging environment can point to the branch translations while production serves from the main version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 6: SaaS customers need different translations
&lt;/h2&gt;

&lt;p&gt;If you build a SaaS product, your customers may need to customize translations — different terminology, different tone, different branding. Managing a separate translation project per customer doesn't scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Multi-tenant overrides
&lt;/h3&gt;

&lt;p&gt;Locize supports &lt;a href="https://www.locize.com/multi-tenant" rel="noopener noreferrer"&gt;multi-tenant localization&lt;/a&gt;: a parent project holds base translations, and each tenant gets a child project that inherits everything but can override specific keys. Each tenant has its own CDN endpoint.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base translations are maintained once&lt;/li&gt;
&lt;li&gt;Each customer can customize only the strings they care about&lt;/li&gt;
&lt;li&gt;Overrides are served efficiently — only the diff, not the full translation set&lt;/li&gt;
&lt;li&gt;Tenant users have isolated access — they can't see other tenants or the parent&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Problem 7: Translation updates require a deploy
&lt;/h2&gt;

&lt;p&gt;This is the most common friction point at scale. A translator fixes a typo. The fix sits in the TMS waiting for a developer to download the file, commit it, open a PR, get it reviewed, merge it, and deploy. Days pass. The typo is live in production the whole time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: CDN delivery with instant publish
&lt;/h3&gt;

&lt;p&gt;When translations are served from a CDN instead of bundled in your build, updates go live without deploying:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Translator fixes the typo in the editor&lt;/li&gt;
&lt;li&gt;Clicks "Publish" (or auto-publish is enabled)&lt;/li&gt;
&lt;li&gt;CDN cache is invalidated&lt;/li&gt;
&lt;li&gt;Next time a user loads the app, they get the fixed translation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Locize, publishing takes seconds. For the &lt;a href="https://www.locize.com/docs/cdn" rel="noopener noreferrer"&gt;standard CDN&lt;/a&gt;, translations propagate globally within minutes. No code change, no build, no deploy, no PR.&lt;/p&gt;




&lt;h2&gt;
  
  
  The complete production setup
&lt;/h2&gt;

&lt;p&gt;Here's what a production-grade i18next setup with Locize looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;saveMissing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt; → keys auto-appear in Locize&lt;/span&gt;
  &lt;span class="s"&gt;Auto-translate → AI generates first drafts&lt;/span&gt;

&lt;span class="na"&gt;Staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;Translation branch → isolated from production&lt;/span&gt;
  &lt;span class="s"&gt;Review workflow → translators verify AI output&lt;/span&gt;

&lt;span class="na"&gt;Production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;CDN delivery → translations update without deploy&lt;/span&gt;
  &lt;span class="s"&gt;Glossary + TM → consistency across 50K keys&lt;/span&gt;
  &lt;span class="s"&gt;Multi-tenant → per-customer overrides&lt;/span&gt;
  &lt;span class="s"&gt;MCP server → AI agents manage translations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each piece is optional — you can adopt them incrementally as your project grows. Start with CDN delivery and saveMissing. Add review workflows when quality matters. Add branches when you have staging environments. Add multi-tenancy when your customers need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;If you're running i18next in production with local JSON files and want to move to a managed workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.locize.app/register" rel="noopener noreferrer"&gt;Register for free&lt;/a&gt;&lt;/strong&gt; — no credit card required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.locize.com/docs/guides/migrating-an-i18next-project" rel="noopener noreferrer"&gt;Migrate your project&lt;/a&gt;&lt;/strong&gt; — the CLI imports your existing JSON files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch to i18next-locize-backend&lt;/strong&gt; — one config change, same API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable saveMissing&lt;/strong&gt; — new keys auto-appear in Locize&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable auto-translate&lt;/strong&gt; — AI translates new keys automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire migration takes less than an hour for most projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/blog/automatic-translation-i18next-locize" rel="noopener noreferrer"&gt;How to set up automatic translation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/blog/i18next-cli" rel="noopener noreferrer"&gt;i18next-cli walkthrough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/blog/i18next-savemissing-ai-automation" rel="noopener noreferrer"&gt;saveMissing + AI automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/guides/going-to-production" rel="noopener noreferrer"&gt;Going to production guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/general-questions/should-i-use-versions-or-branches" rel="noopener noreferrer"&gt;Branches vs. versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/integration/mcp" rel="noopener noreferrer"&gt;MCP server for AI assistants&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>development</category>
    </item>
    <item>
      <title>next-intl vs next-i18next: Choosing the Right i18n Library for Next.js</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Fri, 10 Apr 2026 05:44:13 +0000</pubDate>
      <link>https://forem.com/adrai/next-intl-vs-next-i18next-choosing-the-right-i18n-library-for-nextjs-646</link>
      <guid>https://forem.com/adrai/next-intl-vs-next-i18next-choosing-the-right-i18n-library-for-nextjs-646</guid>
      <description>&lt;p&gt;Next.js developers choosing an i18n solution will likely narrow it down to two libraries: &lt;a href="https://next-intl.dev" rel="noopener noreferrer"&gt;next-intl&lt;/a&gt; and &lt;a href="https://github.com/i18next/next-i18next" rel="noopener noreferrer"&gt;next-i18next&lt;/a&gt;. Both support the App Router and Server Components. Both are actively maintained. But they take fundamentally different architectural approaches.&lt;/p&gt;

&lt;p&gt;This post compares them on the things that matter for a real project: how translations are loaded, how they integrate with your stack, and what workflows they enable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;next-intl&lt;/th&gt;
&lt;th&gt;next-i18next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standalone library (built on &lt;code&gt;intl-messageformat&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Built on &lt;a href="https://www.i18next.com" rel="noopener noreferrer"&gt;i18next&lt;/a&gt; — the most widely used JavaScript i18n framework — with &lt;a href="https://react.i18next.com" rel="noopener noreferrer"&gt;react-i18next&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Message format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ICU MessageFormat&lt;/td&gt;
&lt;td&gt;i18next JSON format (the most common format in the JS ecosystem)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App Router&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported via &lt;code&gt;getT()&lt;/code&gt; / &lt;code&gt;useT()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pages Router&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited/legacy&lt;/td&gt;
&lt;td&gt;Full support via &lt;code&gt;next-i18next/pages&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server Components&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Translation loading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom async loader function (you build it)&lt;/td&gt;
&lt;td&gt;Backend plugin ecosystem (drop-in packages for CDN, filesystem, Locize, chained caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bundle size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal runtime via ICU precompilation&lt;/td&gt;
&lt;td&gt;Flexible — tree-shakeable, scales with features used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type safety&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build-time catalog extraction&lt;/td&gt;
&lt;td&gt;Full TypeScript support with &lt;a href="https://www.locize.com/blog/i18next-typescript-selector-api" rel="noopener noreferrer"&gt;selector API&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How translations are loaded
&lt;/h2&gt;

&lt;p&gt;This is the most important architectural difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  next-intl: custom loader function
&lt;/h3&gt;

&lt;p&gt;next-intl uses a request-scoped configuration function (&lt;code&gt;i18n/request.ts&lt;/code&gt;) where you define how to load messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// i18n/request.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getRequestConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;getRequestConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../../messages/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can load from local files, a CDN, a CMS, or any async source. There is no plugin system — you write the loader yourself. This is flexible but means every integration is custom code.&lt;/p&gt;

&lt;h3&gt;
  
  
  next-i18next: backend plugin ecosystem
&lt;/h3&gt;

&lt;p&gt;next-i18next inherits the full &lt;a href="https://www.i18next.com/overview/plugins-and-utils" rel="noopener noreferrer"&gt;i18next backend plugin ecosystem&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Backend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-locize-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available backends include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/i18next/i18next-http-backend" rel="noopener noreferrer"&gt;i18next-http-backend&lt;/a&gt;&lt;/strong&gt; — load from any HTTP/CDN endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/locize/i18next-locize-backend" rel="noopener noreferrer"&gt;i18next-locize-backend&lt;/a&gt;&lt;/strong&gt; — direct Locize CDN integration with saveMissing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/i18next/i18next-fs-backend" rel="noopener noreferrer"&gt;i18next-fs-backend&lt;/a&gt;&lt;/strong&gt; — load from filesystem (Node.js)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/i18next/i18next-chained-backend" rel="noopener noreferrer"&gt;i18next-chained-backend&lt;/a&gt;&lt;/strong&gt; — chain multiple backends (e.g., localStorage cache + remote)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This plugin architecture means integrations with TMS platforms, CDNs, and caching layers are available as drop-in packages rather than custom code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Message format
&lt;/h2&gt;

&lt;h3&gt;
  
  
  next-intl: ICU MessageFormat
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, {name}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have {count, plural, one {# item} other {# items}} in your cart."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ICU MessageFormat is an industry standard supported by many TMS platforms. next-intl includes a precompilation step (&lt;code&gt;icu-minify&lt;/code&gt;) that reduces the runtime to under 1KB.&lt;/p&gt;

&lt;h3&gt;
  
  
  next-i18next: i18next format
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, {{name}}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items_one"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have {{count}} item in your cart."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items_other"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have {{count}} items in your cart."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;i18next uses its own JSON format with &lt;code&gt;{{interpolation}}&lt;/code&gt; syntax and key-based pluralization (suffix &lt;code&gt;_one&lt;/code&gt;, &lt;code&gt;_other&lt;/code&gt;, &lt;code&gt;_few&lt;/code&gt;, etc.). This format is natively understood by Locize and other i18next-compatible tools. It is also widely adopted — i18next is the most downloaded i18n library in the JavaScript ecosystem.&lt;/p&gt;

&lt;p&gt;Both formats handle plurals, interpolation, and nesting. The choice is largely about ecosystem compatibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  saveMissing: automatic key discovery
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;next-i18next&lt;/strong&gt; supports i18next's &lt;code&gt;saveMissing&lt;/code&gt; feature. When your app encounters a translation key that doesn't exist yet, it can automatically report it to your TMS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;saveMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the Locize backend, missing keys are batched and sent automatically. Combined with &lt;a href="https://www.locize.com/docs/automatic-translation" rel="noopener noreferrer"&gt;automatic translation&lt;/a&gt;, this creates a fully automated pipeline: write code → keys appear in Locize → AI translates → live on CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;next-intl&lt;/strong&gt; does not support saveMissing. Key management is manual — you add keys to your JSON files or TMS yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Translation management integration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;next-i18next&lt;/strong&gt; integrates directly with &lt;a href="https://locize.com" rel="noopener noreferrer"&gt;Locize&lt;/a&gt; via the dedicated &lt;code&gt;i18next-locize-backend&lt;/code&gt; plugin. This provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDN delivery at runtime (translations update without redeploy)&lt;/li&gt;
&lt;li&gt;saveMissing (automatic key discovery)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://locize.com/docs/the-different-views/incontext" rel="noopener noreferrer"&gt;InContext editor&lt;/a&gt; (translate on your running app)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.locize.com/docs/general-questions/should-i-use-versions-or-branches" rel="noopener noreferrer"&gt;Branches&lt;/a&gt; (translation CI/CD)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.locize.com/multi-tenant" rel="noopener noreferrer"&gt;Multi-tenancy&lt;/a&gt; (per-tenant overrides)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Locize backend is maintained by the same team that wrote i18next and next-i18next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;next-intl&lt;/strong&gt; does not have a dedicated TMS plugin. You can integrate with any TMS by writing a custom loader in &lt;code&gt;i18n/request.ts&lt;/code&gt;, but the integration is your responsibility. next-intl's documentation mentions &lt;a href="https://crowdin.com" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; as a compatible TMS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Routing and middleware
&lt;/h2&gt;

&lt;p&gt;Both libraries handle locale routing via Next.js middleware:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;next-intl&lt;/strong&gt; uses &lt;code&gt;createNavigation()&lt;/code&gt; and &lt;code&gt;next-intl/middleware&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL-based routing with dynamic &lt;code&gt;[locale]&lt;/code&gt; segments&lt;/li&gt;
&lt;li&gt;Domain-based routing supported&lt;/li&gt;
&lt;li&gt;Built-in locale detection (URL, cookies, Accept-Language)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;next-i18next&lt;/strong&gt; (v16+) uses &lt;code&gt;createProxy()&lt;/code&gt; (Next.js 16+) or &lt;code&gt;createMiddleware()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookie-based locale detection&lt;/li&gt;
&lt;li&gt;Mixed Router support (App Router + Pages Router coexisting)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;basePath&lt;/code&gt; scoping for multi-router setups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches work well. The routing mechanism is rarely the deciding factor.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to choose next-intl
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You prefer &lt;strong&gt;ICU MessageFormat&lt;/strong&gt; and want it built into the framework&lt;/li&gt;
&lt;li&gt;You want a &lt;strong&gt;standalone, self-contained library&lt;/strong&gt; with no external dependencies&lt;/li&gt;
&lt;li&gt;You prefer to &lt;strong&gt;write custom loader functions&lt;/strong&gt; for translation sources&lt;/li&gt;
&lt;li&gt;You are building a &lt;strong&gt;Next.js-only project&lt;/strong&gt; with no need to share i18n setup across other platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to choose next-i18next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You want access to the &lt;strong&gt;largest i18n plugin ecosystem&lt;/strong&gt; — drop-in backends for CDN, filesystem, caching, and managed services&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;saveMissing&lt;/strong&gt; — automatic key discovery from your running app, no manual extraction needed&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;CDN delivery&lt;/strong&gt; — translations that update without redeploying your app&lt;/li&gt;
&lt;li&gt;You want a &lt;strong&gt;managed translation backend&lt;/strong&gt; with AI translation, review workflows, and a translator UI via &lt;a href="https://locize.com" rel="noopener noreferrer"&gt;Locize&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;branches and multi-tenancy&lt;/strong&gt; for complex localization workflows&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;share the same i18n setup&lt;/strong&gt; across React, Vue, Node.js, React Native, or other platforms in your stack&lt;/li&gt;
&lt;li&gt;You want the &lt;strong&gt;proven maturity&lt;/strong&gt; of i18next — 15+ years, most downloaded i18n library in JavaScript&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Both next-intl and next-i18next are production-ready libraries for Next.js internationalization. The choice comes down to what you need beyond basic translation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;next-intl&lt;/strong&gt; is a self-contained solution with ICU message format. Good for Next.js-only projects where you want to manage everything yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;next-i18next&lt;/strong&gt; connects you to the i18next ecosystem — the most widely adopted i18n infrastructure in JavaScript. Backend plugins, saveMissing, CDN delivery, AI-powered translation, and a managed backend via &lt;a href="https://locize.com" rel="noopener noreferrer"&gt;Locize&lt;/a&gt; are all available as drop-in additions, not custom code you write and maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams that want their translations to work across multiple platforms, update without redeploying, and scale with managed infrastructure — next-i18next with Locize is the most complete setup available.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.locize.com/blog/next-i18next-v16" rel="noopener noreferrer"&gt;next-i18next v16 guide&lt;/a&gt; — complete setup for App Router and Pages Router&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.locize.com/blog/i18n-next-app-router" rel="noopener noreferrer"&gt;Next.js App Router i18n guide&lt;/a&gt; — detailed App Router tutorial&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.locize.com/docs/general-questions/how-is-locize-different-from-the-alternatives" rel="noopener noreferrer"&gt;i18next vs alternatives&lt;/a&gt; — broader comparison&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/locize/next-i18next-locize" rel="noopener noreferrer"&gt;Locize + next-i18next integration&lt;/a&gt; — example repository&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Set Up Automatic Translation with i18next and Locize</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Thu, 09 Apr 2026 06:57:38 +0000</pubDate>
      <link>https://forem.com/adrai/how-to-set-up-automatic-translation-with-i18next-and-locize-32pj</link>
      <guid>https://forem.com/adrai/how-to-set-up-automatic-translation-with-i18next-and-locize-32pj</guid>
      <description>&lt;p&gt;Most i18next setups require manual steps to manage translations: extract keys, send them for translation, download the results, commit the files, deploy. Each step is a context switch, and the longer the cycle, the more likely translations lag behind your code.&lt;/p&gt;

&lt;p&gt;This guide shows how to eliminate all of those manual steps using i18next's &lt;code&gt;saveMissing&lt;/code&gt; feature and Locize's automatic translation workflow. The result: you write code, and translations appear — in all your target languages, live on the CDN, without a deploy.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the automated workflow looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer writes t('checkout.title', 'Complete your order')
     ↓
i18next detects the key is missing
     ↓
saveMissing sends it to Locize automatically
     ↓
Locize auto-translates into all target languages
(using your configured AI/MT provider + styleguide + glossary)
     ↓
Translations publish to CDN
     ↓
Your app fetches the updated translations at next load
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No files to commit. No deploy to trigger. No translator to email (unless you want human review).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Set up i18next with Locize backend
&lt;/h2&gt;

&lt;p&gt;Install the packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;i18next i18next-locize-backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For React, also add &lt;code&gt;react-i18next&lt;/code&gt;. For Next.js, add &lt;code&gt;next-i18next&lt;/code&gt;. The backend plugin works with any i18next setup.&lt;/p&gt;

&lt;p&gt;Configure i18next with the Locize backend and &lt;code&gt;saveMissing&lt;/code&gt; enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Backend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-locize-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;saveMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// needed for saveMissing — remove in production&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;saveMissing&lt;/code&gt; should only run in development. In production, remove the &lt;code&gt;apiKey&lt;/code&gt; and set &lt;code&gt;saveMissing: false&lt;/code&gt;. See the &lt;a href="https://www.locize.com/docs/guides/going-to-production" rel="noopener noreferrer"&gt;going to production guide&lt;/a&gt; for details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;allowedAddOrUpdateHosts&lt;/code&gt; option defaults to &lt;code&gt;['localhost']&lt;/code&gt;, so saveMissing only fires on localhost even if you forget to disable it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Enable automatic translation in Locize
&lt;/h2&gt;

&lt;p&gt;In your Locize project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Project Settings &amp;gt; EDITOR, TM/MT/AI, ORDERING&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Automatic Translation Workflow&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu3zayxdq67l77x6oid0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu3zayxdq67l77x6oid0.jpg" alt="enable automatic translation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose which translation provider to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locize AI&lt;/strong&gt; — built-in, works on all paid plans, no external API key needed. Token-based billing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locize MT&lt;/strong&gt; — built-in machine translation, character-based billing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BYOK (Bring Your Own Key)&lt;/strong&gt; — use your own OpenAI, Gemini, Mistral, or DeepL key. Available on Professional ($99/mo) and above. No markup — you pay the provider directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All providers &lt;em&gt;(where possible)&lt;/em&gt; automatically receive context from your project's &lt;a href="https://www.locize.com/docs/styleguide" rel="noopener noreferrer"&gt;styleguide&lt;/a&gt;, &lt;a href="https://www.locize.com/docs/glossary" rel="noopener noreferrer"&gt;glossary&lt;/a&gt;, and &lt;a href="https://www.locize.com/docs/translation-memory" rel="noopener noreferrer"&gt;translation memory&lt;/a&gt;. This is what makes Locize's automatic translations better than raw API calls — the AI knows your brand voice, approved terminology, and past translations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Write code — translations happen automatically
&lt;/h2&gt;

&lt;p&gt;Use translation keys in your components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTranslation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Complete your order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.subtitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Review your items before payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.pay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pay now&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When your app runs in development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;i18next encounters &lt;code&gt;checkout.title&lt;/code&gt; for the first time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;saveMissing&lt;/code&gt; batches the key (5-second debounce) and sends it to Locize&lt;/li&gt;
&lt;li&gt;Locize receives the key with its default value ("Complete your order")&lt;/li&gt;
&lt;li&gt;Automatic translation kicks in — the key is translated into all target languages&lt;/li&gt;
&lt;li&gt;Translations appear on the Locize CDN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next time the app loads, i18next fetches the translations from the CDN, including all the auto-generated translations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Improve translation quality with context
&lt;/h2&gt;

&lt;p&gt;Automatic translation works better with context. There are several ways to provide it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Styleguide
&lt;/h3&gt;

&lt;p&gt;Define your brand voice once in &lt;a href="https://www.locize.com/docs/styleguide" rel="noopener noreferrer"&gt;Project Settings &amp;gt; Styleguide&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tone of voice (formal, casual, technical)&lt;/li&gt;
&lt;li&gt;Target audience&lt;/li&gt;
&lt;li&gt;Do's and don'ts&lt;/li&gt;
&lt;li&gt;Per-language overrides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The styleguide is automatically injected into every AI translation prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Glossary
&lt;/h3&gt;

&lt;p&gt;Add approved terminology in &lt;a href="https://www.locize.com/docs/glossary" rel="noopener noreferrer"&gt;Project Settings &amp;gt; Glossary&lt;/a&gt;. Terms can be marked as approved or forbidden, with target-language equivalents. The glossary is injected into AI prompts as "use these exact translations."&lt;/p&gt;

&lt;h3&gt;
  
  
  Key-level context
&lt;/h3&gt;

&lt;p&gt;When using saveMissing, you can include context descriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your i18next config:&lt;/span&gt;
&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tDescription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tDescription&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add context directly in the Locize editor for each key. This context appears in the AI prompt and is shown to human translators.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Optional — add human review
&lt;/h2&gt;

&lt;p&gt;For content where AI quality is sufficient (internal tools, developer-facing UI, technical docs), you can skip review entirely.&lt;/p&gt;

&lt;p&gt;For brand-critical content, AI or MT translations are marked as "fuzzy" (quality: AI or MT). A translator reviews them in the Locize editor and accepts or modifies them. Only accepted translations are promoted to "human translated" quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Deploy translations without redeploying
&lt;/h2&gt;

&lt;p&gt;Translations served via the Locize CDN update independently of your app deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-publish&lt;/strong&gt; (if enabled for your version): translations go live on the CDN as soon as they're saved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual publish&lt;/strong&gt;: click "Publish" in the Locize UI, or call &lt;code&gt;locize publish-version&lt;/code&gt; from the CLI or a &lt;a href="https://github.com/locize/sync" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your app fetches translations from the CDN on each load. A copy fix, a new language, or a tone adjustment — all live without touching your codebase.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI agent workflow (bonus)
&lt;/h2&gt;

&lt;p&gt;If you use an AI coding assistant (Claude, Cursor, VS Code), the &lt;a href="https://www.locize.com/docs/integration/mcp" rel="noopener noreferrer"&gt;Locize MCP server&lt;/a&gt; lets your assistant manage translations directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Report these new keys to Locize"&lt;/strong&gt; — the AI-agent equivalent of saveMissing. New keys on the reference language trigger automatic translation when enabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What's the translation coverage for my project?"&lt;/strong&gt; — check if all languages are ready before publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Publish the latest version"&lt;/strong&gt; — push translations to CDN from your AI assistant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The MCP server exposes 22 tools covering the full translation lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Manual effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write code with &lt;code&gt;t('key')&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;i18next detects missing key&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;saveMissing sends key to Locize&lt;/td&gt;
&lt;td&gt;Key appears in project&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic translation&lt;/td&gt;
&lt;td&gt;AI/MT translates all languages&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human review (optional)&lt;/td&gt;
&lt;td&gt;Translator reviews in editor&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN delivery&lt;/td&gt;
&lt;td&gt;Translations go live&lt;/td&gt;
&lt;td&gt;None (auto-publish) or one click&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The entire pipeline from code to live translations can be fully automatic. The only manual step is human review — and that is optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/automatic-translation" rel="noopener noreferrer"&gt;Automatic Translation documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/blog/i18next-savemissing-ai-automation" rel="noopener noreferrer"&gt;saveMissing + AI automation tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/styleguide" rel="noopener noreferrer"&gt;Styleguide documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/guides/going-to-production" rel="noopener noreferrer"&gt;Going to production guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/docs/integration/mcp" rel="noopener noreferrer"&gt;MCP server documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.locize.com/why-not-just-use-ai" rel="noopener noreferrer"&gt;Why not just use AI to translate?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why We Added a Console Notice to i18next — and Why We Removed It</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Sat, 28 Mar 2026 08:00:45 +0000</pubDate>
      <link>https://forem.com/adrai/why-we-added-a-console-notice-to-i18next-and-why-we-removed-it-4j64</link>
      <guid>https://forem.com/adrai/why-we-added-a-console-notice-to-i18next-and-why-we-removed-it-4j64</guid>
      <description>&lt;p&gt;i18next turns 15 this year. It has almost &lt;strong&gt;15 million weekly npm downloads&lt;/strong&gt;,&lt;br&gt;
powers applications in every industry across the world, and is maintained&lt;br&gt;
by a small core team — the same people who founded Locize.&lt;/p&gt;

&lt;p&gt;This is the story of a decision we made, what it cost, and why we reversed it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The sustainability problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;Open source maintainers are expected to deliver production-grade software,&lt;br&gt;
respond to issues, fix security vulnerabilities, and keep pace with a&lt;br&gt;
fast-moving ecosystem — for free.&lt;/p&gt;

&lt;p&gt;We've tried the standard approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Sponsors&lt;/strong&gt; — the numbers were honest and we're grateful for every
contributor, but they never came nearly close to funding even a minimal part of a single full-time engineer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README badges and funding links&lt;/strong&gt; — almost no one reads them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPM funding metadata&lt;/strong&gt; — same story.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reality is that i18next exists today because of &lt;strong&gt;Locize&lt;/strong&gt; — our own&lt;br&gt;
managed localization product, built by the i18next core team to fund the&lt;br&gt;
library's continued development. Every security fix, every new feature,&lt;br&gt;
every improvement is made possible because Locize pays for the&lt;br&gt;
time to build it.&lt;/p&gt;

&lt;p&gt;The problem: most i18next users don't know this. They use the library,&lt;br&gt;
file issues, request features, and have no idea how it stays maintained.&lt;br&gt;
We're not complaining — that's the nature of open source. But it does&lt;br&gt;
create a real tension.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we decided to do
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;v25.8.0&lt;/strong&gt;, we introduced a single &lt;code&gt;console.info&lt;/code&gt; line that appeared&lt;br&gt;
when i18next initialized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🌐 i18next is made possible by our own product, Locize — consider powering your project with managed localization (AI, CDN, integrations): https://locize.com 💙
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reasoning was straightforward. &lt;code&gt;console.info&lt;/code&gt; is a developer-facing&lt;br&gt;
channel. It doesn't affect your end users, it doesn't break builds, it&lt;br&gt;
doesn't affect performance. And it reaches the one audience we actually&lt;br&gt;
wanted to reach: the developers who choose which tools their teams use.&lt;/p&gt;

&lt;p&gt;We provided an opt-out via &lt;code&gt;showSupportNotice: false&lt;/code&gt;, and&lt;br&gt;
later added a &lt;code&gt;globalThis&lt;/code&gt; kill-switch for PaaS and third-party dependency&lt;br&gt;
scenarios, and an &lt;code&gt;I18NEXT_NO_SUPPORT_NOTICE&lt;/code&gt; (and later also a &lt;code&gt;NODE_ENV=production&lt;/code&gt;) environment variable for&lt;br&gt;
CI/CD pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened next
&lt;/h2&gt;

&lt;p&gt;The developer community responded — loudly, and with legitimate concerns.&lt;/p&gt;

&lt;p&gt;The loudest complaints were about &lt;strong&gt;developer experience&lt;/strong&gt;: the notice&lt;br&gt;
appearing multiple times in Next.js builds, flooding test logs, appearing&lt;br&gt;
inside design systems that consumers couldn't configure. We addressed most&lt;br&gt;
of these iteratively: improving the deduplication logic, adding the&lt;br&gt;
&lt;code&gt;globalThis&lt;/code&gt; kill-switch, documenting the suppression options properly.&lt;/p&gt;

&lt;p&gt;But the complaint that made us think hardest came from a PaaS provider&lt;br&gt;
who filed a detailed incident report. Their claim: the console notice may&lt;br&gt;
have caused &lt;strong&gt;Google Safe Browsing to block hundreds of customer sites&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We investigated carefully. Our honest technical assessment is that&lt;br&gt;
&lt;code&gt;console.info&lt;/code&gt; output is not evaluated by Google Safe Browsing — GSB scans&lt;br&gt;
DOM content, not console output, and a link to &lt;code&gt;locize.com&lt;/code&gt; in the console&lt;br&gt;
would have to affect our own platform and thousands of direct users if that&lt;br&gt;
were the mechanism. We still believe the actual cause was unrelated to&lt;br&gt;
the notice.&lt;/p&gt;

&lt;p&gt;But here's what we couldn't dismiss: the reporter had a real production&lt;br&gt;
incident, affecting real customers, and the only change they made — adding&lt;br&gt;
the &lt;code&gt;globalThis&lt;/code&gt; kill-switch at the infrastructure level via a Cloudflare&lt;br&gt;
worker — correlated with the block being lifted. We can argue about&lt;br&gt;
causation. We cannot argue about the operational burden that placed on them.&lt;/p&gt;

&lt;p&gt;And we made a mistake in our response: we suggested a "bad actor on a&lt;br&gt;
subdomain" as the likely root cause. That was an assumption that didn't&lt;br&gt;
fit their specific setup — they don't host user-generated content — and&lt;br&gt;
we shouldn't have made it. We apologized for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data actually showed
&lt;/h2&gt;

&lt;p&gt;We tracked where new Locize registrations came from, based on an optional&lt;br&gt;
"how did you hear about us?" field at signup. Over approximately two months&lt;br&gt;
and &lt;strong&gt;226 responses&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Signups&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google / Search&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i18next ecosystem&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other / Noise&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;14.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI tools (ChatGPT, Gemini, etc.)&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;6.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console notice (explicit)&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;6.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Word of mouth&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;4.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub / npm / docs&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blog posts&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;14 people explicitly cited the console notice. The "i18next ecosystem"&lt;br&gt;
bucket likely includes more who saw it but described it differently.&lt;br&gt;
The notice drove &lt;em&gt;some&lt;/em&gt; awareness. Not zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we're removing it
&lt;/h2&gt;

&lt;p&gt;We're removing the notice in &lt;strong&gt;v26.0.0&lt;/strong&gt;. The sustainability argument hasn't changed — i18next exists because Locize exists, and that's still true.&lt;br&gt;
But the way we chose to communicate that created costs we didn't fully anticipate, and when we weighed those costs honestly, removing it was the right call.&lt;/p&gt;

&lt;p&gt;We're removing it because when we weigh the costs and benefits honestly,&lt;br&gt;
the math has changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;production friction&lt;/strong&gt; — PaaS providers, library authors, third-party
dependency scenarios — turned out to be more complex than we anticipated.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;edge cases&lt;/strong&gt; were real: CI pipelines, test logs, design systems,
environments where suppression required infrastructure-level intervention.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;community cost&lt;/strong&gt; — issues filed, trust eroded, developers who saw
it as a betrayal of open source principles — was higher than we expected.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;conversion impact&lt;/strong&gt; was real, and we're not convinced it would grow faster than the community cost it was accumulating.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;v26.0.0 is a major release with several breaking changes and modernization&lt;br&gt;
work. Removing the notice cleanly — including the &lt;code&gt;showSupportNotice&lt;/code&gt;&lt;br&gt;
option, the &lt;code&gt;globalThis&lt;/code&gt; mechanism, and the env var — fits naturally into&lt;br&gt;
that release.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you can actually support i18next
&lt;/h2&gt;

&lt;p&gt;If this post resonated with you, here's what actually helps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try Locize.&lt;/strong&gt; It's built by the i18next team, integrates deeply with
the ecosystem, and there's also a &lt;a href="https://www.locize.com/pricing" rel="noopener noreferrer"&gt;free plan&lt;/a&gt; to get started.
If it fits your workflow, it directly funds the library you're using.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell your team or decision-makers.&lt;/strong&gt; If your company uses i18next in
production, knowing that Locize funds its development is relevant
information for anyone evaluating localization tooling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mention it when it comes up.&lt;/strong&gt; When someone asks about i18next in your
community or on social media, it costs nothing to mention that Locize
is how the library stays maintained.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're not going anywhere. i18next will keep improving. The team is the&lt;br&gt;
same, the commitment is the same, and the library remains fully free and&lt;br&gt;
MIT-licensed.&lt;/p&gt;

&lt;p&gt;We just learned something about how to talk about that — and we're&lt;br&gt;
adjusting accordingly.&lt;/p&gt;

&lt;p&gt;— The i18next / Locize team&lt;/p&gt;

</description>
      <category>i18next</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>next-i18next v16: App Router, Pages Router, and Everything In Between</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Mon, 23 Mar 2026 08:00:17 +0000</pubDate>
      <link>https://forem.com/adrai/next-i18next-v16-app-router-pages-router-and-everything-in-between-4l5a</link>
      <guid>https://forem.com/adrai/next-i18next-v16-app-router-pages-router-and-everything-in-between-4l5a</guid>
      <description>&lt;p&gt;When &lt;a href="https://github.com/i18next/next-i18next" rel="noopener noreferrer"&gt;next-i18next&lt;/a&gt; was first released, Next.js only had the Pages Router. The library became the go-to way to add i18n to Next.js apps, wrapping &lt;code&gt;_app&lt;/code&gt; with &lt;code&gt;appWithTranslation&lt;/code&gt;, calling &lt;code&gt;serverSideTranslations&lt;/code&gt; in &lt;code&gt;getStaticProps&lt;/code&gt;, and letting Next.js handle locale routing.&lt;/p&gt;

&lt;p&gt;Then Next.js introduced the App Router with Server Components, and our &lt;a href="https://www.locize.com/blog/next-app-dir-i18n/" rel="noopener noreferrer"&gt;advice changed&lt;/a&gt;: "You don't need next-i18next anymore for App Router... just use i18next and react-i18next directly." We even published a &lt;a href="https://www.locize.com/blog/i18n-next-app-router/" rel="noopener noreferrer"&gt;streamlined setup guide&lt;/a&gt; showing how to wire it all up manually.&lt;/p&gt;

&lt;p&gt;But honestly? That manual wiring was boilerplate. Every project needed the same middleware, the same &lt;code&gt;getT&lt;/code&gt; helper, the same &lt;code&gt;I18nProvider&lt;/code&gt; setup. And projects migrating from Pages Router to App Router, or running both routers side by side, had no clean path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;next-i18next v16 fixes all of that.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;getT()&lt;/code&gt;&lt;/strong&gt; for Server Components — async, namespace-aware, type-safe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;useT()&lt;/code&gt;&lt;/strong&gt; for Client Components — reads language from URL params automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;createProxy()&lt;/code&gt;&lt;/strong&gt; for language detection and routing — edge-safe, zero Node.js dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;I18nProvider&lt;/code&gt;&lt;/strong&gt; for client hydration — with lazy-loading support for additional namespaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;basePath&lt;/code&gt;&lt;/strong&gt; scoping — run both App Router and Pages Router in the same app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-locale-path mode&lt;/strong&gt; — cookie-based language without URL prefixes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Pages Router compatibility&lt;/strong&gt; — &lt;code&gt;appWithTranslation&lt;/code&gt; and &lt;code&gt;serverSideTranslations&lt;/code&gt; via &lt;code&gt;next-i18next/pages&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Already using the manual i18next setup from our &lt;a href="https://www.locize.com/blog/i18n-next-app-router/" rel="noopener noreferrer"&gt;2025 blog post&lt;/a&gt;? Migration is straightforward: replace your custom helpers with &lt;code&gt;next-i18next/server&lt;/code&gt; and &lt;code&gt;next-i18next/client&lt;/code&gt;, and your middleware with &lt;code&gt;createProxy()&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  App Router Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;next-i18next i18next react-i18next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Translation Files
&lt;/h3&gt;

&lt;p&gt;Place your translations in your project. The simplest approach uses a &lt;code&gt;resourceLoader&lt;/code&gt; with dynamic imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/i18n/locales/en/common.json
app/i18n/locales/en/home.json
app/i18n/locales/de/common.json
app/i18n/locales/de/home.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configuration
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;i18n.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;I18nConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;I18nConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;resourceLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./app/i18n/locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;resourceLoader&lt;/code&gt; uses dynamic imports to load translation files, they get bundled at build time and loaded on demand. If you prefer, you can also place files in &lt;code&gt;public/locales/&lt;/code&gt; and skip the &lt;code&gt;resourceLoader&lt;/code&gt; (the default filesystem loader will pick them up).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Proxy (Language Detection &amp;amp; Routing)
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;proxy.ts&lt;/code&gt; at your project root. Next.js 16 renamed the old &lt;code&gt;middleware.ts&lt;/code&gt; convention to &lt;code&gt;proxy.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createProxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./i18n.config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i18nConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js|site.webmanifest).*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Language detection from cookie → Accept-Language header → fallback&lt;/li&gt;
&lt;li&gt;Redirecting bare URLs to locale-prefixed paths (&lt;code&gt;/about&lt;/code&gt; → &lt;code&gt;/en/about&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Setting a custom header for Server Components to read the current language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Still on Next.js 14 or 15? Use &lt;code&gt;createMiddleware&lt;/code&gt; from &lt;code&gt;next-i18next/middleware&lt;/code&gt; in your &lt;code&gt;middleware.ts&lt;/code&gt;: same API, just the old file convention.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Root Layout
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;app/[lng]/layout.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initServerI18next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getResources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generateI18nStaticParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;I18nProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../i18n.config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;initServerI18next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i18nConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateStaticParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generateI18nStaticParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lng&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;I18nProvider&lt;/span&gt; &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;I18nProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;initServerI18next(config)&lt;/code&gt; stores the configuration, call it once at module scope&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getResources(i18n)&lt;/code&gt; serializes the loaded translations so the client can hydrate without re-fetching&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;I18nProvider&lt;/code&gt; creates a client-side i18next instance hydrated with those resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Server Components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getT()&lt;/code&gt; reads the language from the proxy-set header, loads the requested namespace if needed, and returns a namespace-typed &lt;code&gt;t&lt;/code&gt; function plus the resolved &lt;code&gt;lng&lt;/code&gt;. No prop drilling, no manual language passing.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;generateMetadata&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta_title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;code&gt;Trans&lt;/code&gt; component in Server Components, use &lt;code&gt;react-i18next/TransWithoutContext&lt;/code&gt; and pass both &lt;code&gt;t&lt;/code&gt; and &lt;code&gt;i18n&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Trans&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-i18next/TransWithoutContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Trans&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;i18nKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Welcome to &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;next-i18next&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Trans&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Client Components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click_me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useT&lt;/code&gt; reads the language from URL params (&lt;code&gt;[lng]&lt;/code&gt; or &lt;code&gt;[locale]&lt;/code&gt;) and keeps the i18next instance in sync. In no-locale-path mode (where there are no URL params), it uses the language set by &lt;code&gt;I18nProvider&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  No-Locale-Path Mode
&lt;/h2&gt;

&lt;p&gt;Not every project wants &lt;code&gt;/en/about&lt;/code&gt; and &lt;code&gt;/de/about&lt;/code&gt;. Some prefer clean URLs with cookie-based language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;I18nConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;localeInPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resourceLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./app/i18n/locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Routes live directly under &lt;code&gt;app/&lt;/code&gt; (no &lt;code&gt;[lng]&lt;/code&gt; segment). Language switching uses the &lt;code&gt;useChangeLanguage&lt;/code&gt; hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useChangeLanguage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LanguageSwitcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changeLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChangeLanguage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;changeLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Deutsch&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets the cookie, updates the i18next instance, and triggers a server re-render — all in one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed Router: App Router + Pages Router Together
&lt;/h2&gt;

&lt;p&gt;This is where v16 really shines. Many real-world projects have an existing Pages Router app and want to start building new features with the App Router, without rewriting everything.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;basePath&lt;/code&gt; option scopes the proxy to a specific URL prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// i18n.config.ts (App Router)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;I18nConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resourceLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./public/locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// proxy.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createProxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18nConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./i18n.config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i18nConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app-router/:path*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy only handles &lt;code&gt;/app-router/*&lt;/code&gt; routes. Pages Router pages at &lt;code&gt;/&lt;/code&gt; continue using Next.js built-in i18n routing with &lt;code&gt;appWithTranslation&lt;/code&gt; and &lt;code&gt;serverSideTranslations&lt;/code&gt; from &lt;code&gt;next-i18next/pages&lt;/code&gt;. Both routers share the same translation files from &lt;code&gt;public/locales/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pages Router (Unchanged)
&lt;/h2&gt;

&lt;p&gt;If you're on the Pages Router and upgrading from v15, the only change is the import path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before (v15)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;appWithTranslation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTranslation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serverSideTranslations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/serverSideTranslations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// After (v16)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;appWithTranslation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTranslation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/pages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serverSideTranslations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/pages/serverSideTranslations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else — &lt;code&gt;next-i18next.config.js&lt;/code&gt;, &lt;code&gt;getStaticProps&lt;/code&gt;, &lt;code&gt;getServerSideProps&lt;/code&gt;, custom backends — works exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Backends
&lt;/h2&gt;

&lt;p&gt;next-i18next supports any i18next backend plugin. When you provide a custom backend via the &lt;code&gt;use&lt;/code&gt; option, the default resource loader is skipped automatically.&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;server side&lt;/strong&gt;, custom backends work through the shared singleton instance, translations are fetched once and cached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HttpBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-http-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;HttpBackend&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;i18nextOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;loadPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.example.com/locales/{{lng}}/{{ns}}.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the &lt;strong&gt;client side&lt;/strong&gt;, pass backends through &lt;code&gt;I18nProvider&lt;/code&gt;. Since backend classes are functions (not serializable), import them in a &lt;code&gt;'use client'&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;I18nProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HttpBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-http-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChainedBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-chained-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LocalStorageBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-localstorage-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;I18nProviderWithBackend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supportedLngs&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;I18nProvider&lt;/span&gt;
      &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;supportedLngs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ChainedBackend&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;i18nextOptions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;backends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;LocalStorageBackend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpBackend&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;backendOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expirationTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour localStorage cache&lt;/span&gt;
            &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="c1"&gt;// HttpBackend defaults&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;I18nProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you server-rendered translations at initial load, with client-side lazy-loading of additional namespaces through the chained backend. The &lt;a href="https://github.com/i18next/i18next-http-backend/tree/master/example/next" rel="noopener noreferrer"&gt;i18next-http-backend example&lt;/a&gt; demonstrates this pattern with both App Router and Pages Router in a single project.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on Serverless Environments
&lt;/h2&gt;

&lt;p&gt;On the server, next-i18next uses a &lt;strong&gt;module-level singleton&lt;/strong&gt; i18next instance. Translations are loaded once and reused across all subsequent requests within the same process — great for performance, and custom backends like &lt;code&gt;i18next-http-backend&lt;/code&gt; or &lt;code&gt;i18next-locize-backend&lt;/code&gt; benefit from this since they don't re-fetch on every request.&lt;/p&gt;

&lt;p&gt;However, in &lt;strong&gt;serverless environments&lt;/strong&gt; (Vercel Serverless Functions, AWS Lambda, Google Cloud Functions, etc.), the module-level cache only lives as long as the warm function instance. Each cold start re-initializes the singleton and re-fetches translations.&lt;/p&gt;

&lt;p&gt;For serverless deployments, we recommend one of these approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Bundle translations at build time&lt;/strong&gt; (recommended for most projects):&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;resourceLoader&lt;/code&gt; with dynamic imports: translations are bundled into the deployment artifact at build time, so there's zero runtime fetching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resourceLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./app/i18n/locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Download translations in CI/CD:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Download translations from your &lt;a href="https://www.locize.com/blog/tms" rel="noopener noreferrer"&gt;TMS&lt;/a&gt; before building (e.g., via &lt;a href="https://github.com/locize/locize-cli" rel="noopener noreferrer"&gt;locize-cli&lt;/a&gt; or an &lt;a href="https://www.locize.com/docs/integration/api#fetch-namespace-resources" rel="noopener noreferrer"&gt;API&lt;/a&gt;). This bundles them with your deployment and avoids runtime HTTP requests entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use HTTP backends with caching:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you must load translations at runtime, combine &lt;code&gt;i18next-http-backend&lt;/code&gt; with &lt;code&gt;i18next-chained-backend&lt;/code&gt; and &lt;code&gt;i18next-localstorage-backend&lt;/code&gt; on the client side for fast subsequent loads. On the server side, the singleton cache will keep translations warm as long as the function instance is alive.&lt;/p&gt;

&lt;p&gt;Avoid relying on HTTP-based backends as the &lt;strong&gt;sole&lt;/strong&gt; translation source in serverless, each cold start adds latency and a potential point of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.locize.com%2Fimg%2Fblog%2Fnext-i18next-v16%2Ftransform_your_localization_process_small.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.locize.com%2Fimg%2Fblog%2Fnext-i18next-v16%2Ftransform_your_localization_process_small.jpg" title="locize © inweso GmbH" alt="transform the localization process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect to an awesome &lt;a href="https://www.locize.com/blog/tms" rel="noopener noreferrer"&gt;translation management system&lt;/a&gt; and manage your translations outside of your code.&lt;/p&gt;

&lt;p&gt;Let's synchronize the translation files with &lt;a href="https://www.locize.com/" rel="noopener noreferrer"&gt;Locize&lt;/a&gt;. This can be done on-demand or on the CI server or before deploying the app.&lt;/p&gt;

&lt;h4&gt;
  
  
  What to do to reach this step:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;In Locize: signup at &lt;a href="https://www.locize.app/register" rel="noopener noreferrer"&gt;https://www.locize.app/register&lt;/a&gt; and &lt;a href="https://www.locize.com/docs/getting-started/create-a-user-account" rel="noopener noreferrer"&gt;login&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;In Locize: &lt;a href="https://www.locize.com/docs/getting-started/add-a-new-project" rel="noopener noreferrer"&gt;create a new project&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/locize/locize-cli" rel="noopener noreferrer"&gt;locize-cli&lt;/a&gt; (&lt;code&gt;npm i locize-cli&lt;/code&gt;) or use the Locize commands built into &lt;a href="https://github.com/i18next/i18next-cli" rel="noopener noreferrer"&gt;i18next-cli&lt;/a&gt; (&lt;code&gt;npm i -D i18next-cli&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In Locize: add all your additional languages (this can also be done via &lt;a href="https://www.locize.com/docs/integration/api#add-a-new-language-to-a-project" rel="noopener noreferrer"&gt;API&lt;/a&gt; or using the &lt;a href="https://github.com/i18next/next-i18next/blob/main/examples/app-router-simple/package.json#L11" rel="noopener noreferrer"&gt;migrate command&lt;/a&gt; of the locize-cli)&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Syncing translations
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;With &lt;a href="https://github.com/locize/locize-cli" rel="noopener noreferrer"&gt;locize-cli&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;locize download&lt;/code&gt; to download the published Locize translations to your local repository before bundling your app. Or use &lt;code&gt;locize sync&lt;/code&gt; to synchronize bidirectionally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With &lt;a href="https://github.com/i18next/i18next-cli" rel="noopener noreferrer"&gt;i18next-cli&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i18next-cli&lt;/code&gt; is a unified toolchain that goes beyond syncing — it also handles key extraction, type generation, locale synchronization, and linting. Its built-in Locize integration wraps &lt;code&gt;locize-cli&lt;/code&gt; commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli locize-download   &lt;span class="c"&gt;# download translations from Locize&lt;/span&gt;
npx i18next-cli locize-sync       &lt;span class="c"&gt;# upload/sync translations to Locize&lt;/span&gt;
npx i18next-cli locize-migrate    &lt;span class="c"&gt;# migrate local translations to Locize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Locize credentials are missing, it will guide you through an interactive setup.&lt;/p&gt;

&lt;p&gt;For projects that want to load translations directly from Locize at runtime, use &lt;a href="https://github.com/locize/i18next-locize-backend" rel="noopener noreferrer"&gt;i18next-locize-backend&lt;/a&gt; as a custom backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LocizeBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-locize-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;supportedLngs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;LocizeBackend&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;i18nextOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the server, the singleton instance caches translations, no re-fetching per request. On the client, combine with &lt;code&gt;i18next-chained-backend&lt;/code&gt; and &lt;code&gt;i18next-localstorage-backend&lt;/code&gt; for offline-first loading with automatic background refresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;next-i18next v16 is a single package that handles every Next.js i18n scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Router&lt;/strong&gt; with Server Components and Client Components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pages Router&lt;/strong&gt; with the familiar &lt;code&gt;appWithTranslation&lt;/code&gt; / &lt;code&gt;serverSideTranslations&lt;/code&gt; API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed setups&lt;/strong&gt; where both routers coexist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom backends&lt;/strong&gt; for loading from CDN, API, or &lt;a href="https://www.locize.com/" rel="noopener noreferrer"&gt;Locize&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locale-in-path&lt;/strong&gt; and &lt;strong&gt;no-locale-path&lt;/strong&gt; modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the complete code, check out the &lt;a href="https://github.com/i18next/next-i18next/tree/master/examples" rel="noopener noreferrer"&gt;examples on GitHub&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/i18next/next-i18next/tree/master/examples/app-router-simple" rel="noopener noreferrer"&gt;App Router with locale-in-path&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/i18next/next-i18next/tree/master/examples/app-router-no-locale-path" rel="noopener noreferrer"&gt;App Router without locale in path&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/i18next/next-i18next/tree/master/examples/mixed-routers" rel="noopener noreferrer"&gt;Mixed App Router + Pages Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/i18next/next-i18next/tree/master/examples/pages-router-simple" rel="noopener noreferrer"&gt;Pages Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/i18next/i18next-http-backend/tree/master/example/next" rel="noopener noreferrer"&gt;With i18next-http-backend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>i18next</category>
      <category>react</category>
    </item>
    <item>
      <title>npx i18next-cli instrument</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Wed, 04 Mar 2026 07:18:25 +0000</pubDate>
      <link>https://forem.com/adrai/npx-i18next-cli-instrument-4e57</link>
      <guid>https://forem.com/adrai/npx-i18next-cli-instrument-4e57</guid>
      <description>&lt;p&gt;Internationalizing a React application used to mean a tedious weekend of hunting down every hardcoded string, manually wrapping each one in a &lt;code&gt;t()&lt;/code&gt; call, injecting &lt;code&gt;useTranslation()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;The new &lt;strong&gt;&lt;code&gt;instrument&lt;/code&gt;&lt;/strong&gt; command in &lt;code&gt;i18next-cli&lt;/code&gt; changes that equation entirely. In this post, we'll take a React + Vite + TypeScript project — &lt;a href="https://github.com/locize/taskly" rel="noopener noreferrer"&gt;&lt;strong&gt;Taskly&lt;/strong&gt;&lt;/a&gt; — from zero i18n to a cloud-synced, multi-language application in just a few steps.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/aWZnZXwGg34"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  The Starting Point: A Standard React App
&lt;/h2&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DashboardPage.tsx (before)&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Here's your overview&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You have &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;activeCount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; tasks left to complete.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the "Before" state. Let's start the transformation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Instrument: Let the CLI Do the Heavy Lifting
&lt;/h2&gt;

&lt;p&gt;Instead of manual search-and-replace, we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli instrument
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no &lt;code&gt;i18next.config.ts&lt;/code&gt; exists, the CLI opens an interactive setup wizard first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the instrumentation begins. The tool performs a deep static analysis of your codebase and:&lt;/p&gt;

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

&lt;p&gt;After the run you'll see a summary like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✔ 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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/main.tsx — modified by the instrument command&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StrictMode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./i18n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;          &lt;span class="c1"&gt;// ← injected automatically&lt;/span&gt;

&lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/i18n.ts — generated by the instrument command&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initReactI18next&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;resourcesToBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-resources-to-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initReactI18next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resourcesToBackend&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../public/locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;returnEmptyString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — The Human Touch: Manual Adjustments
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ First-Step Tool:&lt;/strong&gt; The &lt;code&gt;instrument&lt;/code&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3&gt;
  
  
  Brand names and UI chrome
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;Sidebar.tsx&lt;/code&gt; for example, "Taskly" is a brand name — it must never be translated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* i18next-instrument-ignore */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brandName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Taskly&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dynamic labels mapped from data
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NAV_ITEMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// t('navLabel.dashboard', 'Dashboard')&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// i18next-instrument-ignore&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// Then in JSX, adjust to use the data-driven key:&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;navLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`navLabel.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language switcher — detected automatically
&lt;/h3&gt;

&lt;p&gt;The CLI detects patterns that change the application language and injects &lt;code&gt;i18n.changeLanguage()&lt;/code&gt; alongside them. In &lt;code&gt;SettingsPage.tsx&lt;/code&gt;, the original &lt;code&gt;onClick&lt;/code&gt; only called &lt;code&gt;updateSettings&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateSettings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;

&lt;span class="c1"&gt;// After — instrument detects the language-change pattern and injects:&lt;/span&gt;
&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changeLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nf"&gt;updateSettings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No manual change needed here — the CLI handled it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Extract: Generate Your Translation Files
&lt;/h2&gt;

&lt;p&gt;With the code instrumented, now run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli extract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This parses every source file, collects all &lt;code&gt;t()&lt;/code&gt; calls or &lt;code&gt;Trans&lt;/code&gt; component keys and their default values, and writes them into your locale files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;public/locales/en/translation.json&lt;/code&gt; now contains every key with its English default value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"heresYourOverview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Here's your overview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"highPriority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"High priority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"howAreYou"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How are you today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"navLabel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dashboard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Settings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a complete English baseline ready for the world.&lt;/p&gt;

&lt;p&gt;The other locale files have the same keys but empty values — ready to be translated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: TypeScript Definitions
&lt;/h3&gt;

&lt;p&gt;If you're using TypeScript, one extra command gives you fully typed translation keys — so typos in &lt;code&gt;t()&lt;/code&gt; calls and &lt;code&gt;Trans&lt;/code&gt; components become compile errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✔ TypeScript definitions generated successfully.
  ✓ Resources interface written to src/@types/resources.d.ts
  ✓ TypeScript definitions written to src/@types/i18next.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this after every &lt;code&gt;extract&lt;/code&gt; and your editor will autocomplete translation keys and flag any that don't exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Migrate: Push to Locize
&lt;/h2&gt;

&lt;p&gt;At this point, you have a fully extracted English baseline.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;Add your Locize credentials to &lt;code&gt;i18next.config.ts&lt;/code&gt; (you can also use environment variables or just follow the interactive prompt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// i18next.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;it&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/**/*.{js,jsx,ts,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public/locales/{{language}}/{{namespace}}.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;locize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOCIZE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then push everything to Locize in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli locize-migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If necessary, the CLI prompts for your credentials on the first run, offers to save them securely, then migrates all locale files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✔ 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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Automatic Translation:&lt;/strong&gt; If you have Locize's &lt;strong&gt;Machine / AI Translation&lt;/strong&gt; feature enabled, your target languages will be translated automatically after migration — asynchronously.&lt;br&gt;
&lt;br&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; In case the translated files are not ready yet, wait a moment after migrating and run &lt;code&gt;npx i18next-cli locize-download&lt;/code&gt; to pull those new translations back into your local project.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli locize-download
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5 — Iterate: Adding New Keys
&lt;/h2&gt;

&lt;p&gt;Development doesn't stop after the initial migration. When you add a new feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DashboardPage.tsx&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;howAreYou&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;How are you today?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just run two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli extract      &lt;span class="c"&gt;# picks up the new key locally&lt;/span&gt;
npx i18next-cli locize-sync  &lt;span class="c"&gt;# syncs new keys to Locize, downloads translations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Command reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;extract&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After every code change — keeps local JSON in sync with your source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;types&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After &lt;code&gt;extract&lt;/code&gt; — generates TypeScript definitions for type-safe translation keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;locize-migrate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once, for the initial bulk push of local resources to Locize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;locize-sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ongoing — two-way sync between local files and Locize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;locize-download&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull the latest translations from Locize without pushing changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 6 — Going Further: Dynamic Translation Loading
&lt;/h2&gt;

&lt;p&gt;Bundling JSON files works well, but &lt;code&gt;i18next-locize-backend&lt;/code&gt; lets you decouple your translations entirely from your deployment cycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;i18next-locize-backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/i18n.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initReactI18next&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LocizeBackend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-locize-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initReactI18next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LocizeBackend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;i18next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Step 7 — Power Features: &lt;code&gt;saveMissing&lt;/code&gt; and the InContext Editor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;saveMissing&lt;/code&gt; — Zero-friction key registration
&lt;/h3&gt;

&lt;p&gt;During development, enable &lt;code&gt;saveMissing&lt;/code&gt; to automatically push newly written keys to Locize the moment they appear in the running app — no &lt;code&gt;extract&lt;/code&gt; step needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LocizeBackend&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;saveMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// push unknown keys to Locize immediately&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pairs beautifully with Locize AI: as soon as a new key arrives, Locize translates it into every configured language automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  InContext Editor — Edit translations in place
&lt;/h3&gt;

&lt;p&gt;Add the &lt;a href="https://www.locize.com/docs/context#incontext" rel="noopener noreferrer"&gt;Locize InContext Editor&lt;/a&gt; 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;locize-lastused locize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LastUsed&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;locize-lastused&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locizePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;locize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;i18next&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LastUsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// tracks which keys are actually used in production&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locizePlugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// enables the InContext Editor&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h2&gt;
  
  
  The Full Journey at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary: The Workflow
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Commands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;instrument&lt;/code&gt; → &lt;code&gt;extract&lt;/code&gt; → &lt;code&gt;locize-migrate&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Daily Dev&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;extract&lt;/code&gt; → &lt;code&gt;locize-sync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live Updates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;i18next-locize-backend&lt;/code&gt; for instant publishing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By leveraging the &lt;code&gt;i18next-cli&lt;/code&gt;, we've turned what used to be a grueling manual task into a streamlined, automated pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to see if your project is ready?&lt;/strong&gt; Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx i18next-cli status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gives you an instant overview of your current i18n health.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Example app: &lt;a href="https://github.com/locize/taskly" rel="noopener noreferrer"&gt;github.com/locize/taskly&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;i18next-cli: &lt;a href="https://github.com/i18next/i18next-cli" rel="noopener noreferrer"&gt;github.com/i18next/i18next-cli&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Locize: &lt;a href="https://www.locize.com" rel="noopener noreferrer"&gt;locize.com&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Video: &lt;a href="https://youtu.be/aWZnZXwGg34" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>i18next</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>Finally: A Free Plan for the Official i18next Platform</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Sat, 21 Feb 2026 11:48:30 +0000</pubDate>
      <link>https://forem.com/adrai/finally-a-free-plan-for-the-official-i18next-platform-414d</link>
      <guid>https://forem.com/adrai/finally-a-free-plan-for-the-official-i18next-platform-414d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Stop manually editing JSON files. The "native" home for i18next is now free for hobbyists and side projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The "Manual JSON" Struggle is Real
&lt;/h3&gt;

&lt;p&gt;If you’ve ever built a multi-language app with &lt;a href="https://www.i18next.com/" rel="noopener noreferrer"&gt;i18next&lt;/a&gt;, you know the drill. You start with one &lt;code&gt;en/translation.json&lt;/code&gt; file. It’s fine. Then comes &lt;code&gt;de/translation.json&lt;/code&gt;. Then a teammate adds a key to one but forgets the other. Then a translator sends you an Excel sheet, and you spend your Friday night copy-pasting strings into nested objects.&lt;/p&gt;

&lt;p&gt;We created i18next in 2011 to solve the code side of internationalization. A few years later, we built &lt;strong&gt;Locize&lt;/strong&gt; to solve the &lt;em&gt;management&lt;/em&gt; side.&lt;/p&gt;

&lt;p&gt;Until today, Locize was mostly a "pay-as-you-go" service with a 14-day trial. But we heard you: &lt;strong&gt;"I want to use the official tool for my side projects, but I don't want a ticking clock on my trial."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We listened. Today, we’re launching the &lt;strong&gt;Locize Free Plan.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  What’s in the Free Plan?
&lt;/h3&gt;

&lt;p&gt;We wanted this to be actually useful, not just a "demo" tier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2,000 Words:&lt;/strong&gt; Plenty for a landing page, a SaaS MVP, or a personal blog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100,000 Downloads/mo:&lt;/strong&gt; Enough to handle a healthy amount of traffic via our CDN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Full "Native" Experience:&lt;/strong&gt; You get automatic missing key handling, the In-Context Editor, and runtime updates.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why this matters for the i18next ecosystem
&lt;/h3&gt;

&lt;p&gt;Transparency is one of our core values. We don't have venture capital funding. &lt;strong&gt;Locize is what funds the development of i18next.&lt;/strong&gt; When you use Locize — even on the free plan — you are helping us maintain the open-source libraries you use every day, like &lt;code&gt;i18next&lt;/code&gt;, &lt;code&gt;react-i18next&lt;/code&gt;, and &lt;code&gt;i18next-cli&lt;/code&gt;. It’s a sustainable cycle: the service funds the framework, and the framework makes the service better.&lt;/p&gt;




&lt;h3&gt;
  
  
  How to set it up (The 2-Minute Version)
&lt;/h3&gt;

&lt;p&gt;Integration is exactly what you'd expect from the creators of the framework. You just swap your local backend for the Locize backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initReactI18next&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Backend&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i18next-locize-backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;i18n&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initReactI18next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fallbackLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// only use in development!&lt;/span&gt;
      &lt;span class="na"&gt;referenceLng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// This allows you to send missing keys &lt;/span&gt;
    &lt;span class="c1"&gt;// from your code directly to Locize!&lt;/span&gt;
    &lt;span class="na"&gt;saveMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; 
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;saveMissing: true&lt;/code&gt;, you don't even have to type keys into the Locize UI. You just write &lt;code&gt;t('new_hero_title')&lt;/code&gt; in your React component, and the key appears in Locize automatically.&lt;/p&gt;




&lt;h3&gt;
  
  
  Growing beyond "Hobby" scale?
&lt;/h3&gt;

&lt;p&gt;For teams that need more, we’ve also introduced new &lt;strong&gt;Fixed Pricing Plans&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Our "pay-as-you-go" model is flexible but sometimes hard to budget for. Now, you can choose a fixed tier (Starter, Professional, or Enterprise) so you know exactly what your base cost is every month, with fair, graduated overages if you go viral.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's build together
&lt;/h3&gt;

&lt;p&gt;We’ve been maintaining i18next for over 15 years. This new pricing model is our way of making sure the "Native Home" for i18next is accessible to everyone — from the dev building their first portfolio to the enterprise scaling to hundreds of languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the new plans here:&lt;/strong&gt; &lt;a href="https://www.locize.com/pricing" rel="noopener noreferrer"&gt;locize.com/pricing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d love to hear what you think in the comments! What's the biggest pain point you have with i18n right now?&lt;/p&gt;

&lt;p&gt;PS. &lt;strong&gt;Locize has &lt;a href="https://www.locize.com/why-no-sales" rel="noopener noreferrer"&gt;no sales reps&lt;/a&gt;.&lt;/strong&gt; You can sign up, try it, and talk directly to the engineers. No "discovery calls," no sales pitches, just real support from the people who build the platform.&lt;/p&gt;

</description>
      <category>i18n</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Back to Our Roots: The All-New locize.com</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Mon, 19 Jan 2026 11:08:34 +0000</pubDate>
      <link>https://forem.com/adrai/back-to-our-roots-the-all-new-locizecom-11bg</link>
      <guid>https://forem.com/adrai/back-to-our-roots-the-all-new-locizecom-11bg</guid>
      <description>&lt;p&gt;We’ve been relatively quiet lately, but for a good reason. We weren’t just redesigning a website; we were rethinking how we present the tool we’ve built for you.&lt;/p&gt;

&lt;p&gt;We are launching the new &lt;strong&gt;&lt;a href="https://www.locize.com" rel="noopener noreferrer"&gt;locize.com&lt;/a&gt;&lt;/strong&gt; website.&lt;/p&gt;

&lt;p&gt;If you’ve been with us since the beginning, this update might feel familiar. We’ve stripped away the "marketing fluff" and returned to the core philosophy of our very first site: &lt;strong&gt;content-first, developer-focused, and radically transparent.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Less Noise, More Clarity
&lt;/h2&gt;

&lt;p&gt;In the world of SaaS, it’s easy to get lost in buzzwords. We decided to go the other way. The new website is more elegant and significantly easier to read, focusing on the information you actually need to get your job done.&lt;/p&gt;

&lt;p&gt;But it’s not just a visual change. We’ve rebuilt the engine under the hood to make the entire experience faster and more intuitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  A World-Class Documentation Experience
&lt;/h3&gt;

&lt;p&gt;Documentation is important for us. We’ve overhauled the &lt;strong&gt;&lt;a href="https://www.locize.com/docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/strong&gt; from the ground up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightning Fast Search:&lt;/strong&gt; Find the exact API call or configuration setting instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Readability:&lt;/strong&gt; A cleaner layout that stays out of your way while you code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better Blog Experience:&lt;/strong&gt; Our blog and tutorials now feature a refined reading style and a powerful new search to help you find localization best practices in seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Modern App for a Modern Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqx99a3u6q3kawfrcapl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqx99a3u6q3kawfrcapl.jpg" alt="project dashboard" width="800" height="449"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7h03353zs95m4sfubow.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7h03353zs95m4sfubow.jpg" alt="cat" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new screenshots you see across the new site aren't just "marketing assets" — they are a reflection of the major UI and UX improvements we’ve made to the &lt;strong&gt;Locize App&lt;/strong&gt; itself.&lt;/p&gt;

&lt;p&gt;We’ve updated the design of the platform to be more modern, faster, and easier to navigate. Whether you are managing complex multi-namespace architectures or a single small project, the interface is now more responsive and streamlined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you see on the website is what you get in the tool:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modernized UI:&lt;/strong&gt; A cleaner, high-contrast interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed Improvements:&lt;/strong&gt; Faster loading and save times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refined UX:&lt;/strong&gt; Fewer clicks to get to your most important tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New Power Features:&lt;/strong&gt; We've integrated &lt;a href="https://www.locize.com/docs/backups" rel="noopener noreferrer"&gt;automated backups&lt;/a&gt;, &lt;a href="https://www.locize.com/ai" rel="noopener noreferrer"&gt;simplified AI translation&lt;/a&gt;, advanced charts, and more user roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why We Changed
&lt;/h2&gt;

&lt;p&gt;We believe that a localization platform should be an invisible, high-performance part of your tech stack. It shouldn't require a sales deck to understand. By going back to our roots, we are doubling down on what made Locize the industry standard for i18next and modern localization: &lt;strong&gt;technical excellence without the overhead.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.i18next.com/misc/the-history-of-i18next" rel="noopener noreferrer"&gt;Reflecting on our journey&lt;/a&gt;, having created the foundation with &lt;a href="https://www.i18next.com" rel="noopener noreferrer"&gt;i18next&lt;/a&gt;, it has been a long road to what Locize is today. We want this journey to continue with you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore the new site, search the docs, and &lt;a href="https://www.locize.app/login" rel="noopener noreferrer"&gt;log in&lt;/a&gt; to the app to feel the difference.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>localization</category>
      <category>i18next</category>
      <category>design</category>
    </item>
    <item>
      <title>The Invisible Engine: How the Team Behind i18next is Securing the Future of Global Software</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Tue, 06 Jan 2026 07:10:55 +0000</pubDate>
      <link>https://forem.com/adrai/the-invisible-engine-how-the-team-behind-i18next-is-securing-the-future-of-global-software-26hl</link>
      <guid>https://forem.com/adrai/the-invisible-engine-how-the-team-behind-i18next-is-securing-the-future-of-global-software-26hl</guid>
      <description>&lt;p&gt;If you have ever used a localized web application — one that seamlessly switches from English to German to Japanese — there is a very high chance you were relying on &lt;a href="https://www.i18next.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;i18next&lt;/strong&gt;&lt;/a&gt;. It is the absolute standard for internationalization in the JavaScript ecosystem. But like many pillars of modern digital infrastructure, it faces the age-old open-source dilemma: how do you maintain, improve, and innovate on a massive free project without burning out?&lt;/p&gt;

&lt;p&gt;The answer unfortunately isn't donations; it's &lt;a href="https://www.locize.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Locize&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many developers don't realize that the creators of i18next are also the founders of Locize. This isn't just a coincidence — it's a symbiotic relationship designed to solve the sustainability crisis in open source. Locize isn't just a translation management system; it is also the financial backbone that ensures i18next remains well-maintained, free, and cutting-edge. Every new customer on Locize doesn’t just get a powerful localization platform; they directly secure the future of the open-source framework they rely on.&lt;/p&gt;

&lt;p&gt;And that future is looking faster and smarter than ever. The team has been busy shipping major updates to both sides of the ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust, Speed, and the New i18next-cli&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the open-source side, the developer experience just got a massive upgrade. The team recently launched the new &lt;strong&gt;i18next-cli&lt;/strong&gt;, a complete reimagining of the toolchain.&lt;/p&gt;

&lt;p&gt;Moved away from the slower, legacy extraction tools, the new CLI is built on &lt;strong&gt;SWC&lt;/strong&gt; (a super-fast Rust-based compiler). The result? Operations that used to take minutes now take seconds. It’s a unified tool that handles key extraction, code linting, and locale syncing without the configuration headaches of the past. It represents exactly what commercial backing allows an open-source team to do: take the time to rebuild foundational tools to be performant and modern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI That Just Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the commercial side, Locize is tackling the biggest buzzword of the year — AI — but in a way that actually respects a developer's time.&lt;/p&gt;

&lt;p&gt;With the new &lt;strong&gt;built-in Locize AI service&lt;/strong&gt;, the friction of machine translation is effectively gone. Previously, integrating AI meant hunting for API keys from third-party providers (OpenAI, Google, etc.), configuring billing, and worrying about rate limits. Locize has abstracted all of that away. You no longer need your own API key; the service is integrated directly into the platform, allowing teams to translate content instantly with context-aware AI. It’s "click and go" localization, designed to let developers focus on code, not vendor management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Next Step: Killing the Download Cost&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The team isn’t stopping there. In a move to further align their incentives with their customers', Locize has &lt;a href="https://www.locize.com/blog/bunny-cdn" rel="noopener noreferrer"&gt;just launched&lt;/a&gt; an &lt;strong&gt;alternative “Standard” CDN&lt;/strong&gt; type as opposite to the current “Pro” CDN.&lt;/p&gt;

&lt;p&gt;This new infrastructure is available to customers free of charge. This effectively eliminates download costs for translation files, removing the "tax" on scaling your application to more users. It’s a bold move that doubles down on the philosophy that your localization platform should empower your growth, not penalize it.&lt;/p&gt;

&lt;p&gt;Additionally, for those just starting out, the team is hinting at upcoming changes designed to lower the cost barrier even further for entry-level projects. While the full details are still under wraps, it signals a commitment to accessibility for projects of all sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Sustainable Ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In an industry where open-source maintainers often step away due to lack of support, the i18next and Locize model stands out. It offers a clear path forward: build incredible tools that solve real problems, and use that success to fuel the open-source code that runs the world.&lt;/p&gt;

&lt;p&gt;For developers and CTOs, the message is simple. When you choose Locize, you aren't just buying a service. You’re investing in the longevity of the tools you use every day.&lt;/p&gt;

</description>
      <category>i18next</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>software</category>
    </item>
    <item>
      <title>Supercharge Your GitBook Docs with Live, Real-Time Polls</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Tue, 04 Nov 2025 10:05:00 +0000</pubDate>
      <link>https://forem.com/adrai/supercharge-your-gitbook-docs-with-live-real-time-polls-4jh8</link>
      <guid>https://forem.com/adrai/supercharge-your-gitbook-docs-with-live-real-time-polls-4jh8</guid>
      <description>&lt;p&gt;Technical documentation is often a one-way street: you write it, and users read it. But what if your documentation could be interactive? What if you could gather feedback, survey your readers, and make your content a living, collaborative space?&lt;/p&gt;

&lt;p&gt;Today, we're excited to bridge that gap by launching the &lt;a href="https://app.gitbook.com/integrations/vaultrice-voting-widget" rel="noopener noreferrer"&gt;&lt;strong&gt;Vaultrice Voting Integration for GitBook&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;You can now add beautiful, real-time, and interactive polls directly into any GitBook page. It's the perfect way to engage your readers and gather valuable feedback right where they are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xw842ifx4u4kotayuw1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xw842ifx4u4kotayuw1.gif" alt="a user voting in a live GitBook page and seeing the results update instantly" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From Static Pages to Interactive Experiences
&lt;/h2&gt;

&lt;p&gt;The Vaultrice integration adds a new "Vaultrice Voting Widget" block to your GitBook editor. With just a few clicks, you can create and configure a poll without ever leaving GitBook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklpj5yw68yje9miy1huf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklpj5yw68yje9miy1huf.png" alt="image showing the GitBook editor, a user adding the " title="" width="760" height="1482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This integration is powered by our &lt;a href="https://github.com/vaultrice/react-components" rel="noopener noreferrer"&gt;&lt;code&gt;@vaultrice/react-components&lt;/code&gt;&lt;/a&gt; library, which handles all the complexity of real-time data synchronization. When a user votes, the results are securely stored in Vaultrice and updated instantly for every other reader across the globe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Use Cases
&lt;/h2&gt;

&lt;p&gt;This opens up a world of possibilities for your documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gather feedback&lt;/strong&gt; on a new feature proposal right on the doc page.&lt;/li&gt;
&lt;li&gt;Let users &lt;strong&gt;vote on which tutorial&lt;/strong&gt; you should write next.&lt;/li&gt;
&lt;li&gt;Quickly &lt;strong&gt;survey readers&lt;/strong&gt; about the clarity of an article.&lt;/li&gt;
&lt;li&gt;Run &lt;strong&gt;engaging, fun polls&lt;/strong&gt; for internal team documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started in 3 Simple Steps
&lt;/h2&gt;

&lt;p&gt;You can get your first poll running in under five minutes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/docs/quickstart"&gt;&lt;strong&gt;Get Your Credentials&lt;/strong&gt;&lt;/a&gt;: Create a free &lt;a href="https://www.vaultrice.app/register" rel="noopener noreferrer"&gt;Vaultrice account&lt;/a&gt; to get your &lt;code&gt;projectId&lt;/code&gt;, &lt;code&gt;apiKey&lt;/code&gt;, and &lt;code&gt;apiSecret&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install the Integration&lt;/strong&gt;: Find and install the "Vaultrice Voting Integration" from &lt;a href="https://app.gitbook.com/integrations/vaultrice-voting-widget" rel="noopener noreferrer"&gt;here&lt;/a&gt; - and configure it.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fqwb485epflvc0uz0ri.png" width="800" height="612"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the Block&lt;/strong&gt;: Add the "Vaultrice Voting Widget" block to any page, paste in your credentials, and write your poll questions.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falm5allql43jjptalee6.png" width="800" height="202"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! Your interactive poll is now live.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note on Security
&lt;/h3&gt;

&lt;p&gt;For enhanced security in a public-facing context like documentation, we highly recommend locking down your API key. Using &lt;strong&gt;Origin Restriction&lt;/strong&gt;, you can ensure your key can only be used for requests coming from GitBook's integration service.&lt;/p&gt;

&lt;p&gt;Simply add &lt;code&gt;https://integrations.gitbook.com&lt;/code&gt; as an allowed origin in your Vaultrice project dashboard. This is a powerful way to secure your credentials, and you can learn more about it in our &lt;a href="https://dev.to/docs/security#sf1"&gt;Security Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faxee6pak986nv7qeoizt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faxee6pak986nv7qeoizt.png" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  See It in Action: The i18next Documentation
&lt;/h2&gt;

&lt;p&gt;We're thrilled that the popular internationalization framework &lt;strong&gt;i18next&lt;/strong&gt; is one of the first to adopt this new integration. They are actively using it to gather feedback from their vast community of developers.&lt;/p&gt;

&lt;p&gt;You can see the poll live in their documentation and cast a vote yourself!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See the live poll on &lt;a href="https://www.i18next.com" rel="noopener noreferrer"&gt;i18next.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We believe that documentation should be a living, breathing part of your product's ecosystem. By adding a layer of real-time interactivity, you can get closer to your users and build better products.&lt;/p&gt;

&lt;p&gt;We can't wait to see the creative ways you use this to engage your readers.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>documentation</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>From API Keys to E2EE: A Practical Guide to Securing Your Real-Time App</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Wed, 22 Oct 2025 10:11:00 +0000</pubDate>
      <link>https://forem.com/adrai/from-api-keys-to-e2ee-a-practical-guide-to-securing-your-real-time-app-115m</link>
      <guid>https://forem.com/adrai/from-api-keys-to-e2ee-a-practical-guide-to-securing-your-real-time-app-115m</guid>
      <description>&lt;p&gt;When you're building a new real-time feature, the initial focus is on making it work. You spin up a prototype, get the data flowing, and celebrate that first magical moment when two browser tabs update simultaneously. But before you ship to production, a critical question arises: &lt;strong&gt;Is this secure?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Security in real-time applications is non-trivial. How do you protect your API credentials on the client-side? How do you ensure one user can't access another user's data? How do you handle sensitive information with maximum confidentiality?&lt;/p&gt;

&lt;p&gt;At Vaultrice, we believe security shouldn't be an afterthought. It should be a series of layers you can apply as your application grows in complexity. In this guide, we'll walk through a practical, progressive journey of securing a real-time React application, moving from a basic prototype to a production-ready, end-to-end encrypted system using Vaultrice's security model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Scenario: A Collaborative "To-Do" List&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Imagine we're building a simple collaborative to-do list app. Multiple users can join a shared list and add or remove items.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prototype Goal:&lt;/strong&gt; Get a basic real-time list working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Goal:&lt;/strong&gt; Ensure only authorized users can access their own lists, their identity is verified, and the to-do items (which might be sensitive) are kept private.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stage 1: The Prototype (Security Level 0) 🐣
&lt;/h2&gt;

&lt;p&gt;At the start, we just want to get things working. We'll use &lt;strong&gt;Direct Authentication&lt;/strong&gt; with an API key and secret.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NonLocalStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vaultrice/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// For a quick prototype, we place credentials directly.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sharedTodoList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NonLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;our-first-todo-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// We can now read and write to the list.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sharedTodoList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Build prototype&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security Analysis (Level 0):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What's working:&lt;/strong&gt; All communication is automatically encrypted in transit with TLS (HTTPS), and all data is encrypted at rest by default on the backend. This is &lt;strong&gt;SF 0: Transport + Default At-Rest Encryption&lt;/strong&gt; and it's always active on all plans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Risk:&lt;/strong&gt; Our &lt;code&gt;apiSecret&lt;/code&gt; is exposed in the client-side code. A malicious actor could steal these credentials and use them from anywhere to access our data. This is fine for a local demo, but unacceptable for production.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stage 2: Production-Ready (Security Levels 1 &amp;amp; 2) 🚀
&lt;/h2&gt;

&lt;p&gt;Now, let's take our app to production. We need to lock down our credentials, control data access, and verify user identities.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Upgrading Authentication: Secure Access Tokens&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First and foremost, we must remove the long-lived &lt;code&gt;apiSecret&lt;/code&gt; from our client-side code. The gold standard for this is using &lt;a href="https://www.vaultrice.com/docs/security/#authentication-methods" rel="noopener noreferrer"&gt;&lt;strong&gt;Backend-Issued Access Tokens&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Your own backend has a secure endpoint that uses your &lt;code&gt;apiKey&lt;/code&gt; and &lt;code&gt;apiSecret&lt;/code&gt; to generate a short-lived access token for the client. The client then initializes the SDK with this temporary token, never knowing the secret.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; Create a serverless function or API endpoint on your trusted backend.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On your trusted backend (e.g., /api/vaultrice-token)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;retrieveAccessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vaultrice/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateTokenForClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retrieveAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// Kept securely on the server&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Kept securely on the server&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Pass the client's origin if using Origin Restrictions&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Client-Side Implementation:&lt;/strong&gt; Your client code is now much more secure. You can use the &lt;code&gt;getAccessToken&lt;/code&gt; provider for automatic, hands-free token management.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your React App&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NonLocalStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vaultrice/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secureTodoList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NonLocalStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// The SDK will call this function automatically to get and refresh tokens&lt;/span&gt;
  &lt;span class="na"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vaultrice-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to fetch token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-object-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; You've achieved maximum credential security. Your &lt;code&gt;apiSecret&lt;/code&gt; is never exposed to the browser, and the client operates on temporary, revokable tokens.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Layer 1: Perimeter Defense with Origin Restrictions (SF 1)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, we prevent our API key from being used on any other website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; In the Vaultrice dashboard, we edit our API key and add our app's domain (e.g., &lt;code&gt;https://mytodoapp.com&lt;/code&gt;) to the &lt;strong&gt;Origin Restrictions&lt;/strong&gt; list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; The Vaultrice backend will now reject any request made with this key that doesn't originate from our domain. This is an essential first step for all production applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Layer 2: Data Access Control with ID Signatures (SF 2)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We must ensure a user can only access &lt;em&gt;their own&lt;/em&gt; to-do list, not someone else's. We can't trust the client to be honest about which list it wants to access.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; We enable &lt;strong&gt;Object ID Signature Verification&lt;/strong&gt; in our project's Class settings. Then, we create an endpoint on our backend that signs the &lt;code&gt;objectId&lt;/code&gt; for a logged-in user.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On your trusted backend server&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSignedTodoListId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objectId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`todo-list-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// You sign the ID with your private key&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signWithYourPrivateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;objectId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Client-Side Implementation:&lt;/strong&gt; The client now fetches this signed ID from the backend before initializing the SDK.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fetch the authorized object ID and signature from our backend&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/get-todo-list-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Vaultrice will now verify this signature on every request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userTodoList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NonLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;objectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;idSignature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; A user can no longer guess another user's &lt;code&gt;objectId&lt;/code&gt; and access their data. The Vaultrice API will reject any request where the signature doesn't match the ID.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Layer 3: Optional Server-Side Hardening (SF 3)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For an extra layer of protection on the server, you can enable &lt;strong&gt;Automatic At-Rest Encryption&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is:&lt;/strong&gt; This feature transparently encrypts data values on the Vaultrice server with a unique key before they are stored in the database. This protects against a direct breach of the underlying database infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; In your Class settings, simply enable the "Additional At-Rest Encryption" checkbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Enhanced server-side data protection with &lt;strong&gt;no code changes required&lt;/strong&gt; in your SDK implementation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stage 3: Maximum Confidentiality (Security Level 3) 🛡️
&lt;/h2&gt;

&lt;p&gt;Our app is now secure for general use. But what if our to-do items contain highly sensitive information? For this, we need &lt;strong&gt;Security Level 3&lt;/strong&gt; and &lt;strong&gt;SF 4: End-to-End Encryption (E2EE)&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; We enable E2EE by providing a &lt;code&gt;passphrase&lt;/code&gt; during SDK initialization. This passphrase is known only to the client and is never sent to the server.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NonLocalStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vaultrice/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sensitiveList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NonLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signedObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// from Stage 2&lt;/span&gt;
  &lt;span class="na"&gt;idSignature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// from Stage 2&lt;/span&gt;
  &lt;span class="na"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-secret-passphrase-never-sent-to-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// We must fetch the salt from the server to derive the encryption key&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sensitiveList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEncryptionSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// This data is encrypted ON THE DEVICE before being sent.&lt;/span&gt;
&lt;span class="c1"&gt;// Not even Vaultrice can read its content.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sensitiveList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Top secret plan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; The content of our to-do list is now fully confidential. Vaultrice only stores ciphertext, providing the highest level of privacy. It's important to note that server-side atomic operations are incompatible with E2EE, as the server cannot decrypt the data to operate on it. So those operations are not E2EE encrypted.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Security is a journey, not a destination. By starting with a simple prototype and progressively adding layers of protection as needed, you can move to production with confidence. Vaultrice's &lt;a href="https://www.vaultrice.com/docs/security" rel="noopener noreferrer"&gt;layered security model&lt;/a&gt; provides a clear path to secure your real-time application, from basic transport encryption all the way to zero-knowledge, end-to-end encrypted collaboration.&lt;/p&gt;

&lt;p&gt;Choose the &lt;a href="https://www.vaultrice.com/docs/security" rel="noopener noreferrer"&gt;security level&lt;/a&gt; that matches your needs and start building with confidence on the &lt;strong&gt;&lt;a href="https://www.vaultrice.com/pricing" rel="noopener noreferrer"&gt;Vaultrice free tier&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Add a Live "Who's Online" List to Your React App in 5 Minutes</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Thu, 09 Oct 2025 07:11:00 +0000</pubDate>
      <link>https://forem.com/adrai/how-to-add-a-live-whos-online-list-to-your-react-app-in-5-minutes-2ail</link>
      <guid>https://forem.com/adrai/how-to-add-a-live-whos-online-list-to-your-react-app-in-5-minutes-2ail</guid>
      <description>&lt;p&gt;Modern collaborative apps have a magical feature: that little pile of avatars in the corner showing you who else is on the team and who's currently active. In tools like Figma or Slack, this &lt;strong&gt;rich presence&lt;/strong&gt; transforms a simple user list into a dynamic team dashboard.&lt;/p&gt;

&lt;p&gt;Building this feature is traditionally complex. It requires a backend to merge a static list of team members with real-time connection data, manage sessions, and broadcast updates.&lt;/p&gt;

&lt;p&gt;What if you could do it all with a single, powerful React component?&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll go beyond a basic "who's online" list and build a rich team presence indicator in minutes, showcasing the advanced features of the Vaultrice &lt;code&gt;&amp;lt;Presence /&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Goal: A Rich Team Presence Component&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We will build a presence indicator that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shows a predefined list of team members.&lt;/li&gt;
&lt;li&gt;Clearly indicates who is &lt;strong&gt;online&lt;/strong&gt; (full color) and &lt;strong&gt;offline&lt;/strong&gt; (faded).&lt;/li&gt;
&lt;li&gt;Merges real-time connection data with the predefined user list.&lt;/li&gt;
&lt;li&gt;Is fully customizable to match our brand.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1: The "Backend" &amp;amp; Setup (90 seconds)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First, let's get our project ready.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Get Credentials:&lt;/strong&gt; Sign up for a free account at the &lt;a href="https://www.vaultrice.app/register" rel="noopener noreferrer"&gt;Vaultrice App&lt;/a&gt; to get your &lt;code&gt;projectId&lt;/code&gt;, &lt;code&gt;apiKey&lt;/code&gt;, and &lt;code&gt;apiSecret&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install the Library:&lt;/strong&gt; Install the pre-built Vaultrice React components.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @vaultrice/react-components
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2: Define Your Team &amp;amp; Add the Component (3.5 minutes)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Presence /&amp;gt;&lt;/code&gt; component can take a list of &lt;code&gt;predefinedUsers&lt;/code&gt;. It uses this list as the "source of truth" and merges it with live connection data.&lt;/p&gt;

&lt;p&gt;Let's create our &lt;code&gt;TeamPresence.tsx&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Presence&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vaultrice/react-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JoinedConnection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vaultrice/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// in production you may want to pass the credentials vie environment variables, and maybe also use the access token mechanism: https://www.vaultrice.com/docs/security#authentication-methods&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jane-456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Smith&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://i.pravatar.cc/150?u=jane-smith&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Developer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// This is your predefined list of all team members for this project&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teamMembers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Team Lead&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jane-456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Smith&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Developer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bob-789&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bob Wilson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Designer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TeamPresence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"presence-container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Project Team:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Presence&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"project-alpha-presence"&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;predefinedUsers&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;teamMembers&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;showOfflineUsers&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;maxAvatars&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this single component, you've implemented a complete team presence system.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What We Just Did (The Advanced Features Explained)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is far more than a simple list of who's online. Let's break down a few props we used from the &lt;code&gt;&amp;lt;Presence /&amp;gt;&lt;/code&gt; component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;predefinedUsers={teamMembers}&lt;/code&gt;:&lt;/strong&gt; We provided a static array of our team. The component now knows about all team members, not just the ones who are currently connected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;showOfflineUsers={true}&lt;/code&gt;:&lt;/strong&gt; This tells the component to display users from the &lt;code&gt;predefinedUsers&lt;/code&gt; list even if they are not currently connected. The component renders them with a different style (which we control in our CSS) to indicate their offline status.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Try It Yourself: Interactive Demo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Don't just take our word for it. We've published a live Storybook demo of the &lt;code&gt;&amp;lt;Presence /&amp;gt;&lt;/code&gt; component with all the features we've discussed. You can change the current user, toggle offline visibility, and see how the component responds in real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://vaultrice.github.io/react-components/?path=/story/vaultrice-presence--with-predefined-users" rel="noopener noreferrer"&gt;➡️ Open the Interactive Storybook Demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4e3mmrr4kruz5l6xk02i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4e3mmrr4kruz5l6xk02i.jpg" alt="An image of the interactive Storybook demo for the Vaultrice Presence component." width="456" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Presence /&amp;gt;&lt;/code&gt; component is a powerful tool that goes far beyond a simple online indicator. By leveraging its advanced features like predefined users and custom rendering, you can build a rich, informative, and polished team presence system in minutes. This is a perfect example of how a high-level component can abstract away immense backend complexity and deliver a fantastic developer experience.&lt;/p&gt;

&lt;p&gt;Stop building presence logic from scratch. &lt;strong&gt;&lt;a href="https://www.vaultrice.com/pricing" rel="noopener noreferrer"&gt;Add a rich team presence indicator to your app today with the Vaultrice free tier.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
