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

Ţŕàñšļàţîñĝ ŵîţĥ kapi

ķàþî ƒîļļš ţĥé ĶƂƑ àŕçĥîṽé ţĥàţ ñéöķàþî-î18ñ éẋţŕàçţš. Îţ þšéüđö-ţŕàñšļàţéš ţĥé àŕçĥîṽé ţö çĥéçķ ýöüŕ ļàýöüţ, ţŕàñšļàţéš îţ ŵîţĥ ÀÎ àñđ ýöüŕ çöñţéñţ ḿéḿöŕý, àñđ çĥéçķš ţĥé ŕéšüļţ àĝàîñšţ ýöüŕ ţéŕḿš, àñđ îţ ŵŕîţéš éàçĥ ŕéšüļţ ƃàçķ îñţö ţĥé šàḿé ƒîļéš.

i18n/KBF archiverecyclecontent memorytranslateAI providerqa · term-checkfindingscompile{locale}.json

Éàçĥ ķàþî šţéþ ŕéàđš àñđ ŵŕîţéš ţĥé šàḿé àŕçĥîṽé; ñéöķàþî-î18ñ çöḿþîļé ţüŕñš îţ îñţö ŕüñţîḿé đîçţš.

Þšéüđö-ţŕàñšļàţîöñ ƒöŕ ÜÎ çĥéçķš​

Þšéüđö-ţŕàñšļàţîöñ šĥöŵš ýöü àţ à ĝļàñçé ŵĥîçĥ šţŕîñĝš ŵéŕé éẋţŕàçţéđ àñđ ŵĥîçĥ ŵéŕé ḿîššéđ.

kapi pseudo-translate i18n/

Ŵîţĥ ñö -o, ţĥé đéƒàüļţ ƒöŕ ĶƂƑ îñþüţš îš îñ-þļàçé: ţĥé qps ţàŕĝéţ îš àđđéđ ţö ţĥé šàḿé àŕçĥîṽé. Ŕüñ àĝàîñ ŵîţĥ --target-lang fr ţö àđđ àñöţĥéŕ ļöçàļé; ţĥé ŵŕîţéŕ îš ļöçàļé-àđđîţîṽé àñđ éẋîšţîñĝ ţàŕĝéţš šţàý þüţ.

Šöüŕçé Welcome to Acme ƃéçöḿéš ▒ Ŵéḷçőḿé tő Âçmé ▒:

  • Àççéñţéđ çĥàŕàçţéŕš ḿàķé ţŕàñšļàţéđ šţŕîñĝš ṽîšüàļļý đîšţîñçţ. À šţŕîñĝ ţĥàţ šţàýš þļàîñ Éñĝļîšĥ îš àñ éẋţŕàçţîöñ ĝàþ, àñđ îţ šţàñđš öüţ îñ ţĥé ÜÎ.
  • Ḿàŕķéŕš (▒) ḿàŕķ šţàŕţ àñđ éñđ, šö ţŕüñçàţîöñ îš öƃṽîöüš (Welcome to Ac… ñéṽéŕ ŵŕàþš ţö ▒ Ŵéḷç…).
  • Éẋþàñšîöñ (àđđîñĝ 30–50% ḿöŕé çĥàŕàçţéŕš) ḿîḿîçš Ĝéŕḿàñ, Ƒŕéñçĥ àñđ Ŕüššîàñ šţŕîñĝ ĝŕöŵţĥ šö ļàýöüţ ƃüĝš šüŕƒàçé ƃéƒöŕé ýöü šĥîþ.

Ţĥéñ neokapi-i18n compile îţ àñđ ļöàđ îţ àš àñý öţĥéŕ ļöçàļé:

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

Ýöüŕ đéṽ šéŕṽéŕ ţĥéñ šéŕṽéš à qps ļöçàļé. Àđđ à ļàñĝüàĝé þîçķéŕ, àñđ šéñđ þšéüđö-ţŕàñšļàţéđ šçŕééñšĥöţš ţö đéšîĝñ ŕéṽîéŵ.

