Ƒöŕḿàţţîñĝ đàţéš, ñüḿƃéŕš, çüŕŕéñçý
À ñüḿƃéŕ, đàţé öŕ çüŕŕéñçý îš ƒöŕḿàţţéđ îñ öñé öƒ ţŵö þļàçéš, àñđ ţĥé þļàçé đéçîđéš ŵĥö çöñţŕöļš ţĥé ƒöŕḿàţ:
- Îñšîđé à ţŕàñšļàţéđ šţŕîñĝ:
You have {n, number} unread messages. Ţĥé ŕüñţîḿé ƒöŕḿàţš ţĥé ṽàļüé ţĥŕöüĝĥIntl, îñ ţĥé àçţîṽé ļöçàļé, àš þàŕţ öƒ ŕéšöļṽîñĝ ţĥé šţŕîñĝ. Ţĥé ţŕàñšļàţöŕ çöñţŕöļš ţĥé ƒöŕḿàţ, ŵîţĥ ñö çöđé çĥàñĝé. - Öüţšîđé à ţŕàñšļàţéđ šţŕîñĝ: à þŕîçé îñ à ţàƃļé çéļļ, à ţîḿéšţàḿþ îñ à ļöĝ ṽîéŵ. Ýöüŕ çöđé ƒöŕḿàţš îţ: ñéöķàþî-î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 ļöçàļé šţŕîñĝ, ţĥé ƒöŕḿ ñéöķàþî-î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 ÀÞΚ àŕé ŕéļéṽàñţ ţö ţŕàñšļàţéđ ÜÎ:
Intl.PluralRules: àļŕéàđý üšéđ îñţéŕñàļļý ƃý ñéöķàþî-î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>
);
}
Ţĥîŕđ-þàŕţý ļîƃŕàŕîéš
Ŵĥéñ Intl đöéšñ'ţ çöṽéŕ ŵĥàţ ýöü ñééđ, ţĥé þàţţéŕñ îš ţĥé šàḿé: 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.
Ļüẋöñ
Ļüẋöñ ŵŕàþš Intl, šö îţ ţàķéš ƂÇÞ-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>;
}
Éàçĥ šîđé-郃éçţ îḿþöŕţ ŕéĝîšţéŕš ţĥé đàţà ƒöŕ öñé ļöçàļé, šö àđđ öñé þéŕ ļöçàļé ýöü šĥîþ.
ƑöŕḿàţĴŠ / ŕéàçţ-îñţļ
ƑöŕḿàţĴŠ îš à ƒüļļ ÎÇÜ ḾéššàĝéƑöŕḿàţ šţàçķ. ΃ ýöü àļŕéàđý üšé îţ, ñéöķàþî-î18ñ àñđ ƑöŕḿàţĴŠ çàñ çöéẋîšţ: ƑöŕḿàţĴŠ ƒöŕḿàţš, àñđ ñéöķàþî-î18ñ éẋţŕàçţš àñđ ţŕàñšļàţéš. Ƃöţĥ ţĥéñ ţŕàçķ ţĥé ļöçàļé, šö þàšš ñéöķàþî-î18ñ'š locale îñţö ƑöŕḿàţĴŠ'š 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>
);
}
Ƒöŕ à ñéŵ àþþ, üšé Intl. ƑöŕḿàţĴŠ šĥîþš îţš öŵñ ŕüñţîḿé ƒöŕ ƒéàţüŕéš ñéöķàþî-î18ñ àļŕéàđý ĥàñđļéš (þļüŕàļš, šéļéçţ, ḿéššàĝé îñţéŕþöļàţîöñ, àñđ ñüḿƃéŕ/đàţé/ţîḿé ƒöŕḿàţţîñĝ îñšîđé šţŕîñĝš), àñđ Intl çöṽéŕš ḿöšţ öƒ ţĥé ŕéšţ.
Ļîƃŕàŕý þîçķéŕ
| Ñééđ | Þîçķ | Ñöţéš |
|---|---|---|
| Çüŕŕéñçý, þéŕçéñţ, đàţé, ţîḿé, ŕéļàţîṽé ţîḿé, ļîšţ, üñîţ, çöḿþàçţ ñüḿƃéŕš | Intl.* | Àļŕéàđý îñ ţĥé ŕüñţîḿé. Ñö îḿþöŕţš. |
| Þļüŕàļîžàţîöñ (çöüñţ-àŵàŕé çöþý) | ñéöķàþî-î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
Ţĥé ƒîŕšţ çļîéñţ ŕéñđéŕ ţĥéñ üšéš ţĥé ŕîĝĥţ ļöçàļé, ţĥé Intl ƒöŕḿàţţéŕš ḿàţçĥ, àñđ ţĥé đîçţ šŵàþ çĥàñĝéš öñļý ţĥé šţŕîñĝš.
Šéé àļšö Çöñƒîĝüŕàţîöñ → ĤŢḾĻ lang àñđ dir àţţŕîƃüţéš ƒöŕ ķééþîñĝ ţĥé đöçüḿéñţ ļöçàļé îñ šýñç öñ ƒîŕšţ þàîñţ.
Öüţšîđé ñéöķàþî-î18ñ'š šçöþé
- Ñüḿƃéŕ îñþüţ þàŕšîñĝ. Þàŕšîñĝ
"1.234,56 €"ƃàçķ îñţö1234.56îš ļöçàļé-đéþéñđéñţ àñđ ñöñ-ţŕîṽîàļ. Üšé à ƒöŕḿ ļîƃŕàŕý ŵîţĥ à ļöçàļé-àŵàŕé îñþüţ (react-number-formatĥàš ļöçàļé šüþþöŕţ) öŕ ŵŕîţé à šḿàļļ þàŕšéŕ þéŕ îñþüţ šĥàþé. - Üñîţ çöñṽéŕšîöñ. Îñţļ ƒöŕḿàţš "1 ķḿ"; çöñṽéŕţîñĝ 1 ķḿ ţö ḿîļéš îš ýöüŕ àþþ'š ŕéšþöñšîƃîļîţý.
- Àđđŕéšš / þĥöñé / þöšţàļ çöđé ƒöŕḿàţţîñĝ. Üšé à šþéçîàļîžéđ ļîƃŕàŕý (
libphonenumber-js,libpostal).
Éàçĥ öƒ ţĥéšé îš đàţà ñöŕḿàļîžàţîöñ àñđ ñééđš đöḿàîñ ļöĝîç, ŵĥîçĥ ƃéļöñĝš îñ ýöüŕ àþþ öŕ à šþéçîàļîžéđ ļîƃŕàŕý.
Ŕéļàţéđ
- Þļüŕàļš àñđ šéļéçţ: çöüñţ-àŵàŕé ţéẋţ, ţĥé ƒöŕḿàţţîñĝ çàšé ţĥàţ îš þàŕţ öƒ ţĥé ţŕàñšļàţéđ šţŕîñĝ.
t()éšçàþé ĥàţçĥ: ƒééđîñĝ ƒöŕḿàţţéđ ṽàļüéš îñţö ţŕàñšļàţéđ çöþý ṽîà þļàçéĥöļđéŕš:t("Price: {price}", { price: currencyFormatter.format(amount) }).- Çöñƒîĝüŕàţîöñ: ŕüñţîḿé öþţîöñš, îñçļüđîñĝ ţĥé
<html lang>/diršýñç.