Skip to content

Translations

This document describes how we handle translations in the Registration Backend.

Overview

We use a pragmatic approach where translation keys that are used in the databse are stored in a central translation file in our codebase. The i18n generation tool can then automatically discover and generate translation files, enabling us to serve translated content dynamically without doing manual mapping.

How It Works

We store translation keys in the database. The translations.server.ts file tells the i18n generation tool which keys are used in the DB, so it can generate translation files for them.

1. When translation Keys will be stored in DB, add them to translations file

Add all database translation keys in app/database/translations.server.ts so the i18n generation tool knows about them:

export const translations = [
  i18n`PERSONALIZATION_ACCOUNT_PURPOSE_TITLE`,
  i18n`PERSONALIZATION_ACCOUNT_PURPOSE_SUBTITLE`,
  i18n`PERSONALIZATION_ACCOUNT_PURPOSE_LABEL_FACT_CHECK`,
];

This tells the i18n package: "these keys are used in our codebase, please generate translations for them."

2. Generate Translation Files

The generate-i18n script:

  • Scans all TypeScript files for valid translation keys
  • Finds keys in translations.server.ts
  • Updates hash in app/i18n.ts
  • Generates JSON files with actual translated text: public/locales/{hash}/{language}/common.json

3. Serves translated text

At runtime, translates the keys when serving API responses:

import { translate } from "~/translate.server.js";
import { getLanguage } from "~/helpers/get-language.js";

export async function loader({ request }: Route.LoaderArgs) {
  const lang = getLanguage(request);
  const { t } = await translate(lang);

  // Fetch data containing translation keys
  const stepData = stepsData["account-purpose"];

  // Translate keys to actual text based on user's language
  return success({
    title: t(stepData.title), // EN: "What do you plan to use..."
    subtitle: t(stepData.subtitle), // DE: "Definieren Sie Ihre Ziele..."
  });
}

Configuration

Supported languages are configured in app/i18n.ts:

export const config = {
  supportedLngs: ["de", "en"],
  fallbackLng: "en",
} satisfies I18nConfig;

Why This Approach?

This is the most pragmatic approach for our use case:

  • No manual mapping: Translation keys in codebase are automatically discovered by the i18n tool
  • Easy to serve: Just fetch from DB and translate keys at runtime

Alternative Approaches

Other approaches we considered:

1. Copy Translations from Monolith

Get all locales/translations from the monolith and store them in our codebase.

2. Use Prefixed Keys with Custom Script

Use a prefix like PERSONALIZATION_DATABASE_XXX for all DB keys and create a custom script that filters these out and creates translation files on the fly.

3. Store Translated Text in DB

Store actual translated text in the database