Þšéüđö-ţŕàñšļàţîöñ îñ ÇÎ​

Àđđ îţ ţö ÇÎ àš à ÜÎ-ļàýöüţ šḿöķé ţéšţ:

.github/workflows/ui-checks.yml
- run: vp neokapi-i18n extract
- run: kapi pseudo-translate i18n/
- run: vp neokapi-i18n compile i18n/ --out public/translations
- run: npm run test:e2e # runs against ?locale=qps

ÀÎ ţŕàñšļàţîöñ​

kapi translate šéñđš ţĥé ĶƂƑ đîŕéçţöŕý ţö àñ ĻĻḾ ƒöŕ ţŕàñšļàţîöñ. Îţ þŕéšéŕṽéš þļàçéĥöļđéŕš, îñļîñé éļéḿéñţ ţöķéñš, àñđ þļüŕàļ / šéļéçţ šţŕüçţüŕé:

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

Éàçĥ çàļļ àççüḿüļàţéš à ţàŕĝéţ ļöçàļé îñ þļàçé. Ţö ŕéđîŕéçţ öüţþüţ ţö à đéŕéñţ ƒîļé, þàšš -o target-dir/; ţĥé îñþüţ šţàýš üñţöüçĥéđ.

ķàþî šüþþöŕţš ţĥé ÀÎ þŕöṽîđéŕš ļîšţéđ üñđéŕ Ţŕàñšļàţîöñ, ļöçàļ Öļļàḿà ḿöđéļš àḿöñĝ ţĥéḿ. Šéļéçţ ţĥé þŕöṽîđéŕ (àñđ öþţîöñàļļý ţĥé ḿöđéļ) ŵîţĥ ƒļàĝš, öŕ îñ à ƒļöŵ'š šţéþ çöñƒîĝ:

kapi translate i18n/ --target-lang fr --provider anthropic

ÀÞÎ ķéýš àŕé ñéṽéŕ ŵŕîţţéñ îñţö ţĥé çöḿḿîţţéđ ŕéçîþé. Šüþþļý öñé, îñ þŕéçéđéñçé öŕđéŕ, ŵîţĥ --api-key, à šàṽéđ ķéýçĥàîñ çŕéđéñţîàļ (kapi credentials add, ţĥéñ --credential <name>), öŕ ţĥé þŕöṽîđéŕ'š šţàñđàŕđ éñṽîŕöñḿéñţ ṽàŕîàƃļé (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, …). Ļöçàļ Öļļàḿà ñééđš ñö ķéý.

Šéé Ţŕàñšļàţîöñ ƒöŕ ţĥé ƒüļļ þŕöṽîđéŕ àñđ çöñƒîĝüŕàţîöñ šüŕƒàçé.

Çöñţéẋţ çàŕŕîéš ţĥŕöüĝĥ​

Éṽéŕý ƃļöçķ îñ ţĥé ĶƂƑ đîŕéçţöŕý çàŕŕîéš îţš éļéḿéñţ ("button", "p", …), îţš ƒîļé àñđ ļîñé, îţš çöḿþöñéñţ ñàḿé, àñđ àñý data-i18n-note àññöţàţîöñ. Ţĥé ÀÎ ţŕàñšļàţöŕ ĝéţš ţĥàţ çöñţéẋţ àš þàŕţ öƒ ţĥé þŕöḿþţ, šö à <button>Close</button> ĝéţš à đéŕéñţ ţŕàñšļàţîöñ ţĥàñ àñ <a>Close</a> îñ à ļîšţ-îţéḿ'š đéļéţé àçţîöñ.

Ŵĥéŕé ţĥé éļéḿéñţ àļöñé îš ţöö ļîţţļé ţö đîšàḿƃîĝüàţé (ţŵö ƃüţţöñš ƃöţĥ ŕéàđîñĝ "Öþéñ", öñé à ṽéŕƃ àñđ öñé à šţàţé), àđđ ţĥé ñöţé éẋþļîçîţļý ŵîţĥ data-i18n-note öŕ t(text, context). Ţĥàţ ñöţé îš þàŕţ öƒ ţĥé ķéý, šö ţĥé ţŵö šţŕîñĝš šéþàŕàţé ƒöŕ ţĥé ţŕàñšļàţöŕ àñđ šţàý šéþàŕàţé.

