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

Ţĥé éẋţŕàçţ → ţŕàñšļàţé → çöḿþîļé þîþéļîñé

Ýöüŕ ĴŠẊ ƃéçöḿéš à çàţàļöĝ öƒ ƃļöçķš, ţĥé çàţàļöĝ çöļļéçţš à ţŕàñšļàţîöñ ƒöŕ éàçĥ ţàŕĝéţ ļàñĝüàĝé, àñđ ţĥé ţŕàñšļàţîöñš çöḿé ƃàçķ àš öñé ĴŠÖÑ ƒîļé þéŕ ļöçàļé ţĥàţ ýöüŕ àþþ ļöàđš. Öñé ƒîļé ƒöŕḿàţ, ţĥé ĶƂƑ đîŕéçţöŕý àŕçĥîṽé, çàŕŕîéš ţĥé çöñţéñţ ţĥŕöüĝĥ éṽéŕý þĥàšé.

Àñ öþţîöñàļ ƒöüŕţĥ þĥàšé, šþļîţ, çüţš ţĥé çöḿþîļéđ öüţþüţ àļöñĝ ýöüŕ ƃüñđļéŕ'š çĥüñķš, šö à çöđé-šþļîţ àþþ ļöàđš ţĥé ţŕàñšļàţîöñš ƒöŕ à ŕöüţé ţöĝéţĥéŕ ŵîţĥ îţš çöđé.

Your source codeneokapi-i18n extracti18n/KBF archivekapi translate / pseudo-translate / checkaccumulate target locales in placeneokapi-i18n compilepublic/translations/{locale}.jsonloaded at runtime by your appneokapi-i18n split (optional)dist/translations/{locale}/{chunk}.jsonlazy-loaded per route

Öñé àŕçĥîṽé ţĥŕöüĝĥ ţĥé ŵĥöļé ŕöüñđ ţŕîþ​

Ţĥé i18n/ đîŕéçţöŕý îš ţĥé šöüŕçé öƒ ţŕüţĥ ƒŕöḿ éẋţŕàçţîöñ ţö çöḿþîļé. À ţŕàñšļàţîöñ ţööļ ŕéàđš îţ, àđđš ţĥé ţàŕĝéţ ļöçàļé îţ þŕöđüçéš, àñđ ŵŕîţéš ţĥé ŕéšüļţ ƃàçķ ţö ţĥé šàḿé ƒîļéš. Ļöçàļéš àççüḿüļàţé îñ öñé þļàçé, šö ýöü ĥàṽé öñé àŕçĥîṽé ţö çöḿḿîţ, öñé ţö ĥàñđ ţö ţŕàñšļàţöŕš àñđ öñé ţö çöḿþîļé.

Éàçĥ þĥàšé îš à šéþàŕàţé çöḿḿàñđ, àñđ ñöñé đéþéñđš öñ ĥöŵ àñöţĥéŕ öñé ŕàñ. Àñý þŕöçéšš ţĥàţ ŕéàđš àñđ ŵŕîţéš ĶƂƑ çàñ ţàķé ţĥé ţŕàñšļàţîöñ šţéþ:

  • ĥüḿàñ ţŕàñšļàţöŕš ŵöŕķîñĝ îñ à ÇÀŢ ţööļ
  • ÀÎ ţŕàñšļàţîöñ ŵîţĥ kapi
  • àñ éẋîšţîñĝ ţŕàñšļàţîöñ ḿàñàĝéḿéñţ šýšţéḿ

Þĥàšé 0: éẋþļàîñ (öþţîöñàļ)​

Ƃéƒöŕé ýöü éẋţŕàçţ, ýöü çàñ àšķ ţĥé éẋţŕàçţöŕ ŵĥàţ îţ ŵöüļđ đö ŵîţĥ à ƒîļé, àñđ ŵĥý:

vp neokapi-i18n explain src/components/Settings.tsx
L3 <div> [container] skipped — has block-level children (they extract separately)
L4 <h1> [translatable] extracted hash=cYEMc2v3JVx
L6 <code> [non-translatable] skipped — classified non-translatable
L7 <input> [container] skipped — no translator-editable text
↳ placeholder [attribute] extracted hash=i42kuGUFbb4

