initial commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/* ============================================================================
|
||||
I18N HOOKS
|
||||
----------------------------------------------------------------------------
|
||||
The locale is a property of the URL, never of a cookie or of client state,
|
||||
so every hook here is derived from the router - which means SSR and the
|
||||
first client render agree by construction and hydration never mismatches.
|
||||
|
||||
`useParams().lang` is the fast path inside a page route. The root layout and
|
||||
the root ErrorBoundary sit ABOVE the ":lang" segment, where that param does
|
||||
not exist, so the pathname is the fallback and both are correct.
|
||||
========================================================================= */
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useLocation, useMatches, useParams } from "react-router";
|
||||
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
dirFor,
|
||||
getLocaleFromPathname,
|
||||
isLocale,
|
||||
type Direction,
|
||||
type Locale,
|
||||
} from "./config";
|
||||
import { alternatePath, localizedPath, sectionPath } from "./paths";
|
||||
import type { RouteSlugKey } from "./paths";
|
||||
import {
|
||||
createTranslator,
|
||||
getMessages,
|
||||
type Messages,
|
||||
type Translator,
|
||||
} from "./t";
|
||||
|
||||
/** The locale of the page being rendered. */
|
||||
export function useLocale(): Locale {
|
||||
const params = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const lang = params.lang;
|
||||
|
||||
return useMemo(() => {
|
||||
if (isLocale(lang)) return lang;
|
||||
return getLocaleFromPathname(pathname) ?? DEFAULT_LOCALE;
|
||||
}, [lang, pathname]);
|
||||
}
|
||||
|
||||
/** "ltr" for /en/*, "rtl" for /ar/*. Mirrors `<html dir>`. */
|
||||
export function useDirection(): Direction {
|
||||
return dirFor(useLocale());
|
||||
}
|
||||
|
||||
export interface Translation {
|
||||
readonly locale: Locale;
|
||||
readonly dir: Direction;
|
||||
/** `t("nav.sections.about")`, `t("pagination.page", { page, pageCount })`. */
|
||||
readonly t: Translator;
|
||||
/** The raw catalogue, for the rare case a whole namespace is needed. */
|
||||
readonly messages: Messages;
|
||||
/** `/{locale}` prefixed URL for an unprefixed path. */
|
||||
readonly localizedPath: (path: string) => string;
|
||||
/** The locale-prefixed URL of one of the ten sections. */
|
||||
readonly sectionPath: (key: RouteSlugKey) => string;
|
||||
}
|
||||
|
||||
/** Everything a component needs to render in the current language. */
|
||||
export function useTranslation(): Translation {
|
||||
const locale = useLocale();
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
locale,
|
||||
dir: dirFor(locale),
|
||||
t: createTranslator(locale),
|
||||
messages: getMessages(locale),
|
||||
localizedPath: (path: string) => localizedPath(path, locale),
|
||||
sectionPath: (key: RouteSlugKey) => sectionPath(key, locale),
|
||||
}),
|
||||
[locale],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The counterpart-language URL for the current location, query string and
|
||||
* fragment preserved. This is what the header's language switch points at.
|
||||
*/
|
||||
export function useAlternatePath(targetLocale: Locale): string {
|
||||
const { pathname, search, hash } = useLocation();
|
||||
|
||||
return useMemo(
|
||||
() => alternatePath(pathname, targetLocale, { search, hash }),
|
||||
[pathname, search, hash, targetLocale],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a value off the deepest route `handle` that declares it. Used by the
|
||||
* shell to let a page opt into a light navigation or footer surface.
|
||||
*/
|
||||
export function useRouteHandleValue<T>(
|
||||
read: (handle: Record<string, unknown>) => T | undefined,
|
||||
): T | undefined {
|
||||
const matches = useMatches();
|
||||
|
||||
for (let index = matches.length - 1; index >= 0; index -= 1) {
|
||||
const handle = matches[index]?.handle;
|
||||
if (handle && typeof handle === "object") {
|
||||
const value = read(handle as Record<string, unknown>);
|
||||
if (value !== undefined) return value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user