<?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>DEV Community: Adriano Raiano</title>
    <description>The latest articles on DEV Community by Adriano Raiano (@adrai).</description>
    <link>https://dev.to/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>DEV Community: Adriano Raiano</title>
      <link>https://dev.to/adrai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adrai"/>
    <language>en</language>
    <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://dev.to/adrai/why-we-added-a-console-notice-to-i18next-and-why-we-removed-it-4j64</link>
      <guid>https://dev.to/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://dev.to/adrai/next-i18next-v16-app-router-pages-router-and-everything-in-between-4l5a</link>
      <guid>https://dev.to/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://dev.to/adrai/npx-i18next-cli-instrument-4e57</link>
      <guid>https://dev.to/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://dev.to/adrai/finally-a-free-plan-for-the-official-i18next-platform-414d</link>
      <guid>https://dev.to/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://dev.to/adrai/back-to-our-roots-the-all-new-locizecom-11bg</link>
      <guid>https://dev.to/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://dev.to/adrai/the-invisible-engine-how-the-team-behind-i18next-is-securing-the-future-of-global-software-26hl</link>
      <guid>https://dev.to/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://dev.to/adrai/supercharge-your-gitbook-docs-with-live-real-time-polls-4jh8</link>
      <guid>https://dev.to/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://dev.to/adrai/from-api-keys-to-e2ee-a-practical-guide-to-securing-your-real-time-app-115m</link>
      <guid>https://dev.to/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://dev.to/adrai/how-to-add-a-live-whos-online-list-to-your-react-app-in-5-minutes-2ail</link>
      <guid>https://dev.to/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>
    <item>
      <title>Supercharge Your i18next Workflow with the All-New i18next-cli</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Fri, 26 Sep 2025 06:09:00 +0000</pubDate>
      <link>https://dev.to/adrai/supercharge-your-i18next-workflow-with-the-all-new-i18next-cli-341p</link>
      <guid>https://dev.to/adrai/supercharge-your-i18next-workflow-with-the-all-new-i18next-cli-341p</guid>
      <description>&lt;p&gt;The i18next ecosystem has been at the heart of web localization for over a decade. It was born in a world of jQuery and has evolved alongside the community through the rise of modern frameworks like React, Vue, and Svelte. As our applications have grown in complexity, so have the challenges of managing translations.&lt;/p&gt;

&lt;p&gt;For years, the community has relied on tools like &lt;code&gt;i18next-parser&lt;/code&gt; for the critical task of extracting translation keys. These tools were pioneers and have served us well. But as codebases swelled into the hundreds of thousands of lines and modern JavaScript syntax became the norm, we began to feel the friction. Slow extraction times, struggles with TypeScript and JSX, and the need for a patchwork of different tools to create a complete workflow became common pain points.&lt;/p&gt;

&lt;p&gt;It was time for a change.&lt;/p&gt;

&lt;p&gt;Today, we're thrilled to introduce &lt;strong&gt;&lt;code&gt;i18next-cli&lt;/code&gt;&lt;/strong&gt;, the official, next-generation, all-in-one toolkit for the i18next ecosystem. It’s a complete reimagining of the developer toolchain, built with three core principles in mind: &lt;strong&gt;raw performance&lt;/strong&gt;, &lt;strong&gt;deep code intelligence&lt;/strong&gt;, and a &lt;strong&gt;unified developer experience&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Want to try it without reading the complete announcement? 😉
&lt;/h2&gt;

&lt;p&gt;We designed &lt;code&gt;i18next-cli&lt;/code&gt; to provide immediate value. You can get an instant analysis of your project's translation status &lt;strong&gt;without any configuration&lt;/strong&gt;. Just run this command in your repository's root:&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 heuristically detects your project structure and gives you a report in seconds.&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%2F1jz8acl5mcnbh5wm4z75.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%2F1jz8acl5mcnbh5wm4z75.jpg" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For those using &lt;code&gt;i18next-parser&lt;/code&gt;, the transition is seamless. Our migration command automatically converts your legacy config file to the new format:&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 migrate-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters: A Better Workflow
&lt;/h2&gt;

&lt;p&gt;Before diving into the features, let's talk about the impact:&lt;/p&gt;

&lt;h3&gt;
  
  
  Blazing Fast, By Design
&lt;/h3&gt;

