• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / fa06ce8a-b9ce-4ce5-a176-4276d5cda4c6

pending completion
fa06ce8a-b9ce-4ce5-a176-4276d5cda4c6

push

circleci

GitHub
Merge pull request #2902 from mozilla/mpp-2562-lint-backend-translations

1795 of 2851 branches covered (62.96%)

Branch coverage included in aggregate %.

5203 of 7023 relevant lines covered (74.09%)

20.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/frontend/src/functions/getL10n.ts
1
import { FluentBundle, FluentResource } from "@fluent/bundle";
×
2
import { negotiateLanguages } from "@fluent/langneg";
×
3
import { MarkupParser, ReactLocalization } from "@fluent/react";
×
4

5
/**
6
 * @returns Initialise `@fluent/react`.
7
 * @todo Get the relevant .ftl injected by the server.
8
 */
9
export function getL10n() {
×
10
  // Store all translations as a simple object which is available
11
  // synchronously and bundled with the rest of the code.
12
  // Also, `require` isn't usually valid JS, so skip type checking for that:
13
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
  const translationsContext = (require as any).context(
×
15
    "../../../privaterelay/locales",
16
    true,
17
    /\.ftl$/
18
  );
19
  const RESOURCES: Record<string, FluentResource[]> = {};
×
20

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

26
    if (locale) {
×
27
      RESOURCES[locale] ??= [];
×
28
      RESOURCES[locale].push(new FluentResource(translationsContext(fileName)));
×
29
    }
30
  }
31

32
  // A generator function responsible for building the sequence
33
  // of FluentBundle instances in the order of user's language
34
  // preferences.
35
  function* generateBundles(userLocales: typeof navigator.languages) {
36
    // Choose locales that are best for the user.
37
    const currentLocales = negotiateLanguages(
×
38
      userLocales as string[],
39
      Object.keys(RESOURCES),
40
      { defaultLocale: "en" }
41
    );
42

43
    for (const locale of currentLocales) {
×
44
      if (typeof RESOURCES[locale] === "undefined") {
×
45
        throw new Error(
×
46
          `Locale [${locale}] not found. You might want to run \`git submodule update --remote\` at the root of this repository?`
47
        );
48
      }
49
      const bundle = new FluentBundle(locale);
×
50
      RESOURCES[locale].forEach((resource) => {
×
51
        bundle.addResource(resource);
×
52
      });
53
      if (locale === "en") {
×
54
        // `require` isn't usually valid JS, so skip type checking for that:
55
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
        const pendingTranslations = (require as any)(
×
57
          "../../pendingTranslations.ftl"
58
        );
59
        const pendingTranslationsResource = new FluentResource(
×
60
          pendingTranslations
61
        );
62
        bundle.addResource(pendingTranslationsResource);
×
63
        if (process.env.NEXT_PUBLIC_DEBUG === "true") {
×
64
          // All string IDs in `pendingTranslations.ftl`:
65
          const pendingTranslationsIds = pendingTranslationsResource.body.map(
×
66
            (stringData) => stringData.id
×
67
          );
68
          // All string IDs in all English FTL files:
69
          const mergedEnglishTranslationIds = RESOURCES.en.flatMap(
×
70
            (enResource) => {
71
              return enResource.body.map((stringData) => stringData.id);
×
72
            }
73
          );
74
          const unmergedStrings = pendingTranslationsIds.filter(
×
75
            (id) => !mergedEnglishTranslationIds.includes(id)
×
76
          );
77
          if (unmergedStrings.length > 0) {
×
78
            console.warn(
×
79
              `The following ${unmergedStrings.length} strings have not yet been merged into the l10n repository, and thus cannot be translated yet:`,
80
              unmergedStrings
81
            );
82
          }
83
        }
84
      }
85
      yield bundle;
×
86
    }
87
  }
88

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

101
  // The ReactLocalization instance stores and caches the sequence of generated
102
  // bundles. You can store it in your app's state.
103
  const l10n = new ReactLocalization(
×
104
    generateBundles(
105
      typeof navigator !== "undefined" ? navigator.languages : []
×
106
    ),
107
    parseMarkup
108
  );
109
  return l10n;
×
110
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc