Frontend Development 11 min read

Implementing Internationalization in a Large React Project Using Gulp and Webpack

This article outlines a practical approach for adding i18n support to a mature React-based project by consolidating language resources, using i18next for business strings, leveraging Gulp for on‑demand locale builds, and integrating Webpack for seamless runtime loading and cache management.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing Internationalization in a Large React Project Using Gulp and Webpack

The article begins by describing the need for internationalization (i18n) in a large, multi‑year React project that includes various third‑party libraries, iframes, and a custom plugin system, and notes that the solution presented is for reference only.

Solution Overview

The proposed solution focuses on centralizing all i18n resources, packaging them together, and pre‑loading them in the page header for modern browsers (IE is not supported). The key points include using i18next for business‑level string maintenance, providing a mapping object in component libraries, and minimally modifying third‑party libraries to accept locale objects.

Technical Stack

Build tools: Gulp 4 + Webpack 4

Third‑party libraries: moment, dayjs, gantt, ckeditor, etc.

React full ecosystem

Language Switching Timing

Block page loading until the language pack is loaded, then continue initialization.

Use a full page reload approach to ensure complete and correct language response.

Gulp Integration

Gulp is used to watch locale directories and build language bundles on demand, reducing the overhead of bundling everything with Webpack. The locale files are maintained as an npm module.

import { resolve } from 'path';
import { src, dest, parallel, watch } from 'gulp';
import { accessSync, constants, statSync, readdirSync } from 'fs';
import gulpEsbuild from 'gulp-esbuild';
import { getDevModeAndParams } from '../../utils';

function checkDirExist(checkPath) {
  try {
    accessSync(checkPath, constants.R_OK | constants.W_OK);
    console.log(`${checkPath} 路径gulp能读写`);
  } catch (err) {
    console.error(`${checkPath} 无法尝试访问,请先检测是否存在`, err);
    process.exit(1);
  }
}

function getLocaleDirName(path) {
  if (!path) throw new Error('path no exist');
  try {
    const localeDirName = [];
    const localeGulpTaskName = [];
    const readList = readdirSync(path);
    for (const item of readList) {
      const fullPath = resolve(path, item);
      const stats = statSync(fullPath);
      if (stats.isDirectory()) {
        localeDirName.push(item);
        localeGulpTaskName.push(`${item}_build_locale_task`);
      }
    }
    return { localeDirName, localeGulpTaskName };
  } catch (error) {
    console.log('%c 🍇 error: ', 'font-size:20px;background-color: #7F2B82;color:#fff;', '找不到语言文件', error);
  }
}

function localeBuild(srcPath, DestDirPath, outputName) {
  return () => {
    const inputFile = resolve(srcPath, 'index.js');
    const isRelease = getDevModeAndParams('release', true);
    const esbuildPipe = () => {
      return gulpEsbuild({
        incremental: !isRelease,
        outfile: `${outputName}.js`,
        bundle: true,
        charset: 'utf8',
        format: 'iife',
        minify: !isRelease,
        sourcemap: false,
        platform: 'browser',
        loader: { '.js': 'js' },
      });
    };
    return src(inputFile).pipe(esbuildPipe()).pipe(dest(DestDirPath));
  };
}

export function langBuild() {
  const SrcDirPath = resolve(process.cwd(), 'node_modules', '@ones-ai', 'lang/locale');
  const DestDirPath = resolve(process.cwd(), 'dest/locale');
  checkDirExist(SrcDirPath);
  const { localeDirName } = getLocaleDirName(SrcDirPath);
  const tasksFunction = (srcPath, destPath) =>
    localeDirName.map((localeKey) => localeBuild(resolve(srcPath, localeKey), destPath, localeKey));

  const watchLocaleBuild = (cb) => {
    watch([`${SrcDirPath}/**/*.js`], parallel(...tasksFunction(SrcDirPath, DestDirPath)));
    cb();
  };

  const isDevWatch = getDevModeAndParams('release', true) ? [] : [watchLocaleBuild];
  const taskQueue = [...tasksFunction(SrcDirPath, DestDirPath), ...isDevWatch];
  return parallel(...taskQueue);
}

Webpack Integration

Webpack works together with Gulp to inject variables, adjust the output structure, and automate the build process via a custom CLI, eliminating the need for manual starts.

HTML Template Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>
      // injected by html-webpack-plugin
      window.htmlInjectTplParams = <%= htmlInjectTplParams %>
    </script>
    <!-- dynamic language identifier -->
    <script src="locale-resource-loader/index.js" id="locale-source-loader"></script>
    <script>
      // load language script synchronously
      document.write('<script src="' + window.I18N_LOCALE_SOURCE_URL + '"></script>');
    </script>
  </head>
  <body>
    <!-- React app initialization goes here -->
  </body>
</html>

Locale Utilities

function getCookie(name) {
  const cookie = `; ${document.cookie}`;
  const parts = cookie.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
  return;
}

const isValidLang = (lang) => {
  const validLang = ['zh', 'en', 'ja'];
  if (!lang) return false;
  return validLang.includes(lang);
};

const getLocaleKey = () => {
  let lang = 'zh';
  const getValidLang = [
    getCookie('language'),
    localStorage.getItem('language'),
    navigator.language.slice(0, 2),
  ];
  for (const iterator of getValidLang) {
    if (isValidLang(iterator)) {
      lang = iterator;
      return lang;
    }
  }
};

const getLocaleUrl = (lang, isAbsolute = false) => {
  const { htmlInjectTplParams: { isRelease, commit } } = window;
  return `${isAbsolute ? '/' : ''}locale/${lang}.js?version=${isRelease ? commit : new Date().getTime()}`;
};

const localeUrl = getLocaleUrl(getLocaleKey());
window.I18N_LOCALE_SOURCE_URL = localeUrl;

Cache Strategy

Static resources use query strings for cache busting; in development a timestamp is appended, while in production the Git commit hash is used, ensuring the language bundle version follows code changes.

Pros and Cons

Pros: Full reload guarantees clean language switch; locale data attached to window can be shared across micro‑frontend or iframe scenarios.

Cons: Development mode requires manual reload after Gulp watch; production bundle size can exceed 1 MB per language, and the cache strategy depends on Git metadata.

Conclusion

The presented approach is not a one‑size‑fits‑all solution; it should be adapted to the project's current state, with incremental improvements over time. The author wishes readers a happy new year and invites feedback.

frontendreactWebpacki18nInternationalizationGulp
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.