&lt;p&gt;Performance is not just a feature; it’s the foundation of a great developer experience. The single biggest bottleneck in older tools was the use of JavaScript-based parsers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i18next-cli&lt;/code&gt; solves this by being built on &lt;strong&gt;SWC&lt;/strong&gt;, a modern, Rust-based compiler. By leveraging native code, we've unlocked dramatic speed improvements. &lt;strong&gt;Workflows that once took over a minute to scan a large project now complete in under five seconds.&lt;/strong&gt; This isn't just an upgrade; it's a transformation of the development loop, making localization tasks feel instantaneous.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Tool to Rule Them All
&lt;/h3&gt;

&lt;p&gt;A complete localization workflow requires more than just extraction. &lt;code&gt;i18next-cli&lt;/code&gt; is a true &lt;strong&gt;toolkit&lt;/strong&gt;, unifying all essential functions into a single, cohesive CLI with one configuration file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;extract&lt;/code&gt;&lt;/strong&gt;: The core command to find and save keys with a powerful &lt;code&gt;--watch&lt;/code&gt; mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/strong&gt;: Keeps your language files synchronized, adding missing keys and removing unused ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;types&lt;/code&gt;&lt;/strong&gt;: Generates TypeScript definitions for ultimate type safety and autocompletion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lint&lt;/code&gt;&lt;/strong&gt;: Finds hardcoded strings in your code that should be translated, with smart heuristics to reduce false positives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;status&lt;/code&gt;&lt;/strong&gt;: Gives you a high-level dashboard of your project's translation health, with detailed, per-namespace reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  It Just Works: Advanced Pattern Detection
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;i18next-cli&lt;/code&gt; was built with a scope-aware analyzer that creates a semantic understanding of your code. This means it handles a wide range of modern patterns automatically, with no extra configuration needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;useTranslation&lt;/code&gt; with &lt;code&gt;keyPrefix&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
It understands that &lt;code&gt;keyPrefix&lt;/code&gt; modifies the keys used by &lt;code&gt;t&lt;/code&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="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="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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userProfile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// ✅ Correctly extracts 'userProfile.title' into the 'common' namespace&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&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="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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;Trans&amp;gt;&lt;/code&gt; Component with Complex Children:&lt;/strong&gt;&lt;br&gt;
Parsing the &lt;code&gt;&amp;lt;Trans&amp;gt;&lt;/code&gt; component is notoriously tricky. &lt;code&gt;i18next-cli&lt;/code&gt; handles it beautifully, correctly serializing nested components and even extracting keys from props &lt;em&gt;inside&lt;/em&gt; the component.&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;Trans&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="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WelcomeMessage&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="nc"&gt;Trans&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;"welcomeMessage"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Hello &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&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;userTitle&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;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&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;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;, welcome to our app!
    &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;p&gt;The CLI correctly understands this and extracts &lt;strong&gt;two&lt;/strong&gt; keys:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;welcomeMessage&lt;/code&gt;, with the default value: &lt;code&gt;"Hello &amp;lt;1&amp;gt;{{name}}&amp;lt;/1&amp;gt;, welcome to our app!"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;userTitle&lt;/code&gt; from the nested &lt;code&gt;title&lt;/code&gt; prop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Aliased and Destructured &lt;code&gt;t&lt;/code&gt; functions:&lt;/strong&gt;&lt;br&gt;
Whether you rename &lt;code&gt;t&lt;/code&gt; or pull it from an array, the toolkit follows along.&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;translate&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ns1&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;t2&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ns2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Extracts 'key1' into 'ns1'&lt;/span&gt;
&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Extracts 'key2' into 'ns2'&lt;/span&gt;
&lt;span class="nf"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key2&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;Key Fallbacks and &lt;code&gt;returnObjects&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
It understands advanced i18next features, extracting all static keys from a fallback array and preserving nested objects when you need 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="c1"&gt;// ✅ Extracts both 'key.primary' and 'key.fallback'&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;key.primary&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;key.fallback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Preserves the entire `countries` object in your JSON files&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;countries&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;returnObjects&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;h2&gt;
  
  
  🚀 Try it Now - Zero Config!
&lt;/h2&gt;

&lt;p&gt;We designed &lt;code&gt;i18next-cli&lt;/code&gt; to provide immediate value. For most projects, you can get an instant analysis of your translation status &lt;strong&gt;without any configuration&lt;/strong&gt;. It heuristically detects your project structure and gives you a report in seconds.&lt;/p&gt;