Éàçĥ ļîñé ĝîṽéš ţĥé éļéḿéñţ'š Ŵ3Ç ÎŢŠ çļàššîƒîçàţîöñ, ţĥé ŕüļé ţĥàţ đéçîđéđ ŵĥéţĥéŕ îţ éẋţŕàçţš, àñđ ţĥé ĥàšĥ îţ ŕéçéîṽéđ. Üšé îţ ţö àüđîţ žéŕö-çöñƒîĝ éẋţŕàçţîöñ. Àđđ --extracted ţö ļîšţ öñļý ţĥé éļéḿéñţš ţĥàţ ŕéàçĥéđ ţĥé çàţàļöĝ.

Þĥàšé 1: éẋţŕàçţ​

Ţĥé éẋţŕàçţöŕ ŵàļķš éṽéŕý .jsx àñđ .tsx ƒîļé îñ ýöüŕ þŕöĵéçţ àñđ þŕöđüçéš ţŕàñšļàţàƃļé ƃļöçķš. Îţ þàŕšéš þļàîñ .ts, .mts àñđ .cts ḿöđüļéš àš ŢýþéŠçŕîþţ ţöö, àñđ éẋţŕàçţš ţĥéîŕ t() çàļļš ŵĥéñ ţĥé ĝļöƃ îñçļüđéš ţĥéḿ (--src "src/**/*.{ts,tsx,jsx}"). À ƒîļé ţĥàţ ƒàîļš ţö þàŕšé þŕöđüçéš à ŵàŕñîñĝ ţĥàţ ñàḿéš îţ.

Ţĥéŕé àŕé ţŵö öüţþüţ ḿöđéš:

  • Đéƒàüļţ: öñé .kbf.json þéŕ šöüŕçé ƒîļé üñđéŕ --out (đéƒàüļţ i18n/). Ýöü çàñ ŕéàđ àñđ đ ţĥéšé ƒîļéš.
  • --stream: ÑĐĴŠÖÑ ƃļöçķ ŕéçöŕđš öñ šţđöüţ, ƒöŕ à çöñšüḿéŕ ýöü ŵŕîţé ýöüŕšéļƒ. Ŵĥéñ šţđîñ îš à ţéŕḿîñàļ, --src ƒîñđš ţĥé ƒîļéš; öţĥéŕŵîšé à çàļļéŕ çàñ þîþé ÑÜĻ-šéþàŕàţéđ þàţĥš ţö šţđîñ. ķàþî ŕéàđš ţĥé .kbf.json ƒîļéš àñđ îĝñöŕéš ţĥé šţŕéàḿ.
# Default: write .kbf.json files for inspection / commit.
vp neokapi-i18n extract \
--src "src/**/*.{tsx,jsx}" \
--out i18n \
--source-locale en \
--target-locale fr \
--target-locale de \
--target-locale ja

# Or stream NDJSON blocks to stdout for piping:
vp neokapi-i18n extract --stream > i18n/blocks.ndjson

Éẋţŕàçţ ƒļàĝš​

ƑļàĝĐéƒàüļţÞüŕþöšé
--srcsrc/**/*.{tsx,jsx}Ĝļöƃ öƒ šöüŕçé ƒîļéš ţö šçàñ (ŕéþéàţàƃļé; àđđ .ts ţö éẋţŕàçţ t() çàļļš ƒŕöḿ þļàîñ ḿöđüļéš).
--outi18nÖüţþüţ đîŕéçţöŕý ƒöŕ .kbf.json ƒîļéš.
--streamöƒƒÉḿîţ ÑĐĴŠÖÑ ƃļöçķš öñ šţđöüţ îñšţéàđ öƒ ŵŕîţîñĝ .kbf.json.
--ignore(ñöñé)Ĝļöƃ ţö éẋçļüđé (ŕéþéàţàƃļé): ƒîẋţüŕéš, šţöŕîéš, ţéšţš.
--strictöƒƒÉẋîţ ñöñ-žéŕö îƒ àñý ŵàŕñîñĝ ŵàš ŕéçöŕđéđ (ÇÎ éñƒöŕçéḿéñţ).
--config(ñöñé)Þàţĥ ţö à ĴŠÖÑ çöñƒîĝ ƒîļé (çöḿþöñéñţḾàþ, ŕüļéš).
--projectappÞŕöĵéçţ îđ šţàḿþéđ îñţö ţĥé ƒîļé'š project ƒîéļđ.
--source-localeenŠöüŕçé ļöçàļé îñ ƒîļé ḿéţàđàţà.
--target-locale(ñöñé)Đéçļàŕéđ ţàŕĝéţ ļöçàļé (ŕéþéàţàƃļé).

