@ultraq/icu-message-formatter 0.12.0-beta.0 → 0.13.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 +16 -0
- package/README.md +3 -2
- package/dist/icu-message-formatter.es.min.js +1 -1
- package/dist/icu-message-formatter.es.min.js.map +1 -1
- package/dist/icu-message-formatter.min.js +1 -1
- package/dist/icu-message-formatter.min.js.map +1 -1
- package/index.d.ts +5 -0
- package/lib/icu-message-formatter.cjs.js +222 -244
- package/lib/icu-message-formatter.cjs.js.map +1 -1
- package/lib/icu-message-formatter.es.js +221 -234
- package/lib/icu-message-formatter.es.js.map +1 -1
- package/package.json +31 -23
- package/rollup.config.dist.js +1 -2
- package/source/IcuMessageFormatter.js +16 -5
- package/source/MessageFormatter.js +35 -9
- package/source/MessageFormatter.test.js +8 -8
- package/source/pluralTypeHandler.js +14 -14
- package/source/pluralTypeHandler.test.js +17 -17
- package/source/selectTypeHandler.js +10 -10
- package/source/selectTypeHandler.test.js +3 -3
- package/source/utilities.js +29 -21
- package/source/utilities.test.js +6 -6
- package/types/IcuMessageFormatter.d.ts +4 -0
- package/types/MessageFormatter.d.ts +71 -0
- package/types/pluralTypeHandler.d.ts +15 -0
- package/types/selectTypeHandler.d.ts +15 -0
- package/types/typedefs.d.ts +3 -0
- package/types/utilities.d.ts +52 -0
package/source/utilities.js
CHANGED
@@ -15,18 +15,25 @@
|
|
15
15
|
*/
|
16
16
|
|
17
17
|
/**
|
18
|
-
*
|
19
|
-
*
|
20
|
-
*
|
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.
|
21
29
|
*
|
22
|
-
* This util splits "matches" portions provided to the aforementioned
|
23
|
-
*
|
24
|
-
*
|
25
|
-
*
|
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).
|
26
34
|
*
|
27
|
-
* @param {
|
28
|
-
* @return {
|
29
|
-
* The `arguments` key points to a list of prepended arguments.
|
35
|
+
* @param {string} string
|
36
|
+
* @return {ParseCasesResult}
|
30
37
|
*/
|
31
38
|
export function parseCases(string) {
|
32
39
|
const isWhitespace = ch => /\s/.test(ch);
|
@@ -100,10 +107,11 @@ export function parseCases(string) {
|
|
100
107
|
* Finds the index of the matching closing curly bracket, including through
|
101
108
|
* strings that could have nested brackets.
|
102
109
|
*
|
103
|
-
* @param {
|
104
|
-
* @param {
|
105
|
-
* @return {
|
106
|
-
* closing bracket
|
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.
|
107
115
|
*/
|
108
116
|
export function findClosingBracket(string, fromIndex) {
|
109
117
|
let depth = 0;
|
@@ -126,8 +134,8 @@ export function findClosingBracket(string, fromIndex) {
|
|
126
134
|
* Split a `{key, type, format}` block into those 3 parts, taking into account
|
127
135
|
* nested message syntax that can exist in the `format` part.
|
128
136
|
*
|
129
|
-
* @param {
|
130
|
-
* @return {
|
137
|
+
* @param {string} block
|
138
|
+
* @return {string[]}
|
131
139
|
* An array with `key`, `type`, and `format` items in that order, if present
|
132
140
|
* in the formatted argument block.
|
133
141
|
*/
|
@@ -140,11 +148,11 @@ export function splitFormattedArgument(block) {
|
|
140
148
|
* remainder of the string to be grouped together in a final entry.
|
141
149
|
*
|
142
150
|
* @private
|
143
|
-
* @param {
|
144
|
-
* @param {
|
145
|
-
* @param {
|
146
|
-
* @param {
|
147
|
-
* @return {
|
151
|
+
* @param {string} string
|
152
|
+
* @param {string} separator
|
153
|
+
* @param {number} limit
|
154
|
+
* @param {string[]} accumulator
|
155
|
+
* @return {string[]}
|
148
156
|
*/
|
149
157
|
function split(string, separator, limit, accumulator = []) {
|
150
158
|
if (!string) {
|
package/source/utilities.test.js
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
*/
|
16
16
|
|
17
17
|
/* eslint-env jest */
|
18
|
-
import {
|
18
|
+
import {parseCases} from './utilities';
|
19
19
|
|
20
20
|
/**
|
21
21
|
* Tests for the `parseCases` util.
|
@@ -45,13 +45,13 @@ describe('parseCases', function() {
|
|
45
45
|
test('multiple cases', function() {
|
46
46
|
let result = parseCases('key1 {case1} key2 {case2} key3 {case3}');
|
47
47
|
expect(result.args).toStrictEqual([]);
|
48
|
-
expect(result.cases).toStrictEqual({
|
48
|
+
expect(result.cases).toStrictEqual({key1: 'case1', key2: 'case2', key3: 'case3'});
|
49
49
|
});
|
50
50
|
|
51
51
|
test('multiple cases with symbols', function() {
|
52
52
|
let result = parseCases('=key1 {case1} &key2 {case2} key3 {case3}');
|
53
53
|
expect(result.args).toStrictEqual([]);
|
54
|
-
expect(result.cases).toStrictEqual({
|
54
|
+
expect(result.cases).toStrictEqual({'=key1': 'case1', '&key2': 'case2', key3: 'case3'});
|
55
55
|
});
|
56
56
|
|
57
57
|
test('multiple cases with inconsistent whitespace', function() {
|
@@ -61,13 +61,13 @@ describe('parseCases', function() {
|
|
61
61
|
key2 {case2}
|
62
62
|
key3 {case3}`);
|
63
63
|
expect(result.args).toStrictEqual([]);
|
64
|
-
expect(result.cases).toStrictEqual({
|
64
|
+
expect(result.cases).toStrictEqual({key1: 'case1', key2: 'case2', key3: 'case3'});
|
65
65
|
});
|
66
66
|
|
67
67
|
test('multiple cases with minimal whitespace', function() {
|
68
68
|
let result = parseCases(`key1{case1}key2{case2}key3{case3}`);
|
69
69
|
expect(result.args).toStrictEqual([]);
|
70
|
-
expect(result.cases).toStrictEqual({
|
70
|
+
expect(result.cases).toStrictEqual({key1: 'case1', key2: 'case2', key3: 'case3'});
|
71
71
|
});
|
72
72
|
|
73
73
|
test('multiple cases with complex bodies', function() {
|
@@ -77,7 +77,7 @@ describe('parseCases', function() {
|
|
77
77
|
key2 {=key1 {case1} &key2 {case2} key3 {case3}}
|
78
78
|
key3 {}`);
|
79
79
|
expect(result.args).toStrictEqual([]);
|
80
|
-
expect(result.cases).toStrictEqual({
|
80
|
+
expect(result.cases).toStrictEqual({key1: '{}{}{}{{{{}}}}', key2: '=key1 {case1} &key2 {case2} key3 {case3}', key3: ''});
|
81
81
|
});
|
82
82
|
});
|
83
83
|
|
@@ -0,0 +1,4 @@
|
|
1
|
+
export { default as MessageFormatter } from "./MessageFormatter.js";
|
2
|
+
export { default as pluralTypeHandler } from "./pluralTypeHandler.js";
|
3
|
+
export { default as selectTypeHandler } from "./selectTypeHandler.js";
|
4
|
+
export { findClosingBracket, parseCases, splitFormattedArgument } from "./utilities.js";
|
@@ -0,0 +1,71 @@
|
|
1
|
+
/**
|
2
|
+
* @typedef {Record<string,any>} FormatValues
|
3
|
+
*/
|
4
|
+
/**
|
5
|
+
* @callback ProcessFunction
|
6
|
+
* @param {string} message
|
7
|
+
* @param {FormatValues} [values={}]
|
8
|
+
* @return {any[]}
|
9
|
+
*/
|
10
|
+
/**
|
11
|
+
* @callback TypeHandler
|
12
|
+
* @param {any} value
|
13
|
+
* The object which matched the key of the block being processed.
|
14
|
+
* @param {string} matches
|
15
|
+
* Any format options associated with the block being processed.
|
16
|
+
* @param {string} locale
|
17
|
+
* The locale to use for formatting.
|
18
|
+
* @param {FormatValues} values
|
19
|
+
* The object of placeholder data given to the original `format`/`process`
|
20
|
+
* call.
|
21
|
+
* @param {ProcessFunction} process
|
22
|
+
* The `process` function itself so that sub-messages can be processed by type
|
23
|
+
* handlers.
|
24
|
+
* @return {any | any[]}
|
25
|
+
*/
|
26
|
+
/**
|
27
|
+
* The main class for formatting messages.
|
28
|
+
*
|
29
|
+
* @author Emanuel Rabina
|
30
|
+
*/
|
31
|
+
export default class MessageFormatter {
|
32
|
+
/**
|
33
|
+
* Creates a new formatter that can work using any of the custom type handlers
|
34
|
+
* you register.
|
35
|
+
*
|
36
|
+
* @param {string} locale
|
37
|
+
* @param {Record<string,TypeHandler>} [typeHandlers]
|
38
|
+
* Optional object where the keys are the names of the types to register,
|
39
|
+
* their values being the functions that will return a nicely formatted
|
40
|
+
* string for the data and locale they are given.
|
41
|
+
*/
|
42
|
+
constructor(locale: string, typeHandlers?: Record<string, TypeHandler>);
|
43
|
+
locale: string;
|
44
|
+
typeHandlers: Record<string, TypeHandler>;
|
45
|
+
/**
|
46
|
+
* Formats an ICU message syntax string using `values` for placeholder data
|
47
|
+
* and any currently-registered type handlers.
|
48
|
+
*
|
49
|
+
* @type {(message: string, values?: FormatValues) => string}
|
50
|
+
*/
|
51
|
+
format: (message: string, values?: FormatValues) => string;
|
52
|
+
/**
|
53
|
+
* Process an ICU message syntax string using `values` for placeholder data
|
54
|
+
* and any currently-registered type handlers. The result of this method is
|
55
|
+
* an array of the component parts after they have been processed in turn by
|
56
|
+
* their own type handlers. This raw output is useful for other renderers,
|
57
|
+
* eg: React where components can be used instead of being forced to return
|
58
|
+
* raw strings.
|
59
|
+
*
|
60
|
+
* This method is used by {@link MessageFormatter#format} where it acts as a
|
61
|
+
* string renderer.
|
62
|
+
*
|
63
|
+
* @param {string} message
|
64
|
+
* @param {FormatValues} [values]
|
65
|
+
* @return {any[]}
|
66
|
+
*/
|
67
|
+
process(message: string, values?: FormatValues): any[];
|
68
|
+
}
|
69
|
+
export type FormatValues = Record<string, any>;
|
70
|
+
export type ProcessFunction = (message: string, values?: FormatValues) => any[];
|
71
|
+
export type TypeHandler = (value: any, matches: string, locale: string, values: FormatValues, process: ProcessFunction) => any | any[];
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/**
|
2
|
+
* Handler for `plural` statements within ICU message syntax strings. Returns
|
3
|
+
* a formatted string for the branch that closely matches the current value.
|
4
|
+
*
|
5
|
+
* See https://formatjs.io/docs/core-concepts/icu-syntax#plural-format for more
|
6
|
+
* details on how the `plural` statement works.
|
7
|
+
*
|
8
|
+
* @param {string} value
|
9
|
+
* @param {string} matches
|
10
|
+
* @param {string} locale
|
11
|
+
* @param {Record<string,any>} values
|
12
|
+
* @param {(message: string, values?: Record<string,any>) => any[]} process
|
13
|
+
* @return {any | any[]}
|
14
|
+
*/
|
15
|
+
export default function pluralTypeHandler(value: string, matches: string, locale: string, values: Record<string, any>, process: (message: string, values?: Record<string, any>) => any[]): any | any[];
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/**
|
2
|
+
* Handler for `select` statements within ICU message syntax strings. Returns
|
3
|
+
* a formatted string for the branch that closely matches the current value.
|
4
|
+
*
|
5
|
+
* See https://formatjs.io/docs/core-concepts/icu-syntax#select-format for more
|
6
|
+
* details on how the `select` statement works.
|
7
|
+
*
|
8
|
+
* @param {string} value
|
9
|
+
* @param {string} matches
|
10
|
+
* @param {string} locale
|
11
|
+
* @param {Record<string,any>} values
|
12
|
+
* @param {(message: string, values?: Record<string,any>) => any[]} process
|
13
|
+
* @return {any | any[]}
|
14
|
+
*/
|
15
|
+
export default function selectTypeHandler(value: string, matches: string, locale: string, values: Record<string, any>, process: (message: string, values?: Record<string, any>) => any[]): any | any[];
|
@@ -0,0 +1,52 @@
|
|
1
|
+
/**
|
2
|
+
* @typedef ParseCasesResult
|
3
|
+
* @property {string[]} args
|
4
|
+
* A list of prepended arguments.
|
5
|
+
* @property {Record<string,string>} cases
|
6
|
+
* A map of all cases.
|
7
|
+
*/
|
8
|
+
/**
|
9
|
+
* Most branch-based type handlers are based around "cases". For example,
|
10
|
+
* `select` and `plural` compare compare a value to "case keys" to choose a
|
11
|
+
* subtranslation.
|
12
|
+
*
|
13
|
+
* This util splits "matches" portions provided to the aforementioned handlers
|
14
|
+
* into case strings, and extracts any prepended arguments (for example,
|
15
|
+
* `plural` supports an `offset:n` argument used for populating the magic `#`
|
16
|
+
* variable).
|
17
|
+
*
|
18
|
+
* @param {string} string
|
19
|
+
* @return {ParseCasesResult}
|
20
|
+
*/
|
21
|
+
export function parseCases(string: string): ParseCasesResult;
|
22
|
+
/**
|
23
|
+
* Finds the index of the matching closing curly bracket, including through
|
24
|
+
* strings that could have nested brackets.
|
25
|
+
*
|
26
|
+
* @param {string} string
|
27
|
+
* @param {number} fromIndex
|
28
|
+
* @return {number}
|
29
|
+
* The index of the matching closing bracket, or -1 if no closing bracket
|
30
|
+
* could be found.
|
31
|
+
*/
|
32
|
+
export function findClosingBracket(string: string, fromIndex: number): number;
|
33
|
+
/**
|
34
|
+
* Split a `{key, type, format}` block into those 3 parts, taking into account
|
35
|
+
* nested message syntax that can exist in the `format` part.
|
36
|
+
*
|
37
|
+
* @param {string} block
|
38
|
+
* @return {string[]}
|
39
|
+
* An array with `key`, `type`, and `format` items in that order, if present
|
40
|
+
* in the formatted argument block.
|
41
|
+
*/
|
42
|
+
export function splitFormattedArgument(block: string): string[];
|
43
|
+
export type ParseCasesResult = {
|
44
|
+
/**
|
45
|
+
* A list of prepended arguments.
|
46
|
+
*/
|
47
|
+
args: string[];
|
48
|
+
/**
|
49
|
+
* A map of all cases.
|
50
|
+
*/
|
51
|
+
cases: Record<string, string>;
|
52
|
+
};
|