&lt;p&gt;Just run this command in your repository's root directory:&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;&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%2F3svx8jgilvfyewrb90rz.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%2F3svx8jgilvfyewrb90rz.jpg" alt="status detail" width="800" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration Made Easy
&lt;/h2&gt;

&lt;p&gt;For existing users of &lt;code&gt;i18next-parser&lt;/code&gt;, we've made the transition seamless. The toolkit includes a migration command that automatically converts your legacy config file to the new format.&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 migrate-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Taking Your Workflow to the Cloud with Locize
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;i18next-cli&lt;/code&gt; provides a best-in-class workflow for managing your local translation files in Git. As your project grows, however, managing translations across a team of developers, translators, and product managers can become a challenge.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;locize&lt;/strong&gt;, our translation management platform, is the natural next step. We built &lt;code&gt;i18next-cli&lt;/code&gt; to be the perfect bridge between local development and a professional, cloud-based localization workflow.&lt;/p&gt;

&lt;p&gt;The CLI is fully integrated with locize, allowing you to migrate your project and synchronize translations with simple commands (&lt;code&gt;locize-migrate&lt;/code&gt;, &lt;code&gt;locize-sync&lt;/code&gt;). It moves your translations out of JSON files and into a powerful platform built specifically for collaboration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started Today
&lt;/h2&gt;

&lt;p&gt;We believe &lt;code&gt;i18next-cli&lt;/code&gt; sets a new standard for localization tooling. It’s a reflection of our commitment to the i18next community and our mission to create a truly seamless developer experience.&lt;/p&gt;

&lt;p&gt;Check out the full documentation, advanced configuration, and plugin system on the &lt;a href="https://github.com/i18next/i18next-cli" rel="noopener noreferrer"&gt;&lt;strong&gt;official GitHub repository&lt;/strong&gt;&lt;/a&gt;. We can't wait to see how it speeds up your workflow.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cli</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The Art of the Key: A Definitive Guide to i18n Key Naming for Longevity and Sanity</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Tue, 23 Sep 2025 08:23:00 +0000</pubDate>
      <link>https://dev.to/adrai/the-art-of-the-key-a-definitive-guide-to-i18n-key-naming-for-longevity-and-sanity-1355</link>
      <guid>https://dev.to/adrai/the-art-of-the-key-a-definitive-guide-to-i18n-key-naming-for-longevity-and-sanity-1355</guid>
      <description>&lt;p&gt;Internationalization (&lt;a href="https://www.locize.com/blog/what-is-i18n" rel="noopener noreferrer"&gt;i18n&lt;/a&gt;) is the blueprint for global software. At its heart lies a deceptively simple element: the &lt;strong&gt;translation key&lt;/strong&gt;. Your team's approach to naming these keys isn't a minor detail—it's a foundational architectural decision. A poor strategy creates "i18n debt," a silent tax on every future feature that slows development and inflates costs. A great one ensures longevity and streamlines collaboration for years to come.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key is a Three-Party Contract 📜
&lt;/h2&gt;

&lt;p&gt;Think of an i18n key not as a variable, but as a stable, multi-party contract between three stakeholders:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Developers:&lt;/strong&gt; They need a predictable interface to retrieve text. The key should offer clues about a string's purpose, reducing the need to constantly jump between the codebase and translation files.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Translators:&lt;/strong&gt; The key is often their first and most important piece of context. A key like &lt;code&gt;userProfile.buttons.saveChanges&lt;/code&gt; is a beacon of clarity; &lt;code&gt;key_123&lt;/code&gt; is a black box that invites guesswork and errors.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Systems (TMS):&lt;/strong&gt; A Translation Management System uses the key as a unique, immutable ID to manage a string's entire history, screenshots, comments, and glossary terms. If a key changes, that contract is broken, the history is lost, and the string must be re-translated from scratch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A strategy that only serves one party will inevitably fail. What looks like a simple naming convention is actually a complex system design problem that must balance the needs of everyone involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Key Naming is a Critical Architectural Decision 🏗️
&lt;/h2&gt;

