i18n

Internationalization (i18n) is, except of a few _en/_de columns in the database, completely done in the frontend. We use react-i18next, which is a powerful framework to provide translation functionality and more.

Language Detection

On the initial page load the language gets detected from the browser settings. English is the fallback language if none of our currently supported languages (English, German) could be detected. This language is then persisted into local storage. The language switcher in the navigation bar also persists the chosen language to local storage. On any further page load this persisted language is used.

Structure

We use a feature-based translation approach.

  • The translations live in the /src/config/i18n directory together with the respective i18next configuration.

  • Each language has its own folder which holds all translations in files that are named like the features under /src/features. E.g. for the seeds feature there is a translation file in /src/config/i18n/seeds.json

  • The feature's translation file should be structured like this:

{
  "foo_component": {
    "title": "Foo",
    ...
  },
  "bar_component": {
    "title": "Bar",
    ...
  },
  "error_for_feature": "Sorry, ...",
  "common_key_inside_the_feature": "A mere commoner",
  ...
}

As we see in the example:

  • We use snake_case.
  • Hierarchy for components/sub-parts is recommended.
  • We use error_, title_, etc. prefixes (but you can also use hierarchy instead).
  • We don't abbreviate (button instead of btn).

Furthermore:

  • In case a translation does not fit into any feature there is an additional common namespace common.json defined. E.g. for general error messages.
  • Shared components like a button usually also want to render some text. In this case the text should be translated inside the respective feature where the button is used. E.g. the create seed button inside the seeds feature:
// seeds.json
{
  // CreateSeed component
  "create_seed": {
    "button_create_seed": "Create Seed"
  }
}

How To

If you want to translate a string, for example from the seeds feature in the CreateSeed component, follow this schema:

  • a whole translation key is structured like this <namespace>:<component>.<part>
function CreateSeed() {
  // load the translation namespaces 'seeds' and 'common'
  const { t } = useTranslation(["seeds", "common"]);

  // this is just an example
  const hasError = false;

  // wrap in <Suspense> to wait for the data fetching of useTranslation
  // use the t function to translate a key of namespace 'seeds' and 'common'
  return (
    <Suspense>
      {hasError ? (
        <div>{t("common:unknown_error")}</div>
      ) : (
        <button>{t("seeds:create_seed.btn_create_seed")}</button>
      )}
    </Suspense>
  );
}

Type Safe Keys

The translations are loaded from JSON modules to enable type safety. For this there is a special file @types/i18next.d.ts. Because it is impossible to type based on the chosen language, only the types of the fallback language en are defined.

Backend/Database

If multi-language entries are in the database or send by an endpoint, we use two fields ending with _en and _de.