Ǫüîçķ šţàŕţ
Àđđ neokapi-î18ñ ţö à Ṽîţé + Ŕéàçţ þŕöĵéçţ. ~5 ḿîñüţéš; ýöü'ļļ ƒîñîšĥ ŵîţĥ à ŕüññîñĝ àþþ ţĥàţ ƒļîþš ƃéţŵééñ Éñĝļîšĥ àñđ þšéüđö-Éñĝļîšĥ ƒŕöḿ à ţööļƃàŕ.
1. Îñšţàļļ
npm install -D @neokapi/i18n-react
Ţĥé þàçķàĝé šĥîþš à ƃüîļđ þļüĝîñ (Ṽîţé, Ŕöļļüþ, ŵéƃþàçķ, Ŕšþàçķ, éšƃüîļđ), ţĥé extract / compile / split / explain ÇĻÎ šüƃçöḿḿàñđš, àñđ ţĥé ţîñý ŕüñţîḿé (~2 ķƂ). Ñö þééŕ đéþéñđéñçîéš ƃéýöñđ Ŕéàçţ 18+.
Ţĥé kapi ÇĻÎ îš ţĥé ţŕàñšļàţîöñ þîþéļîñé ţĥàţ þŕöđüçéš þšéüđö-ţŕàñšļàţîöñš ƒŕöḿ ţĥé KBF đîŕéçţöŕý neokapi-î18ñ éẋţŕàçţš. Îñšţàļļ îţ ţöö:
# macOS / Linux
brew install neokapi/tap/kapi-cli
Öñ Ŵîñđöŵš, ţàķé à ƃüîļđ ƒŕöḿ ţĥé đîŕéçţ đöŵñļöàđš. Ţĥé ŴîñĜéţ þàçķàĝé îš ƃéĥîñđ ţĥé çüŕŕéñţ ŕéļéàšé.
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. Éẋţŕàçţ ţö à KBF đîŕéçţöŕý
Ŵîŕé ţĥé éẋţŕàçţöŕ àñđ ţĥé çöḿþîļéŕ îñţö ýöüŕ þàçķàĝé šçŕîþţš:
{
"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 ţüŕñš ţĥé ţŕàñšļàţéđ KBF îñţö à {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() ŵîŕéš ţĥé ŕööţ öƒ ýöüŕ ţŕéé îñţö neokapi-î18ñ'š ţŕàñšļàţîöñ šţöŕé šö à ļöçàļé çĥàñĝé ŕé-ŕéñđéŕš ţĥé ŵĥöļé šüƃšçŕîƃéđ šüƃţŕéé, ŵîţĥ ñö ñàṽîĝàţîöñ ŕéǫüîŕéđ.
Ŵĥàţ ĵüšţ ĥàþþéñéđ
- Žéŕö ŵŕàþþéŕš: ýöü ŵŕöţé ñöŕḿàļ ĴŠẊ.
- Þļüĝîñ éẋţŕàçţéđ éṽéŕý ţŕàñšļàţàƃļé éļéḿéñţ àţ ƃüîļđ ţîḿé, çöḿþüţéđ šţàƃļé ĥàšĥéš, àñđ ŕéŵŕöţé ţĥé ĴŠẊ ţö ļööķ ţĥéḿ üþ àţ ŕéñđéŕ ţîḿé.
- kapi þšéüđö-ţŕàñšļàţéđ ţĥé KBF → àñöţĥéŕ KBF ŵîţĥ
qpsţàŕĝéţš þöþüļàţéđ. - neokapi-î18ñ çöḿþîļéđ ţĥàţ KBF ţö à ĴŠÖÑ đîçţ ýöüŕ àþþ ļöàđš.
- Ţĥé ŕüñţîḿé ŕéšöļṽéđ éàçĥ ĥàšĥ öñ ŕéñđéŕ; üñķñöŵñ ĥàšĥéš ƒàļļ ƃàçķ ţö ţĥé ĴŠẊ šöüŕçé ţéẋţ, šö ţĥé àþþ ñéṽéŕ šĥöŵš ŕàŵ îđéñţîƒîéŕš.
Ñéẋţ šţéþš
- Ŵŕîţîñĝ ţŕàñšļàţàƃļé çöḿþöñéñţš: ŵĥàţ neokapi-î18ñ þîçķš üþ àüţöḿàţîçàļļý, ŵĥàţ îţ ŵàŕñš àƃöüţ, àñđ ĥöŵ ţö öþţ öüţ.
- Þļüŕàļš àñđ šéļéçţ: ÇĻĐŔ-àŵàŕé þļüŕàļ àüţĥöŕîñĝ ŵîţĥöüţ ÎÇÜ šţŕîñĝš îñ ýöüŕ šöüŕçé.
- Éẋţŕàçţ → ţŕàñšļàţé → çöḿþîļé þîþéļîñé: ÀÎ ţŕàñšļàţîöñ, îñçŕéḿéñţàļ éẋţŕàçţš, ÇÎ îñţéĝŕàţîöñ.
t()éšçàþé ĥàţçĥ: ƒöŕ ţĥé šţŕîñĝš ţĥàţ ĝéñüîñéļý ƃéļöñĝ öüţšîđé ĴŠẊ.