@discord/intl 0.2.0 → 0.2.1-canary.dff7875
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/formatters/ast.js +1 -9
- package/dist/formatters/markdown.js +1 -1
- package/dist/formatters/react.d.ts +4 -1
- package/dist/formatters/react.js +8 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -3
- package/dist/intl-manager.d.ts +0 -7
- package/dist/intl-manager.js +5 -12
- package/dist/message-loader.d.ts +42 -3
- package/dist/message-loader.js +135 -22
- package/dist/message.d.ts +1 -15
- package/dist/message.js +1 -83
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/formatters/ast.ts +1 -10
- package/src/formatters/markdown.ts +1 -1
- package/src/formatters/react.ts +15 -9
- package/src/index.ts +8 -2
- package/src/intl-manager.ts +5 -15
- package/src/message-loader.ts +98 -20
- package/src/message.ts +3 -36
- package/src/types.d.ts +2 -2
package/dist/formatters/ast.js
CHANGED
|
@@ -65,14 +65,6 @@ var RichTextNodeType;
|
|
|
65
65
|
*/
|
|
66
66
|
RichTextNodeType["Object"] = "object";
|
|
67
67
|
})(RichTextNodeType || (exports.RichTextNodeType = RichTextNodeType = {}));
|
|
68
|
-
var RICH_TEXT_TAG_TYPES = {
|
|
69
|
-
$_: RichTextNodeType.Text,
|
|
70
|
-
$b: RichTextNodeType.Strong,
|
|
71
|
-
$i: RichTextNodeType.Emphasis,
|
|
72
|
-
$code: RichTextNodeType.Code,
|
|
73
|
-
$link: RichTextNodeType.Link,
|
|
74
|
-
$p: RichTextNodeType.Paragraph,
|
|
75
|
-
};
|
|
76
68
|
var AST_RICH_TEXT_ELEMENTS = {
|
|
77
69
|
$_: function () { return ({ type: RichTextNodeType.Text, content: '' }); },
|
|
78
70
|
$b: function (content) { return ({ type: RichTextNodeType.Strong, content: content }); },
|
|
@@ -100,7 +92,7 @@ var AstBuilder = /** @class */ (function (_super) {
|
|
|
100
92
|
if (!(tag in AST_RICH_TEXT_ELEMENTS)) {
|
|
101
93
|
throw "".concat(tag, " is not a known rich text formatting tag");
|
|
102
94
|
}
|
|
103
|
-
var result = AST_RICH_TEXT_ELEMENTS[tag](children);
|
|
95
|
+
var result = AST_RICH_TEXT_ELEMENTS[tag](children, 0);
|
|
104
96
|
if (Array.isArray(result)) {
|
|
105
97
|
(_a = this.result).push.apply(_a, __spreadArray([], __read(result), false));
|
|
106
98
|
}
|
|
@@ -62,7 +62,7 @@ var MarkdownBuilder = /** @class */ (function (_super) {
|
|
|
62
62
|
return _this;
|
|
63
63
|
}
|
|
64
64
|
MarkdownBuilder.prototype.pushRichTextTag = function (tag, children) {
|
|
65
|
-
this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children);
|
|
65
|
+
this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children, 0);
|
|
66
66
|
};
|
|
67
67
|
return MarkdownBuilder;
|
|
68
68
|
}(string_1.StringBuilder));
|
|
@@ -18,7 +18,10 @@ export type ReactFunctionTypes = FunctionTypes<React.ReactNode, ReactClickHandle
|
|
|
18
18
|
onContextMenu: ReactClickHandler;
|
|
19
19
|
}>;
|
|
20
20
|
export declare const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFunctionTypes['hook']>;
|
|
21
|
-
export
|
|
21
|
+
export type ReactIntlMessage = React.ReactElement<{}, typeof React.Fragment & {
|
|
22
|
+
__brand: 'discord-intl';
|
|
23
|
+
}>;
|
|
24
|
+
export declare function formatReact(this: IntlManager, message: AnyIntlMessage, values: object, Builder: FormatBuilderConstructor<React.ReactElement>): ReactIntlMessage;
|
|
22
25
|
/**
|
|
23
26
|
* Create a new React formatter with the given rich text elements replacing the defaults. Use this
|
|
24
27
|
* function to inject custom components for things like links and paragraphs, which may be best
|
package/dist/formatters/react.js
CHANGED
|
@@ -48,15 +48,15 @@ var format_1 = require("../format");
|
|
|
48
48
|
var h = React.createElement;
|
|
49
49
|
exports.DEFAULT_REACT_RICH_TEXT_ELEMENTS = {
|
|
50
50
|
$_: function () { return ''; },
|
|
51
|
-
$b: function (content) { return h('strong',
|
|
52
|
-
$code: function (content) { return h('code',
|
|
53
|
-
$i: function (content) { return h('em',
|
|
51
|
+
$b: function (content, key) { return h('strong', { key: key }, content); },
|
|
52
|
+
$code: function (content, key) { return h('code', { key: key }, content); },
|
|
53
|
+
$i: function (content, key) { return h('em', { key: key }, content); },
|
|
54
54
|
// $link will always be [href, <empty>, ...content]
|
|
55
|
-
$link: function (_a) {
|
|
55
|
+
$link: function (_a, key) {
|
|
56
56
|
var _b = __read(_a), href = _b[0], content = _b.slice(1);
|
|
57
|
-
return h('a', { href: href }, content);
|
|
57
|
+
return h('a', { href: href, key: key }, content);
|
|
58
58
|
},
|
|
59
|
-
$p: function (content) { return h('p',
|
|
59
|
+
$p: function (content, key) { return h('p', { key: key }, content); },
|
|
60
60
|
};
|
|
61
61
|
/**
|
|
62
62
|
* Creates a new `FormatBuilder` class that constructs a React element tree
|
|
@@ -70,11 +70,12 @@ function createReactBuilder(richTextElements) {
|
|
|
70
70
|
__extends(class_1, _super);
|
|
71
71
|
function class_1() {
|
|
72
72
|
var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
|
|
73
|
+
_this._nodeKey = 0;
|
|
73
74
|
_this.result = [];
|
|
74
75
|
return _this;
|
|
75
76
|
}
|
|
76
77
|
class_1.prototype.pushRichTextTag = function (tag, children) {
|
|
77
|
-
this.result.push(richTextElements[tag](children));
|
|
78
|
+
this.result.push(richTextElements[tag](children, this._nodeKey++));
|
|
78
79
|
};
|
|
79
80
|
class_1.prototype.pushLiteralText = function (text) {
|
|
80
81
|
this.result.push(text);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { RichTextNode } from './formatters';
|
|
3
|
-
export { IntlManager, createLoader, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
|
|
4
3
|
export * from './formatters';
|
|
4
|
+
export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
|
|
5
|
+
export { createLoader, loadAllMessagesInLocale, waitForAllDefaultIntlMessagesLoaded, MessageLoader, } from './message-loader';
|
|
5
6
|
export type * from './types.d.ts';
|
|
6
7
|
/**
|
|
7
8
|
* A type representing any message that has been formatted using one of the
|
package/dist/index.js
CHANGED
|
@@ -14,9 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
17
|
+
exports.MessageLoader = exports.waitForAllDefaultIntlMessagesLoaded = exports.loadAllMessagesInLocale = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = void 0;
|
|
18
|
+
__exportStar(require("./formatters"), exports);
|
|
18
19
|
var intl_manager_1 = require("./intl-manager");
|
|
19
20
|
Object.defineProperty(exports, "IntlManager", { enumerable: true, get: function () { return intl_manager_1.IntlManager; } });
|
|
20
|
-
Object.defineProperty(exports, "createLoader", { enumerable: true, get: function () { return intl_manager_1.createLoader; } });
|
|
21
21
|
Object.defineProperty(exports, "DEFAULT_LOCALE", { enumerable: true, get: function () { return intl_manager_1.DEFAULT_LOCALE; } });
|
|
22
|
-
|
|
22
|
+
var message_loader_1 = require("./message-loader");
|
|
23
|
+
Object.defineProperty(exports, "createLoader", { enumerable: true, get: function () { return message_loader_1.createLoader; } });
|
|
24
|
+
Object.defineProperty(exports, "loadAllMessagesInLocale", { enumerable: true, get: function () { return message_loader_1.loadAllMessagesInLocale; } });
|
|
25
|
+
Object.defineProperty(exports, "waitForAllDefaultIntlMessagesLoaded", { enumerable: true, get: function () { return message_loader_1.waitForAllDefaultIntlMessagesLoaded; } });
|
|
26
|
+
Object.defineProperty(exports, "MessageLoader", { enumerable: true, get: function () { return message_loader_1.MessageLoader; } });
|
package/dist/intl-manager.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { IntlShape } from '@formatjs/intl';
|
|
2
|
-
import { LocaleId, LocaleImportMap, MessageLoader } from './message-loader';
|
|
3
2
|
import type { FormatterImplementation, IntlMessageGetter, RequiredFormatValues, TypedIntlMessageGetter } from './types';
|
|
4
3
|
import { InternalIntlMessage } from './message';
|
|
5
4
|
import { FormatBuilderConstructor } from './format';
|
|
@@ -48,10 +47,4 @@ export declare class IntlManager {
|
|
|
48
47
|
string<T extends TypedIntlMessageGetter<undefined>>(message: T): string;
|
|
49
48
|
bindFormatValues<T>(Builder: FormatBuilderConstructor<T>, message: InternalIntlMessage, values: Record<string, any>): T[];
|
|
50
49
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Create a new MessageLoader, which handles lazily loading messages for
|
|
53
|
-
* different locales and sanity checks as needed to provide accessors for each
|
|
54
|
-
* message defined in `messageKeys`.
|
|
55
|
-
*/
|
|
56
|
-
export declare function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap, defaultLocale: LocaleId): MessageLoader;
|
|
57
50
|
export {};
|
package/dist/intl-manager.js
CHANGED
|
@@ -28,10 +28,8 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
30
|
exports.IntlManager = exports.DEFAULT_LOCALE = void 0;
|
|
31
|
-
exports.createLoader = createLoader;
|
|
32
31
|
var intl_1 = require("@formatjs/intl");
|
|
33
32
|
var intl_messageformat_1 = require("intl-messageformat");
|
|
34
|
-
var message_loader_1 = require("./message-loader");
|
|
35
33
|
var format_1 = require("./format");
|
|
36
34
|
/**
|
|
37
35
|
* Fallback locale used for all internationalization when an operation in the
|
|
@@ -122,8 +120,11 @@ var IntlManager = /** @class */ (function () {
|
|
|
122
120
|
* immediately return the plain string value of the message in the current locale.
|
|
123
121
|
*/
|
|
124
122
|
IntlManager.prototype.string = function (message) {
|
|
125
|
-
//
|
|
126
|
-
|
|
123
|
+
// @ts-expect-error Figure out how to make this typing exact. This
|
|
124
|
+
// currently relies on the generic typing being sound enough to know that
|
|
125
|
+
// the message can only contain a single static text node and no
|
|
126
|
+
// placeholders or rich text.
|
|
127
|
+
return message(this.currentLocale).ast[0].value;
|
|
127
128
|
};
|
|
128
129
|
IntlManager.prototype.bindFormatValues = function (Builder, message, values) {
|
|
129
130
|
return (0, format_1.bindFormatValues)(Builder, message.ast, [this.currentLocale, this.defaultLocale], this.intl.formatters, this.intl.formats, values);
|
|
@@ -131,11 +132,3 @@ var IntlManager = /** @class */ (function () {
|
|
|
131
132
|
return IntlManager;
|
|
132
133
|
}());
|
|
133
134
|
exports.IntlManager = IntlManager;
|
|
134
|
-
/**
|
|
135
|
-
* Create a new MessageLoader, which handles lazily loading messages for
|
|
136
|
-
* different locales and sanity checks as needed to provide accessors for each
|
|
137
|
-
* message defined in `messageKeys`.
|
|
138
|
-
*/
|
|
139
|
-
function createLoader(messageKeys, localeImportMap, defaultLocale) {
|
|
140
|
-
return new message_loader_1.MessageLoader(messageKeys, localeImportMap, defaultLocale);
|
|
141
|
-
}
|
package/dist/message-loader.d.ts
CHANGED
|
@@ -28,9 +28,17 @@ export declare class MessageLoader {
|
|
|
28
28
|
* then a request has already been made to load the locale and it is waiting
|
|
29
29
|
* to resolve.
|
|
30
30
|
*/
|
|
31
|
-
_localeLoadingPromises: Record<LocaleId,
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
_localeLoadingPromises: Record<LocaleId, {
|
|
32
|
+
/**`true` if this locale has been loaded at least once. */
|
|
33
|
+
initialized: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* If the locale is currently being loaded, this Promise will exist and
|
|
36
|
+
* represent that loading status. Otherwise, this value is undefined.
|
|
37
|
+
*/
|
|
38
|
+
current?: Promise<{
|
|
39
|
+
default: MessagesData;
|
|
40
|
+
}> | undefined;
|
|
41
|
+
}>;
|
|
34
42
|
/**
|
|
35
43
|
* Map of pre-parsed messages, keyed by the message key and locale
|
|
36
44
|
*/
|
|
@@ -99,5 +107,36 @@ export declare class MessageLoader {
|
|
|
99
107
|
* Subscribe to events about when a locale has loaded.
|
|
100
108
|
*/
|
|
101
109
|
onChange(callback: () => void): () => void;
|
|
110
|
+
isLocaleLoaded(locale: LocaleId, requireCurrent?: boolean): boolean;
|
|
111
|
+
waitForLocaleLoaded(locale: LocaleId, requireCurrent?: boolean): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Returns true if this loader's default locale has finished loading. When
|
|
114
|
+
* true, all messages managed by this loader are guaranteed to have _a_
|
|
115
|
+
* render-able value, even if one does not exist in the current locale.
|
|
116
|
+
*/
|
|
117
|
+
waitForDefaultLocale(requireCurrent?: boolean): Promise<void>;
|
|
102
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Kick off a load request for the given locale across all currently-registered
|
|
121
|
+
* message loaders, returning a Promise that resolves when all loaders have
|
|
122
|
+
* finished loading that locale.
|
|
123
|
+
*/
|
|
124
|
+
export declare function loadAllMessagesInLocale(locale: LocaleId): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Returns a new Promise that resolves after all currently-registered message
|
|
127
|
+
* loaders have finished successfully loading their default locale content.
|
|
128
|
+
* Once this Promise has resolved, all messages that currently exist in the
|
|
129
|
+
* application (i.e., within all modules that have been imported or required in
|
|
130
|
+
* the current session) can be guaranteed to have _a_ render-able value.
|
|
131
|
+
*
|
|
132
|
+
* This function can be called at any point in an applications life cycle and
|
|
133
|
+
* will always ensure that _all_ loaders that currently exist are initialized.
|
|
134
|
+
*/
|
|
135
|
+
export declare function waitForAllDefaultIntlMessagesLoaded(): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Create a new MessageLoader, which handles lazily loading messages for
|
|
138
|
+
* different locales and sanity checks as needed to provide accessors for each
|
|
139
|
+
* message defined in `messageKeys`.
|
|
140
|
+
*/
|
|
141
|
+
export declare function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap, defaultLocale: LocaleId): MessageLoader;
|
|
103
142
|
export {};
|
package/dist/message-loader.js
CHANGED
|
@@ -64,6 +64,9 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
64
64
|
};
|
|
65
65
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
66
|
exports.MessageLoader = void 0;
|
|
67
|
+
exports.loadAllMessagesInLocale = loadAllMessagesInLocale;
|
|
68
|
+
exports.waitForAllDefaultIntlMessagesLoaded = waitForAllDefaultIntlMessagesLoaded;
|
|
69
|
+
exports.createLoader = createLoader;
|
|
67
70
|
var message_1 = require("./message");
|
|
68
71
|
var MessageLoader = /** @class */ (function () {
|
|
69
72
|
function MessageLoader(messageKeys, localeImportMap, defaultLocale) {
|
|
@@ -78,17 +81,24 @@ var MessageLoader = /** @class */ (function () {
|
|
|
78
81
|
this._parseCache = new Map();
|
|
79
82
|
this._subscribers = new Set();
|
|
80
83
|
this._loadLocale(this.defaultLocale);
|
|
81
|
-
this.fallbackMessage = new message_1.InternalIntlMessage(
|
|
84
|
+
this.fallbackMessage = new message_1.InternalIntlMessage([], this.defaultLocale);
|
|
82
85
|
// In cases where hot module replacement is available, set up cache clearing whenever the
|
|
83
86
|
// targets change so that values are always replaced.
|
|
84
87
|
// @ts-expect-error `hot` not defined in types.
|
|
85
88
|
if (module.hot) {
|
|
86
89
|
var _loop_1 = function (locale, file) {
|
|
87
90
|
// @ts-expect-error `hot` not defined in types.
|
|
88
|
-
module.hot.accept(file, function () {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
module.hot.accept(file, function () { return __awaiter(_this, void 0, void 0, function () {
|
|
92
|
+
return __generator(this, function (_a) {
|
|
93
|
+
switch (_a.label) {
|
|
94
|
+
case 0: return [4 /*yield*/, this._loadLocale(locale)];
|
|
95
|
+
case 1:
|
|
96
|
+
_a.sent();
|
|
97
|
+
this._parseCache.clear();
|
|
98
|
+
return [2 /*return*/];
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}); });
|
|
92
102
|
};
|
|
93
103
|
try {
|
|
94
104
|
for (var _b = __values(Object.entries(localeImportMap)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
@@ -119,9 +129,8 @@ var MessageLoader = /** @class */ (function () {
|
|
|
119
129
|
MessageLoader.prototype.get = function (key, locale) {
|
|
120
130
|
var _a;
|
|
121
131
|
var value = (_a = this.getMessageValue(key, locale)) !== null && _a !== void 0 ? _a : this.getMessageValue(key, this.defaultLocale);
|
|
122
|
-
if (value != null)
|
|
132
|
+
if (value != null)
|
|
123
133
|
return value;
|
|
124
|
-
}
|
|
125
134
|
// If the message couldn't be found in either the requested nor the default locale, then
|
|
126
135
|
// nothing can be done.
|
|
127
136
|
var errorKey = this._debugKeyMap != null ? "\"".concat(this._debugKeyMap[key], "\" (").concat(key, ")") : undefined;
|
|
@@ -146,13 +155,11 @@ var MessageLoader = /** @class */ (function () {
|
|
|
146
155
|
var cachedValue = this._parseCache.get(cacheKey);
|
|
147
156
|
if (cachedValue != null)
|
|
148
157
|
return cachedValue;
|
|
149
|
-
//
|
|
150
|
-
if
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// If not, check whether it's been loaded and trigger a load if not.
|
|
158
|
+
// Check whether the locale exists in memory. If not, start a request to
|
|
159
|
+
// load the locale if one has not already started. It's impossible for
|
|
160
|
+
// the request to finish within the same tick, so return undefined in any
|
|
161
|
+
// case then.
|
|
154
162
|
if (this.messages[locale] == null) {
|
|
155
|
-
// Ensure the locale is loaded, if it is supported
|
|
156
163
|
if (this.supportedLocales.includes(locale)) {
|
|
157
164
|
this._loadLocale(locale);
|
|
158
165
|
}
|
|
@@ -186,12 +193,13 @@ var MessageLoader = /** @class */ (function () {
|
|
|
186
193
|
};
|
|
187
194
|
MessageLoader.prototype._loadLocale = function (locale) {
|
|
188
195
|
return __awaiter(this, void 0, void 0, function () {
|
|
189
|
-
var
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
var current, _a, _b;
|
|
197
|
+
var _c;
|
|
198
|
+
return __generator(this, function (_d) {
|
|
199
|
+
switch (_d.label) {
|
|
192
200
|
case 0:
|
|
193
201
|
// Don't re-load a locale that's already in progress.
|
|
194
|
-
if (this._localeLoadingPromises[locale] != null) {
|
|
202
|
+
if (((_c = this._localeLoadingPromises[locale]) === null || _c === void 0 ? void 0 : _c.current) != null) {
|
|
195
203
|
return [2 /*return*/];
|
|
196
204
|
}
|
|
197
205
|
// Safety check in case the locale map doesn't include a require for the
|
|
@@ -203,14 +211,14 @@ var MessageLoader = /** @class */ (function () {
|
|
|
203
211
|
// If the locale is already set in `messages`, then it doesn't need to be loaded again.
|
|
204
212
|
if (this.messages[locale] != null)
|
|
205
213
|
return [2 /*return*/];
|
|
206
|
-
|
|
207
|
-
this._localeLoadingPromises[locale] =
|
|
214
|
+
current = this.localeImportMap[locale]();
|
|
215
|
+
this._localeLoadingPromises[locale] = { initialized: false, current: current };
|
|
208
216
|
_a = this.messages;
|
|
209
217
|
_b = locale;
|
|
210
|
-
return [4 /*yield*/,
|
|
218
|
+
return [4 /*yield*/, current];
|
|
211
219
|
case 1:
|
|
212
|
-
_a[_b] = (
|
|
213
|
-
|
|
220
|
+
_a[_b] = (_d.sent()).default;
|
|
221
|
+
this._localeLoadingPromises[locale] = { initialized: true, current: undefined };
|
|
214
222
|
this.emitChange();
|
|
215
223
|
return [2 /*return*/];
|
|
216
224
|
}
|
|
@@ -245,6 +253,111 @@ var MessageLoader = /** @class */ (function () {
|
|
|
245
253
|
this._subscribers.add(callback);
|
|
246
254
|
return function () { return _this._subscribers.delete(callback); };
|
|
247
255
|
};
|
|
256
|
+
MessageLoader.prototype.isLocaleLoaded = function (locale, requireCurrent) {
|
|
257
|
+
if (requireCurrent === void 0) { requireCurrent = false; }
|
|
258
|
+
var ref = this._localeLoadingPromises[locale];
|
|
259
|
+
// Ensure the locale at least exists and is initialized.
|
|
260
|
+
if (ref == null || ref.initialized == false)
|
|
261
|
+
return false;
|
|
262
|
+
// `current` will be deleted once the locale has loaded, so if it is null,
|
|
263
|
+
// then the locale data is current.
|
|
264
|
+
if (requireCurrent)
|
|
265
|
+
return ref.current == null;
|
|
266
|
+
// Otherwise, initialization is enough.
|
|
267
|
+
return true;
|
|
268
|
+
};
|
|
269
|
+
MessageLoader.prototype.waitForLocaleLoaded = function (locale_1) {
|
|
270
|
+
return __awaiter(this, arguments, void 0, function (locale, requireCurrent) {
|
|
271
|
+
var ref;
|
|
272
|
+
if (requireCurrent === void 0) { requireCurrent = false; }
|
|
273
|
+
return __generator(this, function (_a) {
|
|
274
|
+
switch (_a.label) {
|
|
275
|
+
case 0:
|
|
276
|
+
ref = this._localeLoadingPromises[locale];
|
|
277
|
+
// If the locale hasn't been loaded at all, kick off the load and return
|
|
278
|
+
// that Promise directly.
|
|
279
|
+
if (ref == null)
|
|
280
|
+
return [2 /*return*/, this._loadLocale(locale)];
|
|
281
|
+
// If initialization is enough for this request, just resolve immediately.
|
|
282
|
+
if (ref.initialized && !requireCurrent)
|
|
283
|
+
return [2 /*return*/];
|
|
284
|
+
// Otherwise, check the `current` loading state on the object, and if that
|
|
285
|
+
// doesn't exist, then the locale data is already finished loading, and the
|
|
286
|
+
// containing Promise will just resolve with `undefined`.
|
|
287
|
+
return [4 /*yield*/, ref.current];
|
|
288
|
+
case 1:
|
|
289
|
+
// Otherwise, check the `current` loading state on the object, and if that
|
|
290
|
+
// doesn't exist, then the locale data is already finished loading, and the
|
|
291
|
+
// containing Promise will just resolve with `undefined`.
|
|
292
|
+
_a.sent();
|
|
293
|
+
return [2 /*return*/];
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Returns true if this loader's default locale has finished loading. When
|
|
300
|
+
* true, all messages managed by this loader are guaranteed to have _a_
|
|
301
|
+
* render-able value, even if one does not exist in the current locale.
|
|
302
|
+
*/
|
|
303
|
+
MessageLoader.prototype.waitForDefaultLocale = function () {
|
|
304
|
+
return __awaiter(this, arguments, void 0, function (requireCurrent) {
|
|
305
|
+
if (requireCurrent === void 0) { requireCurrent = false; }
|
|
306
|
+
return __generator(this, function (_a) {
|
|
307
|
+
return [2 /*return*/, this.waitForLocaleLoaded(this.defaultLocale, requireCurrent)];
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
};
|
|
248
311
|
return MessageLoader;
|
|
249
312
|
}());
|
|
250
313
|
exports.MessageLoader = MessageLoader;
|
|
314
|
+
var LOADER_REGISTRY = [];
|
|
315
|
+
/**
|
|
316
|
+
* Kick off a load request for the given locale across all currently-registered
|
|
317
|
+
* message loaders, returning a Promise that resolves when all loaders have
|
|
318
|
+
* finished loading that locale.
|
|
319
|
+
*/
|
|
320
|
+
function loadAllMessagesInLocale(locale) {
|
|
321
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
322
|
+
return __generator(this, function (_a) {
|
|
323
|
+
switch (_a.label) {
|
|
324
|
+
case 0: return [4 /*yield*/, Promise.all(LOADER_REGISTRY.map(function (loader) { return loader._loadLocale(locale); }))];
|
|
325
|
+
case 1:
|
|
326
|
+
_a.sent();
|
|
327
|
+
return [2 /*return*/];
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Returns a new Promise that resolves after all currently-registered message
|
|
334
|
+
* loaders have finished successfully loading their default locale content.
|
|
335
|
+
* Once this Promise has resolved, all messages that currently exist in the
|
|
336
|
+
* application (i.e., within all modules that have been imported or required in
|
|
337
|
+
* the current session) can be guaranteed to have _a_ render-able value.
|
|
338
|
+
*
|
|
339
|
+
* This function can be called at any point in an applications life cycle and
|
|
340
|
+
* will always ensure that _all_ loaders that currently exist are initialized.
|
|
341
|
+
*/
|
|
342
|
+
function waitForAllDefaultIntlMessagesLoaded() {
|
|
343
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
344
|
+
return __generator(this, function (_a) {
|
|
345
|
+
switch (_a.label) {
|
|
346
|
+
case 0: return [4 /*yield*/, Promise.all(LOADER_REGISTRY.map(function (loader) { return loader.waitForDefaultLocale(); }))];
|
|
347
|
+
case 1:
|
|
348
|
+
_a.sent();
|
|
349
|
+
return [2 /*return*/];
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Create a new MessageLoader, which handles lazily loading messages for
|
|
356
|
+
* different locales and sanity checks as needed to provide accessors for each
|
|
357
|
+
* message defined in `messageKeys`.
|
|
358
|
+
*/
|
|
359
|
+
function createLoader(messageKeys, localeImportMap, defaultLocale) {
|
|
360
|
+
var loader = new MessageLoader(messageKeys, localeImportMap, defaultLocale);
|
|
361
|
+
LOADER_REGISTRY.push(loader);
|
|
362
|
+
return loader;
|
|
363
|
+
}
|
package/dist/message.d.ts
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import { FormatJsNode } from './keyless-json';
|
|
2
2
|
export declare class InternalIntlMessage {
|
|
3
3
|
locale: string;
|
|
4
|
-
message?: string;
|
|
5
4
|
ast: FormatJsNode[];
|
|
6
|
-
|
|
7
|
-
* A stripped-down representation of the message with no rich formatting
|
|
8
|
-
* elements preserved. This will be lazily created the first time it is
|
|
9
|
-
* requested (e.g., by calling `formatToPlainString`).
|
|
10
|
-
*/
|
|
11
|
-
get plainAst(): FormatJsNode[];
|
|
12
|
-
_plainAst: FormatJsNode[];
|
|
13
|
-
constructor(messageOrAst: string | FormatJsNode[], locale: string);
|
|
14
|
-
/**
|
|
15
|
-
* Returns the same element with any formatting tags removed. If the element
|
|
16
|
-
* itself is a tag, the children will be hoisted and returned, otherwise the
|
|
17
|
-
* element is returned as an array of itself.
|
|
18
|
-
*/
|
|
19
|
-
private _removeRichTags;
|
|
5
|
+
constructor(messageOrAst: FormatJsNode[], locale: string);
|
|
20
6
|
}
|
package/dist/message.js
CHANGED
|
@@ -1,93 +1,11 @@
|
|
|
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
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
30
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
31
|
-
if (ar || !(i in from)) {
|
|
32
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
33
|
-
ar[i] = from[i];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
37
|
-
};
|
|
38
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
3
|
exports.InternalIntlMessage = void 0;
|
|
40
|
-
var keyless_json_1 = require("./keyless-json");
|
|
41
4
|
var InternalIntlMessage = /** @class */ (function () {
|
|
42
5
|
function InternalIntlMessage(messageOrAst, locale) {
|
|
43
6
|
this.locale = locale;
|
|
44
|
-
|
|
45
|
-
this.message = messageOrAst;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
this.ast = messageOrAst;
|
|
49
|
-
}
|
|
7
|
+
this.ast = messageOrAst;
|
|
50
8
|
}
|
|
51
|
-
Object.defineProperty(InternalIntlMessage.prototype, "plainAst", {
|
|
52
|
-
/**
|
|
53
|
-
* A stripped-down representation of the message with no rich formatting
|
|
54
|
-
* elements preserved. This will be lazily created the first time it is
|
|
55
|
-
* requested (e.g., by calling `formatToPlainString`).
|
|
56
|
-
*/
|
|
57
|
-
get: function () {
|
|
58
|
-
return (this._plainAst = this._removeRichTags(this.ast));
|
|
59
|
-
},
|
|
60
|
-
enumerable: false,
|
|
61
|
-
configurable: true
|
|
62
|
-
});
|
|
63
|
-
/**
|
|
64
|
-
* Returns the same element with any formatting tags removed. If the element
|
|
65
|
-
* itself is a tag, the children will be hoisted and returned, otherwise the
|
|
66
|
-
* element is returned as an array of itself.
|
|
67
|
-
*/
|
|
68
|
-
InternalIntlMessage.prototype._removeRichTags = function (nodes) {
|
|
69
|
-
var e_1, _a;
|
|
70
|
-
var result = new Array(nodes.length);
|
|
71
|
-
try {
|
|
72
|
-
for (var nodes_1 = __values(nodes), nodes_1_1 = nodes_1.next(); !nodes_1_1.done; nodes_1_1 = nodes_1.next()) {
|
|
73
|
-
var node = nodes_1_1.value;
|
|
74
|
-
if (node.type === keyless_json_1.FormatJsNodeType.Tag) {
|
|
75
|
-
result.push.apply(result, __spreadArray([], __read(this._removeRichTags(node.children)), false));
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
result.push(node);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
83
|
-
finally {
|
|
84
|
-
try {
|
|
85
|
-
if (nodes_1_1 && !nodes_1_1.done && (_a = nodes_1.return)) _a.call(nodes_1);
|
|
86
|
-
}
|
|
87
|
-
finally { if (e_1) throw e_1.error; }
|
|
88
|
-
}
|
|
89
|
-
return result;
|
|
90
|
-
};
|
|
91
9
|
return InternalIntlMessage;
|
|
92
10
|
}());
|
|
93
11
|
exports.InternalIntlMessage = InternalIntlMessage;
|
package/dist/types.d.ts
CHANGED
|
@@ -198,8 +198,8 @@ export interface FunctionTypeMap {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
export interface FunctionTypes<Result, HandlerType = (content: Result[]) => Result | Result[]> {
|
|
201
|
-
link: undefined | ((content: Result[]) => Result | Result[]);
|
|
202
|
-
hook: undefined | ((content: Result[]) => Result | Result[]);
|
|
201
|
+
link: undefined | ((content: Result[], key: number) => Result | Result[]);
|
|
202
|
+
hook: undefined | ((content: Result[], key: number) => Result | Result[]);
|
|
203
203
|
handler: undefined | HandlerType;
|
|
204
204
|
}
|
|
205
205
|
|
package/package.json
CHANGED
package/src/formatters/ast.ts
CHANGED
|
@@ -36,15 +36,6 @@ export enum RichTextNodeType {
|
|
|
36
36
|
Object = 'object',
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const RICH_TEXT_TAG_TYPES: Record<RichTextTagNames, RichTextNodeType> = {
|
|
40
|
-
$_: RichTextNodeType.Text,
|
|
41
|
-
$b: RichTextNodeType.Strong,
|
|
42
|
-
$i: RichTextNodeType.Emphasis,
|
|
43
|
-
$code: RichTextNodeType.Code,
|
|
44
|
-
$link: RichTextNodeType.Link,
|
|
45
|
-
$p: RichTextNodeType.Paragraph,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
39
|
interface RichTextNodeBase<ContentType> {
|
|
49
40
|
type: RichTextNodeType;
|
|
50
41
|
content: ContentType;
|
|
@@ -101,7 +92,7 @@ class AstBuilder extends FormatBuilder<RichTextNode> {
|
|
|
101
92
|
if (!(tag in AST_RICH_TEXT_ELEMENTS)) {
|
|
102
93
|
throw `${tag} is not a known rich text formatting tag`;
|
|
103
94
|
}
|
|
104
|
-
const result = AST_RICH_TEXT_ELEMENTS[tag](children);
|
|
95
|
+
const result = AST_RICH_TEXT_ELEMENTS[tag](children, 0);
|
|
105
96
|
if (Array.isArray(result)) {
|
|
106
97
|
this.result.push(...result);
|
|
107
98
|
} else {
|
|
@@ -31,7 +31,7 @@ class MarkdownBuilder extends StringBuilder {
|
|
|
31
31
|
result: string = '';
|
|
32
32
|
|
|
33
33
|
pushRichTextTag(tag: RichTextTagNames, children: string[]) {
|
|
34
|
-
this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children);
|
|
34
|
+
this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children, 0);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
package/src/formatters/react.ts
CHANGED
|
@@ -28,12 +28,12 @@ export type ReactFunctionTypes = FunctionTypes<
|
|
|
28
28
|
const h = React.createElement;
|
|
29
29
|
export const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFunctionTypes['hook']> = {
|
|
30
30
|
$_: () => '',
|
|
31
|
-
$b: (content) => h('strong',
|
|
32
|
-
$code: (content) => h('code',
|
|
33
|
-
$i: (content) => h('em',
|
|
31
|
+
$b: (content, key) => h('strong', { key }, content),
|
|
32
|
+
$code: (content, key) => h('code', { key }, content),
|
|
33
|
+
$i: (content, key) => h('em', { key }, content),
|
|
34
34
|
// $link will always be [href, <empty>, ...content]
|
|
35
|
-
$link: ([href, ...content]) => h('a', { href }, content),
|
|
36
|
-
$p: (content) => h('p',
|
|
35
|
+
$link: ([href, ...content], key) => h('a', { href, key }, content),
|
|
36
|
+
$p: (content, key) => h('p', { key }, content),
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -47,10 +47,11 @@ function createReactBuilder(richTextElements: RichTextFormattingMap<ReactFunctio
|
|
|
47
47
|
new (): FormatBuilder<React.ReactNode>;
|
|
48
48
|
} {
|
|
49
49
|
return class extends FormatBuilder<React.ReactNode> {
|
|
50
|
+
_nodeKey: number = 0;
|
|
50
51
|
result: React.ReactNode[] = [];
|
|
51
52
|
|
|
52
53
|
pushRichTextTag(tag: RichTextTagNames, children: React.ReactNode[]) {
|
|
53
|
-
this.result.push(richTextElements[tag](children));
|
|
54
|
+
this.result.push(richTextElements[tag](children, this._nodeKey++));
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
pushLiteralText(text: string) {
|
|
@@ -69,18 +70,23 @@ function createReactBuilder(richTextElements: RichTextFormattingMap<ReactFunctio
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
export type ReactIntlMessage = React.ReactElement<
|
|
74
|
+
{},
|
|
75
|
+
typeof React.Fragment & { __brand: 'discord-intl' }
|
|
76
|
+
>;
|
|
77
|
+
|
|
72
78
|
export function formatReact(
|
|
73
79
|
this: IntlManager,
|
|
74
80
|
message: AnyIntlMessage,
|
|
75
81
|
values: object,
|
|
76
82
|
Builder: FormatBuilderConstructor<React.ReactElement>,
|
|
77
|
-
):
|
|
83
|
+
): ReactIntlMessage {
|
|
78
84
|
if (typeof message === 'string') {
|
|
79
|
-
return React.createElement(React.Fragment, undefined, message);
|
|
85
|
+
return React.createElement(React.Fragment, undefined, message) as ReactIntlMessage;
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
const parts = this.bindFormatValues(Builder, message, values);
|
|
83
|
-
return React.createElement(React.Fragment, undefined, parts);
|
|
89
|
+
return React.createElement(React.Fragment, undefined, parts) as ReactIntlMessage;
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
/**
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { RichTextNode } from './formatters';
|
|
3
3
|
|
|
4
|
-
export { IntlManager, createLoader, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
|
|
5
4
|
export * from './formatters';
|
|
6
|
-
|
|
5
|
+
export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
|
|
6
|
+
export {
|
|
7
|
+
createLoader,
|
|
8
|
+
loadAllMessagesInLocale,
|
|
9
|
+
waitForAllDefaultIntlMessagesLoaded,
|
|
10
|
+
MessageLoader,
|
|
11
|
+
} from './message-loader';
|
|
7
12
|
export type * from './types.d.ts';
|
|
8
13
|
|
|
9
14
|
/**
|
|
@@ -21,6 +26,7 @@ export type * from './types.d.ts';
|
|
|
21
26
|
* intl message, even if the actual value comes from elsewhere (like a
|
|
22
27
|
* user-generated string).
|
|
23
28
|
*/
|
|
29
|
+
// TODO: Replace this with ReactIntlMessage from `formatters/react`.
|
|
24
30
|
export type ReactIntlMessage = string | React.ReactElement;
|
|
25
31
|
|
|
26
32
|
/**
|
package/src/intl-manager.ts
CHANGED
|
@@ -116,8 +116,11 @@ export class IntlManager {
|
|
|
116
116
|
* immediately return the plain string value of the message in the current locale.
|
|
117
117
|
*/
|
|
118
118
|
string<T extends TypedIntlMessageGetter<undefined>>(message: T): string {
|
|
119
|
-
//
|
|
120
|
-
|
|
119
|
+
// @ts-expect-error Figure out how to make this typing exact. This
|
|
120
|
+
// currently relies on the generic typing being sound enough to know that
|
|
121
|
+
// the message can only contain a single static text node and no
|
|
122
|
+
// placeholders or rich text.
|
|
123
|
+
return message(this.currentLocale).ast[0].value;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
bindFormatValues<T>(
|
|
@@ -135,16 +138,3 @@ export class IntlManager {
|
|
|
135
138
|
);
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Create a new MessageLoader, which handles lazily loading messages for
|
|
141
|
-
* different locales and sanity checks as needed to provide accessors for each
|
|
142
|
-
* message defined in `messageKeys`.
|
|
143
|
-
*/
|
|
144
|
-
export function createLoader(
|
|
145
|
-
messageKeys: string[],
|
|
146
|
-
localeImportMap: LocaleImportMap,
|
|
147
|
-
defaultLocale: LocaleId,
|
|
148
|
-
) {
|
|
149
|
-
return new MessageLoader(messageKeys, localeImportMap, defaultLocale);
|
|
150
|
-
}
|
package/src/message-loader.ts
CHANGED
|
@@ -32,7 +32,18 @@ export class MessageLoader {
|
|
|
32
32
|
* then a request has already been made to load the locale and it is waiting
|
|
33
33
|
* to resolve.
|
|
34
34
|
*/
|
|
35
|
-
_localeLoadingPromises: Record<
|
|
35
|
+
_localeLoadingPromises: Record<
|
|
36
|
+
LocaleId,
|
|
37
|
+
{
|
|
38
|
+
/**`true` if this locale has been loaded at least once. */
|
|
39
|
+
initialized: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* If the locale is currently being loaded, this Promise will exist and
|
|
42
|
+
* represent that loading status. Otherwise, this value is undefined.
|
|
43
|
+
*/
|
|
44
|
+
current?: Promise<{ default: MessagesData }> | undefined;
|
|
45
|
+
}
|
|
46
|
+
>;
|
|
36
47
|
|
|
37
48
|
/**
|
|
38
49
|
* Map of pre-parsed messages, keyed by the message key and locale
|
|
@@ -85,7 +96,7 @@ export class MessageLoader {
|
|
|
85
96
|
this._subscribers = new Set();
|
|
86
97
|
|
|
87
98
|
this._loadLocale(this.defaultLocale);
|
|
88
|
-
this.fallbackMessage = new InternalIntlMessage(
|
|
99
|
+
this.fallbackMessage = new InternalIntlMessage([], this.defaultLocale);
|
|
89
100
|
|
|
90
101
|
// In cases where hot module replacement is available, set up cache clearing whenever the
|
|
91
102
|
// targets change so that values are always replaced.
|
|
@@ -93,9 +104,9 @@ export class MessageLoader {
|
|
|
93
104
|
if (module.hot) {
|
|
94
105
|
for (const [locale, file] of Object.entries(localeImportMap)) {
|
|
95
106
|
// @ts-expect-error `hot` not defined in types.
|
|
96
|
-
module.hot.accept(file, () => {
|
|
107
|
+
module.hot.accept(file, async () => {
|
|
108
|
+
await this._loadLocale(locale);
|
|
97
109
|
this._parseCache.clear();
|
|
98
|
-
this._loadLocale(locale);
|
|
99
110
|
});
|
|
100
111
|
}
|
|
101
112
|
}
|
|
@@ -116,9 +127,7 @@ export class MessageLoader {
|
|
|
116
127
|
get(key: string, locale: LocaleId): InternalIntlMessage {
|
|
117
128
|
const value =
|
|
118
129
|
this.getMessageValue(key, locale) ?? this.getMessageValue(key, this.defaultLocale);
|
|
119
|
-
if (value != null)
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
130
|
+
if (value != null) return value;
|
|
122
131
|
|
|
123
132
|
// If the message couldn't be found in either the requested nor the default locale, then
|
|
124
133
|
// nothing can be done.
|
|
@@ -149,18 +158,14 @@ export class MessageLoader {
|
|
|
149
158
|
const cachedValue = this._parseCache.get(cacheKey);
|
|
150
159
|
if (cachedValue != null) return cachedValue;
|
|
151
160
|
|
|
152
|
-
//
|
|
153
|
-
if
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// If not, check whether it's been loaded and trigger a load if not.
|
|
161
|
+
// Check whether the locale exists in memory. If not, start a request to
|
|
162
|
+
// load the locale if one has not already started. It's impossible for
|
|
163
|
+
// the request to finish within the same tick, so return undefined in any
|
|
164
|
+
// case then.
|
|
158
165
|
if (this.messages[locale] == null) {
|
|
159
|
-
// Ensure the locale is loaded, if it is supported
|
|
160
166
|
if (this.supportedLocales.includes(locale)) {
|
|
161
167
|
this._loadLocale(locale);
|
|
162
168
|
}
|
|
163
|
-
|
|
164
169
|
return undefined;
|
|
165
170
|
}
|
|
166
171
|
|
|
@@ -197,7 +202,7 @@ export class MessageLoader {
|
|
|
197
202
|
|
|
198
203
|
async _loadLocale(locale: LocaleId) {
|
|
199
204
|
// Don't re-load a locale that's already in progress.
|
|
200
|
-
if (this._localeLoadingPromises[locale] != null) {
|
|
205
|
+
if (this._localeLoadingPromises[locale]?.current != null) {
|
|
201
206
|
return;
|
|
202
207
|
}
|
|
203
208
|
|
|
@@ -213,10 +218,10 @@ export class MessageLoader {
|
|
|
213
218
|
// If the locale is already set in `messages`, then it doesn't need to be loaded again.
|
|
214
219
|
if (this.messages[locale] != null) return;
|
|
215
220
|
|
|
216
|
-
const
|
|
217
|
-
this._localeLoadingPromises[locale] =
|
|
218
|
-
this.messages[locale] = (await
|
|
219
|
-
|
|
221
|
+
const current = this.localeImportMap[locale]();
|
|
222
|
+
this._localeLoadingPromises[locale] = { initialized: false, current };
|
|
223
|
+
this.messages[locale] = (await current).default;
|
|
224
|
+
this._localeLoadingPromises[locale] = { initialized: true, current: undefined };
|
|
220
225
|
this.emitChange();
|
|
221
226
|
}
|
|
222
227
|
|
|
@@ -238,4 +243,77 @@ export class MessageLoader {
|
|
|
238
243
|
|
|
239
244
|
return () => this._subscribers.delete(callback);
|
|
240
245
|
}
|
|
246
|
+
|
|
247
|
+
isLocaleLoaded(locale: LocaleId, requireCurrent: boolean = false): boolean {
|
|
248
|
+
const ref = this._localeLoadingPromises[locale];
|
|
249
|
+
// Ensure the locale at least exists and is initialized.
|
|
250
|
+
if (ref == null || ref.initialized == false) return false;
|
|
251
|
+
// `current` will be deleted once the locale has loaded, so if it is null,
|
|
252
|
+
// then the locale data is current.
|
|
253
|
+
if (requireCurrent) return ref.current == null;
|
|
254
|
+
// Otherwise, initialization is enough.
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async waitForLocaleLoaded(locale: LocaleId, requireCurrent = false): Promise<void> {
|
|
259
|
+
const ref = this._localeLoadingPromises[locale];
|
|
260
|
+
// If the locale hasn't been loaded at all, kick off the load and return
|
|
261
|
+
// that Promise directly.
|
|
262
|
+
if (ref == null) return this._loadLocale(locale);
|
|
263
|
+
// If initialization is enough for this request, just resolve immediately.
|
|
264
|
+
if (ref.initialized && !requireCurrent) return;
|
|
265
|
+
// Otherwise, check the `current` loading state on the object, and if that
|
|
266
|
+
// doesn't exist, then the locale data is already finished loading, and the
|
|
267
|
+
// containing Promise will just resolve with `undefined`.
|
|
268
|
+
await ref.current;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Returns true if this loader's default locale has finished loading. When
|
|
273
|
+
* true, all messages managed by this loader are guaranteed to have _a_
|
|
274
|
+
* render-able value, even if one does not exist in the current locale.
|
|
275
|
+
*/
|
|
276
|
+
async waitForDefaultLocale(requireCurrent = false): Promise<void> {
|
|
277
|
+
return this.waitForLocaleLoaded(this.defaultLocale, requireCurrent);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const LOADER_REGISTRY: MessageLoader[] = [];
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Kick off a load request for the given locale across all currently-registered
|
|
285
|
+
* message loaders, returning a Promise that resolves when all loaders have
|
|
286
|
+
* finished loading that locale.
|
|
287
|
+
*/
|
|
288
|
+
export async function loadAllMessagesInLocale(locale: LocaleId): Promise<void> {
|
|
289
|
+
await Promise.all(LOADER_REGISTRY.map((loader) => loader._loadLocale(locale)));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Returns a new Promise that resolves after all currently-registered message
|
|
294
|
+
* loaders have finished successfully loading their default locale content.
|
|
295
|
+
* Once this Promise has resolved, all messages that currently exist in the
|
|
296
|
+
* application (i.e., within all modules that have been imported or required in
|
|
297
|
+
* the current session) can be guaranteed to have _a_ render-able value.
|
|
298
|
+
*
|
|
299
|
+
* This function can be called at any point in an applications life cycle and
|
|
300
|
+
* will always ensure that _all_ loaders that currently exist are initialized.
|
|
301
|
+
*/
|
|
302
|
+
export async function waitForAllDefaultIntlMessagesLoaded(): Promise<void> {
|
|
303
|
+
await Promise.all(LOADER_REGISTRY.map((loader) => loader.waitForDefaultLocale()));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Create a new MessageLoader, which handles lazily loading messages for
|
|
308
|
+
* different locales and sanity checks as needed to provide accessors for each
|
|
309
|
+
* message defined in `messageKeys`.
|
|
310
|
+
*/
|
|
311
|
+
export function createLoader(
|
|
312
|
+
messageKeys: string[],
|
|
313
|
+
localeImportMap: LocaleImportMap,
|
|
314
|
+
defaultLocale: LocaleId,
|
|
315
|
+
) {
|
|
316
|
+
const loader = new MessageLoader(messageKeys, localeImportMap, defaultLocale);
|
|
317
|
+
LOADER_REGISTRY.push(loader);
|
|
318
|
+
return loader;
|
|
241
319
|
}
|
package/src/message.ts
CHANGED
|
@@ -1,44 +1,11 @@
|
|
|
1
|
-
import { FormatJsNode
|
|
1
|
+
import { FormatJsNode } from './keyless-json';
|
|
2
2
|
|
|
3
3
|
export class InternalIntlMessage {
|
|
4
4
|
locale: string;
|
|
5
|
-
message?: string;
|
|
6
5
|
ast: FormatJsNode[];
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
* A stripped-down representation of the message with no rich formatting
|
|
10
|
-
* elements preserved. This will be lazily created the first time it is
|
|
11
|
-
* requested (e.g., by calling `formatToPlainString`).
|
|
12
|
-
*/
|
|
13
|
-
get plainAst(): FormatJsNode[] {
|
|
14
|
-
return (this._plainAst = this._removeRichTags(this.ast));
|
|
15
|
-
}
|
|
16
|
-
_plainAst: FormatJsNode[];
|
|
17
|
-
|
|
18
|
-
constructor(messageOrAst: string | FormatJsNode[], locale: string) {
|
|
7
|
+
constructor(messageOrAst: FormatJsNode[], locale: string) {
|
|
19
8
|
this.locale = locale;
|
|
20
|
-
|
|
21
|
-
if (typeof messageOrAst === 'string') {
|
|
22
|
-
this.message = messageOrAst;
|
|
23
|
-
} else {
|
|
24
|
-
this.ast = messageOrAst;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Returns the same element with any formatting tags removed. If the element
|
|
30
|
-
* itself is a tag, the children will be hoisted and returned, otherwise the
|
|
31
|
-
* element is returned as an array of itself.
|
|
32
|
-
*/
|
|
33
|
-
private _removeRichTags(nodes: FormatJsNode[]): FormatJsNode[] {
|
|
34
|
-
const result = new Array(nodes.length);
|
|
35
|
-
for (const node of nodes) {
|
|
36
|
-
if (node.type === FormatJsNodeType.Tag) {
|
|
37
|
-
result.push(...this._removeRichTags(node.children));
|
|
38
|
-
} else {
|
|
39
|
-
result.push(node);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
9
|
+
this.ast = messageOrAst;
|
|
43
10
|
}
|
|
44
11
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -198,8 +198,8 @@ export interface FunctionTypeMap {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
export interface FunctionTypes<Result, HandlerType = (content: Result[]) => Result | Result[]> {
|
|
201
|
-
link: undefined | ((content: Result[]) => Result | Result[]);
|
|
202
|
-
hook: undefined | ((content: Result[]) => Result | Result[]);
|
|
201
|
+
link: undefined | ((content: Result[], key: number) => Result | Result[]);
|
|
202
|
+
hook: undefined | ((content: Result[], key: number) => Result | Result[]);
|
|
203
203
|
handler: undefined | HandlerType;
|
|
204
204
|
}
|
|
205
205
|
|