Fuma Translate

Translate Hook

All-in-one function to translate text.

useTranslations() returns t, a function for translating plain strings in your UI.

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

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

  return <h1>{t("Hello")}</h1>;
}
  • looks up the translations from TranslationProvider.
  • falls back to the original text when no translation is defined.
  • handles variable placeholders.

Variables

Pass dynamic values with variables. Placeholder names in the string must match the keys you provide:

t("Hello {user}", { variables: { user: "Ada" } });
// => "Hello Ada"

Translators can reorder placeholders in the translated string.

Notes

Use note to disambiguate the same text in different contexts. The compiler encodes it into the translation key as text(note):

t("Close", { note: "dialog button" });
// key: "Close(dialog button)"

You can also set a hook-level note. All t() calls from that hook inherit it:

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

  return <h1>{t("Hello")}</h1>;
  // key: "Hello(text in a dialog)"
}

Per-call note values are appended after the hook-level note.

Translation Keys

The compiler extracts keys from static t("...") calls.

export type Translations = {
  Hello: string;
  "Hello {user}": string;
  "Close(dialog button)": string;
};

Translation keys must be statically analyzable. Dynamic keys such as t(key) or t(`Hello ${name}`) will fail at compile time.

Comment Annotations

Use a // @fuma-translate comment when you need the compiler to extract a translation from a compatible wrapper function or component.

The annotation must be immediately above the call or JSX element:

function Label({ text, note }: { text: string; note?: string }) {
  return <span data-note={note}>{text}</span>;
}

function legacyTranslate(text: string, options?: { note?: string }) {
  return text;
}

export function Sidebar() {
  // @fuma-translate
  return legacyTranslate("Open menu", { note: "sidebar button" });
}

export function Header() {
  return (
    // @fuma-translate
    <Label text="Open menu" note="header button" />
  );
}

Inside a JSX fragment, use a JSX comment:

export function Footer() {
  return (
    <>
      {/* @fuma-translate */}
      <Label text="Contact us" />
    </>
  );
}

As Component

Use <T /> when you want JSX syntax instead of calling t() or t.jsx() manually.

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

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

<T /> accepts the same rich text options as t.jsx():

function LoginHint() {
  return (
    <T
      text="Click <login>here</login> or <signup/>."
      tags={{
        login: (children) => <a href="/login">{children}</a>,
        signup: () => <a href="/signup">create an account</a>,
      }}
    />
  );
}

Add note to disambiguate reused text:

<T text="Close" note="dialog button" />
// key: "Close(dialog button)"

The compiler only extracts <T /> components imported from @fuma-translate/react. Aliased imports are supported:

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

<Translate text="Read docs" />;

text and note must be static strings, and spread attributes are not supported.

React Server Components

<T /> can be rendered from React Server Components. When passing rich text tags across the RSC boundary, prefer React elements instead of render functions:

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

export default function Page() {
  return (
    <T
      text="Read <docs>the docs</docs>"
      tags={{
        docs: <a href="/docs" />,
      }}
    />
  );
}

JSX

Use t.jsx() when a translation needs React/JSX nodes, such as links, buttons, bold text, or other markup. It works like t(), but returns JSX nodes instead of a plain string.

Variables as React nodes

{variable} placeholders accept React nodes in variables:

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

  return (
    <p>
      {t.jsx("Hello {user}", {
        variables: {
          user: <strong>Ada</strong>,
        },
      })}
    </p>
  );
}

Translators can reorder {user} in the translated string. The runtime substitutes each placeholder with the matching React node.

Placeholder tags

For rich text, write placeholder tags in the message and map each tag to a render function in tags:

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

  return (
    <p>
      {t.jsx("Click <login>here</login> or <signup/>.", {
        tags: {
          login: (children) => <a href="/login">{children}</a>,
          signup: () => <a href="/signup">create an account</a>,
        },
      })}
    </p>
  );
}
  • Paired tags: <login>here</login> passes the inner content to the render function as children.
  • Self-closing tags: <signup/> calls the render function with no children, useful for fixed elements like buttons.

Tags can be nested:

t.jsx("Read <bold><link>docs</link></bold>", {
  tags: {
    bold: (children) => <b>{children}</b>,
    link: (children) => <a href="/docs">{children}</a>,
  },
});

If a tag has no matching entry in tags, its children are rendered as plain text.

Escaping

Prefix { or < with a backslash to render them literally:

t.jsx("Use \\{brackets} and \\<tags>");
// renders: Use {brackets} and <tags>

On this page