Ƒöŕḿàţţîñĝ đàţéš, ñüḿƃéŕš, çüŕŕéñçý
Ţĥéŕé àŕé ţŵö þļàçéš ƒöŕḿàţţîñĝ ĥàþþéñš, àñđ ţĥé šþļîţ ḿàţţéŕš:
- Îñšîđé à ţŕàñšļàţéđ šţŕîñĝ:
You have {n, number} unread messages. Ţĥé ŕüñţîḿé ƒöŕḿàţš ţĥé ṽàļüé ţĥŕöüĝĥIntl, îñ ţĥé àçţîṽé ļöçàļé, àš þàŕţ öƒ ŕéšöļṽîñĝ ţĥé šţŕîñĝ. Ţĥé ţŕàñšļàţöŕ çöñţŕöļš îţ; ñö çöđé çĥàñĝé. - Öüţšîđé à ţŕàñšļàţéđ šţŕîñĝ: à þŕîçé îñ à ţàƃļé çéļļ, à ţîḿéšţàḿþ îñ à ļöĝ ṽîéŵ. Ţĥàţ'š ýöüŕ çöđé'š ĵöƃ. neokapi-î18ñ ĝîṽéš ýöü ţĥé ļöçàļé; ýöü ƃŕîñĝ ţĥé ƒöŕḿàţţéŕ.
Ƒöŕḿàţţîñĝ îñšîđé ţŕàñšļàţéđ šţŕîñĝš
À þļàçéĥöļđéŕ çàñ çàŕŕý àñ ÎÇÜ ƒöŕḿàţ, àñđ ţĥé ŕüñţîḿé ŕéšöļṽéš îţ àĝàîñšţ ţĥé àçţîṽé ļöçàļé:
<p>You have {count} unread messages.</p>
Ţĥé éẋţŕàçţéđ ţéḿþļàţé îš You have {count} unread messages., à ƃàŕé þļàçéĥöļđéŕ, îñţéŕþöļàţéđ ṽéŕƃàţîḿ. Ƃüţ à ţŕàñšļàţöŕ çàñ üþĝŕàđé îţ îñ ţĥé ţàŕĝéţ, ƃéçàüšé ţĥé ƒöŕḿàţ ļîṽéš îñ ţĥé šţŕîñĝ, ñöţ ţĥé šöüŕçé:
Du hast {count, number} ungelesene Nachrichten.
Ñöŵ 1234 ŕéñđéŕš àš 1.234 îñ Ĝéŕḿàñ àñđ 1,234 îñ Éñĝļîšĥ, àñđ ñöƃöđý ţöüçĥéđ ţĥé ĴŠẊ. Ţĥé šüþþöŕţéđ ƒöŕḿàţš:
| Ƒöŕḿàţ | Éẋàḿþļé îñþüţ | en | de |
|---|---|---|---|
{n, number} | 1234.5 | 1,234.5 | 1.234,5 |
{n, number, integer} | 1234.5 | 1,235 | 1.235 |
{n, number, percent} | 0.182 | 18% | 18 % |
{n, number, currency/EUR} | 1234.5 | €1,234.50 | 1.234,50 € |
{d, date} | à Date | Jul 11, 2026 | 11.07.2026 |
{d, date, short|medium|long|full} | à Date | 7/11/26 … | 11.07.26 … |
{t, time} | à Date | 2:30:00 PM | 14:30:00 |
{t, time, short|medium|long|full} | à Date | 2:30 PM … | 14:30 … |
Îñšîđé à <Plural> ƃŕàñçĥ, # îš ţĥé çöüñţ, ƒöŕḿàţţéđ ţĥé šàḿé ŵàý: # îñ Ĝéŕḿàñ ĝîṽéš 1.234.
Þàšš Date öƃĵéçţš àñđ ñüḿƃéŕš ţĥŕöüĝĥ àš-îš; ţĥé ŕüñţîḿé đöéš ţĥé çöñṽéŕšîöñ:
<p>Last synced {when}, {count} items.</p> // when: Date, count: number
Ţĥîš çöšţš ñö éẋţŕà ƃüñđļé: îţ'š ţĥé šàḿé Intl ţĥé þļüŕàļ ŕéšöļṽéŕ àļŕéàđý üšéš.
Ƒöŕḿàţţîñĝ öüţšîđé ţŕàñšļàţéđ šţŕîñĝš: ţĥé îñţéĝŕàţîöñ šüŕƒàçé
Éṽéŕý ļöçàļé-àŵàŕé ļîƃŕàŕý öñ ţĥé þļàţƒöŕḿ ţàķéš à ƂÇÞ-47 ļöçàļé šţŕîñĝ, ţĥé šàḿé šĥàþé neokapi-î18ñ ţŕàçķš îñţéŕñàļļý. Þüļļ îţ öüţ ŕéàçţîṽéļý ṽîà useNeokapi():
import { useNeokapi } from "@neokapi/i18n-react/runtime";
function Price({ amount, currency }: { amount: number; currency: string }) {
const { locale } = useNeokapi();
return (
<span>{new Intl.NumberFormat(locale, { style: "currency", currency }).format(amount)}</span>
);
}
Ŵĥéñ loadTranslations() šŵàþš ţĥé đîçţ, useNeokapi() ƒîŕéš à ŕé-ŕéñđéŕ ŵîţĥ ţĥé ñéŵ ļöçàļé šţŕîñĝ. Ƒöŕḿàţţéŕš þîçķ îţ üþ öñ ţĥé ñéẋţ ŕéñđéŕ. Ýöü đöñ'ţ ñééđ ţö þļüḿƃ ļöçàļé ţĥŕöüĝĥ þŕöþš öŕ çöñţéẋţ; ţĥé ĥööķ îš ţĥé ƃöüñđàŕý.
Šţàŕţ ŵîţĥ ñàţîṽé Intl
Intl.* šĥîþš îñ éṽéŕý ḿöđéŕñ ŕüñţîḿé (ƃŕöŵšéŕ + Ñöđé ≥ 18) ŵîţĥ ƒüļļ ÇĻĐŔ đàţà. Žéŕö ƃüñđļé çöšţ, éẋçéļļéñţ ŢýþéŠçŕîþţ ţýþéš, ƒàšţ. Çöṽéŕš ţĥé çöḿḿöñ çàšéš:
// Currency
new Intl.NumberFormat(locale, { style: "currency", currency: "USD" }).format(1234.56);
// "$1,234.56" / "1.234,56 $"
// Percent
new Intl.NumberFormat(locale, { style: "percent", maximumFractionDigits: 1 }).format(0.1824);
// "18.2%" / "18,2 %"
// Date
new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(new Date());
// "Apr 22, 2026" / "22. Apr. 2026"
// Date + time
new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "short" }).format(new Date());
// "4/22/26, 6:30 PM" / "22.04.26, 18:30"
// Relative time
new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(-2, "day");
// "2 days ago" / "vor 2 Tagen"
// List
new Intl.ListFormat(locale, { style: "long", type: "conjunction" }).format([
"apples",
"oranges",
"bananas",
]);
// "apples, oranges, and bananas"
// Unit
new Intl.NumberFormat(locale, { style: "unit", unit: "kilometer-per-hour" }).format(80);
// "80 km/h"
// Compact
new Intl.NumberFormat(locale, { notation: "compact", compactDisplay: "short" }).format(1_250_000);
// "1.3M" / "1,3 Mio."
Ţĥŕéé Îñţļ ÀÞΚ ţĥàţ ḿàţţéŕ ƒöŕ öţĥéŕ šüƃšýšţéḿš:
Intl.PluralRules: àļŕéàđý üšéđ îñţéŕñàļļý ƃý neokapi-î18ñ'š<Plural>çöḿþöñéñţ. Ýöü đöñ'ţ ñééđ à ţĥîŕđ-þàŕţý þļüŕàļîžéŕ.Intl.Collator: ļöçàļé-çöŕŕéçţ šţŕîñĝ çöḿþàŕîšöñ. Üšé ƒöŕ šöŕţîñĝ ļîšţš öƒ ţŕàñšļàţéđ ñàḿéš (items.sort((a, b) => new Intl.Collator(locale).compare(a.name, b.name))).Intl.Segmenter: ŵöŕđ / šéñţéñçé / ĝŕàþĥéḿé ƃöüñđàŕîéš (üšéƒüļ ŵĥéñ ýöü ŵàñţ ţö çüţ à ļàƃéļ ḿîđ-ŵöŕđ çöŕŕéçţļý îñ ÇĴĶ).
Ŕéüšàƃļé ƒöŕḿàţţéŕ ĥööķš
Ŕé-çŕéàţîñĝ ƒöŕḿàţţéŕš öñ éṽéŕý ŕéñđéŕ îš ƒîñé (ţĥéý'ŕé çĥéàþ), ƃüţ ḿéḿöîžîñĝ îš çļéàñéŕ, àñđ ļéţš ýöü šĥàŕé çöñƒîĝüŕàţîöñ àçŕöšš çöḿþöñéñţš. À ţîñý ŵŕàþþéŕ:
import { useMemo } from "react";
import { useNeokapi } from "@neokapi/i18n-react/runtime";
export function useCurrency(currency: string) {
const { locale } = useNeokapi();
return useMemo(
() => new Intl.NumberFormat(locale, { style: "currency", currency }),
[locale, currency],
);
}
export function useDateFormat(options: Intl.DateTimeFormatOptions = { dateStyle: "medium" }) {
const { locale } = useNeokapi();
// Stringify options once so useMemo deps stay stable for callers
// passing a fresh object literal each render.
const key = JSON.stringify(options);
return useMemo(() => new Intl.DateTimeFormat(locale, options), [locale, key]);
}
Üšàĝé:
function Cart() {
const currency = useCurrency("EUR");
const date = useDateFormat({ dateStyle: "short" });
return (
<footer>
Subtotal: {currency.format(subtotal)}
Delivered: {date.format(deliveryDate)}
</footer>
);
}
Ţĥîŕđ-þàŕţý ļîƃŕàŕîéš
Ŵĥéñ Îñţļ đöéšñ'ţ çöṽéŕ ŵĥàţ ýöü ñééđ, ţĥé îđîöḿ îš ţĥé šàḿé: const { locale } = useNeokapi() → ḿàþ ţö ţĥé ļîƃŕàŕý'š ļöçàļé ţýþé → þàšš îñ.
đàţé-ƒñš
đàţé-ƒñš ļöçàļéš àŕé éẋþļîçîţ îḿþöŕţš. Ķééþ à šḿàļļ ḿàþ:
import { formatDistance, format } from "date-fns";
import { enUS, de, fr, es, ja } from "date-fns/locale";
import type { Locale } from "date-fns";
const DATE_FNS_LOCALES: Record<string, Locale> = {
en: enUS,
de,
fr,
es,
ja,
};
function useDateFnsLocale(): Locale {
const { locale } = useNeokapi();
const primary = locale.split("-")[0];
return DATE_FNS_LOCALES[primary] ?? enUS;
}
function Ago({ date }: { date: Date }) {
const dfl = useDateFnsLocale();
return <time>{formatDistance(date, new Date(), { addSuffix: true, locale: dfl })}</time>;
}
Ƒöŕ ţîḿéžöñé-àŵàŕé ƒöŕḿàţţîñĝ, þàîŕ ŵîţĥ date-fns-tz.
Ļüẋöñ
Ļüẋöñ šþéàķš ƂÇÞ-47 ñàţîṽéļý (îţ'š àñ Îñţļ ŵŕàþþéŕ üñđéŕñéàţĥ) àñđ šüþþöŕţš ţîḿéžöñéš ƒîŕšţ-çļàšš.
import { DateTime } from "luxon";
function LocalTime({ iso, zone }: { iso: string; zone: string }) {
const { locale } = useNeokapi();
const dt = DateTime.fromISO(iso, { zone }).setLocale(locale);
return <time>{dt.toLocaleString(DateTime.DATETIME_MED)}</time>;
}
đàýĵš
import dayjs from "dayjs";
import "dayjs/locale/de";
import "dayjs/locale/fr";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
function Ago({ iso }: { iso: string }) {
const { locale } = useNeokapi();
return <time>{dayjs(iso).locale(locale).fromNow()}</time>;
}
Šîđé-郃éçţ îḿþöŕţš ŕéĝîšţéŕ ļöçàļé đàţà; öñé þéŕ ļöçàļé ýöü šĥîþ.
ƑöŕḿàţĴŠ / ŕéàçţ-îñţļ
ƑöŕḿàţĴŠ îš à ƒüļļ-ƒéàţüŕéđ ÎÇÜ ḾéššàĝéƑöŕḿàţ šţàçķ. ΃ ýöü'ŕé àļŕéàđý öñ îţ, neokapi-î18ñ àñđ ƑöŕḿàţĴŠ çàñ çöéẋîšţ: üšé ƑöŕḿàţĴŠ ƒöŕ ƒöŕḿàţţîñĝ àñđ neokapi-î18ñ ƒöŕ éẋţŕàçţîöñ + ţŕàñšļàţîöñ. Ƃüţ ýöü'ļļ ĥàṽé ţŵö šýšţéḿš ţŕàçķîñĝ ļöçàļé: ŵîŕé currentLocale îñţö ƑöŕḿàţĴŠ'š IntlProvider:
import { IntlProvider } from "react-intl";
import { useNeokapi } from "@neokapi/i18n-react/runtime";
function I18nRoot({ children }) {
const { locale } = useNeokapi();
return (
<IntlProvider locale={locale} defaultLocale="en" messages={{}}>
{children}
</IntlProvider>
);
}
Ƒöŕ ĝŕééñƒîéļđ àþþš: šţîçķ ŵîţĥ Îñţļ. ƑöŕḿàţĴŠ šĥîþš îţš öŵñ ŕüñţîḿé ƒöŕ ƒéàţüŕéš neokapi-î18ñ àļŕéàđý ĥàñđļéš (þļüŕàļš, šéļéçţ, ḿéššàĝé îñţéŕþöļàţîöñ, àñđ ñüḿƃéŕ/đàţé/ţîḿé ƒöŕḿàţţîñĝ îñšîđé šţŕîñĝš) þļüš šöḿé îţ đöéšñ'ţ (ŵĥîçĥ Îñţļ öƒţéñ çöṽéŕš).
Ļîƃŕàŕý þîçķéŕ
| Ñééđ | Þîçķ | Ñöţéš |
|---|---|---|
| Çüŕŕéñçý, þéŕçéñţ, đàţé, ţîḿé, ŕéļàţîṽé ţîḿé, ļîšţ, üñîţ, çöḿþàçţ ñüḿƃéŕš | Intl.* | Àļŕéàđý îñ ţĥé ŕüñţîḿé. Ñö îḿþöŕţš. |
| Þļüŕàļîžàţîöñ (çöüñţ-àŵàŕé çöþý) | neokapi-î18ñ'š <Plural> | Üšéš Intl.PluralRules. Ñö éẋţŕà ļîƃŕàŕý. |
| Šöŕţîñĝ ţŕàñšļàţéđ ñàḿéš | Intl.Collator | list.sort((a,b) => col.compare(a,b)) |
| Ţîḿéžöñé-àŵàŕé đàţéš, ĥéàṽý đàţé ḿàţĥ | Ļüẋöñ öŕ đàţé-ƒñš(-ţž) | Ļüẋöñ îš Îñţļ-ƃàšéđ; đàţé-ƒñš îḿþöŕţš þéŕ ƒüñçţîöñ. |
| Đüŕàţîöñ ƒöŕḿàţţîñĝ ("3ĥ 12ḿ") | Ļüẋöñ Duration.toHuman() öŕ @formatjs/intl-durationformat þöļýƒîļļ | Intl.DurationFormat éẋîšţš îñ ñéŵéŕ ŕüñţîḿéš ƃüţ îšñ'ţ üñîṽéŕšàļļý šĥîþþéđ ýéţ. |
| Ļéĝàçý ḿöḿéñţ.ĵš çöđéƃàšé | ḿîĝŕàţé îñçŕéḿéñţàļļý | ḿöḿéñţ.ĵš îš îñ ḿàîñţéñàñçé ḿöđé; Ļüẋöñ àñđ đàţé-ƒñš àŕé ţĥé üšüàļ ḿîĝŕàţîöñ ţàŕĝéţš. |
| Ñüḿƃéŕ/đàţé/ţîḿé îñšîđé à ţŕàñšļàţéđ šţŕîñĝ | ÎÇÜ îñ ţĥé šţŕîñĝ: {n, number}, {d, date, long} | Ƃüîļţ îñ; šéé ţĥé ţöþ öƒ ţĥîš þàĝé. Ñö ļîƃŕàŕý. |
| ÎÇÜ ḾéššàĝéƑöŕḿàţ ƃéýöñđ ţĥàţ šüƃšéţ (öŕđîñàļš, üñîţ šķéļéţöñš, …) | @formatjs/intl-messageformat šţàñđàļöñé | Ĵüšţ ţĥé ƒöŕḿàţţéŕ, ñöţ ţĥé ŵĥöļé ŕéàçţ-îñţļ šţàçķ. |
Îñîţîàļ ŕéñđéŕ àñđ ŠŠŔ
Àļļ öƒ ţĥé àƃöṽé ŕéàđ ţĥé ļöçàļé àţ ŕéñđéŕ ţîḿé. Öñ ƒîŕšţ þàîñţ (ƃéƒöŕé loadTranslations() ŕéšöļṽéš), useNeokapi() ŕéţüŕñš ţĥé đéƒàüļţ ļöçàļé ("" üñļéšš ýöü þŕé-çàļļéđ setTranslations). Ţĥàţ üšüàļļý ḿàþš ţö Éñĝļîšĥ ƒàļļƃàçķ ƒöŕḿàţţîñĝ, ŵĥîçĥ ḿàţçĥéš ţĥé Éñĝļîšĥ šöüŕçé ţéẋţ ţĥé àþþ ŕéñđéŕš ƃéƒöŕé ţŕàñšļàţîöñš àŕŕîṽé. ΃ ţĥé ƒļîçķéŕ ḿàţţéŕš, šééđ ţĥé ļöçàļé öñ ţĥé šéŕṽéŕ šîđé:
// On the server, before hydration
setTranslations(cookieLocale, {}); // empty dict; locale alone is enough
Ñöŵ ţĥé ƒîŕšţ çļîéñţ ŕéñđéŕ ĥàþþéñš ŵîţĥ ţĥé ŕîĝĥţ ļöçàļé, Îñţļ ƒöŕḿàţţéŕš ḿàţçĥ, àñđ ţĥé đîçţ šŵàþ çĥàñĝéš šţŕîñĝš öñļý àñđ ļéàṽéš ƒöŕḿàţţîñĝ àļöñé.
Šéé àļšö Çöñƒîĝüŕàţîöñ → ĤŢḾĻ lang àñđ dir àţţŕîƃüţéš ƒöŕ ķééþîñĝ ţĥé đöçüḿéñţ ļöçàļé îñ šýñç öñ ƒîŕšţ þàîñţ.
Ŵĥàţ neokapi-î18ñ đéļîƃéŕàţéļý đöéšñ'ţ đö
- Ñüḿƃéŕ îñþüţ þàŕšîñĝ. Þàŕšîñĝ
"1.234,56 €"ƃàçķ îñţö1234.56îš ļöçàļé-đéþéñđéñţ àñđ ñöñ-ţŕîṽîàļ. Üšé à ƒöŕḿ ļîƃŕàŕý ŵîţĥ à ļöçàļé-àŵàŕé îñþüţ (react-number-formatĥàš ļöçàļé šüþþöŕţ) öŕ ŵŕîţé à šḿàļļ þàŕšéŕ þéŕ îñþüţ šĥàþé. - Üñîţ çöñṽéŕšîöñ. Îñţļ ƒöŕḿàţš "1 ķḿ"; çöñṽéŕţîñĝ 1 ķḿ ţö ḿîļéš îš ýöüŕ àþþ'š ŕéšþöñšîƃîļîţý.
- Àđđŕéšš / þĥöñé / þöšţàļ çöđé ƒöŕḿàţţîñĝ. Üšé à šþéçîàļîžéđ ļîƃŕàŕý (
libphonenumber-js,libpostal).
Ţĥéšé àŕéñ'ţ î18ñ çöñçéŕñš šö ḿüçĥ àš đàţà ñöŕḿàļîžàţîöñ: ţĥéý ñééđ đöḿàîñ ļöĝîç neokapi-î18ñ ĥàš ñö ƃüšîñéšš îñ.
Ñéẋţ
- Þļüŕàļš àñđ šéļéçţ: ţĥé öñé ƒöŕḿàţţîñĝ çàšé neokapi-î18ñ đöéš öŵñ, ƃéçàüšé îţ'š îñţéŕţŵîñéđ ŵîţĥ ţĥé ţŕàñšļàţéđ šţŕîñĝ îţšéļƒ.
t()éšçàþé ĥàţçĥ: ƒééđîñĝ ƒöŕḿàţţéđ ṽàļüéš îñţö ţŕàñšļàţéđ çöþý ṽîà þļàçéĥöļđéŕš:t("Price: {price}", { price: currencyFormatter.format(amount) }).- Çöñƒîĝüŕàţîöñ: ŕüñţîḿé öþţîöñš, îñçļüđîñĝ ţĥé
<html lang>/diršýñç.