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

mozilla / blurts-server / fe6cfeb1-ae03-482b-9144-7da84a045922

pending completion
fe6cfeb1-ae03-482b-9144-7da84a045922

push

circleci

Vincent
All states for breach results

282 of 1655 branches covered (17.04%)

Branch coverage included in aggregate %.

55 of 55 new or added lines in 2 files covered. (100.0%)

959 of 4458 relevant lines covered (21.51%)

3.59 hits per line

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

0.0
/src/utils/fluent.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
import { join, resolve } from 'node:path'
6
import { readdirSync } from 'node:fs'
7
import { readFile } from 'node:fs/promises'
8
import { FluentBundle, FluentResource } from '@fluent/bundle'
9
import { negotiateLanguages } from '@fluent/langneg'
10
import AppConstants from '../app-constants.js'
11
import { localStorage } from './local-storage.js'
12

13
const supportedLocales = AppConstants.SUPPORTED_LOCALES?.split(',')
×
14
/** @type {Record<string, FluentBundle>} */
15
const fluentBundles = {}
×
16

17
/**
18
 * Create Fluent bundles for all supported locales.
19
 * Reads .ftl files in parallel for better server start performance.
20
 */
21
async function initFluentBundles () {
22
  const promises = supportedLocales.map(async locale => {
×
23
    const bundle = new FluentBundle(locale, { useIsolating: false })
×
24
    const dirname = resolve('../locales', locale)
×
25

26
    try {
×
27
      const filenames = readdirSync(dirname).filter(item => item.endsWith('.ftl'))
×
28

29
      await Promise.all(filenames.map(async filename => {
×
30
        const str = await readFile(join(dirname, filename), 'utf8')
×
31

32
        bundle.addResource(new FluentResource(str))
×
33
      }))
34
    } catch (/** @type {any} */ e) {
35
      console.error('Could not read Fluent file:', e)
×
36
      throw new Error(e)
×
37
    }
38

39
    fluentBundles[locale] = bundle
×
40
  })
41

42
  await Promise.allSettled(promises)
×
43

44
  // Fluent files in this directory are not currently localized, but might be
45
  // moved to `/locales/` later to submit them for localization:
46
  const unlocalizedDirname = resolve('../unlocalized/en')
×
47

48
  try {
×
49
    const filenames = readdirSync(unlocalizedDirname).filter(item => item.endsWith('.ftl'))
×
50

51
    await Promise.all(filenames.map(async filename => {
×
52
      const str = await readFile(join(unlocalizedDirname, filename), 'utf8')
×
53

54
      fluentBundles.en.addResource(new FluentResource(str))
×
55
    }))
56
  } catch (e) {
57
    console.error('Could not read Fluent file:', e)
×
58
    throw new Error(e)
×
59
  }
60

61
  console.log('Fluent bundles created:', Object.keys(fluentBundles))
×
62
}
63

64
/**
65
 * Set the locale used for translations negotiated between requested and available
66
 *
67
 * @param {string[]} requestedLocales - Locales requested by client.
68
 */
69
function updateLocale (requestedLocales) {
70
  return negotiateLanguages(
×
71
    requestedLocales,
72
    supportedLocales,
73
    { strategy: 'lookup', defaultLocale: 'en' }
74
  )
75
}
76

77
/**
78
 * Return the locale negotiated between requested and supported locales.
79
 * Default 'en' if localStorage hasn't initialized (called without client request)
80
 */
81
function getLocale () {
82
  return localStorage.getStore()?.get('locale') || ['en']
×
83
}
84

85
/**
86
 * Translate a message and return the raw string
87
 * Defaults to en if message id not found in requested locale
88
 *
89
 * @param {string} id - The Fluent message id.
90
 */
91
function getRawMessage (id) {
92
  let bundle = fluentBundles[getLocale()]
×
93

94
  if (!bundle.hasMessage(id)) bundle = fluentBundles.en
×
95

96
  if (bundle.hasMessage(id)) return bundle.getMessage(id)?.value
×
97

98
  return id
×
99
}
100

101
/**
102
 * Translate and transform a message pattern with current locale
103
 * Defaults to en if message id not found in requested locale
104
 *
105
 * @param {string} id - The Fluent message id.
106
 * @param {Record<string, import('@fluent/bundle').FluentVariable>} [args] - key/value pairs corresponding to pattern in Fluent resource.
107
 * @example
108
 * // Given FluentResource("hello = Hello, {$name}!")
109
 * getMessage (hello, {name: "Jane"})
110
 * // Returns "Hello, Jane!"
111
 */
112
function getMessage (id, args) {
113
  return getMessageWithLocale(id, getLocale(), args)
×
114
}
115

116
/**
117
 * Translate and transform a message pattern
118
 * Can pass in any locale
119
 * Defaults to en if message id not found in requested locale
120
 *
121
 * @param {string} id - The Fluent message id.
122
 * @param {string[]} localePreferences
123
 * @param {Record<string, import('@fluent/bundle').FluentVariable>} [args] - key/value pairs corresponding to pattern in Fluent resource.
124
 * @returns {string}
125
 * @example
126
 * // Given FluentResource("hello = Hello, {$name}!")
127
 * getMessage (hello, {name: "Jane"})
128
 * // Returns "Hello, Jane!"
129
 */
130
function getMessageWithLocale (id, localePreferences, args) {
131
  let bundle = fluentBundles[localePreferences[0]]
×
132

133
  if (!bundle.hasMessage(id)) bundle = fluentBundles.en
×
134

135
  if (!bundle.hasMessage(id)) {
×
136
    return id
×
137
  }
138

139
  return bundle.formatPattern(bundle.getMessage(id)?.value ?? '', args)
×
140
}
141

142
/**
143
 * @param {string} id
144
 */
145
function fluentError (id) {
146
  return new Error(getMessage(id))
×
147
}
148

149
export { initFluentBundles, updateLocale, getLocale, getMessage, getMessageWithLocale, getRawMessage, fluentError }
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