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.
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.no
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.no";
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.
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.