fluent-transpiler 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/cli.js +61 -29
  2. package/index.js +50 -38
  3. package/package.json +10 -7
package/cli.js CHANGED
@@ -6,10 +6,10 @@ import { Command, Option } from 'commander'
6
6
  import compile from './index.js'
7
7
 
8
8
  const fileExists = async (filepath) => {
9
- const stats = await stat(filepath)
10
- if (!stats.isFile()) {
11
- throw new Error(`${filepath} is not a file`)
12
- }
9
+ const stats = await stat(filepath)
10
+ if (!stats.isFile()) {
11
+ throw new Error(`${filepath} is not a file`)
12
+ }
13
13
  }
14
14
 
15
15
  new Command()
@@ -17,38 +17,70 @@ new Command()
17
17
  .description('Compile Fluent (.ftl) files to JavaScript (.js or .mjs)')
18
18
  //.version(package.version)
19
19
  .argument('<input>', 'Path to the Fluent file to compile')
20
- .requiredOption('--locale <locale...>', 'What locale(s) to be used. Multiple can be set to allow for fallback. i.e. en-CA')
21
- .addOption(new Option('--comments', 'Include comments in output file.')
22
- .preset(true)
20
+ .requiredOption(
21
+ '--locale <locale...>',
22
+ 'What locale(s) to be used. Multiple can be set to allow for fallback. i.e. en-CA'
23
+ )
24
+ .addOption(
25
+ new Option('--comments', 'Include comments in output file.').preset(true)
26
+ )
27
+ .addOption(
28
+ new Option(
29
+ '--include-key <includeMessageKey...>',
30
+ 'Allowed messages to be included. Default to include all.'
31
+ )
32
+ )
33
+ .addOption(
34
+ new Option(
35
+ '--exclude-key <excludeMessageKey...>',
36
+ 'Ignored messages to be excluded. Default to exclude none.'
37
+ )
38
+ )
39
+ .addOption(
40
+ new Option(
41
+ '--exclude-value <excludeMessageValue>',
42
+ 'Set message to an empty string when it contains this value. Default to not allowing empty strings.'
43
+ )
23
44
  )
24
- .addOption(new Option('--include <includeMessages...>', 'Allowed messages to be included. Default to include all.'))
25
- .addOption(new Option('--exclude <excludeMessages...>', 'Ignored messages to be excluded. Default to exclude none.'))
26
45
  /*.addOption(new Option('--tree-shaking', 'Export all messages to allow tree shaking')
27
46
  .preset(true)
28
47
  )*/
29
- .addOption(new Option('--variable-notation <variableNotation>', 'What variable notation to use with exports')
30
- .choices(['camelCase','pascalCase','constantCase','snakeCase'])
31
- .default('camelCase')
48
+ .addOption(
49
+ new Option(
50
+ '--variable-notation <variableNotation>',
51
+ 'What variable notation to use with exports'
52
+ )
53
+ .choices(['camelCase', 'pascalCase', 'constantCase', 'snakeCase'])
54
+ .default('camelCase')
32
55
  )
33
- .addOption(new Option('--disable-minify', 'If disabled, all exported messages will have the same interface `(params) => ({value, attributes})`.')
34
- .preset(true)
56
+ .addOption(
57
+ new Option(
58
+ '--disable-minify',
59
+ 'If disabled, all exported messages will have the same interface `(params) => ({value, attributes})`.'
60
+ ).preset(true)
35
61
  )
36
- .addOption(new Option('--use-isolating', 'Wrap placeable with \\u2068 and \\u2069.')
37
- .preset(true)
62
+ .addOption(
63
+ new Option(
64
+ '--use-isolating',
65
+ 'Wrap placeable with \\u2068 and \\u2069.'
66
+ ).preset(true)
67
+ )
68
+ .addOption(
69
+ new Option(
70
+ '-o, --output <output>',
71
+ 'Path to store the resulting JavaScript file. Will be in ESM.'
72
+ )
38
73
  )
39
- .addOption(new Option('-o, --output <output>', 'Path to store the resulting JavaScript file. Will be in ESM.'))
40
74
  .action(async (input, options) => {
41
- await fileExists(input)
42
-
43
- const ftl = await readFile(input, {encoding:'utf8'})
44
-
45
- const js = compile(ftl, options)
46
- if (options.output) {
47
- await writeFile(options.output, js, 'utf8')
48
- } else {
49
- console.log(js)
50
- }
51
-
75
+ await fileExists(input)
76
+
77
+ const ftl = await readFile(input, { encoding: 'utf8' })
78
+
79
+ const js = compile(ftl, options)
80
+ if (options.output) {
81
+ await writeFile(options.output, js, 'utf8')
82
+ } else {
83
+ console.log(js)
84
+ }
52
85
  })
53
86
  .parse()
54
-
package/index.js CHANGED
@@ -2,18 +2,19 @@ import { parse } from '@fluent/syntax'
2
2
  import { camelCase, pascalCase, constantCase, snakeCase } from 'change-case'
3
3
 
4
4
  const exportDefault = `(id, params) => {
5
- const source = __exports[id] ?? __exports['_'+id]
6
- if (typeof source === 'undefined') return '*** '+id+' ***'
7
- if (typeof source === 'function') return source(params)
8
- return source
5
+ const source = __exports[id] ?? __exports['_'+id]
6
+ if (typeof source === 'undefined') return '*** '+id+' ***'
7
+ if (typeof source === 'function') return source(params)
8
+ return source
9
9
  }
10
10
  `
11
11
  export const compile = (src, opts) => {
12
12
  const options = {
13
13
  comments: true,
14
14
  errorOnJunk: true,
15
- includeMessages: [],
16
- excludeMessages: [],
15
+ includeKey: [],
16
+ excludeKey: [],
17
+ excludeValue: undefined,
17
18
  //treeShaking: false,
18
19
  variableNotation: 'camelCase',
19
20
  disableMinify: false, // TODO needs better name strictInterface?
@@ -23,10 +24,15 @@ export const compile = (src, opts) => {
23
24
  ...opts
24
25
  }
25
26
  if (!Array.isArray(options.locale)) options.locale = [options.locale]
26
- if (!Array.isArray(options.includeMessages))
27
- options.includeMessages = [options.includeMessages]
28
- if (!Array.isArray(options.excludeMessages))
29
- options.excludeMessages = [options.excludeMessages]
27
+ if (!Array.isArray(options.includeKey))
28
+ options.includeKey = [options.includeKey]
29
+ if (!Array.isArray(options.excludeKey))
30
+ options.excludeKey = [options.excludeKey]
31
+ if (options.excludeValue) {
32
+ // cast to template literal
33
+ options.excludeValue = '`' + options.excludeValue + '`'
34
+ }
35
+ console.log({ options })
30
36
 
31
37
  const metadata = {}
32
38
  const exports = []
@@ -113,21 +119,26 @@ export const compile = (src, opts) => {
113
119
  const assignment = compileAssignment(data.id)
114
120
 
115
121
  if (
116
- options.includeMessages.length &&
117
- !options.includeMessages.includes(assignment)
122
+ options.includeKey.length &&
123
+ !options.includeKey.includes(assignment)
118
124
  ) {
119
125
  return ''
120
126
  }
121
127
 
122
128
  if (
123
- options.excludeMessages.length &&
124
- options.excludeMessages.includes(assignment)
129
+ options.excludeKey.length &&
130
+ options.excludeKey.includes(assignment)
125
131
  ) {
126
132
  return ''
127
133
  }
128
134
 
129
135
  const templateStringLiteral =
130
136
  data.value && compileType(data.value, data.type)
137
+
138
+ if (options.excludeValue === templateStringLiteral) {
139
+ templateStringLiteral = '``'
140
+ }
141
+
131
142
  metadata[assignment].attributes = data.attributes.length
132
143
  let attributes = {}
133
144
  if (metadata[assignment].attributes) {
@@ -173,12 +184,12 @@ export const compile = (src, opts) => {
173
184
  }
174
185
  return `export const ${assignment} = ${message}`
175
186
  /*} else {
176
- if (assignment === metadata[assignment].id) {
177
- exports.push(`${assignment}: ${message}`)
178
- } else {
179
- exports.push(`'${metadata[assignment].id}': ${message}`)
180
- }
181
- }*/
187
+ if (assignment === metadata[assignment].id) {
188
+ exports.push(`${assignment}: ${message}`)
189
+ } else {
190
+ exports.push(`'${metadata[assignment].id}': ${message}`)
191
+ }
192
+ }*/
182
193
  return ''
183
194
  },
184
195
  Comment: (data) => {
@@ -202,6 +213,7 @@ export const compile = (src, opts) => {
202
213
  },
203
214
  // Element
204
215
  TextElement: (data) => {
216
+ if (data.value === options.emptyString) return
205
217
  return data.value.replaceAll('`', '\\`') // escape string literal
206
218
  },
207
219
  Placeable: (data, parent) => {
@@ -358,10 +370,10 @@ const formatTime = (value) => {
358
370
  value = new Date(value)
359
371
  if (isNaN(value.getTime())) return value
360
372
  try {
361
- const [duration, unit] = relativeTimeDiff(value)
362
- return relativeTimeFormat.format(duration, unit)
373
+ const [duration, unit] = relativeTimeDiff(value)
374
+ return relativeTimeFormat.format(duration, unit)
363
375
  } catch (e) {
364
- return dateTimeFormat.format(value)
376
+ return dateTimeFormat.format(value)
365
377
  }
366
378
  }
367
379
  */
@@ -375,24 +387,24 @@ const __relativeTimeDiff = (d) => {
375
387
  const msPerMonth = msPerDay * 30
376
388
  const msPerYear = msPerDay * 365.25
377
389
  const elapsed = d - new Date()
378
-
390
+
379
391
  if (Math.abs(elapsed) < msPerMinute) {
380
392
  return [Math.round(elapsed / 1000), 'second']
381
393
  }
382
394
  if (Math.abs(elapsed) < msPerHour) {
383
- return [Math.round(elapsed / msPerMinute), 'minute']
395
+ return [Math.round(elapsed / msPerMinute), 'minute']
384
396
  }
385
397
  if (Math.abs(elapsed) < msPerDay) {
386
- return [Math.round(elapsed / msPerHour), 'hour']
398
+ return [Math.round(elapsed / msPerHour), 'hour']
387
399
  }
388
400
  if (Math.abs(elapsed) < msPerWeek * 2) {
389
- return [Math.round(elapsed / msPerDay), 'day']
401
+ return [Math.round(elapsed / msPerDay), 'day']
390
402
  }
391
403
  if (Math.abs(elapsed) < msPerMonth) {
392
- return [Math.round(elapsed / msPerWeek), 'week']
404
+ return [Math.round(elapsed / msPerWeek), 'week']
393
405
  }
394
406
  if (Math.abs(elapsed) < msPerYear) {
395
- return [Math.round(elapsed / msPerMonth), 'month']
407
+ return [Math.round(elapsed / msPerMonth), 'month']
396
408
  }
397
409
  return [Math.round(elapsed / msPerYear), 'year']
398
410
  }
@@ -400,8 +412,8 @@ const __formatRelativeTime = (value, options) => {
400
412
  if (typeof value === 'string') value = new Date(value)
401
413
  if (isNaN(value.getTime())) return value
402
414
  try {
403
- const [duration, unit] = __relativeTimeDiff(value)
404
- return new Intl.RelativeTimeFormat(__locales, options).format(duration, unit)
415
+ const [duration, unit] = __relativeTimeDiff(value)
416
+ return new Intl.RelativeTimeFormat(__locales, options).format(duration, unit)
405
417
  } catch (e) {}
406
418
  return new Intl.DateTimeFormat(__locales, options).format(value)
407
419
  }
@@ -410,16 +422,16 @@ const __formatRelativeTime = (value, options) => {
410
422
  if (functions.__formatDateTime) {
411
423
  output += `
412
424
  const __formatDateTime = (value, options) => {
413
- if (typeof value === 'string') value = new Date(value)
414
- if (isNaN(value.getTime())) return value
415
- return new Intl.DateTimeFormat(__locales, options).format(value)
425
+ if (typeof value === 'string') value = new Date(value)
426
+ if (isNaN(value.getTime())) return value
427
+ return new Intl.DateTimeFormat(__locales, options).format(value)
416
428
  }
417
429
  `
418
430
  }
419
431
  if (functions.__formatVariable || functions.__formatNumber) {
420
432
  output += `
421
433
  const __formatNumber = (value, options) => {
422
- return new Intl.NumberFormat(__locales, options).format(value)
434
+ return new Intl.NumberFormat(__locales, options).format(value)
423
435
  }
424
436
  `
425
437
  }
@@ -436,9 +448,9 @@ const __formatVariable = (value) => {
436
448
  if (functions.__select) {
437
449
  output += `
438
450
  const __select = (value, cases, fallback, options) => {
439
- const pluralRules = new Intl.PluralRules(__locales, options)
440
- const rule = pluralRules.select(value)
441
- return cases[value] ?? cases[rule] ?? fallback
451
+ const pluralRules = new Intl.PluralRules(__locales, options)
452
+ const rule = pluralRules.select(value)
453
+ return cases[value] ?? cases[rule] ?? fallback
442
454
  }
443
455
  `
444
456
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluent-transpiler",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Transpile Fluent (ftl) files into optimized, tree-shakable, JavaScript EcmaScript Modules (esm).",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "test": "npm run test:cli && npm run test:unit",
16
16
  "test:cli": "./cli.js --locale en-CA --locale en --use-isolating --variable-notation camelCase test/files/index.ftl --output test/files/index.mjs",
17
- "test:unit": "c8 node --test"
17
+ "test:unit": "node --test"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
@@ -39,12 +39,15 @@
39
39
  },
40
40
  "homepage": "https://github.com/willfarrell/fluent-transpiler",
41
41
  "dependencies": {
42
- "@fluent/syntax": "0.18.1",
43
- "change-case": "4.1.2",
44
- "commander": "9.4.0"
42
+ "@fluent/syntax": "0.19.0",
43
+ "change-case": "5.4.4",
44
+ "commander": "13.1.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@fluent/bundle": "^0.17.1",
48
- "c8": "^7.12.0"
47
+ "@fluent/bundle": "^0.19.0"
48
+ },
49
+ "funding": {
50
+ "type": "github",
51
+ "url": "https://github.com/sponsors/willfarrell"
49
52
  }
50
53
  }