Zurück zu allen Dokumentationen

Diese Integrationsanleitung ist nur auf Englisch verfügbar.

BeMyWords with React + i18next

Integration guide for React applications using i18next / react-i18next.

Shape of the integration:

  1. At build time, fetch translations from BeMyWords into JSON resource files.
  2. Configure i18next to load those resource files at app startup.
  3. Use useTranslation() / t() as normal.

No runtime dependency on BeMyWords.


Before and after

The point of this integration is that the only translation file you edit is English. Concretely:

Before — every namespace × every language is a JSON file someone keeps in sync:

public/locales/
├── en/common.json     ← you add a key here
├── nb/common.json     ← …then here
├── sv/common.json     ← …and here
└── de/common.json     ← …and here, usually copy-pasted English
const { t } = useTranslation();
t("checkout.shipping.free_over_threshold");
// nb → "checkout.shipping.free_over_threshold"  (key echoed back)

After — the non-English files are build output, fetched before the bundler runs:

src/locales/
├── en.json     ← the only file you edit (or push from BeMyWords)
├── nb.json     ← written by scripts/fetch-translations.mjs, gitignored
├── sv.json     ← written
└── de.json     ← written
const { t } = useTranslation();
t("checkout.shipping.free_over_threshold");
// nb → "Gratis frakt over 500 kr"

The diff in package.json is one line — the fetch runs before the build, so a missing translation can never reach the bundle as a raw key:

- "build": "vite build"
+ "build": "node scripts/fetch-translations.mjs && vite build"

Nothing changes in your components: useTranslation() and t() work exactly as before, i18next still loads plain JSON, and the shipped bundle has no BeMyWords code in it. Because the fetch happens at build time, a BeMyWords outage cannot affect your running app — the worst case is a build that ships the previous translations.


Prerequisites

  • A React app (Create React App, Vite, etc.) with i18next + react-i18next installed, or ready to install.
  • A BeMyWords workspace, project, and namespace.
  • A BeMyWords API key.
npm install i18next react-i18next i18next-browser-languagedetector

1. Environment variables

.env.local:

BEMYWORDS_BASE_URL=https://www.bemywords.org
BEMYWORDS_PROJECT_ID=<your-project-uuid>
BEMYWORDS_API_TOKEN=<your-api-token>
BEMYWORDS_NAMESPACE=app

The BEMYWORDS_API_TOKEN is used only at build time, not shipped to the browser. Keep it out of NEXT_PUBLIC_* / VITE_* / any client-exposed prefix.

2. Fetch script

scripts/fetch-translations.mjs:

import { writeFileSync, mkdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const OUTPUT_DIR = join(__dirname, "..", "public", "locales");

const BASE_URL = process.env.BEMYWORDS_BASE_URL || "https://www.bemywords.org";
const PROJECT_ID = process.env.BEMYWORDS_PROJECT_ID;
const API_TOKEN = process.env.BEMYWORDS_API_TOKEN;
const NAMESPACE = process.env.BEMYWORDS_NAMESPACE || "app";

// Languages your app supports
const LANGUAGES = ["en", "nb"];

async function fetchLanguage(lang) {
  const url = `${BASE_URL}/api/${PROJECT_ID}/latest/${lang}/${NAMESPACE}`;
  const res = await fetch(url, {
    headers: { Authorization: `Token token=${API_TOKEN}` },
  });
  if (!res.ok) throw new Error(`${lang}: HTTP ${res.status}`);
  return res.json();
}

async function main() {
  if (!PROJECT_ID || !API_TOKEN) {
    console.warn("BEMYWORDS_PROJECT_ID or BEMYWORDS_API_TOKEN not set.");
    return;
  }

  for (const lang of LANGUAGES) {
    const data = await fetchLanguage(lang);
    const langDir = join(OUTPUT_DIR, lang);
    mkdirSync(langDir, { recursive: true });
    writeFileSync(
      join(langDir, `${NAMESPACE}.json`),
      JSON.stringify(data, null, 2) + "\n"
    );
    console.log(`  [${lang}] ${Object.keys(data).length} keys`);
  }
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});

