Zurück zu allen Dokumentationen

Diese Integrationsanleitung ist nur auf Englisch verfügbar.

BeMyWords with Next.js

Integration guide for Next.js applications, covering both the App Router and Pages Router. Uses next-intl for the App Router path (recommended for new apps) and next-i18next for Pages Router.

Shape of the integration:

  1. At build time, fetch translations from BeMyWords into JSON resource files.
  2. Configure the i18n library to load those files server-side (App Router) or pre-generate pages (Pages Router).
  3. Use the library's hooks / server components to render translations.

No runtime BeMyWords dependency — translations are baked in at build time.


Prerequisites

  • A Next.js app (14+ recommended for App Router).
  • A BeMyWords workspace, project, namespace, API key.

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

⚠ Do not prefix BEMYWORDS_API_TOKEN with NEXT_PUBLIC_ — the token must stay server-side.

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, "..", "messages");

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

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() {
  mkdirSync(OUTPUT_DIR, { recursive: true });

  if (!PROJECT_ID || !API_TOKEN) {
    console.warn("BEMYWORDS_PROJECT_ID or BEMYWORDS_API_TOKEN not set.");
    for (const lang of LANGUAGES) {
      writeFileSync(join(OUTPUT_DIR, `${lang}.json`), "{}\n");
    }
    return;
  }

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

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

Add messages/ to .gitignore if you regenerate every build.

Install:

npm install next-intl

next.config.js:

const createNextIntlPlugin = require("next-intl/plugin");
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");

module.exports = withNextIntl({
  // your existing Next.js config
});

src/i18n/request.ts:

import { getRequestConfig } from "next-intl/server";

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`../../messages/${locale}.json`)).default,
}));

src/app/[locale]/layout.tsx:

import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";

export default async function LocaleLayout({
  children,
  params: { locale },
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Use in a server component:

import { useTranslations } from "next-intl";

export default function Home() {
  const t = useTranslations();
  return <h1>{t("home.hero.title")}</h1>;
}

Use in a client component:

"use client";
import { useTranslations } from "next-intl";

export default function InteractiveBit() {
  const t = useTranslations();
  return <button>{t("common.actions.save")}</button>;
}

BeMyWords returns flat dot-keys ("home.hero.title": "..."). next-intl accepts either flat or nested; no conversion needed.

4. Pages Router setup

Install:

npm install next-i18next react-i18next i18next

next-i18next.config.js:

module.exports = {
  i18n: {
    defaultLocale: "en",
    locales: ["en", "nb"],
  },
  localePath: "./public/locales",
};

Adjust the fetch script to write to public/locales/<lang>/<namespace>.json instead of messages/<lang>.json.

In pages/_app.tsx:

import { appWithTranslation } from "next-i18next";
export default appWithTranslation(MyApp);

In any page:

import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";

export async function getStaticProps({ locale }) {
  return {
    props: { ...(await serverSideTranslations(locale, ["app"])) },
  };
}

export default function Home() {
  const { t } = useTranslation();
  return <h1>{t("home.hero.title", "Localization for solo devs and small teams")}</h1>;
}

5. Hook into build

package.json:

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

6. (Optional) Push English to BeMyWords

Same as React + i18next.

7. Vercel / deployment platforms

Set the four BEMYWORDS_* env vars in your deployment provider's project settings. They need to exist at build time only; they're not accessed at runtime.

For Vercel: Project Settings → Environment Variables. Scope to Production + Preview + Development.

Pluralization

next-intl uses ICU message format:

{ "apples": "{count, plural, =0 {no apples} one {1 apple} other {# apples}}" }

t("apples", { count: 3 }) returns "3 apples".

Store these ICU strings verbatim in BeMyWords. Enable ICU placeholder validation in project settings.

next-i18next / i18next uses the suffix convention: apples_one, apples_other.


API reference

See Astro integration guide.

Troubleshooting

App Router: "Unable to find next-intl locale": check that the [locale] dynamic segment in your folder structure matches the locale values returned by i18n config.

Pages Router: translations missing on client-side navigation: ensure you included the namespace in serverSideTranslations() for every page.

Large JSON bundle on client: for App Router, server components render translations server-side — the JSON doesn't ship to the client unless you put it in a client component. Prefer server components where possible.

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