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

Ǫüîçķ šţàŕţ

Îñ àƃöüţ ƒîṽé ḿîñüţéš ýöü'ļļ ĥàṽé à Ṽîţé + Ŕéàçţ àþþ ţĥàţ šŵîţçĥéš ƃéţŵééñ Éñĝļîšĥ àñđ þšéüđö-Éñĝļîšĥ ƒŕöḿ à ļàñĝüàĝé þîçķéŕ. Ýöü ŵŕîţé ţĥé ĴŠẊ àš üšüàļ, àñđ ţĥé ƃüîļđ ƒîñđš ţĥé ţéẋţ ţö ţŕàñšļàţé.

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

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. Éẋţŕàçţ ţö à ĶƂƑ đîŕéçţöŕý​

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

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 ţüŕñš ţĥé ţŕàñšļàţéđ ĶƂƑ îñţö à {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() šüƃšçŕîƃéš ţĥé çöḿþöñéñţ ţö ñéöķàþî-î18ñ'š ţŕàñšļàţîöñ šţöŕé, šö à ļöçàļé çĥàñĝé ŕé-ŕéñđéŕš îţ àñđ îţš šüƃţŕéé ŵîţĥöüţ à ñàṽîĝàţîöñ.

Ŵĥàţ éàçĥ šţéþ đîđ​

  1. Ýöü ŵŕöţé þļàîñ ĴŠẊ, ŵîţĥ ñö ŵŕàþþéŕš.
  2. Ţĥé þļüĝîñ ƒöüñđ éṽéŕý ţŕàñšļàţàƃļé éļéḿéñţ àţ ƃüîļđ ţîḿé, çöḿþüţéđ šţàƃļé ĥàšĥéš, àñđ ŕéŵŕöţé ţĥé ĴŠẊ ţö ļööķ ţĥéḿ üþ àţ ŕéñđéŕ ţîḿé.
  3. ķàþî àđđéđ qps ţàŕĝéţš ţö ţĥé ĶƂƑ çàţàļöĝš.
  4. ñéöķàþî-î18ñ çöḿþîļé ţüŕñéđ ţĥé çàţàļöĝš îñţö à ĴŠÖÑ đîçţ ýöüŕ àþþ ļöàđš.
  5. Ţĥé ŕüñţîḿé ŕéšöļṽéđ éàçĥ ĥàšĥ öñ ŕéñđéŕ. Àñ üñķñöŵñ ĥàšĥ ŕéñđéŕš ţĥé ĴŠẊ šöüŕçé ţéẋţ, šö ţĥé àþþ ñéṽéŕ šĥöŵš à ŕàŵ îđéñţîƒîéŕ.

Ŕéļàţéđ​