Šķîþ ţö ḿàîñ çöñţéñţ

Ţĥé t() éšçàþé ĥàţçĥ

t() ḿàŕķš à šţŕîñĝ ƒöŕ éẋţŕàçţîöñ ŵĥéñ îţ ļîṽéš öüţšîđé ĴŠẊ: à ƃüţţöñ-ļàƃéļ àŕŕàý ƒéđ îñţö à .map, àñ éŕŕöŕ ḿéššàĝé ŕéţüŕñéđ ƒŕöḿ à ŕéđüçéŕ, à ţööļţîþ šţöŕéđ îñ à ŕéƒ. Ţĥé éẋţŕàçţöŕ çàñ'ţ šéé à šţŕîñĝ ƃéĥîñđ àñ éẋþŕéššîöñ, àñđ t() ĝîṽéš îţ à ƃļöçķ ŵîţĥ þļàçéĥöļđéŕš àñđ çöñţéẋţ, ļîķé àñý ĴŠẊ ţéẋţ.

Ţĥé þàţţéŕñ​

import { t } from "@neokapi/i18n-react/runtime";

const UI_LANGUAGES = [
{ value: "en", label: t("English") },
{ value: "qps", label: t("Pseudo English (qps)") },
];

const THEMES = [
{ value: "system", icon: Monitor, label: t("System") },
{ value: "light", icon: Sun, label: t("Light") },
{ value: "dark", icon: Moon, label: t("Dark") },
];

function greet(user: User) {
return t("Hello, {name}!", { name: user.displayName });
}

Àţ ƃüîļđ ţîḿé ţĥé þļüĝîñ ŕéŵŕîţéš éṽéŕý t("...") çàļļ ƃöüñđ ţö @neokapi/i18n-react/runtime îñţö à ĥàšĥ-ƃàšéđ ļööķüþ:

// Input
t("English");

// Output (runtime mode)
__t("aB3xZ", "English");

Îñ đéṽ ḿöđé (þļüĝîñ ñöţ àçţîṽé) t ŕéţüŕñš ţĥé šöüŕçé ţéẋţ ṽéŕƃàţîḿ, ŵîţĥ {name} šüƃšţîţüţîöñš àþþļîéđ. Ýöü çàñ çàļļ îţ àñýŵĥéŕé: ţéšţš, ŠŠŔ, Šţöŕýƃööķ, ţĥé đéṽ šéŕṽéŕ.

Ŵĥý à šéþàŕàţé ḿàŕķéŕ?​

ĴŠẊ ñééđš ñö ŵŕàþþéŕš, ƃéçàüšé ţĥé éļéḿéñţ ţéļļš ţĥé éẋţŕàçţöŕ ŵĥàţ îţ ĥöļđš. Îñ à ĴŠ đàţà šţŕüçţüŕé, ñöţĥîñĝ îñ ţĥé šýñţàẋ ḿàŕķš label: "English" àš çöþý; îţ çöüļđ àš ŵéļļ ƃé àñ ÎĐ, àñ éñüḿ ṽàļüé öŕ à ÇŠŠ çļàšš.

t() ḿàŕķš îţ àš çöþý, ŵîţĥ öñé ƒüñçţîöñ çàļļ þéŕ šţŕîñĝ, àñđ ĴŠẊ šţàýš ƒŕéé öƒ ŵŕàþþéŕš.

Þàŕàḿéţéŕš​

t("Hello, {name}!", { name: "Alice" });
// → "Hello, Alice!" in dev mode
// → translation with {name} substituted at runtime in production

Þàŕàḿéţéŕ šýñţàẋ ḿîŕŕöŕš ŵĥàţ ţĥé ĴŠẊ éẋţŕàçţöŕ üšéš ({name}), šö à ţŕàñšļàţöŕ šééš ţĥé šàḿé þļàçéĥöļđéŕ šĥàþé îñ à šţŕîñĝ ƒŕöḿ ĴŠẊ àñđ îñ öñé ƒŕöḿ t().

Çöñţéẋţ: đîšàḿƃîĝüàţîñĝ îđéñţîçàļ šöüŕçé šţŕîñĝš​