i18next's HTTP backend expects files at /locales/<lang>/<namespace>.json — we write them there directly.

3. Configure i18next

src/i18n.ts:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(HttpBackend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "en",
    supportedLngs: ["en", "nb"],
    defaultNS: "app",
    ns: ["app"],
    backend: {
      loadPath: "/locales/{{lng}}/{{ns}}.json",
    },
    interpolation: { escapeValue: false },
  });

export default i18n;

Import this in your app entry point (main.tsx / index.tsx):

import "./i18n";

4. Use in components

import { useTranslation } from "react-i18next";

export function HeroSection() {
  const { t } = useTranslation();

  return (
    <>
      <h1>{t("home.hero.title", "Localization for solo devs and small teams")}</h1>
      <p>{t("home.hero.subtitle", "Cheap, simple, developer-first.")}</p>
    </>
  );
}

The second argument to t() is the default value — shown if the key is missing in the current language. Keep this as your English master.

5. Hook into build

package.json:

{
  "scripts": {
    "predev": "node scripts/fetch-translations.mjs",
    "prebuild": "node scripts/fetch-translations.mjs",
    "dev": "vite",
    "build": "vite build"
  }
}

The pre- hooks run automatically before dev/build.

6. (Optional) Push English to BeMyWords

Extract your t() calls' default values or maintain an en.json manually, then:

const url = `${BASE_URL}/api/overwrite/${PROJECT_ID}/latest/en/${NAMESPACE}`;
await fetch(url, {
  method: "PUT",
  headers: {
    Authorization: `Token token=${API_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ translations: englishMaster }),
});

Extraction tools like i18next-parser can produce en.json from your source code automatically.

React i18next pluralization and interpolation

i18next supports CLDR plurals out of the box. Key names for plurals follow i18next conventions:

{
  "apples_one":   "1 apple",
  "apples_other": "{{count}} apples"
}

Use t("apples", { count: 3 }) — i18next picks the right plural form.

Interpolation uses {{name}}:

{ "greeting": "Hello, {{name}}!" }

Store these strings as-is in BeMyWords. BeMyWords preserves placeholders verbatim. Use placeholder validation in BeMyWords project settings to catch broken translations.


API reference

See Astro integration guide — the API is framework-agnostic.

Troubleshooting

Translations show as raw keys (home.hero.title) in the browser: i18next couldn't load the JSON. Check DevTools Network tab for /locales/en/app.json — it should return 200. Re-run the fetch script if needed.

NEXT_PUBLIC_BEMYWORDS_* exposes your API token: don't use NEXT_PUBLIC_ / VITE_ prefix for BEMYWORDS_API_TOKEN. The fetch script runs in Node at build time and has access to non-public env vars.

Pluralization doesn't kick in: i18next v21+ uses suffix _one, _other, _few, _many, _zero. Older versions used _plural. Check your i18next version and plural suffix convention.

Was kostet BeMyWords?

Kostenloser Start: 5.000 gespeicherte Wörter, 1 Website, keine Karte. Bezahlte Tarife beginnen bei 199 kr pro Monat – 1.990 kr pro Jahr – und werden nach gespeicherten Wörtern berechnet – die Quelle plus jede Übersetzung davon – niemals nach Sitzplätzen, Seitenaufrufen oder einer Begrenzung der Anzahl der Sprachen, die Sie betreiben. Bringen Sie Ihren eigenen KI-Schlüssel mit und es kostet pauschal 199 kr pro Monat ohne Wortlimit.

Welche Preisstufe das ist, hängt davon ab, wie viele Wörter Ihre Website enthält. Fügen Sie Ihre Adresse ein und wir lesen sie aus und sagen es Ihnen.

Jeder Tarif und was er enthält Größe Ihrer Website ermitteln

Installiere es auf dem Projekt, über das du gerade gelesen hast.

Der kostenlose Tarif reicht aus, um eine Landing-Page zu übersetzen und die Ausgabe selbst zu beurteilen. Keine Karte erforderlich.

Kostenlos starten