Per Lang Approaches

in React

Current Implementation

.
├── App.js
└── locales
    ├── de
    │   └── translation.json
    ├── en
    │   └── translation.json
    ├── es
    │   └── translation.json
    └── index.js
// App.js
import React from "react";
import translations from "./locales";
import { addTranslations } from "@fs/zion-locale";
import { useTranslation } from "react-i18next";

addTranslations(translations);

export default function App() {
  const [t] = useTranslation();
  return (
    <div>
      <h1>{t("overview")}</h1>
    </div>
  );
}
{
  "overview": "Overview"
}
// intentionally left blank
export default {
  de: {
    overview: "Überblick"
  },
  en: {
    overview: "Overview"
  },
  es: {
    overview: "Visión general"
  }
};
.
├── App.js
└── locales
    ├── de
    │   └── translation.json
    ├── en
    │   └── translation.json
    ├── es
    │   └── translation.json
    └── index.js
// intentionally left blank

Three Options

  1. dynamic import

  2. i18next-http-backend

  3. per-lang build

 

Dynamic import

.
├── App.js
└── locales
    ├── de
    │   └── translation.json
    ├── en
    │   └── translation.json
    ├── es
    │   └── translation.json
    └── index.js
// App.js
import React from "react";
import /*translations from*/ "./locales";
// import { addTranslations } from "@fs/zion-locale";
import { useTranslation } from "react-i18next";

// addTranslations(translations);

export default function App() {
  const [t] = useTranslation();
  return (
    <div>
      <h1>{t("overview")}</h1>
    </div>
  );
}
{
  "overview": "Overview"
}
// intentionally left blank
// App.js
import React from "react";
import translations from "./locales";
import { addTranslations } from "@fs/zion-locale";
import { useTranslation } from "react-i18next";

addTranslations(translations);

export default function App() {
  const [t] = useTranslation();
  return (
    <div>
      <h1>{t("overview")}</h1>
    </div>
  );
}
// locales/index.js

import i18n from 'i18next'

import(`./locales/${i18n.language}/translation`).then(addLocales)
import(`./locales/en/translation`).then(addLocales)

import(`./locales/${i18n.language}/common-ui`).then(addLocales)
import(`./locales/en/common-ui`).then(addLocales)
.
├── App.js
├── node_modules
│   └── @fs
│       └── zion-locale
│           └── locales
│               ├── de
│               │   ├── common-ui.json
│               │   └── translation.json
│               ├── en
│               │   ├── common-ui.json
│               │   └── translation.json
│               ├── es
│               │   ├── common-ui.json
│               │   └── translation.json
│               └── index.js
└── locales
    ├── de
    │   └── translation.json
    ├── en
    │   └── translation.json
    ├── es
    │   └── translation.json
    └── index.js

// intentionally left blank
  • Basically the same as current implementation except it splits locales

  • loaded via webpack as a chunk at runtime

 

Adoption

  • Apps major bump @fs/react-scripts

Pros/Cons

Pros

  • 100% backward compatible
  • smaller payload size
  • loads faster on slower networks
  • webpack handles loading

Cons

  • our own implementation

  • more requests

i18next-http-backend

.
├── App.js
└── locales
    ├── de
    │   └── translation.json
    ├── en
    │   └── translation.json
    ├── es
    │   └── translation.json
    └── index.js
// App.js
import React from "react";
import /*translations from*/ "./locales";
// import { addTranslations } from "@fs/zion-locale";
import { useTranslation } from "react-i18next";

// addTranslations(translations);

export default function App() {
  const [t] = useTranslation();
  return (
    <div>
      <h1>{t("overview")}</h1>
    </div>
  );
}
{
  "overview": "Overview"
}
// intentionally left blank
// App.js
import React from "react";
import translations from "./locales";
import { addTranslations } from "@fs/zion-locale";
import { useTranslation } from "react-i18next";

addTranslations(translations);

export default function App() {
  const [t] = useTranslation();
  return (
    <div>
      <h1>{t("overview")}</h1>
    </div>
  );
}
// https://edge.fscdn.org/assets/static/locales/en/translation-1h4l12412m.json
{
  "overview": "Overview",
  "ok": "OK",
  "stuff": "Stuff",
}
.
├── App.js
├── node_modules
│   └── @fs
│       └── zion-locale
│           └── locales
│               ├── de
│               │   ├── common-ui.json
│               │   └── translation.json
│               ├── en
│               │   ├── common-ui.json
│               │   └── translation.json
│               ├── es
│               │   ├── common-ui.json
│               │   └── translation.json
│               └── index.js
└── locales
    ├── de
    │   └── translation.json
    ├── en
    │   └── translation.json
    ├── es
    │   └── translation.json
    └── index.js

// intentionally left blank
// https://edge.fscdn.org/assets/static/locales/en/common-ui-123irnwelk.json
{
  "common.things": "Common Things",
  "cool": "Super cool"
}
module.exports = {}
  • Single file for each namespace/locale

  • Loaded via xhr at runtime

// index.html
<script>
window.localeManifest = {
  'en-translation': 'https://edge.fscdn.org/assets/static/locales/en/translation-1h4l12412m.json',
  'en-common-ui': 'https://edge.fscdn.org/assets/static/locales/en/common-io-mmklk3i23.json',
}
</script>
<script src="https://edge.fscdn.org/assets/static/js/main-sdfisdg9e.js">
// @fs/zion-locale

import i18n from "i18next"
import Backend from "i18next-http-backend";

i18n
  .use(Backend)
  .init({
    backend: {
      loadPath: (locale, ns) => window.localeManifest?.[`${locale}-${ns}`]
    }
  });

Adoption

  • zion storybook will have to inject the manifest somehow
  • zion tests may need to change
  • zion-locale will have a major bump
  • Apps will need to add a <Suspense fallback="" /> as top level component

Pros/Cons

Pros

  • Used by the community
  • fewer requests
  • Shared common-ui cache across apps

Cons

  • Major bump to zion-locale

  • need to figure out a way to load locales in storybook

  • could be loading unused locale strings

  • issue with json file loading slowly/uncompressed from cdn
  • Not backward compatible

Perfomance

Payload

alienfast                                                                          1086KB

i18next-http-backend        788KB (27% smaller)

dynamic import    618KB (43% smaller)

Page Speed

Per Lang Approaches

By Tyler Graf

Per Lang Approaches

  • 631