Šöḿé šţŕîñĝš àŕé šþéļļéđ ţĥé šàḿé îñ Éñĝļîšĥ ƃüţ ḿéàñ đéŕéñţ ţĥîñĝš. Šĥöŵñ "Šţàţé" àļöñé îñ à ÇÀŢ ţööļ, à ţŕàñšļàţöŕ çàñ'ţ ţéļļ ŵĥéţĥéŕ îţ ḿéàñš à ÜŠ šţàţé, à ŵöŕķƒļöŵ šţàţüš öŕ à þĥýšîçš šţàţé.

Þàšš à þöšîţîöñàļ çöñţéẋţ àš ţĥé šéçöñđ àŕĝüḿéñţ:

t("State", "US state"); // → address form field
t("State", "workflow status"); // → task lifecycle
t("State", "physics lecture"); // → solid / liquid / gas / plasma

Éàçĥ öƒ ţĥöšé îš à šéþàŕàţé ƃļöçķ ŵîţĥ à đéŕéñţ ĥàšĥ, šö ţŕàñšļàţöŕš çàñ ĝîṽé éàçĥ öñé îţš öŵñ ţàŕĝéţ šţŕîñĝ.

Ŵîţĥ þàŕàḿš, çöñţéẋţ çöḿéš ƒîŕšţ:

t("Hello, {name}!", "greeting", { name: user.name });

Çöñţéẋţ öñļý àƒƒéçţš ţĥé ĥàšĥ àţ éẋţŕàçţ / ţŕàñšƒöŕḿ ţîḿé. Îţ'š šţŕîþþéđ ƒŕöḿ ţĥé éḿîţţéđ __t() çàļļ àñđ ñéṽéŕ šĥîþš ţö ţĥé ŕüñţîḿé; ţĥé ĥàšĥ àļŕéàđý éñçöđéš ţĥé đîšàḿƃîĝüàţîöñ.

Çöñţéẋţ ŵöŕķš ļîķé ĝéţţéẋţ'š msgctxt.

Îḿþöŕţ-ñàḿé ţŕàçķîñĝ​

Ţĥé þļüĝîñ öñļý ŕéŵŕîţéš t îđéñţîƒîéŕš ƃöüñđ ţö @neokapi/i18n-react/runtime. À ļöçàļ ĥéļþéŕ ñàḿéđ t öŕ à t îḿþöŕţéđ ƒŕöḿ à đéŕéñţ ļîƃŕàŕý îš ļéƒţ àļöñé:

import { t } from "@neokapi/i18n-react/runtime";
import { t as styled } from "styled-components"; // ← unrelated

const Wrapper = styled.div`...`; // ← not rewritten

const label = t("Hello"); // ← rewritten to __t("hash", "Hello")

Àļîàšéš ŵöŕķ ţöö:

import { t as tr } from "@neokapi/i18n-react/runtime";

const label = tr("Hello"); // ← rewritten

Ŵĥéŕé ţĥé ĥàšĥ çöḿéš ƒŕöḿ​

t() çàļļš ĥàšĥ öñ à šéþàŕàţé çĥàññéļ ƒŕöḿ ĴŠẊ éẋţŕàçţîöñ:

hash = hashKey(text, "t\x1F" + context) // context is "" when you pass none

À ĴŠẊ ƃļöçķ'š đéšçŕîþţöŕ îš îţš éļéḿéñţ ("button", "p"); à t() çàļļ'š îš ţĥé ļîţéŕàļ t. Šö t("Save") àñđ <button>Save</button> þŕöđüçé đéŕéñţ ĥàšĥéš. Ţĥéý àŕé đéŕéñţ šüŕƒàçéš, àñđ à ţŕàñšļàţöŕ ḿàý ŵàñţ Ĝéŕḿàñ "Šþéîçĥéŕñ" ƒöŕ ţĥé ƃüţţöñ àñđ "Ĝéšþéîçĥéŕţ!" ƒöŕ à ţöàšţ'š t("Saved"). Ţŵö t() çàļļš ŵîţĥ ţĥé šàḿé ţéẋţ çöļļàþšé ţö öñé ķéý üñļéšš ýöü đîšţîñĝüîšĥ ţĥéḿ ŵîţĥ ţĥé šéçöñđ àŕĝüḿéñţ (t("Open", "verb") ṽš t("Open", "adjective")), ţĥé šàḿé msgctxt ḿöđéļ ĝéţţéẋţ üšéš.

