Šĥîþ-àŵàŕé ļàñĝüàĝé þîçķéŕ
À þŕöĵéçţ đéçîđéš ŵĥîçĥ ţŕàñšļàţéđ ṽéŕšîöñš ţö öƒƒéŕ îţš üšéŕš. neokapi ḿöđéļš
ţĥàţ đéçîšîöñ ŵîţĥ ţŵö ĝàţéš, ƃöţĥ đéçļàŕéđ îñ kapi.yaml, ƃöţĥ éṽàļüàţéđ
ţĥé šàḿé ŵàý àĝàîñšţ ţĥé ţàŕĝéţ šţàţüš ļàđđéŕ
(draft → translated → reviewed → signed-off):
- Ţĥé šĥîþ ĝàţé: ţĥé ƃàŕ ţö ĝö ļîṽé. À ļöçàļé ţĥàţ çļéàŕš îţ îš šàƒé ţö
öƒƒéŕ. Îţ îš ţĥé
ship_gate/ship_gatesçöñƒîĝüŕàţîöñ. - Ţĥé ṽéŕîƒîéđ ĝàţé: ţĥé ƃàŕ ţö çöüñţ àš ĥüḿàñ-ṽéŕîƒîéđ: à þéŕšöñ ŕéṽîéŵéđ öŕ šîĝñéđ öƒƒ ţĥé çöñţéñţ. À ļöçàļé ţĥàţ šĥîþš ƃüţ îš ñöţ ṽéŕîƒîéđ îš ÀÎ-öñļý ŵöŕķ.
Ţĥé ţŵö ĝàţéš àŕé îñđéþéñđéñţ. Ƃéîñĝ ṽéŕîƒîéđ îš ñöţ à þŕéŕéǫüîšîţé ƒöŕ šĥîþþîñĝ: à þŕöĵéçţ çàñ ĝö ļîṽé ŵîţĥ ḿàçĥîñé ţŕàñšļàţîöñ àñđ ḿàŕķ ţĥöšé ļöçàļéš àš üñṽéŕîƒîéđ üñţîļ à ŕéṽîéŵéŕ çàţçĥéš üþ.
Đéçļàŕîñĝ ţĥé ṽéŕîƒîéđ ĝàţé
Ţĥé ṽéŕîƒîéđ ĝàţé üšéš ţĥé šàḿé ţĥŕéé àđđîţîṽé ƒöŕḿš àñđ ţĥé šàḿé þŕéçéđéñçé àš
ţĥé šĥîþ ĝàţé, àñđ ŕéšöļṽéš à gate: ñàḿé àĝàîñšţ ţĥé šĥàŕéđ gates: ŕéĝîšţŕý:
# kapi.yaml
ship_gate: { translated: 100 } # go live once fully translated
verified_gate: { reviewed: 100 } # count as verified once fully reviewed
À ŕüļé ļîšţ ñàŕŕöŵš ţĥé ƃàŕ þéŕ çöļļéçţîöñ öŕ ļöçàļé, ḿöšţ-šþéçîƒîç ŕüļé ŵîñš:
verified_gates:
- when: { locales: [ja] }
gate: { signed-off: 100 } # Japanese needs sign-off
- gate: { reviewed: 100 } # everything else: reviewed
Ţĥé ŕéçîþé ķéýš àŕé verified_gate (à šîñĝļé çàţçĥ-àļļ ĝàţé) àñđ
verified_gates (à ŵĥéñ/ĝàţé ŕüļé ļîšţ).
À þŕöĵéçţ ŵîţĥ ñö ṽéŕîƒîéđ ĝàţé ĥàš ñö ṽéŕîƒîéđ ļöçàļéš: éṽéŕý šĥîþþàƃļé
ļöçàļé ŕéàđš àš ÀÎ-öñļý. Đéçļàŕîñĝ verified_gate / verified_gates îš ĥöŵ à
þŕöĵéçţ öþţš îñ ţö ţĥé šţŕöñĝéŕ çļàîḿ. Ţĥîš ķééþš ţĥé ļàƃéļ ĥöñéšţ: "ṽéŕîƒîéđ"
ñéṽéŕ àþþéàŕš üñļéšš à ƃàŕ ŵàš đéçļàŕéđ àñđ çļéàŕéđ.
À ļöçàļé ţĥàţ ñö ship_gate öŕ ship_gates ŕüļé ḿàţçĥéš ĥàš ñö ƃàŕ ţö çļéàŕ,
šö kapi ŕéþöŕţš îţ àš ñöţ ĝàţéđ ŕàţĥéŕ ţĥàñ šĥîþþàƃļé. Ñöţĥîñĝ ŵîţĥĥöļđš îţ
öñ çöṽéŕàĝé, šö ţĥé þîçķéŕ öƒƒéŕš îţ ƃý đéƒàüļţ. Šţàļé ŵöŕđîñĝ, à ţŕàñšļàţîöñ à
ŕéṽîéŵéŕ ţüŕñéđ đöŵñ, à ƒàîļîñĝ çĥéçķ, öŕ ţéŕḿš ţĥàţ ŵéŕé ñöţ çĥéçķéđ ŵîţĥĥöļđ à
ļöçàļé ŵĥéţĥéŕ öŕ ñöţ à ĝàţé ḿàţçĥéš îţ.
Éḿîţţîñĝ ţĥé ḿàñîƒéšţ
kapi status --ship þŕöĵéçţš ţĥé þéŕ-ļöçàļé šţàñđîñĝ ţö à ḿîñîḿàļ ḿàñîƒéšţ à
ļàñĝüàĝé þîçķéŕ çàñ çöñšüḿé. Ŵŕîţé îţ îñţö ýöüŕ àþþ'š šţàţîç àššéţš àš þàŕţ öƒ
ţĥé ƃüîļđ:
kapi status --ship --emit public/ship.json
Ŵîţĥöüţ --emit, ţĥé ḿàñîƒéšţ ĝöéš ţö šţđöüţ, šö à ƃüîļđ šţéþ çàñ ŕéđîŕéçţ îţ:
kapi status --ship > public/ship.json
Ţĥé ƒîļé îš ķéýéđ ƃý ļöçàļé, éàçĥ éñţŕý çàŕŕýîñĝ ţĥé ţŵö ĝàţéš' öüţçöḿéš àñđ ţĥé ļöçàļé'š šĥîþ šţàţé:
{
"fr": { "shippable": true, "verified": true, "state": "shippable" },
"de": { "shippable": true, "verified": false, "state": "shippable" },
"nl": { "shippable": true, "verified": false, "state": "shippable", "not_governed": ["terms"] },
"sv": { "shippable": true, "verified": false, "state": "not_gated" },
"ja": { "shippable": false, "verified": false, "state": "withheld" }
}
Ĥéŕé Ƒŕéñçĥ šĥîþš àñđ îš ṽéŕîƒîéđ (ñö ƃàđĝé), Ĝéŕḿàñ šĥîþš ƃüţ îš ÀÎ-öñļý (ƒļàĝĝéđ), Đüţçĥ šĥîþš ţĥé šàḿé ŵàý îñ à ļàñĝüàĝé ñö ţéŕḿš ĝöṽéŕñ, Šŵéđîšĥ ĥàš ñö šĥîþ ĝàţé àñđ îš öƒƒéŕéđ àš ñöţ ĝàţéđ, àñđ Ĵàþàñéšé îš ŵîţĥĥéļđ.
state ţàķéš öñé öƒ ţĥŕéé ṽàļüéš:
state | Ḿéàñîñĝ | shippable |
|---|---|---|
shippable | À šĥîþ ĝàţé ḿàţçĥéš ţĥé ļöçàļé àñđ ţĥé ļöçàļé çļéàŕš îţ. | true |
withheld | Ţĥé ļöçàļé đöéš ñöţ šĥîþ: îţ îš šĥöŕţ öƒ îţš ĝàţé, öŕ šţàļé, ŕéĵéçţéđ öŕ ƒàîļîñĝ çöñţéñţ, öŕ üñçĥéçķéđ ţéŕḿš, ĥöļđ îţ ƃàçķ. | false |
not_gated | Ñö šĥîþ ĝàţé ḿàţçĥéš ţĥé ļöçàļé, àñđ ñöţĥîñĝ ŵîţĥĥöļđš îţ. | true |
À ļöçàļé šþŕéàđ öṽéŕ šéṽéŕàļ çöļļéçţîöñš ţàķéš ţĥé ŵéàķéšţ šţàţé àḿöñĝ ţĥéḿ:
öñé ŵîţĥĥéļđ çöļļéçţîöñ ŵîţĥĥöļđš îţ, àñđ à çöļļéçţîöñ ñö ĝàţé ḿàţçĥéš ļéàṽéš îţ
ñöţ ĝàţéđ. shippable îš true ƒöŕ ƃöţĥ shippable àñđ not_gated, šö à
þîçķéŕ ţĥàţ ŕéàđš öñļý shippable öƒƒéŕš à ñöţ-ĝàţéđ ļöçàļé; state îš ŵĥàţ
ţéļļš à çļéàŕéđ ĝàţé ƒŕöḿ ñö ĝàţé. Àñ éñţŕý çàŕŕîéš not_governed ŵĥéñ à đîḿéñšîöñ ĝöṽéŕñš
ñöţĥîñĝ îñ ţĥàţ ļàñĝüàĝé: terms ḿéàñš ţĥàţ ñöñé öƒ ţĥé ţéŕḿš ƃöüñđ ŵĥéŕé ţĥé
ļàñĝüàĝé'š çöñţéñţ šîţš, üñđéŕ ţĥé þŕöĵéçţ đéƒàüļţš öŕ öñ à þŕöƒîļé, ĥàš à ţéŕḿ
ƒöŕ îţ. Ţĥé þîçķéŕ đöéš ñöţ ŕéàđ îţ. Îţ îš ţĥéŕé šö à ƃüîļđ šţéþ öŕ à
ŕéàđéŕ đöéš ñöţ ţàķé ţĥé ļàñĝüàĝé ƒöŕ à ĝöṽéŕñéđ öñé. Ţĥé ŕîçĥéŕ kapi status --json
ŕéþöŕţ çàŕŕîéš ţĥé šàḿé shippable àñđ verified ƒîéļđš þéŕ çöļļéçţîöñ àñđ
ļöçàļé, ŵîţĥ ţĥé šţàţé àš shipState àñđ ţĥé ƒüļļ çöṽéŕàĝé þéŕçéñţàĝéš, ƒöŕ
đàšĥƃöàŕđš.
À ĥöšţéđ ƒééđ îñšţéàđ öƒ à ƃüîļţ ƒîļé
Ţĥé ḿàñîƒéšţ đöéš ñöţ ĥàṽé ţö ƃé à ƒîļé ýöü ƃüîļđ îñţö ýöüŕ àššéţš. Ƃéçàüšé ţĥé ļöàđéŕ ţàķéš à ÜŔĻ, îţ çàñ ŕéàđ ţĥé šàḿé šĥàþé ƒŕöḿ à ĥöšţéđ ƒééđ, à šéŕṽéŕ ţĥàţ šéŕṽéš ţĥé þéŕ-ļöçàļé ḿàñîƒéšţ ļîṽé àţ à þüƃļîç ÜŔĻ. Ţĥé ƃüîļđ šţéþ đîšàþþéàŕš, àñđ ţĥé þîçķéŕ ŕéàđš ţĥé çüŕŕéñţ šţàñđîñĝ öñ éàçĥ ļöàđ ŕàţĥéŕ ţĥàñ ŵĥàţéṽéŕ ŵàš ţŕüé àţ ƃüîļđ ţîḿé.
Ţĥé çöñţŕàçţ îš éẋàçţļý ţĥé ƒîļé'š: àñ öƃĵéçţ ķéýéđ ƃý ļöçàļé, éàçĥ ṽàļüé
{ shippable, verified, 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: "shippable" }, …]
Ţĥé ļàƃéļ îš ŕéšöļṽéđ îñ ţĥîš öŕđéŕ: àñ éẋþļîçîţ 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' ŵĥéñ ţĥé ļöçàļé šĥîþš ƃüţ îš ñöţ ṽéŕîƒîéđ, àñđ 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 öṽéŕŕîđé ḿàþ
îƒ ýöü ļîšţ ţĥéḿ îñ ţĥé þîçķéŕ, öŕ šüŕƒàçé ţĥéḿ šéþàŕàţéļý îñ ýöüŕ öŵñ đéṽ
ţööļîñĝ.
Çöḿþàţîƃîļîţý
Ţĥé ṽéŕîƒîéđ ĝàţé îš þüŕéļý àđđîţîṽé. Ŕéçîþéš ţĥàţ đéçļàŕé ñö verified_gate
àŕé üñàƒƒéçţéđ: ţĥéîŕ šĥîþ-ĝàţé ƃéĥàṽîöüŕ îš üñçĥàñĝéđ àñđ éṽéŕý ļöçàļé ŕéàđš àš
üñṽéŕîƒîéđ. Ţĥé þîçķéŕ ĥéļþéŕ đéĝŕàđéš šàƒéļý ŵĥéñ ship.json îš àƃšéñţ, àñđ
ŕéàđš àñ éñţŕý ŵîţĥ ñö state ƃý îţš shippable ƒļàĝ àļöñé, šö à þŕöĵéçţ çàñ
àđöþţ ţĥé ḿàñîƒéšţ àñđ ţĥé þîçķéŕ îñđéþéñđéñţļý.