API
The functions and types @k8ordo/i18n exports. There is one entry point, @k8ordo/i18n, and both Server Component and Client Component modules import from it.
On this page
defineLocales
Import from @k8ordo/i18n
Defines the locale set. Everything that depends on the list of locales reads it from what this returns.
defineLocales(
definitions: Record<string, LocaleDefinition>,
options?: LocalesOptions<D>,
): Locales<L, D>Parameters
definitionsRecord<string, LocaleDefinition>- An object keyed by locale tag, each value that locale’s
timeZoneanddir. Tags are BCP 47, and the order written is the order ofall. optionsLocalesOptions<D>defaultpicks the default locale; without it, the locale written first is the default.
Returns
Locales<L, D> — The set: the list, and everything made from it.
Caveats
- An empty set and a
defaultoutside the list throw aTypeErrorwheredefineLocalesis called. So do a tag that is not BCP 47, a time zone the runtime does not know, a missingtimeZone, and adirother thanltrorrtl. - The set is registered on
globalThis, wheremessagereads the default from. The last set defined wins, so call it once per app. - The keys are inferred as literal types, so no
as constis needed.
i18n.tsexport const locales = defineLocales(
{
ja: { timeZone: 'Asia/Tokyo', dir: 'ltr' },
en: { timeZone: 'America/New_York', dir: 'ltr' },
},
{ default: 'en' },
);Locales
Import from @k8ordo/i18n
The set defineLocales returns. L is the union of its tags, and D the default among them.
type Locales<L extends string, D extends L> =
IntlFormats & {
all: readonly L[];
definitions: Readonly<Record<L, LocaleDefinition>>;
default: D;
is: (value: unknown) => value is L;
negotiate: (requested: Iterable<string>) => L;
negotiateRequest: (
request: Request,
options?: NegotiateRequestOptions,
) => L;
localize: (pathname: string, locale: L) => string;
delocalize: (pathname: string) => Delocalized<L>;
paths: (patterns: readonly string[]) => string[];
paramsSchema: LocaleParamsSchema<L>;
getLocale: () => L;
run: <T>(locale: L, fn: () => T) => T;
};Fields
allreadonly L[]- The tags, in the order written.
definitionsReadonly<Record<L, LocaleDefinition>>- Each locale’s
{ timeZone, dir }: the object given todefineLocales, as it is. defaultD- The default locale, used when nothing names a locale and when negotiation matches none.
is(value: unknown) => value is L- Membership, as a type guard. It is case-sensitive:
is('EN')isfalse. negotiate(requested) => L- Picks one locale of the set from a list of preferences. Each tag is tried exactly, then by language; nothing matching by the end is the default.
negotiateRequest(request, options?) => L- Picks a locale for a
Request: the cookie named byoptions.cookiefirst, thenAccept-Language. localize(pathname, locale) => string- Puts a locale segment in front of a pathname:
'/ui'becomes'/en/ui', and'/'becomes'/en'. delocalize(pathname) => Delocalized<L>- Takes the leading locale segment off. When the first segment is no locale,
localeisnull. paths(patterns) => string[]@k8ordo/static’spathsoption: every pattern with a/:localesegment, once per locale.paramsSchemaLocaleParamsSchema<L>- The
[locale]segment’s schema. On the server, the locale it accepts becomes the locale of that page’s render. getLocale() => L- The locale of the render in progress: on the server the one accepted, in the browser the first segment of the URL, and the default when neither names one.
run(locale, fn) => T- Runs
fn, and everything it starts, underlocale, and returns whatfnreturns. Server only. dateTimeFormat …IntlFormats- The members that return
Intlobjects for the current locale; seeIntlFormatsbelow.
Caveats
- No member uses
this, so any can be taken out of the set, as inexport const { getLocale } = locales. - Called in a browser,
runthrowslocales.run: in the browser the URL is the locale — navigate instead. - On a server runtime with no
AsyncLocalStorageto offer,runthrows, and so doesparamsSchemawhen it accepts a locale.
IntlFormats
Import from @k8ordo/i18n
The members of the set that return Intl objects for the current locale — the Intl objects themselves.
type IntlFormats = {
dateTimeFormat: (
options?: LocaleDateTimeFormatOptions,
) => Intl.DateTimeFormat;
numberFormat: (
options?: Intl.NumberFormatOptions,
) => Intl.NumberFormat;
relativeTimeFormat: (
options?: Intl.RelativeTimeFormatOptions,
) => Intl.RelativeTimeFormat;
pluralRules: (
options?: Intl.PluralRulesOptions,
) => Intl.PluralRules;
listFormat: (options?: Intl.ListFormatOptions) => Intl.ListFormat;
};Fields
dateTimeFormat(options?) => Intl.DateTimeFormatIntl.DateTimeFormatin the current locale and that locale’stimeZone.numberFormat(options?) => Intl.NumberFormatIntl.NumberFormatin the current locale.relativeTimeFormat(options?) => Intl.RelativeTimeFormatIntl.RelativeTimeFormatin the current locale.pluralRules(options?) => Intl.PluralRulesIntl.PluralRulesin the current locale.listFormat(options?) => Intl.ListFormatIntl.ListFormatin the current locale.
Caveats
- One is made per locale and options and returned again after that. Options are told apart by their JSON.
- None is a hook, so each can be called in a Server Component, a Client Component, or inside a message’s function.
message
Import from @k8ordo/i18n
Declares one message with its text in every locale. The function it returns gives the text in the locale where it is called.
function message(variants: Variants<string>): Message;
function message<A extends readonly unknown[]>(
variants: Variants<(...args: A) => string>,
): Message<A>;Parameters
variantsVariants<string> | Variants<(...args: A) => string>- The text per locale: text in every locale, or a function of the same arguments in every locale.
Returns
Message<A> — A function that takes the arguments and returns the text in the current locale.
Caveats
- With
Registermerged, a declaration missing a locale does not compile. A gap that gets past the types throws aTypeErrorsuch asmessage: no text for "en" in ["ja"]where the message is read. - When nothing names a locale, it returns the default locale’s text — or, where no set has been defined in this environment yet, the first text written.
- The argument types come from the locale you annotate. With no annotation anywhere they are
unknown, so annotate one, or pass them as a type argument:message<[name: string]>({ … }). - The declaration has no side effect, so a bundler drops a message nothing calls.
messages/nav.tsexport const home = message({ ja: 'ホーム', en: 'Home' });
export const greeting = message({
ja: (name: string) => `こんにちは、${name}さん`,
en: (name) => `Hello, ${name}`,
});Message
Import from @k8ordo/i18n
The type of what message returns. A component that takes a message as a prop takes this type.
type Message<A extends readonly unknown[] = []> = (
...args: A
) => string;Caveats
- Being a function, it cannot be a prop from a Server Component to a Client Component; pass the string you get by calling it there.
parseAcceptLanguage
Import from @k8ordo/i18n
Turns an Accept-Language header into a list of preferences for negotiate.
parseAcceptLanguage(header: string | null): string[]Parameters
headerstring | null- The header’s value;
request.headers.get('accept-language')goes in as it is.
Returns
string[] — The tags, highest weight first; an empty array for a missing or blank header.
Caveats
- Tags of equal weight keep the header’s order. Tags weighted 0 or less, and
*, are dropped. - The tags themselves are not checked;
negotiateskips the ones that are not BCP 47.
parseAcceptLanguage('en-US;q=0.8, ja, en;q=0.9');
// ['ja', 'en', 'en-US']currentLocale
Import from @k8ordo/i18n
The current locale, as the app’s set resolves it — for a library that renders inside an app it knows nothing about.
currentLocale(): RegisteredLocale | nullReturns
RegisteredLocale | null — The locale named, else the set’s default; null where no set has been defined in this environment.
Caveats
@k8ordo/uipicks the locale of its built-in text with this.- App code uses
locales.getLocale(), which is typed by the set.
Register
Import from @k8ordo/i18n
How the app tells the package its locales’ type. Merge it once per app.
declare module '@k8ordo/i18n' {
interface Register {
locale: LocaleOf<typeof locales>;
}
}Caveats
- It exists to be merged, so it is an
interface, not atype. - Until it is merged,
RegisteredLocaleisstring, and a missing locale is no type error.
RegisteredLocale
Import from @k8ordo/i18n
The union of the locales in Register; string until it is merged.
type RegisteredLocale = Register extends {
locale: infer L extends string;
}
? L
: string;Variants
Import from @k8ordo/i18n
An object with one value per locale in Register. It is also what message takes.
type Variants<V> = Readonly<Record<RegisteredLocale, V>>;Caveats
- It also fits values other than messages that every locale must have, such as the names a language switcher lists.
LocaleOf
Import from @k8ordo/i18n
The union of a set’s tags. It is also what Register’s locale is set to.
type LocaleOf<Ls> =
Ls extends Locales<infer L, infer _D> ? L : never;LocaleDefinition
Import from @k8ordo/i18n
What each locale states besides its tag. Neither is derived from the runtime.
type LocaleDefinition = {
timeZone: string;
dir: 'ltr' | 'rtl';
};Fields
timeZonestring- The IANA time zone the locale’s dates are shown in — the same on the server and in every browser, so a date reads the same on both.
dir'ltr' | 'rtl'- The direction the locale’s text runs in, for
<html dir>.
LocalesOptions
Import from @k8ordo/i18n
The type of defineLocales’ second argument.
type LocalesOptions<D extends string> = { default?: D };Fields
defaultD- The default locale. It has to be one of the list; without it, the first one written.
NegotiateRequestOptions
Import from @k8ordo/i18n
The type of negotiateRequest’s second argument.
type NegotiateRequestOptions = { cookie?: string };Fields
cookiestring- The name of the cookie holding the visitor’s choice. Without it, only
Accept-Languageis read.
Delocalized
Import from @k8ordo/i18n
What delocalize returns.
type Delocalized<L extends string> = {
locale: L | null;
pathname: string;
};Fields
localeL | null- The locale the first segment names, or
nullwhen it names none. pathnamestring- The pathname with the segment taken off;
/when there was nothing else.
LocaleParamsSchema
Import from @k8ordo/i18n
The type of paramsSchema: the Standard Schema shape, declared by this package itself so it depends on no schema library.
type LocaleParamsSchema<L extends string> = {
'~standard': {
version: 1;
vendor: '@k8ordo/i18n';
validate: (value: unknown) =>
| { value: { locale: L } }
| { issues: { message: string; path: ['locale'] }[] };
};
};Caveats
- Validation is synchronous. A locale outside the list gets an issue at
path: ['locale']. - The accepted value is
{ locale }alone; other params keep their strings for the schemas that follow.
LocaleDateTimeFormatOptions
Import from @k8ordo/i18n
The options of dateTimeFormat: Intl.DateTimeFormatOptions without timeZone.
type LocaleDateTimeFormatOptions = Intl.DateTimeFormatOptions & {
timeZone?: never;
};Caveats
- A
timeZoneforced through withasis overridden by the locale’s.