export function getL10n()

in frontend/src/functions/getL10n.ts [10:115]


export function getL10n(options: { deterministicLocales: boolean }) {
  // Store all translations as a simple object which is available
  // synchronously and bundled with the rest of the code.
  // Also, `require` isn't usually valid JS, so skip type checking for that:
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const translationsContext = (require as any).context(
    "../../../privaterelay/locales",
    true,
    /\.ftl$/,
  );
  const RESOURCES: Record<string, FluentResource[]> = {};

  for (const fileName of translationsContext.keys()) {
    // Filenames are formatted as `./<locale>/<module>.ftl`.
    // Example: ./en/bundle.ftl
    const locale = fileName.split("/")[1];

    if (locale) {
      RESOURCES[locale] ??= [];
      RESOURCES[locale].push(new FluentResource(translationsContext(fileName)));
    }
  }

  // A generator function responsible for building the sequence
  // of FluentBundle instances in the order of user's language
  // preferences.
  function* generateBundles(userLocales: typeof navigator.languages) {
    // Choose locales that are best for the user.
    const currentLocales = negotiateLanguages(
      // During pre-render, no locales are available yet. To avoid a mismatch
      // between pre-rendered HTML and the DOM generated in the first render,
      // we don't load locales dependent on the user's preferences on first
      // render either:
      options.deterministicLocales ? [] : (userLocales as string[]),
      Object.keys(RESOURCES),
      { defaultLocale: "en" },
    );

    for (const locale of currentLocales) {
      if (typeof RESOURCES[locale] === "undefined") {
        throw new Error(
          `Locale [${locale}] not found. You might want to run \`git submodule update --remote\` at the root of this repository?`,
        );
      }
      const bundle = new FluentBundle(locale);
      RESOURCES[locale].forEach((resource) => {
        bundle.addResource(resource);
      });
      if (locale === "en") {
        // `require` isn't usually valid JS, so skip type checking for that:
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const pendingTranslations = (require as any)(
          "../../pendingTranslations.ftl",
        );
        const pendingTranslationsResource = new FluentResource(
          pendingTranslations,
        );
        bundle.addResource(pendingTranslationsResource);
        if (process.env.NEXT_PUBLIC_DEBUG === "true") {
          // All string IDs in `pendingTranslations.ftl`:
          const pendingTranslationsIds = pendingTranslationsResource.body.map(
            (stringData) => stringData.id,
          );
          // All string IDs in all English FTL files:
          const mergedEnglishTranslationIds = RESOURCES.en.flatMap(
            (enResource) => {
              return enResource.body.map((stringData) => stringData.id);
            },
          );
          const unmergedStrings = pendingTranslationsIds.filter(
            (id) => !mergedEnglishTranslationIds.includes(id),
          );
          if (unmergedStrings.length > 0) {
            console.warn(
              `The following ${unmergedStrings.length} strings have not yet been merged into the l10n repository, and thus cannot be translated yet:`,
              unmergedStrings,
            );
          }
        }
      }
      yield bundle;
    }
  }

  // To enable server-side rendering, all tags are converted to plain text nodes.
  // They will be upgraded to regular HTML elements in the browser:
  const parseMarkup: MarkupParser | undefined =
    typeof document === "undefined"
      ? (str: string) => [
          {
            nodeName: "#text",
            textContent: str.replace(/<(.*?)>/g, ""),
          } as Node,
        ]
      : undefined;

  // The ReactLocalization instance stores and caches the sequence of generated
  // bundles. You can store it in your app's state.
  const l10n = new ReactLocalization(
    generateBundles(
      typeof navigator !== "undefined" ? navigator.languages : [],
    ),
    parseMarkup,
  );
  return l10n;
}