&lt;p&gt;Choosing your key strategy hastily is like building a house on a shaky foundation. The i18n debt you incur manifests in several painful ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance Brittleness:&lt;/strong&gt; When keys are tied to file paths, refactoring UI code becomes a nightmare. Developers become afraid to make changes for fear of breaking translations in a dozen languages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased Bugs:&lt;/strong&gt; Ambiguous or reused keys are a primary source of localization bugs. Using a single key for "Read" can lead to grammatical errors when the context shifts from a verb ("Read more") to a status ("Message Read").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration Friction:&lt;/strong&gt; A chaotic system forces translators to constantly ask developers for clarification, pulling them away from feature development and slowing down the entire localization cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Scalability:&lt;/strong&gt; As an application grows, a flat list of keys becomes unmanageable and kills performance. A scalable architecture requires a structured approach from day one to enable optimizations like code-splitting translations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Investing time to establish a robust strategy is a down payment on future agility, enabling your team to build and scale a global product efficiently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Philosophies of Key Naming
&lt;/h2&gt;

&lt;p&gt;Three dominant philosophies have emerged for naming keys. The choice between them is a fundamental decision about where to place the burden of complexity in your workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Structured (Semantic) Keys: The Path of Order
&lt;/h3&gt;

&lt;p&gt;This is the most robust and widely recommended approach. It involves creating abstract but descriptive identifiers that are decoupled from the source language text.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;t('userProfile.labels.firstName')&lt;/code&gt;, &lt;code&gt;t('errors.network.timeout')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;High Maintainability:&lt;/strong&gt; The English source text can be changed without touching the code, preserving all existing translations.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Provides Context:&lt;/strong&gt; A key like &lt;code&gt;checkout.buttons.confirmOrder&lt;/code&gt; tells translators its location and purpose.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Avoids Collisions:&lt;/strong&gt; A partial namespacing is built-in, preventing ambiguity. But can also be improved with real &lt;a href="https://www.locize.com/docs/namespaces" rel="noopener noreferrer"&gt;namespaces&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cons:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Increased Upfront Effort:&lt;/strong&gt; It's a "write-it-twice" workflow: create the key in code, then add its value in a JSON file.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Reduced In-Code Readability:&lt;/strong&gt; The code shows &lt;code&gt;t('userProfile.labels.firstName')&lt;/code&gt;, not the actual text.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;However, this readability drawback is being solved by innovations in the i18next ecosystem. The new &lt;a href="https://www.locize.com/blog/i18next-typescript-selector-api" rel="noopener noreferrer"&gt;&lt;strong&gt;Selector API&lt;/strong&gt;&lt;/a&gt; transforms the experience. Instead of a magic string, you use a type-safe function: &lt;code&gt;t($=&amp;gt;$.userProfile.labels.firstName)&lt;/code&gt;. This enables autocompletion, compile-time checks, and seamless refactoring in TypeScript, turning a historic weakness into a major strength.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Natural Language (Content-as-Key): The Path of Convenience
&lt;/h3&gt;

&lt;p&gt;This philosophy prioritizes initial development speed by using the full source language string (typically English) as the key itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;t('Please enter your email address')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;High In-Code Readability:&lt;/strong&gt; The code is self-documenting, showing the exact default text.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Low Initial Effort:&lt;/strong&gt; It's the fastest way to start, as there's no need to invent a separate keying system.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cons:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Extremely Brittle:&lt;/strong&gt; This is the deal-breaker. Fixing a simple typo or rephrasing the text &lt;strong&gt;changes the key&lt;/strong&gt;, breaking the link to all existing translations and forcing costly re-translation.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Problematic for Complex Strings:&lt;/strong&gt; Using paragraphs or strings with HTML as keys is unwieldy and error-prone.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Inherent Ambiguity:&lt;/strong&gt; It can't differentiate between two identical strings that need different translations based on context (e.g., "View" as a noun vs. a verb).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Content-Agnostic (Generated) Keys: The Path of Ultimate Decoupling
&lt;/h3&gt;

&lt;p&gt;A less common approach that uses machine-generated identifiers as keys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;t('key_aB38fG1p')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Guaranteed Uniqueness and Stability:&lt;/strong&gt; Eliminates human error in naming and ensures the key is never changed.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Complete Decoupling:&lt;/strong&gt; The key is independent of both content and code structure, offering maximum architectural flexibility.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cons:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Zero Human Readability:&lt;/strong&gt; The keys are meaningless without a sophisticated layer of tooling.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Heavy Reliance on Tooling:&lt;/strong&gt; This approach is only viable with mature tools like IDE extensions that display source text inline and a robust TMS that provides all context to translators.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Ultimately, &lt;strong&gt;Structured (Semantic) keys&lt;/strong&gt; represent the most balanced approach. They place a manageable burden on the developer during initial implementation to reduce systemic friction and cost for all stakeholders over the long term.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Great Debate: To Reuse or Not to Reuse? 🤔
&lt;/h2&gt;