Ţŕàñšļàţé à šüƃšéţ​

Ƒöŕ îñçŕéḿéñţàļ ţŕàñšļàţîöñš (öñļý ţĥé šţŕîñĝš ţĥàţ çĥàñĝéđ), ŕé-éẋţŕàçţ îñţö ţĥé šàḿé àŕçĥîṽé àñđ ţŕàñšļàţé öñļý ţĥé üñţŕàñšļàţéđ ƃļöçķš. neokapi-i18n extract îš ļöçàļé-àđđîţîṽé, šö ŕé-ŕüññîñĝ îţ àđđš ñéŵ šöüŕçé ƃļöçķš ŵîţĥöüţ đîšţüŕƃîñĝ éẋîšţîñĝ ţàŕĝéţš; kapi exec translate --skip-matched ţĥéñ šķîþš àñý ƃļöçķ ţĥàţ àļŕéàđý ĥàš à ţàŕĝéţ ƒöŕ ţĥé ļöçàļé:

vp neokapi-i18n extract # refresh i18n/ with new/changed blocks
kapi exec translate i18n/ --target-lang fr --skip-matched

ķàþî šéñđš öñļý ţĥé ƃļöçķš àđđéđ šîñçé ţĥé ļàšţ þàšš ţö ţĥé ĻĻḾ, àñđ ļéàṽéš ţĥé ţŕàñšļàţéđ ƃļöçķš àš ţĥéý àŕé.

Çĥéçķš​

kapi exec qa ŕüñš þļàçéĥöļđéŕ, îñļîñé-çöđé, ŵĥîţéšþàçé, àñđ ļéñĝţĥ çĥéçķš àĝàîñšţ à ţŕàñšļàţéđ àŕçĥîṽé; kapi exec term-check éñƒöŕçéš àþþŕöṽéđ ţéŕḿîñöļöĝý:

kapi exec qa i18n/ --target-lang fr # placeholder, code, length, consistency
kapi exec term-check i18n/ --target-lang fr --termstore terms/fr.db # terminology

qa çöṽéŕš:

  • Þļàçéĥöļđéŕ àñđ îñļîñé-çöđé îñţéĝŕîţý: éṽéŕý {name} àñđ îñļîñé-éļéḿéñţ ţöķéñ ({=m0}) îñ ţĥé šöüŕçé àþþéàŕš îñ ţĥé ţàŕĝéţ.
  • Ļéñĝţĥ ƃöüñđš: ƒļàĝ ţàŕĝéţš ţĥàţ ĝŕöŵ öŕ šĥŕîñķ ƃéýöñđ çöñƒîĝüŕàƃļé þéŕçéñţàĝéš öƒ ţĥé šöüŕçé (üšéƒüļ ƒöŕ ƒîẋéđ-ŵîđţĥ ÜÎ çöñţàîñéŕš).
  • Çöñšîšţéñçý: đöüƃļé šþàçéš, đöüƃļéđ ŵöŕđš, ļéàđîñĝ/ţŕàîļîñĝ ŵĥîţéšþàçé, ţàŕĝéţ-îđéñţîçàļ-ţö-šöüŕçé, àñđ ḿöŕé (éàçĥ îñđîṽîđüàļļý ţöĝĝļéàƃļé).

term-check ƒļàĝš ţàŕĝéţš ţĥàţ đîṽéŕĝé ƒŕöḿ àñ àþþŕöṽéđ ţéŕḿ, ƒöŕ éẋàḿþļé à þŕöđüçţ ñàḿé ţĥàţ ḿüšţ šţàý üñţŕàñšļàţéđ.

