@ultraq/icu-message-formatter 0.13.0 → 0.14.0
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 +7 -0
- package/README.md +2 -4
- package/dist/icu-message-formatter.browser.es.min.js +2 -0
- package/dist/icu-message-formatter.browser.es.min.js.map +1 -0
- package/dist/icu-message-formatter.browser.min.js +2 -0
- package/dist/icu-message-formatter.browser.min.js.map +1 -0
- package/{lib/icu-message-formatter.cjs.js → dist/icu-message-formatter.cjs} +37 -18
- package/dist/icu-message-formatter.cjs.map +1 -0
- package/dist/icu-message-formatter.d.cts +153 -0
- package/dist/icu-message-formatter.d.ts +153 -0
- package/{lib/icu-message-formatter.es.js → dist/icu-message-formatter.js} +37 -18
- package/dist/icu-message-formatter.js.map +1 -0
- package/package.json +25 -16
- package/dist/icu-message-formatter.es.min.js +0 -2
- package/dist/icu-message-formatter.es.min.js.map +0 -1
- package/dist/icu-message-formatter.min.js +0 -2
- package/dist/icu-message-formatter.min.js.map +0 -1
- package/index.d.ts +0 -5
- package/lib/icu-message-formatter.cjs.js.map +0 -1
- package/lib/icu-message-formatter.es.js.map +0 -1
- package/rollup.config.dist.js +0 -36
- package/rollup.config.js +0 -35
- package/source/IcuMessageFormatter.js +0 -20
- package/source/MessageFormatter.js +0 -138
- package/source/MessageFormatter.test.js +0 -153
- package/source/pluralTypeHandler.js +0 -122
- package/source/pluralTypeHandler.test.js +0 -188
- package/source/selectTypeHandler.js +0 -46
- package/source/selectTypeHandler.test.js +0 -59
- package/source/utilities.js +0 -174
- package/source/utilities.test.js +0 -123
- package/types/IcuMessageFormatter.d.ts +0 -4
- package/types/MessageFormatter.d.ts +0 -71
- package/types/pluralTypeHandler.d.ts +0 -15
- package/types/selectTypeHandler.d.ts +0 -15
- package/types/typedefs.d.ts +0 -3
- package/types/utilities.d.ts +0 -52
@@ -1,20 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
|
3
|
-
*
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
* you may not use this file except in compliance with the License.
|
6
|
-
* You may obtain a copy of the License at
|
7
|
-
*
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
*
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
* See the License for the specific language governing permissions and
|
14
|
-
* limitations under the License.
|
15
|
-
*/
|
16
|
-
|
17
|
-
export {default as MessageFormatter} from './MessageFormatter.js';
|
18
|
-
export {default as pluralTypeHandler} from './pluralTypeHandler.js';
|
19
|
-
export {default as selectTypeHandler} from './selectTypeHandler.js';
|
20
|
-
export {findClosingBracket, parseCases, splitFormattedArgument} from './utilities.js';
|
@@ -1,138 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
|
3
|
-
*
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
* you may not use this file except in compliance with the License.
|
6
|
-
* You may obtain a copy of the License at
|
7
|
-
*
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
*
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
* See the License for the specific language governing permissions and
|
14
|
-
* limitations under the License.
|
15
|
-
*/
|
16
|
-
|
17
|
-
import {findClosingBracket, splitFormattedArgument} from './utilities.js';
|
18
|
-
|
19
|
-
import {flatten} from '@ultraq/array-utils';
|
20
|
-
import {memoize} from '@ultraq/function-utils';
|
21
|
-
|
22
|
-
/**
|
23
|
-
* @typedef {Record<string,any>} FormatValues
|
24
|
-
*/
|
25
|
-
|
26
|
-
/**
|
27
|
-
* @callback ProcessFunction
|
28
|
-
* @param {string} message
|
29
|
-
* @param {FormatValues} [values={}]
|
30
|
-
* @return {any[]}
|
31
|
-
*/
|
32
|
-
|
33
|
-
/**
|
34
|
-
* @callback TypeHandler
|
35
|
-
* @param {any} value
|
36
|
-
* The object which matched the key of the block being processed.
|
37
|
-
* @param {string} matches
|
38
|
-
* Any format options associated with the block being processed.
|
39
|
-
* @param {string} locale
|
40
|
-
* The locale to use for formatting.
|
41
|
-
* @param {FormatValues} values
|
42
|
-
* The object of placeholder data given to the original `format`/`process`
|
43
|
-
* call.
|
44
|
-
* @param {ProcessFunction} process
|
45
|
-
* The `process` function itself so that sub-messages can be processed by type
|
46
|
-
* handlers.
|
47
|
-
* @return {any | any[]}
|
48
|
-
*/
|
49
|
-
|
50
|
-
/**
|
51
|
-
* The main class for formatting messages.
|
52
|
-
*
|
53
|
-
* @author Emanuel Rabina
|
54
|
-
*/
|
55
|
-
export default class MessageFormatter {
|
56
|
-
|
57
|
-
/**
|
58
|
-
* Creates a new formatter that can work using any of the custom type handlers
|
59
|
-
* you register.
|
60
|
-
*
|
61
|
-
* @param {string} locale
|
62
|
-
* @param {Record<string,TypeHandler>} [typeHandlers]
|
63
|
-
* Optional object where the keys are the names of the types to register,
|
64
|
-
* their values being the functions that will return a nicely formatted
|
65
|
-
* string for the data and locale they are given.
|
66
|
-
*/
|
67
|
-
constructor(locale, typeHandlers = {}) {
|
68
|
-
|
69
|
-
this.locale = locale;
|
70
|
-
this.typeHandlers = typeHandlers;
|
71
|
-
}
|
72
|
-
|
73
|
-
/**
|
74
|
-
* Formats an ICU message syntax string using `values` for placeholder data
|
75
|
-
* and any currently-registered type handlers.
|
76
|
-
*
|
77
|
-
* @type {(message: string, values?: FormatValues) => string}
|
78
|
-
*/
|
79
|
-
format = memoize((message, values = {}) => {
|
80
|
-
|
81
|
-
return flatten(this.process(message, values)).join('');
|
82
|
-
});
|
83
|
-
|
84
|
-
/**
|
85
|
-
* Process an ICU message syntax string using `values` for placeholder data
|
86
|
-
* and any currently-registered type handlers. The result of this method is
|
87
|
-
* an array of the component parts after they have been processed in turn by
|
88
|
-
* their own type handlers. This raw output is useful for other renderers,
|
89
|
-
* eg: React where components can be used instead of being forced to return
|
90
|
-
* raw strings.
|
91
|
-
*
|
92
|
-
* This method is used by {@link MessageFormatter#format} where it acts as a
|
93
|
-
* string renderer.
|
94
|
-
*
|
95
|
-
* @param {string} message
|
96
|
-
* @param {FormatValues} [values]
|
97
|
-
* @return {any[]}
|
98
|
-
*/
|
99
|
-
process(message, values = {}) {
|
100
|
-
|
101
|
-
if (!message) {
|
102
|
-
return [];
|
103
|
-
}
|
104
|
-
|
105
|
-
let blockStartIndex = message.indexOf('{');
|
106
|
-
if (blockStartIndex !== -1) {
|
107
|
-
let blockEndIndex = findClosingBracket(message, blockStartIndex);
|
108
|
-
if (blockEndIndex !== -1) {
|
109
|
-
let block = message.substring(blockStartIndex, blockEndIndex + 1);
|
110
|
-
if (block) {
|
111
|
-
let result = [];
|
112
|
-
let head = message.substring(0, blockStartIndex);
|
113
|
-
if (head) {
|
114
|
-
result.push(head);
|
115
|
-
}
|
116
|
-
let [key, type, format] = splitFormattedArgument(block);
|
117
|
-
let body = values[key];
|
118
|
-
if (body === null || body === undefined) {
|
119
|
-
body = '';
|
120
|
-
}
|
121
|
-
let typeHandler = type && this.typeHandlers[type];
|
122
|
-
result.push(typeHandler ?
|
123
|
-
typeHandler(body, format, this.locale, values, this.process.bind(this)) :
|
124
|
-
body);
|
125
|
-
let tail = message.substring(blockEndIndex + 1);
|
126
|
-
if (tail) {
|
127
|
-
result.push(this.process(tail, values));
|
128
|
-
}
|
129
|
-
return result;
|
130
|
-
}
|
131
|
-
}
|
132
|
-
else {
|
133
|
-
throw new Error(`Unbalanced curly braces in string: "${message}"`);
|
134
|
-
}
|
135
|
-
}
|
136
|
-
return [message];
|
137
|
-
}
|
138
|
-
}
|
@@ -1,153 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
|
3
|
-
*
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
* you may not use this file except in compliance with the License.
|
6
|
-
* You may obtain a copy of the License at
|
7
|
-
*
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
*
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
* See the License for the specific language governing permissions and
|
14
|
-
* limitations under the License.
|
15
|
-
*/
|
16
|
-
|
17
|
-
/* eslint-env jest */
|
18
|
-
import MessageFormatter from './MessageFormatter';
|
19
|
-
import pluralTypeHandler from './pluralTypeHandler';
|
20
|
-
import selectTypeHandler from './selectTypeHandler';
|
21
|
-
|
22
|
-
/**
|
23
|
-
* Tests for the ICU message formatter.
|
24
|
-
*/
|
25
|
-
describe('MessageFormatter', function() {
|
26
|
-
|
27
|
-
describe('#format', function() {
|
28
|
-
|
29
|
-
test('Basic string replacement', function() {
|
30
|
-
let formatter = new MessageFormatter('en-NZ');
|
31
|
-
let message = formatter.format('Hey {name}, that\'s gonna cost you!', {
|
32
|
-
name: 'Emanuel'
|
33
|
-
});
|
34
|
-
expect(message).toBe('Hey Emanuel, that\'s gonna cost you!');
|
35
|
-
});
|
36
|
-
|
37
|
-
test('Options finding when containing nested types', function() {
|
38
|
-
let locale = 'en-NZ';
|
39
|
-
let selectSpy = jest.fn(selectTypeHandler);
|
40
|
-
let formatter = new MessageFormatter(locale, {
|
41
|
-
select: selectSpy
|
42
|
-
});
|
43
|
-
|
44
|
-
let values = {
|
45
|
-
at: 'asap'
|
46
|
-
};
|
47
|
-
let message = formatter.format(
|
48
|
-
'Hit me up {at, select, date {at {specificDate, date}} asap {as soon as possible}}',
|
49
|
-
values
|
50
|
-
);
|
51
|
-
|
52
|
-
expect(selectSpy).toHaveBeenCalledWith(
|
53
|
-
values.at,
|
54
|
-
'date {at {specificDate, date}} asap {as soon as possible}',
|
55
|
-
locale,
|
56
|
-
values,
|
57
|
-
expect.any(Function)
|
58
|
-
);
|
59
|
-
expect(message).toBe('Hit me up as soon as possible');
|
60
|
-
});
|
61
|
-
|
62
|
-
test('Replaces multiple instances of the same parameter', function() {
|
63
|
-
let formatter = new MessageFormatter('en-NZ');
|
64
|
-
let message = formatter.format('Hello {name} {name}!', {
|
65
|
-
name: 'Emanuel'
|
66
|
-
});
|
67
|
-
expect(message).toBe('Hello Emanuel Emanuel!');
|
68
|
-
});
|
69
|
-
|
70
|
-
test('Returns empty strings for null/undefined message values', function() {
|
71
|
-
let formatter = new MessageFormatter('en-NZ');
|
72
|
-
[null, undefined].forEach(value => {
|
73
|
-
let message = formatter.format(value);
|
74
|
-
expect(message).toBe('');
|
75
|
-
});
|
76
|
-
});
|
77
|
-
|
78
|
-
test('Lets falsey values in parameters through', function() {
|
79
|
-
let formatter = new MessageFormatter('en-NZ');
|
80
|
-
[0, false].forEach(value => {
|
81
|
-
let message = formatter.format(`{param}`, {
|
82
|
-
param: value
|
83
|
-
});
|
84
|
-
expect(message).toBe(value.toString());
|
85
|
-
});
|
86
|
-
});
|
87
|
-
|
88
|
-
test('Plural inside select', function() {
|
89
|
-
let formatter = new MessageFormatter('en-NZ', {
|
90
|
-
plural: pluralTypeHandler,
|
91
|
-
select: selectTypeHandler
|
92
|
-
});
|
93
|
-
let message = `{gender_of_host, select,
|
94
|
-
female {{num_guests, plural,
|
95
|
-
=0 {{host} does not give a party.}
|
96
|
-
=1 {{host} invites {guest} to her party.}
|
97
|
-
=2 {{host} invites {guest} and one other person to her party.}
|
98
|
-
other {{host} invites {guest} and # other people to her party.}
|
99
|
-
}}
|
100
|
-
male {{num_guests, plural,
|
101
|
-
=0 {{host} does not give a party.}
|
102
|
-
=1 {{host} invites {guest} to his party.}
|
103
|
-
=2 {{host} invites {guest} and one other person to his party.}
|
104
|
-
other {{host} invites {guest} and # other people to his party.}
|
105
|
-
}}
|
106
|
-
other {{num_guests, plural,
|
107
|
-
=0 {{host} does not give a party.}
|
108
|
-
=1 {{host} invites {guest} to their party.}
|
109
|
-
=2 {{host} invites {guest} and one other person to their party.}
|
110
|
-
other {{host} invites {guest} and # other people to their party.}
|
111
|
-
}}
|
112
|
-
}`;
|
113
|
-
|
114
|
-
expect(formatter.format(message, {'gender_of_host': 'male', host: 'John', 'num_guests': 115, guest: 'you'})).toBe('John invites you and 115 other people to his party.');
|
115
|
-
expect(formatter.format(message, {'gender_of_host': 'female', host: 'John', 'num_guests': 1, guest: 'you'})).toBe('John invites you to her party.');
|
116
|
-
expect(formatter.format(message, {'gender_of_host': 'other', host: 'John', 'num_guests': 2, guest: 'you'})).toBe('John invites you and one other person to their party.');
|
117
|
-
expect(formatter.format(message, {'gender_of_host': 'other', host: 'John', 'num_guests': 12345, guest: 'you'})).toBe('John invites you and 12345 other people to their party.');
|
118
|
-
});
|
119
|
-
|
120
|
-
test('Select inside plural', function() {
|
121
|
-
let formatter = new MessageFormatter('en-NZ', {
|
122
|
-
plural: pluralTypeHandler,
|
123
|
-
select: selectTypeHandler
|
124
|
-
});
|
125
|
-
|
126
|
-
let message = `{num_guests, plural,
|
127
|
-
=0 {{host} does not give a party.}
|
128
|
-
=1 {{gender_of_host, select,
|
129
|
-
female {{host} invites {guest} to her party.}
|
130
|
-
male {{host} invites {guest} to his party.}
|
131
|
-
other {{host} invites {guest} to their party.}
|
132
|
-
}}
|
133
|
-
=2 {{gender_of_host, select,
|
134
|
-
female {{host} invites {guest} and one other person to her party.}
|
135
|
-
male {{host} invites {guest} and one other person to his party.}
|
136
|
-
other {{host} invites {guest} and one other person to their party.}
|
137
|
-
}}
|
138
|
-
other {{gender_of_host, select,
|
139
|
-
female {{host} invites {guest} and # other people to her party.}
|
140
|
-
male {{host} invites {guest} and # other people to his party.}
|
141
|
-
other {{host} invites {guest} and # other people to their party.}
|
142
|
-
}}
|
143
|
-
}`;
|
144
|
-
|
145
|
-
expect(formatter.format(message, {'gender_of_host': 'female', host: 'John', 'num_guests': 1, guest: 'you'})).toBe('John invites you to her party.');
|
146
|
-
expect(formatter.format(message, {'gender_of_host': 'other', host: 'John', 'num_guests': 2, guest: 'you'})).toBe('John invites you and one other person to their party.');
|
147
|
-
|
148
|
-
// number sign only works in the immediately corresponding layer! Can't use a nested one.
|
149
|
-
expect(formatter.format(message, {'gender_of_host': 'male', host: 'John', 'num_guests': 115, guest: 'you'})).toBe('John invites you and # other people to his party.');
|
150
|
-
expect(formatter.format(message, {'gender_of_host': 'other', host: 'John', 'num_guests': 12345, guest: 'you'})).toBe('John invites you and # other people to their party.');
|
151
|
-
});
|
152
|
-
});
|
153
|
-
});
|
@@ -1,122 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
|
3
|
-
*
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
* you may not use this file except in compliance with the License.
|
6
|
-
* You may obtain a copy of the License at
|
7
|
-
*
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
*
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
* See the License for the specific language governing permissions and
|
14
|
-
* limitations under the License.
|
15
|
-
*/
|
16
|
-
|
17
|
-
import {parseCases} from './utilities.js';
|
18
|
-
|
19
|
-
let pluralFormatter;
|
20
|
-
|
21
|
-
let keyCounter = 0;
|
22
|
-
|
23
|
-
// All the special keywords that can be used in `plural` blocks for the various branches
|
24
|
-
const ONE = 'one';
|
25
|
-
const OTHER = 'other';
|
26
|
-
|
27
|
-
/**
|
28
|
-
* @private
|
29
|
-
* @param {string} caseBody
|
30
|
-
* @param {number} value
|
31
|
-
* @return {{caseBody: string, numberValues: object}}
|
32
|
-
*/
|
33
|
-
function replaceNumberSign(caseBody, value) {
|
34
|
-
let i = 0;
|
35
|
-
let output = '';
|
36
|
-
let numBraces = 0;
|
37
|
-
const numberValues = {};
|
38
|
-
|
39
|
-
while (i < caseBody.length) {
|
40
|
-
if (caseBody[i] === '#' && !numBraces) {
|
41
|
-
let keyParam = `__hashToken${keyCounter++}`;
|
42
|
-
output += `{${keyParam}, number}`;
|
43
|
-
numberValues[keyParam] = value;
|
44
|
-
}
|
45
|
-
else {
|
46
|
-
output += caseBody[i];
|
47
|
-
}
|
48
|
-
|
49
|
-
if (caseBody[i] === '{') {
|
50
|
-
numBraces++;
|
51
|
-
}
|
52
|
-
else if (caseBody[i] === '}') {
|
53
|
-
numBraces--;
|
54
|
-
}
|
55
|
-
|
56
|
-
i++;
|
57
|
-
}
|
58
|
-
|
59
|
-
return {
|
60
|
-
caseBody: output,
|
61
|
-
numberValues
|
62
|
-
};
|
63
|
-
}
|
64
|
-
|
65
|
-
/**
|
66
|
-
* Handler for `plural` statements within ICU message syntax strings. Returns
|
67
|
-
* a formatted string for the branch that closely matches the current value.
|
68
|
-
*
|
69
|
-
* See https://formatjs.io/docs/core-concepts/icu-syntax#plural-format for more
|
70
|
-
* details on how the `plural` statement works.
|
71
|
-
*
|
72
|
-
* @param {string} value
|
73
|
-
* @param {string} matches
|
74
|
-
* @param {string} locale
|
75
|
-
* @param {Record<string,any>} values
|
76
|
-
* @param {(message: string, values?: Record<string,any>) => any[]} process
|
77
|
-
* @return {any | any[]}
|
78
|
-
*/
|
79
|
-
export default function pluralTypeHandler(value, matches = '', locale, values, process) {
|
80
|
-
const {args, cases} = parseCases(matches);
|
81
|
-
|
82
|
-
let intValue = parseInt(value);
|
83
|
-
|
84
|
-
args.forEach((arg) => {
|
85
|
-
if (arg.startsWith('offset:')) {
|
86
|
-
intValue -= parseInt(arg.slice('offset:'.length));
|
87
|
-
}
|
88
|
-
});
|
89
|
-
|
90
|
-
const keywordPossibilities = [];
|
91
|
-
|
92
|
-
if ('PluralRules' in Intl) {
|
93
|
-
// Effectively memoize because instantiation of `Int.*` objects is expensive.
|
94
|
-
if (pluralFormatter === undefined || pluralFormatter.resolvedOptions().locale !== locale) {
|
95
|
-
pluralFormatter = new Intl.PluralRules(locale);
|
96
|
-
}
|
97
|
-
|
98
|
-
const pluralKeyword = pluralFormatter.select(intValue);
|
99
|
-
|
100
|
-
// Other is always added last with least priority, so we don't want to add it here.
|
101
|
-
if (pluralKeyword !== OTHER) {
|
102
|
-
keywordPossibilities.push(pluralKeyword);
|
103
|
-
}
|
104
|
-
}
|
105
|
-
if (intValue === 1) {
|
106
|
-
keywordPossibilities.push(ONE);
|
107
|
-
}
|
108
|
-
keywordPossibilities.push(`=${intValue}`, OTHER);
|
109
|
-
|
110
|
-
for (let i = 0; i < keywordPossibilities.length; i++) {
|
111
|
-
const keyword = keywordPossibilities[i];
|
112
|
-
if (keyword in cases) {
|
113
|
-
const {caseBody, numberValues} = replaceNumberSign(cases[keyword], intValue);
|
114
|
-
return process(caseBody, {
|
115
|
-
...values,
|
116
|
-
...numberValues
|
117
|
-
});
|
118
|
-
}
|
119
|
-
}
|
120
|
-
|
121
|
-
return value;
|
122
|
-
}
|
@@ -1,188 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
|
3
|
-
*
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
* you may not use this file except in compliance with the License.
|
6
|
-
* You may obtain a copy of the License at
|
7
|
-
*
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
*
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
* See the License for the specific language governing permissions and
|
14
|
-
* limitations under the License.
|
15
|
-
*/
|
16
|
-
|
17
|
-
/* eslint-env jest */
|
18
|
-
import MessageFormatter from './MessageFormatter.js';
|
19
|
-
import pluralTypeHandler from './pluralTypeHandler.js';
|
20
|
-
|
21
|
-
/**
|
22
|
-
* Tests for the `plural` handler.
|
23
|
-
*/
|
24
|
-
describe('pluralTypeHandler', function() {
|
25
|
-
|
26
|
-
describe('Branch selection', function() {
|
27
|
-
const formatter = new MessageFormatter('en-NZ', {
|
28
|
-
plural: pluralTypeHandler
|
29
|
-
});
|
30
|
-
const message = `Stop thinking about {value, plural,
|
31
|
-
one {that donut}
|
32
|
-
=2 {that delicious duo of donuts}
|
33
|
-
=3 {that triumphant trio of donuts}
|
34
|
-
other {those donuts}
|
35
|
-
}`;
|
36
|
-
|
37
|
-
test('=n', function() {
|
38
|
-
let result = formatter.format(message, {value: 2});
|
39
|
-
expect(result).toBe('Stop thinking about that delicious duo of donuts');
|
40
|
-
|
41
|
-
result = formatter.format(message, {value: 3});
|
42
|
-
expect(result).toBe('Stop thinking about that triumphant trio of donuts');
|
43
|
-
});
|
44
|
-
|
45
|
-
test('other', function() {
|
46
|
-
let result = formatter.format(message, {value: 1000});
|
47
|
-
expect(result).toBe('Stop thinking about those donuts');
|
48
|
-
});
|
49
|
-
|
50
|
-
test('other fallback', function() {
|
51
|
-
let result = formatter.format(message);
|
52
|
-
expect(result).toBe('Stop thinking about those donuts');
|
53
|
-
});
|
54
|
-
});
|
55
|
-
|
56
|
-
describe('Fallback of emitting the value', function() {
|
57
|
-
const formatter = new MessageFormatter('en-NZ', {
|
58
|
-
plural: pluralTypeHandler
|
59
|
-
});
|
60
|
-
|
61
|
-
test('No matching branch', function() {
|
62
|
-
let message = 'I have {cows, plural, one {a cow} two {some cows}}';
|
63
|
-
let result = formatter.format(message, {cows: 7});
|
64
|
-
expect(result).toBe('I have 7');
|
65
|
-
});
|
66
|
-
|
67
|
-
test('Keyword without branch', function() {
|
68
|
-
let message = 'I have {cows, plural, other}';
|
69
|
-
let result = formatter.format(message, {cows: 7});
|
70
|
-
expect(result).toBe('I have 7');
|
71
|
-
});
|
72
|
-
});
|
73
|
-
|
74
|
-
describe('Special # token', function() {
|
75
|
-
let message = '{days, plural, one {# day} other {# days}}...';
|
76
|
-
|
77
|
-
test('Emit value without a registered number handler', function() {
|
78
|
-
let formatter = new MessageFormatter('en-NZ', {
|
79
|
-
plural: pluralTypeHandler
|
80
|
-
});
|
81
|
-
let result = formatter.format(message, {days: 7});
|
82
|
-
expect(result).toBe('7 days...');
|
83
|
-
});
|
84
|
-
|
85
|
-
test('Use the registered number handler', function() {
|
86
|
-
let numberTypeHandler = jest.fn((value, options, values, locale) => new Intl.NumberFormat(locale).format(value));
|
87
|
-
let formatter = new MessageFormatter('en-NZ', {
|
88
|
-
number: numberTypeHandler,
|
89
|
-
plural: pluralTypeHandler
|
90
|
-
});
|
91
|
-
let result = formatter.format(message, {days: 1000});
|
92
|
-
expect(result).toBe('1,000 days...');
|
93
|
-
});
|
94
|
-
|
95
|
-
test('Number signs for nested plurals doesnt apply top level count to inner sign', function() {
|
96
|
-
let formatter = new MessageFormatter('en-NZ', {
|
97
|
-
plural: pluralTypeHandler
|
98
|
-
});
|
99
|
-
let result = formatter.format(`{id, plural,
|
100
|
-
other {ID: # {count, plural,
|
101
|
-
other {Count: #}
|
102
|
-
}}
|
103
|
-
}`, {id: 5, count: 11});
|
104
|
-
expect(result).toBe('ID: 5 Count: 11');
|
105
|
-
});
|
106
|
-
});
|
107
|
-
|
108
|
-
describe('Supports offset argument', function() {
|
109
|
-
const formatter = new MessageFormatter('en-NZ', {
|
110
|
-
plural: pluralTypeHandler
|
111
|
-
});
|
112
|
-
|
113
|
-
test('If unspecified, offset is null', function() {
|
114
|
-
let message = '{days, plural, one {one day} =2 {two days} other {# days}}...';
|
115
|
-
|
116
|
-
let result = formatter.format(message, {days: 5});
|
117
|
-
expect(result).toBe('5 days...');
|
118
|
-
});
|
119
|
-
|
120
|
-
test('Specified offset will be applied via =X branch', function() {
|
121
|
-
let message = '{days, plural, offset:3 one {one day} =2 {two days} other {# days}}...';
|
122
|
-
|
123
|
-
let result = formatter.format(message, {days: 5});
|
124
|
-
expect(result).toBe('two days...');
|
125
|
-
});
|
126
|
-
|
127
|
-
test('Specified offset will be applied via number sign', function() {
|
128
|
-
let message = '{days, plural, offset:1 one {one day} =2 {two days} other {# days}}...';
|
129
|
-
|
130
|
-
let result = formatter.format(message, {days: 5});
|
131
|
-
expect(result).toBe('4 days...');
|
132
|
-
});
|
133
|
-
|
134
|
-
test('Specified offset will be applied via `one` keyword', function() {
|
135
|
-
let message = '{days, plural, offset:4 one {one day} =2 {two days} other {# days}}...';
|
136
|
-
|
137
|
-
let result = formatter.format(message, {days: 5});
|
138
|
-
expect(result).toBe('one day...');
|
139
|
-
});
|
140
|
-
});
|
141
|
-
|
142
|
-
|
143
|
-
describe('Intl PluralRules', function() {
|
144
|
-
const formatterAr = new MessageFormatter('ar', {
|
145
|
-
plural: pluralTypeHandler
|
146
|
-
});
|
147
|
-
|
148
|
-
const messageAr = `{value, plural,
|
149
|
-
zero {ZERO}
|
150
|
-
one {ONE}
|
151
|
-
two {TWO}
|
152
|
-
few {FEW}
|
153
|
-
other {OTHER}
|
154
|
-
}`;
|
155
|
-
|
156
|
-
test('zero', function() {
|
157
|
-
let result = formatterAr.format(messageAr, {value: 0});
|
158
|
-
expect(result).toBe('ZERO');
|
159
|
-
});
|
160
|
-
|
161
|
-
test('one', function() {
|
162
|
-
let result = formatterAr.format(messageAr, {value: 1});
|
163
|
-
expect(result).toBe('ONE');
|
164
|
-
});
|
165
|
-
|
166
|
-
test('two', function() {
|
167
|
-
let result = formatterAr.format(messageAr, {value: 2});
|
168
|
-
expect(result).toBe('TWO');
|
169
|
-
});
|
170
|
-
|
171
|
-
test('few', function() {
|
172
|
-
let result = formatterAr.format(messageAr, {value: 3});
|
173
|
-
expect(result).toBe('FEW');
|
174
|
-
});
|
175
|
-
|
176
|
-
test('other', function() {
|
177
|
-
let result = formatterAr.format(messageAr, {value: 17});
|
178
|
-
expect(result).toBe('OTHER');
|
179
|
-
});
|
180
|
-
});
|
181
|
-
|
182
|
-
describe('Empty matches', function() {
|
183
|
-
test('No matching branch', function() {
|
184
|
-
let result = pluralTypeHandler('some value');
|
185
|
-
expect(result).toBe('some value');
|
186
|
-
});
|
187
|
-
});
|
188
|
-
});
|
@@ -1,46 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
|
3
|
-
*
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
* you may not use this file except in compliance with the License.
|
6
|
-
* You may obtain a copy of the License at
|
7
|
-
*
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
*
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
* See the License for the specific language governing permissions and
|
14
|
-
* limitations under the License.
|
15
|
-
*/
|
16
|
-
|
17
|
-
import {parseCases} from './utilities.js';
|
18
|
-
|
19
|
-
const OTHER = 'other';
|
20
|
-
|
21
|
-
/**
|
22
|
-
* Handler for `select` statements within ICU message syntax strings. Returns
|
23
|
-
* a formatted string for the branch that closely matches the current value.
|
24
|
-
*
|
25
|
-
* See https://formatjs.io/docs/core-concepts/icu-syntax#select-format for more
|
26
|
-
* details on how the `select` statement works.
|
27
|
-
*
|
28
|
-
* @param {string} value
|
29
|
-
* @param {string} matches
|
30
|
-
* @param {string} locale
|
31
|
-
* @param {Record<string,any>} values
|
32
|
-
* @param {(message: string, values?: Record<string,any>) => any[]} process
|
33
|
-
* @return {any | any[]}
|
34
|
-
*/
|
35
|
-
export default function selectTypeHandler(value, matches = '', locale, values, process) {
|
36
|
-
const {cases} = parseCases(matches);
|
37
|
-
|
38
|
-
if (value in cases) {
|
39
|
-
return process(cases[value], values);
|
40
|
-
}
|
41
|
-
else if (OTHER in cases) {
|
42
|
-
return process(cases[OTHER], values);
|
43
|
-
}
|
44
|
-
|
45
|
-
return value;
|
46
|
-
}
|