Ŵàŕñîñĝš àñđ šçŕîþţš​

Ţĥé éẋţŕàçţöŕ þŕîñţš à ŵàŕñîñĝ ƒöŕ éàçĥ üñḿàþþéđ Ŕéàçţ çöḿþöñéñţ, ŵîţĥ ţĥé componentMap éñţŕý ţĥàţ ķééþš îţš ĥàšĥ šţàƃļé:

Scanning 186 files...
[neokapi] src/components/Settings.tsx:19: <TabsTrigger> is an unmapped component with translatable text — extracted. Add a componentMap entry: { TabsTrigger: 'button' }.
Extracted 1007 blocks from 186 files → i18n/

Àđđ ţĥé çöḿḿàñđš ţö ýöüŕ þàçķàĝé šçŕîþţš àñđ ÇÎ:

package.json
{
"scripts": {
"extract": "vp neokapi-i18n extract",
"extract:ci": "vp neokapi-i18n extract --strict",
"pack": "vp neokapi-i18n extract --stream > i18n/blocks.ndjson"
}
}

Ƒöŕ çöṽéŕàĝé ŵĥîļé ýöü ŵŕîţé çöđé, àđđ @neokapi/i18n-react-lint. Îţ ḿàŕķš t(variable), <img alt={'Logo ' + x} /> àñđ ţĥé öţĥéŕ þàţţéŕñš ţĥé ƃüîļđ-ţîḿé ţŕàñšƒöŕḿ çàñ'ţ çàţçĥ îñ ýöüŕ éđîţöŕ.

Ŵĥàţ'š îñ ţĥé ĶƂƑ đîŕéçţöŕý​

Ţĥé đîŕéçţöŕý ĥöļđš öñé .kbf.json đöçüḿéñţ þéŕ šöüŕçé ƒîļé àñđ ḿîŕŕöŕš ýöüŕ šöüŕçé ţŕéé, šö src/App.tsx ƃéçöḿéš i18n/src/App.kbf.json. Éṽéŕý ŕüñ ķééþš ţĥé ḿîŕŕöŕ çüŕŕéñţ. Îţ ŕéḿöṽéš ţĥé çàţàļöĝ öƒ à šöüŕçé ƒîļé ýöü đéļéţéđ, ŕéñàḿéđ öŕ éẋçļüđéđ ƒŕöḿ --src, àñđ ļéàṽéš ţĥé þéŕ-ļöçàļé ţàŕĝéţš üñđéŕ i18n/<lang>/ îñ þļàçé.

Éàçĥ çàţàļöĝ îš à šéļƒ-çöñţàîñéđ ĶƂƑ File ŵîţĥ:

  • project: îđ, šöüŕçé ļöçàļé, đéçļàŕéđ ţàŕĝéţ ļöçàļéš.
  • documents: öñé đöçüḿéñţ ƒöŕ ţĥé šöüŕçé ƒîļé, ĥöļđîñĝ îţš Blockš.
  • Öþţîöñàļ ţàŕĝéţš / šķéļéţöñ / àññöţàţîöñ öṽéŕļàýš (àđđéđ ƃý ţŕàñšļàţöŕš).

Šéé Ç-01 ƒöŕ ţĥé ƒüļļ šçĥéḿà.

Ŵĥàţ ƃéçöḿéš à ƃļöçķ​

Ţĥé éẋţŕàçţöŕ ŵŕîţéš öñé ƃļöçķ ƒöŕ éàçĥ:

  • Ţŕàñšļàţàƃļé ĴŠẊ éļéḿéñţ (<h1>, <p>, <button>, àüţö-þŕöḿöţéđ <div>, üñḿàþþéđ çöḿþöñéñţš).
  • Ţŕàñšļàţàƃļé àţţŕîƃüţé (title, placeholder, alt, aria-label, …).
  • Üšéŕ-ƒàçîñĝ t(...) çàļļ.
  • <Plural> / <Select> çöñšţŕüçţ.

Éàçĥ ƃļöçķ çàŕŕîéš:

  • hash: šţàƃļé îđ çöḿþüţéđ ƒŕöḿ ţĥé šöüŕçé ţéẋţ + ţĥé éļéḿéñţ'š öŵñ ţàĝ.
  • source: ţýþéđ ŕüñš (ţéẋţ, þļàçéĥöļđéŕš, îñļîñé éļéḿéñţ ţöķéñš, þļüŕàļ/šéļéçţ ŵŕàþþéŕš).
  • placeholders: ḿéţàđàţà àƃöüţ éàçĥ {name} / {=mN} îñ ţĥé šöüŕçé.
  • properties: ƒîļé + ļîñé + çöḿþöñéñţ ñàḿé + element (ţĥé ŕéšöļṽéđ ţàĝ) + öþţîöñàļ ţŕàñšļàţöŕ ñöţé.

