Fuma Translate

Quick Start

Set up Fuma Translate with compile-time type generation and React.

Fuma Translate extracts translation keys from your React code statically, generates a strict Translations type, and provides a lightweight runtime hook.

Install

npm install fuma-translate @fuma-translate/react

Setup

Add a compile script

Use the CLI to scan your source files and write generated translation files.

package.json
{
  "type": "module",
  "scripts": {
    "compile:translations": "fuma-translate \"src/**/*.tsx\"",
    "dev": "pnpm compile:translations && ...",
    "build": "pnpm compile:translations && ..."
  }
}

By default, this writes .translations/index.ts and .translations/manifest.json.

.translations/index.ts
export type Translations = {
  "Close(dialog button)": string;
  Hello: string;
  "Hello {user}": string;
};

Keys are generated from the original text with meaningful context, your future translation work can rely purely on the keys as a reference.

For other needs, see CLI Usage or Programmatic Usage.

Define translations

Create translations.ts and shape it strictly against the generated type.

translations.ts
import type { Translations } from "./.translations";

export const translations = {
  Hello: "Hello",
  "Hello {user}": "Hello {user}",
  "Close(dialog button)": "Close",
} satisfies Translations;

TypeScript will error on missing/unused keys.

Use in React

Wrap your app with TranslationProvider, then call useTranslations() in components.

app.tsx
import { TranslationProvider, useTranslations } from "@fuma-translate/react";
import { translations } from "./translations";

export function App() {
  return (
    <TranslationProvider translations={translations}>
      <HomePage />
      <Dialog />
    </TranslationProvider>
  );
}

function HomePage() {
  const t = useTranslations();

  return (
    <main>
      <h1>{t("Hello")}</h1>
      <p>{t("Hello {user}", { variables: { user: "Ada" } })}</p>
      {/* `note` provides additional context to the text */}
      <button type="button">{t("Close", { note: "dialog button" })}</button>
    </main>
  );
}

function Dialog() {
  const t = useTranslations({
    note: "text in a dialog",
  });

  return (
    <main>
      {/* will inherit `note` from `useTranslations()` too */}
      <h1>{t("Hello")}</h1>
    </main>
  );
}

useTranslations() returns the translate hook, see the t() section for more details.

For JSX-only usage, import the T component and pass the original text as a static text prop:

import { T } from "@fuma-translate/react";

function HomePage() {
  return <T text="Hello {user}" variables={{ user: <strong>Ada</strong> }} />;
}

You can stack multiple providers to override or extend translations:

<TranslationProvider translations={translations}>
  <TranslationProvider translations={{ Hello: "Bonjour" }}>
    <HomePage />
  </TranslationProvider>
</TranslationProvider>
  1. Write UI with static t("...") calls, t.jsx("...") calls, <T text="..." /> components, or annotated calls/components.
  2. Run pnpm compile:translations to regenerate .translations/index.ts.
  3. Fill in translations.ts to satisfy the generated type.
  4. Pass translations to TranslationProvider.

Translation keys must be statically analyzable. Dynamic keys such as t(key), t(`Hello ${name}`), or <T text={label} /> will fail at compile time.

On this page