tutorbookapp/tutorbook

View on GitHub
lib/intl/index.tsx

Summary

Maintainability
A
0 mins
Test Coverage
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;
}