&lt;p&gt;Developers are trained on the &lt;strong&gt;"Don't Repeat Yourself" (DRY)&lt;/strong&gt; principle, so creating a &lt;code&gt;common.save&lt;/code&gt; key seems efficient. However, this is a dangerous trap.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Creating new messages is cheaper than trying to manage shared messages."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The flaw is that &lt;strong&gt;textual identity is not semantic identity&lt;/strong&gt;. While two strings may be identical in English, their correct translations can vary dramatically depending on context. The unbreakable rule is to adopt the linguist's perspective: the fundamental unit of translation is the &lt;strong&gt;message in context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dangers of Reuse:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grammatical Context:&lt;/strong&gt; "Read" (present-tense verb) and "Read" (past-tense status) require different translations. A single key forces them to be the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Context:&lt;/strong&gt; "Cancel" for a subscription is a significant action; "Cancel" for a dialog box is trivial. The translation should reflect this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future Divergence:&lt;/strong&gt; Two "Back" buttons might be identical today, but next month a PM may want one of them to say "Previous Step." Changing the shared key breaks the other instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your default policy should be: &lt;strong&gt;never reuse keys&lt;/strong&gt;. Each string instance in the UI should have its own unique key. An approach that creates distinct keys like &lt;code&gt;userProfile.buttons.save&lt;/code&gt; and &lt;code&gt;documentEditor.buttons.save&lt;/code&gt; is vastly superior because they are free to evolve independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building for Scale: Namespaces and Performance
&lt;/h2&gt;

&lt;p&gt;As an application grows, a flat list of keys collapses into chaos. A structured system is essential for scale and performance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Namespacing:&lt;/strong&gt; Partition keys into logical groups. The most robust strategy is to namespace &lt;strong&gt;by feature or domain&lt;/strong&gt; (&lt;code&gt;checkout&lt;/code&gt;, &lt;code&gt;adminDashboard&lt;/code&gt;). This aligns your i18n structure with your application architecture and is resilient to UI refactoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nesting:&lt;/strong&gt; Use a dot (&lt;code&gt;.&lt;/code&gt;) separator to create a hierarchy within namespaces. A nesting depth of &lt;strong&gt;2-3 levels&lt;/strong&gt; (&lt;code&gt;checkout.paymentForm.submitButton&lt;/code&gt;) provides the best balance of organization and simplicity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code-Splitting:&lt;/strong&gt; A key benefit of &lt;a href="https://www.locize.com/docs/namespaces" rel="noopener noreferrer"&gt;namespacing&lt;/a&gt; is performance. Instead of loading one giant translation file, modern libraries like &lt;code&gt;i18next&lt;/code&gt; can load namespaces on demand. When a user navigates to the checkout page, you only fetch the &lt;code&gt;checkout&lt;/code&gt; translations, dramatically improving your app's initial load time.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Beyond Plain Text: Mastering Complex Content
&lt;/h2&gt;

&lt;p&gt;Modern UIs are dynamic. Simply concatenating strings is the cardinal sin of i18n—it hardcodes English grammar into your app and is guaranteed to fail in other languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  ICU Message Format
&lt;/h3&gt;

