Çöñƒîĝüŕàţîöñ
Ţĥéšé šéţţîñĝš çöñţŕöļ ŵĥàţ ñéöķàþî-î18ñ éẋţŕàçţš, ĥöŵ îţ ķéýš éàçĥ šţŕîñĝ àñđ ĥöŵ ţŕàñšļàţîöñš ŕéàçĥ ţĥé þàĝé. Ţĥé þļüĝîñ öþţîöñš çöḿé ƒîŕšţ, ţĥéñ ţĥé 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 éñţŕîéš ḿéŕĝé öṽéŕ ţĥé ŕéšöļṽéđ ḿàþ, šö àñ éẋþļîçîţ éñţŕý ŵîñš. Çöḿþöñéñţš ƒŕöḿ à ļîƃŕàŕý ŵîţĥ þŕöþéŕ ţýþéš öŕ à šĥîþþéđ ḿàñîƒéšţ, šüçĥ àš šĥàđçñ-šţýļé çöḿþöñéñţš, ñééđ ñö éñţŕý.
Àñ üñḿàþþéđ çöḿþöñéñţ šţîļļ éẋţŕàçţš ţĥŕöüĝĥ ţĥé þŕöḿöţîöñ ŕüļé, ŵîţĥ à ŵàŕñîñĝ. Àđđîñĝ àñ éñţŕý šîļéñçéš ţĥé ŵàŕñîñĝ àñđ ŕé-ķéýš ţĥé ƃļöçķ'š ĥàšĥ ƒŕöḿ 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 àƃöṽé ĝöṽéŕñš ḿîššîñĝ ţŕàñšļàţîöñš îñ îñļîñé ḿöđé; warningsAsErrors ĝöṽéŕñš ţĥé àüţĥöŕîñĝ îššüéš ţĥé éẋţŕàçţöŕ ŕéçöŕđš.
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.
Àñđ îñ .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" ƒöŕ éṽéŕýţĥîñĝ éļšé. Ƃŕöŵšéŕš üšé ţĥéšé àţţŕîƃüţéš ƒöŕ ĥýþĥéñàţîöñ, šþéļļ çĥéçķîñĝ àñđ ƒöñţ ƒàļļƃàçķ, àñđ à šçŕééñ ŕéàđéŕ üšéš lang ţö çĥööšé ţĥé ļàñĝüàĝé îţ ŕéàđš îñ.
Îñîţîàļ þàĝé ļöàđ
Ýöüŕ 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} ñàḿé îš ŕéñđéŕîñĝ àš {ᴘʟᴀᴄᴇʜᴏʟᴅᴇʀ} îñ þšéüđö"
ķàþî'š þšéüđö-ţŕàñšļàţé ţööļ þŕéšéŕṽéš {…} çöñţéñţš ṽéŕƃàţîḿ ţĥŕöüĝĥ
ţĥé àççéñţ ţŕàñšƒöŕḿ, šö à ḿàñĝļéđ ñàḿé ḿéàñš ţĥé çàţàļöĝ îš šţàļé.
Ŕéĝéñéŕàţé îţ (ţýþîçàļļý npm run extract && kapi pseudo-translate … && npm run compile, öŕ ŵĥàţéṽéŕ šçŕîþţ ýöüŕ þŕöĵéçţ ŵîŕéš üþ).
Ŕéļàţéđ
- Ç-01 Ķàþî Þŕöĵéçţ Ḿöđéļ: þŕöĵéçţ ļàýöüţ àñđ ƃļöçķ šţöŕé.
- ķàþî ÇĻÎ öṽéŕṽîéŵ: ţŕàñšļàţîöñ çöḿḿàñđš ţĥàţ çöñšüḿé ýöüŕ
.kbf.json.