@ultraq/icu-message-formatter 0.13.0 → 0.14.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.
- package/CHANGELOG.md +10 -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} +65 -52
- 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} +65 -52
- 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,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
|
-
}
|
@@ -1,59 +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 selectTypeHandler from './selectTypeHandler.js';
|
20
|
-
|
21
|
-
/**
|
22
|
-
* Tests for the `select` handler.
|
23
|
-
*/
|
24
|
-
describe('selectTypeHandler', function() {
|
25
|
-
|
26
|
-
const formatter = new MessageFormatter('en-NZ', {
|
27
|
-
select: selectTypeHandler
|
28
|
-
});
|
29
|
-
|
30
|
-
describe('Branch selection', function() {
|
31
|
-
let message = '{mood, select, happy {Good} sad {bad} other {Ambivalent}} morning';
|
32
|
-
|
33
|
-
test('Explicit branch', function() {
|
34
|
-
let result = formatter.format(message, {mood: 'happy'});
|
35
|
-
expect(result).toBe('Good morning');
|
36
|
-
});
|
37
|
-
|
38
|
-
test('Fallback branch', function() {
|
39
|
-
let result = formatter.format(message, {mood: 'angry'});
|
40
|
-
expect(result).toBe('Ambivalent morning');
|
41
|
-
});
|
42
|
-
});
|
43
|
-
|
44
|
-
describe('Fallback of emitting the value', function() {
|
45
|
-
|
46
|
-
test('No matching branch', function() {
|
47
|
-
let message = 'I am feeling {mood, select, happy {good}}';
|
48
|
-
let result = formatter.format(message, {mood: 'ambivalent'});
|
49
|
-
expect(result).toBe('I am feeling ambivalent');
|
50
|
-
});
|
51
|
-
});
|
52
|
-
|
53
|
-
describe('Empty matches', function() {
|
54
|
-
test('No matching branch', function() {
|
55
|
-
let result = selectTypeHandler('some value');
|
56
|
-
expect(result).toBe('some value');
|
57
|
-
});
|
58
|
-
});
|
59
|
-
});
|
package/source/utilities.js
DELETED
@@ -1,174 +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
|
-
/**
|
18
|
-
* @typedef ParseCasesResult
|
19
|
-
* @property {string[]} args
|
20
|
-
* A list of prepended arguments.
|
21
|
-
* @property {Record<string,string>} cases
|
22
|
-
* A map of all cases.
|
23
|
-
*/
|
24
|
-
|
25
|
-
/**
|
26
|
-
* Most branch-based type handlers are based around "cases". For example,
|
27
|
-
* `select` and `plural` compare compare a value to "case keys" to choose a
|
28
|
-
* subtranslation.
|
29
|
-
*
|
30
|
-
* This util splits "matches" portions provided to the aforementioned handlers
|
31
|
-
* into case strings, and extracts any prepended arguments (for example,
|
32
|
-
* `plural` supports an `offset:n` argument used for populating the magic `#`
|
33
|
-
* variable).
|
34
|
-
*
|
35
|
-
* @param {string} string
|
36
|
-
* @return {ParseCasesResult}
|
37
|
-
*/
|
38
|
-
export function parseCases(string) {
|
39
|
-
const isWhitespace = ch => /\s/.test(ch);
|
40
|
-
|
41
|
-
const args = [];
|
42
|
-
const cases = {};
|
43
|
-
|
44
|
-
let currTermStart = 0;
|
45
|
-
let latestTerm = null;
|
46
|
-
let inTerm = false;
|
47
|
-
|
48
|
-
let i = 0;
|
49
|
-
while (i < string.length) {
|
50
|
-
// Term ended
|
51
|
-
if (inTerm && (isWhitespace(string[i]) || string[i] === '{')) {
|
52
|
-
inTerm = false;
|
53
|
-
latestTerm = string.slice(currTermStart, i);
|
54
|
-
|
55
|
-
// We want to process the opening char again so the case will be properly registered.
|
56
|
-
if (string[i] === '{') {
|
57
|
-
i--;
|
58
|
-
}
|
59
|
-
}
|
60
|
-
|
61
|
-
// New term
|
62
|
-
else if (!inTerm && !isWhitespace(string[i])) {
|
63
|
-
const caseBody = string[i] === '{';
|
64
|
-
|
65
|
-
// If there's a previous term, we can either handle a whole
|
66
|
-
// case, or add that as an argument.
|
67
|
-
if (latestTerm && caseBody) {
|
68
|
-
const branchEndIndex = findClosingBracket(string, i);
|
69
|
-
|
70
|
-
if (branchEndIndex === -1) {
|
71
|
-
throw new Error(`Unbalanced curly braces in string: "${string}"`);
|
72
|
-
}
|
73
|
-
|
74
|
-
cases[latestTerm] = string.slice(i + 1, branchEndIndex); // Don't include the braces
|
75
|
-
|
76
|
-
i = branchEndIndex; // Will be moved up where needed at end of loop.
|
77
|
-
latestTerm = null;
|
78
|
-
}
|
79
|
-
else {
|
80
|
-
if (latestTerm) {
|
81
|
-
args.push(latestTerm);
|
82
|
-
latestTerm = null;
|
83
|
-
}
|
84
|
-
|
85
|
-
inTerm = true;
|
86
|
-
currTermStart = i;
|
87
|
-
}
|
88
|
-
}
|
89
|
-
i++;
|
90
|
-
}
|
91
|
-
|
92
|
-
if (inTerm) {
|
93
|
-
latestTerm = string.slice(currTermStart);
|
94
|
-
}
|
95
|
-
|
96
|
-
if (latestTerm) {
|
97
|
-
args.push(latestTerm);
|
98
|
-
}
|
99
|
-
|
100
|
-
return {
|
101
|
-
args,
|
102
|
-
cases
|
103
|
-
};
|
104
|
-
}
|
105
|
-
|
106
|
-
/**
|
107
|
-
* Finds the index of the matching closing curly bracket, including through
|
108
|
-
* strings that could have nested brackets.
|
109
|
-
*
|
110
|
-
* @param {string} string
|
111
|
-
* @param {number} fromIndex
|
112
|
-
* @return {number}
|
113
|
-
* The index of the matching closing bracket, or -1 if no closing bracket
|
114
|
-
* could be found.
|
115
|
-
*/
|
116
|
-
export function findClosingBracket(string, fromIndex) {
|
117
|
-
let depth = 0;
|
118
|
-
for (let i = fromIndex + 1; i < string.length; i++) {
|
119
|
-
let char = string.charAt(i);
|
120
|
-
if (char === '}') {
|
121
|
-
if (depth === 0) {
|
122
|
-
return i;
|
123
|
-
}
|
124
|
-
depth--;
|
125
|
-
}
|
126
|
-
else if (char === '{') {
|
127
|
-
depth++;
|
128
|
-
}
|
129
|
-
}
|
130
|
-
return -1;
|
131
|
-
}
|
132
|
-
|
133
|
-
/**
|
134
|
-
* Split a `{key, type, format}` block into those 3 parts, taking into account
|
135
|
-
* nested message syntax that can exist in the `format` part.
|
136
|
-
*
|
137
|
-
* @param {string} block
|
138
|
-
* @return {string[]}
|
139
|
-
* An array with `key`, `type`, and `format` items in that order, if present
|
140
|
-
* in the formatted argument block.
|
141
|
-
*/
|
142
|
-
export function splitFormattedArgument(block) {
|
143
|
-
return split(block.slice(1, -1), ',', 3);
|
144
|
-
}
|
145
|
-
|
146
|
-
/**
|
147
|
-
* Like `String.prototype.split()` but where the limit parameter causes the
|
148
|
-
* remainder of the string to be grouped together in a final entry.
|
149
|
-
*
|
150
|
-
* @private
|
151
|
-
* @param {string} string
|
152
|
-
* @param {string} separator
|
153
|
-
* @param {number} limit
|
154
|
-
* @param {string[]} accumulator
|
155
|
-
* @return {string[]}
|
156
|
-
*/
|
157
|
-
function split(string, separator, limit, accumulator = []) {
|
158
|
-
if (!string) {
|
159
|
-
return accumulator;
|
160
|
-
}
|
161
|
-
if (limit === 1) {
|
162
|
-
accumulator.push(string);
|
163
|
-
return accumulator;
|
164
|
-
}
|
165
|
-
let indexOfDelimiter = string.indexOf(separator);
|
166
|
-
if (indexOfDelimiter === -1) {
|
167
|
-
accumulator.push(string);
|
168
|
-
return accumulator;
|
169
|
-
}
|
170
|
-
let head = string.substring(0, indexOfDelimiter).trim();
|
171
|
-
let tail = string.substring(indexOfDelimiter + separator.length + 1).trim();
|
172
|
-
accumulator.push(head);
|
173
|
-
return split(tail, separator, limit - 1, accumulator);
|
174
|
-
}
|