Ḿöđüļé-ļéṽéļ t() ĝöţçĥà​

t() ŕéàđš ţĥé àçţîṽé đîçţîöñàŕý àţ çàļļ ţîḿé. À ḿöđüļé-ļéṽéļ çöñšţ éṽàļüàţéš öñçé, àţ îḿþöŕţ, ţýþîçàļļý ƃéƒöŕé ţĥé àþþ ĥàš ƒîñîšĥéđ çàļļîñĝ loadTranslations(), šö îţ ķééþš ţĥé ƒàļļƃàçķ-ļàñĝüàĝé ţéẋţ ƒöŕ ţĥé ļîƒé öƒ ţĥé þàĝé:

// ✗ Frozen at load time. "Utility" still says "Utility" in pseudo.
const categoryMeta = {
utility: { label: t("Utility") },
pipeline: { label: t("Pipeline") },
};

Ƒîẋ: ŵŕàþ ţĥé ļööķüþ îñ à ƒüñçţîöñ ţĥàţ ŕüñš þéŕ ŕéñđéŕ. Éàçĥ çàļļ ŕéàđš ţĥé çüŕŕéñţ đîçţ:

// ✓ Per-render resolution.
function categoryMeta(cat: string) {
switch (cat) {
case "utility":
return { label: t("Utility") };
case "pipeline":
return { label: t("Pipeline") };
// …
}
}

function Chip({ cat }: { cat: string }) {
const meta = categoryMeta(cat);
return <span translate="no">{meta.label}</span>;
// ^ prevents double-wrap; see below.
}

Ŵĥý ţĥé translate="no"? ΃ ţĥé þàŕéñţ ŵöüļđ éẋţŕàçţ öñ îţš öŵñ (îţ ĥàš šţàţîç ţéẋţ, îñļîñé çĥîļđŕéñ àñđ šö öñ), îţ ŵöüļđ ŵŕàþ ţĥé àļŕéàđý-ţŕàñšļàţéđ meta.label îñ à šéçöñđ ţŕàñšļàţîöñ ļàýéŕ, šĥöŵîñĝ ▒ ▒ Utility ▒ ▒ îñ þšéüđö. translate="no" ţéļļš ţĥé éẋţŕàçţöŕ ţĥàţ ţĥé îññéŕ t() öŵñš ţĥé ţŕàñšļàţîöñ ƒöŕ ţĥîš šüƃţŕéé. Šéé Ŵŕîţîñĝ çöḿþöñéñţš → Đöüƃļé-ţŕàñšļàţîöñ.

Ţéŕñàŕý çĥîļđŕéñ ŵîţĥ šţŕîñĝ ļîţéŕàļš​

À þļàîñ šţŕîñĝ ļîţéŕàļ îñ à ƃŕàñçĥ öƒ à ĴŠẊ-çĥîļđŕéñ çöñđîţîöñàļ éẋţŕàçţš öñ îţš öŵñ, ķéýéđ ƃý ţĥé ƃŕàñçĥ'š šļöţ, šö îţ ñééđš ñö t():

// ✓ "Saving..." and "Save" each extract as a block.
<Button>{saving ? "Saving..." : "Save"}</Button>

Ļîţéŕàļš îñ à çöñđîţîöñàļ'š ƃŕàñçĥéš đéšçŕîƃéš ţĥé šļöţ ķéýš.

À ţéḿþļàţé ļîţéŕàļ ŵîţĥ šţàţîç çöþý îñšîđé îš öñé éẋþŕéššîöñ, àñđ éẋţŕàçţîöñ ŕéàđš îţ ŵĥöļé. Ŵŕàþ ţĥàţ ƃŕàñçĥ ŵîţĥ t():

// ✗ template never extracts
<span>{count > 0 ? `Loading ${count}...` : "Idle"}</span>

// ✓ placeholder-aware t()
<span>
{count > 0 ? t("Loading {count}...", { count }) : t("Idle")}
</span>

Ŵŕàþþîñĝ "Idle" îñ t() îš öþţîöñàļ, ƃéçàüšé ţĥé þļàîñ ļîţéŕàļ éẋţŕàçţš öñ îţš öŵñ. À ƒöŕḿàţ-öñļý ţéḿþļàţé ŵîţĥ ñö àļþĥàƃéţîç ţéẋţ, šüçĥ àš `${pct}%` öŕ `v${version}`, îš çöđé-ļéṽéļ ƒöŕḿàţţîñĝ àñđ ñééđš ñö t(). Ţĥé ļîñţ ŕüļé no-ternary-literals-in-jsx-child šķîþš îţ.

