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

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

Ţĥŕéé þĥàšéš, öñé çöñţŕàçţ: ţĥé KBF đîŕéçţöŕý àŕçĥîṽé. À ƒöüŕţĥ öþţîöñàļ þĥàšé, šþļîţ, šļîçéš ţĥé çöḿþîļéđ öüţþüţ àļöñĝ ƃüñđļéŕ çĥüñķ ļîñéš šö çöđé-šþļîţ àþþš çàñ ļàžý-ļöàđ ţŕàñšļàţîöñš þéŕ ŕöüţé.

Your source codeneokapi-i18n extracti18n/KBF archivekapi translate / pseudo-translate / qa / reviewaccumulate 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/ îš ţĥé šöüŕçé-öƒ-ţŕüţĥ àŕţîƒàçţ ţĥŕöüĝĥ ţĥé ŵĥöļé ŕöüñđ-ţŕîþ. Ţŕàñšļàţîöñ ţööļš ŕéàđ îţ, àþþéñđ ţĥé ţàŕĝéţ ļöçàļé ţĥéý'ŕé þŕöđüçîñĝ, àñđ ŵŕîţé ƃàçķ ţö ţĥé šàḿé ƒîļé, šö ýöü àççüḿüļàţé ļöçàļéš ŕàţĥéŕ ţĥàñ ĵüĝĝļîñĝ þéŕ-ŕüñ öüţþüţ ƒîļéš. Öñé ƒîļé îñ ţĥé ŕéþö, öñé ƒîļé ţö šĥîþ ţö ţŕàñšļàţöŕš, öñé ƒîļé ţö çöḿþîļé.

Éàçĥ þĥàšé ĥàš à šîñĝļé ţööļ; ñöñé öƒ ţĥéḿ àŕé çöüþļéđ ţö ţĥé öţĥéŕš. Ýöü çàñ šŵàþ öüţ ţĥé ţŕàñšļàţöŕ šţéþ ƒöŕ àñý þŕöçéšš ţĥàţ þŕéšéŕṽéš ţĥé KBF çöñţŕàçţ: ĥüḿàñ ţŕàñšļàţöŕš ŵöŕķîñĝ îñ à ÇÀŢ ţööļ, ÀÎ ţŕàñšļàţîöñ, à þŕé-éẋîšţîñĝ ŢḾŠ.

Þĥàšé 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 ĝļöƃ ŵĥéñ šţđîñ îš à ţéŕḿîñàļ; kapi'š éẋéç ƒöŕḿàţ çàñ þîþé ÑÜĻ-šéþàŕàţéđ þàţĥš ţö šţđîñ ƒöŕ ƃàţçĥ-çöñţŕöļļéđ éẋţŕàçţîöñ.
# 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 đîŕéçţöŕý

À đîŕéçţöŕý öƒ þéŕ-ƒîļé .kbf.json đöçüḿéñţš, ḿîŕŕöŕîñĝ ýöüŕ šöüŕçé ţŕéé (é.ĝ. src/App.tsxi18n/src/App.kbf.json). Ţĥé ḿîŕŕöŕ îš ķéþţ öñ éṽéŕý ŕüñ: à çàţàļöĝ ƒöŕ à šöüŕçé ţĥàţ ĥàš ƃééñ đéļéţéđ, ŕéñàḿéđ öŕ éẋçļüđéđ ƒŕöḿ --src îš ŕéḿöṽéđ, ŵĥîļé þéŕ-ļöçàļé ţàŕĝéţš üñđéŕ i18n/<lang>/ àŕé ļéƒţ àļöñé. Éàçĥ çàţàļöĝ îš à šéļƒ-çöñţàîñéđ KBF 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 ţööļ çöḿḿàñđš đéƒàüļţ ţö îñ-þļàçé ƒöŕ KBF îñþüţš: kapi pseudo-translate i18n/ ŕéàđš àñđ ŵŕîţéš ţĥé šàḿé ƒîļéš, šîñçé ţĥé KBF ŵŕîţéŕ îš ļöçàļé-àđđîţîṽé (îţ àđđš öŕ üþđàţéš ţĥé ŕéǫüéšţéđ ļöçàļé, ļéàṽîñĝ ţĥé öţĥéŕš îñţàçţ). Þàšš -o other-dir/ ţö ŕéđîŕéçţ ŵîţĥöüţ ţöüçĥîñĝ ţĥé öŕîĝîñàļš.