kapi exec ŕéþöŕţš ŵĥàţ à çĥéçķ ƒîñđš àñđ éẋîţš 0 ŵĥàţéṽéŕ îţ ƒîñđš, šö à ŕüñ ñéṽéŕ ƒàîļš à ƃüîļđ öñ îţš öŵñ. Ţĥé çöḿḿàñđ ţĥàţ ƒàîļš öñé îš kapi check, ŵĥîçĥ éẋîţš 3 ŵĥîļé à ƒîñđîñĝ ƒàîļš; šéé Šĥîþ ĝàţéš àñđ ÇÎ.

Ŕéüšé þàšţ ţŕàñšļàţîöñš ƒŕöḿ çöñţéñţ ḿéḿöŕý​

kapi ĥàš à ƃüîļţ-îñ çöñţéñţ ḿéḿöŕý. Îḿþöŕţ ýöüŕ þàšţ ţŕàñšļàţîöñš:

kapi memory import historical-translations.tmx -s en -t fr

Ţĥéñ þŕé-ƒîļļ ḿàţçĥéš ƃéƒöŕé ţĥé ÀÎ þàšš: kapi exec recycle ŵŕîţéš éẋàçţ àñđ ĥîĝĥ-šçöŕîñĝ ƒüžžý ḿàţçĥéš îñţö ţĥé ţàŕĝéţ, àñđ kapi exec translate --skip-matched ţŕàñšļàţéš öñļý ŵĥàţ'š ļéƒţ:

kapi exec recycle i18n/ --target-lang fr # fill targets from the project content memory
kapi exec translate i18n/ --target-lang fr --skip-matched

Þàšš --memory <name-or-path> ţö kapi exec recycle ţö đŕàŵ öñ à šþéçîƒîç çöñţéñţ ḿéḿöŕý. Šéé Çöñţéñţ ḿéḿöŕý ƒöŕ ţĥé ḿàţçĥ àñđ ƒîļļ ţĥŕéšĥöļđš.

Ţéŕḿîñöļöĝý çöñšîšţéñçý​

Ƒöŕ àñ àþþ ŵîţĥ à ļàŕĝé þŕöđüçţ ṽöçàƃüļàŕý, à ţéŕḿš šţöŕé ķééþš éàçĥ ţéŕḿ ţŕàñšļàţéđ ţĥé šàḿé ŵàý. Îḿþöŕţ ţĥé ţéŕḿ ļîšţ îñţö à ñàḿéđ šţöŕé, ţĥéñ çĥéçķ ţŕàñšļàţîöñš ŵîţĥ kapi exec term-check, ŵĥîçĥ ƒļàĝš àñý ţàŕĝéţ ţĥàţ đîṽéŕĝéš ƒŕöḿ àñ àþþŕöṽéđ ţéŕḿ:

kapi terms import product-terms.csv -s en -t fr --name product-terms
kapi exec term-check i18n/ --target-lang fr --termstore product-terms

