@ultraq/icu-message-formatter 0.14.2 → 0.14.3

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ ### 0.14.3
6
+ - Fix "Default condition should be last one" webpack build error
7
+ ([#14](https://github.com/ultraq/icu-message-formatter/issues/14))
8
+
5
9
  ### 0.14.2
6
10
  - Fix types on the main exports too (tsc was being run over
7
11
  transpiled-for-browser code 🤦‍♂️)
package/README.md CHANGED
File without changes
@@ -40,61 +40,71 @@ var functionUtils = require('@ultraq/function-utils');
40
40
  * @return {ParseCasesResult}
41
41
  */
42
42
  function parseCases(string = '') {
43
- const isWhitespace = ch => /\s/.test(ch);
44
- const args = [];
45
- const cases = {};
46
- let currTermStart = 0;
47
- let latestTerm = null;
48
- let inTerm = false;
49
- let i = 0;
50
- while (i < string.length) {
51
- // Term ended
52
- if (inTerm && (isWhitespace(string[i]) || string[i] === '{')) {
53
- inTerm = false;
54
- latestTerm = string.slice(currTermStart, i);
55
-
56
- // We want to process the opening char again so the case will be properly registered.
57
- if (string[i] === '{') {
58
- i--;
59
- }
60
- }
61
-
62
- // New term
63
- else if (!inTerm && !isWhitespace(string[i])) {
64
- const caseBody = string[i] === '{';
65
-
66
- // If there's a previous term, we can either handle a whole
67
- // case, or add that as an argument.
68
- if (latestTerm && caseBody) {
69
- const branchEndIndex = findClosingBracket(string, i);
70
- if (branchEndIndex === -1) {
71
- throw new Error(`Unbalanced curly braces in string: "${string}"`);
72
- }
73
- cases[latestTerm] = string.slice(i + 1, branchEndIndex); // Don't include the braces
74
-
75
- i = branchEndIndex; // Will be moved up where needed at end of loop.
76
- latestTerm = null;
77
- } else {
78
- if (latestTerm) {
79
- args.push(latestTerm);
80
- latestTerm = null;
81
- }
82
- inTerm = true;
83
- currTermStart = i;
84
- }
85
- }
86
- i++;
87
- }
88
- if (inTerm) {
89
- latestTerm = string.slice(currTermStart);
90
- }
91
- if (latestTerm) {
92
- args.push(latestTerm);
93
- }
94
- return {
95
- args,
96
- cases
97
- };
43
+ const isWhitespace = ch => /\s/.test(ch);
44
+
45
+ const args = [];
46
+ const cases = {};
47
+
48
+ let currTermStart = 0;
49
+ let latestTerm = null;
50
+ let inTerm = false;
51
+
52
+ let i = 0;
53
+ while (i < string.length) {
54
+ // Term ended
55
+ if (inTerm && (isWhitespace(string[i]) || string[i] === '{')) {
56
+ inTerm = false;
57
+ latestTerm = string.slice(currTermStart, i);
58
+
59
+ // We want to process the opening char again so the case will be properly registered.
60
+ if (string[i] === '{') {
61
+ i--;
62
+ }
63
+ }
64
+
65
+ // New term
66
+ else if (!inTerm && !isWhitespace(string[i])) {
67
+ const caseBody = string[i] === '{';
68
+
69
+ // If there's a previous term, we can either handle a whole
70
+ // case, or add that as an argument.
71
+ if (latestTerm && caseBody) {
72
+ const branchEndIndex = findClosingBracket(string, i);
73
+
74
+ if (branchEndIndex === -1) {
75
+ throw new Error(`Unbalanced curly braces in string: "${string}"`);
76
+ }
77
+
78
+ cases[latestTerm] = string.slice(i + 1, branchEndIndex); // Don't include the braces
79
+
80
+ i = branchEndIndex; // Will be moved up where needed at end of loop.
81
+ latestTerm = null;
82
+ }
83
+ else {
84
+ if (latestTerm) {
85
+ args.push(latestTerm);
86
+ latestTerm = null;
87
+ }
88
+
89
+ inTerm = true;
90
+ currTermStart = i;
91
+ }
92
+ }
93
+ i++;
94
+ }
95
+
96
+ if (inTerm) {
97
+ latestTerm = string.slice(currTermStart);
98
+ }
99
+
100
+ if (latestTerm) {
101
+ args.push(latestTerm);
102
+ }
103
+
104
+ return {
105
+ args,
106
+ cases
107
+ };
98
108
  }
99
109
 
100
110
  /**
@@ -108,19 +118,20 @@ function parseCases(string = '') {
108
118
  * could be found.
109
119
  */
110
120
  function findClosingBracket(string, fromIndex) {
111
- let depth = 0;
112
- for (let i = fromIndex + 1; i < string.length; i++) {
113
- let char = string.charAt(i);
114
- if (char === '}') {
115
- if (depth === 0) {
116
- return i;
117
- }
118
- depth--;
119
- } else if (char === '{') {
120
- depth++;
121
- }
122
- }
123
- return -1;
121
+ let depth = 0;
122
+ for (let i = fromIndex + 1; i < string.length; i++) {
123
+ let char = string.charAt(i);
124
+ if (char === '}') {
125
+ if (depth === 0) {
126
+ return i;
127
+ }
128
+ depth--;
129
+ }
130
+ else if (char === '{') {
131
+ depth++;
132
+ }
133
+ }
134
+ return -1;
124
135
  }
125
136
 
126
137
  /**
@@ -133,7 +144,7 @@ function findClosingBracket(string, fromIndex) {
133
144
  * in the formatted argument block.
134
145
  */
135
146
  function splitFormattedArgument(block) {
136
- return split(block.slice(1, -1), ',', 3);
147
+ return split(block.slice(1, -1), ',', 3);
137
148
  }
138
149
 
139
150
  /**
@@ -148,22 +159,22 @@ function splitFormattedArgument(block) {
148
159
  * @return {string[]}
149
160
  */
150
161
  function split(string, separator, limit, accumulator = []) {
151
- if (!string) {
152
- return accumulator;
153
- }
154
- if (limit === 1) {
155
- accumulator.push(string);
156
- return accumulator;
157
- }
158
- let indexOfDelimiter = string.indexOf(separator);
159
- if (indexOfDelimiter === -1) {
160
- accumulator.push(string);
161
- return accumulator;
162
- }
163
- let head = string.substring(0, indexOfDelimiter).trim();
164
- let tail = string.substring(indexOfDelimiter + separator.length + 1).trim();
165
- accumulator.push(head);
166
- return split(tail, separator, limit - 1, accumulator);
162
+ if (!string) {
163
+ return accumulator;
164
+ }
165
+ if (limit === 1) {
166
+ accumulator.push(string);
167
+ return accumulator;
168
+ }
169
+ let indexOfDelimiter = string.indexOf(separator);
170
+ if (indexOfDelimiter === -1) {
171
+ accumulator.push(string);
172
+ return accumulator;
173
+ }
174
+ let head = string.substring(0, indexOfDelimiter).trim();
175
+ let tail = string.substring(indexOfDelimiter + separator.length + 1).trim();
176
+ accumulator.push(head);
177
+ return split(tail, separator, limit - 1, accumulator);
167
178
  }
168
179
 
169
180
  /*
@@ -217,80 +228,88 @@ function split(string, separator, limit, accumulator = []) {
217
228
  * @author Emanuel Rabina
218
229
  */
219
230
  class MessageFormatter {
220
- /**
221
- * Creates a new formatter that can work using any of the custom type handlers
222
- * you register.
223
- *
224
- * @param {string} locale
225
- * @param {Record<string,TypeHandler>} [typeHandlers]
226
- * Optional object where the keys are the names of the types to register,
227
- * their values being the functions that will return a nicely formatted
228
- * string for the data and locale they are given.
229
- */
230
- constructor(locale, typeHandlers = {}) {
231
- this.locale = locale;
232
- this.typeHandlers = typeHandlers;
233
- }
234
-
235
- /**
236
- * Formats an ICU message syntax string using `values` for placeholder data
237
- * and any currently-registered type handlers.
238
- *
239
- * @type {(message: string, values?: FormatValues) => string}
240
- */
241
- format = functionUtils.memoize((message, values = {}) => {
242
- return this.process(message, values).flat(Infinity).join('');
243
- });
244
-
245
- /**
246
- * Process an ICU message syntax string using `values` for placeholder data
247
- * and any currently-registered type handlers. The result of this method is
248
- * an array of the component parts after they have been processed in turn by
249
- * their own type handlers. This raw output is useful for other renderers,
250
- * eg: React where components can be used instead of being forced to return
251
- * raw strings.
252
- *
253
- * This method is used by {@link MessageFormatter#format} where it acts as a
254
- * string renderer.
255
- *
256
- * @param {string} message
257
- * @param {FormatValues} [values]
258
- * @return {any[]}
259
- */
260
- process(message, values = {}) {
261
- if (!message) {
262
- return [];
263
- }
264
- let blockStartIndex = message.indexOf('{');
265
- if (blockStartIndex !== -1) {
266
- let blockEndIndex = findClosingBracket(message, blockStartIndex);
267
- if (blockEndIndex !== -1) {
268
- let block = message.substring(blockStartIndex, blockEndIndex + 1);
269
- if (block) {
270
- let result = [];
271
- let head = message.substring(0, blockStartIndex);
272
- if (head) {
273
- result.push(head);
274
- }
275
- let [key, type, format] = splitFormattedArgument(block);
276
- let body = values[key];
277
- if (body === null || body === undefined) {
278
- body = '';
279
- }
280
- let typeHandler = type && this.typeHandlers[type];
281
- result.push(typeHandler ? typeHandler(body, format, this.locale, values, this.process.bind(this)) : body);
282
- let tail = message.substring(blockEndIndex + 1);
283
- if (tail) {
284
- result.push(this.process(tail, values));
285
- }
286
- return result;
287
- }
288
- } else {
289
- throw new Error(`Unbalanced curly braces in string: "${message}"`);
290
- }
291
- }
292
- return [message];
293
- }
231
+
232
+ /**
233
+ * Creates a new formatter that can work using any of the custom type handlers
234
+ * you register.
235
+ *
236
+ * @param {string} locale
237
+ * @param {Record<string,TypeHandler>} [typeHandlers]
238
+ * Optional object where the keys are the names of the types to register,
239
+ * their values being the functions that will return a nicely formatted
240
+ * string for the data and locale they are given.
241
+ */
242
+ constructor(locale, typeHandlers = {}) {
243
+
244
+ this.locale = locale;
245
+ this.typeHandlers = typeHandlers;
246
+ }
247
+
248
+ /**
249
+ * Formats an ICU message syntax string using `values` for placeholder data
250
+ * and any currently-registered type handlers.
251
+ *
252
+ * @type {(message: string, values?: FormatValues) => string}
253
+ */
254
+ format = functionUtils.memoize((message, values = {}) => {
255
+
256
+ return this.process(message, values).flat(Infinity).join('');
257
+ });
258
+
259
+ /**
260
+ * Process an ICU message syntax string using `values` for placeholder data
261
+ * and any currently-registered type handlers. The result of this method is
262
+ * an array of the component parts after they have been processed in turn by
263
+ * their own type handlers. This raw output is useful for other renderers,
264
+ * eg: React where components can be used instead of being forced to return
265
+ * raw strings.
266
+ *
267
+ * This method is used by {@link MessageFormatter#format} where it acts as a
268
+ * string renderer.
269
+ *
270
+ * @param {string} message
271
+ * @param {FormatValues} [values]
272
+ * @return {any[]}
273
+ */
274
+ process(message, values = {}) {
275
+
276
+ if (!message) {
277
+ return [];
278
+ }
279
+
280
+ let blockStartIndex = message.indexOf('{');
281
+ if (blockStartIndex !== -1) {
282
+ let blockEndIndex = findClosingBracket(message, blockStartIndex);
283
+ if (blockEndIndex !== -1) {
284
+ let block = message.substring(blockStartIndex, blockEndIndex + 1);
285
+ if (block) {
286
+ let result = [];
287
+ let head = message.substring(0, blockStartIndex);
288
+ if (head) {
289
+ result.push(head);
290
+ }
291
+ let [key, type, format] = splitFormattedArgument(block);
292
+ let body = values[key];
293
+ if (body === null || body === undefined) {
294
+ body = '';
295
+ }
296
+ let typeHandler = type && this.typeHandlers[type];
297
+ result.push(typeHandler ?
298
+ typeHandler(body, format, this.locale, values, this.process.bind(this)) :
299
+ body);
300
+ let tail = message.substring(blockEndIndex + 1);
301
+ if (tail) {
302
+ result.push(this.process(tail, values));
303
+ }
304
+ return result;
305
+ }
306
+ }
307
+ else {
308
+ throw new Error(`Unbalanced curly braces in string: "${message}"`);
309
+ }
310
+ }
311
+ return [message];
312
+ }
294
313
  }
295
314
 
296
315
  /*
@@ -309,11 +328,13 @@ class MessageFormatter {
309
328
  * limitations under the License.
310
329
  */
311
330
 
331
+
312
332
  let pluralFormatter;
333
+
313
334
  let keyCounter = 0;
314
335
 
315
336
  // All the special keywords that can be used in `plural` blocks for the various branches
316
- const ONE = 'one';
337
+ const ONE = 'one';
317
338
  const OTHER$1 = 'other';
318
339
 
319
340
  /**
@@ -323,29 +344,35 @@ const OTHER$1 = 'other';
323
344
  * @return {{caseBody: string, numberValues: object}}
324
345
  */
325
346
  function replaceNumberSign(caseBody, value) {
326
- let i = 0;
327
- let output = '';
328
- let numBraces = 0;
329
- const numberValues = {};
330
- while (i < caseBody.length) {
331
- if (caseBody[i] === '#' && !numBraces) {
332
- let keyParam = `__hashToken${keyCounter++}`;
333
- output += `{${keyParam}, number}`;
334
- numberValues[keyParam] = value;
335
- } else {
336
- output += caseBody[i];
337
- }
338
- if (caseBody[i] === '{') {
339
- numBraces++;
340
- } else if (caseBody[i] === '}') {
341
- numBraces--;
342
- }
343
- i++;
344
- }
345
- return {
346
- caseBody: output,
347
- numberValues
348
- };
347
+ let i = 0;
348
+ let output = '';
349
+ let numBraces = 0;
350
+ const numberValues = {};
351
+
352
+ while (i < caseBody.length) {
353
+ if (caseBody[i] === '#' && !numBraces) {
354
+ let keyParam = `__hashToken${keyCounter++}`;
355
+ output += `{${keyParam}, number}`;
356
+ numberValues[keyParam] = value;
357
+ }
358
+ else {
359
+ output += caseBody[i];
360
+ }
361
+
362
+ if (caseBody[i] === '{') {
363
+ numBraces++;
364
+ }
365
+ else if (caseBody[i] === '}') {
366
+ numBraces--;
367
+ }
368
+
369
+ i++;
370
+ }
371
+
372
+ return {
373
+ caseBody: output,
374
+ numberValues
375
+ };
349
376
  }
350
377
 
351
378
  /**
@@ -363,47 +390,48 @@ function replaceNumberSign(caseBody, value) {
363
390
  * @return {any | any[]}
364
391
  */
365
392
  function pluralTypeHandler(value, matches, locale, values, process) {
366
- const {
367
- args,
368
- cases
369
- } = parseCases(matches);
370
- let intValue = parseInt(value);
371
- args.forEach(arg => {
372
- if (arg.startsWith('offset:')) {
373
- intValue -= parseInt(arg.slice('offset:'.length));
374
- }
375
- });
376
- const keywordPossibilities = [];
377
- if ('PluralRules' in Intl) {
378
- // Effectively memoize because instantiation of `Int.*` objects is expensive.
379
- if (pluralFormatter === undefined || pluralFormatter.resolvedOptions().locale !== locale) {
380
- pluralFormatter = new Intl.PluralRules(locale);
381
- }
382
- const pluralKeyword = pluralFormatter.select(intValue);
383
-
384
- // Other is always added last with least priority, so we don't want to add it here.
385
- if (pluralKeyword !== OTHER$1) {
386
- keywordPossibilities.push(pluralKeyword);
387
- }
388
- }
389
- if (intValue === 1) {
390
- keywordPossibilities.push(ONE);
391
- }
392
- keywordPossibilities.push(`=${intValue}`, OTHER$1);
393
- for (let i = 0; i < keywordPossibilities.length; i++) {
394
- const keyword = keywordPossibilities[i];
395
- if (keyword in cases) {
396
- const {
397
- caseBody,
398
- numberValues
399
- } = replaceNumberSign(cases[keyword], intValue);
400
- return process(caseBody, {
401
- ...values,
402
- ...numberValues
403
- });
404
- }
405
- }
406
- return value;
393
+ const {args, cases} = parseCases(matches);
394
+
395
+ let intValue = parseInt(value);
396
+
397
+ args.forEach((arg) => {
398
+ if (arg.startsWith('offset:')) {
399
+ intValue -= parseInt(arg.slice('offset:'.length));
400
+ }
401
+ });
402
+
403
+ const keywordPossibilities = [];
404
+
405
+ if ('PluralRules' in Intl) {
406
+ // Effectively memoize because instantiation of `Int.*` objects is expensive.
407
+ if (pluralFormatter === undefined || pluralFormatter.resolvedOptions().locale !== locale) {
408
+ pluralFormatter = new Intl.PluralRules(locale);
409
+ }
410
+
411
+ const pluralKeyword = pluralFormatter.select(intValue);
412
+
413
+ // Other is always added last with least priority, so we don't want to add it here.
414
+ if (pluralKeyword !== OTHER$1) {
415
+ keywordPossibilities.push(pluralKeyword);
416
+ }
417
+ }
418
+ if (intValue === 1) {
419
+ keywordPossibilities.push(ONE);
420
+ }
421
+ keywordPossibilities.push(`=${intValue}`, OTHER$1);
422
+
423
+ for (let i = 0; i < keywordPossibilities.length; i++) {
424
+ const keyword = keywordPossibilities[i];
425
+ if (keyword in cases) {
426
+ const {caseBody, numberValues} = replaceNumberSign(cases[keyword], intValue);
427
+ return process(caseBody, {
428
+ ...values,
429
+ ...numberValues
430
+ });
431
+ }
432
+ }
433
+
434
+ return value;
407
435
  }
408
436
 
409
437
  /*
@@ -422,6 +450,7 @@ function pluralTypeHandler(value, matches, locale, values, process) {
422
450
  * limitations under the License.
423
451
  */
424
452
 
453
+
425
454
  const OTHER = 'other';
426
455
 
427
456
  /**
@@ -439,15 +468,16 @@ const OTHER = 'other';
439
468
  * @return {any | any[]}
440
469
  */
441
470
  function selectTypeHandler(value, matches, locale, values, process) {
442
- const {
443
- cases
444
- } = parseCases(matches);
445
- if (value in cases) {
446
- return process(cases[value], values);
447
- } else if (OTHER in cases) {
448
- return process(cases[OTHER], values);
449
- }
450
- return value;
471
+ const {cases} = parseCases(matches);
472
+
473
+ if (value in cases) {
474
+ return process(cases[value], values);
475
+ }
476
+ else if (OTHER in cases) {
477
+ return process(cases[OTHER], values);
478
+ }
479
+
480
+ return value;
451
481
  }
452
482
 
453
483
  exports.MessageFormatter = MessageFormatter;