<?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: Akshat Sharma</title>
    <description>The latest articles on DEV Community by Akshat Sharma (@akkii2022).</description>
    <link>https://dev.to/akkii2022</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3059042%2F3f190df9-2474-4508-bb2d-11a51b8806dc.jpg</url>
      <title>DEV Community: Akshat Sharma</title>
      <link>https://dev.to/akkii2022</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akkii2022"/>
    <language>en</language>
    <item>
      <title>I Built the Same i18n App Twice: Here is What Broke the First Time</title>
      <dc:creator>Akshat Sharma</dc:creator>
      <pubDate>Fri, 31 Jul 2026 14:24:47 +0000</pubDate>
      <link>https://dev.to/akkii2022/i-built-the-same-i18n-app-twice-here-is-what-broke-the-first-time-24bm</link>
      <guid>https://dev.to/akkii2022/i-built-the-same-i18n-app-twice-here-is-what-broke-the-first-time-24bm</guid>
      <description>&lt;h2&gt;
  
  
  Why Localization Matters to Me as a Football Fan and Developer
&lt;/h2&gt;

&lt;p&gt;Every four years, the FIFA World Cup brings the entire planet together. Billions of fans tune in from almost every country on earth. Like many of you, I am a huge football fan. I also happen to read and speak several languages, and one thing I have noticed over the years is how much more personal and exciting following the tournament feels when you can read match stats, live commentaries, and news in your own mother tongue.&lt;/p&gt;

&lt;p&gt;But while browsing international sports articles and tournament dashboards across different languages, I kept running into the same frustrating problem: grammatical errors everywhere. Plural goal counts that sounded robotic or broken. Buttons that overflowed their boxes, or pages that crashed completely when someone tried to use auto translate in their browser.&lt;/p&gt;

&lt;p&gt;Being a developer, I could not help but wonder why localizing web software cleanly is still such a minefield. I started exploring modern engineering solutions to solve this once and for all, and that is when I discovered &lt;strong&gt;&lt;a href="https://tolgee.io/" rel="noopener noreferrer"&gt;Tolgee&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To test it properly against the traditional way we build multilingual apps, I decided to run a real engineering experiment.&lt;/p&gt;

&lt;p&gt;I built a &lt;strong&gt;FIFA World Cup 2026™ tournament dashboard&lt;/strong&gt; with a live scoreboard, top scorers, group standings, match fixtures, match feed, and tournament facts localized into &lt;strong&gt;7 languages&lt;/strong&gt; across multiple writing systems and plural rule families (&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;cs&lt;/code&gt;, &lt;code&gt;pl&lt;/code&gt;, &lt;code&gt;ru&lt;/code&gt;, &lt;code&gt;hi&lt;/code&gt;, &lt;code&gt;ar&lt;/code&gt;), using two different i18n stacks. The same UI. The same data. Two translation engines. And I ran the whole thing through &lt;strong&gt;27 automated unit tests&lt;/strong&gt; to prove which one handles complex Slavic and Arabic grammar right.&lt;/p&gt;

&lt;p&gt;Here is what the dashboard looks like:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fn8nv9sg3ms57l0s1a9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fn8nv9sg3ms57l0s1a9.png" alt="FIFA World Cup 2026 Dashboard: English Mode" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two stacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;i18next&lt;/strong&gt; (standard setup): the industry standard, file-based, suffix-key plurals (&lt;code&gt;_one&lt;/code&gt;, &lt;code&gt;_other&lt;/code&gt;…), HTTP backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tolgee&lt;/strong&gt; (with &lt;code&gt;@tolgee/format-icu&lt;/code&gt;): an open source SDK and Translation Management System with native ICU MessageFormat support&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on framing:&lt;/strong&gt; This is not a claim that i18next and Tolgee are mutually exclusive — Tolgee ships an official &lt;a href="https://docs.tolgee.io/js-sdk/integrations/i18next/react-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@tolgee/i18next&lt;/code&gt; integration&lt;/a&gt; that lets you keep your existing i18next runtime while adding Tolgee's TMS on top. The comparison here is specifically between &lt;strong&gt;i18next's default suffix-key plural approach&lt;/strong&gt; and &lt;strong&gt;ICU MessageFormat&lt;/strong&gt; (which Tolgee defaults to). That distinction matters most for languages with more than two plural forms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup: Same UI, Two Translation Engines
&lt;/h2&gt;

&lt;p&gt;The architecture of this demo is built around a clean &lt;strong&gt;adapter pattern&lt;/strong&gt;. There is one shared dashboard component, &lt;code&gt;FifaDashboard.tsx&lt;/code&gt;, that accepts a single &lt;code&gt;t(key, params)&lt;/code&gt; function as a prop. That function is the only thing that changes between the two modes.&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;// The shared component signature — framework-agnostic&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DashboardProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tolgee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&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="nl"&gt;lang&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="nl"&gt;onLangChange&lt;/span&gt;&lt;span class="p"&gt;:&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="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;void&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;Two thin wrapper components — &lt;code&gt;TolgeeDashboardWrapper.tsx&lt;/code&gt; and &lt;code&gt;I18nextDashboardWrapper.tsx&lt;/code&gt; — each hook into their respective SDK and pass the correct &lt;code&gt;t()&lt;/code&gt; implementation down. The shared component never knows which engine is running.&lt;/p&gt;