&lt;p&gt;The industry standard is the &lt;strong&gt;ICU (International Components for Unicode) Message Format&lt;/strong&gt;, which embeds logic directly into your translation string.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pluralization:&lt;/strong&gt; Handle complex plural rules for different languages with a single key.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"notifications.unread"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{count, plural, =0 {You have no new messages.} one {You have one new message.} other {You have # new messages.}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gender:&lt;/strong&gt; Handle gendered language without cluttering your application code.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"user.activity.likedPost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{gender, select, female {She liked your post.} male {He liked your post.} other {They liked your post.}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;But seems the simpler i18next format is preferred by most developers and offers &lt;a href="https://www.i18next.com/translation-function/interpolation" rel="noopener noreferrer"&gt;interpolation&lt;/a&gt;, &lt;a href="https://www.i18next.com/translation-function/context" rel="noopener noreferrer"&gt;context&lt;/a&gt;, &lt;a href="https://www.i18next.com/translation-function/nesting" rel="noopener noreferrer"&gt;nesting&lt;/a&gt;, &lt;a href="https://www.i18next.com/translation-function/formatting" rel="noopener noreferrer"&gt;formatting&lt;/a&gt;, &lt;a href="https://www.i18next.com/translation-function/plurals" rel="noopener noreferrer"&gt;pluralization&lt;/a&gt; and much more...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Rich Text and HTML
&lt;/h3&gt;

&lt;p&gt;Never embed raw HTML in translations and render it with &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;. This is a major security risk. The correct solution is &lt;strong&gt;component interpolation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Libraries like &lt;a href="https://react.i18next.com/" rel="noopener noreferrer"&gt;&lt;code&gt;react-i18next&lt;/code&gt;&lt;/a&gt; use a &lt;a href="https://react.i18next.com/latest/trans-component" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;Trans&amp;gt;&lt;/code&gt; component&lt;/a&gt; that lets you safely mix text and React components. This maintains a clean separation of concerns, allowing translators to reorder the sentence while you retain full control over the link's behavior in your code.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Trans&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;"terms.agreement"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Please agree to our &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/terms"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Terms of Service&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bridging the Context Gap: Empowering Translators for Success
&lt;/h2&gt;

&lt;p&gt;Even with a perfect key naming convention, the single greatest threat to translation quality remains the &lt;strong&gt;void of context&lt;/strong&gt;. Professional translators are linguistic experts, not mind readers. A successful internationalization workflow is not just about extracting strings; it is about exporting context.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tooling Revolution: From Guesswork to Certainty
&lt;/h3&gt;

&lt;p&gt;While good naming and comments are essential, the most comprehensive solution lies in a modern &lt;strong&gt;Translation Management System (&lt;a href="https://www.locize.com/blog/tms" rel="noopener noreferrer"&gt;TMS&lt;/a&gt;)&lt;/strong&gt;. These platforms are purpose-built to serve as a central hub for your entire localization workflow, with a primary focus on delivering rich context. Key features include &lt;a href="https://www.locize.com/docs/context#incontext" rel="noopener noreferrer"&gt;in-context&lt;/a&gt; editors, &lt;a href="https://www.locize.com/docs/context#screenshots" rel="noopener noreferrer"&gt;screenshot&lt;/a&gt; attachments, &lt;a href="https://www.locize.com/docs/translation-memory" rel="noopener noreferrer"&gt;translation memory&lt;/a&gt;, and &lt;a href="https://www.locize.com/docs/glossary" rel="noopener noreferrer"&gt;term bases&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bringing Context Directly Into the Code
&lt;/h3&gt;

&lt;p&gt;The deepest integrations go a step further, closing the gap between the developer's editor and the translator's tool. For example, the tight bond between &lt;a href="https://www.i18next.com/" rel="noopener noreferrer"&gt;&lt;code&gt;i18next&lt;/code&gt;&lt;/a&gt; and its creator's TMS, &lt;a href="https://www.locize.com/" rel="noopener noreferrer"&gt;&lt;code&gt;locize&lt;/code&gt;&lt;/a&gt;, enables a workflow that embeds context provision directly into the development process.&lt;/p&gt;

&lt;p&gt;Traditionally, a developer would add a key in the code, then add the default value in a JSON file, and then perhaps go to a separate system to add a comment for the translator. Modern integrations streamline this into a single, frictionless action right in the code:&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;// This single function call defines the key, the default text, AND the context for the translator.&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userProfile.buttons.requestExport&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 stable, semantic key&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Request My Data&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 default value for the source language&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A button that allows the user to request a GDPR data export. Should have a formal tone.&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 context for the translator&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This elegant approach solves multiple problems at once. The developer provides crucial context at the moment of creation, when it's top of mind. The "write-it-twice" friction of structured keys is dramatically reduced, and the translator receives a rich contextual package automatically (i.e. via &lt;a href="https://www.locize.com/blog/missing-translations" rel="noopener noreferrer"&gt;saveMissing feature&lt;/a&gt;). The debate is no longer solely about the key's name but about the entire &lt;strong&gt;localization context pipeline&lt;/strong&gt;, which a modern toolchain can fully automate.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Unified Strategy: Your Team's Checklist ✅
&lt;/h2&gt;

&lt;p&gt;Synthesizing these principles, here is a recommended framework for modern applications. It is a hybrid approach that champions structured keys as the default, backed by clear conventions and deep integration with your toolchain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Default to Structured (Semantic) Keys.&lt;/strong&gt; They offer the best balance of maintainability, scalability, and clarity.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enforce a "Don't Reuse" Policy.&lt;/strong&gt; Create a unique key for every unique instance of a string. Make exceptions only for a strictly governed &lt;code&gt;common&lt;/code&gt; namespace (e.g., country names, months).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Mandate a Translation Management System (TMS).&lt;/strong&gt; For any serious project, a TMS like &lt;code&gt;locize&lt;/code&gt; is essential infrastructure. It bridges the context gap with features like in-context editors, screenshots, translation memory, and glossaries, making the structured key approach truly powerful.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Integrate Your Toolchain.&lt;/strong&gt; Use IDE extensions like i18n Ally and modern library features like the &lt;code&gt;i18next&lt;/code&gt; Selector API to supercharge your developer experience. Automate hygiene checks in your CI/CD pipeline to find and remove unused keys.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use this checklist to define and document your team's strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Casing Convention:&lt;/strong&gt; camelCase or snake_case? (Be consistent).&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Namespace Strategy:&lt;/strong&gt; By feature/domain?&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Nesting Depth:&lt;/strong&gt; Max 2-3 levels?&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Reuse Policy:&lt;/strong&gt; Unique keys by default?&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;&lt;code&gt;common&lt;/code&gt; Namespace Governance:&lt;/strong&gt; What are the explicit, documented rules for adding new keys to it?&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Context Provisioning:&lt;/strong&gt; How will you provide context to translators? &lt;strong&gt;Leverage tight integrations like the &lt;code&gt;i18next-locize&lt;/code&gt; backend to allow developers to pass descriptions directly from their code using options like &lt;code&gt;tDescription&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Complex Content Handling:&lt;/strong&gt; How will you handle plurals/rich text? (ICU, &lt;code&gt;&amp;lt;Trans&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Maintenance Process:&lt;/strong&gt; How will you audit orphaned keys?  (&lt;a href="https://www.locize.com/docs/find-unused-translations" rel="noopener noreferrer"&gt;find unused translations&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>i18n</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>i18next</category>
    </item>
    <item>
      <title>Taming Stateful at the Edge: A Real-Time API with Cloudflare Durable Objects</title>
      <dc:creator>Adriano Raiano</dc:creator>
      <pubDate>Tue, 23 Sep 2025 07:39:00 +0000</pubDate>
      <link>https://dev.to/adrai/taming-stateful-at-the-edge-a-real-time-api-with-cloudflare-durable-objects-57dm</link>
      <guid>https://dev.to/adrai/taming-stateful-at-the-edge-a-real-time-api-with-cloudflare-durable-objects-57dm</guid>
      <description>&lt;p&gt;Serverless architecture has revolutionized how we build applications, but it comes with a well-known Achilles' heel: managing state. For stateless functions, it’s a dream. But as soon as your application requires real-time collaboration, live presence, or persistent chat, you enter a world of complexity. How do you manage WebSocket connections when your functions are ephemeral?&lt;/p&gt;

&lt;p&gt;The traditional workarounds are often complex and costly. You might find yourself managing sticky sessions on load balancers or bolting on a separate stateful service like Redis just to handle pub/sub and connection mapping. This adds significant operational overhead, undermining the core promise of serverless.&lt;/p&gt;

&lt;p&gt;At Vaultrice, we faced this exact dilemma. We needed to build a global, low-latency, and strongly-consistent backend for real-time state, without building a traditional, resource-intensive server fleet. This post details the architectural journey we took and how we solved this problem by betting on &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;'s cutting-edge stack, with Durable Objects at its core.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Game Changer: Our Bet on Cloudflare Durable Objects&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After evaluating the landscape, we chose Cloudflare Durable Objects (DOs) as the foundation of our architecture. They offered solutions to the most difficult stateful problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For Strong Consistency:&lt;/strong&gt; We needed to guarantee atomic operations for features like &lt;a href="https://www.vaultrice.com/blog/how-to-react-live-poll" rel="noopener noreferrer"&gt;live vote&lt;/a&gt; counters and collaborative data editing. The single-threaded execution model of a Durable Object instance was the perfect fit. It ensures that all operations for a given data object are processed serially, eliminating the risk of race conditions that plague eventually-consistent systems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For Cost-Effective, Stateful WebSockets:&lt;/strong&gt; The biggest challenge in building a real-time service is managing persistent WebSocket connections at scale. The Durable Object WebSocket Hibernation API was the key. This powerful feature allows us to maintain a client's WebSocket connection at the Cloudflare edge while the DO itself hibernates, waking it only when a message arrives. This is fundamental to our ability to offer a generous free tier and an efficient, scalable service making powerful real-time features accessible to projects of any scale.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Vaultrice Architecture: A High-Level Flyover&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Our architecture is designed for simplicity and performance, abstracting the power of the edge into a developer-friendly API.&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%2Fpekg2whn6ifx79cmpvz7.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%2Fpekg2whn6ifx79cmpvz7.jpg" alt="Figure: High-level request flow in the Vaultrice architecture." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Stateless Entrypoint (Cloudflare Worker):&lt;/strong&gt; Every request first hits a standard Cloudflare Worker. Its job is purely to handle authentication (verifying API keys or access tokens) and route the request to the correct Durable Object instance.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Stateful Core (Durable Object):&lt;/strong&gt; This is where the real work happens. Each Durable Object acts as a self-contained, stateful micro-server for a single data object—like a chat room, a shared document, or a user's settings. It manages the in-memory state, handles WebSockets, and interacts with its private, strongly-consistent transactional storage. All complexity is contained here.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Simple Abstraction (Our &lt;a href="https://vaultrice.github.io/sdk/" rel="noopener noreferrer"&gt;SDK&lt;/a&gt; or our &lt;a href="https://github.com/vaultrice/react" rel="noopener noreferrer"&gt;React bindings&lt;/a&gt;):&lt;/strong&gt; All of this is hidden behind our simple, &lt;code&gt;localStorage&lt;/code&gt;-like SDKs (&lt;code&gt;NonLocalStorage&lt;/code&gt; and &lt;code&gt;SyncObject&lt;/code&gt;). The developer never interacts with the Durable Object directly; they just call familiar methods like &lt;code&gt;nls.setItem('key', 'value')&lt;/code&gt; or &lt;code&gt;doc.title = 'New Title'&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Key Challenges We Abstract Away For You&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Building this architecture reliably is non-trivial. The real value of Vaultrice is in the hard problems we've already solved so our users don't have to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seamless Reconnection &amp;amp; Presence:
&lt;/h3&gt;

&lt;p&gt;Ensuring a user's presence is accurately reflected across flaky networks and browser tabs is notoriously difficult. We built a robust session management system within our DOs that handles reconnection and state reconciliation automatically, so your "who's online" list is always accurate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure, Multi-Tenant Data Isolation:
&lt;/h3&gt;

&lt;p&gt;Architecting a secure multi-tenant system requires careful data scoping. Our platform maps each customer's data objects to unique Durable Object instances, providing a strong layer of isolation. This is then enhanced by our own &lt;a href="https://www.vaultrice.com/docs/security" rel="noopener noreferrer"&gt;security model&lt;/a&gt;, with features like &lt;strong&gt;Object ID Signature Verification&lt;/strong&gt;, which cryptographically ensures that even a valid API key can only access authorized data objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency &amp;amp; Data Integrity:
&lt;/h3&gt;

&lt;p&gt;With many users modifying the same data, preventing data corruption is paramount. Instead of forcing developers to build complex locking mechanisms, Vaultrice provides a suite of server-side atomic operations for common mutations like counters, array pushes, and object merges, which are executed safely within the single-threaded context of the DO.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion: Leverage the Edge, Today&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Stateful applications at the edge are the future of the real-time web, but the path to building them from scratch is fraught with complexity. By building Vaultrice on Cloudflare's powerful serverless platform, we've created a "batteries-included" solution that makes this new architecture accessible to any developer.&lt;/p&gt;

&lt;p&gt;We handled the hard parts of distributed systems so you can focus on what matters: building amazing user experiences.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;See this architecture in action by cloning our &lt;a href="https://github.com/vaultrice/vaultrice-chat-starter" rel="noopener noreferrer"&gt;React Chat Starter on GitHub&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy your first global, real-time feature in minutes on our &lt;a href="https://www.vaultrice.com/pricing" rel="noopener noreferrer"&gt;generous free tier&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>cloud</category>
      <category>cdn</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
