Ţĥé 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} šüƃšţîţüţîöñš àþþļîéđ. Šö ýöü çàñ üšé îţ üñçöñđîţîöñàļļý (ţéšţš, ŠŠŔ, šţöŕýƃööķ, đéṽ šéŕṽéŕ).
Ŵĥý à šéþàŕàţé ḿàŕķéŕ?
neokapi-î18ñ'š þŕöḿîšé îš žéŕö ŵŕàþþéŕš ƒöŕ ĴŠẊ. ĴŠ đàţà šţŕüçţüŕéš àŕé đéŕéñţ: ţĥé éẋţŕàçţöŕ ĥàš ñö ÀŠŢ-ļéṽéļ šîĝñàļ ţĥàţ 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"); // → h / cold / 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() îš ţĥé šîñĝļé šöüŕçé öƒ ţŕüţĥ ƒöŕ ţĥîš
šüƃţŕéé. Šéé Ŵŕîţîñĝ çöḿþöñéñţš → Đöüƃļé-ţŕàñšļàţîöñ.
Ţéŕñàŕý çĥîļđŕéñ ŵîţĥ šţŕîñĝ ļîţéŕàļš
neokapi-î18ñ ţŕéàţš ţĥé ŵĥöļé JSXExpressionContainer àš öñé
þļàçéĥöļđéŕ; îţ ñéṽéŕ ļööķš îñšîđé à ţéŕñàŕý àţ îţš ƃŕàñçĥéš:
// ✗ Neither "Saving..." nor "Save" gets extracted.
<Button>{saving ? "Saving..." : "Save"}</Button>
Ŵŕàþ éàçĥ ƃŕàñçĥ ŵîţĥ t():
<Button>{saving ? t("Saving...") : t("Save")}</Button>
Ţéḿþļàţé ļîţéŕàļš ŵîţĥ šţàţîç çöþý îñšîđé: šàḿé ţŕéàţḿéñţ.
// ✗ template never extracts
<span>{count > 0 ? `Loading ${count}...` : "Idle"}</span>
// ✓ placeholder-aware t()
<span>
{count > 0 ? t("Loading {count}...", { count }) : t("Idle")}
</span>
Ţĥé "Idle" ƃéšîđé îţ ñééđš ñöţĥîñĝ: à þļàîñ šţŕîñĝ ļîţéŕàļ îñ à
ƃŕàñçĥ éẋţŕàçţš öñ îţš öŵñ, ķéýéđ ƃý ţĥé ƃŕàñçĥ'š šļöţ. Þüŕéļý-ƒöŕḿàţ
ţéḿþļàţéš (ñö àļþĥàƃéţîç ţéẋţ: `${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) đöéš:
- Ļööķ üþ
hashîñ ţĥé ļöàđéđ đîçţ. - Ŕéšöļṽé ÎÇÜ þļüŕàļ / šéļéçţ ƒöŕḿš îƒ þŕéšéñţ.
- Šüƃšţîţüţé
{name}ţöķéñš. - Ŕéţüŕñ ţĥé ţŕàñšļàţéđ šţŕîñĝ (öŕ ţĥé ƒàļļƃàçķ îƒ ñö éñţŕý).
Îñ đéṽ (þļüĝîñ ñöţ àçţîṽé), t(text, params) đöéš:
- Šüƃšţîţüţé
{name}ţöķéñš îñ ţĥé šöüŕçé ţéẋţ. - Ŕéţüŕñ îţ.
Ƃöţĥ ŕéţüŕñ à string. Ƒöŕ à ŔéàçţÑöđé ŕéšüļţ ýöü ñééđ ţĥé ĴŠẊ þàţĥ.
ÉŠĻîñţ / öẋļîñţ: ķééþ t() ĥöñéšţ
t(someVariable) đéƒéàţš ţĥé þöîñţ: ţĥé éẋţŕàçţöŕ ĥàš ñö ţéẋţ ţö ĥàšĥ. Îñšţàļļ @neokapi/i18n-react-lint ŵĥîçĥ šĥîþš ŕüļéš ƒöŕ ƃöţĥ ÉŠĻîñţ àñđ öẋļîñţ ţĥàţ çàţçĥ ţĥîš àñđ ţĥé ŕéļàţéđ þîţƒàļļš (t('Hello ' + name), <img alt={'Logo ' + brand} />, šţŕîñĝ ļîţéŕàļš ĥîđđéñ îñ ĴŠẊ éẋþŕéššîöñ çöñţàîñéŕš).
Ñéẋţ
- Þļüŕàļš àñđ šéļéçţ: ţĥé öţĥéŕ þàţţéŕñ ŵĥéŕé ýöü ñééđ éẋþļîçîţ ḿàŕķéŕš (ƒöŕ ţĥé þļüŕàļ/çàšé àüţĥöŕîñĝ çöḿþöñéñţš).
- Þîþéļîñé: ĥöŵ
t()ƃļöçķš ƒļöŵ ţĥŕöüĝĥ éẋţŕàçţ/ţŕàñšļàţé/çöḿþîļé àļöñĝšîđé ĴŠẊ ƃļöçķš.