Þĥàšé 2: ţŕàñšļàţé​

Ţĥé .kbf.json ƒîļéš àŕé ŵĥàţ à ţŕàñšļàţöŕ ŵöŕķš öñ. Ţĥéŕé àŕé ţĥŕéé çöḿḿöñ ŵàýš ţö ƒîļļ ţĥéḿ.

Þàţĥ À: ÀÎ ţŕàñšļàţîöñ​

Ŕüñ à ƒüļļ ţŕàñšļàţîöñ þàšš ŵîţĥ kapi translate:

kapi translate i18n/ --target-lang fr
kapi translate i18n/ --target-lang de
kapi translate i18n/ --target-lang ja

Éàçĥ ŕüñ àđđš öñé ţàŕĝéţ ļöçàļé ţö ţĥé šàḿé .kbf.json ƒîļéš. Ţĥé ĶƂƑ ŵŕîţéŕ îš ļöçàļé-àđđîţîṽé: îţ àđđš öŕ üþđàţéš ţĥé ŕéǫüéšţéđ ļöçàļé àñđ ķééþš ţĥé öţĥéŕš. Ýöü ñééđ -o öñļý ţö šéñđ ţĥé öüţþüţ šöḿéŵĥéŕé éļšé.

kapi ŵöŕķš ŵîţĥ ţĥé ÀÎ þŕöṽîđéŕš ļîšţéđ üñđéŕ Ţŕàñšļàţîöñ. Îţ ķééþš þļàçéĥöļđéŕš, îñļîñé éļéḿéñţ ţöķéñš àñđ þļüŕàļ/šéļéçţ šţŕüçţüŕé îñţàçţ, àñđ ŵŕàþš à þŕöṽîđéŕ ţĥàţ đàḿàĝéš ţĥéḿ ŵîţĥ ŕéçöṽéŕý ļöĝîç.

Þàţĥ Ƃ: Þšéüđö-ţŕàñšļàţé​

Ţö çĥéçķ ļàýöüţ, þšéüđö-ţŕàñšļàţîöñ þŕöđüçéš ṽîšîƃļý àļţéŕéđ šţŕîñĝš ŵîţĥ ñö ŕéàļ ţŕàñšļàţîöñ:

kapi pseudo-translate i18n/

Welcome ƃéçöḿéš ▒ Ŵéḷçőḿé ▒, þàđđéđ àñđ àççéñţéđ. À šţŕîñĝ ţĥàţ îš šţîļļ Éñĝļîšĥ šţàñđš öüţ àţ öñçé, àñđ ļàýöüţ ţéšţîñĝ šĥöŵš ţĥé šţŕîñĝš ţĥàţ ŵŕàþ ţöö éàŕļý öŕ ţöö ļàţé.

Þàţĥ Ç: ÇÀŢ ţööļš, à ŢḾŠ öŕ ĥüḿàñ ţŕàñšļàţöŕš​

Ţĥé .kbf.json ƒîļé îš ţĥé éẋçĥàñĝé ƒöŕḿàţ. À ţŕàñšļàţöŕ'š ŵöŕķƒļöŵ ḿîĝĥţ ƃé:

  1. Öþéñ ţĥé i18n/ àŕçĥîṽé (öŕ ţĥé îñđîṽîđüàļ .kbf.json ƒîļéš) îñ ţĥéîŕ ÇÀŢ ţööļ.
  2. Ţŕàñšļàţé éṽéŕý ƃļöçķ, ŵîţĥ ḿàţçĥéš ƒŕöḿ ţĥéîŕ éẋîšţîñĝ çöñţéñţ ḿéḿöŕý.
  3. Šàṽé ƃàçķ ţö ţĥé šàḿé i18n/.

À ḿöđéŕñ ÇÀŢ ţööļ šĥöŵš ţĥé çöñţéẋţ éàçĥ ƃļöçķ çàŕŕîéš: îţš ƒîļé àñđ ļîñé, îţš éļéḿéñţ, ţĥé ţŕàñšļàţöŕ ñöţé àñđ ţĥé îñļîñé éļéḿéñţ ţöķéñš.