Ţö ĝîṽé ţĥé ţŕàñšļàţîöñ šţéþ ţĥé ţéŕḿš àš ŵéļļ àš çĥéçķîñĝ ţĥéḿ àƒţéŕŵàŕđ, ŕéàđ ţĥé ţéŕḿš îñţö ţĥé þŕöĵéçţ ŵîţĥ kapi store import (ţĥé þŕöĵéçţ'š öŵñ ţéŕḿš ĝöṽéŕñ ŵîţĥ ñöţĥîñĝ ƃöüñđ) öŕ ļîšţ ŕüļéš üñđéŕ term_rules: îñ ţĥé šţéþ'š çöñƒîĝ. translate ŕéàđš ţĥé ḿàţçĥîñĝ ŕüļéš îţšéļƒ àñđ šéñđš ţĥéḿ îñ ţĥé þŕöḿþţ'š ţéŕḿîñöļöĝý šéçţîöñ.

Šéé Ţéŕḿîñöļöĝý.

Þüţţîñĝ îţ ţöĝéţĥéŕ​

À çöḿþļéţé þàçķàĝé-šçŕîþţš šéţüþ ƒöŕ à ḿüļţî-ļöçàļé àþþ:

package.json
{
"scripts": {
"i18n:extract": "vp neokapi-i18n extract",
"i18n:pseudo": "kapi pseudo-translate i18n/",
"i18n:ai": "for lang in fr de ja; do kapi translate i18n/ --target-lang $lang; done",
"i18n:compile": "vp neokapi-i18n compile i18n/ --out public/translations"
}
}

Đŕîṽé îţ ƒŕöḿ à þŕöĵéçţ​

Ţĥé çöḿḿàñđš àƃöṽé ţàķé ţĥéîŕ ƒļàĝš öñ éṽéŕý çàļļ, ŵĥîçĥ šüîţš à ǫüîçķ ŕüñ. Ƒöŕ àñ àþþ ýöü ţŕàñšļàţé éṽéŕý ŕéļéàšé, à kapi.yaml þŕöĵéçţ ƒîļé ŕéçöŕđš ţĥé çöñţéñţ þàţţéŕñš, ţàŕĝéţ ļàñĝüàĝéš, ƒļöŵš àñđ đéƒàüļţš öñçé. Ýöü ţĥéñ ŕüñ ñàḿéđ ƒļöŵš îñšţéàđ öƒ ŕéþéàţîñĝ ƒļàĝš, àñđ ţĥé þŕöĵéçţ šţöŕé ķééþš çöñţéñţ ḿéḿöŕý àñđ ţéŕḿîñöļöĝý àçŕöšš ŕéļéàšéš.

kapi init --framework neokapi-i18n šçàƒƒöļđš à ļàýöüţ ţĥàţ ķééþš éṽéŕý çàţàļöĝ üñđéŕ öñé i18n/ đîŕéçţöŕý. Ţĥé ƃüñđļéŕ ŵŕîţéš šöüŕçé çàţàļöĝš ţö i18n/src/, àñđ ķàþî ŵŕîţéš þéŕ-ļöçàļé ţàŕĝéţš ţö i18n/{lang}/. Ƃéçàüšé ţĥé šöüŕçé ļîṽéš üñđéŕ i18n/src/, ţĥé šöüŕçé ĝļöƃ ñéṽéŕ ḿàţçĥéš ţĥé ĝéñéŕàţéđ ţàŕĝéţš, àñđ ýöü ñééđ ñö šîƃļîñĝ i18n-<lang>/ ţŕééš:

kapi.yaml
version: v1
name: MyApp
defaults:
source_language: en
target_languages: [de, fr, ja, nb]
# The voice profile `kapi store import context` read from context/voice.yaml,
# bound by its name; the same import reads context/terms.json into the
# project's terms.
voice:
profile: myapp
collections:
- path: "i18n/src/**/*.kbf.json"
format: kbf
target: "i18n/{lang}/{path}.kbf.json"
i18n/
├── src/ source KBF catalogs (from `neokapi-i18n extract`)
└── de/ fr/ ja/ nb/ per-locale targets (from `kapi merge`)
context/
├── terms.json terms, read in by `kapi store import context`
└── voice.yaml voice profile, read in by the same import
.kapi/
└── work/ content memory and pseudo-locale output (ignored by git)

Ţĥé çöñţéñţ ḿéḿöŕý'š ļöçàļ šţöŕé àñđ ţĥé þšéüđö-ļöçàļé öüţþüţ àŕé šţàţé ţĥàţ ķàþî çàñ ŕéƃüîļđ, šö ţĥéý ļîṽé üñđéŕ .kapi/work/, à çàçĥé îñ ţĥé çĥéçķöüţ ţĥàţ ĝîţ îĝñöŕéš. Ţĥé ţéŕḿš àñđ ţĥé ṽöîçé þŕöƒîļé àŕé ƒîļéš ýöü çöḿḿîţ üñđéŕ context/; àñ éđîţ ţö context/voice.yaml öŕ context/terms.json ţàķéš éƒƒéçţ ŵĥéñ ýöü ŕüñ kapi store import context àĝàîñ. Šéé Çöñţéẋţ þöŕţàƃîļîţý.

package.json
{
"scripts": {
"i18n:extract": "vp neokapi-i18n extract",
"i18n:translate": "kapi up && kapi merge",
"i18n:compile": "vp neokapi-i18n compile i18n/ --out public/translations"
}
}

kapi up đîšçöṽéŕš ţĥé ñéàŕéšţ kapi.yaml ŕéçîþé (öŕ þàšš -p kapi.yaml) àñđ ŕüñš ţĥé þŕöĵéçţ'š đéƒàüļţ ƒļöŵ öṽéŕ éṽéŕý đéçļàŕéđ ţàŕĝéţ ļàñĝüàĝé: çöñţéñţ ḿéḿöŕý ŕéüšé, ţĥéñ ÀÎ ţŕàñšļàţîöñ, ţĥéñ ţĥé ƃöüñđ çĥéçķš, ļööþîñĝ üñţîļ éàçĥ ļöçàļé çļéàŕš îţš šĥîþ ĝàţé öŕ ŵàîţš ƒöŕ ŕéṽîéŵ ƃý à þéŕšöñ. kapi merge ŵŕîţéš ţĥé þéŕ-ļöçàļé çàţàļöĝš ţĥé ŕéçîþé ḿàþš. kapi status ŕéþöŕţš ţĥé çöṽéŕàĝé þéŕ ļöçàļé ŵîţĥöüţ ŕüññîñĝ àñýţĥîñĝ, àñđ kapi run <flow> ŕüñš öñé ñàḿéđ ƒļöŵ ƒöŕ à šîñĝļé þàšš. Šéé Çöñṽéŕĝéñçé, Ƒļöŵš àñđ ţĥé þŕöĵéçţ ḿöđéļ.

Đŕîṽé îţ ƒŕöḿ Çļàüđé​

Ţĥé šàḿé ƒļöŵš ŕüñ ƒŕöḿ ýöüŕ ÀÎ àššîšţàñţ. Ŵîţĥ ţĥé ķàþî ḾÇÞ šéŕṽéŕ çöññéçţéđ, þöîñţ Çļàüđé àţ ýöüŕ éẋţŕàçţéđ šţŕîñĝš àñđ àšķ îţ ţö ţŕàñšļàţé àñđ çĥéçķ ţĥéḿ. Îţ çàļļš kapi ţö ţŕàñšļàţé ţĥé ĶƂƑ àŕçĥîṽé, ŕüñš ţĥé çĥéçķš, àñđ ƒîẋéš àñýţĥîñĝ ţĥàţ ƃŕéàķš, ţĥé šàḿé àüţĥöŕ → çĥéçķ → ŕéṽîšé ļööþ ýöü'đ ŕüñ ƃý ĥàñđ:

"Ţŕàñšļàţé ţĥé šţŕîñĝš îñ i18n/ ţö Ƒŕéñçĥ àñđ Ĝéŕḿàñ, ķééþ ţĥé þļàçéĥöļđéŕš àñđ îñļîñé éļéḿéñţš îñţàçţ, ţĥéñ ŕüñ ţĥé çĥéçķš àñđ ƒîẋ àñýţĥîñĝ ţĥàţ ƒàîļš."

Çļàüđé ţŕàñšļàţéš îñ þļàçé (ļöçàļé-àđđîţîṽé), ŕüñš kapi exec qa / kapi exec term-check, àñđ ļööþš öñ ţĥé ƒîñđîñĝš üñţîļ ţĥé àŕçĥîṽé þàššéš; ýöü neokapi-i18n compile ţĥé ŕéšüļţ àš üšüàļ. Çļàüđé çĥàñĝéš ţĥé çàţàļöĝ àñđ ļéàṽéš ýöüŕ çöḿþöñéñţš àš ţĥéý àŕé.

Šéé Üšé ŵîţĥ Çļàüđé ƒöŕ ţĥé ḾÇÞ šéţüþ àñđ ţĥé ƃŕöàđéŕ àĝéñţ ļööþ.

Ŕéļàţéđ​