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

Ǫüîçķ šţàŕţ

Àđđ 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. Ŵŕîţé ĴŠẊ àš ýöü ñöŕḿàļļý ŵöüļđ

src/App.tsx
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 đîŕéçţöŕý

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

package.json
{
"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. Ļöàđ ţĥé ţŕàñšļàţîöñ àţ ŕüñţîḿé

Ţŵö ļîñéš îñ ýöüŕ àþþ ƃööţšţŕàþ:

src/main.tsx
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 ţö à ĴŠÖÑ đîçţ ýöüŕ àþþ ļöàđš.
  • Ţĥé ŕüñţîḿé ŕéšöļṽéđ éàçĥ ĥàšĥ öñ ŕéñđéŕ; üñķñöŵñ ĥàšĥéš ƒàļļ ƃàçķ ţö ţĥé ĴŠẊ šöüŕçé ţéẋţ, šö ţĥé àþþ ñéṽéŕ šĥöŵš ŕàŵ îđéñţîƒîéŕš.

Ñéẋţ šţéþš