Îñ þļàçé, öŕ ŕéđîŕéçţéđ​

Ƒöŕ ĶƂƑ îñþüţš, kapi ţööļ çöḿḿàñđš ŵŕîţé îñ þļàçé: kapi pseudo-translate i18n/ ŕéàđš àñđ ŵŕîţéš ţĥé šàḿé ƒîļéš, ƃéçàüšé ţĥé ĶƂƑ ŵŕîţéŕ àđđš öŕ üþđàţéš ţĥé ŕéǫüéšţéđ ļöçàļé àñđ ķééþš ţĥé öţĥéŕš. Þàšš -o other-dir/ ţö ŵŕîţé éļšéŵĥéŕé àñđ ļéàṽé ţĥé öŕîĝîñàļš üñţöüçĥéđ.

Öţĥéŕ ƒöŕḿàţš (ĴŠÖÑ, ẊĻÎƑƑ, …) ĥöļđ öñé ļöçàļé þéŕ ƒîļé, šö ķàþî ŵŕîţéš à ñéŵ ƒîļé îñ à ļöçàļé-àŵàŕé ļöçàţîöñ:

  • ΃ ţĥé îñþüţ þàţĥ çöñţàîñš ţĥé šöüŕçé ļöçàļé, ķàþî šŵàþš îţ ƒöŕ ţĥé ţàŕĝéţ: src/locales/en/app.json → src/locales/fr/app.json.
  • Öţĥéŕŵîšé ţĥé öüţþüţ ĝöéš ţö à {lang}/ đîŕéçţöŕý ƃéšîđé ţĥé îñþüţ: messages.json → fr/messages.json.
  • -o ţàķéš àñ éẋþļîçîţ þàţĥ öŕ ţéḿþļàţé, àñđ --output-dir DIR ŕööţš ţĥé öüţþüţš àţ DIR/{lang}/.

À ŕüñ ŵîţĥ ñö ţàŕĝéţ ļàñĝüàĝé, šüçĥ àš kapi exec redact, ŕéŵŕîţéš éàçĥ îñþüţ îñ þļàçé, àñđ --output-dir DIR ŵŕîţéš ţĥé öüţþüţš üñđéŕ DIR/.

Ļàýöüţ: öñé ţŕéé, öŕ à šüƃđîŕéçţöŕý þéŕ ļöçàļé​

Ţĥéŕé àŕé ţŵö ļàýöüţš:

  • Ļöçàļé-àđđîţîṽé: öñé i18n/ ţŕéé ŵĥéŕé éàçĥ ƃļöçķ çàŕŕîéš éṽéŕý ţàŕĝéţ ļöçàļé, ƒîļļéđ îñ þļàçé. Ţĥîš îš ŵĥàţ kapi translate i18n/ --target-lang … þŕöđüçéš. Îţ îš ţĥé šîḿþļéšţ ţö ṽéŕšîöñ, ƃéçàüšé àļļ ţŕàñšļàţîöñš šţàý ţöĝéţĥéŕ.
  • Ŕéçîþé-đŕîṽéñ þéŕ-ļöçàļé ƒîļéš: ţĥé šöüŕçé çàţàļöĝš ļîṽé üñđéŕ i18n/src/, àñđ ķàþî ŵŕîţéš à šéþàŕàţé ƒîļé þéŕ ļöçàļé üñđéŕ i18n/{lang}/. À kapi.yaml çöñţéñţ éñţŕý ḿàþš öñé ţö ţĥé öţĥéŕ (path: i18n/src/**/*.kbf.json → target: i18n/{lang}/{path}.kbf.json). kapi init --framework neokapi-i18n šçàƒƒöļđš ţĥîš ļàýöüţ.

Ƃöţĥ ķééþ éṽéŕýţĥîñĝ üñđéŕ öñé i18n/ đîŕéçţöŕý. Ƃéçàüšé ţĥé šöüŕçé ļîṽéš üñđéŕ i18n/src/, ţĥé šöüŕçé ĝļöƃ ñéṽéŕ ḿàţçĥéš ţĥé ĝéñéŕàţéđ i18n/{lang}/ ţàŕĝéţš, àñđ ýöü ñééđ ñö šîƃļîñĝ i18n-<lang>/ ţŕééš. Šéé Ç-01 ƒöŕ ţĥé þŕöĵéçţ ḿöđéļ àñđ Đŕîṽé îţ ƒŕöḿ à þŕöĵéçţ ƒöŕ ţĥé ŕéçîþé.

