Šĥîþ-àŵàŕé ļàñĝüàĝé þîçķéŕ
À šĥîþ-àŵàŕé ļàñĝüàĝé þîçķéŕ öƒƒéŕš ýöüŕ üšéŕš öñļý ţĥé ļöçàļéš ţĥàţ àŕé ŕéàđý,
àñđ ḿàŕķš ţĥé öñéš ţĥàţ àŕé ÀÎ ţŕàñšļàţîöñ. ķàþî ŵŕîţéš éàçĥ ļöçàļé'š šţàñđîñĝ
ţö à ship.json ḿàñîƒéšţ, àñđ @neokapi/i18n-react/ship ţüŕñš ţĥàţ ḿàñîƒéšţ
îñţö à þîçķéŕ ḿöđéļ.
Ţĥé ĝàţéš îñ ţĥé ŕéçîþé đéçîđé éàçĥ ļöçàļé'š šţàţé, àñđ ţĥé þîçķéŕ ŕéàđš ţĥé ḿàñîƒéšţ ķàþî ŵŕîţéš.
Ţŵö ĝàţéš đéçîđé ŵĥàţ šĥîþš
À þŕöĵéçţ'š ḿàîñţàîñéŕš đéçîđé ŵĥîçĥ ţŕàñšļàţéđ ṽéŕšîöñš ţö öƒƒéŕ îţš üšéŕš.
Ţĥéý đéçļàŕé ţĥàţ đéçîšîöñ àš ţŵö ĝàţéš îñ kapi.yaml, àñđ ķàþî éṽàļüàţéš
ƃöţĥ ţĥé šàḿé ŵàý àĝàîñšţ ţĥé ţàŕĝéţ šţàţüš ļàđđéŕ
(draft → translated → approved):
- Ţĥé šĥîþ ĝàţé: ţĥé ƃàŕ ţö ĝö ļîṽé. À ļöçàļé ţĥàţ çļéàŕš îţ šĥîþš
translated: ţŕàñšļàţéđ ŵîţĥ îţš çĥéçķš ĝŕééñ, àñđ šàƒé ţö öƒƒéŕ. Îţ îš ţĥéship_gate/ship_gatesçöñƒîĝüŕàţîöñ. - Ţĥé àþþŕöṽàļ ĝàţé: ţĥé ƃàŕ ţö šĥîþ àþþŕöṽéđ, ḿéàñîñĝ à þéŕšöñ àþþŕöṽéđ
ţĥé çöñţéñţ. Îţ îš ţĥé
established_gate/established_gatesçöñƒîĝüŕàţîöñ. Ţĥé ŕéçîþé àñđship.jsonñàḿé ţĥé àþþŕöṽéđ ŕüñĝ àñđ šţàţéestablished.
À ļöçàļé ţĥàţ šĥîþš translated îš ÀÎ ŵöŕķ, àñđ ţĥé þîçķéŕ ḿàŕķš îţ. À þŕöĵéçţ
ţĥàţ đéļîṽéŕš öñļý àþþŕöṽéđ çöñţéñţ šàýš šö îñ îţš šĥîþ ĝàţé
(ship_gate: { established: 100 }).
Đéçļàŕîñĝ ţĥé àþþŕöṽàļ ĝàţé
Ţĥé àþþŕöṽàļ ĝàţé üšéš ţĥé šàḿé ţĥŕéé àđđîţîṽé ƒöŕḿš àñđ ţĥé šàḿé
þŕéçéđéñçé àš ţĥé šĥîþ ĝàţé, àñđ ŕéšöļṽéš à gate: ñàḿé àĝàîñšţ ţĥé šĥàŕéđ
gates: ŕéĝîšţŕý:
# kapi.yaml
ship_gate: { translated: 100 } # go live once fully translated
established_gate: { established: 100 } # ship approved once a person approved every translation
À ŕüļé ļîšţ ñàŕŕöŵš ţĥé ƃàŕ þéŕ çöļļéçţîöñ öŕ ļöçàļé, ḿöšţ-šþéçîƒîç ŕüļé ŵîñš:
established_gates:
- when: { locales: [ja] }
gate: { established: 100 } # Japanese needs a person on every translation
- gate: { established: 80 }
À þŕöĵéçţ ŵîţĥ ñö àþþŕöṽàļ ĝàţé ĥàš ñö ļöçàļé îñ ţĥé established
šţàţé: éṽéŕý ļöçàļé ţĥàţ šĥîþš ŕéàđš translated, ḿàŕķéđ ÀÎ.
À ļöçàļé ţĥàţ ñö ship_gate öŕ ship_gates ŕüļé ḿàţçĥéš ĥàš ñö ƃàŕ ţö çļéàŕ,
šö ķàþî ŕéþöŕţš îţ àš ñöţ ĝàţéđ ŕàţĥéŕ ţĥàñ šĥîþþàƃļé. Ñöţĥîñĝ ŵîţĥĥöļđš îţ
öñ çöṽéŕàĝé, šö ţĥé þîçķéŕ öƒƒéŕš îţ ƃý đéƒàüļţ. Šţàļé ŵöŕđîñĝ, à ţŕàñšļàţîöñ à
ŕéṽîéŵéŕ ţüŕñéđ đöŵñ, à ƒàîļîñĝ çĥéçķ, öŕ ţéŕḿš ţĥàţ ŵéŕé ñöţ çĥéçķéđ ŵîţĥĥöļđ à
ļöçàļé ŵĥéţĥéŕ öŕ ñöţ à ĝàţé ḿàţçĥéš îţ.
Éḿîţţîñĝ ţĥé ḿàñîƒéšţ
kapi status --ship ŵŕîţéš éàçĥ ļöçàļé'š šţàñđîñĝ àš à šḿàļļ ḿàñîƒéšţ ţĥàţ à
ļàñĝüàĝé þîçķéŕ ŕéàđš. Ŵŕîţé îţ îñţö ýöüŕ àþþ'š šţàţîç àššéţš àš þàŕţ öƒ ţĥé
ƃüîļđ:
kapi status --ship --emit public/ship.json
Ŵîţĥöüţ --emit, ţĥé ḿàñîƒéšţ ĝöéš ţö šţđöüţ, šö à ƃüîļđ šţéþ çàñ ŕéđîŕéçţ îţ:
kapi status --ship > public/ship.json
Ţĥé ƒîļé îš ķéýéđ ƃý ļöçàļé, éàçĥ éñţŕý çàŕŕýîñĝ ŵĥéţĥéŕ ţĥé ļöçàļé šĥîþš àñđ îţš šĥîþ šţàţé:
{
"fr": { "shippable": true, "state": "established" },
"de": { "shippable": true, "state": "translated" },
"nl": { "shippable": true, "state": "translated", "not_governed": ["terms"] },
"sv": { "shippable": true, "state": "not_gated" },
"ja": { "shippable": false, "state": "withheld" }
}
Ĥéŕé Ƒŕéñçĥ šĥîþš àþþŕöṽéđ (ñö ƃàđĝé), Ĝéŕḿàñ šĥîþš ţŕàñšļàţéđ (ƒļàĝĝéđ ÀÎ), Đüţçĥ šĥîþš ţĥé šàḿé ŵàý îñ à ļàñĝüàĝé ñö ţéŕḿš ĝöṽéŕñ, Šŵéđîšĥ ĥàš ñö ĝàţé àñđ îš öƒƒéŕéđ àš ñöţ ĝàţéđ, àñđ Ĵàþàñéšé îš ŵîţĥĥéļđ.
state ţàķéš öñé öƒ ƒöüŕ ṽàļüéš:
state | Ḿéàñîñĝ | shippable |
|---|---|---|
established | Àñ àþþŕöṽàļ ĝàţé ḿàţçĥéš ţĥé ļöçàļé àñđ ţĥé ļöçàļé çļéàŕš îţ: à þéŕšöñ àþþŕöṽéđ ţĥé çöñţéñţ. | true |
translated | À šĥîþ ĝàţé ḿàţçĥéš ţĥé ļöçàļé àñđ ţĥé ļöçàļé çļéàŕš îţ, àñđ ñö àþþŕöṽàļ ĝàţé îš ḿéţ: ţĥé ļöçàļé šĥîþš àš ÀÎ ţŕàñšļàţîöñ. | true |
withheld | Ţĥé ļöçàļé đöéš ñöţ šĥîþ: îţ îš šĥöŕţ öƒ îţš ĝàţé, öŕ šţàļé, ŕéĵéçţéđ öŕ ƒàîļîñĝ çöñţéñţ, öŕ üñçĥéçķéđ ţéŕḿš, ĥöļđ îţ ƃàçķ. | false |
not_gated | Ñö ĝàţé ḿàţçĥéš ţĥé ļöçàļé, àñđ ñöţĥîñĝ ŵîţĥĥöļđš îţ. | true |
À ļöçàļé šþŕéàđ öṽéŕ šéṽéŕàļ çöļļéçţîöñš ţàķéš ţĥé ŵéàķéšţ šţàţé àḿöñĝ ţĥéḿ, îñ
ţĥé öŕđéŕ ŵîţĥĥéļđ, ñöţ_ĝàţéđ, ţŕàñšļàţéđ, éšţàƃļîšĥéđ. shippable îš true
ƒöŕ éṽéŕý šţàţé ƃüţ withheld, šö à þîçķéŕ ţĥàţ ŕéàđš öñļý shippable öƒƒéŕš à
ñöţ-ĝàţéđ ļöçàļé. state ţéļļš à çļéàŕéđ ĝàţé ƒŕöḿ ñö ĝàţé.
Àñ éñţŕý çàŕŕîéš not_governed ŵĥéñ à đîḿéñšîöñ ĝöṽéŕñš ñöţĥîñĝ îñ ţĥàţ
ļàñĝüàĝé. terms ḿéàñš ţĥàţ ñöñé öƒ ţĥé ţéŕḿš ƃöüñđ ŵĥéŕé ţĥé ļàñĝüàĝé'š
çöñţéñţ šîţš, üñđéŕ ţĥé þŕöĵéçţ đéƒàüļţš öŕ öñ à þŕöƒîļé, ĥàš à ţéŕḿ ƒöŕ îţ. Ţĥé
þîçķéŕ îĝñöŕéš ţĥé ƒîéļđ; îţ ţéļļš à ƃüîļđ šţéþ öŕ à þéŕšöñ ţĥàţ ñö ţéŕḿš
ĝöṽéŕñ ţĥé ļàñĝüàĝé.
Ƒöŕ đàšĥƃöàŕđš, ţĥé kapi status --json ŕéþöŕţ çàŕŕîéš shippable àñđ ţĥé
šţàţé, àš shipState, þéŕ çöļļéçţîöñ àñđ ļöçàļé, ŵîţĥ ţĥé ƒüļļ çöṽéŕàĝé
þéŕçéñţàĝéš.
À ĥöšţéđ ƒééđ îñšţéàđ öƒ à ƃüîļţ ƒîļé
Ţĥé ļöàđéŕ ţàķéš à ÜŔĻ, šö îţ çàñ ŕéàđ ţĥé ḿàñîƒéšţ ƒŕöḿ à ĥöšţéđ ƒééđ àš ŵéļļ àš ƒŕöḿ à ƃüîļţ ƒîļé. À ĥöšţéđ ƒééđ îš à šéŕṽéŕ ţĥàţ šéŕṽéš ţĥé þéŕ-ļöçàļé ḿàñîƒéšţ ļîṽé àţ à þüƃļîç ÜŔĻ. Ýöü đŕöþ ţĥé ƃüîļđ šţéþ, àñđ ţĥé þîçķéŕ ŕéàđš ţĥé çüŕŕéñţ šţàñđîñĝ öñ éàçĥ ļöàđ.
Ţĥé ƒééđ ŕéţüŕñš ţĥé ƒîļé'š šĥàþé: àñ öƃĵéçţ ķéýéđ ƃý ļöçàļé, éàçĥ ṽàļüé
{ shippable, state }, ŵîţĥ not_governed ŵĥéŕé îţ àþþļîéš. À ĥöšţéđ ƒééđ îš
ŕéàđ-öñļý àñđ ñééđš ñö àüţĥ, šîñçé à þüƃļîç þîçķéŕ ƒéţçĥéš îţ đîŕéçţļý. Îţ
šĥöüļđ šéñđ àñ ETag àñđ à šĥöŕţ Cache-Control: public, max-age=…, šö à
þîçķéŕ öŕ à ÇĐÑ çàñ ŕéṽàļîđàţé çĥéàþļý ŵîţĥ à 304.
Þöîñţ ţĥé ļöàđéŕ àţ ţĥé ÜŔĻ; ñöţĥîñĝ éļšé çĥàñĝéš:
const status = await loadShipStatus("https://example.com/ship.json");
const model = languagePickerModel(status, ["en", "fr", "de", "ja"]);
Ţĥé šàḿé languagePickerModel ţŕàñšƒöŕḿ àñđ ţĥé šàḿé useShipStatus ĥööķ (ŵĥöšé
šéçöñđ àŕĝüḿéñţ îš ţĥé ḿàñîƒéšţ ÜŔĻ) đŕîṽé ţĥé þîçķéŕ ƒöŕ à ƃüîļţ ƒîļé àñđ à
ĥöšţéđ ƒééđ àļîķé.
Đŕîṽîñĝ ţĥé þîçķéŕ
@neokapi/i18n-react/ship þŕöṽîđéš à đéþéñđéñçý-ƒŕéé ļöàđéŕ àñđ à ĥéàđļéšš
ţŕàñšƒöŕḿ. ΃ ţĥé ḿàñîƒéšţ îš ḿîššîñĝ öŕ ḿàļƒöŕḿéđ, ţĥé ļöàđéŕ ŕéšöļṽéš ţö àñ
éḿþţý öƃĵéçţ, àñđ ţĥé þîçķéŕ šĥöŵš éṽéŕý ļöçàļé ŵîţĥöüţ à ƃàđĝé.
Þàšš ļöçàļé çöđéš. Ţĥé ļàƃéļ ƒöŕ éàçĥ îš ţĥé ļöçàļé'š éñđöñýḿ (ţĥé ļàñĝüàĝé
ñàḿéđ îñ îţš öŵñ ļàñĝüàĝé), ƒŕöḿ Intl.DisplayNames, šö ýöü ķééþ ñö þéŕ-ļöçàļé
ļàƃéļ ţàƃļé:
import { loadShipStatus, languagePickerModel } from "@neokapi/i18n-react/ship";
const status = await loadShipStatus(); // defaults to /ship.json
const model = languagePickerModel(status, ["en", "fr", "de", "ja"]);
// → [{ locale: "fr", label: "Français", shippable: true, badge: 'ai' | null, state: "translated" }, …]
Ţĥé ļàƃéļ îš ŕéšöļṽéđ îñ ţĥîš öŕđéŕ: àñ éẋþļîçîţ label öñ à LocaleInput, ţĥéñ
àñ éñţŕý îñ ţĥé labels öṽéŕŕîđé ḿàþ, ţĥéñ ţĥé Intl.DisplayNames éñđöñýḿ, ţĥéñ
ţĥé ŕàŵ çöđé. Àñ éẋþļîçîţ ļàƃéļ ŕéþļàçéš ţĥé đéŕîṽéđ öñé ŵĥéŕé ýöü ŵàñţ à
đéŕéñţ ƒöŕḿ, àñđ ţĥé öṽéŕŕîđé ḿàþ ñàḿéš ļöçàļéš ţĥàţ Intl çàññöţ. À
þšéüđö-ļöçàļé šüçĥ àš qps ĥàš ñö šţàñđàŕđ ñàḿé, šö ĝîṽé îţ öñé:
const model = languagePickerModel(status, ["fr", "de", "qps"], {
labels: { qps: "Pseudo English" },
});
// fr → "Français" (derived), de → "Deutsch" (derived), qps → "Pseudo English" (override)
Ţĥé šîĝñàţüŕé îš
languagePickerModel(status, locales, options?), ŵĥéŕé options îš
{ labels?: Record<string, string>; includeNotGated?: boolean }.
Ţĥé ƒîŕšţ ļéţţéŕ öƒ à đéŕîṽéđ éñđöñýḿ îš çàþîţàļîžéđ ƒöŕ à ḿéñü-šţýļé ļàƃéļ
(français → Français), šö ļöŵéŕçàšé éñđöñýḿš ŕéàđ çöñšîšţéñţļý àļöñĝšîđé ţĥé
öñéš ţĥàţ àŕé àļŕéàđý çàþîţàļîžéđ (Deutsch); šçŕîþţš ŵîţĥöüţ çàšé (日本語) àŕé
ļéƒţ üñçĥàñĝéđ. ΃ Intl.DisplayNames îš üñàṽàîļàƃļé öŕ ĥàš ñö ñàḿé ƒöŕ à çöđé,
ţĥé ļàƃéļ ƒàļļš ƃàçķ ţö ţĥé öṽéŕŕîđé ḿàþ àñđ ţĥéñ ţö ţĥé ŕàŵ çöđé; îţ ñéṽéŕ
ţĥŕöŵš.
languagePickerModel ŕéţüŕñš ţĥé ļöçàļéš ŵĥöšé éñţŕý îš šĥîþþàƃļé, ŵĥîçĥ
îñçļüđéš à ñöţ-ĝàţéđ ļöçàļé. Þàšš includeNotGated: false ţö öƒƒéŕ öñļý ţĥé
ļöçàļéš ţĥàţ çļéàŕ à ĝàţé. Éàçĥ éñţŕý çàŕŕîéš ţĥé ḿàñîƒéšţ'š state àñđ à
badge: 'ai' ŵĥéñ ţĥé ļöçàļé šĥîþš ƃüţ îš ñöţ established, àñđ null ŵĥéñ
îţ îš. 'ai' îš ţĥé öñļý ƃàđĝé ţĥîš ļàýéŕ éḿîţš; àñ àþþŕöṽéđ ļöçàļé ĥàš
ñö ƃàđĝé. À Ŕéàçţ ƃîñđîñĝ ŵŕàþš ţĥé šàḿé ţŵö ƒüñçţîöñš àñđ ţàķéš ţĥé šàḿé öþţîöñš
àš à ţĥîŕđ àŕĝüḿéñţ:
import { useShipStatus } from "@neokapi/i18n-react/ship/react";
function LanguagePicker({ locales }) {
// loads /ship.json, derives labels, returns the model
const options = useShipStatus(locales, undefined, { labels: { qps: "Pseudo English" } });
return (
<ul>
{options.map(({ locale, label, badge }) => (
<li key={locale}>
{label}
{badge === "ai" && <span className="badge-ai">AI</span>}
</li>
))}
</ul>
);
}
Ýöü ŕéñđéŕ ţĥé þîçķéŕ ýöüŕšéļƒ: šţýļé ţĥé éñţŕîéš àñđ ţĥé AI ƃàđĝé ţö ḿàţçĥ
ýöüŕ đéšîĝñ. Đéṽéļöþḿéñţ-öñļý ļöçàļéš (þšéüđö-ţŕàñšļàţîöñ šüçĥ àš qps)
àŕé ñöţ þàŕţ öƒ ţĥé šĥîþ ḿàñîƒéšţ; ñàḿé ţĥéḿ ţĥŕöüĝĥ ţĥé labels öṽéŕŕîđé ḿàþ
îƒ ýöü ļîšţ ţĥéḿ îñ ţĥé þîçķéŕ, öŕ šüŕƒàçé ţĥéḿ šéþàŕàţéļý îñ ýöüŕ öŵñ đéṽ
ţööļîñĝ.
Çöḿþàţîƃîļîţý
Ţĥé àþþŕöṽàļ ĝàţé îš àđđîţîṽé. À ŕéçîþé ţĥàţ đéçļàŕéš ñöñé ĥàš éṽéŕý
šĥîþþîñĝ ļöçàļé ŕéàđ translated. Ţĥé þîçķéŕ ĥéļþéŕ đéĝŕàđéš šàƒéļý ŵĥéñ
ship.json îš àƃšéñţ, àñđ ŕéàđš àñ éñţŕý ŵîţĥ ñö state ƃý îţš shippable
ƒļàĝ àļöñé, šö à þŕöĵéçţ çàñ àđöþţ ţĥé ḿàñîƒéšţ àñđ ţĥé þîçķéŕ îñđéþéñđéñţļý.
À ŕéçîþé ţĥàţ šţîļļ çàŕŕîéš verified_gate ƒàîļš ţö ļöàđ àñđ ñàḿéš
established_gate.
Ŕéļàţéđ
- Šĥîþ ĝàţéš àñđ ÇÎ: đéçļàŕîñĝ ĝàţéš àñđ ƒàîļîñĝ à ŕéļéàšé öñ ţĥéḿ.
- Ŕüñţîḿé ṽš. îñļîñé ḿöđé: ļöàđîñĝ ţĥé ļöçàļéš ţĥé þîçķéŕ öƒƒéŕš.