Ñöñ-KBF ƒöŕḿàţš (ĴŠÖÑ, ẊĻÎƑƑ, …) àŕéñ'ţ ļöçàļé-àđđîţîṽé, šö ţĥéý ŵŕîţé à ñéŵ ƒîļé îñ à ļöçàļé-àŵàŕé ļöçàţîöñ: îƒ ţĥé îñþüţ þàţĥ çàŕŕîéš ţĥé šöüŕçé ļöçàļé îţ îš šŵàþþéđ ƒöŕ ţĥé ţàŕĝéţ (src/locales/en/app.json → src/locales/fr/app.json), öţĥéŕŵîšé ţĥé öüţþüţ ļàñđš üñđéŕ à {lang}/ đîŕéçţöŕý ƃéšîđé ţĥé îñþüţ (messages.json → fr/messages.json). Üšé -o ƒöŕ àñ éẋþļîçîţ þàţĥ öŕ ţéḿþļàţé, öŕ --output-dir DIR ţö ŕööţ öüţþüţš àţ DIR/{lang}/.

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

Ţŵö ļàýöüţš, ƃöţĥ çļéàñ:

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

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

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

΃ ýöü àļŕéàđý üšé à kapi.yaml þŕöĵéçţ ƒîļé ţö đéƒîñé ýöüŕ ŵöŕķƒļöŵ, đéçļàŕé éàçĥ àŕçĥîṽé-ƃàçķéđ çöļļéçţîöñ ŵîţĥ àñ exec ƒöŕḿàţ þöîñţîñĝ àţ neokapi-î18ñ (öŕ àñý öţĥéŕ éẋţŕàçţöŕ):

kapi.yaml
version: v1
name: MyApp
defaults:
source_language: en
target_languages: [fr, de, ja]
collections:
- name: ui
# Block state lives in the project cache (gitignored, regenerable).
content:
- path: "src/**/*.tsx"
format:
name: exec
config:
command: "vp neokapi-i18n extract --stream"
# 1. Extract: kapi runs the declared command for each collection and
# streams NDJSON blocks into the collection's block store.
kapi extract -p kapi.yaml

# 2. Translate: run a composed flow over the project for each target language.
kapi run translate-qa -p kapi.yaml

Ţĥé command šţŕîñĝ þîçķš ţĥé þàçķàĝé ḿàñàĝéŕ (vp, pnpm, npm, yarn, öŕ à đîŕéçţ ƃîñàŕý þàţĥ), šö ţĥé þŕöĵéçţ đéçļàŕéš îţš þŕéƒéŕéñçéš éẋþļîçîţļý ŵîţĥöüţ kapi ḿàķîñĝ àššüḿþţîöñš. kapi run ţĥéñ éẋéçüţéš ţĥé ñàḿéđ ƒļöŵ àĝàîñšţ ţĥé þŕöĵéçţ'š éẋţŕàçţéđ ƃļöçķš ƒöŕ éàçĥ ţàŕĝéţ ļàñĝüàĝé.

Šţàñđàļöñé þîþé (ñö kapi.yaml)

Ƒöŕ àđ-ĥöç þŕöĵéçţš, šķîþ kapi.yaml éñţîŕéļý àñđ çöḿþöšé ŵîţĥ Üñîẋ þîþéš:

vp neokapi-i18n extract --stream > i18n/blocks.ndjson
kapi pseudo-translate i18n/
vp neokapi-i18n compile i18n/ --out public/translations

Šàḿé üñđéŕļýîñĝ ŵîŕé ƒöŕḿàţ (ÑĐĴŠÖÑ öñ ţĥé éẋţŕàçţ šţàĝé, KBF ƒŕöḿ ţĥéŕé öñ); ţĥé đéçļàŕàţîṽé kapi.yaml ƒöŕḿ ƒàçţöŕš ţĥé þîþé îñţö ţĥé þŕöĵéçţ ƒîļé.

Þĥàšé 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"

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

Ţĥé éẋţŕàçţ îš đéţéŕḿîñîšţîç, šö ÇÎ çàñ üšé ţĥé àŕçĥîṽé ĥàšĥ àš à çöñţŕàçţ:

.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 ƒöŕ ţĥé šàḿé šöüŕçé + çöñƒîĝ. Ƒöŕ àñ îñçŕéḿéñţàļ þîþéļîñé (öñļý ţŕàñšļàţé ŵĥàţ çĥàñĝéđ), đ ţŵö àŕçĥîṽéš öñ ţĥé ţŕàñšļàţîöñ šîđé. Éàçĥ ƃļöçķ'š ĥàšĥ ţéļļš ýöü ŵĥéţĥéŕ îţš šöüŕçé šĥîƒţéđ.

Ñéẋţ