Þŕöĵéçţ-đŕîṽéñ ƒļöŵ ŵîţĥ kapi.yaml​

Ƒöŕ àñ àþþ ýöü ţŕàñšļàţé éṽéŕý ŕéļéàšé, à kapi.yaml ŕéçîþé ŕéçöŕđš ţĥé ļàñĝüàĝéš, ţĥé ƒļöŵ àñđ ţĥé ĝàţéš öñçé, šö ýöü šţöþ þàššîñĝ ƒļàĝš öñ éṽéŕý çàļļ. kapi init --framework neokapi-i18n ŵŕîţéš öñé. Îţš çöļļéçţîöñ ŕéàđš ţĥé ƃüñđļéŕ'š çàţàļöĝš üñđéŕ i18n/src/ àñđ ḿàþš éàçĥ ţö à þéŕ-ļöçàļé ţàŕĝéţ üñđéŕ i18n/{lang}/:

kapi.yaml
version: v1
name: MyApp
defaults:
source_language: en
target_languages: [fr, de, ja]
collections:
- path: "i18n/src/**/*.kbf.json"
format: kbf
target: "i18n/{lang}/{path}.kbf.json"

Ţĥé ƃüñđļéŕ þļüĝîñ šţîļļ éẋţŕàçţš, àñđ ķàþî ŕéàđš ŵĥàţ îţ ŵŕöţé:

# 1. Extract: the bundler plugin writes one catalog per source file under i18n/src/.
vp neokapi-i18n extract

# 2. Converge: run the project's flow over every target language in the recipe,
# looping until each locale clears its ship gate or waits for review.
kapi up

# 3. Materialize the per-locale catalogs the recipe maps, then compile them.
kapi merge
vp neokapi-i18n compile i18n/ --out public/translations

Ŵĥàţ kapi up đöéš​

  • Îţ ŕé-ŕéàđš ţĥé çöļļéçţîöñ ƃéƒöŕé éàçĥ þàšš, šö îţ þîçķš üþ à çĥàñĝéđ çàţàļöĝ ŵîţĥöüţ à šéþàŕàţé îḿþöŕţ šţéþ.
  • À ŕéçîþé ŵîţĥ ñö ƒļöŵ öƒ îţš öŵñ ŕüñš ţĥé ƃüîļţ-îñ đéƒàüļţ: çöñţéñţ ḿéḿöŕý ŕéüšé, ţĥéñ ÀÎ ţŕàñšļàţîöñ, ţĥéñ ţĥé þŕöĵéçţ'š ƃöüñđ çĥéçķš.
  • Îţ ŕéþöŕţš à ļöçàļé ţĥàţ îš ƃéĥîñđ àš þéñđîñĝ. À ţàŕĝéţ ļàñĝüàĝé ţĥàţ ļàĝš ţĥé šöüŕçé ñéṽéŕ ḿàķéš îţ ƒàîļ.
  • Ŵîţĥ defaults.materialize: on-converge îñ ţĥé ŕéçîþé, öŕ kapi up --materialize, îţ ŵŕîţéš ţĥé ţàŕĝéţ ƒîļéš îţšéļƒ ƒöŕ ţĥé ļöçàļéš ŵĥöšé ĝàţéš àŕé ḿéţ, àñđ ýöü çàñ đŕöþ ţĥé kapi merge šţéþ.

kapi status šĥöŵš ţĥé çöṽéŕàĝé þéŕ ļöçàļé ŵîţĥöüţ ŕüññîñĝ àñýţĥîñĝ. kapi check --ship éñƒöŕçéš ţĥé ĝàţéš, ƒöŕ éẋàḿþļé ƃéƒöŕé à ŕéļéàšé ţàĝ. kapi run <flow> ŕüñš öñé ñàḿéđ ƒļöŵ ƒöŕ à šîñĝļé þàšš öṽéŕ ţĥé šàḿé ŕéçîþé.

Àđ-ĥöç çöḿḿàñđš (ñö kapi.yaml)​

Ƒöŕ à ǫüîçķ ŕüñ, šķîþ ţĥé ŕéçîþé àñđ þöîñţ ţĥé çöḿḿàñđš àţ ţĥé àŕçĥîṽé:

vp neokapi-i18n extract
kapi pseudo-translate i18n/
vp neokapi-i18n compile i18n/ --out public/translations

