DEV Community

Mauro Mazzocchetti
Mauro Mazzocchetti

Posted on

From Build-Time to Real-Time: A Better i18n Workflow for React

Localization in React often starts simple. You install a library, add a couple of JSON files, and you're done. But as your project grows, that simple setup starts to show its cracks. Soon, you're reviewing PRs just to fix a typo, dealing with merge conflicts in massive translation files, and your translation team is blocked, waiting on developers for every single text update.

The core problem? We start by treating translations—which are content—as code.

Let's walk through an evolutionary path for managing translations in React, moving from a basic local setup to a fully automated, live-updating system that will make your life, and your team's life, so much easier.

Phase 1: The Humble Beginning - Local Files

Almost every project starts here. We pick a solid library like react-intl or react-i18next and create our language files.

The structure is familiar to us all:

/src
  /locales
    en.json
    it.json
Enter fullscreen mode Exit fullscreen mode

en.json

{
  "sidebar.home": "Home",
  "buttons.submit": "Submit"
}
Enter fullscreen mode Exit fullscreen mode

Then we wire it up in our app.

import { IntlProvider } from 'react-intl';
import messages_en from './locales/en.json';
// ... other locales

function App() {
  // Logic to determine locale and messages
  return (
    <IntlProvider messages={messages} locale={locale}>
      {/* The rest of your app */}
    </IntlProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Drawback: While simple, this approach tightly couples your content to your codebase. Every change requires a developer, a PR, and a full deployment. It's slow and doesn't scale.

Phase 2: The First Big Leap - Automating at Build-Time 🚀

The first major improvement is to decouple translations from your source code. Let's treat them as data, fetching them from an external source of truth right before we build our app.

This is where a dedicated localization platform (like POEditor, Lokalise, or in our case, TnT - Terms and Translations, which has a generous free tier) comes in.

The workflow is straightforward:

  1. Your translation team works on a user-friendly dashboard (TnT).
  2. Before every build, a script pulls the latest versions of the language files into your project.

Let's create a simple script in scripts/fetch-translations.js:

// scripts/fetch-translations.js
const https = require('https');
const fs = require('fs');

const projectId = process.env.TNT_PROJECT_ID; // From your .env file
const apiKey = process.env.TNT_API_SECRET;   // From your .env file

const LANGUAGES = ['en', 'it', 'de'];
const TARGET_PATH = './src/locales';

async function fetchAllTranslations() {
  console.log('Fetching latest translations...');
  for (const lang of LANGUAGES) {
    const url = `https://localizationpro.tnl.one/api/v1/read-all/${projectId}?locale=${lang}`;
    // Add your fetch logic here to call the API with the key
    // and save the response to `${TARGET_PATH}/${lang}.json`
    console.log(`✅ Updated ${lang}.json successfully!`);
  }
}

fetchAllTranslations();
Enter fullscreen mode Exit fullscreen mode

Then, we hook it into our package.json:

"scripts": {
  "translate:fetch": "node ./scripts/fetch-translations.js",
  "build": "npm run translate:fetch && react-scripts build",
  "start": "react-scripts start"
}
Enter fullscreen mode Exit fullscreen mode

The Wins:

  • Automation: Translations are always fresh on deployment.
  • CI/CD Friendly: This script runs perfectly in GitHub Actions, GitLab CI, or any other pipeline.
  • Single Source of Truth: Your Git repo is clean. The truth lives on the localization platform.

Phase 3: The Ultimate Goal - Live Client-Side Updates ✨

This is the holy grail. What if we could update translations without a new deploy at all? This is the most agile approach, perfect for dynamic web apps where you want to push content changes instantly.

The idea is to stop bundling the JSON files altogether. Instead, the client's browser fetches them on its own and uses localStorage for intelligent caching.

Here's the workflow, which you can implement with TnT's specific APIs:

  1. App Load:

    • The app first checks localStorage for translations_it and a timestamp, last_update_it.
    • If they exist, it loads them into react-intl instantly. The user sees translated text immediately.
  2. Async Check:

    • In the background, the app makes a lightweight call to TnT's check-last-edit endpoint for the current language. This endpoint is hyper-efficient—it only returns the timestamp of the last modification.
    • GET /api/v1/check-last-edit/{projectId}?locale=it
    • Response: { "updatedAt": "2025-10-01T10:00:00.000Z" }
  3. Compare and Update:

    • The app compares the updatedAt timestamp from the API with the one stored in localStorage.
    • If the server's timestamp is newer, it means fresh translations are available! Only then does the app make a call to the read-all endpoint to download the full, updated JSON payload.
    • The new translations and the new updatedAt timestamp are saved to localStorage, ready for the next visit.

The Superpower: Git-like Branching for Your Translations 🤯

This live-update model becomes incredibly powerful when combined with a feature like translation branching, which TnT supports. Just like Git, you can have a dev and a main branch for your content.

  • Staging Environment: Your staging app can be configured to fetch translations from the dev branch. This is where you test new features with their corresponding new text.
  • Production Environment: Your live app points to the main branch.

When the new feature is ready for release, a project admin can merge the translations from dev to main right from the TnT dashboard. The moment the merge happens, the updatedAt timestamp on the main branch changes. The next time a user opens your production app, it will automatically detect the change and download the updated text.

The Final Wins:

  • Instant Updates: Fix a typo in TnT, and it's live for your users in minutes. No deploy needed.
  • Safe, Collaborative Workflow: Developers, translators, and copywriters can work in parallel. An admin reviews and merges content, ensuring quality control.
  • Total Decoupling: The content lifecycle is now completely independent of the code deployment cycle.

By starting with a simple local file setup and evolving towards a live, API-driven model, you can build a robust, scalable, and incredibly efficient localization process for any React application.

Let's Build This Together

I'm building TnT as an open-source project, and feedback from fellow developers is the most valuable resource there is.

I'd love to hear what you think. If you try out the platform, have ideas, or run into any issues, please let me know.

I'm also exploring the idea of creating a dedicated React library (maybe react-tnt?) to make the live-update workflow even more seamless. If a helper library like that sounds useful to you, or you have ideas for what it should include, the best way to share your thoughts is by opening an issue on the GitHub repository.

Thanks for reading, and happy building

Top comments (0)