&lt;p&gt;This matters for the comparison. Every string you see rendered in the dashboard — the match score minute (&lt;code&gt;67'&lt;/code&gt;), the live match count pill (&lt;code&gt;2 matches live&lt;/code&gt;), the top scorers list (&lt;code&gt;7 goals&lt;/code&gt;), the Czech facts ticker — is routed through the same JSX and the same translation keys. The only variable is which engine resolves them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App.tsx (tab switcher)
    ├── TolgeeProvider → TolgeeDashboardWrapper → FifaDashboard
    └── I18nextProvider → Suspense → I18nextDashboardWrapper → FifaDashboard
                                    ↑ same component, same props interface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 1: i18next — What Works, and Where It Breaks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Good Stuff First
&lt;/h3&gt;

&lt;p&gt;Setting up i18next is genuinely fast. The entire configuration is 15 lines:&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;// src/i18n.ts&lt;/span&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;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="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;HttpBackend&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;lng&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;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;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;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;cs&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;pl&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;ru&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;hi&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;ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;loadPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/locales/{{lng}}/translation.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="na"&gt;interpolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;escapeValue&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="c1"&gt;// NOTE: Standard setup only handles _one and _other plural suffixes.&lt;/span&gt;
    &lt;span class="c1"&gt;// Czech needs _few too — but that requires the i18next-icu plugin.&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;useTranslation&lt;/code&gt; hook integrates cleanly with React 19. The HTTP backend lazy-loads locale files from &lt;code&gt;public/locales/&lt;/code&gt;, so your initial JS bundle stays lean. English and French render perfectly: button labels, match feed events, fixture kickoff times, group standings headers — everything just works.&lt;/p&gt;

&lt;p&gt;For the match events feed in &lt;code&gt;data.ts&lt;/code&gt;, the interpolation is straightforward:&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;// data.ts — match events data&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;FEED_EVENTS&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;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;goal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vinicius Jr.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;club&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Real Madrid&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;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mohammed Al-Burayk&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;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bellingham&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Valverde&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;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ht&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="c1"&gt;// en/translation.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;feed_goal&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="s2"&gt;⚽ Goal! {player} ({club})&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="s2"&gt;feed_yellow&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="s2"&gt;🟨 Yellow card — {player}&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="s2"&gt;feed_sub&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="s2"&gt;🔄 Substitution — {out} off, {in} on&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="s2"&gt;feed_ht&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="s2"&gt;🔔 Half Time!&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;That works. The problem only surfaces when you switch the language to Czech.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem: Slavic 4-Form Plurals and Arabic 6-Form Plurals
&lt;/h3&gt;

&lt;p&gt;English plurals are binary: &lt;code&gt;1 match&lt;/code&gt; / &lt;code&gt;5 matches&lt;/code&gt;. Slavic languages like &lt;strong&gt;Czech, Polish, and Russian&lt;/strong&gt; follow the &lt;strong&gt;Unicode CLDR plural specification&lt;/strong&gt; with four grammatically distinct forms (&lt;code&gt;one&lt;/code&gt;, &lt;code&gt;few&lt;/code&gt;, &lt;code&gt;many&lt;/code&gt;, &lt;code&gt;other&lt;/code&gt;). Arabic is even more extreme, with &lt;strong&gt;six distinct plural categories&lt;/strong&gt; — the maximum possible in CLDR:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CLDR Category&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Czech (&lt;code&gt;gól&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Polish (&lt;code&gt;gol&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Russian (&lt;code&gt;гол&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Arabic (&lt;code&gt;هدف&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zero&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 gólů&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 goli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 голов&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;صفر أهداف&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;one&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 gól&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 gol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 гол&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;هدف واحد&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;two&lt;/code&gt; (dual)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;هدفان&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;few&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2–4 (Slavic) / 3–10 (Ar)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2 góly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2 gole&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2 гола&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3 أهداف&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;many&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Decimals / 11–99 (Ar)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2.5 gólu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5 goli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5 голов&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15 هدفاً&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;other&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5+&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5 gólů&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;21 goli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;21 гол&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100 هدف&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In our demo, the interactive plural showdown panel renders goal counts across all these categories specifically to expose how translation engines behave when confronted with these rules.&lt;/p&gt;

&lt;p&gt;Here is what i18next does with the standard setup. The Czech translation file needs:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;public/locales/cs/translation.json&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="err"&gt;i&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="err"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format&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;"scorers_goals_one"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"{{count}} gól"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scorers_goals_other"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{count}} gólů"&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;When i18next encounters &lt;code&gt;count: 2&lt;/code&gt;, it calls &lt;code&gt;Intl.PluralRules('cs').select(2)&lt;/code&gt;, which correctly returns &lt;code&gt;"few"&lt;/code&gt;. But then it looks for a key named &lt;code&gt;scorers_goals_few&lt;/code&gt; — and finds nothing, because you only defined &lt;code&gt;_one&lt;/code&gt; and &lt;code&gt;_other&lt;/code&gt;. It silently falls back to &lt;code&gt;_other&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ 1 gól     (one — correct)
❌ 2 gólů    (other — should be: 2 góly)
❌ 3 gólů    (other — should be: 3 góly)
✅ 5 gólů    (other — correct)
✅ 21 gólů   (other — correct)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No error. No warning. It renders. It is wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;To be precise:&lt;/strong&gt; this silent fallback is not unique to i18next — ICU MessageFormat also falls back gracefully when a plural category is missing from the translation string. The difference is structural: with i18next's suffix-key model, each plural form is a &lt;strong&gt;separate, independently-omittable JSON key&lt;/strong&gt;. It is easy to define &lt;code&gt;_one&lt;/code&gt; and &lt;code&gt;_other&lt;/code&gt; and simply never notice that &lt;code&gt;_few&lt;/code&gt; is missing. With ICU, the entire plural rule lives inside &lt;strong&gt;one string&lt;/strong&gt; — &lt;code&gt;{count, plural, one {…} few {…} other {…}}&lt;/code&gt; — so a missing category is immediately visible to anyone who reads the file. The structure itself makes the gap obvious at authoring time rather than invisible until a native speaker files a bug.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The live count pill in the header also breaks: the dashboard shows &lt;code&gt;2 zápasů živě&lt;/code&gt; instead of the grammatically correct &lt;code&gt;2 zápasy živě&lt;/code&gt;. You can see this in the i18next Czech screenshot:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz6l2knul5zmjc0ag2gv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz6l2knul5zmjc0ag2gv.png" alt="i18next Czech Mode — Broken Plurals" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The orange warning banner in the dashboard explicitly calls this out: &lt;em&gt;"Režim i18next — POZOR: '3 góly' je správně, ale i18next zobrazuje '3 gólů'. Chybí plugin i18next-icu!"&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why This Bug Ships Silently
&lt;/h3&gt;

&lt;p&gt;The mechanism that makes this particularly dangerous in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The missing key is invisible at the file level — &lt;code&gt;_one&lt;/code&gt; and &lt;code&gt;_other&lt;/code&gt; look complete; &lt;code&gt;_few&lt;/code&gt; is simply absent with no structural hint it should exist&lt;/li&gt;
&lt;li&gt;Your CI/CD pipeline does not fail — the app builds and deploys cleanly&lt;/li&gt;
&lt;li&gt;Your English and French tests still pass — the bug is locale-specific&lt;/li&gt;
&lt;li&gt;A developer who does not speak Czech has no way to spot it during code review&lt;/li&gt;
&lt;li&gt;Most translation reviewers work from static screenshots, not live apps with dynamic counts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not a hypothetical. It is the kind of bug that makes it into production in apps localized for Polish, Russian, Arabic, or any other language with more than two plural forms — and stays there because nobody who notices it has the ability to fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Configuring Tolgee — Where It Gets Better
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ICU MessageFormat: The Plural Problem Solved by Design
&lt;/h3&gt;

&lt;p&gt;Tolgee's &lt;code&gt;@tolgee/format-icu&lt;/code&gt; plugin uses ICU MessageFormat — the Unicode standard for formatting messages across locales. Instead of separate keys with suffixes, the entire plural logic lives inside a single string:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;src/locales/cs.json&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="err"&gt;Tolgee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ICU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format&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;"scorers_goals"&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, one {# gól} few {# góly} other {# gólů}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"header_live_count"&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, one {# zápas živě} few {# zápasy živě} other {# zápasů živě}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stat_goals_subtitle"&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, one {# gól vstřelen dosud} few {# góly vstřeleny dosud} other {# gólů vstřeleno dosud}}"&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;The &lt;code&gt;#&lt;/code&gt; token is replaced by the count. The plural category — &lt;code&gt;one&lt;/code&gt;, &lt;code&gt;few&lt;/code&gt;, &lt;code&gt;other&lt;/code&gt; — is resolved by the ICU engine against the active locale's CLDR rules. The &lt;code&gt;few&lt;/code&gt; category for Czech is handled automatically, with no plugin configuration, no suffix keys, and no fallback behaviour.&lt;/p&gt;

&lt;p&gt;Here is what the dashboard renders in Czech with Tolgee:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ 1 gól     (one — correct)
✅ 2 góly    (few — correct)
✅ 3 góly    (few — correct)
✅ 5 gólů    (other — correct)
✅ 21 gólů   (other — correct)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header live count becomes &lt;code&gt;2 zápasy živě&lt;/code&gt;. The stats subtitle for 163 goals reads &lt;code&gt;163 gólů vstřeleno dosud&lt;/code&gt;. Every plural string in the dashboard resolves to the correct grammatical form:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vbtzcwa8vcwbzd58kq9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vbtzcwa8vcwbzd58kq9.png" alt="Tolgee Czech Mode" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Notice "2 zápasy živě" in the live count pill — the &lt;code&gt;few&lt;/code&gt; form, correctly applied.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tolgee SDK Configuration
&lt;/h3&gt;

&lt;p&gt;The full Tolgee setup, including the offline fallback:&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;// src/tolgee.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;Tolgee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DevTools&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;@tolgee/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;FormatIcu&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;@tolgee/format-icu&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tolgee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tolgee&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="nc"&gt;DevTools&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="c1"&gt;// Alt+click in-context editing (dev/staging only)&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="nc"&gt;FormatIcu&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;// Full ICU MessageFormat: plurals, genders, selects, ordinals&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;apiUrl&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;meta&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;VITE_TOLGEE_API_URL&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&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;VITE_TOLGEE_API_KEY&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="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;availableLanguages&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;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;cs-CZ&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;pl&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;ru&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;hi&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;ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// staticData: embedded fallback — app works fully offline if cloud is unavailable&lt;/span&gt;
    &lt;span class="na"&gt;staticData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/en.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;fr&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/fr.json&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;cs-CZ&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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/cs-CZ.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;pl&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/pl.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;ru&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/ru.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;hi&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/hi.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;ar&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/ar.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth calling out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;DevTools()&lt;/code&gt;&lt;/strong&gt; — this is the in-context editing plugin. It activates only when &lt;code&gt;apiKey&lt;/code&gt; is present (development and staging environments). In a production build without a key, the plugin is inert and ships zero overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;staticData&lt;/code&gt;&lt;/strong&gt; — these are dynamic imports that Vite bundles as separate async chunks. Check the build output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;vite v8.1.3 building client environment for production...
✓ 61 modules transformed.

dist/index.html                   0.46 kB │ gzip:   0.29 kB
dist/assets/index-4C8f5oeS.css   21.35 kB │ gzip:   4.74 kB
dist/assets/en-1mDUlYNd.js        7.03 kB │ gzip:   2.87 kB
dist/assets/fr-DwcYreVG.js        7.26 kB │ gzip:   2.97 kB
dist/assets/cs-CZ-DGy2fZiq.js     7.49 kB │ gzip:   3.17 kB
dist/assets/pl-B5rMwVrt.js        7.55 kB │ gzip:   3.18 kB
dist/assets/ru-CTfCuXxl.js        9.08 kB │ gzip:   3.55 kB
dist/assets/ar-DJu1entp.js        9.36 kB │ gzip:   3.44 kB
dist/assets/hi-44eIwXL0.js        9.48 kB │ gzip:   3.28 kB
dist/assets/index-DoMQ5NLb.js   338.36 kB │ gzip: 104.82 kB

✓ built in 306ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each locale is lazy-loaded on demand. If Tolgee Cloud is offline or the API key is missing, the app falls back to these embedded files silently — no broken UI, no missing strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping the App
&lt;/h3&gt;

&lt;p&gt;The provider pattern is straightforward:&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/App.tsx&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;TolgeeProvider&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;@tolgee/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;tolgee&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;./tolgee&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;App&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;activeTab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveTab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tolgee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&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="o"&gt;&amp;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;tolgee&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="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;"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="nt"&gt;nav&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;"tab-nav"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Tolgee / i18next switcher */&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;nav&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;activeTab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tolgee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;TolgeeProvider&lt;/span&gt; &lt;span class="na"&gt;tolgee&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;tolgee&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;fallback&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="nc"&gt;LoadingScreen&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;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TolgeeDashboardWrapper&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;TolgeeProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;activeTab&lt;/span&gt; &lt;span class="o"&gt;===&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="o"&gt;&amp;amp;&amp;amp;&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;I18nextProvider&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="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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="nc"&gt;LoadingScreen&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;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;I18nextDashboardWrapper&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;Suspense&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;I18nextProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;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;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;The &lt;code&gt;TolgeeProvider&lt;/code&gt; handles language state reactively via the &lt;code&gt;tolgee&lt;/code&gt; instance. Language changes are called with &lt;code&gt;tolgee.changeLanguage(code)&lt;/code&gt; from the wrapper, and every component subscribed through &lt;code&gt;useTranslate()&lt;/code&gt; re-renders automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: Proving it With Code, Not Just Screenshots
&lt;/h2&gt;

&lt;p&gt;Screenshots are easy to fake. Tests are not. The repo includes a Vitest test suite that directly asserts the plural output from both engines for every Czech count:&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;// src/__tests__/plurals.test.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;IntlMessageFormat&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;intl-messageformat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// IntlMessageFormat is the same underlying engine that @tolgee/format-icu wraps.&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ICU_GOALS_CS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{count, plural, one {# gól} few {# góly} other {# gólů}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Mirrors i18next standard behaviour without the ICU plugin:&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;simulateI18nextCzech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PluralRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// i18next reads _one and _other keys. 'few' falls through to _other silently.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;one&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; gól`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; gólů`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrong for count 2, 3, 4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Czech Plural Forms — Tolgee ICU&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 gól&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;    &lt;span class="c1"&gt;// ✅ one&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2 góly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// ✅ few  ← this is the one that matters&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3 góly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// ✅ few&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4 góly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// ✅ few&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5 gólů&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// ✅ other&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;21 gólů&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// ✅ other&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100 gólů&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// ✅ other&lt;/span&gt;
  &lt;span class="p"&gt;])(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count=%i → "%s"&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="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&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;result&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;IntlMessageFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ICU_GOALS_CS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// all 7 pass&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Czech Plural Forms — Standard i18next (no ICU plugin)&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count=2 → WRONG "2 gólů" (should be "2 góly")&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simulateI18nextCzech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2 gólů&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// ❌ wrong form&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simulateI18nextCzech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2 góly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// correct form unreachable&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;Run &lt;code&gt;npm run test&lt;/code&gt;. You get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✓ Czech Plural Forms — Tolgee ICU (FormatIcu plugin) (7 tests)
✓ Czech Plural Forms — Standard i18next (no ICU plugin) (5 tests)
✓ Tournament Facts — Tolgee ICU plurals (Czech) (4 tests)
✓ Cross-language ICU consistency (4 tests)
✓ Polish Plural Forms — Tolgee ICU vs Standard i18next (2 tests)
✓ Russian Plural Forms — Tolgee ICU vs Standard i18next (2 tests)
✓ Hindi Plural Rules — Note on 0 and 1 (1 test)
✓ Arabic 6 Plural Forms — Tolgee ICU vs Standard i18next (2 tests)

Test Files  1 passed (1)
     Tests  27 passed (27)
  Duration  ~300ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test for the i18next Czech behaviour &lt;em&gt;passes&lt;/em&gt; — but what it asserts is that i18next &lt;em&gt;does&lt;/em&gt; produce the wrong form. The &lt;code&gt;expect(...).not.toBe('2 góly')&lt;/code&gt; assertion confirms the correct form is never reached. The tests are documentation that happens to be executable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: The Workflow Problem (Bigger Than the Plural Problem)
&lt;/h2&gt;

&lt;p&gt;The plural bug is a technical problem with a technical fix. The workflow problem is structural — and it is the one that compounds over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Translation Edit Loop in i18next
&lt;/h3&gt;

&lt;p&gt;In our i18next setup, the Czech translation files live in &lt;code&gt;public/locales/cs/translation.json&lt;/code&gt;. Every string — from the match stats labels (&lt;code&gt;Držení míče&lt;/code&gt;, &lt;code&gt;Fauly&lt;/code&gt;) to the sidebar navigation (&lt;code&gt;Přehled&lt;/code&gt;, &lt;code&gt;Střelci&lt;/code&gt;) to the footer (&lt;code&gt;FIFA Mistrovství světa 2026™&lt;/code&gt;) — is a static file checked into Git.&lt;/p&gt;

&lt;p&gt;When a Czech translator reviews the deployed staging dashboard and spots that &lt;code&gt;Čtvrtfinále · 16. hrací den&lt;/code&gt; should read &lt;code&gt;Čtvrtfinále · 16. zápasový den&lt;/code&gt;, here is what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Translator notices the error on the live staging URL
2. Takes a screenshot, opens a Jira ticket
3. Developer picks up the ticket
4. Finds 'live_match_title' key in public/locales/cs/translation.json
5. Creates a branch: git checkout -b fix/cs-match-title
6. Edits the file, commits, opens PR
7. Code review (even if it is just a translator fix)
8. CI/CD pipeline runs — TypeScript check, Vite build, deploy
9. Staging is updated
10. Translator verifies
11. If it is wrong again: start over from step 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total time for a one-word copy change in a JSON file: &lt;strong&gt;1–4 hours of developer time that has nothing to do with development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the tax. And in a dashboard like this one — with over 100 translation keys across &lt;strong&gt;seven locales&lt;/strong&gt; (&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;cs-CZ&lt;/code&gt;, &lt;code&gt;pl&lt;/code&gt;, &lt;code&gt;ru&lt;/code&gt;, &lt;code&gt;hi&lt;/code&gt;, &lt;code&gt;ar&lt;/code&gt;), covering navigation labels, match statistics, tournament facts, scorers, standings headers, feed events, and footer copy — that tax compounds every time a translator or product manager wants to touch anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tolgee Workflow: A Real Edit, End to End
&lt;/h3&gt;

&lt;p&gt;To demonstrate this concretely, I performed a real fix through the in-context editor during the writing of this post.&lt;/p&gt;

&lt;p&gt;Switching to Czech in the sidebar and holding &lt;code&gt;Alt&lt;/code&gt;, I clicked on the &lt;strong&gt;"7 gólů"&lt;/strong&gt; goal count next to Kylian Mbappé in the Top Scorers section. An overlay appeared immediately, showing the &lt;code&gt;scorers_goals&lt;/code&gt; ICU string editable across all seven languages at once:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvavus4i8iolaxsunr55b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvavus4i8iolaxsunr55b.png" alt="Tolgee In-Context Editing" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I noticed the Czech &lt;code&gt;Many&lt;/code&gt; category (used for decimal counts like &lt;em&gt;2.5 gólu&lt;/em&gt;) was empty — a genuine gap. I typed &lt;code&gt;gólu&lt;/code&gt; into that field:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2vpg9bp9q4p7k9hnofim.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2vpg9bp9q4p7k9hnofim.png" alt="Czech Many field filled in with gólu" width="799" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking &lt;strong&gt;Save&lt;/strong&gt;, the change was pushed directly to Tolgee Cloud with no build, no PR, and no deploy. The Tolgee activity log confirmed the edit instantly:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdxc8j9u437esqj68w9o.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdxc8j9u437esqj68w9o.png" alt="Tolgee Cloud activity log" width="799" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full audit trail shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key:&lt;/strong&gt; &lt;code&gt;scorers_goals&lt;/code&gt; (Czech / cs-CZ)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change:&lt;/strong&gt; &lt;code&gt;many {gólu}&lt;/code&gt; added (highlighted green in the diff)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author:&lt;/strong&gt; Akshat Sharma&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; 7/20/2026 9:34 AM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How:&lt;/strong&gt; Alt+Click in the live running app — no code touched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The corrected ICU string is now:&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="err"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;plural&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;gól&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;few&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;góly&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;gólu&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;gólů&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;Total time from noticing the gap to seeing it live in Tolgee Cloud: &lt;strong&gt;under 60 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With i18next, the same fix would require editing the JSON locale file, committing, opening a PR, getting a review, merging, and waiting for the CI pipeline to deploy. With Tolgee's in-context editor connected to a live project, a translator (not a developer) can do this themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live In-Context Editing &amp;amp; Cloud Synchronization Gallery
&lt;/h3&gt;

&lt;p&gt;Below are additional captures from the running app showing the full scope of what the in-context editor exposes:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Tolgee Cloud Project Overview&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Live cloud dashboard (&lt;code&gt;app.tolgee.io&lt;/code&gt;) showing our project FIFA WC 2026 Demo with 103 keys at 100% Translated status across all 7 target languages (&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;ar&lt;/code&gt;, &lt;code&gt;cs-CZ&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;hi&lt;/code&gt;, &lt;code&gt;pl&lt;/code&gt;, &lt;code&gt;ru&lt;/code&gt;).&lt;/em&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0v8iig9xx67og1r0rwqx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0v8iig9xx67og1r0rwqx.png" alt="Tolgee Cloud" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Tolgee Cloud Translation Management System&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;The collaborative TMS string view filtering keys side-by-side across English, Russian (&lt;code&gt;Cyrillic&lt;/code&gt;), and Polish (&lt;code&gt;Latin with diacritics&lt;/code&gt;).&lt;/em&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wx6em29n3tn4oo58tsr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wx6em29n3tn4oo58tsr.png" alt="Tolgee TMS" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;Full 7-Language Tournament Dashboard&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Overview of the live dashboard running in browser (&lt;code&gt;localhost:5173&lt;/code&gt;) with instant hot-swapping across all 7 languages and writing systems.&lt;/em&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw0f9ueh7hl1bg4q3eho.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw0f9ueh7hl1bg4q3eho.png" alt="Full 7-Language Tournament Dashboard" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. &lt;strong&gt;Editing ICU Plural Categories in Context&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Holding &lt;code&gt;Alt&lt;/code&gt; and clicking a player's goal count opens the &lt;code&gt;scorers_goals&lt;/code&gt; key. Notice how all 6 Arabic ICU plural forms (&lt;code&gt;Zero&lt;/code&gt;, &lt;code&gt;One&lt;/code&gt;, &lt;code&gt;Two&lt;/code&gt;, &lt;code&gt;Few&lt;/code&gt;, &lt;code&gt;Many&lt;/code&gt;, &lt;code&gt;Other&lt;/code&gt;) and 4 Czech forms are directly editable inside the overlay.&lt;/em&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfpaj5hbbubt3a85m61w.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfpaj5hbbubt3a85m61w.png" alt="Editing ICU Plural" width="799" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  5. &lt;strong&gt;Editing UI Badge Copy Across Scripts&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;In-context editing on the &lt;code&gt;tolgee_badge&lt;/code&gt; string across English (Latin), Arabic (RTL), Czech (Diacritics), French, and Hindi (Devanagari).&lt;/em&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdz4lxsraoxtdo0bx27j3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdz4lxsraoxtdo0bx27j3.png" alt="Editing UI Badge" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  6. &lt;strong&gt;Interactive Plural Showdown &amp;amp; Language Filtering&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Filtering active target languages in the DevTools popup while interacting with the live Arabic 6-form Plural Showdown grid.&lt;/em&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbtgt5tiekqjqpnicxfqb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbtgt5tiekqjqpnicxfqb.png" alt="Interactive Plural Showdown &amp;amp; Language Filtering" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: The Browser-Translate Crash — A React Production Bug Worth Understanding
&lt;/h2&gt;

&lt;p&gt;There is a third class of problem that does not show up during development: what happens when your users auto-translate your app with a browser extension.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;React and browser compatibility issue&lt;/strong&gt; — it affects any React application, regardless of which i18n library you use. When Google Translate (or any browser-level translator) runs on a React application, it directly manipulates DOM text nodes. React's Virtual DOM maintains references to those nodes. When the component re-renders — for example, when the live match minute counter ticks forward — React tries to call &lt;code&gt;removeChild&lt;/code&gt; on the original text node, which the browser's translator has already replaced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NotFoundError: Failed to execute 'removeChild' on 'Node':
The node to be removed is not a child of this node.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component tree crashes. The page is dead until a hard refresh. i18next, Tolgee, or any other i18n library will experience the same crash if the user's browser translator is active — neither library controls DOM mutation.&lt;/p&gt;

&lt;p&gt;In our dashboard, the match minute counter in &lt;code&gt;FifaDashboard.tsx&lt;/code&gt; ticks every 30 seconds:&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTick&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&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;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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;setTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;30000&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentMinute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LIVE_MATCH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Renders: t('live_minute', { minute: currentMinute }) → "67'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tick triggers a re-render. If a Czech user who found the app via Google Search had their browser set to auto-translate to Czech — because the app was only available in English — that re-render would crash the tab.&lt;/p&gt;

&lt;p&gt;So where does Tolgee fit in? Not as a direct fix to the crash, but as the reason users stop needing browser translators in the first place. With i18next's file-based workflow (edit JSON → commit → PR → CI/CD → deploy), adding a new language carries a meaningful developer cost. Teams frequently ship in English-only or in two to three languages and leave the rest to browser auto-translate. With Tolgee, a translator can add a full language through the in-context editor without touching code — dramatically lowering the cost of shipping native localizations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Curipod case study&lt;/strong&gt; makes this concrete: an EdTech platform with 20+ language targets saw &lt;strong&gt;300+ crashes per day&lt;/strong&gt; from exactly this mechanism — teachers in classrooms relying on Chrome's auto-translate because the app did not have their language natively. When they migrated to Tolgee, the low friction of in-context editing made it feasible to localize into all 20+ languages properly. Native localizations meant users stopped needing the browser translator. Crashes dropped to zero.&lt;/p&gt;

&lt;p&gt;The fix for the crash itself is a React-level workaround (wrapping dynamic text in a &lt;code&gt;&amp;lt;span translate="no"&amp;gt;&lt;/code&gt; or using &lt;code&gt;key&lt;/code&gt; props to force remounts). But the more sustainable solution is not needing the browser translator at all — and that is what a low-friction localization workflow enables.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: Open Source — What It Actually Means for Your Stack
&lt;/h2&gt;

&lt;p&gt;Both tools in this comparison are MIT-licensed open source software. &lt;a href="https://github.com/i18next/i18next" rel="noopener noreferrer"&gt;i18next&lt;/a&gt; has been open source since 2011. Tolgee's client SDK is MIT-licensed too.&lt;/p&gt;

&lt;p&gt;The meaningful open-source distinction here is not about the client library — it is about the &lt;strong&gt;Translation Management System platform&lt;/strong&gt;. i18next is a client library; there is no i18next server to self-host. Tolgee, by contrast, ships a full TMS — the web UI, server, and database all live in &lt;a href="https://github.com/tolgee/tolgee-platform" rel="noopener noreferrer"&gt;&lt;code&gt;tolgee/tolgee-platform&lt;/code&gt;&lt;/a&gt;, while the client SDKs (React, Vue, Angular, Svelte, and the i18next integration) live in &lt;a href="https://github.com/tolgee/tolgee-js" rel="noopener noreferrer"&gt;&lt;code&gt;tolgee/tolgee-js&lt;/code&gt;&lt;/a&gt; — all open source, all MIT-licensed. That changes what self-hosting means in practice.&lt;/p&gt;

&lt;p&gt;This has concrete engineering implications that go beyond "it is free":&lt;/p&gt;

&lt;h3&gt;
  
  
  Your translation data does not leave your infrastructure
&lt;/h3&gt;

&lt;p&gt;With a SaaS-only TMS, your translation strings are stored on a third-party server. For most applications this is fine. For applications handling regulated data — healthcare, finance, government — or for teams with strict data residency requirements, it is a blocker.&lt;/p&gt;

&lt;p&gt;Self-hosting Tolgee is 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;docker run &lt;span class="nt"&gt;-v&lt;/span&gt; tolgee_data:/data &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 tolgee/tolgee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt;. Create a project. Get an API key. Update &lt;code&gt;.env.local&lt;/code&gt;. That is the entire setup. The full TMS — in-context editing, AI translation, team management, screenshot attachment, translation status tracking — runs in your own environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can audit exactly what the SDK sends over the network
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;DevTools()&lt;/code&gt; plugin runs inside your browser in dev/staging mode. With a closed-source SDK you cannot audit what data it transmits. With Tolgee, the source is public and the network calls are inspectable. For teams with strict security review requirements, this matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  No vendor lock-in
&lt;/h3&gt;

&lt;p&gt;Your translation data in Tolgee is stored in a standard database. The export formats (JSON, XLIFF, CSV, PO) are standard. If you ever need to migrate off Tolgee, you own your data in a portable format. There is no proprietary format that locks your 3,000 translation keys into one vendor's ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The community ships integrations for your stack
&lt;/h3&gt;

&lt;p&gt;Beyond React, the open-source community maintains official Tolgee SDKs for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; (App Router + Pages Router)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vue 3&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Angular&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Svelte / SvelteKit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vanilla JavaScript / Web Components&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need something that does not exist yet, you can build it. The SDK architecture is extensible — as shown by the &lt;code&gt;DevTools()&lt;/code&gt; and &lt;code&gt;FormatIcu()&lt;/code&gt; plugin pattern in this demo.&lt;/p&gt;

&lt;p&gt;If you want to go deeper, the &lt;a href="https://docs.tolgee.io" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; covers everything from project setup to CI/CD integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Comparison Table
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 7-Language Plural &amp;amp; Writing System Comparison Matrix
&lt;/h3&gt;

&lt;p&gt;Our project tests all 7 locales side-by-side across 4 distinct script families (Latin, Cyrillic, Devanagari, Arabic RTL). Both translation files (&lt;code&gt;public/locales&lt;/code&gt; and &lt;code&gt;src/locales&lt;/code&gt;) use authentic grammar and diacritics so the comparison tests each engine's handling of native ICU MessageFormat vs. standard suffix keys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Writing System&lt;/th&gt;
&lt;th&gt;CLDR Plural Forms&lt;/th&gt;
&lt;th&gt;Standard i18next Output&lt;/th&gt;
&lt;th&gt;Tolgee ICU Output&lt;/th&gt;
&lt;th&gt;Test Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;English&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;en&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Latin&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (2)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 goal&lt;/code&gt; / &lt;code&gt;2 goals&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 goal&lt;/code&gt; / &lt;code&gt;2 goals&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ Identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;French&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Latin&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (2)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 but&lt;/code&gt; / &lt;code&gt;2 buts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 but&lt;/code&gt; / &lt;code&gt;2 buts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ Identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hindi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Devanagari&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (2)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0/1 गोल&lt;/code&gt; / &lt;code&gt;2 गोल&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0/1 गोल&lt;/code&gt; / &lt;code&gt;2 गोल&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ Identical (&lt;code&gt;0-as-one&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Czech&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cs-CZ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Latin + Diacritics&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one&lt;/code&gt; / &lt;code&gt;few&lt;/code&gt; / &lt;code&gt;many&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (4)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2 gólů&lt;/code&gt; ❌ (&lt;em&gt;falls to _other&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2 góly&lt;/code&gt; ✅ (&lt;em&gt;native few&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;🏆 Tolgee Wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Polish&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Latin + Diacritics&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one&lt;/code&gt; / &lt;code&gt;few&lt;/code&gt; / &lt;code&gt;many&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (4)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2 goli&lt;/code&gt; ❌ (&lt;em&gt;falls to _other&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2 gole&lt;/code&gt; ✅ (&lt;em&gt;native few&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;🏆 Tolgee Wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Russian&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ru&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cyrillic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one&lt;/code&gt; / &lt;code&gt;few&lt;/code&gt; / &lt;code&gt;many&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (4)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2 голов&lt;/code&gt; ❌ (&lt;em&gt;falls to _other&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2 гола&lt;/code&gt; ✅ (&lt;em&gt;native few&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;🏆 Tolgee Wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Arabic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Arabic (RTL)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;zero&lt;/code&gt; / &lt;code&gt;one&lt;/code&gt; / &lt;code&gt;two&lt;/code&gt; / &lt;code&gt;few&lt;/code&gt; / &lt;code&gt;many&lt;/code&gt; / &lt;code&gt;other&lt;/code&gt; (&lt;strong&gt;6&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;Fails on 4 of 6 forms ❌&lt;/td&gt;
&lt;td&gt;Correct across all 6 forms ✅&lt;/td&gt;
&lt;td&gt;🏆 Tolgee Wins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  2. Full Engine Feature &amp;amp; Architecture Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Architecture&lt;/th&gt;
&lt;th&gt;Standard i18next (&lt;code&gt;react-i18next&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Tolgee SDK + Cloud / Self-Hosted TMS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plural handling across 7 locales&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_one&lt;/code&gt; / &lt;code&gt;_other&lt;/code&gt; suffix keys by default — &lt;code&gt;few&lt;/code&gt;/&lt;code&gt;two&lt;/code&gt;/&lt;code&gt;zero&lt;/code&gt; silently fall back to &lt;code&gt;_other&lt;/code&gt; unless extra plugins are configured&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Native ICU MessageFormat&lt;/strong&gt; — all CLDR categories (&lt;code&gt;zero&lt;/code&gt; to &lt;code&gt;other&lt;/code&gt;) resolved automatically within one string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slavic &amp;amp; Arabic Plural Accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Incorrect outputs for counts 2–4 (Slavic) and dual/few/many (Arabic)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100% Grammatically Correct&lt;/strong&gt; verified by 27 automated unit tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Right-to-Left (RTL) Layout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires separate manual DOM/CSS orchestration&lt;/td&gt;
&lt;td&gt;Seamlessly integrates with multilingual dashboard state (&lt;code&gt;dir="rtl"&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configuration Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15-line &lt;code&gt;i18n.ts&lt;/code&gt; (+ extra setup for &lt;code&gt;i18next-icu&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;15-line &lt;code&gt;tolgee.ts&lt;/code&gt; (&lt;code&gt;DevTools()&lt;/code&gt; + &lt;code&gt;FormatIcu()&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Translation Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static JSON files in &lt;code&gt;public/locales/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Connected Tolgee Cloud / Self-Hosted TMS + &lt;code&gt;staticData&lt;/code&gt; bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Copy Edit Workflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Edit JSON → Commit → PR → CI/CD build → Production deploy&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;Alt + Click&lt;/code&gt;&lt;/strong&gt; → Edit in overlay → Save → Instantly live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Translator &amp;amp; QA Tooling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None built-in (static code files)&lt;/td&gt;
&lt;td&gt;Live in-context visual editor, AI translation suggestions, screenshot context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser Auto-Translate Crash Risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any React app can crash when browser translator mutates DOM nodes — adding more native locales reduces user reliance on browser auto-translate&lt;/td&gt;
&lt;td&gt;Low-friction in-context editing makes it practical to ship native localizations into many more languages, reducing the need for browser translators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-Hosting Options&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A (client library only)&lt;/td&gt;
&lt;td&gt;One Docker command (&lt;code&gt;docker run tolgee/tolgee&lt;/code&gt;) or Azure Marketplace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline Reliability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local JSON files&lt;/td&gt;
&lt;td&gt;Automatic fallback to bundled &lt;code&gt;staticData&lt;/code&gt; if offline/API unconfigured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;MIT&lt;/strong&gt; (Open Source SDK + Open Source Platform)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Stick with i18next if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have an existing codebase with thousands of keys already in i18next format and the migration cost outweighs the benefits&lt;/li&gt;
&lt;li&gt;Your target languages all follow standard two-form plural rules (English, Spanish, Portuguese, German)&lt;/li&gt;
&lt;li&gt;Copy changes are infrequent and always developer-driven&lt;/li&gt;
&lt;li&gt;You have custom tooling (scripts, pipelines, backend endpoints) tightly coupled to static JSON artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Start with Tolgee if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You are building a new React/Next.js/Vite project from scratch&lt;/li&gt;
&lt;li&gt;Your target locales include Slavic languages (Czech, Polish, Russian, Slovak), Arabic, Welsh, or any CLDR language with more than two plural forms&lt;/li&gt;
&lt;li&gt;You want translators, product managers, or clients to be able to edit copy without filing developer tickets&lt;/li&gt;
&lt;li&gt;You want to self-host your translation infrastructure with full data ownership&lt;/li&gt;
&lt;li&gt;You want AI-assisted translation suggestions built into the editorial workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Running the Demo Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;repo&amp;gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;tolgee-demo
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev     &lt;span class="c"&gt;# → http://localhost:5173&lt;/span&gt;
npm run &lt;span class="nb"&gt;test&lt;/span&gt;    &lt;span class="c"&gt;# → 27 assertions across 7 locales, proves the plural difference in code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Tolgee account required. The app falls back to embedded static translations from &lt;code&gt;src/locales/&lt;/code&gt; if &lt;code&gt;VITE_TOLGEE_API_KEY&lt;/code&gt; is absent. Every UI feature works — language switching, the facts ticker, the live match feed, the standings table — the only thing that will not work without a key is saving changes through the Alt+Click editor to a remote TMS.&lt;/p&gt;

&lt;p&gt;To enable in-context editing on your own Tolgee project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env.local
VITE_TOLGEE_API_URL=https://app.tolgee.io
VITE_TOLGEE_API_KEY=tg_your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or self-host the TMS locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-v&lt;/span&gt; tolgee_data:/data &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 tolgee/tolgee
&lt;span class="c"&gt;# Then: VITE_TOLGEE_API_URL=http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;i18next is not broken. It is an excellent library that has served the JavaScript ecosystem well for over a decade. If your application targets English and a handful of Romance languages, and translation edits go through developers, it will serve you well.&lt;/p&gt;

&lt;p&gt;But "deceptively simple" is the right way to describe i18n. The Czech plural failure in this demo is not a corner case — it is what happens by default when you localize a React app into Czech with a standard i18next setup and do not specifically know to install and configure the &lt;code&gt;i18next-icu&lt;/code&gt; plugin. It ships. It is wrong. Nobody catches it until a Czech-speaking user files a bug.&lt;/p&gt;

&lt;p&gt;Tolgee was built by people who understand that the plural problem is not the only problem. The workflow is a problem. The translator experience is a problem. The browser-translate crash is a problem. The vendor lock-in of closed-source TMS platforms is a problem.&lt;/p&gt;

&lt;p&gt;The ICU format is technically correct. The in-context editor is the right workflow. Open source and self-hostable is the right infrastructure model. This demo exists to show all three of those things running at the same time, in a real application, with code you can clone and tests you can run.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Using Both Together
&lt;/h2&gt;

&lt;p&gt;This post frames i18next's default approach and Tolgee's ICU approach as alternatives, because that is the most useful way to show the difference in how they handle plurals. But in practice, the two tools are not mutually exclusive.&lt;/p&gt;

&lt;p&gt;Tolgee ships an official &lt;a href="https://www.npmjs.com/package/@tolgee/i18next" rel="noopener noreferrer"&gt;&lt;code&gt;@tolgee/i18next&lt;/code&gt;&lt;/a&gt; package — a first-party integration that sits on top of your existing i18next runtime. Many teams use i18next as the translation engine — keeping the familiar &lt;code&gt;t()&lt;/code&gt; API, namespaces, and their existing JSON locale files — while adding Tolgee on top as the Translation Management System. You get the Alt+Click in-context editor, the cloud project, the activity log, AI translation suggestions, and the full translator workflow, without rewriting any of your existing &lt;code&gt;useTranslation()&lt;/code&gt; calls.&lt;/p&gt;

&lt;p&gt;If you already have an i18next codebase and the plural handling is not a problem for your language set, that integration path is worth looking at before considering a full migration. The two tools genuinely complement each other.&lt;/p&gt;

&lt;p&gt;The decision tree is roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New project, complex plural languages → Tolgee with ICU from day one.&lt;/strong&gt; You get the full benefit of ICU's single-string plural model and the TMS workflow without any migration cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing i18next project, need TMS workflow → &lt;code&gt;@tolgee/i18next&lt;/code&gt;.&lt;/strong&gt; Keep your runtime, add Tolgee's platform on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing i18next project, plural accuracy gap → add &lt;code&gt;i18next-icu&lt;/code&gt; plugin.&lt;/strong&gt; You can adopt ICU MessageFormat within i18next without switching TMS at all.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tolgee/tolgee-platform" rel="noopener noreferrer"&gt;Tolgee GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/i18next/i18next" rel="noopener noreferrer"&gt;i18next GitHub Repository (MIT)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Akshat111111/Tolgee-vs.-i18next" rel="noopener noreferrer"&gt;My Implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.tolgee.io/" rel="noopener noreferrer"&gt;Tolgee Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@tolgee/react" rel="noopener noreferrer"&gt;@tolgee/react on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@tolgee/format-icu" rel="noopener noreferrer"&gt;@tolgee/format-icu on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@tolgee/i18next" rel="noopener noreferrer"&gt;@tolgee/i18next on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cldr.unicode.org/index/cldr-spec/plural-rules" rel="noopener noreferrer"&gt;Unicode CLDR Plural Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unicode-org.github.io/icu/userguide/format_parse/messages/" rel="noopener noreferrer"&gt;ICU MessageFormat Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Tetrix vs Claude</title>
      <dc:creator>Akshat Sharma</dc:creator>
      <pubDate>Sat, 01 Nov 2025 06:07:44 +0000</pubDate>
      <link>https://dev.to/akkii2022/tetrix-vs-claude-4923</link>
      <guid>https://dev.to/akkii2022/tetrix-vs-claude-4923</guid>
      <description>&lt;p&gt;Learn how Tetrix connects code, infrastructure, and operations to your AI, enabling it to reason across your full software system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deskree.com/how-it-works" rel="noopener noreferrer"&gt;https://deskree.com/how-it-works&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>devops</category>
      <category>mcp</category>
    </item>
    <item>
      <title>GitHub Co‑Creator: Tetrix for Codebase Mastery</title>
      <dc:creator>Akshat Sharma</dc:creator>
      <pubDate>Thu, 30 Oct 2025 06:16:54 +0000</pubDate>
      <link>https://dev.to/akkii2022/github-co-creator-tetrix-for-codebase-mastery-4bnp</link>
      <guid>https://dev.to/akkii2022/github-co-creator-tetrix-for-codebase-mastery-4bnp</guid>
      <description>&lt;p&gt;See how Tetrix uses your repository context to speed up navigation, generation, and review. With your code indexed in a knowledge graph, Tetrix can find the right files instantly, generate code that follows your conventions, map dependencies across repos, and review PRs with inline references.&lt;/p&gt;

&lt;p&gt;You’ll learn&lt;/p&gt;

&lt;p&gt;Graph-aware code search &amp;amp; cross-repo dependency mapping&lt;br&gt;
Repo-aware generation aligned to your framework and lint rules&lt;br&gt;
Practical PR review, and repo health checks.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Resources&lt;br&gt;
Full Video: &lt;a href="https://www.youtube.com/watch?v=iTARwCNLFtU" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=iTARwCNLFtU&lt;/a&gt;&lt;br&gt;
🌐 Learn more: &lt;a href="https://www.deskree.com" rel="noopener noreferrer"&gt;https://www.deskree.com&lt;/a&gt;&lt;br&gt;
📘 Docs: &lt;a href="https://docs.deskree.com" rel="noopener noreferrer"&gt;https://docs.deskree.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Tetrix Overview - Giving AI context across your entire system</title>
      <dc:creator>Akshat Sharma</dc:creator>
      <pubDate>Thu, 30 Oct 2025 03:02:23 +0000</pubDate>
      <link>https://dev.to/akkii2022/tetrix-overview-giving-ai-context-across-your-entire-system-3e4b</link>
      <guid>https://dev.to/akkii2022/tetrix-overview-giving-ai-context-across-your-entire-system-3e4b</guid>
      <description>&lt;p&gt;Connect AWS and GitHub to Tetrix AI – Your Entire Stack, Understood&lt;/p&gt;

&lt;p&gt;Tetrix AI brings deep context awareness to your engineering stack — connecting your GitHub repositories and AWS infrastructure so your AI can reason across real code, services, and environments.&lt;/p&gt;

&lt;p&gt;Connect your GitHub account and select specific repositories and branches.&lt;/p&gt;

&lt;p&gt;Integrate AWS securely using read-only roles and scoped permissions.&lt;/p&gt;

&lt;p&gt;View and manage integrations directly from the Tetrix dashboard.&lt;/p&gt;

&lt;p&gt;Ask natural-language questions about your own infrastructure — from IAM roles to repo insights — and get grounded, real-time answers.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Resources&lt;/p&gt;

&lt;p&gt;Full Video: &lt;a href="https://www.youtube.com/watch?v=syoaxJla_EY" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=syoaxJla_EY&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;🌐 Learn more: &lt;a href="https://www.deskree.com" rel="noopener noreferrer"&gt;https://www.deskree.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;Stay Connected&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