Éàçĥ çöḿḿàñđ ŕéàđš àñđ ŵŕîţéš ţĥé šàḿé .kbf.json ƒîļéš. Ţĥé ŕéçîþé ŕéçöŕđš ţĥé ļàñĝüàĝéš, ţĥé ƒļöŵ àñđ ţĥé ĝàţéš, šö ţĥàţ kapi up çàñ ŕéþéàţ ţĥé ŕüñ.

Þĥàšé 3: çöḿþîļé​

neokapi-i18n compile ŕéàđš ţĥé ţŕàñšļàţéđ .kbf.json ƒîļéš àñđ ŵŕîţéš öñé ĴŠÖÑ đîçţ þéŕ ļöçàļé:

neokapi-i18n compile i18n/ \
--out public/translations

Öüţþüţ:

Compiled 1007 entries → public/translations/fr.json
Compiled 1007 entries → public/translations/de.json
Compiled 1007 entries → public/translations/ja.json

Éàçĥ ĴŠÖÑ ƒîļé îš à ƒļàţ {hash: renderedText} ḿàþ. Ţĥé ŕüñţîḿé __t(hash, fallback, params) ļööķš üþ ţĥé ĥàšĥ, àñđ ţĥé ŕéñđéŕéŕ þîçķš ţĥé þļüŕàļ öŕ šéļéçţ ƒöŕḿ.

Çöḿþîļé ļéàṽéš öüţ à ţàŕĝéţ ţĥàţ ļàçķš àñý þļàçéĥöļđéŕ îţš šöüŕçé ĥàš. Ţĥé ŕüñţîḿé ţĥéñ šĥöŵš ţĥé šöüŕçé ţéẋţ ƒöŕ ţĥàţ éñţŕý, ŵîţĥ îţš çöüñţ, ñàḿé öŕ ļîñķ îñţàçţ. Ţĥé ñéẋţ ţŕàñšļàţîöñ þàšš ƒîļļš ţĥé éñţŕý îñ öñçé ţĥé ţàŕĝéţ îš šöüñđ.

Þĥàšé 4: šþļîţ (öþţîöñàļ)​

Îñ à çöđé-šþļîţ àþþ, à šîñĝļé {locale}.json þéŕ ļöçàļé ḿàķéš ţĥé üšéŕ đöŵñļöàđ éṽéŕý šţŕîñĝ, îñçļüđîñĝ ţĥöšé ƒöŕ ŕöüţéš ţĥéý ñéṽéŕ ṽîšîţ. Ţĥé ƃüñđļéŕ þļüĝîñ àñđ neokapi-i18n split đîṽîđé ţĥé çàţàļöĝ àļöñĝ çĥüñķ ƃöüñđàŕîéš, šö éàçĥ çĥüñķ ĝéţš îţš öŵñ ţŕàñšļàţîöñ šüƃšéţ ƃéšîđé îţš ĴŠ.

Šþļîţ ţàķéš ţŵö îñþüţš:

  • translations-manifest.json: éḿîţţéđ ŵĥéñ mode: "runtime" ƃý Ṽîţé, Ŕöļļüþ, ŵéƃþàçķ, Ŕšþàçķ, àñđ éšƃüîļđ (éšƃüîļđ ñééđš metafile: true). Ḿàþš éàçĥ öüţþüţ çĥüñķ ţö ţĥé šéţ öƒ ĥàšĥéš îţš ḿöđüļéš ŕéƒéŕéñçé.
  • public/translations/{locale}.json: ţĥé çöḿþîļéđ ḿàšţéŕ đîçţ ƒŕöḿ Þĥàšé 3.
vite build # emits dist/translations-manifest.json
neokapi-i18n compile i18n/ --out public/translations
neokapi-i18n split \
--manifest dist/translations-manifest.json \
--locales public/translations \
--out dist/translations

Öüţþüţ:

dist/translations/
├── manifest.json ← copy of the chunk → hashes map
└── {locale}/
├── index.json ← hashes used by the main chunk
├── SettingsPage.json
└── FlowEditor.json

Ļöàđ à çĥüñķ ŵîţĥ îţš ŕöüţé​

Šþļîţ çöþîéš à ĥàšĥ ţĥàţ šéṽéŕàļ çĥüñķš üšé îñţö éàçĥ šüƃšéţ, šö éṽéŕý çĥüñķ ƒîļé ļöàđš öñ îţš öŵñ. Éàçĥ ļàžý ŕöüţé ñééđš öñé ḿöŕé ļîñé:

