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

mozilla / blurts-server / #13056

pending completion
#13056

push

circleci

Vinnl
Ensure that @ts-ignore is always commented

It's an escape hatch, so we want to know why it was added (and when
it can be removed again).

282 of 1619 branches covered (17.42%)

Branch coverage included in aggregate %.

959 of 4334 relevant lines covered (22.13%)

1.84 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
  console.log('Fluent bundles created:', Object.keys(fluentBundles))
×
45
}
46

47
/**
48
 * Set the locale used for translations negotiated between requested and available
49
 *
50
 * @param {string[]} requestedLocales - Locales requested by client.
51
 */
52
function updateLocale (requestedLocales) {
53
  return negotiateLanguages(
×
54
    requestedLocales,
55
    supportedLocales,
56
    { strategy: 'lookup', defaultLocale: 'en' }
57
  )
58
}
59

60
/**
61
 * Return the locale negotiated between requested and supported locales.
62
 * Default 'en' if localStorage hasn't initialized (called without client request)
63
 */
64
function getLocale () {
65
  return localStorage.getStore()?.get('locale') || ['en']
×
66
}
67

68
/**
69
 * Translate a message and return the raw string
70
 * Defaults to en if message id not found in requested locale
71
 *
72
 * @param {string} id - The Fluent message id.
73
 */
74
function getRawMessage (id) {
75
  let bundle = fluentBundles[getLocale()]
×
76

77
  if (!bundle.hasMessage(id)) bundle = fluentBundles.en
×
78

79
  if (bundle.hasMessage(id)) return bundle.getMessage(id)?.value
×
80

81
  return id
×
82
}
83

84
/**
85
 * Translate and transform a message pattern with current locale
86
 * Defaults to en if message id not found in requested locale
87
 *
88
 * @param {string} id - The Fluent message id.
89
 * @param {object} [args] - key/value pairs corresponding to pattern in Fluent resource.
90
 * @example
91
 * // Given FluentResource("hello = Hello, {$name}!")
92
 * getMessage (hello, {name: "Jane"})
93
 * // Returns "Hello, Jane!"
94
 */
95
function getMessage (id, args) {
96
  return getMessageWithLocale(id, getLocale(), args)
×
97
}
98

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

116
  if (!bundle.hasMessage(id)) bundle = fluentBundles.en
×
117

118
  if (bundle.hasMessage(id)) {
×
119
    // @ts-ignore We know `.value` exists thanks to `.hasMessage()`
120
    return bundle.formatPattern(bundle.getMessage(id).value, args)
×
121
  }
122

123
  return id
×
124
}
125

126
/**
127
 * @param {string} id
128
 */
129
function fluentError (id) {
130
  return new Error(getMessage(id))
×
131
}
132

133
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