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

mozilla / fx-private-relay / ff115f0a-e3b0-450c-9c76-ebac3dc28753

13 Jan 2025 04:32PM CUT coverage: 85.092%. Remained the same
ff115f0a-e3b0-450c-9c76-ebac3dc28753

push

circleci

web-flow
Merge pull request #5301 from mozilla/dependabot/pip/boto-e4160fe91d

Bump the boto group with 7 updates

2434 of 3561 branches covered (68.35%)

Branch coverage included in aggregate %.

16996 of 19273 relevant lines covered (88.19%)

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

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

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

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

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

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

106
  // The ReactLocalization instance stores and caches the sequence of generated
107
  // bundles. You can store it in your app's state.
108
  const l10n = new ReactLocalization(
×
109
    generateBundles(
110
      typeof navigator !== "undefined" ? navigator.languages : [],
×
111
    ),
112
    parseMarkup,
113
  );
114
  return l10n;
×
115
}
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