<?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: Eray Gündoğmuş</title>
    <description>The latest articles on DEV Community by Eray Gündoğmuş (@erayg).</description>
    <link>https://dev.to/erayg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1028953%2F8de067da-fa24-4837-99df-abd355c119d5.jpeg</url>
      <title>DEV Community: Eray Gündoğmuş</title>
      <link>https://dev.to/erayg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erayg"/>
    <language>en</language>
    <item>
      <title>Build a Multilingual Next.js App in 5 Minutes with App Router</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Mon, 02 Mar 2026 22:21:56 +0000</pubDate>
      <link>https://dev.to/erayg/build-a-multilingual-nextjs-app-in-5-minutes-with-app-router-5fd2</link>
      <guid>https://dev.to/erayg/build-a-multilingual-nextjs-app-in-5-minutes-with-app-router-5fd2</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;A production-ready Next.js 15 + App Router i18n starter template with server-side translations, instant locale switching, and 15 languages out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/better-i18n/nextjs-i18n-starter" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://nextjs-i18n-starter.vercel.app/en" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Next.js 15&lt;/strong&gt; with App Router and Server Components&lt;/li&gt;
&lt;li&gt;🌍 &lt;strong&gt;15 languages&lt;/strong&gt; pre-configured (EN, TR, DE, ES, FR, JA, KO, ZH, AR, RU, IT, NL, HI, PL, PT)&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Instant locale switching&lt;/strong&gt; — no full page reload&lt;/li&gt;
&lt;li&gt;🖥️ &lt;strong&gt;SSR translations&lt;/strong&gt; — no flash of untranslated content&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Cloud-managed translations&lt;/strong&gt; via &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;better-i18n&lt;/a&gt; CDN&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;Tailwind CSS 4&lt;/strong&gt; + shadcn/ui components&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;SEO-ready&lt;/strong&gt; — hreflang tags, Open Graph, JSON-LD schemas, sitemap&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Clone and install&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app &lt;span class="nt"&gt;-e&lt;/span&gt; https://github.com/better-i18n/nextjs-i18n-starter my-i18n-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-i18n-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Create a free project at &lt;a href="https://dash.better-i18n.com" rel="noopener noreferrer"&gt;dash.better-i18n.com&lt;/a&gt;, add your languages, and set the env variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'NEXT_PUBLIC_BETTER_I18N_PROJECT=your-org/your-project'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Run it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;span class="c"&gt;# Open http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. You have a multilingual Next.js app.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The entire setup is &lt;strong&gt;3 files&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Config (&lt;code&gt;i18n.config.ts&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createI18n&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="s2"&gt;@better-i18n/next&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;i18n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createI18n&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BETTER_I18N_PROJECT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;better-i18n/demo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;localePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware (&lt;code&gt;middleware.ts&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;i18n&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="s2"&gt;./i18n.config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;betterMiddleware&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, that's it. One line handles locale detection, redirects, and path prefixing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout (&lt;code&gt;src/app/[locale]/layout.tsx&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LocaleLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nProvider&lt;/span&gt; &lt;span class="na"&gt;config&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translations load server-side from CDN → passed to provider → available everywhere via &lt;code&gt;useTranslations()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add a new language
&lt;/h3&gt;

&lt;p&gt;Just add it in the &lt;a href="https://dash.better-i18n.com" rel="noopener noreferrer"&gt;better-i18n dashboard&lt;/a&gt;. The &lt;code&gt;useManifestLanguages&lt;/code&gt; hook in the language switcher picks it up automatically — no code changes needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a new page
&lt;/h3&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/[locale]/pricing/page.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;useTranslations&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="s2"&gt;next-intl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingPage&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pricing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your translation keys in the dashboard, and the page works in all languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy to Vercel
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;span class="c"&gt;# Or push to GitHub and connect to Vercel for automatic deploys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The starter works out of the box on Vercel with zero configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://github.com/better-i18n/nextjs-i18n-starter" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;a href="https://nextjs-i18n-starter.vercel.app/en" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://docs.better-i18n.com" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://dash.better-i18n.com" rel="noopener noreferrer"&gt;Dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏠 &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;better-i18n.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is useful, drop a ⭐ on the &lt;a href="https://github.com/better-i18n/nextjs-i18n-starter" rel="noopener noreferrer"&gt;repo&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>i18n</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Best i18n Libraries for Next.js, React &amp; React Native in 2026 (Honest Comparison)</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Mon, 02 Mar 2026 15:30:25 +0000</pubDate>
      <link>https://dev.to/erayg/best-i18n-libraries-for-nextjs-react-react-native-in-2026-honest-comparison-3m8f</link>
      <guid>https://dev.to/erayg/best-i18n-libraries-for-nextjs-react-react-native-in-2026-honest-comparison-3m8f</guid>
      <description>&lt;p&gt;If you're building a multilingual app in 2026, the i18n landscape has never been more competitive — or more confusing. The Next.js App Router killed the old routing-based i18n approach, React Server Components changed how translations load, and AI-powered workflows are reshaping the entire localization pipeline.&lt;/p&gt;

&lt;p&gt;We reviewed &lt;strong&gt;12 libraries and platforms&lt;/strong&gt; across five dimensions: &lt;strong&gt;developer experience, type safety, performance, ecosystem maturity, and scalability&lt;/strong&gt;. This guide helps you pick the right tool — whether you're a solo developer or an enterprise team shipping to 40+ locales.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Transparency note:&lt;/strong&gt; We built &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;Better i18n&lt;/a&gt;, so we list it first and know it best. But this comparison is honest — we include real limitations and tell you when a competitor is the better choice for your use case.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  TL;DR — Quick Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;React&lt;/th&gt;
&lt;th&gt;Next.js App Router&lt;/th&gt;
&lt;th&gt;React Native&lt;/th&gt;
&lt;th&gt;TypeScript&lt;/th&gt;
&lt;th&gt;Bundle Size&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Better i18n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (Expo)&lt;/td&gt;
&lt;td&gt;✅ (full)&lt;/td&gt;
&lt;td&gt;~2KB SDK&lt;/td&gt;
&lt;td&gt;AI translations + CDN delivery + DX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;next-intl&lt;/strong&gt;&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;/td&gt;
&lt;td&gt;457B&lt;/td&gt;
&lt;td&gt;Next.js-only projects, quick setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;react-i18next&lt;/strong&gt;&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;/td&gt;
&lt;td&gt;~6KB&lt;/td&gt;
&lt;td&gt;Large teams with existing i18next knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LinguiJS&lt;/strong&gt;&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;/td&gt;
&lt;td&gt;~2KB&lt;/td&gt;
&lt;td&gt;Compile-time safety, minimal bundles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Paraglide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (full)&lt;/td&gt;
&lt;td&gt;Tree-shaken&lt;/td&gt;
&lt;td&gt;Translated URLs, type-safe DX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FormatJS (react-intl)&lt;/strong&gt;&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;/td&gt;
&lt;td&gt;~20KB&lt;/td&gt;
&lt;td&gt;Complex ICU message formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;next-translate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (Pages)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;498B&lt;/td&gt;
&lt;td&gt;Legacy Next.js Pages Router projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Intlayer&lt;/strong&gt;&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;/td&gt;
&lt;td&gt;~8KB&lt;/td&gt;
&lt;td&gt;Enterprise SEO + component-scoped i18n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;typesafe-i18n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (full)&lt;/td&gt;
&lt;td&gt;~1KB&lt;/td&gt;
&lt;td&gt;Maximum type safety, zero deps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;zero-intl&lt;/strong&gt;&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;/td&gt;
&lt;td&gt;~15KB&lt;/td&gt;
&lt;td&gt;Lightweight modern React apps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How We Evaluated
&lt;/h2&gt;

