Ce guide d'intégration est disponible en anglais uniquement.
BeMyWords with React + i18next
Integration guide for React applications using i18next / react-i18next.
Shape of the integration:
- At build time, fetch translations from BeMyWords into JSON resource files.
- Configure i18next to load those resource files at app startup.
- 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-i18nextinstalled, 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.
Combien coûte BeMyWords ?
Gratuit pour commencer : 5 000 mots stockés, 1 site, sans carte. Les forfaits payants commencent à 199 kr par mois — 1 990 kr pour un an — et sont comptés sur les mots stockés — la source plus chaque traduction — jamais sur les sièges, les pages vues ou un plafond sur le nombre de langues que vous utilisez. Apportez votre propre clé IA et c'est 199 kr par mois forfaitaire sans limite de mots.
La formule correspondante dépend du nombre de mots que contient votre site. Collez votre adresse et nous la lirons pour vous le dire.
Chaque forfait et ce qu'il contient Évaluer votre siteInstallez-le sur le projet que vous venez de découvrir.
Le forfait gratuit suffit pour traduire une landing page et juger le résultat par vous-même. Aucune carte requise.
Commencer gratuitement