Ǫüîçķ šţàŕţ
Îñ àƃöüţ ƒîṽé ḿîñüţéš ýöü'ļļ ĥàṽé à Ṽîţé + Ŕéàçţ àþþ ţĥàţ šŵîţçĥéš ƃéţŵééñ Éñĝļîšĥ àñđ þšéüđö-Éñĝļîšĥ ƒŕöḿ à ļàñĝüàĝé þîçķéŕ. Ýöü ŵŕîţé ţĥé ĴŠẊ àš üšüàļ, àñđ ţĥé ƃüîļđ ƒîñđš ţĥé ţéẋţ ţö ţŕàñšļàţé.
1. Îñšţàļļ
npm install -D @neokapi/i18n-react
Ţĥé þàçķàĝé šĥîþš à ƃüîļđ þļüĝîñ (Ṽîţé, Ŕöļļüþ, ŵéƃþàçķ, Ŕšþàçķ, éšƃüîļđ), ţĥé extract / compile / split / explain ÇĻÎ šüƃçöḿḿàñđš, àñđ ţĥé ŕüñţîḿé (~2 ķƂ). Ñö þééŕ đéþéñđéñçîéš ƃéýöñđ Ŕéàçţ 18+.
Ţĥé kapi ÇĻÎ ƒîļļš ţĥé ĶƂƑ đîŕéçţöŕý ţĥàţ ñéöķàþî-î18ñ éẋţŕàçţš ŵîţĥ ţŕàñšļàţîöñš. Ţĥîš ĝüîđé üšéš îţ ƒöŕ þšéüđö-ţŕàñšļàţîöñ. Îñšţàļļ îţ ţöö:
# macOS / Linux
brew install neokapi/tap/kapi-cli
Öñ Ŵîñđöŵš, ţàķé à ƃüîļđ ƒŕöḿ ţĥé đîŕéçţ đöŵñļöàđš. Ţĥé ŴîñĜéţ þàçķàĝé îš ƃéĥîñđ ţĥé çüŕŕéñţ ŕéļéàšé.
Üšé @neokapi/i18n-react 3 ŵîţĥ ķàþî 1.3.0 öŕ ļàţéŕ. Ƃöţĥ ŕéàđ àñđ ŵŕîţé ţĥé
ĶƂƑ šçĥéḿà 2 çàţàļöĝš ţĥàţ çàŕŕý éàçĥ šţŕîñĝ'š šöüŕçé àñđ ţŕàñšļàţîöñš ţöĝéţĥéŕ.
Àñ öļđéŕ ķàþî çàññöţ ŕéàđ ţĥé çàţàļöĝš @neokapi/i18n-react 3 éẋţŕàçţš, àñđ àñ
öļđéŕ @neokapi/i18n-react çöḿþîļéš ñöñé öƒ ţĥé ţŕàñšļàţîöñš à çüŕŕéñţ ķàþî
ŵŕîţéš. kapi inspect öñ à çàţàļöĝ éẋîţš ŵîţĥ àñ éŕŕöŕ ŵĥéñ ţĥé îñšţàļļéđ ķàþî
çàññöţ ŕéàđ îţ.
2. Àđđ ţĥé þļüĝîñ ţö vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import neokapi from "@neokapi/i18n-react/vite";
export default defineConfig({
plugins: [
neokapi({ mode: "runtime" }), // ← add this
react(),
],
});
Ţŵö ḿöđéš àŕé àṽàîļàƃļé; þîçķ runtime ƒöŕ ñöŵ:
runtime: šĥîþ öñé ƃüñđļé; ļöàđ à ţŕàñšļàţîöñ đîçţ àţ ŕüñţîḿé ṽîàfetch. Ĝööđ ƒöŕ àþþš ţĥàţ šĥîþ ḿàñý ļöçàļéš ƒŕöḿ à ÇĐÑ.inline: þŕöđüçé öñé ƃüñđļé þéŕ ļöçàļé ŵîţĥ ţŕàñšļàţîöñš þŕé-îñļîñéđ. Žéŕö ŕüñţîḿé ļööķüþ, ƒàšţéšţ ƒîŕšţ þàîñţ.
3. Ŵŕîţé ĴŠẊ àš ýöü ñöŕḿàļļý ŵöüļđ
export default function App() {
return (
<main>
<h1>Welcome to Acme</h1>
<p>Ship your product in every language your users speak.</p>
<button>Get started</button>
</main>
);
}
Ţĥéŕé àŕé ñö t(...) çàļļš àñđ ñö ķéýš. Ţĥé þļüĝîñ ŵàļķš ţĥé ĴŠẊ àţ ƃüîļđ ţîḿé àñđ ŕéŵŕîţéš éàçĥ ţŕàñšļàţàƃļé šîţé ţö à ĥàšĥ-ƃàšéđ ļööķüþ.
4. Éẋţŕàçţ ţö à ĶƂƑ đîŕéçţöŕý
Ŵîŕé ţĥé éẋţŕàçţöŕ àñđ ţĥé çöḿþîļéŕ îñţö ýöüŕ þàçķàĝé šçŕîþţš:
{
"scripts": {
"extract": "vp neokapi-i18n extract",
"compile": "vp neokapi-i18n compile i18n/ --out public/translations"
}
}
Ţĥé vp þŕéƒîẋ îš ţĥé Ṽîţé+ ŕüññéŕ, üšéđ ţĥŕöüĝĥöüţ ţĥéšé þàĝéš. À þŕöĵéçţ ŵîţĥöüţ îţ ŕüñš neokapi-i18n extract đîŕéçţļý: ţĥé đéṽ đéþéñđéñçý þüţš ţĥé ƃîñàŕý öñ ţĥé šçŕîþţ'š PATH.
Ŕüñ éẋţŕàçţ:
npm run extract
Öüţþüţ:
Scanning 1 files...
Extracted 3 blocks from 1 files → i18n/
i18n/ îš à đîŕéçţöŕý çàŕŕýîñĝ öñé .kbf.json đöçüḿéñţ þéŕ šöüŕçé ƒîļé, ḿîŕŕöŕîñĝ
ýöüŕ šöüŕçé ţŕéé (é.ĝ. i18n/src/App.kbf.json). Ţĥé ţĥŕéé ƃļöçķš àŕé
"Ŵéļçöḿé ţö Àçḿé", ţĥé þàŕàĝŕàþĥ, àñđ "Ĝéţ šţàŕţéđ". Éàçĥ ƒîļé îš þļàîñ ĴŠÖÑ, àš
ţĥé šüƒƒîẋ šàýš, šö ýöüŕ éđîţöŕ, jq àñđ ĜîţĤüƃ ŕéàđ îţ ŵîţĥ ñö šéţüþ àñđ îţš
đš šţàý ŕéàđàƃļé.
5. Þšéüđö-ţŕàñšļàţé ŵîţĥ kapi
Þšéüđö-ţŕàñšļàţîöñ þŕöđüçéš àççéñţéđ šţŕîñĝš šüçĥ àš ▒ Wëlcömé tö Âcmé ▒. Éṽéŕý éẋţŕàçţéđ šţŕîñĝ çĥàñĝéš, šö à šţŕîñĝ ţĥàţ šţîļļ ŕéàđš àš þļàîñ Éñĝļîšĥ îš öñé ţĥé éẋţŕàçţöŕ ḿîššéđ.
kapi pseudo-translate i18n/
6. Çöḿþîļé ţö à ŕüñţîḿé đîçţ
neokapi-i18n compile ţüŕñš ţĥé ţŕàñšļàţéđ ĶƂƑ îñţö à {locale}.json ƒîļé þéŕ ļöçàļé:
npm run compile
Öüţþüţ:
Compiled 3 entries → public/translations/qps.json
Ţĥé ĴŠÖÑ îš { "<hash>": "<flattened target text>" }.
7. Ļöàđ ţĥé ţŕàñšļàţîöñ àţ ŕüñţîḿé
Ţŵö ļîñéš îñ ýöüŕ àþþ ƃööţšţŕàþ:
import { loadTranslations } from "@neokapi/i18n-react/runtime";
import ReactDOM from "react-dom/client";
import App from "./App";
async function bootstrap() {
await loadTranslations("qps", "/translations/qps.json").catch(() => {});
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
}
void bootstrap();
loadTranslations(locale, url) ƒéţçĥéš ţĥé đîçţ àñđ àçţîṽàţéš îţ. Àƒţéŕ îţ ŕéšöļṽéš, éṽéŕý ŕéñđéŕéđ <h1>Welcome to Acme</h1> ŕéñđéŕš àš ▒ Wëlcömé tö Âcmé ▒. Ţö ƃüîļđ à ƒàļļƃàçķ çĥàîñ, šüçĥ àš pt-BR öṽéŕ pt, þàšš àñ àŕŕàý öƒ ÜŔĻš îñšţéàđ öƒ öñé.
8. Àđđ à ļàñĝüàĝé šŵîţçĥéŕ (öþţîöñàļ)
À 10-ļîñé ļàñĝüàĝé þîçķéŕ ŵîŕéđ ţö setTranslations / loadTranslations:
import { loadTranslations, setTranslations, useNeokapi } from "@neokapi/i18n-react/runtime";
export function LocaleSwitcher() {
useNeokapi(); // subscribe so the component re-renders on locale change
return (
<select
onChange={async (e) => {
const value = e.target.value;
if (value === "en") setTranslations("en", {});
else await loadTranslations(value, `/translations/${value}.json`);
}}
>
<option value="en">English</option>
<option value="qps">Pseudo-English</option>
</select>
);
}
useNeokapi() šüƃšçŕîƃéš ţĥé çöḿþöñéñţ ţö ñéöķàþî-î18ñ'š ţŕàñšļàţîöñ šţöŕé, šö à ļöçàļé çĥàñĝé ŕé-ŕéñđéŕš îţ àñđ îţš šüƃţŕéé ŵîţĥöüţ à ñàṽîĝàţîöñ.
Ŵĥàţ éàçĥ šţéþ đîđ
- Ýöü ŵŕöţé þļàîñ ĴŠẊ, ŵîţĥ ñö ŵŕàþþéŕš.
- Ţĥé þļüĝîñ ƒöüñđ éṽéŕý ţŕàñšļàţàƃļé éļéḿéñţ àţ ƃüîļđ ţîḿé, çöḿþüţéđ šţàƃļé ĥàšĥéš, àñđ ŕéŵŕöţé ţĥé ĴŠẊ ţö ļööķ ţĥéḿ üþ àţ ŕéñđéŕ ţîḿé.
- ķàþî àđđéđ
qpsţàŕĝéţš ţö ţĥé ĶƂƑ çàţàļöĝš. - ñéöķàþî-î18ñ çöḿþîļé ţüŕñéđ ţĥé çàţàļöĝš îñţö à ĴŠÖÑ đîçţ ýöüŕ àþþ ļöàđš.
- Ţĥé ŕüñţîḿé ŕéšöļṽéđ éàçĥ ĥàšĥ öñ ŕéñđéŕ. Àñ üñķñöŵñ ĥàšĥ ŕéñđéŕš ţĥé ĴŠẊ šöüŕçé ţéẋţ, šö ţĥé àþþ ñéṽéŕ šĥöŵš à ŕàŵ îđéñţîƒîéŕ.
Ŕéļàţéđ
- Ŵŕîţîñĝ ţŕàñšļàţàƃļé çöḿþöñéñţš: ŵĥàţ ñéöķàþî-î18ñ þîçķš üþ àüţöḿàţîçàļļý, ŵĥàţ îţ ŵàŕñš àƃöüţ, àñđ ĥöŵ ţö öþţ öüţ.
- Þļüŕàļš àñđ šéļéçţ: ÇĻĐŔ-àŵàŕé þļüŕàļ àüţĥöŕîñĝ ŵîţĥöüţ ÎÇÜ šţŕîñĝš îñ ýöüŕ šöüŕçé.
- Éẋţŕàçţ → ţŕàñšļàţé → çöḿþîļé þîþéļîñé: ÀÎ ţŕàñšļàţîöñ, îñçŕéḿéñţàļ éẋţŕàçţš, ÇÎ îñţéĝŕàţîöñ.
t()éšçàþé ĥàţçĥ: ƒöŕ ţĥé šţŕîñĝš ţĥàţ ĝéñüîñéļý ƃéļöñĝ öüţšîđé ĴŠẊ.