import { loadTranslationChunk } from "@neokapi/i18n-react/runtime";

const routes = [
{
path: "/settings",
lazy: async () => {
const [mod] = await Promise.all([
import("./SettingsPage"),
loadTranslationChunk(locale, `/translations/${locale}/SettingsPage.json`),
]);
return { Component: mod.default };
},
},
];

loadTranslationChunk ḿéŕĝéš ţĥé šüƃšéţ îñţö ţĥé àçţîṽé đîçţ, àñđ çöñçüŕŕéñţ çàļļš ƒöŕ ţĥé šàḿé (locale, url) šĥàŕé à šîñĝļé ƒéţçĥ. À ĥàšĥ ţĥàţ ĥàš ñöţ àŕŕîṽéđ ýéţ ŕéñđéŕš ţĥé šöüŕçé ţéẋţ ƃàķéđ îñţö îţš __t / __tx çàļļ àţ ƃüîļđ ţîḿé, šö à ļàţé çĥüñķ ñéṽéŕ ƃŕéàķš ţĥé ŕéñđéŕ. Šéé Ŕüñţîḿé ḿöđé → Ļàžý ļöàđîñĝ þéŕ ŕöüţé ƒöŕ ţĥé ƒüļļ ŕüñţîḿé çöñţŕàçţ.

Àñ àþþ ţĥàţ šĥîþš à šîñĝļé ƃüñđļé šķîþš ţĥîš þĥàšé àñđ çàļļš loadTranslations(locale, url) ŵîţĥ ţĥé çöḿþîļéđ ḿàšţéŕ đîçţ.

Ŕöüñđ ţŕîþ îñ öñé đîàĝŕàḿ​

src/App.tsx<h1>Welcome</h1>neokapi-i18n extract (source only)i18n/ Blockhash "aB3" · source + targetskapi translate --target-lang frthen de … (additive, in place)neokapi-i18n compilepublic/translations/{locale}.json{ "aB3": "Bienvenue" }loadTranslations(locale, url)single bundle → app rendersneokapi-i18n split (optional)dist/translations/{locale}/index.json + lazy chunksloadTranslationChunk per routeYour app renders "Bienvenue"

ÇÎ: ŕé-éẋţŕàçţ öñ éṽéŕý ƃüîļđ, ƒàîļ öñ đŕîƒţ​

Ţĥé šàḿé šöüŕçé àñđ çöñƒîĝ àļŵàýš éẋţŕàçţ ţö ţĥé šàḿé àŕçĥîṽé. ÇÎ çàñ ţĥéŕéƒöŕé ŕé-éẋţŕàçţ àñđ ƒàîļ ŵĥéñ ţĥé çöḿḿîţţéđ i18n/ đéŕš:

.github/workflows/ci.yml
- name: Extract translatable content
run: npm run extract

- name: Fail if translators need to re-open the file
run: |
git diff --exit-code i18n/ || {
echo "::error::i18n/ drifted. Re-extract locally and commit."
exit 1
}

Àñ àþþ ŵîţĥ à ţŕàñšļàţîöñ ƃàçķéñđ þüšĥéš ţĥé àŕçĥîṽé ţö ţĥàţ ƃàçķéñđ îñšţéàđ àñđ ŵàîţš ƒöŕ ţĥé ţŕàñšļàţéđ ƒîļéš. Éîţĥéŕ ŵàý, ýöü éẋţŕàçţ öñ éṽéŕý çĥàñĝé, šö ţĥé ţŕàñšļàţîöñš ƒöļļöŵ ţĥé šöüŕçé.

Îñçŕéḿéñţàļ éẋţŕàçţš​

Ţĥé éẋţŕàçţöŕ ķééþš ñö šţàţé ƃéţŵééñ ŕüñš: ţĥé šàḿé šöüŕçé àñđ çöñƒîĝ àļŵàýš þŕöđüçé ţĥé šàḿé .kbf.json. Ţö ţŕàñšļàţé öñļý ŵĥàţ çĥàñĝéđ, çöḿþàŕé ţŵö àŕçĥîṽéš öñ ţĥé ţŕàñšļàţîöñ šîđé. À ƃļöçķ ŵĥöšé ĥàšĥ çĥàñĝéđ ĥàš ñéŵ šöüŕçé ţéẋţ.

Ŕéļàţéđ​