@discord/intl 0.5.2 → 0.6.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/dist/format.d.ts +3 -3
- package/dist/format.js +76 -107
- package/dist/formatters/ast.js +30 -77
- package/dist/formatters/markdown.js +18 -65
- package/dist/formatters/react.js +26 -72
- package/dist/formatters/string.js +15 -58
- package/dist/hash.js +11 -11
- package/dist/intl-manager.js +38 -89
- package/dist/message-loader.d.ts +8 -2
- package/dist/message-loader.js +93 -225
- package/dist/message.d.ts +3 -3
- package/dist/message.js +76 -135
- package/package.json +3 -2
- package/src/format.ts +39 -30
- package/src/intl-manager.ts +4 -2
- package/src/message-loader.ts +8 -2
- package/src/message.ts +48 -39
- package/dist/keyless-json.d.ts +0 -68
- package/dist/keyless-json.js +0 -107
- package/src/keyless-json.ts +0 -161
package/dist/message.js
CHANGED
|
@@ -1,186 +1,127 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __values = (this && this.__values) || function(o) {
|
|
3
|
-
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4
|
-
if (m) return m.call(o);
|
|
5
|
-
if (o && typeof o.length === "number") return {
|
|
6
|
-
next: function () {
|
|
7
|
-
if (o && i >= o.length) o = void 0;
|
|
8
|
-
return { value: o && o[i++], done: !o };
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
12
|
-
};
|
|
13
|
-
var __read = (this && this.__read) || function (o, n) {
|
|
14
|
-
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
15
|
-
if (!m) return o;
|
|
16
|
-
var i = m.call(o), r, ar = [], e;
|
|
17
|
-
try {
|
|
18
|
-
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
19
|
-
}
|
|
20
|
-
catch (error) { e = { error: error }; }
|
|
21
|
-
finally {
|
|
22
|
-
try {
|
|
23
|
-
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
24
|
-
}
|
|
25
|
-
finally { if (e) throw e.error; }
|
|
26
|
-
}
|
|
27
|
-
return ar;
|
|
28
|
-
};
|
|
29
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
3
|
exports.InternalIntlMessage = void 0;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
4
|
+
const intl_ast_1 = require("@discord/intl-ast");
|
|
5
|
+
class InternalIntlMessage {
|
|
6
|
+
constructor(messageOrAst, locale) {
|
|
34
7
|
this.locale = locale;
|
|
35
|
-
this.ast = messageOrAst;
|
|
8
|
+
this.ast = (0, intl_ast_1.isCompressedAst)(messageOrAst) ? messageOrAst : (0, intl_ast_1.compressFormatJsToAst)(messageOrAst);
|
|
36
9
|
}
|
|
37
10
|
/**
|
|
38
11
|
* Return a stringified serialization of this message's AST, with no
|
|
39
12
|
* formatting or values applied.
|
|
40
13
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
14
|
+
reserialize() {
|
|
15
|
+
const result = { value: '' };
|
|
16
|
+
serializeAst(this.ast, result);
|
|
44
17
|
return result.value;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
}());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
48
20
|
exports.InternalIntlMessage = InternalIntlMessage;
|
|
49
21
|
// Accepting an object as the `result` parameter lets the same string get passed around and
|
|
50
22
|
// appended, rather than creating a bunch of intermediate strings.
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
23
|
+
function serializeAst(ast, result) {
|
|
24
|
+
for (const node of ast) {
|
|
25
|
+
switch (node[intl_ast_1.AstNodeIndices.Type]) {
|
|
26
|
+
case intl_ast_1.FormatJsNodeType.Literal:
|
|
27
|
+
result.value += node[intl_ast_1.AstNodeIndices.Value];
|
|
28
|
+
return;
|
|
29
|
+
case intl_ast_1.FormatJsNodeType.Argument:
|
|
30
|
+
// Empties are an artifact of our parsing strategy, not necessary here.
|
|
31
|
+
if (result.value === '$_')
|
|
59
32
|
return;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
33
|
+
result.value += '{' + node[intl_ast_1.AstNodeIndices.Value] + '}';
|
|
34
|
+
return;
|
|
35
|
+
case intl_ast_1.FormatJsNodeType.Date:
|
|
36
|
+
result.value += '{' + node[intl_ast_1.AstNodeIndices.Value] + ', date';
|
|
37
|
+
if (node[intl_ast_1.AstNodeIndices.Style] != null) {
|
|
38
|
+
result.value += ', ' + node[intl_ast_1.AstNodeIndices.Style];
|
|
39
|
+
}
|
|
40
|
+
result.value += '}';
|
|
41
|
+
return;
|
|
42
|
+
case intl_ast_1.FormatJsNodeType.Time:
|
|
43
|
+
result.value += '{' + node[intl_ast_1.AstNodeIndices.Value] + ', time';
|
|
44
|
+
if (node[intl_ast_1.AstNodeIndices.Style] != null) {
|
|
45
|
+
result.value += ', ' + node[intl_ast_1.AstNodeIndices.Style];
|
|
46
|
+
}
|
|
47
|
+
result.value += '}';
|
|
48
|
+
return;
|
|
49
|
+
case intl_ast_1.FormatJsNodeType.Number:
|
|
50
|
+
result.value += '{' + node[intl_ast_1.AstNodeIndices.Value] + ', number';
|
|
51
|
+
if (node[intl_ast_1.AstNodeIndices.Style] != null) {
|
|
52
|
+
result.value += ', ' + node[intl_ast_1.AstNodeIndices.Style];
|
|
53
|
+
}
|
|
54
|
+
result.value += '}';
|
|
55
|
+
return;
|
|
56
|
+
case intl_ast_1.FormatJsNodeType.Plural: {
|
|
57
|
+
const pluralType = node[intl_ast_1.AstNodeIndices.PluralType] == 'ordinal' ? 'selectordinal' : 'plural';
|
|
58
|
+
result.value += '{' + node[intl_ast_1.AstNodeIndices.Value] + ', ' + pluralType + ', ';
|
|
59
|
+
if (node[intl_ast_1.AstNodeIndices.Offset]) {
|
|
60
|
+
result.value += 'offset:' + node[intl_ast_1.AstNodeIndices.Offset];
|
|
61
|
+
}
|
|
62
|
+
for (const [name, arm] of Object.entries(node[intl_ast_1.AstNodeIndices.Options])) {
|
|
63
|
+
result.value += ' ' + name + ' {';
|
|
64
|
+
serializeAst(arm.value, result);
|
|
85
65
|
result.value += '}';
|
|
86
|
-
return;
|
|
87
|
-
case keyless_json_1.FormatJsNodeType.Plural: {
|
|
88
|
-
var pluralType = node.pluralType == 'ordinal' ? 'selectordinal' : 'plural';
|
|
89
|
-
result.value += '{' + node.value + ', ' + pluralType + ', ';
|
|
90
|
-
if (node.offset) {
|
|
91
|
-
result.value += 'offset:' + node.offset;
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
for (var _d = (e_2 = void 0, __values(Object.entries(node.options))), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
95
|
-
var _f = __read(_e.value, 2), name = _f[0], arm = _f[1];
|
|
96
|
-
result.value += ' ' + name + ' {';
|
|
97
|
-
serializeFormatJsAst(arm.value, result);
|
|
98
|
-
result.value += '}';
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
102
|
-
finally {
|
|
103
|
-
try {
|
|
104
|
-
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
105
|
-
}
|
|
106
|
-
finally { if (e_2) throw e_2.error; }
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
109
66
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
124
|
-
finally {
|
|
125
|
-
try {
|
|
126
|
-
if (_h && !_h.done && (_c = _g.return)) _c.call(_g);
|
|
127
|
-
}
|
|
128
|
-
finally { if (e_3) throw e_3.error; }
|
|
129
|
-
}
|
|
130
|
-
return;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
case intl_ast_1.FormatJsNodeType.Pound:
|
|
70
|
+
result.value += '#';
|
|
71
|
+
return;
|
|
72
|
+
case intl_ast_1.FormatJsNodeType.Select: {
|
|
73
|
+
result.value += '{' + node[intl_ast_1.AstNodeIndices.Value] + ', select, ';
|
|
74
|
+
for (const [name, arm] of Object.entries(node[intl_ast_1.AstNodeIndices.Options])) {
|
|
75
|
+
result.value += ' ' + name + ' {';
|
|
76
|
+
serializeAst(arm.value, result);
|
|
77
|
+
result.value += '}';
|
|
131
78
|
}
|
|
132
|
-
|
|
133
|
-
serializeFormatJsTag(node, result);
|
|
134
|
-
return;
|
|
79
|
+
return;
|
|
135
80
|
}
|
|
81
|
+
case intl_ast_1.FormatJsNodeType.Tag:
|
|
82
|
+
serializeAstTag(node, result);
|
|
83
|
+
return;
|
|
136
84
|
}
|
|
137
85
|
}
|
|
138
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
139
|
-
finally {
|
|
140
|
-
try {
|
|
141
|
-
if (ast_1_1 && !ast_1_1.done && (_a = ast_1.return)) _a.call(ast_1);
|
|
142
|
-
}
|
|
143
|
-
finally { if (e_1) throw e_1.error; }
|
|
144
|
-
}
|
|
145
86
|
}
|
|
146
|
-
function
|
|
147
|
-
switch (node.
|
|
87
|
+
function serializeAstTag(node, result) {
|
|
88
|
+
switch (node[intl_ast_1.AstNodeIndices.Value]) {
|
|
148
89
|
case '$b':
|
|
149
90
|
result.value += '**';
|
|
150
|
-
|
|
91
|
+
serializeAst(node[intl_ast_1.AstNodeIndices.Children], result);
|
|
151
92
|
result.value += '**';
|
|
152
93
|
return;
|
|
153
94
|
case '$i':
|
|
154
95
|
result.value += '*';
|
|
155
|
-
|
|
96
|
+
serializeAst(node[intl_ast_1.AstNodeIndices.Children], result);
|
|
156
97
|
result.value += '*';
|
|
157
98
|
return;
|
|
158
99
|
case '$code':
|
|
159
100
|
result.value += '`';
|
|
160
|
-
|
|
101
|
+
serializeAst(node[intl_ast_1.AstNodeIndices.Children], result);
|
|
161
102
|
result.value += '`';
|
|
162
103
|
return;
|
|
163
104
|
case '$p':
|
|
164
|
-
|
|
105
|
+
serializeAst(node[intl_ast_1.AstNodeIndices.Children], result);
|
|
165
106
|
result.value += '\n\n';
|
|
166
107
|
return;
|
|
167
108
|
case '$link':
|
|
168
109
|
// The target is the first child of the link. We don't have to care if it's a placeholder
|
|
169
110
|
// or not, because the serialization will automatically remove the extra empty.
|
|
170
|
-
|
|
111
|
+
const [target, ...children] = node[intl_ast_1.AstNodeIndices.Children];
|
|
171
112
|
result.value += '[';
|
|
172
|
-
|
|
113
|
+
serializeAst(children, result);
|
|
173
114
|
result.value += '](';
|
|
174
115
|
if (target != null) {
|
|
175
|
-
|
|
116
|
+
serializeAst([target], result);
|
|
176
117
|
}
|
|
177
118
|
result.value += ')';
|
|
178
119
|
return;
|
|
179
120
|
default:
|
|
180
121
|
// Any other tag name is a hook, which just adds the `$[` on a link tag.
|
|
181
122
|
result.value += '$[';
|
|
182
|
-
|
|
183
|
-
result.value += '](' + node.
|
|
123
|
+
serializeAst(node[intl_ast_1.AstNodeIndices.Children], result);
|
|
124
|
+
result.value += '](' + node[intl_ast_1.AstNodeIndices.Value] + ')';
|
|
184
125
|
return;
|
|
185
126
|
}
|
|
186
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@discord/intl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Client runtime for managing messages and translations in a React project.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"@formatjs/icu-skeleton-parser": "1.8.2",
|
|
19
19
|
"@formatjs/intl": "^2.10.1",
|
|
20
20
|
"@intrnl/xxhash64": "^0.1.2",
|
|
21
|
-
"intl-messageformat": "^10.5.11"
|
|
21
|
+
"intl-messageformat": "^10.5.11",
|
|
22
|
+
"@discord/intl-ast": "0.6.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@swc/cli": "^0.3.12",
|
package/src/format.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
parseNumberSkeletonFromString,
|
|
24
24
|
} from '@formatjs/icu-skeleton-parser';
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { AstNode, AstNodeIndices, FormatJsNodeType } from '@discord/intl-ast';
|
|
27
27
|
import { Formatters, MissingValueError } from 'intl-messageformat';
|
|
28
28
|
import type { FormatConfig } from './format-config';
|
|
29
29
|
import type { RichTextTagNames } from './types';
|
|
@@ -47,7 +47,7 @@ export type FormatBuilderConstructor<Result> = new () => FormatBuilder<Result>;
|
|
|
47
47
|
|
|
48
48
|
export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>(
|
|
49
49
|
builder: Builder,
|
|
50
|
-
nodes:
|
|
50
|
+
nodes: AstNode[],
|
|
51
51
|
locales: string | string[],
|
|
52
52
|
formatters: Formatters,
|
|
53
53
|
formatConfig: FormatConfig,
|
|
@@ -56,15 +56,16 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
56
56
|
originalMessage?: string,
|
|
57
57
|
) {
|
|
58
58
|
// Hot path for static messages that are just parsed as a single string element.
|
|
59
|
-
if (nodes.length === 1 && nodes[0].
|
|
60
|
-
builder.pushLiteralText(nodes[0].
|
|
59
|
+
if (nodes.length === 1 && nodes[0][AstNodeIndices.Type] === FormatJsNodeType.Literal) {
|
|
60
|
+
builder.pushLiteralText(nodes[0][AstNodeIndices.Value]);
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
for (const node of nodes) {
|
|
65
|
-
|
|
65
|
+
const nodeType = node[AstNodeIndices.Type];
|
|
66
|
+
switch (nodeType) {
|
|
66
67
|
case FormatJsNodeType.Literal:
|
|
67
|
-
builder.pushLiteralText(node.
|
|
68
|
+
builder.pushLiteralText(node[AstNodeIndices.Value]);
|
|
68
69
|
continue;
|
|
69
70
|
|
|
70
71
|
case FormatJsNodeType.Pound:
|
|
@@ -78,7 +79,7 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
78
79
|
continue;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
const
|
|
82
|
+
const variableName = node[AstNodeIndices.Value];
|
|
82
83
|
// Enforce that all required values are provided by the caller, even if the
|
|
83
84
|
// actual value is falsy/undefined.
|
|
84
85
|
if (!(variableName in values) && !isRichTextTag(variableName)) {
|
|
@@ -86,8 +87,7 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
const value = values[variableName];
|
|
89
|
-
|
|
90
|
-
switch (node.type) {
|
|
90
|
+
switch (nodeType) {
|
|
91
91
|
case FormatJsNodeType.Argument:
|
|
92
92
|
// Empty values don't need to be added at all, they are purely for AST representation.
|
|
93
93
|
if (variableName == '$_') break;
|
|
@@ -101,26 +101,28 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
101
101
|
}
|
|
102
102
|
break;
|
|
103
103
|
case FormatJsNodeType.Date: {
|
|
104
|
+
const nodeStyle = node[AstNodeIndices.Style];
|
|
104
105
|
// Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
|
|
105
106
|
// so this manages parsing the skeleton as well before passing it onto the date formatter.
|
|
106
107
|
const style =
|
|
107
|
-
|
|
108
|
-
? formatConfig.date[
|
|
109
|
-
:
|
|
110
|
-
? parseDateTimeSkeleton(
|
|
108
|
+
nodeStyle in formatConfig.date
|
|
109
|
+
? formatConfig.date[nodeStyle]
|
|
110
|
+
: nodeStyle != null
|
|
111
|
+
? parseDateTimeSkeleton(nodeStyle)
|
|
111
112
|
: formatConfig.time.medium;
|
|
112
113
|
// @ts-expect-error Cast string values to dates properly.
|
|
113
114
|
builder.pushLiteralText(formatters.getDateTimeFormat(locales, style).format(value));
|
|
114
115
|
break;
|
|
115
116
|
}
|
|
116
117
|
case FormatJsNodeType.Time: {
|
|
118
|
+
const nodeStyle = node[AstNodeIndices.Style];
|
|
117
119
|
// Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
|
|
118
120
|
// so this manages parsing the skeleton as well before passing it onto the date formatter.
|
|
119
121
|
const style =
|
|
120
|
-
|
|
121
|
-
? formatConfig.time[
|
|
122
|
-
:
|
|
123
|
-
? parseDateTimeSkeleton(
|
|
122
|
+
nodeStyle in formatConfig.time
|
|
123
|
+
? formatConfig.time[nodeStyle]
|
|
124
|
+
: nodeStyle != null
|
|
125
|
+
? parseDateTimeSkeleton(nodeStyle)
|
|
124
126
|
: undefined; // TODO: parseSkeleton();
|
|
125
127
|
builder.pushLiteralText(
|
|
126
128
|
// @ts-expect-error Cast string values to dates properly.
|
|
@@ -129,13 +131,14 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
129
131
|
break;
|
|
130
132
|
}
|
|
131
133
|
case FormatJsNodeType.Number: {
|
|
134
|
+
const nodeStyle = node[AstNodeIndices.Style];
|
|
132
135
|
// Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
|
|
133
136
|
// so this manages parsing the skeleton as well before passing it onto the date formatter.
|
|
134
137
|
const style =
|
|
135
|
-
|
|
136
|
-
? formatConfig.number[
|
|
137
|
-
:
|
|
138
|
-
? parseNumberSkeleton(parseNumberSkeletonFromString(
|
|
138
|
+
nodeStyle in formatConfig.number
|
|
139
|
+
? formatConfig.number[nodeStyle]
|
|
140
|
+
: nodeStyle != null
|
|
141
|
+
? parseNumberSkeleton(parseNumberSkeletonFromString(nodeStyle))
|
|
139
142
|
: undefined;
|
|
140
143
|
const scaledValue =
|
|
141
144
|
// @ts-expect-error This is a weird cast that's not accurate, but works in the short term.
|
|
@@ -145,7 +148,7 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
case FormatJsNodeType.Tag: {
|
|
148
|
-
const
|
|
151
|
+
const children = node[AstNodeIndices.Children];
|
|
149
152
|
const appliedChildren = bindFormatValues(
|
|
150
153
|
builder.constructor as FormatBuilderConstructor<T>,
|
|
151
154
|
children,
|
|
@@ -175,9 +178,11 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
case FormatJsNodeType.Select: {
|
|
178
|
-
const
|
|
181
|
+
const value = node[AstNodeIndices.Value];
|
|
182
|
+
const options = node[AstNodeIndices.Options];
|
|
183
|
+
const option = value in options ? options[value] : options.other;
|
|
179
184
|
if (option == null) {
|
|
180
|
-
throw `${
|
|
185
|
+
throw `${value} is not a known option for select value ${variableName}. Valid options are ${Object.keys(options).join(', ')}`;
|
|
181
186
|
}
|
|
182
187
|
bindFormatValuesWithBuilder(
|
|
183
188
|
builder,
|
|
@@ -191,18 +196,22 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
191
196
|
}
|
|
192
197
|
|
|
193
198
|
case FormatJsNodeType.Plural: {
|
|
199
|
+
const value = node[AstNodeIndices.Value];
|
|
200
|
+
const options = node[AstNodeIndices.Options];
|
|
201
|
+
const offset = node[AstNodeIndices.Offset];
|
|
202
|
+
const pluralType = node[AstNodeIndices.PluralType];
|
|
194
203
|
const option = (() => {
|
|
195
204
|
const exactSelector = `=${value}`;
|
|
196
|
-
if (exactSelector in
|
|
205
|
+
if (exactSelector in options) return options[exactSelector];
|
|
197
206
|
const rule = formatters
|
|
198
|
-
.getPluralRules(locales, { type:
|
|
207
|
+
.getPluralRules(locales, { type: pluralType })
|
|
199
208
|
// @ts-expect-error Assert this `as number` properly.
|
|
200
|
-
.select((value as number) - (
|
|
201
|
-
return
|
|
209
|
+
.select((value as number) - (offset ?? 0));
|
|
210
|
+
return options[rule] ?? options.other;
|
|
202
211
|
})();
|
|
203
212
|
|
|
204
213
|
if (option == null) {
|
|
205
|
-
throw `${
|
|
214
|
+
throw `${value} is not a known option for plural value ${variableName}. Valid options are ${Object.keys(options).join(', ')}`;
|
|
206
215
|
}
|
|
207
216
|
bindFormatValuesWithBuilder(
|
|
208
217
|
builder,
|
|
@@ -222,7 +231,7 @@ export function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>
|
|
|
222
231
|
|
|
223
232
|
export function bindFormatValues<Result>(
|
|
224
233
|
Builder: FormatBuilderConstructor<Result>,
|
|
225
|
-
nodes:
|
|
234
|
+
nodes: AstNode[],
|
|
226
235
|
locales: string | string[],
|
|
227
236
|
formatters: Formatters,
|
|
228
237
|
formatConfig: FormatConfig,
|
package/src/intl-manager.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AstNodeIndices } from '@discord/intl-ast';
|
|
1
2
|
import { createIntl, IntlShape } from '@formatjs/intl';
|
|
2
3
|
import { Formatters } from 'intl-messageformat';
|
|
3
4
|
|
|
@@ -9,7 +10,6 @@ import type {
|
|
|
9
10
|
} from './types';
|
|
10
11
|
import { InternalIntlMessage } from './message';
|
|
11
12
|
import { bindFormatValues, FormatBuilderConstructor } from './format';
|
|
12
|
-
import { FormatJsLiteral } from './keyless-json';
|
|
13
13
|
import { DEFAULT_FORMAT_CONFIG, FormatConfig } from './format-config';
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -123,7 +123,9 @@ export class IntlManager {
|
|
|
123
123
|
// TODO: Figure out how to make this typing exact. This currently relies on
|
|
124
124
|
// the generic typing being sound enough to know that the message can only
|
|
125
125
|
// contain a single static text node and no placeholders or rich text.
|
|
126
|
-
|
|
126
|
+
// As it stands, this is _technically_ typed as string | undefined, even though
|
|
127
|
+
// TypeScript doesn't actually complain about it.
|
|
128
|
+
return resolved.ast[0][AstNodeIndices.Value];
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
/**
|
package/src/message-loader.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AstNode, FullFormatJsNode } from '@discord/intl-ast';
|
|
2
2
|
import { InternalIntlMessage } from './message';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Type representing the serialized content of a translations file, which is a record of hashed
|
|
6
|
+
* message keys to their AST structure. This type represents both compressed, keyless AstNodes as
|
|
7
|
+
* well as fully-typed, object FullFormatJsNodes as the message content, since either can be given
|
|
8
|
+
* depending on the configuration of the bundler/compiler.
|
|
9
|
+
*/
|
|
10
|
+
type MessagesData = Record<string, AstNode[] | FullFormatJsNode[]>;
|
|
5
11
|
export interface IntlMessageGetterAdditions {
|
|
6
12
|
onChange(callback: () => void): () => void;
|
|
7
13
|
}
|