&lt;p&gt;We scored each library across five dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Developer Experience (DX)&lt;/strong&gt; — Setup time, API ergonomics, documentation quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt; — Autocomplete on keys, compile-time checks, TypeScript depth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; — Bundle size, tree-shaking, runtime overhead, CDN delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem &amp;amp; Maturity&lt;/strong&gt; — Community size, maintenance frequency, plugin ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt; — How well it handles 50+ locales, 10K+ keys, large teams&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Better i18n
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A full localization platform with type-safe SDKs, AI-powered translations, git-native workflow, and instant CDN delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's different:&lt;/strong&gt; Most libraries on this list handle only the &lt;em&gt;client-side rendering&lt;/em&gt; of translations. Better i18n covers the entire pipeline — from key discovery in your codebase to AI translation to CDN delivery — so you never manually export/import JSON files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered, brand-aware translations&lt;/strong&gt; — Not generic machine translation. Uses your glossary, brand voice, and product context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AST-based key discovery&lt;/strong&gt; — Scans your codebase and finds translation keys automatically. No manual extraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git-native sync&lt;/strong&gt; — Translations come as automatic PRs to your repository. No manual file management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant CDN delivery&lt;/strong&gt; — Sub-50ms from Cloudflare's edge. No build step needed for translation updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP integration&lt;/strong&gt; — Manage translations directly from Claude or Cursor. (No other platform offers this.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11 framework SDKs&lt;/strong&gt; — React, Next.js, Vue, Angular, Svelte, Nuxt, Remix, Astro, TanStack Start, Vite, Expo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example (Next.js App Router)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/[locale]/page.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;useTranslations&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="s2"&gt;@better-i18n/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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&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;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hero.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hero.subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&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;Keys are fully typed — you get autocomplete and compile-time errors for missing keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Choose Better i18n
&lt;/h3&gt;

&lt;p&gt;✅ You want AI translations that understand your product context&lt;br&gt;
✅ You need instant CDN delivery without build steps&lt;br&gt;
✅ You're tired of manually managing JSON translation files&lt;br&gt;
✅ You want one platform for React, Next.js, Vue, and more&lt;br&gt;
✅ You work with AI tools (Claude, Cursor) and want MCP integration&lt;/p&gt;
&lt;h3&gt;
  
  
  When NOT to Choose Better i18n
&lt;/h3&gt;

&lt;p&gt;❌ You need a zero-dependency, client-only library with no external service&lt;br&gt;
❌ You already have a well-established i18next pipeline&lt;br&gt;
❌ You want a library with 5+ years of community battle-testing&lt;br&gt;
❌ Your project has zero budget (the free tier covers 1K keys / 2 languages)&lt;/p&gt;
&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free:&lt;/strong&gt; 1,000 keys, 2 languages, CDN delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro:&lt;/strong&gt; $19/mo — unlimited everything, AI translations, git sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise:&lt;/strong&gt; Custom — SSO, SLA, on-premise&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  2. next-intl
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; The most popular i18n library built specifically for Next.js App Router.&lt;/p&gt;

&lt;p&gt;next-intl has become the go-to choice for Next.js developers who want a quick, reliable setup. It integrates through a Next.js plugin, provides &lt;code&gt;useTranslations&lt;/code&gt; hooks, locale-aware routing via middleware, and JSON-based dictionaries.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&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="s2"&gt;next-intl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;About&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast setup (minutes, not hours)&lt;/li&gt;
&lt;li&gt;First-class Next.js App Router support with Server Components&lt;/li&gt;
&lt;li&gt;Active maintenance, strong community&lt;/li&gt;
&lt;li&gt;Lightweight (457B gzipped)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Next.js only — no React Native, no Vue, no standalone React&lt;/li&gt;
&lt;li&gt;You manage translation files yourself (no TMS included)&lt;/li&gt;
&lt;li&gt;No AI translation or CDN delivery built in&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;You're building a Next.js-only project, want zero overhead, and are happy managing JSON files by hand.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. react-i18next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; The most battle-tested i18n library in the React ecosystem, backed by the i18next framework.&lt;/p&gt;

&lt;p&gt;If your team already knows i18next, this is the safest bet. It supports namespaces, lazy loading, interpolation, pluralization, and virtually every pattern you can think of. The community is massive.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;


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

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;welcome_message&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Largest community and most plugins in the ecosystem&lt;/li&gt;
&lt;li&gt;Works everywhere: React, React Native, Next.js, Node.js&lt;/li&gt;
&lt;li&gt;Battle-tested at enterprise scale&lt;/li&gt;
&lt;li&gt;Extensive documentation and examples&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;More boilerplate than newer alternatives&lt;/li&gt;
&lt;li&gt;Server-side rendering requires careful configuration&lt;/li&gt;
&lt;li&gt;No built-in type safety for keys (needs extra setup)&lt;/li&gt;
&lt;li&gt;Bundle size is larger (~6KB)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;Your team already knows i18next, or you need maximum flexibility and plugin ecosystem.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. LinguiJS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A compile-time i18n library that extracts messages at build time and produces minimal bundles.&lt;/p&gt;

&lt;p&gt;LinguiJS takes a fundamentally different approach. Instead of loading JSON files at runtime, it compiles translations at build time. You write messages using macros directly in your components, then run extraction and compilation steps.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Trans&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@lingui/macro&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;Welcome&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Trans&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;, welcome back!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Trans&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Smallest possible runtime bundle (~2KB)&lt;/li&gt;
&lt;li&gt;Build-time checks catch missing translations&lt;/li&gt;
&lt;li&gt;ICU message format support&lt;/li&gt;
&lt;li&gt;Tree-shaking: only ships messages actually used&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requires Babel/SWC plugin configuration&lt;/li&gt;
&lt;li&gt;Build-time extraction adds complexity to CI/CD&lt;/li&gt;
&lt;li&gt;Steeper learning curve for macro-based API&lt;/li&gt;
&lt;li&gt;Smaller community than react-i18next&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;Performance is critical, your team is comfortable with build tooling, and you want compile-time guarantees.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Paraglide (by Inlang)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A modern, fully type-safe i18n library with tree-shakeable messages and translated URL pathnames.&lt;/p&gt;

&lt;p&gt;Paraglide generates type-safe functions from your messages, so every translation key is a function call with full autocomplete. It also supports translated pathnames — your URLs can be &lt;code&gt;/en/about&lt;/code&gt; and &lt;code&gt;/de/ueber-uns&lt;/code&gt; natively.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Full type safety with generated message functions&lt;/li&gt;
&lt;li&gt;Tree-shakeable: zero unused translations in bundle&lt;/li&gt;
&lt;li&gt;Translated pathnames for localized URLs&lt;/li&gt;
&lt;li&gt;GUI editor for translators (via Inlang ecosystem)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Younger ecosystem, fewer production case studies&lt;/li&gt;
&lt;li&gt;No React Native support&lt;/li&gt;
&lt;li&gt;Requires code generation step&lt;/li&gt;
&lt;li&gt;Smaller community&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;You prioritize type safety and localized URLs, and you're building for web only.&lt;/p&gt;


&lt;h2&gt;
  
  
  6. FormatJS (react-intl)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A comprehensive i18n framework with full ICU message format support.&lt;/p&gt;

&lt;p&gt;FormatJS is the gold standard for complex message formatting — plurals, select statements, gender variations, and rich text. If your content has phrases like "You have {count, plural, one {# item} other {# items}} in your cart," FormatJS handles it natively.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Most complete ICU message format implementation&lt;/li&gt;
&lt;li&gt;Handles complex pluralization and gender rules&lt;/li&gt;
&lt;li&gt;Works across React, React Native, and Node.js&lt;/li&gt;
&lt;li&gt;Backed by a large, long-standing community&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Largest bundle size (~20KB gzipped)&lt;/li&gt;
&lt;li&gt;Verbose API compared to newer libraries&lt;/li&gt;
&lt;li&gt;Setup is heavier than lightweight alternatives&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;Your app has complex linguistic requirements (multiple pluralization rules, gender-dependent messages, rich text interpolation).&lt;/p&gt;


&lt;h2&gt;
  
  
  7. Intlayer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; An opinionated, enterprise-focused i18n solution with component-scoped translations and built-in SEO helpers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Component-scoped translation declarations&lt;/li&gt;
&lt;li&gt;Built-in SEO helpers (hreflang, meta tags)&lt;/li&gt;
&lt;li&gt;Strict TypeScript types with build-time validation&lt;/li&gt;
&lt;li&gt;Missing key detection at build time&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Heavier and more opinionated than alternatives&lt;/li&gt;
&lt;li&gt;Smaller community&lt;/li&gt;
&lt;li&gt;Steeper learning curve&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;Enterprise projects that need SEO integration and strict build-time validation.&lt;/p&gt;


&lt;h2&gt;
  
  
  8. typesafe-i18n
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A zero-dependency, fully type-safe i18n library with code generation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Smallest runtime (~1KB + generated code)&lt;/li&gt;
&lt;li&gt;Full type safety with autocomplete&lt;/li&gt;
&lt;li&gt;Zero external dependencies&lt;/li&gt;
&lt;li&gt;Works with React, Svelte, Vue, Vanilla JS&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Last major update in 2023&lt;/li&gt;
&lt;li&gt;Requires code generation&lt;/li&gt;
&lt;li&gt;Custom message format (not standard ICU)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  When to Choose
&lt;/h3&gt;

&lt;p&gt;You want absolute minimal bundle size and full type safety in a smaller project.&lt;/p&gt;


&lt;h2&gt;
  
  
  Decision Framework
&lt;/h2&gt;

&lt;p&gt;Still not sure? Here's a flowchart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do you need a full platform (TMS + CDN + AI)?
  ├── Yes → Better i18n
  └── No → Continue...

Are you using Next.js App Router only?
  ├── Yes → next-intl (fastest setup)
  └── No → Continue...

Do you need React Native support?
  ├── Yes → react-i18next or Better i18n
  └── No → Continue...

Is bundle size your #1 priority?
  ├── Yes → LinguiJS or typesafe-i18n
  └── No → Continue...

Do you need complex ICU formatting?
  ├── Yes → FormatJS (react-intl)
  └── No → Continue...

Do you want type-safe translated URLs?
  ├── Yes → Paraglide
  └── No → react-i18next (safest default)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Bundle Size (gzip)&lt;/th&gt;
&lt;th&gt;Runtime Overhead&lt;/th&gt;
&lt;th&gt;Cold Start Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Better i18n SDK&lt;/td&gt;
&lt;td&gt;~2KB&lt;/td&gt;
&lt;td&gt;Minimal (CDN fetch)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;next-intl&lt;/td&gt;
&lt;td&gt;457B&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-i18next&lt;/td&gt;
&lt;td&gt;~6KB&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinguiJS&lt;/td&gt;
&lt;td&gt;~2KB&lt;/td&gt;
&lt;td&gt;Near-zero (compiled)&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paraglide&lt;/td&gt;
&lt;td&gt;Tree-shaken&lt;/td&gt;
&lt;td&gt;Near-zero&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FormatJS&lt;/td&gt;
&lt;td&gt;~20KB&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typesafe-i18n&lt;/td&gt;
&lt;td&gt;~1KB&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;There's no single "best" i18n library — only the best one for your context. Here's our honest ranking by use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best all-in-one platform:&lt;/strong&gt; Better i18n — if you want AI translations, CDN delivery, and git sync without stitching together multiple tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for Next.js simplicity:&lt;/strong&gt; next-intl — drop it in and go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for large existing teams:&lt;/strong&gt; react-i18next — community knowledge and plugin ecosystem are unmatched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for performance purists:&lt;/strong&gt; LinguiJS — compile-time guarantees and minimal bundles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for type-safe DX:&lt;/strong&gt; Paraglide — every key is a typed function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for complex messages:&lt;/strong&gt; FormatJS — full ICU power.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever you choose, pick a tool that grows with your project. Migrating i18n libraries mid-project is one of the most painful refactors in frontend development. Measure twice, ship once.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>nextjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Scaling i18n Beyond Lazy Loading: What Framework Comparisons Miss About Real-World Localization</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Sun, 01 Mar 2026 15:02:55 +0000</pubDate>
      <link>https://dev.to/erayg/scaling-i18n-beyond-lazy-loading-what-framework-comparisons-miss-about-real-world-localization-4o08</link>
      <guid>https://dev.to/erayg/scaling-i18n-beyond-lazy-loading-what-framework-comparisons-miss-about-real-world-localization-4o08</guid>
      <description>&lt;p&gt;I've read a lot of "how we scaled our i18n" posts. They almost all follow the same arc: team evaluates frameworks, picks i18next or FormatJS, implements lazy loading per namespace, ships it, writes the blog post. Bundle size goes down, page load improves, everyone's happy.&lt;/p&gt;

&lt;p&gt;I know because we wrote that exact post internally 18 months ago. Then we spent the next 12 months dealing with everything the framework comparison didn't cover.&lt;/p&gt;

&lt;p&gt;This post is about those 12 months. Not the framework choice — the infrastructure, workflow, and operational problems that show up at scale but never make it into the comparison table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The framework comparison trap
&lt;/h2&gt;

&lt;p&gt;Most i18n framework evaluations look something like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;i18next&lt;/th&gt;
&lt;th&gt;FormatJS&lt;/th&gt;
&lt;th&gt;Polyglot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size (core)&lt;/td&gt;
&lt;td&gt;~40KB&lt;/td&gt;
&lt;td&gt;~32KB&lt;/td&gt;
&lt;td&gt;~3KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lazy loading&lt;/td&gt;
&lt;td&gt;Plugin-based&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ICU support&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React integration&lt;/td&gt;
&lt;td&gt;react-i18next&lt;/td&gt;
&lt;td&gt;react-intl&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is accurate and useful for picking a library. It is almost entirely useless for predicting what your localization workflow will look like at 500+ keys, 6+ languages, and a team of 8+ developers shipping weekly.&lt;/p&gt;

&lt;p&gt;Here's what the comparison table doesn't tell you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How will you detect unused translation keys after a refactor?&lt;/li&gt;
&lt;li&gt;What happens when two feature branches modify the same locale file?&lt;/li&gt;
&lt;li&gt;How long does it take to push a one-word translation fix to production?&lt;/li&gt;
&lt;li&gt;How will your translators get context about what they're translating?&lt;/li&gt;
&lt;li&gt;Who maintains the mapping between translation keys and the codebase?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are the problems that actually consume engineering time. The framework is a pipe fitting. The plumbing is everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: The namespace illusion
&lt;/h2&gt;

&lt;p&gt;Namespace splitting is the first optimization every team implements. Instead of one massive &lt;code&gt;translation.json&lt;/code&gt;, you split into &lt;code&gt;auth.json&lt;/code&gt;, &lt;code&gt;dashboard.json&lt;/code&gt;, &lt;code&gt;settings.json&lt;/code&gt;, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// i18next namespace config&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;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth&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;dashboard&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;settings&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;billing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;defaultNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;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}}/{{ns}}.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With lazy loading, each namespace loads on demand:&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;// Only loads 'dashboard' namespace when this route mounts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DashboardPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bundle problem: solved. Performance: improved. Blog post: written.&lt;/p&gt;

&lt;p&gt;But here's what happens next.&lt;/p&gt;

&lt;h3&gt;
  
  
  The namespace decision tax
&lt;/h3&gt;

&lt;p&gt;Developer writes a new error component for the billing page. Where does &lt;code&gt;billing.errors.cardDeclined&lt;/code&gt; go? In &lt;code&gt;billing.json&lt;/code&gt; or &lt;code&gt;errors.json&lt;/code&gt;? If you have both namespaces, now you have a naming convention meeting. If you decide it goes in &lt;code&gt;billing.json&lt;/code&gt;, what happens when the same error also shows on the settings page where users update their payment method?&lt;/p&gt;

&lt;p&gt;We tracked this over 4 sprints. Developers spent an average of &lt;strong&gt;7 minutes per new key&lt;/strong&gt; deciding which namespace it belonged to. With 15-20 new keys per sprint, that's 2+ hours of namespace deliberation per sprint across the team. And that's before the reviewer disagrees and asks for a rename.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cross-namespace import problem
&lt;/h3&gt;

&lt;p&gt;Components that span multiple features need multiple namespaces:&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;// A notification component that shows messages from different domains&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NotificationItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;notification&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tBilling&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;billing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tDashboard&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;billing&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="nf"&gt;tBilling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`notifications.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notification&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth&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="nf"&gt;tAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`notifications.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notification&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;tDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`notifications.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notification&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="s2"&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defeats the purpose of lazy loading. You're now loading 3 namespaces for a single component. Worse, you've created implicit coupling between namespaces and feature boundaries that breaks every time someone moves a component.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually works
&lt;/h3&gt;

&lt;p&gt;Flat keys with convention-based prefixes, no namespace boundaries:&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;// No namespaces. One flat map. Convention handles organization.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;billing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;errors.cardDeclined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// → "billing.errors.cardDeclined"&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifications.overdue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// → "billing.notifications.overdue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The namespace is a logical prefix, not a file boundary. No files to split. No lazy loading configuration per namespace. The entire locale payload is edge-cached on a CDN and delivered in a single request.&lt;/p&gt;

&lt;p&gt;"But won't that be slow?" At 1,000 keys, a gzipped JSON payload is ~18KB. That's smaller than most hero images. At 5,000 keys, it's ~70KB. A single CDN request at ~2ms from the nearest PoP beats the waterfall of 8 namespace requests at ~40ms each, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: The merge conflict multiplier
&lt;/h2&gt;

&lt;p&gt;If your translations live as JSON files in the repo, every feature branch that adds a key modifies the same file. With a team of 6 developers, you're touching &lt;code&gt;en.json&lt;/code&gt; in every sprint, often in every PR.&lt;/p&gt;

&lt;p&gt;Git's diff algorithm treats JSON as text. A single reordered key produces a multi-line diff. Two developers adding keys in the same file — even in different sections — produces a merge conflict because JSON doesn't have a natural merge strategy.&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;Developer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;adds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;847&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"billing.newPlan.title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Choose your plan"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Developer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;adds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;848&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"billing.upgrade.cta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Upgrade now"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Git:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CONFLICT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Namespace splitting reduces conflict frequency but doesn't eliminate it. With 5 namespaces and 6 developers, you still get 1-2 conflicts per sprint. Each conflict requires someone to manually resolve a JSON file, re-validate the JSON syntax, and re-run the translation CI check.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually works
&lt;/h3&gt;

&lt;p&gt;Remove JSON files from the repo entirely.&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;// Translations fetched from CDN at request time&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;getMessages&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;@better-i18n/use-intl/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IntlProvider&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IntlProvider&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;No &lt;code&gt;locales/&lt;/code&gt; directory in the repo. No JSON files to conflict. Keys are managed on a platform and served via CDN. If you want a Git record, the platform creates archive PRs — but they're documentation, not source of truth.&lt;/p&gt;

&lt;p&gt;The Git PR flow inverts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:  Code → JSON files → TMS → JSON files → Merge conflicts
After:   Code → Platform → CDN (live) + Git PR (archive)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problem 3: The ghost key problem
&lt;/h2&gt;

&lt;p&gt;After 6 months of active development, we had 2,400 translation keys. After running a dead code analysis, 340 of them (14%) weren't referenced anywhere in the codebase.&lt;/p&gt;

&lt;p&gt;These ghost keys accumulated over multiple refactors. When you rename a component from &lt;code&gt;&amp;lt;OnboardingWizard&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;SetupFlow&amp;gt;&lt;/code&gt;, the code changes but nobody remembers to delete &lt;code&gt;onboarding.wizard.step1.title&lt;/code&gt; from the JSON file. Why? Because deleting a key feels risky. What if it's used somewhere you didn't check? What if another component imports it dynamically?&lt;/p&gt;

&lt;p&gt;So the keys stay. And your translators translate all of them. At $0.08/word across 6 languages, those 340 ghost keys cost us roughly $1,100 in unnecessary translation work over 6 months. That's before you count the translator time wasted on context questions about strings that don't correspond to any visible UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why regex-based detection fails
&lt;/h3&gt;

&lt;p&gt;The obvious fix is to scan the codebase for key references. Most teams write a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find keys in JSON that aren't referenced in source&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"onboarding.wizard"&lt;/span&gt; src/ &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.tsx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Namespaced keys: &lt;code&gt;t('step1.title')&lt;/code&gt; inside a component that called &lt;code&gt;useTranslations('onboarding.wizard')&lt;/code&gt; — the full key is &lt;code&gt;onboarding.wizard.step1.title&lt;/code&gt;, but the source code only has &lt;code&gt;step1.title&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dynamic keys: &lt;code&gt;t(\&lt;/code&gt;error.${code}&lt;code&gt;)&lt;/code&gt; — you can't statically resolve the full key.&lt;/li&gt;
&lt;li&gt;Passed references: &lt;code&gt;&amp;lt;Child t={t} /&amp;gt;&lt;/code&gt; — the key usage is in the child, but the namespace binding is in the parent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What actually works: AST scanning
&lt;/h3&gt;

&lt;p&gt;Parse the actual syntax tree, not the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx @better-i18n/cli scan

Scanning src/ &lt;span class="k"&gt;for &lt;/span&gt;translation keys...

Found 2,412 keys &lt;span class="k"&gt;in &lt;/span&gt;project
  ├── 2,072 matched &lt;span class="o"&gt;(&lt;/span&gt;used &lt;span class="k"&gt;in &lt;/span&gt;code&lt;span class="o"&gt;)&lt;/span&gt;
  ├── 340 unused &lt;span class="o"&gt;(&lt;/span&gt;safe to remove&lt;span class="o"&gt;)&lt;/span&gt;
  └── 18 dynamic &lt;span class="o"&gt;(&lt;/span&gt;manual review needed&lt;span class="o"&gt;)&lt;/span&gt;

Dynamic keys &lt;span class="o"&gt;(&lt;/span&gt;cannot statically resolve&lt;span class="o"&gt;)&lt;/span&gt;:
  src/components/ErrorBanner.tsx:14  →  t&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;error.&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  src/lib/notifications.ts:28       →  t&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;notification.&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.title&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AST parser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds all &lt;code&gt;useTranslations('namespace')&lt;/code&gt; calls and tracks the bound variable&lt;/li&gt;
&lt;li&gt;Follows the variable through the component scope&lt;/li&gt;
&lt;li&gt;When it sees &lt;code&gt;t('key')&lt;/code&gt;, resolves the full qualified key: &lt;code&gt;namespace.key&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cross-references against the platform's key inventory&lt;/li&gt;
&lt;li&gt;Flags dynamic keys (template literals, computed values) for manual review&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This runs in CI. Every PR gets a check: "This PR removes 3 key references. The following keys will become unused: [list]." No ghost keys accumulate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 4: The 47-minute typo fix
&lt;/h2&gt;

&lt;p&gt;Your German translator finds that "Weiter" (next) should be "Fortfahren" (continue) on the checkout page. One word.&lt;/p&gt;

&lt;p&gt;With file-based i18n, pushing that fix requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open translation file or TMS&lt;/li&gt;
&lt;li&gt;Edit the string&lt;/li&gt;
&lt;li&gt;If TMS: wait for sync → PR bot → merge → build → deploy&lt;/li&gt;
&lt;li&gt;If file-based: edit JSON → commit → push → build → deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Either way, a one-word change triggers a full deployment pipeline. CI runs, tests execute, containers build, CDN cache purges. Minimum 5 minutes. Average 15-45 minutes depending on your pipeline.&lt;/p&gt;

&lt;p&gt;Now multiply this by 3-5 translation fixes per week. Your deployment pipeline is spending 15-25% of its runs on copy changes that don't touch a single line of application code.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually works: CDN-decoupled translations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Translation fix flow:
  1. Edit string in dashboard
  2. Click "Publish"
  3. CDN cache invalidates globally (~2 seconds)
  4. Next user request serves the corrected translation

Time: under 30 seconds. No build. No deploy. No PR.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the architecture-curious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → Next.js Server Component
  → getMessages({ project, locale })
    → CDN Edge (cache HIT: ~2ms / MISS: ~40ms)
      → Platform API → Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is a flat JSON object, edge-cached with &lt;code&gt;stale-while-revalidate&lt;/code&gt;. Even on cache miss, the server doesn't block — it serves stale content and revalidates in the background.&lt;/p&gt;

&lt;p&gt;For SSR/RSC, messages are fetched once in the layout and serialized into the RSC payload. No additional CDN request from the client. Hydration uses the pre-fetched messages.&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;// Server: one CDN fetch per request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Client: zero CDN fetches, messages arrive via RSC payload&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IntlProvider&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IntlProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problem 5: The context black hole
&lt;/h2&gt;

&lt;p&gt;Translators see a key and a source string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Key: checkout.summary.total_label
Value: "Total"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don't see the UI. "Total" in English is unambiguous. In German, it's three different words depending on context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Table column header → "Gesamt"&lt;/li&gt;
&lt;li&gt;Button text → "Gesamtbetrag"&lt;/li&gt;
&lt;li&gt;Invoice line item → "Summe"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your translator picks one. It's wrong. You get a bug report from a German user. A developer screenshots the UI, explains the context in Slack, the translator fixes it, and the fix goes through the deployment pipeline.&lt;/p&gt;

&lt;p&gt;This cycle repeats 5-10 times per sprint. Each cycle involves a developer context-switch (15 min), a Slack thread (30 min round-trip), and a deployment (15-45 min). That's 1-2 hours per mistranslation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What reduces this
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI translation with glossary enforcement&lt;/strong&gt;. Define domain-specific terms once:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Term (EN)    | Term (DE)       | Context                    |
|--------------|-----------------|----------------------------|
| Total        | Gesamt          | Summary labels, headers    |
| Workspace    | Arbeitsbereich  | Not "Arbeitsplatz"         |
| Dashboard    | Dashboard       | Keep English (UX standard) |
| Provider     | Anbieter        | Service provider context   |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When AI translates a new key, it checks the glossary first. "Total" always becomes "Gesamt" in summary contexts. Consistency that's mechanically impossible when human translators work independently across 800+ keys.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Screenshot context on keys&lt;/strong&gt;. Attach a visual reference to each key so translators see the component, not just the string.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review workflow with diff&lt;/strong&gt;. Translators see what changed, not the entire file. When a developer changes "Total" to "Subtotal", the translator sees the before/after in context.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Problem 6: The type safety gap
&lt;/h2&gt;

&lt;p&gt;This one is specific to TypeScript projects. Your translation keys are strings. Your IDE doesn't know which keys exist. You type &lt;code&gt;t('header.buttn_text')&lt;/code&gt; with a typo, and you find out when a user reports a blank button.&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;// No error at compile time. No error at build time. Error at runtime.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashbord.title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// typo: should be 'dashboard.title'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;i18next has &lt;code&gt;@types/i18next&lt;/code&gt; which can provide type checking if you configure it correctly. FormatJS has similar support. But both require you to generate type definitions from your JSON files and keep them in sync. That's another CI step, another file to maintain, another thing that can go stale.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually works
&lt;/h3&gt;

&lt;p&gt;Generate types from the source of truth (the platform), not from local files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx @better-i18n/cli generate-types

Generated src/i18n.d.ts with 2,072 typed keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// IDE autocomplete for every key. Red squiggly on typos.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// ✅ autocomplete&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;titl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// ❌ TypeScript error: 'titl' not in DashboardKeys&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stats.users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ✅ autocomplete with nested keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type file is generated from the platform's key inventory, which is the same data the CDN serves. One source of truth, zero drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real scaling checklist
&lt;/h2&gt;

&lt;p&gt;Framework comparisons evaluate the 10% of i18n work that involves writing &lt;code&gt;t()&lt;/code&gt; calls. Here's what you should actually evaluate when your project passes 500 keys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;File-based answer&lt;/th&gt;
&lt;th&gt;CDN-first answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How do you detect unused keys?&lt;/td&gt;
&lt;td&gt;Manual audit or regex script&lt;/td&gt;
&lt;td&gt;AST scanner in CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do you fix a translation typo?&lt;/td&gt;
&lt;td&gt;Full deploy (15-45 min)&lt;/td&gt;
&lt;td&gt;CDN publish (30 sec)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do you avoid merge conflicts?&lt;/td&gt;
&lt;td&gt;Namespace splitting (reduces, doesn't eliminate)&lt;/td&gt;
&lt;td&gt;No locale files in repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do translators get context?&lt;/td&gt;
&lt;td&gt;Screenshot in Slack&lt;/td&gt;
&lt;td&gt;Attached to key, AI-assisted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do you ensure key type safety?&lt;/td&gt;
&lt;td&gt;Generated types from local JSON (drift risk)&lt;/td&gt;
&lt;td&gt;Generated types from platform (single source)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How long is your translation pipeline?&lt;/td&gt;
&lt;td&gt;Dev → TMS → PR → Merge → Build → Deploy&lt;/td&gt;
&lt;td&gt;Dev → Platform → CDN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What's your cost per language added?&lt;/td&gt;
&lt;td&gt;Linear: new files, new conflicts, new translations&lt;/td&gt;
&lt;td&gt;Sublinear: glossary-enforced AI + human review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The migration is not the hard part
&lt;/h2&gt;

&lt;p&gt;If you're currently running i18next or FormatJS with file-based translations, the migration to a CDN-first model is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; your current JSON files (you already have them)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import&lt;/strong&gt; into a translation platform via CLI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; &lt;code&gt;i18next.init({ backend: { loadPath } })&lt;/code&gt; with &lt;code&gt;getMessages({ project, locale })&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove&lt;/strong&gt; the &lt;code&gt;locales/&lt;/code&gt; directory from your repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run&lt;/strong&gt; the AST scanner to verify key coverage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1-4 took our team about 4 hours. Step 5 found 340 unused keys in 30 seconds.&lt;/p&gt;

&lt;p&gt;The framework you use for &lt;code&gt;t()&lt;/code&gt; calls barely changes. The infrastructure around it changes completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd recommend today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick any modern i18n library.&lt;/strong&gt; i18next, FormatJS, or next-intl — they all handle the &lt;code&gt;t()&lt;/code&gt; function well. This is the least important decision you'll make.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't split into namespace files.&lt;/strong&gt; Use logical key prefixes (&lt;code&gt;billing.errors.cardDeclined&lt;/code&gt;) without file boundaries. Let the CDN serve everything in one cached response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up AST scanning from day one.&lt;/strong&gt; The longer you wait, the more ghost keys accumulate. Run it in CI so unused keys are caught in the PR that removes them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decouple translations from deployments immediately.&lt;/strong&gt; A translation fix should never trigger a build. This alone will save your team hours per week.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build a glossary before you translate.&lt;/strong&gt; 40 domain-specific terms in a glossary produces more consistent translations than a 10-page style guide nobody reads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure developer time on i18n.&lt;/strong&gt; Track it for one sprint. Count the merge conflicts, the Slack threads with translators, the namespace debates, the deployment time on copy-only changes. The number will be higher than you expect.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Our team uses &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;Better i18n&lt;/a&gt; for CDN-delivered translations, AST key scanning, and AI translation with glossary enforcement. If you're evaluating your i18n infrastructure, the free tier is enough to test whether the CDN-first model works for your workflow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How I Set Up i18n in My React/Next.js App Without Losing My Mind</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Sun, 01 Mar 2026 14:04:45 +0000</pubDate>
      <link>https://dev.to/erayg/how-i-set-up-i18n-in-my-reactnextjs-app-without-losing-my-mind-2k3o</link>
      <guid>https://dev.to/erayg/how-i-set-up-i18n-in-my-reactnextjs-app-without-losing-my-mind-2k3o</guid>
      <description>&lt;p&gt;Last month I shipped a SaaS app in 6 languages. The translation part took me 2 days. Not 2 weeks. Two days.&lt;/p&gt;

&lt;p&gt;If you've ever set up internationalization in a production app, you know that sounds suspicious. The typical i18n story goes like this: you spend a day configuring the library, another day wiring up translation files, then you lose the next two weeks chasing down missing keys, syncing JSON files back and forth with translators, and praying that your build doesn't break because someone added a comma in the wrong place.&lt;/p&gt;

&lt;p&gt;I want to share what actually worked for me, and the specific decisions that saved me from the usual i18n death spiral.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody warns you about
&lt;/h2&gt;

&lt;p&gt;Every i18n tutorial starts the same way: install a library, create a &lt;code&gt;locales/&lt;/code&gt; folder, add some JSON files. Done!&lt;/p&gt;

&lt;p&gt;Except that's only 10% of the work.&lt;/p&gt;

&lt;p&gt;The other 90% is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key management&lt;/strong&gt; - Who decides the key names? What happens when you rename a component and forget to update the key? What about unused keys from features you deleted 3 months ago?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation sync&lt;/strong&gt; - Your translator works in a spreadsheet or a separate SaaS tool. You work in Git. Someone has to bridge that gap manually, every single sprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment coupling&lt;/strong&gt; - You fix a typo in the French translation. Now you need a full redeploy to push that one-character change to production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt; - You call &lt;code&gt;t("header.buttn_text")&lt;/code&gt; with a typo, and you don't find out until a user reports a blank button in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the problems that make i18n painful at scale. The library choice matters far less than your workflow around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  My setup (and why I chose it)
&lt;/h2&gt;

&lt;p&gt;I'll walk through the actual stack I used, with code. The app is a Next.js 14 project with TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Pick a translation format
&lt;/h3&gt;

&lt;p&gt;I went with ICU MessageFormat. Not because it's trendy, but because it handles pluralization without ugly hacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Bad: separate keys for every plural form
"items_zero": "No items"
"items_one": "1 item"
"items_other": "{count} items"

// Good: one ICU key handles all cases
"items": "{count, plural, =0 {No items} one {1 item} other {{count} items}}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ICU is the format used by &lt;code&gt;react-intl&lt;/code&gt; (FormatJS) and &lt;code&gt;@better-i18n/use-intl&lt;/code&gt;. It's also what most professional translators already know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set up the provider
&lt;/h3&gt;

&lt;p&gt;Here's the minimal setup:&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;// app/[locale]/layout.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;BetterI18nProvider&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;@better-i18n/use-intl&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;getMessages&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;@better-i18n/use-intl/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LocaleLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&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="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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;locale&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nProvider&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nProvider&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;getMessages&lt;/code&gt; call fetches translations from a CDN at build/request time. No local JSON files to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Use translations in components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&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;@better-i18n/use-intl&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;PricingCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;plan&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pricing&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="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plan.title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plan.description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plan.price&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;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&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;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing revolutionary here. Every i18n library has a &lt;code&gt;t()&lt;/code&gt; function. The difference is what happens next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Handle routing
&lt;/h3&gt;

&lt;p&gt;For multilingual SEO, you need locale-prefixed URLs: &lt;code&gt;/en/pricing&lt;/code&gt;, &lt;code&gt;/de/pricing&lt;/code&gt;, &lt;code&gt;/fr/pricing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With Next.js App Router:&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;// middleware.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;createI18nMiddleware&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;@better-i18n/next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createI18nMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&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="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/((?!api|_next|.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;..*).*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This middleware detects the user's preferred language from the &lt;code&gt;Accept-Language&lt;/code&gt; header, checks if the URL already has a locale prefix, and redirects accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important for SEO&lt;/strong&gt;: use 301 redirects (not 302) for locale redirects. A 302 tells Google "this is temporary" and it won't pass link equity to the localized page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Add hreflang tags
&lt;/h3&gt;

&lt;p&gt;This is where most tutorials stop, and where most sites get penalized. Every localized page needs &lt;code&gt;&amp;lt;link rel="alternate" hreflang="x"&amp;gt;&lt;/code&gt; tags pointing to all its language variants:&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;// app/[locale]/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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;locales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* current path without locale */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://myapp.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&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;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`https://myapp.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules that took me way too long to figure out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every hreflang URL must return HTTP 200.&lt;/strong&gt; If &lt;code&gt;/en/about&lt;/code&gt; redirects to &lt;code&gt;/about&lt;/code&gt;, Google ignores the hreflang and you get "conflicting hreflang" errors in Search Console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include a &lt;code&gt;x-default&lt;/code&gt; hreflang&lt;/strong&gt; pointing to your primary language. This tells Google what to show users whose language you don't support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hreflang tags must be bidirectional.&lt;/strong&gt; If &lt;code&gt;/en/about&lt;/code&gt; says "my French version is &lt;code&gt;/fr/about&lt;/code&gt;", then &lt;code&gt;/fr/about&lt;/code&gt; must also say "my English version is &lt;code&gt;/en/about&lt;/code&gt;".&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 6: Structured data per locale
&lt;/h3&gt;

&lt;p&gt;If you have &lt;code&gt;Organization&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;, or &lt;code&gt;Article&lt;/code&gt; schema markup, the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; fields should match the page language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;structuredData&lt;/span&gt; &lt;span class="o"&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;@context&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;https://schema.org&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;@type&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;SoftwareApplication&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schema.appName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schema.appDescription&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;applicationCategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DeveloperApplication&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;operatingSystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;offers&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;@type&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;Offer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&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="na"&gt;priceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&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;Google specifically looks for language consistency between the page content and structured data. A page in German with English schema markup sends mixed signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow that actually scales
&lt;/h2&gt;

&lt;p&gt;Here's where the library comparison stops mattering and the workflow starts mattering.&lt;/p&gt;

&lt;h3&gt;
  
  
  The old way (what I did before)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Developer adds t("newFeature.title") in code
2. Developer manually adds "newFeature.title": "" to en.json
3. Developer commits, pushes, creates PR
4. PM exports en.json, uploads to translation tool
5. Translator translates in the tool
6. PM downloads de.json, fr.json, es.json...
7. PM creates a PR with the new JSON files
8. Merge conflicts. Always merge conflicts.
9. Repeat steps 4-8 for every sprint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow has a person-shaped bottleneck in the middle. The PM becomes a full-time JSON file courier.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Git-native way
&lt;/h3&gt;

&lt;p&gt;What I do now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Developer adds t("newFeature.title") in code
2. CLI scans the codebase automatically, discovers new keys
3. AI translates with glossary-aware context
4. Translations land as a GitHub PR for review
5. Approved translations deploy to CDN instantly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Steps 2-5 are automated. No JSON files to commit. No merge conflicts. No PM spending their Tuesday copying cells from Google Sheets.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;translations should flow through the same system as your code (Git), but deploy independently (CDN).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a translator fixes a typo in the Spanish copy, that fix goes live in seconds through the CDN. No build. No deploy. No release cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic key discovery
&lt;/h3&gt;

&lt;p&gt;This is the feature I didn't know I needed until I had it. The CLI scans your codebase for &lt;code&gt;t()&lt;/code&gt; calls and builds a manifest of every translation key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @better-i18n/cli scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discovered 847 translation keys
  - 12 new keys (not yet translated)
  - 3 unused keys (safe to remove)
  - 832 up to date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those 3 unused keys? They're from a feature I removed last month. Without the scanner, they'd sit in the JSON files forever, confusing every translator who encounters them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework-specific gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Next.js App Router
&lt;/h3&gt;

&lt;p&gt;Server Components can't use &lt;code&gt;useTranslations()&lt;/code&gt; (it's a hook). You need the server import:&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;// Server Component&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;getTranslations&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;@better-i18n/use-intl/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTranslations&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client Component&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="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;useTranslations&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;@better-i18n/use-intl&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;function&lt;/span&gt; &lt;span class="nf"&gt;CTAButton&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  React (Vite / CRA)
&lt;/h3&gt;

&lt;p&gt;No server components, so everything uses the hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BetterI18nProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&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;@better-i18n/use-intl&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nProvider&lt;/span&gt;
      &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;
      &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;detectedLocale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&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;Router&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;BetterI18nProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vue 3
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&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="nx"&gt;useI18n&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;@better-i18n/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useI18n&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  React Native (Expo)
&lt;/h3&gt;

&lt;p&gt;Mobile has an extra requirement: offline support. Users open your app on a plane and you can't fetch translations from a CDN.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BetterI18nExpoProvider&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;@better-i18n/expo&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;fallback&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;./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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nExpoProvider&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;
      &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;
      &lt;span class="na"&gt;fallbackMessages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fallback&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;Navigation&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;BetterI18nExpoProvider&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 Expo provider caches translations locally and uses the bundled fallback when offline. When connectivity returns, it pulls fresh translations from the CDN in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SEO checklist I wish I had on day one
&lt;/h2&gt;

&lt;p&gt;After getting 1,775 hreflang errors in one Semrush crawl (true story), I built myself a checklist. Here it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every locale URL returns HTTP 200 (not a redirect)&lt;/li&gt;
&lt;li&gt;[ ] Canonical tags use the locale-prefixed URL (&lt;code&gt;/en/about&lt;/code&gt;, not &lt;code&gt;/about&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Hreflang tags are bidirectional across all locales&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;x-default&lt;/code&gt; hreflang points to your primary language&lt;/li&gt;
&lt;li&gt;[ ] Sitemap includes all locale variants&lt;/li&gt;
&lt;li&gt;[ ] Structured data fields are translated (not English-only)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;&amp;lt;html lang="xx"&amp;gt;&lt;/code&gt; matches the page locale&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tags are under 70 characters (watch out for German, it's 30% longer than English)&lt;/li&gt;
&lt;li&gt;[ ] Redirects use 301, not 302/307&lt;/li&gt;
&lt;li&gt;[ ] OG tags are localized (og:title, og:description, og:locale)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI-powered translation with guard rails
&lt;/h2&gt;

&lt;p&gt;I'll be honest: I use AI translation for the first pass. It saves days of work. But raw machine translation shipped straight to production is a bad idea.&lt;/p&gt;

&lt;p&gt;What works is a review workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI translates&lt;/strong&gt; with access to your glossary (so it knows "workspace" is always "Arbeitsbereich" in German, not "Arbeitsplatz")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native speaker reviews&lt;/strong&gt; the AI output, approves or edits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved translations deploy&lt;/strong&gt; to CDN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The glossary part is critical. Without it, AI translates the same term differently across pages. Your "Dashboard" becomes "Armaturenbrett" on one page and "Instrumententafel" on another.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I started over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up i18n on day one, not day 100.&lt;/strong&gt; Retrofitting &lt;code&gt;t()&lt;/code&gt; calls into 200 components is brutal. Starting with i18n means every new component uses translation keys from the start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use ICU format from the beginning.&lt;/strong&gt; I started with simple key-value JSON and had to migrate when I needed pluralization. ICU handles plurals, dates, numbers, and gender from day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automate the key discovery early.&lt;/strong&gt; The CLI scanner caught 47 unused keys in my codebase. That's 47 strings translators were spending time on for no reason.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't skip the SEO fundamentals.&lt;/strong&gt; Hreflang, canonical tags, and locale routing are not optional if you want organic traffic from non-English markets. I learned this by losing 3 months of German organic traffic to indexing issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The library you pick for &lt;code&gt;t()&lt;/code&gt; calls matters less than you think. What matters is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can your translators work without needing a developer to shuffle files around?&lt;/li&gt;
&lt;li&gt;Can you fix a translation without triggering a full deploy?&lt;/li&gt;
&lt;li&gt;Do you know which keys are unused and which are missing?&lt;/li&gt;
&lt;li&gt;Are your localized pages actually indexed correctly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to any of those is "no", you have a workflow problem, not a library problem.&lt;/p&gt;

&lt;p&gt;I'm using &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;Better i18n&lt;/a&gt; for my projects. It handles the Git sync, CDN delivery, AI translation, and key discovery parts so I can focus on building features instead of managing JSON files. The free tier covers small projects; the Pro plan at $19/month works for production apps with multiple locales.&lt;/p&gt;

&lt;p&gt;Whatever tool you pick, get the workflow right first. The code is the easy part.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>Stop Managing Translations in JSON Files — There's a Better Way</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:21:41 +0000</pubDate>
      <link>https://dev.to/erayg/stop-managing-translations-in-json-files-theres-a-better-way-4fd6</link>
      <guid>https://dev.to/erayg/stop-managing-translations-in-json-files-theres-a-better-way-4fd6</guid>
      <description>&lt;p&gt;Every multilingual app starts the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locales/
├── en.json    ← 12 keys
├── tr.json    ← 12 keys
└── de.json    ← 12 keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Six months later:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locales/
├── en.json    ← 847 keys
├── tr.json    ← 791 keys (56 missing, nobody knows which)
├── de.json    ← 823 keys (24 orphaned from a deleted feature)
├── ja.json    ← 402 keys (translator quit halfway)
└── ar.json    ← 0 keys (we said we'd get to it)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If this looks familiar, keep reading.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Real Problem
&lt;/h2&gt;

&lt;p&gt;The issue isn't JSON files. JSON is fine. The problem is that &lt;strong&gt;translations live outside your development workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your code goes through pull requests, CI checks, type checking, and automated tests. Your translations? Someone exports a spreadsheet, a translator fills in cells, and someone else copies the values into JSON files. No review. No validation. No automated checks.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keys drift&lt;/strong&gt; — a developer renames &lt;code&gt;save_draft&lt;/code&gt; to &lt;code&gt;save_as_draft&lt;/code&gt; in code, but the translation files still have &lt;code&gt;save_draft&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead keys accumulate&lt;/strong&gt; — features get deleted, translation keys don't&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format errors hide&lt;/strong&gt; — &lt;code&gt;{count, plural, one {# item} other {# items}}&lt;/code&gt; is easy to break and hard to spot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploys block on translations&lt;/strong&gt; — fixing a typo requires a full rebuild&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What if translations worked like code?
&lt;/h2&gt;

&lt;p&gt;That's what we built with &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;Better i18n&lt;/a&gt;. Here's the workflow:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer adds t('pricing.title') in code
        ↓
CLI scans the codebase, detects the new key
        ↓
Key appears in the translation dashboard
        ↓
Translator (or AI) adds the translation
        ↓
Better i18n creates a PR in your GitHub repo
        ↓
Team reviews the PR → CI checks pass → merge
        ↓
CDN cache updates → users see the new translation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No JSON file juggling. No manual exports. No "did we translate this?" Slack messages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's Build It
&lt;/h2&gt;

&lt;p&gt;I'll walk through setting up a Next.js app with Better i18n. The same principles apply to React and React Native — I'll cover those at the end.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Install
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @better-i18n/next @better-i18n/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@better-i18n/next" rel="noopener noreferrer"&gt;&lt;code&gt;@better-i18n/next&lt;/code&gt;&lt;/a&gt; — middleware, hooks, server component support&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@better-i18n/cli" rel="noopener noreferrer"&gt;&lt;code&gt;@better-i18n/cli&lt;/code&gt;&lt;/a&gt; — scans your code, syncs with the platform&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 2: Middleware for Locale Routing
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.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;createI18nMiddleware&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;@better-i18n/next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createI18nMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&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="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/((?!api|_next|.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;..*).*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/about&lt;/code&gt; → English (default)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/tr/about&lt;/code&gt; → Turkish&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/de/about&lt;/code&gt; → German&lt;/li&gt;
&lt;li&gt;Automatic detection from &lt;code&gt;Accept-Language&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;Locale persistence via cookies&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 3: Use Translations in Your Components
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&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;@better-i18n/next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingPage&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pricing&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;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;plans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`plans.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.name`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price_per_month&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;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&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;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`features.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&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;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&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 translation file for this might look like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pricing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Simple, transparent pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;"No hidden fees. Cancel anytime."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price_per_month"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{amount, number, currency} / month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"plans"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"starter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starter"&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;"pro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pro"&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;"enterprise"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enterprise"&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"unlimited_projects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unlimited projects"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"github_sync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GitHub sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ai_translation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI-powered translation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cdn_delivery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CDN delivery"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Better i18n supports &lt;a href="https://unicode-org.github.io/icu/userguide/format_parse/messages/" rel="noopener noreferrer"&gt;ICU MessageFormat&lt;/a&gt; — the same standard used by &lt;a href="https://formatjs.io" rel="noopener noreferrer"&gt;FormatJS&lt;/a&gt;, &lt;a href="https://next-intl-docs.vercel.app/" rel="noopener noreferrer"&gt;next-intl&lt;/a&gt;, and &lt;a href="https://www.npmjs.com/package/use-intl" rel="noopener noreferrer"&gt;use-intl&lt;/a&gt;. Plurals, dates, numbers, select statements — all handled.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Scan Your Codebase
&lt;/h3&gt;

&lt;p&gt;Here's where it gets interesting. Instead of manually tracking which keys exist:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @better-i18n/cli scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanning src/ for translation keys...

✓ Found 847 translation keys across 124 files

  New keys (not yet translated):
    + pricing.plans.enterprise.name
    + pricing.features.cdn_delivery

  Unused keys (not found in code):
    - settings.old_theme_toggle
    - onboarding.deprecated_step_3

  Missing translations:
    ⚠ tr: 12 keys missing
    ⚠ de: 3 keys missing
    ⚠ ja: 45 keys missing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The CLI uses AST parsing, not regex — so it understands your code structure. It catches dynamic keys, template literals, and namespace patterns.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Sync
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @better-i18n/cli &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This pushes the scan results to the &lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;Better i18n dashboard&lt;/a&gt; where translators can work on missing translations. When they're done, a PR lands in your repo.&lt;/p&gt;
&lt;h2&gt;
  
  
  AI Translation That Understands Context
&lt;/h2&gt;

&lt;p&gt;Most machine translation tools translate strings in isolation. Better i18n sends context to &lt;a href="https://deepmind.google/technologies/gemini/" rel="noopener noreferrer"&gt;Google Gemini&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Namespace&lt;/strong&gt; — "this string is in the pricing section"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glossary&lt;/strong&gt; — "we translate 'workspace' as 'çalışma alanı' in Turkish"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearby keys&lt;/strong&gt; — "the strings around this one say..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ICU structure&lt;/strong&gt; — "this is a plural form, handle it correctly"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is noticeable. Instead of:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Generic MT
"Workspace" → "Çalışma Alanı" (correct but formal)
"{count} items selected" → "{count} öğe seçildi" (wrong plural form)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You get:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Context-aware
"Workspace" → "Çalışma alanı" (matches glossary casing)
"{count, plural, one {# öğe seçildi} other {# öğe seçildi}}" (correct ICU)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Bonus: Let AI Agents Handle It
&lt;/h2&gt;

&lt;p&gt;We built an &lt;a href="https://www.npmjs.com/package/@better-i18n/mcp" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; so AI assistants can manage translations directly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @better-i18n/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Connect it to &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt;, &lt;a href="https://cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, or &lt;a href="https://windsurf.com" rel="noopener noreferrer"&gt;Windsurf&lt;/a&gt;, and you can:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "Add the key 'dashboard.welcome_back' with value
      'Welcome back, {name}!' and translate it to Turkish and German"

AI:  Creates the key, translates it, and the changes flow
     through your GitHub PR workflow.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is becoming the standard for connecting AI assistants to external tools. If you're not using it yet, it's worth exploring.&lt;/p&gt;
&lt;h2&gt;
  
  
  React Native / Expo
&lt;/h2&gt;

&lt;p&gt;Mobile apps need offline support and OTA translation updates. The &lt;a href="https://www.npmjs.com/package/@better-i18n/expo" rel="noopener noreferrer"&gt;&lt;code&gt;@better-i18n/expo&lt;/code&gt;&lt;/a&gt; package handles both:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BetterI18nExpoProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&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;@better-i18n/expo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BetterI18nExpoProvider&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"your-project-id"&lt;/span&gt;
      &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;
      &lt;span class="na"&gt;fallbackMessages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./messages/en.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RootNavigator&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;BetterI18nExpoProvider&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;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first&lt;/strong&gt; — translations cached locally, works without network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background updates&lt;/strong&gt; — new translations fetched silently, no app store update&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundled fallback&lt;/strong&gt; — ships with default translations so the app works on first launch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic locale detection&lt;/strong&gt; — reads device language settings&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Full Picture
&lt;/h2&gt;

&lt;p&gt;Here's what the complete workflow looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  Developer   │────▶│  GitHub Repo  │◀────│  Translator  │
│  (adds key)  │     │  (source of   │     │  (via Better  │
│              │     │   truth)      │     │   i18n UI)   │
└─────────────┘     └──────┬───────┘     └─────────────┘
                           │
                    ┌──────▼───────┐
                    │   CDN Edge    │
                    │  (Cloudflare) │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
         ┌────────┐  ┌────────┐  ┌────────┐
         │ Next.js │  │ React  │  │  Expo  │
         │  App    │  │  App   │  │  App   │
         └────────┘  └────────┘  └────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No separate translation pipeline. No manual exports. No "which JSON file has the latest version?" conversations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The SDKs are open source under MIT:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/better-i18n" rel="noopener noreferrer"&gt;
        better-i18n
      &lt;/a&gt; / &lt;a href="https://github.com/better-i18n/oss" rel="noopener noreferrer"&gt;
        oss
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Official TypeScript SDKs for Better i18n — Next.js, React, Expo/React Native integrations with CLI, MCP server, and CDN-powered translation delivery
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a href="https://better-i18n.com" rel="nofollow noopener noreferrer"&gt;
    
      
      &lt;a href="https://camo.githubusercontent.com/d0d6d522dacf73fd98c91718305f101f460cedaaf29ec814498bc28d60332f5e/68747470733a2f2f6265747465722d6931386e2e636f6d2f6272616e642f6c6f676f2d6c696768742e737667" class="article-body-image-wrapper"&gt;&lt;img src="https://camo.githubusercontent.com/d0d6d522dacf73fd98c91718305f101f460cedaaf29ec814498bc28d60332f5e/68747470733a2f2f6265747465722d6931386e2e636f6d2f6272616e642f6c6f676f2d6c696768742e737667" alt="Better i18n" width="240"&gt;&lt;/a&gt;
    
  &lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;TypeScript SDKs for the Better i18n localization platform&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;
  Manage translations with CDN delivery, GitHub sync, and AI-powered workflows
  &lt;br&gt;
  &lt;a href="https://docs.better-i18n.com" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/a&gt; · &lt;a href="https://better-i18n.com" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt; · &lt;a href="https://better-i18n.com/blog" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Blog&lt;/strong&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/@better-i18n/cli" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3be735d1aa32e4f42804c5f633e836edcdfedf3fa91ea4812c7cf09c3c20ae9e/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406265747465722d6931386e2f636c693f6c6162656c3d2534306265747465722d6931386e253246636c6926636f6c6f723d303238346337" alt="CLI version"&gt;&lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/@better-i18n/next" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ea70dd4c2f37aa24107fa7757ba6631d5cfb30d06e494e4853206e63c673332f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406265747465722d6931386e2f6e6578743f6c6162656c3d2534306265747465722d6931386e2532466e65787426636f6c6f723d303238346337" alt="Next.js SDK version"&gt;&lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/@better-i18n/expo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ceb94e9f07715975f8dbcf46068218643027e9b392f9e78ff7237aa39c7df185/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406265747465722d6931386e2f6578706f3f6c6162656c3d2534306265747465722d6931386e2532466578706f26636f6c6f723d303238346337" alt="Expo SDK version"&gt;&lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/@better-i18n/mcp" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0a7cecdb33474e17c80aac26f78efe2cb4f67073f8c1bad2073ea218bf988bde/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406265747465722d6931386e2f6d63703f6c6162656c3d2534306265747465722d6931386e2532466d637026636f6c6f723d303238346337" alt="MCP version"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/better-i18n/oss/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a1023c275c59d4eed9f02ae79f2e5b079a5d9ffe8a6f149562a8fba533ca399b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6265747465722d6931386e2f6f73733f636f6c6f723d303238346337" alt="MIT License"&gt;&lt;/a&gt;
&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Better i18n?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Most localization tools weren't built for modern developer workflows. Better i18n is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub-native&lt;/strong&gt; — translations sync as PRs, reviews happen in your existing workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN-delivered&lt;/strong&gt; — translations served from &lt;a href="https://cloudflare.com" rel="nofollow noopener noreferrer"&gt;Cloudflare's edge network&lt;/a&gt;, updated without redeployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered&lt;/strong&gt; — context-aware translation with &lt;a href="https://deepmind.google/technologies/gemini/" rel="nofollow noopener noreferrer"&gt;Google Gemini&lt;/a&gt;, not word-by-word machine output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript-first&lt;/strong&gt; — full type safety across &lt;a href="https://nextjs.org" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt;, &lt;a href="https://react.dev" rel="nofollow noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://expo.dev" rel="nofollow noopener noreferrer"&gt;Expo&lt;/a&gt;, and &lt;a href="https://reactnative.dev" rel="nofollow noopener noreferrer"&gt;React Native&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP-ready&lt;/strong&gt; — AI agents can manage translations through the &lt;a href="https://modelcontextprotocol.io/" rel="nofollow noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;How it compares&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;br&gt;
&lt;thead&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;Better i18n&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;&lt;a href="https://crowdin.com" rel="nofollow noopener noreferrer"&gt;Crowdin&lt;/a&gt;&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;&lt;a href="https://lokalise.com" rel="nofollow noopener noreferrer"&gt;Lokalise&lt;/a&gt;&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;&lt;a href="https://phrase.com" rel="nofollow noopener noreferrer"&gt;Phrase&lt;/a&gt;&lt;/th&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/thead&gt;
&lt;br&gt;
&lt;tbody&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;GitHub-first workflow&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;AI translation with context&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;CDN delivery (no redeploy)&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;TypeScript SDKs&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;MCP server for AI agents&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;CLI scanner&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/tbody&gt;
&lt;br&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/p&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/better-i18n/oss" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://better-i18n.com" rel="noopener noreferrer"&gt;better-i18n.com&lt;/a&gt; — platform (free tier available)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.better-i18n.com" rel="noopener noreferrer"&gt;docs.better-i18n.com&lt;/a&gt; — documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/org/better-i18n" rel="noopener noreferrer"&gt;npm packages&lt;/a&gt; — all published SDKs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/better-i18n/skills" rel="noopener noreferrer"&gt;AI agent skills&lt;/a&gt; — i18n best practices for AI assistants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have questions, drop a comment below or open an issue on &lt;a href="https://github.com/better-i18n/oss/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>nextjs</category>
      <category>react</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GPT-4 and GitHub Actions for PR Review Automation: AI Code Reviewer</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Sun, 12 Nov 2023 14:16:24 +0000</pubDate>
      <link>https://dev.to/erayg/gpt-4-and-github-actions-for-pr-review-automation-ai-code-reviewer-cb3</link>
      <guid>https://dev.to/erayg/gpt-4-and-github-actions-for-pr-review-automation-ai-code-reviewer-cb3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuombfc30hsc3m8hw0m22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuombfc30hsc3m8hw0m22.png" alt="banner" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pull request reviews generally involve exchanging ideas with team members to improve code quality, passing our written code through a pipeline with certain automation tools, and allow us to use our time more efficiently and systematically.&lt;/p&gt;

&lt;p&gt;Following advancements in artificial intelligence, I thought, why not benefit from AI in an area as crucial for software development teams as "Pull request review?" After some research, I came across a solution: &lt;a href="https://github.com/marketplace/actions/ai-code-review-action" rel="noopener noreferrer"&gt;AI Code Reviewer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AI Code Reviewer is an innovative tool that can perform automatic code reviews for pull requests in an integrated manner with GitHub Actions using OpenAI's GPT-4 technology. This tool is designed to facilitate the software development process and offers developers valuable feedback by intelligently identifying potential issues and improvement opportunities in their code. AI Code Reviewer examines pull requests in real time, provides detailed suggestions for code changes, and makes customizable comments. It also focuses only on important areas by excluding certain files and patterns. This tool is an ideal solution for teams looking to enhance code quality and manage development processes efficiently.&lt;/p&gt;

&lt;p&gt;I am aware that there are tools like SonarQube that analyze code quality, control rules, and patterns. However, AI Code Reviewer offers a different approach by providing more dynamic and context-sensitive automatic reviews in pull requests using OpenAI's GPT-4 technology.&lt;/p&gt;

&lt;p&gt;While SonarQube detects overall code quality and security vulnerabilities through static code analysis, AI Code Reviewer provides customizable and interactive feedback by examining code changes in real time and integrates with GitHub. Therefore, it can be said that both tools serve different needs in the software development process and complement each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;To use AI Code Reviewer in GitHub Actions, obtain an OpenAI API key.&lt;br&gt;
Save the OpenAI API key as a GitHub Secret value named OPENAI_API_KEY.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Create a folder named ".github" and add another folder named "workflows." Then create a file named "[name].yml" inside it and paste the following content into the file.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AI Code Reviewer&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;synchronize&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write-all&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AI Code Reviewer&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-username/ai-codereviewer@main&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;OPENAI_API_MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4"&lt;/span&gt; &lt;span class="c1"&gt;# Optional: defaults to "gpt-4"&lt;/span&gt;
          &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/*.json,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;**/*.md"&lt;/span&gt; &lt;span class="c1"&gt;# Optional: exclude patterns separated by commas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Actually that's all! Now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customize the "exclude" feature to specify files you don't want to be reviewed.&lt;/li&gt;
&lt;li&gt;Commit your changes. Now, AI Code Reviewer will be working on your next pull request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to this tool, we will now receive AI support before our team members and make the job easier for those who will review.&lt;/p&gt;

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

&lt;p&gt;Wishing everyone good work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About me&lt;/strong&gt;&lt;br&gt;
As a frontend engineer with a passion for open-source work, I take pride in my ability to deliver high-quality results to clients. My experience and expertise have equipped me with the skills needed to develop innovative solutions that exceed expectations. As an active member of the tech community, I value the importance of open communication, continuous learning, and collaboration. If you're interested in learning more about my work or how I can contribute to your project, &lt;strong&gt;&lt;a href="https://t.co/49411mZSEO" rel="noopener noreferrer"&gt;feel free to connect with me.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>pr</category>
      <category>review</category>
    </item>
    <item>
      <title>Simplifying Client-Side Authentication with Firebase and SvelteKit</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Sun, 12 Nov 2023 14:10:03 +0000</pubDate>
      <link>https://dev.to/erayg/simplifying-client-side-authentication-with-firebase-and-sveltekit-2eo</link>
      <guid>https://dev.to/erayg/simplifying-client-side-authentication-with-firebase-and-sveltekit-2eo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqxkiik8pbai12aq791h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqxkiik8pbai12aq791h.png" alt="banner" width="800" height="771"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Authentication tasks can often be a complex and challenging aspect of web development. If you've never worked with Firebase before, you're in luck. In this article, I've created a sample repository that's not just for learning purposes but can also be used in a production environment. We'll explore how to use Firebase Authentication seamlessly with SvelteKit and TypeScript, making the authentication process smoother and more efficient.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app. It supports authentication using passwords, phone numbers, popular federated identity providers like Google, Facebook and Twitter, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Understanding Firebase Authentication
&lt;/h2&gt;

&lt;p&gt;Before diving into the implementation details, let's briefly discuss what Firebase Authentication offers. Firebase Authentication is a robust backend service that provides developers with user authentication capabilities. It offers an easy-to-use SDK and pre-built UI libraries that allow you to authenticate users to your web application effortlessly. Firebase supports a wide range of authentication methods, including email/password, phone numbers, and federated identity providers like Google, Facebook, and Twitter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a SvelteKit project
&lt;/h2&gt;

&lt;p&gt;To begin, we'll set up a new SvelteKit project. If you're new to SvelteKit, you can quickly create a project using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create svelte@latest my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Firebase Emulator
&lt;/h2&gt;

&lt;p&gt;Next, we'll integrate Firebase into our SvelteKit project. Firebase provides a suite of services, and for this article, we're focusing on Firebase Authentication. But before we proceed, we'll set up Firebase Emulators for local development. Firebase Emulators allow us to test authentication locally, ensuring smooth development.&lt;br&gt;
To set up Firebase Emulators, you need to install the Firebase CLI and initialize your Firebase project:Local development with Local Emulator Suite can be a good fit for your evaluation, prototyping, development and continuous integration workflows.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing Firebase CLI
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; firebase-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or you can download CLI manually.&lt;/p&gt;

&lt;p&gt;After installing Firebase CLI, you need to login to your Google account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase login

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

&lt;/div&gt;



&lt;p&gt;Then, you need to initialize Firebase project in your project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase init

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

&lt;/div&gt;



&lt;p&gt;During the initialization process, choose Firebase Emulators, and select the Firebase project you want to use. Also, make sure to select the Authentication emulator.&lt;br&gt;
After installation we will be ready to use Firebase Auth Emulator.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install Firebase
&lt;/h2&gt;

&lt;p&gt;In your project folder run the following command.&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;After the installation, create a &lt;strong&gt;firebase.client.ts&lt;/strong&gt; file in &lt;em&gt;src/lib&lt;/em&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initializeApp&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;firebase/app&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;connectAuthEmulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getAuth&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;firebase/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FirebaseApp&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;firebase/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Firestore&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;firebase/firestore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&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;firebase/auth&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;browser&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;$app/environment&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;let&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Firestore&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;let&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FirebaseApp&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;let&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Auth&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;firebaseConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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_FIREBASE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;appId&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_FIREBASE_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;useEmulator&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_FIREBASE_USE_EMULATOR&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;authDomain&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_FIREBASE_AUTH_DOMAIN&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;initializeFirebase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Can't use the Firebase client on the server.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebaseConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebaseConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useEmulator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;connectAuthEmulator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:9099&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This is our base code to initialize Firebase for our project. All the config variables we are going to need will be in .env file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VITE_FIREBASE_API_KEY=API_KEY_HERE
VITE_FIREBASE_APP_ID=APP_ID_HERE
VITE_FIREBASE_USE_EMULATOR=true # true in development, false in production
VITE_FIREBASE_AUTH_DOMAIN=AUTH_DOMAIN_HERE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to invoke this &lt;strong&gt;initializeFirebase&lt;/strong&gt; function in our project, let's create a &lt;strong&gt;+layout.ts&lt;/strong&gt; file in &lt;em&gt;src/routes&lt;/em&gt; and add our load function that we are going to use on +layout.svelte page.&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="cm"&gt;/** @type {import('./$types').LayoutLoad} */&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;initializeFirebase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&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;$lib/firebase.client&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;browser&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;$app/environment&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;onAuthStateChanged&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;firebase/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;initializeFirebase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ex&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;function&lt;/span&gt; &lt;span class="nf"&gt;getAuthUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;onAuthStateChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;user&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="p"&gt;});&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;getAuthUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getAuthUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;Now our load function will get auth user on auth state changed. If user is not logged in, it will return false on mount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the State
&lt;/h2&gt;

&lt;p&gt;To manage the authentication state in your SvelteKit app, we'll create a &lt;strong&gt;session.ts&lt;/strong&gt; file in the &lt;em&gt;src/lib&lt;/em&gt; folder. This file will define the session state and export a session store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Writable&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;svelte/store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;photoURL&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SessionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Writable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SessionState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Layout page
&lt;/h2&gt;

&lt;p&gt;Now to use our load function, let's create &lt;strong&gt;+layout.svelte&lt;/strong&gt; page&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from 'svelte';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$lib/session';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;goto&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$app/navigation';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signOut&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from 'firebase/auth';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$lib/firebase.client';

 import type &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LayoutData&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from './$types';
 export let data: LayoutData;

 let loading: boolean = true;
 let loggedIn: boolean = false;

 session.subscribe((cur: any) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;loggedIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="si"&gt;}&lt;/span&gt;);

 onMount(async () =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthUser&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;loggedIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;emailVerified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;...&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&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="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="si"&gt;}&lt;/span&gt;);

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&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="err"&gt;!--&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;svelte&lt;/span&gt; &lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;loading&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;Loading...&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="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;else&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;
   Logged in: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loggedIn&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;slot&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sr"&gt;/if&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a Login Page
&lt;/h2&gt;

&lt;p&gt;We'll start by creating a basic login form. In your Svelte page, you can create the login form and handle authentication actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- login/+page.svelte --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"login-form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;on:submit=&lt;/span&gt;&lt;span class="s"&gt;{loginWithMail}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{email}&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{password}&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

 &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;or&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

 &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{loginWithGoogle}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login with Google&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Don't you have an account? &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/register"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; Register&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the script part of our Svelte page, we are going to add our functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
// login/+page.svelte
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$lib/session';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$lib/firebase.client';
 import &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;GoogleAuthProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signInWithPopup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signInWithEmailAndPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserCredential&lt;/span&gt;
 &lt;span class="si"&gt;}&lt;/span&gt; from 'firebase/auth';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;goto&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$app/navigation';

 let email: string = '';
 let password: string = '';

 async function loginWithMail() &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signInWithEmailAndPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;UserCredential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;photoURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;photoURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="si"&gt;}&lt;/span&gt;

 async function loginWithGoogle() &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&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;GoogleAuthProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signInWithPopup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;photoURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;photoURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;uid&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a Register Page
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 // register/+page.svelte
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$lib/firebase.client';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createUserWithEmailAndPassword&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from 'firebase/auth';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;goto&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$app/navigation';
 import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from '$lib/session';

 let email: string = '';
 let password: string = '';

 async function handleRegister() &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUserWithEmailAndPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loading&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="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- register/+page.svelte --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"register-form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;on:submit=&lt;/span&gt;&lt;span class="s"&gt;{handleRegister}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Register&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{email}&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{password}&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Register&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this article, we've walked through the process of integrating Firebase Authentication with a SvelteKit application. We've covered setting up Firebase Emulators for local development, initializing Firebase in your project, managing authentication state, and implementing login and registration features. This combination of Firebase and SvelteKit offers a powerful and streamlined solution for client-side authentication.&lt;/p&gt;

&lt;p&gt;As you continue to work on your project, you can build upon these foundations and explore additional Firebase features to enhance the user experience and security of your web application.&lt;/p&gt;

&lt;p&gt;By following these steps, you can simplify client-side authentication and create a secure and user-friendly web application using Firebase and SvelteKit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/eraygundogmus/sveltekit-firebase-auth-example" rel="noopener noreferrer"&gt;GitHub Repository: SvelteKit Firebase Authentication Example&lt;br&gt;
&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this example project, you'll see how all the pieces come together. You can examine the project structure, review the implementation of authentication features, and use it as a reference for your own projects.&lt;br&gt;
Here are some key points about the example project:&lt;br&gt;
It showcases the integration of Firebase Authentication with SvelteKit, just as we discussed in this article.&lt;br&gt;
You can explore how Firebase Emulators are set up for local development.&lt;br&gt;
The project includes login and registration pages with email/password and Google authentication methods.&lt;br&gt;
The management of authentication state using Svelte's stores is also demonstrated.&lt;br&gt;
Feel free to clone, fork, or download the project for your own use. It's a practical resource to accelerate your development when working on projects that require user authentication.&lt;/p&gt;

&lt;p&gt;By examining the example project alongside this article, you'll gain a more comprehensive understanding of how to implement Firebase Authentication with SvelteKit effectively.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;About me&lt;/strong&gt;&lt;br&gt;
As a frontend engineer with a passion for open-source work, I take pride in my ability to deliver high-quality results to clients. My experience and expertise have equipped me with the skills needed to develop innovative solutions that exceed expectations. As an active member of the tech community, I value the importance of open communication, continuous learning, and collaboration. If you're interested in learning more about my work or how I can contribute to your project, &lt;strong&gt;&lt;a href="https://t.co/49411mZSEO" rel="noopener noreferrer"&gt;feel free to connect with me.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>firebase</category>
      <category>auth</category>
    </item>
    <item>
      <title>What other teams did on Disaster Map in first 72 hours</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Tue, 21 Feb 2023 20:50:21 +0000</pubDate>
      <link>https://dev.to/erayg/what-other-teams-did-on-afetharita-in-first-72-hours-26pg</link>
      <guid>https://dev.to/erayg/what-other-teams-did-on-afetharita-in-first-72-hours-26pg</guid>
      <description>&lt;ul&gt;
&lt;li&gt;An application was developed on the first day in the field of artificial intelligence that converts text from a screenshot and extracts address, phone, and name information from this text and saves it to a database. &lt;strong&gt;EasyOCR&lt;/strong&gt; was used to convert text and the &lt;strong&gt;OpenAI DaVinci model&lt;/strong&gt; was trained to extract the address. On the third day, a model training was started using this model on a tweet that meets their needs. This model would be able to distinguish their needs.&lt;/li&gt;
&lt;li&gt;The legal team tried to support "Açık Yazılım Ağı" by preparing necessary legal texts for the personal data to be processed during this process, informing team members voluntarily about the risks that may be encountered, &lt;strong&gt;and providing legal advice.&lt;/strong&gt; This was necessary due to the nature of the work done in terms of personal data.&lt;/li&gt;
&lt;li&gt;Test teams channeled our human resources to certain projects and tasks according to needs and requirements and made people who volunteered to lead responsible for planning. Afterwards, they started testing and created high-level requirements by documenting them. &lt;strong&gt;They detailed the errors in an Excel document, prioritized them, and moved them to GitHub, i.e. development teams.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Moderation teams quickly directed incoming people on the server to relevant teams and channels to contribute, improved role settings to enable each team to work faster and more stable. Additionally, they constantly monitored and cleared spam and harmful messages on the server. &lt;strong&gt;Afterwards, they kept active communication with teams and solved communication issues such as informing and announcements.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;As the Cloud &amp;amp; Infra &amp;amp; DevOps team, they preferred cloud solutions together with the urgency of the process. They &lt;strong&gt;dockerized&lt;/strong&gt; the projects and &lt;strong&gt;created CI&amp;amp;CD pipelines&lt;/strong&gt; for production deployment to cloud environments that would be &lt;strong&gt;multi-cloud providers&lt;/strong&gt; and &lt;strong&gt;technology-independent&lt;/strong&gt; to provide the best support to &lt;strong&gt;1000-person development teams both horizontally and vertically scalable&lt;/strong&gt;. Thus, codes prepared independently by teams in terms of technology and human resources were deployed live on the relevant branches. They also resolved maintenance and zero downtime efforts by choosing serverless solutions for dependent databases, monitoring, cache, and streaming solutions in cloud environments. &lt;strong&gt;All working systems were designed as serverless and HA (high available)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;With the realization of application ideas, social media teams started marketing and communication activities to reach more people. Different sub-working groups were formed and organized quickly, from the design of the Açık Yazılım Ağı corporate identity to the preparation of content for social media accounts, collaboration with NGOs, and communication with influencers. Communication regarding applications and the community was done by these teams.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codenewbie</category>
      <category>career</category>
      <category>ai</category>
      <category>learning</category>
    </item>
    <item>
      <title>How an Open-Source Disaster Map Helped Thousands of Earthquake Survivors: afetharita.com</title>
      <dc:creator>Eray Gündoğmuş</dc:creator>
      <pubDate>Mon, 20 Feb 2023 17:23:38 +0000</pubDate>
      <link>https://dev.to/erayg/how-an-open-source-disaster-map-helped-thousands-of-earthquake-survivors-afetharitacom-440</link>
      <guid>https://dev.to/erayg/how-an-open-source-disaster-map-helped-thousands-of-earthquake-survivors-afetharitacom-440</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;On February 6, 2023, earthquakes measuring 7.8 and 7.6 hit the Kahramanmaraş region of Turkey, affecting 10 cities and resulting in more than 42.000 deaths and 120.000 injuries as of February 21.&lt;/p&gt;

&lt;p&gt;In the hours following the earthquake, a group of programmers quickly become together on the Discord server called "Açık Yazılım Ağı" , inviting IT professionals to volunteer and develop a project that could serve as a resource for rescue teams, earthquake survivors, and those who wanted to help: &lt;strong&gt;afetharita.com&lt;/strong&gt;. It literally means &lt;strong&gt;"disaster map".&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As there was a lack of preparation for the first few days of such a huge earthquake, disaster victims in distress started making urgent aid requests on social media. With the help of &lt;strong&gt;thousands of volunteers&lt;/strong&gt;, we utilized technologies such as &lt;strong&gt;artificial intelligence and machine learning&lt;/strong&gt; to transform these &lt;strong&gt;aid requests into readable data and visualized&lt;/strong&gt; them on afetharita.com. Later, we gathered critical data related to the disaster from necessary institutions and added them to the map.&lt;/p&gt;

&lt;p&gt;Disaster Map, which received a total of 35 million requests and 627,000 unique visitors, &lt;strong&gt;played a significant role in providing software support during the most urgent and critical periods of the disaster, and helped NGOs, volunteers, and disaster victims to access important information&lt;/strong&gt;. I wanted to share the process, our experiences, and technical details of this project clearly in writing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmruymubup90a4bjylng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmruymubup90a4bjylng.png" alt="Furkan's announcement" width="800" height="181"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;"thanks to "afetharita.com" my friend was saved. all the people on that collapsed building saved."&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.amazonaws.com%2Fuploads%2Farticles%2Fwfb9h9gziia45xf1t2m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfb9h9gziia45xf1t2m4.png" alt="afetharita.com statistics for 10 days" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I should warn you that the article will be quite long. When you read the entire article, you will be able to see the technical perspective of how such a project was developed end-to-end in a short time, how multiple disciplines worked together, how technical decisions were made based on which criteria, how it was managed, and similar topics.&lt;/p&gt;
&lt;h2&gt;
  
  
  At the beginning
&lt;/h2&gt;

&lt;p&gt;When I joined the frontend team of the project a few hours after the earthquake, &lt;strong&gt;I was faced with complete chaos&lt;/strong&gt;. People wanted to help, but they didn't know how they could contribute. Therefore, we needed to take urgent action and create a plan. As I asked, everyone was thinking about what they could do. At that moment, my friend Zafer suggested that I could take on the map integration. That's how we started contributing to the project.&lt;/p&gt;

&lt;p&gt;At this point, I wasn't even aware that I was starting a project &lt;strong&gt;that would become an example worldwide&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  First 3 hours: Uncertainty
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How much time do we have?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Less than limited. We can say very little.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Where is the design?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is no clear design file yet. However, there may not be enough time to do a detailed design work to advance the project development process quickly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Who will use the application mostly and on which devices?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Earthquake victims, field teams, and NGOs can use this data. Therefore, it is important to have a clear and user-friendly interface with technical details kept to a minimum. Also, since the closest device to both earthquake victims and moving teams is expected to be the phone, &lt;strong&gt;mobile-first&lt;/strong&gt; interfaces should be developed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Internet speed?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After an earthquake, internet speed can generally be low in disaster situations. 2G internet speed can be around 0.1 mbs/second on average, while 3G speed can be around 3 mbs/second. Therefore, project developers should coordinate with the backend team to determine the lightest data flow and model, so that users can receive services even in the worst scenarios.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where were we? Zafer, what's up? Oh yes, the map.&lt;/p&gt;

&lt;p&gt;To sum up, the main purpose of this map was to be &lt;strong&gt;fast and simple&lt;/strong&gt;. Considering that internet access is limited in the earthquake zone and users may face significant issues, we aimed to develop an application that works quickly, has a simple user experience and is always functional. This way, NGO volunteers who will reach earthquake victims could quickly see reported locations and provide help.&lt;br&gt;
Since the map was the key element, we had to choose the right technology to use. After discussing it with the team, we decided to use &lt;strong&gt;Leaflet&lt;/strong&gt;.&lt;br&gt;
The reasons for this decision were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's open source&lt;/li&gt;
&lt;li&gt;It has no external dependencies&lt;/li&gt;
&lt;li&gt;It has browser support&lt;/li&gt;
&lt;li&gt;It's lightweight with a 42KB bundle size&lt;/li&gt;
&lt;li&gt;It's customizable&lt;/li&gt;
&lt;li&gt;It's well-documented&lt;/li&gt;
&lt;li&gt;It's mobile-friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I added the map as a dependency and showed it on the screen in my initial commits. &lt;/p&gt;

&lt;p&gt;I thought we would need a modal or page to show details for each point on the map, so I also included the MUI React Drawer component in the project.&lt;/p&gt;

&lt;p&gt;The final version of the Drawer component can be seen below.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0q6ti8dfiucv1vfiqhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0q6ti8dfiucv1vfiqhq.png" alt="afetharita.com drawer component" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, the benefits of choosing Material UI can be summarized as providing a ready-made design system to meet our needs and eliminating the need for component-level testing. On the other hand, its weight is quite heavy &lt;strong&gt;(493kb)&lt;/strong&gt;, which can be considered a downside.&lt;/p&gt;
&lt;h3&gt;
  
  
  What about documentation?
&lt;/h3&gt;

&lt;p&gt;To support and encourage other developer colleagues to contribute, I added &lt;strong&gt;README.md&lt;/strong&gt; and &lt;strong&gt;CONTRIBUTING.md&lt;/strong&gt; to the project after reviewing a few drafts. These documents include details about what to consider before contributing, project installation, code format, and semantic commit for commit messages.&lt;/p&gt;
&lt;h2&gt;
  
  
  First 12 hours: Proper positioning
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Working with Mock API
&lt;/h3&gt;

&lt;p&gt;Eliminating the situation that blocks the frontend development team while waiting for the backend developers is an important step to continue development. As an anonymous backend developer said: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A good frontend developer is also someone who develops while waiting for the backend."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The frontend team did not need to wait for the API because we knew how the package we would use wanted the data. &lt;strong&gt;Therefore, creating a mock API and replacing it with the real API when it was ready would solve our problem.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Rapid delivery
&lt;/h3&gt;

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

&lt;p&gt;Needs could change, data types could change, interfaces could change, &lt;strong&gt;but the fact that we were racing against time would not change.&lt;/strong&gt;&lt;br&gt;
I realized that it was meaningless to engage in long technical discussions in a development environment where we were racing against time, and I took the initiative to manage this process. &lt;/p&gt;

&lt;p&gt;I opened &lt;strong&gt;main&lt;/strong&gt;, &lt;strong&gt;rc&lt;/strong&gt; (release candidate), and &lt;strong&gt;development&lt;/strong&gt; branches to move forward with 3 main branches and added branch protection with the help of my colleagues to create a safe and fast development environment. From now on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a PR is opened and approved by the team, it will be merged into the development branch after being tested through Vercel's preview link and &lt;strong&gt;receiving approval from at least 2 people.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Changes in the development branch will be merged to the rc branch, so that features that are candidates to go live can be tested in a specific domain (rc.afetharita.com) off of the rc branch.&lt;/li&gt;
&lt;li&gt;To release to production, the rc branch will be merged directly into the main branch that is linked to the main domain.&lt;/li&gt;
&lt;li&gt;For hotfixes, a copy will be taken from the previous version in the main branch and merged directly.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The second approach I added to the frontend was to implement the &lt;strong&gt;Testing in Production&lt;/strong&gt; (TIP) practice.&lt;br&gt;
Although we had not yet been in touch with the test teams, product managers, and designers, &lt;strong&gt;we had to test our software ourselves.&lt;/strong&gt; In this case, since the likelihood of making mistakes was high, instead of finding a solution to this problem, I decided to prioritize needs and errors coming from the field, which were our product users. As a team, our priority should not be to create a flawless software development process, but to move forward by first doing the simple thing and creating a live product.&lt;br&gt;
Therefore, &lt;strong&gt;it was enough for us as a team to perform acceptance tests in the development and rc branches.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Initial Load
&lt;/h3&gt;

&lt;p&gt;Not requesting data for unused elements during the initial load could reduce the data load at the first launch. For our mock API, it was a good solution to only put pins on the locations of notifications during the initial load and make a separate request for details if needed since other detailed data is not used until the page is loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// get pin[]&lt;/span&gt;
&lt;span class="nx"&gt;pin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// get pin details&lt;/span&gt;
&lt;span class="nl"&gt;pinDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{...},&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{...},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
   Ease of use, mobile approach, browser and device support
&lt;/h3&gt;

&lt;p&gt;One of the things we needed to pay attention to as a team was &lt;strong&gt;user feedback&lt;/strong&gt;. To address this, we prepared the rc.afetharita.com environment from the moment we received our first deploy, and asked non-technical friends to test it from their phones and report any issues they encountered to us.&lt;br&gt;
Until our test teams joined the project, we tried to gather user feedback as the most important metric.&lt;br&gt;
&lt;strong&gt;I added each feedback to a small note and applied it in my work, and shared these feedback with my team members, providing collaboration and exchange of ideas.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding map features: Heatmap, clustering, and legend
&lt;/h3&gt;

&lt;p&gt;We started by adding a legend to the map to help users understand which colors represent which data.&lt;/p&gt;

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

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

&lt;p&gt;We wanted to use the &lt;strong&gt;Leaflet.markercluster&lt;/strong&gt; plugin for clustering. This plugin would cause performance improvement in large data clusters and make map operations faster and smoother. Additionally, its high number of stars (&lt;strong&gt;3.6K&lt;/strong&gt;) and compatibility with the Leaflet.js library were among the reasons why we preferred it.&lt;/p&gt;

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

&lt;p&gt;For the heat map, we chose the &lt;strong&gt;react-leaflet-heatmap-layer&lt;/strong&gt;, a map component for &lt;strong&gt;react-leaflet.&lt;/strong&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  First 36 Hours: Recovery
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Topics&lt;/strong&gt;: Chaos, lack of human resource processes, inability to transfer to developers who want to help, Discord usage becoming difficult due to message overload, backend integration, going live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chaos
&lt;/h3&gt;

&lt;p&gt;As more people joined the Discord server, &lt;strong&gt;we began to face problems such as crowded voice channels and difficulty in pair programming.&lt;/strong&gt;&lt;br&gt;
In this case, I encouraged users in the voice channels to &lt;strong&gt;split into groups and deal with different issues.&lt;/strong&gt;&lt;br&gt;
However, later on, I noticed a problem where users who wanted to help but couldn't offer meaningful contributions also joined the channels.&lt;br&gt;
While everyone's contributions were valuable, the chaotic environment made development almost impossible. Isolation was necessary, &lt;strong&gt;but a measure had to be taken to not miss out on truly valuable ideas amidst the crowd.&lt;/strong&gt;&lt;br&gt;
For this reason, I requested the necessary changes from the moderators to only allow users with the "afetharita" role to access voice and text channels. This way, we could direct those who had development proposals or error reports directly to &lt;strong&gt;GitHub Issues&lt;/strong&gt;.&lt;br&gt;
With the next topic, this problem would be largely prevented by a wombo-combo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Request and Issue Templates
&lt;/h3&gt;

&lt;p&gt;Freedom of expression is important as long as it adheres to rules. It was crucial that PR and issue templates had enough detailed explanations to ensure that we understood them correctly. &lt;strong&gt;I added encouraging templates for detailed explanations, as shown in the example below.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Sometimes, when adding templates, I felt too strict and authoritarian, but ultimately, I believed that these solutions would work and prevent chaos.&lt;/p&gt;

&lt;p&gt;However, there was a problem: &lt;strong&gt;who would check the compliance of opened issues and PRs with these templates?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  afetharita-checkforce
&lt;/h3&gt;

&lt;p&gt;I brought together reliable, selfless, and dedicated friends whom I knew from the &lt;strong&gt;Frontendship&lt;/strong&gt; channel, where I took the lead as the founder to unite developers in the frontend area and to be an open-source organization, and created a team by giving them triage permissions on GitHub. &lt;/p&gt;

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

&lt;p&gt;This excellent team would review the issues and PRs opened in the repository and tag &lt;strong&gt;inappropriate&lt;/strong&gt; ones as invalid, telling the author to try again in compliance with the rules. In this way, &lt;strong&gt;they would take a significant burden off the developers.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ID Checks
&lt;/h3&gt;

&lt;p&gt;GitHub issue IDs were important for fragmented progress and work to form a comprehensible whole. &lt;strong&gt;Again, if the opened PR did not refer to an issue ID, the checkforce team asked for the PR to be closed and reopened.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Labels
&lt;/h3&gt;

&lt;p&gt;It was necessary to properly organize and write descriptions of labels, with the participation of the &lt;strong&gt;afetharita-checkforce&lt;/strong&gt; team in the development process. Labels also played a crucial role in making the work process acceptable.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Discord Category &amp;amp; Channel Structure
&lt;/h3&gt;

&lt;p&gt;Considering that not everyone on the server was working on the same project and the potential for Disaster Map to be a flagship, I asked the moderators to do something outside of the server structure.&lt;/p&gt;

&lt;p&gt;We separated the Disaster Map categories by discipline to increase isolation. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;It would look like the one below in the next 24 hours.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjapbm3348ye0hz1zgxh3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjapbm3348ye0hz1zgxh3.png" alt="disaster map discord structure" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Standards
&lt;/h3&gt;

&lt;p&gt;Our top priority was not code quality, but as our codebase grew, our concerns increased, and we faced the risk of creating a chaotic environment. Therefore, we decided to use some tools to maintain code standards.&lt;/p&gt;

&lt;p&gt;We included &lt;strong&gt;ESlint&lt;/strong&gt; and &lt;strong&gt;Prettier&lt;/strong&gt; tools in the project and completed their configurations. Additionally, we added a &lt;strong&gt;pre-commit hook&lt;/strong&gt; by creating an intermediate stage with a tool I love, called &lt;strong&gt;Husky&lt;/strong&gt;. Thus, everyone working on the project had to format the code and check for errors before submitting a commit.&lt;/p&gt;

&lt;p&gt;We also added the &lt;strong&gt;commitlint&lt;/strong&gt; feature to maintain certain standards for our commit messages by applying semantic commit rules. We noticed that the --no-verify option was used in some pull requests, so we provided a second check with &lt;strong&gt;GitHub Actions&lt;/strong&gt; to prevent this.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Connection
&lt;/h3&gt;

&lt;p&gt;We learned the data model prepared on the backend side of the project before it was launched, so all we had to do was add an API_URL. We had no problem other than getting a &lt;strong&gt;CORS&lt;/strong&gt; error in the test environment. &lt;strong&gt;However, thanks to everyone responding quickly, we almost synchronized the error.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I think the hardest part was still fighting with emotions. The destruction, insomnia, and tension we see on social media. And not just for me. Everyone I talked to had a tired voice and still said they hadn't slept or were about to pass out. As I saw this effort, my faith and hope in humanity increased.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Going live
&lt;/h3&gt;

&lt;p&gt;After completing the necessary developments to print the data on the screen, we took it to the rc environment, tested it after the final checks. However, we were aware that we needed more development for the application. Still, we did not encounter any significant problems during the testing phase. As a result, &lt;strong&gt;we deployed on production&lt;/strong&gt;, thinking that we were ready to make Disaster Map available for use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ujjrfq4lg0awyf7c3r6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ujjrfq4lg0awyf7c3r6.png" alt="going production" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;My tweet about my 33 hours of effort in 37 hours.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In what I've told so far, I have mainly focused on the frontend part because I was not fully aware of the details of the remaining work.&lt;br&gt;
However, I was sure that one of the biggest team efforts that an open-source community could achieve in Turkey was being carried out. On the Discord server, professionals from every discipline came together and worked towards common goals, with more than 10,000 people at that time. Many people worked intensively day and night, regardless of working hours and time differences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/erayg/what-other-teams-did-on-afetharita-in-first-72-hours-26pg"&gt;Click&lt;/a&gt; too see what other teams did &lt;/p&gt;

&lt;h2&gt;
  
  
  First 72 hours: Recovery
&lt;/h2&gt;

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

&lt;p&gt;While we were not sure whether Disaster Map was being used, we learned from &lt;strong&gt;Furkan's announcement that the platform had received 4.2 million views and had helped valuable AKUT teams rescue 160 people from the wreckage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although we had established API connection to the backend, we observed that instead of data coming from two separate requests as we had mock-prepared, a data that could cause performance loss was received by providing the list of pins and their details in a single request, &lt;strong&gt;and response time could go up to 10 seconds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks to the presence of experienced and talented colleagues on the frontend side, the system had settled and started working on its own. During this process, we developed a sense of trust in each other and strengthened our cooperation.&lt;/p&gt;

&lt;p&gt;However, I was not aware of who I needed to communicate with to synchronize with the backend. After struggling to find answers to my questions for a while, &lt;strong&gt;I learned that there were some problems on the backend&lt;/strong&gt; and that the API side was re-written under the leadership of Emre, who is working as a Senior Software Engineer.&lt;/p&gt;

&lt;p&gt;Focusing on the solution, I directly asked how I could help &lt;strong&gt;instead of asking questions about the topics I did not know.&lt;/strong&gt; After learning that Emre had experienced similar problems, &lt;strong&gt;I thanked him for his time and stated that I would do my best to help. Thus, we started to communicate seamlessly with Emre and the backend team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I added the structure we used to check PRs and issues on the frontend to the backend repository with small edits to monitor PRs and issues. The &lt;strong&gt;checkforce&lt;/strong&gt; team was now monitoring both the frontend and backend repositories and closing invalid issues and PRs.&lt;/p&gt;

&lt;p&gt;A special category was opened on the server for backend and related teams to work in a focused manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alignment
&lt;/h3&gt;

&lt;p&gt;Although there were tensions and separations between the teams, there were issues that we should not separate from each other. &lt;strong&gt;Our priority was to focus on how our applications could help save the lives of earthquake victims.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Personally, I have to say that I have never been so passionate about software development in my life. My new mission was to bring all the teams at Disaster Map into communication and alignment. &lt;/p&gt;

&lt;p&gt;Since we had already shaken hands with Emre and moved forward together, the teams began to collaborate easily.&lt;br&gt;
&lt;strong&gt;With short meetings, we produced solutions together to the obstacles encountered by both sides and were eagerly awaiting the new API endpoint.&lt;/strong&gt; During the waiting period, I frequently joined the voice channels of the frontend team to assist with PRs and issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Legal issues
&lt;/h3&gt;

&lt;p&gt;To ensure that we act in compliance with the law, I quickly added the &lt;strong&gt;Privacy Agreement, Data Sources, and Cookie Policy&lt;/strong&gt; to the footer section. Documents prepared by lawyers on our server &lt;/p&gt;

&lt;h3&gt;
  
  
  Bottleneck problem
&lt;/h3&gt;

&lt;p&gt;The fact that the data returned by the current API was large and not prepared as we had planned was a significant problem for the users. &lt;strong&gt;It seemed that fetching too much data at once and printing it to the user's browser could cause serious bottleneck problems.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We came up with &lt;strong&gt;4 solutions&lt;/strong&gt; to this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default to show the map at a higher zoom level to &lt;strong&gt;reduce the number of pins on the screen.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Load pins &lt;strong&gt;dynamically&lt;/strong&gt; based on the user's position and the map viewport.&lt;/li&gt;
&lt;li&gt;Provide a &lt;strong&gt;filter&lt;/strong&gt; mechanism to allow users to select specific types of pins to display.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;pagination&lt;/strong&gt; to limit the number of pins displayed on a single page.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Thanks to every hero who contributed, we succeeded in all of our endeavors. Maybe it's because I didn't see what the volunteers on the other side did, but the rational solution that Umut brought in for the drowning problem caused by clustering on the map, after working tirelessly for hours, really saved the day and left me in awe in every way possible. I hope he writes about it himself. &lt;a href="https://twitter.com/usirin" rel="noopener noreferrer"&gt;Umut&lt;/a&gt;, you're a fantastic engineer!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Hold on for a minute! We can grow.
&lt;/h3&gt;

&lt;p&gt;Although the backend and frontend teams are now working in a coordinated manner, &lt;strong&gt;I am aware that my area of expertise is not in product/project management and that many people are waiting to help in this regard.&lt;/strong&gt; Therefore, I asked the moderators to direct me to someone who could help with the hiring process.&lt;/p&gt;

&lt;p&gt;We started sharing job postings for a product manager and a frontend developer together because we needed more support. The new frontend developers were welcomed and transferred by other frontend team leaders, and I transferred information to keep the product managers up-to-date. &lt;/p&gt;

&lt;p&gt;My duties were helping to integrate product management tools into projec, act as a communication bridge between the backend/frontend team and product managers, and onboarding product managers for the Disaster Map. &lt;/p&gt;

&lt;p&gt;After a while I added new rules to our work routine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approved&lt;/strong&gt;: No work should start until product managers approve the issues on GitHub. Product managers would prioritize the issues opened in the repositories and plan the development processes. This way, no one in the development team would work unnecessarily, and their efforts would not be wasted. &lt;strong&gt;Also, we would control the direction the product was going.&lt;/strong&gt;&lt;br&gt;
Although there were minor incompatibilities at the beginning, developers and product managers started to collaborate in a short time.&lt;/p&gt;

&lt;h3&gt;
  
  
  But wait a minute! Testing?
&lt;/h3&gt;

&lt;p&gt;Another transfer I made to the product managers was that new features and the test environment on the frontend should be tested first. &lt;strong&gt;At this point, it was time to bring the waiting test experts on the server into work and be in contact with the test team manager.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I added two more labels to GitHub regarding &lt;strong&gt;testing, tested, and test-failed&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;As a result, PRs were not merged without being tested by test experts in the preview screens provided by Vercel.&lt;/p&gt;

&lt;h3&gt;
  
  
  One more second. Design?
&lt;/h3&gt;

&lt;p&gt;I have to mention that the designer colleagues on the server have designed a user interface before we went live, without forgetting to convey my greetings to all of them.&lt;/p&gt;

&lt;p&gt;However, as we were using a ready-made library, we could not go beyond using the design they created as a sketch, thinking that customizing it would take too much time. &lt;/p&gt;

&lt;p&gt;Our lack of communication from the beginning had brought us to a separate working point for a while. However, it was time to unite and accept the valuable help of the designers in order not to create a worse user interface.&lt;/p&gt;

&lt;p&gt;I added an &lt;strong&gt;UI/UX&lt;/strong&gt; tag so that product managers could add it if an open issue needed a design and designers could follow the process. This way, everyone would be informed when a design was expected for an issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disaster Map organized, but?
&lt;/h3&gt;

&lt;p&gt;Communication between the Backend, Cloud, and Data teams was already naturally provided. Thanks to the category organization we have on Discord, the Design, Test, Backend, Frontend, Cloud, Security, and Data teams were working connected and informed.&lt;/p&gt;

&lt;p&gt;I had no idea of what other applications were being built. To be able to support the processes and get further information, I asked Eser, whom I had known before and saw as a role model and leads everyone in discord, if he could host me in his house during the process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eser hosted me as a guest at his home for a week, and his approach to topics and people in the processes I witnessed influenced my perspective on life. I saw him unable to eat the meal he ordered 8 hours ago because of the meetings. Yet still not complaining at all. He listened carefully and tried to respond to everyone he made video conference. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Synchronizing with Eser and exchanging ideas has immensely contributed to my ability to gain a comprehensive understanding, increase coordination, and establish closer communication with users and NGOs despite the challenges faced in the field.&lt;/p&gt;

&lt;h2&gt;
  
  
  144 Hours: It's not over yet
&lt;/h2&gt;

&lt;p&gt;With the new API written in Go on the back end, the speed problem had been overcome, and a highly optimized system had been established. Things seemed to be going well. &lt;strong&gt;However, for the earthquake victims, nothing had ended. On the contrary, it was just beginning. We had to shape the product and add data according to the need.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  New data available
&lt;/h3&gt;

&lt;p&gt;We continued to add help, support, and search and rescue calls shared/made/published by non-governmental organizations, associations, aid organizations, press and media organizations, and public institutions and organizations to the API and the map to provide a more comprehensive interface and support.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Ahbap Locations&lt;/li&gt;
&lt;li&gt;Hospitals&lt;/li&gt;
&lt;li&gt;Hot Meal Points&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  New features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Multiple languages:&lt;/strong&gt; Although our application does not use many language features, &lt;strong&gt;we added English language support in case teams coming from abroad may use it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Map settings:&lt;/strong&gt; We added satellite and terrain options, considering that &lt;strong&gt;there may be places where roads are in bad condition and unrecognizable.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Filtering&lt;/strong&gt;: Time filtering and the addition of data verified by reliable sources made notifications filterable.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Is social media a weapon?
&lt;/h3&gt;

&lt;p&gt;We had already learned things that would make hope sprout within us and ease our conscience a little. Volunteers called coordinators from the field and thanked us, saying that they used the application and gave feedback. This situation fueled us even more.&lt;/p&gt;

&lt;p&gt;What was important on the technical side was that everyone using the product had to know that it was constantly being worked on and many people were working to provide a service.&lt;/p&gt;

&lt;p&gt;Although AYA's social media teams continued to work for afetharita.com, &lt;strong&gt;we started to make announcements on social media because we knew the importance of every passing minute.&lt;/strong&gt;&lt;/p&gt;

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

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

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

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

&lt;p&gt;Everyone supported the posts to ensure that afetharita.com was heard by the field teams and earthquake victims. At this point, we saw that valuable figures like &lt;strong&gt;Memet Ali Alabora&lt;/strong&gt; reported bugs and made social media posts to support us in the Discord server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Week One
&lt;/h2&gt;

&lt;p&gt;We knew that afetharita.com was being used. We knew that it gave hope to people. We knew that it received millions of requests and provided help.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscmnhb4av1gud3uapb1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscmnhb4av1gud3uapb1y.png" alt="people using disaster map on " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;People using disaster map on hospital&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We knew that we needed to continue developing the application. I think everyone knew that. So much so that a helpful friend from abroad had prepared a map showing the donation points and details in Berlin and reached out to me to add it to afetharita.com, instead of directly adding it to the disaster map, I decided to redirect afetharita.com/berlin domain to it.&lt;/p&gt;

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

&lt;p&gt;Later, afetharita.com/london and afetharita.com/netherlands joined our efforts. &lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;Moreover, we added new data to the map&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Filtering by need&lt;/strong&gt;: We added filtering by intent with the help of the artificial intelligence model that our friends working in the background started to distinguish aid calls by need to make it easier to use. We immediately added improvements to the interface.&lt;/p&gt;

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

&lt;p&gt;In addition to the map being heard on all social media, some people also added Disaster Map as an iframe module - without data or system sharing - to their projects to help reach more people. &lt;/p&gt;

&lt;p&gt;Foreign media outlets such as &lt;strong&gt;&lt;a href="https://uk.news.yahoo.com/twitter-helped-survivors-trapped-beneath-180236405.html?guccounter=1" rel="noopener noreferrer"&gt;Yahoo&lt;/a&gt;, &lt;a href="https://www.euronews.com/next/2023/02/10/how-twitter-helped-find-survivors-trapped-beneath-rubble-after-turkeys-earthquakes" rel="noopener noreferrer"&gt;Euronews&lt;/a&gt;, &lt;a href="https://www.wired.co.uk/article/tech-volunteers-rush-to-save-turkeys-earthquake-survivors" rel="noopener noreferrer"&gt;Wired&lt;/a&gt; and &lt;a href="https://time.com/6254500/turkey-earthquake-twitter-musk-rescue/" rel="noopener noreferrer"&gt;Time&lt;/a&gt;&lt;/strong&gt; talked about Disaster Map. &lt;/p&gt;

&lt;p&gt;We received great feedbacks from volunteers using the application.&lt;/p&gt;

&lt;p&gt;Disaster map, which we provided as a free service about 30 hours after the earthquake disaster, had become a very effective tool in just its first week. Although I was proud of the work we did together, I was experiencing mixed feelings as I empathized.&lt;/p&gt;

&lt;p&gt;Individually, &lt;strong&gt;the things i learned are priceless&lt;/strong&gt; I had the opportunity to be a part of a project that I probably wouldn't see another example of in my life and meet amazing people.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I learned that ideas and impact could be more important than titles, how important communication skills could be under stress, how effective teamwork could be, and how helping someone else could speed up things... Love and solidarity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Caner Akın, a Product Manager at GHD with whom I worked and established an interesting bond, &lt;strong&gt;expressed his feelings with these beautiful sentences&lt;/strong&gt; on his LinkedIn profile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These nine days were like nine years. We went through such a period that we became 40-year-old friends. We fought, apologized, and mourned together. We worked together day and night. We cried with our brother who found out that his relative's body had been found at dawn. We said, "Just one more day," and continued. The most important thing was this: we were proud. Knowing that we had people we could rely on made us proud. The fact that people who said, "As long as one more life is saved with a pure heart without any expectations," were together in Disaster Map made us feel that we were not alone and hopeless.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Among the products developed under the Açık Yazılım Ağı, it served as the flagship, and although I have only spoken and shared my experiences about it specifically for afetharita.com so far, there are many applications in the background that receive millions of traffic and provide software support to earthquake victims.&lt;/p&gt;

&lt;p&gt;For more detailed information about these applications, you can visit &lt;a href="https://afet.org" rel="noopener noreferrer"&gt;afet.org&lt;/a&gt;.&lt;br&gt;
All repositories are collected publicly under the &lt;a href="https://github.com/acikkaynak" rel="noopener noreferrer"&gt;Açık Kaynak organization&lt;/a&gt; on GitHub.&lt;/p&gt;




&lt;h2&gt;
  
  
  Week 2
&lt;/h2&gt;

&lt;p&gt;As we had aimed, Disaster Map had provided its support during &lt;strong&gt;the emergency and critical days of the earthquake&lt;/strong&gt;, and had gained public attention. &lt;/p&gt;

&lt;p&gt;In fact, &lt;a href="https://www.nbcsandiego.com/news/local/rancho-penasquitos-man-working-with-thousands-online-to-help-turkey-earthquake-victims/3170373/" rel="noopener noreferrer"&gt;NBC broadcasted about Disaster Map&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After a period of more than 10 days, as necessary awareness and aid began to be delivered across the country both at the volunteer and public level, we felt the need to determine a new roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Today: Our roadmap
&lt;/h2&gt;

&lt;p&gt;For Açık Yazılım Ağı, it's time to destroy personal and sensitive data within the scope of the Personal Data Protection Law. &lt;/p&gt;

&lt;p&gt;For Disaster Map, in the first stage, it continues to provide service with only verified and non-personal information. In the near future, we are going to continue adding useful features to Disaster Map with more planned user interface design behind the scenes but afetharita.com will be completely closed to usage and requests will be directed to afet.org.&lt;/p&gt;

&lt;p&gt;This may sound like bad news, but it's actually not, because now we are closer to my imaginary goal that came to my mind on the fourth day. &lt;strong&gt;&lt;em&gt;"This application should be turned into a program that can be downloaded and installed end to end, and should be available for customization and use in any disaster situation, offering free service to the whole world."&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Final words and a message for the world
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;We cannot stress enough the importance of having tools like afetharita.com available during times of crisis and urge you to take action and support this project so that it is ready and available for those who need it most.&lt;br&gt;
This project was born from the tireless efforts of volunteers and the generosity of small donors.&lt;br&gt;
However, to ensure its continued success and fulfillment of its objectives, we humbly &lt;strong&gt;request your support.&lt;/strong&gt; &lt;br&gt;
Established international organizations with experience in supporting similar initiatives are particularly encouraged to assist us.&lt;br&gt;
Please note that support can come in various ways. This disaster is not limited to Turkey and the product produced will have global significance.&lt;br&gt;
To contact for collaboration send a mail with "afetharita" subject: &lt;a href="mailto:eray@gundogmus.dev"&gt;eray@gundogmus.dev&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;About me&lt;/strong&gt;&lt;br&gt;
As a frontend engineer with a passion for open-source work, I take pride in my ability to deliver high-quality results to clients. My experience and expertise have equipped me with the skills needed to develop innovative solutions that exceed expectations. As an active member of the tech community, I value the importance of open communication, continuous learning, and collaboration. If you're interested in learning more about my work or how I can contribute to your project, &lt;strong&gt;&lt;a href="https://t.co/49411mZSEO" rel="noopener noreferrer"&gt;feel free to connect with me.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Other useful links&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://afet.org/" rel="noopener noreferrer"&gt;Afet.org&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/acikyazilimagi" rel="noopener noreferrer"&gt;Frontend Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/acikyazilimagi" rel="noopener noreferrer"&gt;Backend Repository&lt;/a&gt;&lt;br&gt;
Special thanks to &lt;a href="https://twitter.com/mervenoyann" rel="noopener noreferrer"&gt;Merve Noyan&lt;/a&gt; and AI, NLP and ML teams...&lt;br&gt;
&lt;a href="https://huggingface.co/deprem-ml" rel="noopener noreferrer"&gt;Machine Learning Models&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
