Çöñƒîĝüŕàţîöñ
Ţĥé neokapi(...) þļüĝîñ öþţîöñš, ţĥé neokapi-i18n ÇĻÎ ƒļàĝš, àñđ ţĥé éçöšýšţéḿ ƃîţš (Šţöŕýƃööķ, çüšţöḿ ŵàŕñîñĝ ŕöüţîñĝ).
Þļüĝîñ öþţîöñš
import neokapi from "@neokapi/i18n-react/vite";
neokapi({
mode: "runtime",
locale: "fr",
fallbackLocales: ["fr", "en"],
translationsDir: "./translations",
componentMap: { TabsTrigger: "button" },
rules: [{ selector: ".hero-caption", translate: false }],
strict: "warn",
warnUnmapped: false,
communityManifestDir: "./i18n-manifests",
review: false,
reviewKbfDir: "i18n",
onWarning: (msg) => logger.warn(msg),
});
mode: "runtime" | "inline"
"runtime": öñé ƃüñđļé, đîçţ ļöàđéđ àţ ŕüñţîḿé."inline": öñé ƃüñđļé þéŕ ļöçàļé, ţŕàñšļàţîöñš îñļîñéđ.- Öḿîţţéđ: þļüĝîñ îš à ñö-öþ. Üšéƒüļ ƒöŕ đéṽ ḿöđé (ñö éẋţŕàçţîöñ, šöüŕçé ţéẋţ ŕéñđéŕš àš-îš).
locale (îñļîñé ḿöđé öñļý)
Ţĥé ţàŕĝéţ ļöçàļé. Đŕîṽéš ŵĥîçĥ translations/<locale>.json ƒîļé ţĥé þļüĝîñ ŕéàđš àţ ƃüîļđ ţîḿé.
fallbackLocales (îñļîñé ḿöđé öñļý)
Öŕđéŕéđ ļîšţ öƒ ļöçàļéš ţŕîéđ ŵĥéñ ţĥé þŕîḿàŕý îš ḿîššîñĝ à ţŕàñšļàţîöñ.
neokapi({
mode: "inline",
locale: "de-AT",
fallbackLocales: ["de", "en"],
});
translationsDir (îñļîñé ḿöđé öñļý)
Đîŕéçţöŕý ĥöļđîñĝ <locale>.json ƒîļéš. Đéƒàüļţ: ./translations.
componentMap
Ḿàþš Ŕéàçţ çöḿþöñéñţš ţö ţĥéîŕ üñđéŕļýîñĝ ĤŢḾĻ éļéḿéñţ šö ĥàšĥéš šţàý šţàƃļé àçŕöšš ŕéƒàçţöŕš:
neokapi({
componentMap: {
// Internal components
PageHeader: "header",
Heading: "h2",
},
});
Ƃéƒöŕé çöñšüļţîñĝ ţĥîš öþţîöñ, ţĥé þļüĝîñ àüţö-ŕéšöļṽéš ḿàþþîñĝš ƒöŕ éṽéŕý ñöñ-ŕéļàţîṽé îḿþöŕţ îţ šééš, îñ ţĥŕéé šţàĝéš:
- Ļîƃŕàŕý-šĥîþþéđ ḿàñîƒéšţ:
<package>/i18n-manifest.json. Ţĥîš îš ţĥé ƒîŕšţ-þŕîöŕîţý šöüŕçé àñđ ţĥé þàţţéŕñ ŵé ŕéçöḿḿéñđ ƒöŕ ļîƃŕàŕý àüţĥöŕš; šéé Àüţĥöŕîñĝ î18ñ ḿàñîƒéšţš. - Çöḿḿüñîţý ḿàñîƒéšţ đîŕéçţöŕý:
<communityManifestDir>/<package-name>.json, îƒ ýöü'ṽé çöñƒîĝüŕéđ öñé. .d.tsĥéüŕîšţîç: ŕéĝéẋ-ḿàţçĥ ƒöŕReact.ForwardRefExoticComponent<... & RefAttributes<HTMLXxxElement>>îñ ţĥé þàçķàĝé'š đéçļàŕéđ ţýþéš. Þîçķš üþ ḿöšţ þŕé-Ŕéàçţ-19 šĥàđçñ / Ŕàđîẋ / ḾÜÎ çöḿþöñéñţš ƒöŕ ƒŕéé.
Ýöüŕ componentMap éñţŕîéš ḿéŕĝé öñ ţöþ öƒ ţĥé àüţö-ŕéšöļṽéđ ḿàþ, šö éẋþļîçîţ öṽéŕŕîđéš àļŵàýš ŵîñ. Ţĥé çöḿḿöñ çàšé (üšîñĝ šĥàđçñ-šţýļé çöḿþöñéñţš ƒŕöḿ à ļîƃŕàŕý ŵîţĥ þŕöþéŕ ţýþéš öŕ à šĥîþþéđ ḿàñîƒéšţ) ñééđš ñö componentMap éñţŕý àţ àļļ.
Üñḿàþþéđ çöḿþöñéñţš šţîļļ àüţö-éẋţŕàçţ ṽîà ţĥé þŕöḿöţîöñ ŕüļé, ƃüţ éàçĥ öñé ƒîŕéš à ŵàŕñîñĝ. Àđđîñĝ àñ éñţŕý šîļéñçéš ţĥé ŵàŕñîñĝ àñđ ŕé-ķéýš ţĥé ƃļöçķ'š ĥàšĥ ƒŕöḿ Component ţö ţĥé üñđéŕļýîñĝ ĤŢḾĻ ţàĝ.
Àüţĥöŕîñĝ î18ñ ḿàñîƒéšţš ƒöŕ ļîƃŕàŕîéš
Šĥîþ àñ i18n-manifest.json àţ ţĥé ŕööţ öƒ ýöüŕ çöḿþöñéñţ ļîƃŕàŕý šö çöñšüḿéŕš đöñ'ţ ñééđ ţö ḿàîñţàîñ componentMap éñţŕîéš:
{
"components": {
"Button": "button",
"Badge": "span",
"CardTitle": "h3",
"CardDescription": "p",
"Label": "label",
"TabsTrigger": "button",
"SelectItem": "option",
"Input": null,
"Textarea": null,
"Skeleton": null
},
"aliases": {
"Trigger": "TabsTrigger"
}
}
- Ķéýš àŕé ţĥé éẋþöŕţéđ çöḿþöñéñţ ñàḿéš.
- Ṽàļüéš àŕé ţĥé üñđéŕļýîñĝ ĤŢḾĻ éļéḿéñţ ñàḿé, öŕ
nullţö éẋþļîçîţļý öþţ öüţ öƒ ţŕàñšļàţîöñ. aliasesḿàþ àļţéŕñàţîṽé éẋþöŕţ ñàḿéš öñţö çàñöñîçàļ öñéš (üšéƒüļ ƒöŕ Ŕàđîẋ-šţýļé ñàḿéšþàçé ŕé-éẋþöŕţš ļîķéTabs.Trigger).
Ţĥé þļüĝîñ ļöàđš ţĥîš ƒîļé àüţöḿàţîçàļļý ŵĥéñ àñý ƒîļé îḿþöŕţš ƒŕöḿ ţĥé ļîƃŕàŕý. Šéé @neokapi/ui-primitives/i18n-manifest.json ƒöŕ à þŕöđüçţîöñ ŕéƒéŕéñçé.
rules
Đéçļàŕàţîṽé öṽéŕŕîđéš ķéýéđ öñ šéļéçţöŕš:
neokapi({
rules: [
// Turn translation off for specific matches
{ selector: ".code-block", translate: false },
{ selector: "[data-testid]", translate: false },
// Attach a translator note
{ selector: ".legal-copy", locNote: "Must match legal-approved wording" },
// Turn translation on for a container that wouldn't normally auto-promote
{ selector: ".hero-tagline", translate: true },
],
});
Šéļéçţöŕ ƒöŕḿš:
- Ƃàŕé ţàĝ:
code(ḿàţçĥéš<code>). - Çļàšš:
.className(ḿàţçĥéš àñ éļéḿéñţ ŵĥöšéclassNameçöñţàîñš ţĥé ñàḿé). - Àţţŕîƃüţé þŕéšéñçé:
[data-testid]. - Àţţŕîƃüţé ṽàļüé:
[role="alert"].
strict
Ĥöŵ ţĥé þļüĝîñ ĥàñđļéš ḿîššîñĝ ţŕàñšļàţîöñš îñ îñļîñé ḿöđé:
"warn"(đéƒàüļţ): ļöĝ à çöñšöļé ŵàŕñîñĝ, ƒàļļ ƃàçķ ţö šöüŕçé ţéẋţ."error": ţĥŕöŵ à ƃüîļđ éŕŕöŕ.false: šîļéñţ, ƒàļļ ƃàçķ ţö šöüŕçé ţéẋţ.
onWarning
Öṽéŕŕîđé ŵĥéŕé üñḿàþþéđ-çöḿþöñéñţ ŵàŕñîñĝš ĝö. Đéƒàüļţš ţö console.warn.
neokapi({
onWarning: (msg) => {
logger.warn(msg);
stats.increment("neokapi.warning");
},
});
Üšéƒüļ ƒöŕ ţéšţš (šüþþŕéšš ñöîšé) öŕ ţö îñţéĝŕàţé ŵîţĥ à þŕöĵéçţ ļöĝĝéŕ.
warningsAsErrors
Þŕöḿöţé éẋţŕàçţîöñ-ţîḿé ŵàŕñîñĝš (çüŕŕéñţļý: unknown-component) ţö à ţĥŕöŵñ ƃüîļđ éŕŕöŕ. Öŕţĥöĝöñàļ ţö strict àƃöṽé: strict îš àƃöüţ ḿîššîñĝ ţŕàñšļàţîöñš àţ îñļîñé ţîḿé, ţĥîš îš àƃöüţ àüţĥöŕîñĝ-ţîḿé îššüéš ţĥé ŵàļķéŕ ŕéçöŕđš.
neokapi({
warningsAsErrors: process.env.CI === "true",
});
Þàîŕ ŵîţĥ @neokapi/i18n-react-lint ţö ĝéţ à ƒüļļý-éñƒöŕçéđ "ñö àüţĥöŕîñĝ ḿîšţàķéš ļàñđ öñ ḿàîñ" šţöŕý.
review / reviewKbfDir
Ţüŕñ öñ îñ-çöñţéẋţ ŕéṽîéŵ: ţĥé ţŕàñšƒöŕḿ šţàḿþš data-kapi-id / data-kapi-loc / data-kapi-attr öñţö éẋţŕàçţéđ éļéḿéñţš, ţĥé đéṽ šéŕṽéŕ ḿöüñţš ţĥé ŕéṽîéŵ ḿîđđļéŵàŕé àţ /__kapi/review öṽéŕ reviewKbfDir (đéƒàüļţ i18n), àñđ ţĥé öṽéŕļàý îš îñĵéçţéđ îñţö index.html.
neokapi({
review: true, // or KAPI_REVIEW=1
reviewKbfDir: "i18n",
});
Đéṽ àñđ šţàĝîñĝ öñļý; ñéṽéŕ šĥîþ à þŕöđüçţîöñ ƃüîļđ ŵîţĥ îţ öñ.
ÇĻÎ ƒļàĝš
neokapi-i18n extract:
neokapi-i18n extract \
--src "src/**/*.{tsx,jsx}" \
--ignore "src/stories/**" \
--ignore "**/*.test.tsx" \
--out i18n \
--config i18n.config.json \
--project my-app \
--source-locale en \
--target-locale fr \
--target-locale de
# or stream mode for pipes into any kapi-aware consumer:
neokapi-i18n extract --stream | any-kapi-tool
# CI-friendly: fail on any recorded warning.
neokapi-i18n extract --strict
# Record every warning as JSON (kind, file, line, tag), for a check that
# compares the warnings with a baseline instead of failing on all of them.
neokapi-i18n extract --warnings-json build/extract-warnings.json
--ignore îš ŕéþéàţàƃļé àñđ àççéþţš àñý ĝļöƃ; îţ'š þîþéđ ţĥŕöüĝĥ ţö
Ñöđé'š fs/promises.glob exclude öþţîöñ. Üšé îţ ţö ķééþ
ƒîẋţüŕé-öñļý çöđé (src/stories/**, ţéšţ ĥéļþéŕš) öüţ öƒ ţĥé çàţàļöĝ;
ýöüŕ ļîñţ çöñƒîĝ šĥöüļđ àĝŕéé (šéé Ļîñţîñĝ → Éẋçļüđîñĝ ƒîẋţüŕé
çöđé).
neokapi-i18n compile (àççéþţš à .kbf.json ƒîļé, à đîŕéçţöŕý öƒ ţĥéḿ, öŕ - ƒöŕ ÑĐĴŠÖÑ šţđîñ):
neokapi-i18n compile \
i18n/ \
--out public/translations \
--locale fr # optional: filter to a single locale
neokapi-i18n explain (àüđîţ ŵĥàţ éẋţŕàçţš, àñđ ŵĥý):
neokapi-i18n explain src/Settings.tsx
neokapi-i18n explain "src/**/*.tsx" --extracted # only the elements that extracted
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Ç ÎŢŠ çļàššîƒîçàţîöñ, ţĥé ĝàţé ţĥàţ ƒîŕéđ, àñđ ţĥé ĥàšĥ ţĥé ƃļöçķ ĝöţ. Ŕéàçĥ ƒöŕ îţ ŵĥéñ à šţŕîñĝ ýöü éẋþéçţéđ đîđñ'ţ ḿàķé ţĥé çàţàļöĝ, öŕ öñé ýöü đîđñ'ţ éẋþéçţ đîđ.
neokapi-i18n split šļîçéš ḿàšţéŕ đîçţš îñţö þéŕ-çĥüñķ šüƃšéţš ƒöŕ ļàžý ļöàđîñĝ; šéé Ļàžý ļöàđîñĝ þéŕ ŕöüţé.
Šĥàŕé öñé çöñƒîĝ ƃéţŵééñ ţĥé ÇĻÎ àñđ ţĥé þļüĝîñ
Ţĥé componentMap àñđ rules ƒééđ ţĥé ĥàšĥ. ΃ ţĥé éẋţŕàçţ ÇĻÎ àñđ ţĥé ƃüîļđ þļüĝîñ đîšàĝŕéé àƃöüţ ţĥéḿ, ţĥé ţŵö šîđéš çöḿþüţé đéŕéñţ ķéýš ƒöŕ ţĥé šàḿé éļéḿéñţ, àñđ éṽéŕý àƒƒéçţéđ šţŕîñĝ šîļéñţļý ƒàļļš ƃàçķ ţö šöüŕçé ţéẋţ, à ƒàîļüŕé ŵîţĥ ñö éŕŕöŕ ḿéššàĝé àñýŵĥéŕé.
Šö đöñ'ţ ḿàîñţàîñ ţŵö çöþîéš. Ķééþ öñé ĴŠÖÑ ƒîļé àñđ ĥàṽé ƃöţĥ šîđéš ŕéàđ îţ:
{
"componentMap": {
"TabsTrigger": "button",
"PageHeader": "header"
},
"rules": [{ "selector": "[data-testid]", "translate": false }]
}
import neokapiI18nConfig from "./neokapi-i18n.config.json";
neokapi({ mode: "runtime", ...neokapiI18nConfig });
neokapi-i18n extract --config neokapi-i18n.config.json
Ţĥé ƒîļé ñàḿé îš ýöüŕš ţö çĥööšé: ţĥé ÇĻÎ ţàķéš îţ ṽîà --config àñđ ţĥé þļüĝîñ ĵüšţ ţàķéš ţĥé öƃĵéçţ. Ŵĥàţ ḿàţţéŕš îš ţĥàţ ţĥéŕé îš éẋàçţļý öñé öƒ ţĥéḿ.
Šţöŕýƃööķ îñţéĝŕàţîöñ
@neokapi/i18n-react/storybook éẋþöŕţš à ţööļƃàŕ éñţŕý, à ļöàđéŕ àñđ à đéçöŕàţöŕ ƒöŕ šŵîţçĥîñĝ ļöçàļéš îñšîđé Šţöŕýƃööķ:
import type { Preview } from "@storybook/react-vite";
import {
neokapiDecorator,
neokapiGlobalType,
neokapiLoader,
} from "@neokapi/i18n-react/storybook";
const i18n = {
locales: [
{ value: "en", title: "English" },
{ value: "fr", title: "French", url: "/translations/fr.json" },
{ value: "qps", title: "Pseudo", url: "/translations/qps.json" },
],
};
const preview: Preview = {
globalTypes: {
locale: neokapiGlobalType(i18n),
},
loaders: [neokapiLoader(i18n)],
decorators: [neokapiDecorator(i18n)],
};
export default preview;
Àļļ ţĥŕéé ţàķé ţĥé šàḿé öþţîöñš öƃĵéçţ. Ţĥé ļöàđéŕ ƒéţçĥéš ţĥé çàţàļöĝ àñđ ţĥé đéçöŕàţöŕ ŕéñđéŕš ţĥé šţöŕý îñ îţ, ŵĥîçĥ ḿàţţéŕš ƒöŕ šţöŕîéš ŵîţĥ à play ƒüñçţîöñ: Šţöŕýƃööķ ŕüñš ļöàđéŕš, ţĥéñ ḿöüñţš ţĥé šţöŕý, ţĥéñ ŕüñš play, šö à šţöŕý ţĥàţ ƒéţçĥéđ îţš çàţàļöĝ ƒŕöḿ ţĥé đéçöŕàţöŕ ŕé-ķéýéđ àƒţéŕ play ĥàđ àļŕéàđý çļîçķéđ, àñđ ţĥé çàñṽàš šĥöŵéđ ţĥé üñöþéñéđ šţàţé ŵĥîļé ţĥé Îñţéŕàçţîöñš þàñéļ ŕéàđ àš þàššéđ.
Àñđ îñ .storybook/main.ts, éñàƃļé ţĥé þļüĝîñ šö šţöŕîéš ĝéţ ţĥé ŕüñţîḿé ţŕàñšƒöŕḿ:
import neokapi from "@neokapi/i18n-react/vite";
export default {
stories: ["../src/**/*.stories.tsx"],
async viteFinal(config) {
config.plugins?.push(neokapi({ mode: "runtime" }));
return config;
},
};
À ĝļöƃé îçöñ àþþéàŕš îñ ţĥé Šţöŕýƃööķ ţööļƃàŕ; šŵîţçĥîñĝ ļöçàļé ŕé-ŕéñđéŕš éṽéŕý šţöŕý. Üšéƒüļ ƒöŕ đéšîĝñ ŕéṽîéŵ, ţŕàñšļàţîöñ çĥéçķš, àñđ ŔŢĻ ļàýöüţ ţéšţîñĝ.
ĤŢḾĻ lang àñđ dir àţţŕîƃüţéš
setTranslations() àñđ loadTranslations() þüšĥ ţĥé ļöçàļé öñţö ţĥé đöçüḿéñţ ŕööţ àüţöḿàţîçàļļý:
<!-- before -->
<html lang="en" dir="ltr">
<!-- after loadTranslations("ar", …) -->
<html lang="ar" dir="rtl"></html>
</html>
Ţĥé ŕüñţîḿé àļšö šŵàþš dir="rtl" ƒöŕ ţĥé çöḿḿöñ ŔŢĻ þŕîḿàŕý šüƃţàĝš (ar, dv, fa, he, ku, ps, sd, ur, yi, àñđ à ƒéŵ ḿöŕé). Éṽéŕýţĥîñĝ éļšé đéƒàüļţš ţö dir="ltr". Ţĥé àţţŕîƃüţé đŕîṽéš ƃŕöŵšéŕ-ļéṽéļ ĥýþĥéñàţîöñ, šþéļļîñĝ, ƒöñţ ƒàļļƃàçķš, àñđ, àƃöṽé àļļ, šçŕééñ-ŕéàđéŕ ļàñĝüàĝé àññöüñçéḿéñţš.
Îñîţîàļ þàĝé ļöàđ
Ýöüŕ index.html ŕéñđéŕš ŵîţĥ ŵĥàţéṽéŕ lang ýöü ĥàŕđ-çöđé, ţýþîçàļļý en. Ŵĥéñ loadTranslations() ŕéšöļṽéš (àšýñç, ĥàþþéñš àƒţéŕ îñîţîàļ þàîñţ), ţĥé ŕüñţîḿé šýñçš ţĥé àţţŕîƃüţé. À üšéŕ öñ ţĥé đéƒàüļţ ļöçàļé šééš ñö ƒļàšĥ; à üšéŕ ŵĥöšé ļàñĝüàĝé îš ļöàđéđ àţ ƃööţ šééš à ṽéŕý ƃŕîéƒ en → <their-locale> ƒļîþ öñ ƒîŕšţ ŕéñđéŕ. ΃ ţĥàţ ḿàţţéŕš, šéţ lang öñ ţĥé šéŕṽéŕ ţö ḿàţçĥ ţĥé üšéŕ'š çööķîé / ĥéàđéŕ ƃéƒöŕé šéŕṽîñĝ ţĥé ĤŢḾĻ.
Öþţîñĝ öüţ
΃ ýöüŕ àþþ ḿàñàĝéš <html lang> îţšéļƒ (ŠŠŔ ŵîţĥ þŕéšéţ ļàñĝ, ƒŕàḿéŵöŕķ-öŵñéđ ļöçàļé ŕöüţîñĝ, ḿüļţî-ļöçàļé šüŕƒàçéš öñ öñé þàĝé), þàšš syncDocumentLocale: false:
import { setTranslations } from "@neokapi/i18n-react/runtime";
setTranslations("ja", dict, { syncDocumentLocale: false });
// or:
await loadTranslations("ja", "/translations/ja.json", {
syncDocumentLocale: false,
});
ŠŠŔ îš ĥàñđļéđ àüţöḿàţîçàļļý: ţĥé öþţîöñ đéƒàüļţš ţö true ŵĥéñ document îš đéƒîñéđ àñđ false öţĥéŕŵîšé, šö setTranslations îš šàƒé ţö çàļļ ƒŕöḿ Ñöđé.
Ḿàñüàļ šýñç
Ŵĥéñ ýöü ñééđ ţö þüšĥ ļöçàļé šţàţé ŵîţĥöüţ šŵàþþîñĝ ţĥé đîçţ (é.ĝ. ýöüŕ àþþ ĥàš ţĥé đîçţ îñļîñéđ àñđ ýöü öñļý ŵàñţ ţö šéţ <html lang>), üšé syncDocumentLocale đîŕéçţļý:
import { syncDocumentLocale } from "@neokapi/i18n-react/runtime";
syncDocumentLocale("fr");
Çüšţöḿ ŔŢĻ đéţéçţîöñ
Ţĥé ƃüîļţ-îñ ŔŢĻ šéţ çöṽéŕš ţĥé çöḿḿöñ çàšéš. ΃ ýöü ñééđ à đéŕéñţ ḿàþþîñĝ (šþàŕšé šçŕîþţ ƒöŕ à šþéçîƒîç þŕöĵéçţ, çüšţöḿ þšéüđö-ļöçàļé ţĥàţ šĥöüļđ ŕéñđéŕ ŔŢĻ, éţç.), ḿàñàĝé <html dir> ýöüŕšéļƒ ŵîţĥ syncDocumentLocale: false:
setTranslations(locale, dict, { syncDocumentLocale: false });
document.documentElement.setAttribute("lang", locale);
document.documentElement.setAttribute("dir", myRTLPolicy(locale) ? "rtl" : "ltr");
Öþţ-öüţ àñđ öṽéŕŕîđé þàţţéŕñš
Þéŕ éļéḿéñţ
<h1 translate="no">SDK_VERSION_4_2</h1>
Þéŕ šéļéçţöŕ
rules: [
{ selector: ".monospace", translate: false },
{ selector: "[aria-hidden]", translate: false },
];
Þéŕ àţţŕîƃüţé öñ à çöḿþöñéñţ
Ţĥé çöñṽéñţîöñ þŕöþš (label, description, heading, …) öñļý éẋţŕàçţ öñ ÞàšçàļÇàšé çöḿþöñéñţš, šö <div label="draft-pending"> îš àļŕéàđý ļéƒţ àļöñé. ΃ öñé öƒ ýöüŕ çöḿþöñéñţš ŕéüšéš öñé öƒ ţĥöšé ñàḿéš ƒöŕ šöḿéţĥîñĝ îñţéŕñàļ (description="internal-id"), ŕéñàḿé ţĥé þŕöþ, ḿàŕķ ţĥé éļéḿéñţ translate="no", öŕ šçöþé îţ öüţ ŵîţĥ à [selector] ŕüļé.
Þéŕ ƒîļé (ĝļöƃ-ƃàšéđ)
Üšé ţĥé ÇĻÎ --src ƒļàĝ ţö šçöþé éẋţŕàçţîöñ. Ţĥé þļüĝîñ šţîļļ ŕüñš ƒöŕ ţĥé Ṽîţé ƃüîļđ, ƃüţ öḿîţţéđ ƒîļéš þŕöđüçé ñö .kbf.json éñţŕîéš.
Đéƃüĝĝîñĝ
"Î çĥàñĝéđ à šţŕîñĝ ƃüţ ţŕàñšļàţîöñš šţîļļ ļöàđ ţĥé öļđ ţéẋţ"
Ĥàšĥ çĥàñĝéđ; ŕüñ neokapi-i18n extract àñđ üþđàţé ţĥé ţŕàñšļàţîöñ đîçţ. À šţàļé .kbf.json ḿéàñš šţàļé ĥàšĥéš.
"Ḿý çüšţöḿ çöḿþöñéñţ'š ţéẋţ îšñ'ţ ĝéţţîñĝ ţŕàñšļàţéđ"
Ŕüñ neokapi-i18n explain <file>: îţ þŕîñţš ţĥé đéçîšîöñ ƒöŕ éṽéŕý éļéḿéñţ öñ ţĥé þàĝé, šö ýöü ŕàŕéļý ĥàṽé ţö ĝüéšš. Ţĥé üšüàļ çàüšéš:
- Đöéš ţĥé çöḿþöñéñţ ĥàṽé đîŕéçţ ĴŠẊŢéẋţ çĥîļđŕéñ? Ţĥé
<MyWidget>some text</MyWidget>þàţţéŕñ àüţö-éẋţŕàçţš ŵîţĥ à ŵàŕñîñĝ. - Κ ţĥé þŕöþ ţŕàñšļàţàƃļé ĥéŕé? ĤŢḾĻ/ÀŔÎÀ ñàḿéš éẋţŕàçţ àñýŵĥéŕé; çöñṽéñţîöñ ñàḿéš ļîķé
helpTextéẋţŕàçţ öñ ÞàšçàļÇàšé çöḿþöñéñţš öñļý. - Κ ţĥé ţéẋţ à ĴŠ ṽàŕîàƃļé? Üšé
t().
"Ŵàŕñîñĝš àŕé ƒļööđîñĝ ḿý çöñšöļé"
Ýöü'ŕé þŕöƃàƃļý ƃüîļđîñĝ Šţöŕýƃööķ öŕ ŕüññîñĝ ţéšţš ŵîţĥ ţĥé þļüĝîñ àçţîṽé. Ŕöüţé ŵàŕñîñĝš ţö à ļöĝĝéŕ ŵîţĥ onWarning öŕ ţüŕñ ţĥé þļüĝîñ öƒƒ îñ ţĥöšé çöñƒîĝš.
"Ĥàšĥ ḿîšḿàţçĥ ƃéţŵééñ éẋţŕàçţ àñđ ţŕàñšƒöŕḿ"
Àļḿöšţ àļŵàýš à componentMap đéšýñç: ţĥé þļüĝîñ àñđ ţĥé ÇĻÎ çöḿþüţéđ đéŕéñţ ķéýš ƃéçàüšé ţĥéý ŵéŕé çöñƒîĝüŕéđ đéŕéñţļý. Šĥàŕé öñé çöñƒîĝ ƒîļé ƃéţŵééñ ţĥéḿ. neokapi-i18n explain þŕîñţš ţĥé ĥàšĥ éàçĥ éļéḿéñţ ĝéţš, šö ýöü çàñ çöḿþàŕé îţ àĝàîñšţ ţĥé .kbf.json đîŕéçţļý.
"À šţŕîñĝ ŕéñđéŕš îñ Éñĝļîšĥ îñ à þšéüđö ƃüîļđ, ƃüţ ţĥé çöḿþöñéñţ ļööķš ţŕàñšļàţàƃļé"
Üšüàļļý öñé öƒ ţĥéšé ţĥŕéé:
- À šţàļé ƃüîļđ, ñöţ à šţàļé đîçţ. Ţĥé þļüĝîñ ŕé-ŕéàđš à
ţŕàñšļàţîöñ ƒîļé ŵĥéñéṽéŕ îţš ḿţîḿé çĥàñĝéš, šö ŕé-ŕüññîñĝ
neokapi-i18n compileŵĥîļé ţĥé đéṽ šéŕṽéŕ îš üþ îš éñöüĝĥ; ţĥéŕé îš ñö ñééđ ţö ŕéšţàŕţ îţ. ΃ à çöđé çĥàñĝé šééḿš ñöţ ţö ĥàṽé ļàñđéđ, ţĥàţ'š Ṽîţé'š đéþ çàçĥé: ķîļļ ţĥé đéṽ šéŕṽéŕ àñđrm -rf node_modules/.vite. - Ļîñķéđ ŵöŕķšþàçé þàçķàĝé: ýöüŕ àþþ'š éẋţŕàçţ öñļý ŵàļķš
îţš öŵñ
src/**ƃý đéƒàüļţ. À ĴŠẊ šţŕîñĝ îñ à ļîñķéđ ŵöŕķšþàçé þàçķàĝé ĝéţš ţĥé ŕüñţîḿé__t()ŕéŵŕîţé (ṽîà Ṽîţé'š þļüĝîñ) ƃüţ ñö éẋţŕàçţéđ çàţàļöĝ éñţŕý, šö ţĥé ļööķüþ ƒàļļš ƃàçķ ţö šöüŕçé. Þàšš àñöţĥéŕ--srcĝļöƃ ƒöŕ éàçĥ þàçķàĝé, öŕ ŕüñ éàçĥ þàçķàĝé'š éẋţŕàçţ îñţö à šĥàŕéđi18n/đîŕéçţöŕý. - Đöüƃļé-ŵŕàþ đéţéçţîöñ: šéé "Ţŕàñšļàţéđ çöñţéñţ šĥöŵš
▒ ▒ … ▒ ▒îñ þšéüđö" ƃéļöŵ.
"Ţŕàñšļàţéđ çöñţéñţ šĥöŵš ▒ ▒ … ▒ ▒ îñ þšéüđö"
Ţŵö ţŕàñšļàţîöñ ļàýéŕš šţàçķîñĝ: àñ îññéŕ t() çàļļ þŕöđüçéš à
þšéüđö-ţŕàñšļàţéđ šţŕîñĝ, ţĥéñ àñ öüţéŕ éļéḿéñţ ŵŕàþš îţš ŵĥöļé ƃöđý
(îñçļüđîñĝ ţĥàţ àļŕéàđý-ţŕàñšļàţéđ šţŕîñĝ) àš îţš öŵñ ƃļöçķ, àđđîñĝ à
šéçöñđ þàîŕ öƒ ḿàŕķéŕš. Çöḿḿöñ ŵîţĥ đýñàḿîç ļàƃéļ þàţţéŕñš:
// meta.label is already a t()-resolved string from categoryMeta()
<Button>
{meta.label} ({catTools.length})
</Button>
// pseudo: ▒ ▒ Utility ▒ (32) ▒ ← double wrap
Ḿàŕķ ţĥé öüţéŕ éļéḿéñţ translate="no" šö öñļý ţĥé îññéŕ t() ŵŕàþš:
<Button translate="no">
{meta.label} ({catTools.length})
</Button>
// pseudo: ▒ Utility ▒ (32) ← single wrap
"À {placeholder} ñàḿé îš ŕéñđéŕîñĝ àš {ᴘʟᴀᴄᴇʜᴏʟᴅᴇʀ} îñ þšéüđö"
kapi'š þšéüđö-ţŕàñšļàţé ţööļ þŕéšéŕṽéš {…} çöñţéñţš ṽéŕƃàţîḿ ţĥŕöüĝĥ
ţĥé àççéñţ ţŕàñšƒöŕḿ, šö à ḿàñĝļéđ ñàḿé ḿéàñš ţĥé çàţàļöĝ îš šţàļé.
Ŕéĝéñéŕàţé îţ (ţýþîçàļļý npm run extract && kapi pseudo-translate … && npm run compile, öŕ ŵĥàţéṽéŕ šçŕîþţ ýöüŕ þŕöĵéçţ ŵîŕéš üþ).
Ñéẋţ
- Ç-01 Kapi Þŕöĵéçţ Ḿöđéļ: þŕöĵéçţ ļàýöüţ àñđ ƃļöçķ šţöŕé.
- kapi ÇĻÎ öṽéŕṽîéŵ: ţŕàñšļàţîöñ çöḿḿàñđš ţĥàţ çöñšüḿé ýöüŕ
.kbf.json.