@discord/intl 0.5.2 → 0.6.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/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
- var keyless_json_1 = require("./keyless-json");
32
- var InternalIntlMessage = /** @class */ (function () {
33
- function InternalIntlMessage(messageOrAst, locale) {
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
- InternalIntlMessage.prototype.reserialize = function () {
42
- var result = { value: '' };
43
- serializeFormatJsAst(this.ast, result);
14
+ reserialize() {
15
+ const result = { value: '' };
16
+ serializeAst(this.ast, result);
44
17
  return result.value;
45
- };
46
- return InternalIntlMessage;
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 serializeFormatJsAst(ast, result) {
52
- var e_1, _a, e_2, _b, e_3, _c;
53
- try {
54
- for (var ast_1 = __values(ast), ast_1_1 = ast_1.next(); !ast_1_1.done; ast_1_1 = ast_1.next()) {
55
- var node = ast_1_1.value;
56
- switch (node.type) {
57
- case keyless_json_1.FormatJsNodeType.Literal:
58
- result.value += node.value;
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
- case keyless_json_1.FormatJsNodeType.Argument:
61
- // Empties are an artifact of our parsing strategy, not necessary here.
62
- if (result.value === '$_')
63
- return;
64
- result.value += '{' + node.value + '}';
65
- return;
66
- case keyless_json_1.FormatJsNodeType.Date:
67
- result.value += '{' + node.value + ', date';
68
- if (node.style != null) {
69
- result.value += ', ' + node.style;
70
- }
71
- result.value += '}';
72
- return;
73
- case keyless_json_1.FormatJsNodeType.Time:
74
- result.value += '{' + node.value + ', time';
75
- if (node.style != null) {
76
- result.value += ', ' + node.style;
77
- }
78
- result.value += '}';
79
- return;
80
- case keyless_json_1.FormatJsNodeType.Number:
81
- result.value += '{' + node.value + ', number';
82
- if (node.style != null) {
83
- result.value += ', ' + node.style;
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
- case keyless_json_1.FormatJsNodeType.Pound:
111
- result.value += '#';
112
- return;
113
- case keyless_json_1.FormatJsNodeType.Select: {
114
- result.value += '{' + node.value + ', select, ';
115
- try {
116
- for (var _g = (e_3 = void 0, __values(Object.entries(node.options))), _h = _g.next(); !_h.done; _h = _g.next()) {
117
- var _j = __read(_h.value, 2), name = _j[0], arm = _j[1];
118
- result.value += ' ' + name + ' {';
119
- serializeFormatJsAst(arm.value, result);
120
- result.value += '}';
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
- case keyless_json_1.FormatJsNodeType.Tag:
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 serializeFormatJsTag(node, result) {
147
- switch (node.value) {
87
+ function serializeAstTag(node, result) {
88
+ switch (node[intl_ast_1.AstNodeIndices.Value]) {
148
89
  case '$b':
149
90
  result.value += '**';
150
- serializeFormatJsAst(node.children, result);
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
- serializeFormatJsAst(node.children, result);
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
- serializeFormatJsAst(node.children, result);
101
+ serializeAst(node[intl_ast_1.AstNodeIndices.Children], result);
161
102
  result.value += '`';
162
103
  return;
163
104
  case '$p':
164
- serializeFormatJsAst(node.children, result);
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
- var _a = __read(node.children), target = _a[0], children = _a.slice(1);
111
+ const [target, ...children] = node[intl_ast_1.AstNodeIndices.Children];
171
112
  result.value += '[';
172
- serializeFormatJsAst(children, result);
113
+ serializeAst(children, result);
173
114
  result.value += '](';
174
115
  if (target != null) {
175
- serializeFormatJsAst([target], result);
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
- serializeFormatJsAst(node.children, result);
183
- result.value += '](' + node.value + ')';
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.5.2",
3
+ "version": "0.6.1",
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.1"
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 { FormatJsNode, FormatJsNodeType } from './keyless-json';
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: FormatJsNode[],
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].type === FormatJsNodeType.Literal) {
60
- builder.pushLiteralText(nodes[0].value);
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
- switch (node.type) {
65
+ const nodeType = node[AstNodeIndices.Type];
66
+ switch (nodeType) {
66
67
  case FormatJsNodeType.Literal:
67
- builder.pushLiteralText(node.value);
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 { value: variableName } = node;
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
- node.style in formatConfig.date
108
- ? formatConfig.date[node.style]
109
- : node.style != null
110
- ? parseDateTimeSkeleton(node.style)
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
- node.style in formatConfig.time
121
- ? formatConfig.time[node.style]
122
- : node.style != null
123
- ? parseDateTimeSkeleton(node.style)
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
- node.style in formatConfig.number
136
- ? formatConfig.number[node.style]
137
- : node.style != null
138
- ? parseNumberSkeleton(parseNumberSkeletonFromString(node.style))
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 { children } = node;
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 option = node.value in node.options ? node.options[node.value] : node.options.other;
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 `${node.value} is not a known option for select value ${variableName}. Valid options are ${Object.keys(node.options).join(', ')}`;
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 node.options) return node.options[exactSelector];
205
+ if (exactSelector in options) return options[exactSelector];
197
206
  const rule = formatters
198
- .getPluralRules(locales, { type: node.pluralType })
207
+ .getPluralRules(locales, { type: pluralType })
199
208
  // @ts-expect-error Assert this `as number` properly.
200
- .select((value as number) - (node.offset ?? 0));
201
- return node.options[rule] ?? node.options.other;
209
+ .select((value as number) - (offset ?? 0));
210
+ return options[rule] ?? options.other;
202
211
  })();
203
212
 
204
213
  if (option == null) {
205
- throw `${node.value} is not a known option for plural value ${variableName}. Valid options are ${Object.keys(node.options).join(', ')}`;
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: FormatJsNode[],
234
+ nodes: AstNode[],
226
235
  locales: string | string[],
227
236
  formatters: Formatters,
228
237
  formatConfig: FormatConfig,
@@ -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
- return (resolved.ast[0] as FormatJsLiteral).value;
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
  /**
@@ -1,7 +1,13 @@
1
- import { FormatJsNode } from './keyless-json';
1
+ import { AstNode, FullFormatJsNode } from '@discord/intl-ast';
2
2
  import { InternalIntlMessage } from './message';
3
3
 
4
- type MessagesData = Record<string, FormatJsNode[]>;
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
  }