Ŵĥéñ ţö üšé t() ṽš. ŕéƒàçţöŕ ţö ĴŠẊ​

Šöḿéţîḿéš îţ îš çļéàŕéŕ ţö ḿöṽé ţĥé šţŕîñĝ îñţö ĴŠẊ:

// Data-driven, needs t()
const THEMES = [
{ value: "system", label: t("System") },
{ value: "light", label: t("Light") },
];

// Unrolled, no t() needed
<button onClick={() => setTheme("system")}>System</button>
<button onClick={() => setTheme("light")}>Light</button>

Ĥéüŕîšţîçš:

  • 3 îţéḿš öŕ ƒéŵéŕ, àñđ ţĥé ŕéñđéŕ îš à šîḿþļé .map → üñŕöļļîñĝ îš üšüàļļý çļéàŕéŕ àñđ ŕéḿöṽéš ţĥé t() çàļļš.
  • Đàţà ļîṽéš îñ à ḿöđüļé öţĥéŕ ţĥàñ ţĥé öñé ŕéñđéŕîñĝ îţ, öŕ îš àššéḿƃļéđ đýñàḿîçàļļý → üšé t().
  • Ţĥé đàţà àļŕéàđý çàŕŕîéš ñöñ-šţŕîñĝ ḿéţàđàţà (îçöñš, çàļļƃàçķš, ÎĐš) → ķééþ îţ àš đàţà, üšé t() ƒöŕ ļàƃéļš.

Ŕüñţîḿé ƒàļļƃàçķ ƃéĥàṽîöüŕ​

Îñ þŕöđüçţîöñ (þļüĝîñ àçţîṽé), __t(hash, fallback, params) đöéš:

  1. Ļööķ üþ hash îñ ţĥé ļöàđéđ đîçţ.
  2. Ŕéšöļṽé ÎÇÜ þļüŕàļ / šéļéçţ ƒöŕḿš îƒ þŕéšéñţ.
  3. Šüƃšţîţüţé {name} ţöķéñš.
  4. Ŕéţüŕñ ţĥé ţŕàñšļàţéđ šţŕîñĝ (öŕ ţĥé ƒàļļƃàçķ îƒ ñö éñţŕý).

Îñ đéṽ (þļüĝîñ ñöţ àçţîṽé), t(text, params) đöéš:

  1. Šüƃšţîţüţé {name} ţöķéñš îñ ţĥé šöüŕçé ţéẋţ.
  2. Ŕéţüŕñ îţ.

Ƃöţĥ ŕéţüŕñ à string. Ƒöŕ à ŔéàçţÑöđé ŕéšüļţ, ŵŕîţé ţĥé ţéẋţ àš ĴŠẊ.

ÉŠĻîñţ / öẋļîñţ: ṽàļîđàţé t() çàļļš​

Ţĥé éẋţŕàçţöŕ çàññöţ ĥàšĥ ţĥé ŕüñţîḿé ṽàļüé îñ t(someVariable). @neokapi/i18n-react-lint ĥàš ŕüļéš ƒöŕ ÉŠĻîñţ àñđ öẋļîñţ ţĥàţ çàţçĥ ţĥîš àñđ ŕéļàţéđ þàţţéŕñš: t('Hello ' + name), <img alt={'Logo ' + brand} />, àñđ šţŕîñĝ ļîţéŕàļš ĥîđđéñ îñ ĴŠẊ éẋþŕéššîöñ çöñţàîñéŕš.

Ŕéļàţéđ​

  • Þļüŕàļš àñđ šéļéçţ: çöüñţ-àŵàŕé àñđ çĥöîçé-ƃàšéđ ţéẋţ, ŵŕîţţéñ ŵîţĥ àüţĥöŕîñĝ çöḿþöñéñţš.
  • Þîþéļîñé: ĥöŵ t() ƃļöçķš ƒļöŵ ţĥŕöüĝĥ éẋţŕàçţ/ţŕàñšļàţé/çöḿþîļé àļöñĝšîđé ĴŠẊ ƃļöçķš.