Skip to content
React실무 경험

React i18n Build Size Optimization 2: Splitting Translations by Page

2 min readWorkMoved from my Tistory blogRead the Korean original

This continues my earlier post, React i18n Build Size Optimization 1.

In the previous post, I moved the translation files that had been kept under src into the public directory and used i18next-http-backend to load only the needed language at runtime, which improved the JS bundle size and initial loading performance.

The next step was to split the translation data managed in a Google Sheet by page, and below is how I optimized it so each page loads only the translations it really needs.

Key design background: why I chose the namespace.key structure in advance

This optimization was not done by suddenly changing the structure. From the time I first designed the translations, I had designed the key structure with a later per-page split in mind.

Below is the structure of the Google Sheet for managing translations that is actually in use for the Autowini service.

Autowini translation management sheet
Figure 1. Autowini translation management sheet

I wrote the translation keys by these rules.

  • Words likely to be shared across several pages are declared as a single key, like search
  • Text that belongs only to a specific page or UI area, like myWini.saved, shippingSchedule.title, userSideMenu.username, is
    clearly separated in namespace.key form

Google Sheet → JSON split by namespace

Instead of generating one translation file per language, I extended the logic to generate per-page JSON files by splitting namespaces based on the key. This took the structure one step further, so translation resources can be managed per page (feature).

It works like this.

  • Fetch the translation data from the Google Sheet
  • If a key contains a . → the part before the . is treated as the namespace
  • A key without a . → treated as a shared translation and put in the common namespace
  • Generate a JSON file for each language and namespace

The whole logic is fairly simple. The main part looks like this.

js
const keyParts = key.split('.');
const namespace = keyParts.length > 1 ? keyParts[0] : 'common';
const actualKey = keyParts.length > 1 ? keyParts.slice(1).join('.') : key;

With this structure, files like the following are generated.

  • /locales/ko/common.json
  • /locales/ko/myWini.json
  • /locales/ko/shippingSchedule.json
  • /locales/ko/userSideMenu.json
The translation .json files actually generated
Figure 2. The translation .json files actually generated

i18n .ts setup after splitting namespaces

js
i18n
  .use(HttpBackend)
  .use(initReactI18next)
  .init({
    supportedLngs: supportLanguages,
    fallbackLng: false,
    ns: ['common', 'seo'],
    lng: supportLanguages.includes(getCookieValue(PROPERTY_LANGUAGE) ?? '')
      ? getCookieValue(PROPERTY_LANGUAGE)!
      : fallbackLng,
    debug: false,
    backend: {
      loadPath: '/w/react-route/locales/{{lng}}/{{ns}}.json',
    },
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;

I changed loadPath to match the language + namespace structure, and set the common and seo namespaces, which every page uses, to load together on the initial load.

With this setup, the shared translations a page always needs on entry are available right away, and the remaining namespaces each page needs are loaded only later, when they are needed.

Looking back

Before, I had optimized it to load only the translation .json file for the selected language. This time I went one step further and optimized the structure to load only the translation .json files for the page that needs them. Thanks to this, even when all the legacy JSP pages are later moved to React, I think I was able to address problems like build size growth or slower initial rendering in advance.

  • #Autowini
  • #i18n
  • #WiniLogis
  • #Localization
  • #Rendering Optimization
  • #Per-Page Translation Splitting

More in Work