lib/intl/index.tsx
import { ParsedUrlQuery } from 'querystring';
import { ComponentType, FunctionComponent } from 'react';
import I18nProvider from 'next-translate/I18nProvider';
import localeConfig from './config.json';
export const { locales, defaultLocale } = localeConfig;
export type Namespaces = Record<string, Record<string, string>>;
export type IntlPaths = Array<{ params: { locale: string } }>;
export interface IntlProps {
locale: string;
namespaces: Namespaces;
}
async function importNamespaces(
locale: string,
namespacesToFetch: string[]
): Promise<Namespaces> {
const pageNamespaces = await Promise.all(
namespacesToFetch.map((ns) =>
import(`locales/${locale}/${ns}.json`).then(
(mod: { default: Record<string, string> }) => mod.default
)
)
);
return namespacesToFetch.reduce(
(obj: Namespaces, ns, idx) => ({ ...obj, [ns]: pageNamespaces[idx] }),
{}
);
}
/**
* Dynamically imports the requested locale's static translations/messages file
* and `full-icu` support (i.e. for `Date` stringification) for the given
* locale.
*
* This function is called with the `params` generated by `getIntlPaths()` and
* ensures that our SSG (Static Site Generation) pages only contain the bare
* minimum `Intl' support for their target locale..
* @see {@link https://bit.ly/2SAqwCe}
*/
export async function getIntlProps(
ctx: { params?: ParsedUrlQuery },
namespacesToFetch: string[]
): Promise<IntlProps> {
const locale = (ctx.params?.locale as string) || defaultLocale;
const namespaces = await importNamespaces(locale, namespacesToFetch);
return { locale, namespaces };
}
/**
* Gets the paths of every locale that we currently support (i.e. locales that
* we have translations for). This enables us to statically pre-render all of
* our support locales.
* @see {@link https://bit.ly/2L1kOFm}
*/
export function getIntlPaths(): IntlPaths {
return locales.map((locale: string) => ({ params: { locale } }));
}
/**
* Adds a HOC (Higher Order Component) to the given component to provide it with
* the `Intl` information it needs (via `react-intl`'s `IntlProvider`).
* @see {@link https://bit.ly/3c5vXRg}
*
* Types were made easy to define thanks to the following blog post.
* @see {@link https://bit.ly/3b7ZuZb}
*/
export function withIntl<P extends Record<string, any>>(
Component: ComponentType<P>
): FunctionComponent<P & IntlProps> {
function WithIntl({ locale, namespaces, ...props }: IntlProps): JSX.Element {
return (
<I18nProvider lang={locale} namespaces={namespaces}>
<Component {...(props as P)} />
</I18nProvider>
);
}
return WithIntl;
}
/**
* Temporary helper function to add a wrapper around pages that already import
* their static i18n locale files (to re-enable static optimization).
*/
export function withI18n<P extends Record<string, any>>(
Component: ComponentType<P>,
namespaces: Namespaces
): FunctionComponent<P> {
function WithI18n(props: P): JSX.Element {
return (
<I18nProvider lang='en' namespaces={namespaces}>
<Component {...props} />
</I18nProvider>
);
}
return WithI18n;
}