@discord/intl 0.16.1 → 0.17.0-canary.18e68ab
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/data-formatters/cache.d.ts +21 -0
- package/dist/data-formatters/cache.js +47 -0
- package/dist/data-formatters/config.d.ts +81 -0
- package/dist/{format-config.js → data-formatters/config.js} +16 -5
- package/dist/data-formatters/index.d.ts +26 -0
- package/dist/data-formatters/index.js +54 -0
- package/dist/format.d.ts +4 -4
- package/dist/format.js +22 -18
- package/dist/formatters/index.d.ts +1 -1
- package/dist/formatters/react.d.ts +30 -1
- package/dist/formatters/react.js +2 -3
- package/dist/index.d.ts +3 -19
- package/dist/index.js +4 -2
- package/dist/intl-manager.d.ts +17 -8
- package/dist/intl-manager.js +13 -13
- package/package.json +3 -2
- package/src/data-formatters/cache.ts +60 -0
- package/src/data-formatters/config.ts +98 -0
- package/src/data-formatters/index.ts +103 -0
- package/src/format.ts +36 -22
- package/src/formatters/index.ts +2 -0
- package/src/formatters/react.ts +42 -7
- package/src/index.ts +3 -22
- package/src/intl-manager.ts +29 -19
- package/dist/format-config.d.ts +0 -17
- package/src/format-config.ts +0 -72
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DurationFormat as FormatJsDurationFormat } from '@formatjs/intl-durationformat';
|
|
2
|
+
type Cache<Value> = Map<string, Value>;
|
|
3
|
+
type TemporaryIntlDurationFormat = typeof FormatJsDurationFormat;
|
|
4
|
+
declare class FormatterCache {
|
|
5
|
+
dateTime: Cache<Intl.DateTimeFormat>;
|
|
6
|
+
duration: Cache<TemporaryIntlDurationFormat>;
|
|
7
|
+
list: Cache<Intl.ListFormat>;
|
|
8
|
+
number: Cache<Intl.NumberFormat>;
|
|
9
|
+
pluralRules: Cache<Intl.PluralRules>;
|
|
10
|
+
relativeTime: Cache<Intl.RelativeTimeFormat>;
|
|
11
|
+
getDateTimeFormatter(...args: ConstructorParameters<typeof Intl.DateTimeFormat>): Intl.DateTimeFormat;
|
|
12
|
+
getDurationFormatter(...args: ConstructorParameters<TemporaryIntlDurationFormat>): any;
|
|
13
|
+
getListFormatter(...args: ConstructorParameters<typeof Intl.ListFormat>): Intl.ListFormat;
|
|
14
|
+
getNumberFormatter(...args: ConstructorParameters<typeof Intl.NumberFormat>): Intl.NumberFormat;
|
|
15
|
+
getPluralRules(...args: ConstructorParameters<typeof Intl.PluralRules>): Intl.PluralRules;
|
|
16
|
+
getRelativeTimeFormatter(...args: ConstructorParameters<typeof Intl.RelativeTimeFormat>): Intl.RelativeTimeFormat;
|
|
17
|
+
_getCached<T, Args>(cache: Cache<T>, args: Args, constructor: (args: Args) => T): T;
|
|
18
|
+
_getKey(...args: any): string;
|
|
19
|
+
}
|
|
20
|
+
export declare const dataFormatterCache: FormatterCache;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dataFormatterCache = void 0;
|
|
4
|
+
class FormatterCache {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.dateTime = new Map();
|
|
7
|
+
this.duration = new Map();
|
|
8
|
+
this.list = new Map();
|
|
9
|
+
this.number = new Map();
|
|
10
|
+
this.pluralRules = new Map();
|
|
11
|
+
this.relativeTime = new Map();
|
|
12
|
+
}
|
|
13
|
+
getDateTimeFormatter(...args) {
|
|
14
|
+
return this._getCached(this.dateTime, args, (args) => new Intl.DateTimeFormat(...args));
|
|
15
|
+
}
|
|
16
|
+
getDurationFormatter(...args) {
|
|
17
|
+
return this._getCached(this.duration, args,
|
|
18
|
+
// @ts-expect-error DurationFormat is _not_ included in typescript
|
|
19
|
+
// https://github.com/microsoft/TypeScript/issues/60608
|
|
20
|
+
(args) => new Intl.DurationFormat(...args));
|
|
21
|
+
}
|
|
22
|
+
getListFormatter(...args) {
|
|
23
|
+
return this._getCached(this.list, args, (args) => new Intl.ListFormat(...args));
|
|
24
|
+
}
|
|
25
|
+
getNumberFormatter(...args) {
|
|
26
|
+
return this._getCached(this.number, args, (args) => new Intl.NumberFormat(...args));
|
|
27
|
+
}
|
|
28
|
+
getPluralRules(...args) {
|
|
29
|
+
return this._getCached(this.pluralRules, args, (args) => new Intl.PluralRules(...args));
|
|
30
|
+
}
|
|
31
|
+
getRelativeTimeFormatter(...args) {
|
|
32
|
+
return this._getCached(this.relativeTime, args, (args) => new Intl.RelativeTimeFormat(...args));
|
|
33
|
+
}
|
|
34
|
+
_getCached(cache, args, constructor) {
|
|
35
|
+
const key = this._getKey(args);
|
|
36
|
+
const cached = cache.get(key);
|
|
37
|
+
if (cached)
|
|
38
|
+
return cached;
|
|
39
|
+
const created = constructor(args);
|
|
40
|
+
cache.set(key, created);
|
|
41
|
+
return created;
|
|
42
|
+
}
|
|
43
|
+
_getKey(...args) {
|
|
44
|
+
return JSON.stringify(args);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.dataFormatterCache = new FormatterCache();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { DurationFormatOptions, DurationInput } from '@formatjs/intl-durationformat/src/types';
|
|
2
|
+
export type TemporaryDurationInput = DurationInput;
|
|
3
|
+
export type TemporaryDurationFormatOptions = DurationFormatOptions;
|
|
4
|
+
export interface FormatConfigType {
|
|
5
|
+
date: Record<string, Intl.DateTimeFormatOptions>;
|
|
6
|
+
duration: Record<string, TemporaryDurationFormatOptions>;
|
|
7
|
+
list: Record<string, Intl.ListFormatOptions>;
|
|
8
|
+
number: Record<string, Intl.NumberFormatOptions>;
|
|
9
|
+
relativeTime: Record<string, Intl.RelativeTimeFormatOptions>;
|
|
10
|
+
time: Record<string, Intl.DateTimeFormatOptions>;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveFormatConfigOptions<T extends object, const K extends string>(config: Record<K, T>, style?: T & {
|
|
13
|
+
format?: K;
|
|
14
|
+
}): T;
|
|
15
|
+
export declare const DEFAULT_FORMAT_CONFIG: {
|
|
16
|
+
duration: {};
|
|
17
|
+
list: {};
|
|
18
|
+
relativeTime: {};
|
|
19
|
+
/**
|
|
20
|
+
* Default formatting configuration options for common date, time, and number formats. This is
|
|
21
|
+
* taken almost directly from FormatJS's defaults here, for the sake of compatibility.
|
|
22
|
+
* https://github.com/formatjs/formatjs/blob/c30975bfbe2db7eb62f4dbe6c8ad6ca5e786dcb3/packages/intl-messageformat/src/core.ts#L229-L296
|
|
23
|
+
*/
|
|
24
|
+
number: {
|
|
25
|
+
integer: {
|
|
26
|
+
maximumFractionDigits: number;
|
|
27
|
+
};
|
|
28
|
+
currency: {
|
|
29
|
+
style: "currency";
|
|
30
|
+
};
|
|
31
|
+
percent: {
|
|
32
|
+
style: "percent";
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
date: {
|
|
36
|
+
short: {
|
|
37
|
+
month: "numeric";
|
|
38
|
+
day: "numeric";
|
|
39
|
+
year: "2-digit";
|
|
40
|
+
};
|
|
41
|
+
medium: {
|
|
42
|
+
month: "short";
|
|
43
|
+
day: "numeric";
|
|
44
|
+
year: "numeric";
|
|
45
|
+
};
|
|
46
|
+
long: {
|
|
47
|
+
month: "long";
|
|
48
|
+
day: "numeric";
|
|
49
|
+
year: "numeric";
|
|
50
|
+
};
|
|
51
|
+
full: {
|
|
52
|
+
weekday: "long";
|
|
53
|
+
month: "long";
|
|
54
|
+
day: "numeric";
|
|
55
|
+
year: "numeric";
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
time: {
|
|
59
|
+
short: {
|
|
60
|
+
hour: "numeric";
|
|
61
|
+
minute: "numeric";
|
|
62
|
+
};
|
|
63
|
+
medium: {
|
|
64
|
+
hour: "numeric";
|
|
65
|
+
minute: "numeric";
|
|
66
|
+
second: "numeric";
|
|
67
|
+
};
|
|
68
|
+
long: {
|
|
69
|
+
hour: "numeric";
|
|
70
|
+
minute: "numeric";
|
|
71
|
+
second: "numeric";
|
|
72
|
+
timeZoneName: "short";
|
|
73
|
+
};
|
|
74
|
+
full: {
|
|
75
|
+
hour: "numeric";
|
|
76
|
+
minute: "numeric";
|
|
77
|
+
second: "numeric";
|
|
78
|
+
timeZoneName: "short";
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_FORMAT_CONFIG = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
exports.resolveFormatConfigOptions = resolveFormatConfigOptions;
|
|
5
|
+
function resolveFormatConfigOptions(config, style) {
|
|
6
|
+
if (typeof (style === null || style === void 0 ? void 0 : style.format) === 'string') {
|
|
7
|
+
return Object.assign(Object.assign({}, config[style.format]), style);
|
|
8
|
+
}
|
|
9
|
+
return style;
|
|
10
|
+
}
|
|
9
11
|
exports.DEFAULT_FORMAT_CONFIG = {
|
|
12
|
+
// These have no known defaults, so they can't be applied easily.
|
|
13
|
+
duration: {},
|
|
14
|
+
list: {},
|
|
15
|
+
relativeTime: {},
|
|
16
|
+
/**
|
|
17
|
+
* Default formatting configuration options for common date, time, and number formats. This is
|
|
18
|
+
* taken almost directly from FormatJS's defaults here, for the sake of compatibility.
|
|
19
|
+
* https://github.com/formatjs/formatjs/blob/c30975bfbe2db7eb62f4dbe6c8ad6ca5e786dcb3/packages/intl-messageformat/src/core.ts#L229-L296
|
|
20
|
+
*/
|
|
10
21
|
number: {
|
|
11
22
|
integer: { maximumFractionDigits: 0 },
|
|
12
23
|
currency: { style: 'currency' },
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FormatConfigType, TemporaryDurationFormatOptions, TemporaryDurationInput } from './config';
|
|
2
|
+
export interface DataFormatters<FormatConfig extends FormatConfigType> {
|
|
3
|
+
formatDate(value: number | Date, style?: Intl.DateTimeFormatOptions | {
|
|
4
|
+
format?: keyof FormatConfig['date'] & string;
|
|
5
|
+
}): string;
|
|
6
|
+
formatDuration(value: TemporaryDurationInput, style?: TemporaryDurationFormatOptions | {
|
|
7
|
+
format?: keyof FormatConfig['duration'] & string;
|
|
8
|
+
}): string;
|
|
9
|
+
formatNumber(value: number, style?: Intl.NumberFormatOptions | {
|
|
10
|
+
format?: keyof FormatConfig['number'] & string;
|
|
11
|
+
}): string;
|
|
12
|
+
formatList(values: any[], style?: Intl.ListFormatOptions | {
|
|
13
|
+
format?: keyof FormatConfig['list'] & string;
|
|
14
|
+
}): string;
|
|
15
|
+
formatListToParts<T>(values: T[], style?: Intl.ListFormatOptions | {
|
|
16
|
+
format?: keyof FormatConfig['list'] & string;
|
|
17
|
+
}): Array<T | string>;
|
|
18
|
+
formatRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, style?: Intl.RelativeTimeFormatOptions | {
|
|
19
|
+
format?: keyof FormatConfig['relativeTime'] & string;
|
|
20
|
+
}): string;
|
|
21
|
+
formatTime(value: number | Date, style?: Intl.DateTimeFormatOptions | {
|
|
22
|
+
format?: keyof FormatConfig['time'] & string;
|
|
23
|
+
}): string;
|
|
24
|
+
getPluralRules(options: Intl.PluralRulesOptions): Intl.PluralRules;
|
|
25
|
+
}
|
|
26
|
+
export declare function makeDataFormatters<const FormatConfig extends FormatConfigType>(locales: string[], formatConfig: FormatConfig): DataFormatters<FormatConfig>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeDataFormatters = makeDataFormatters;
|
|
4
|
+
const config_1 = require("./config");
|
|
5
|
+
const cache_1 = require("./cache");
|
|
6
|
+
function makeDataFormatters(locales, formatConfig) {
|
|
7
|
+
return {
|
|
8
|
+
formatDate(value, style) {
|
|
9
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.date, style);
|
|
10
|
+
return cache_1.dataFormatterCache.getDateTimeFormatter(locales, options).format(value);
|
|
11
|
+
},
|
|
12
|
+
formatDuration(value, style) {
|
|
13
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.time, style);
|
|
14
|
+
return cache_1.dataFormatterCache.getDurationFormatter(locales, options).format(value);
|
|
15
|
+
},
|
|
16
|
+
formatNumber(value, style) {
|
|
17
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.number, style);
|
|
18
|
+
return cache_1.dataFormatterCache.getNumberFormatter(locales, options).format(value);
|
|
19
|
+
},
|
|
20
|
+
formatList(values, style) {
|
|
21
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.list, style);
|
|
22
|
+
return cache_1.dataFormatterCache.getListFormatter(locales, options).format(values);
|
|
23
|
+
},
|
|
24
|
+
formatListToParts(values, style) {
|
|
25
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.list, style);
|
|
26
|
+
// Intl.ListFormat only accepts string arguments, even for `formatToParts`,
|
|
27
|
+
// but we want to support formatting of complex values like React nodes as
|
|
28
|
+
// part of a list (think an "and more" link at the end of a list).
|
|
29
|
+
//
|
|
30
|
+
// To enable that, we make placeholders with hopefully-unusable sentinel
|
|
31
|
+
// values that can be passed to `formatToParts`, then after the formatting
|
|
32
|
+
// we map the original values back into the parts.
|
|
33
|
+
const placeholders = {};
|
|
34
|
+
for (const index in values) {
|
|
35
|
+
placeholders['$+/-$placeholder.' + index] = values[index];
|
|
36
|
+
}
|
|
37
|
+
const parts = cache_1.dataFormatterCache
|
|
38
|
+
.getListFormatter(locales, options)
|
|
39
|
+
.formatToParts(Object.keys(placeholders));
|
|
40
|
+
return parts.map((part) => { var _a; return (part.value = (_a = placeholders[part.value]) !== null && _a !== void 0 ? _a : part.value); });
|
|
41
|
+
},
|
|
42
|
+
formatRelativeTime(value, unit, style) {
|
|
43
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.relativeTime, style);
|
|
44
|
+
return cache_1.dataFormatterCache.getRelativeTimeFormatter(locales, options).format(value, unit);
|
|
45
|
+
},
|
|
46
|
+
formatTime(value, style) {
|
|
47
|
+
const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.time, style);
|
|
48
|
+
return cache_1.dataFormatterCache.getDateTimeFormatter(locales, options).format(value);
|
|
49
|
+
},
|
|
50
|
+
getPluralRules(options) {
|
|
51
|
+
return cache_1.dataFormatterCache.getPluralRules(locales, options);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/format.d.ts
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* parameter, important to have React treat the resulting elements nicely.
|
|
18
18
|
*/
|
|
19
19
|
import { AstNode } from '@discord/intl-ast';
|
|
20
|
-
import { Formatters } from 'intl-messageformat';
|
|
21
|
-
import type { FormatConfig } from './format-config';
|
|
22
20
|
import type { RichTextTagNames } from './types';
|
|
21
|
+
import { FormatConfigType } from './data-formatters/config';
|
|
22
|
+
import { DataFormatters } from './data-formatters';
|
|
23
23
|
export declare abstract class FormatBuilder<Result, ObjectType = Result extends object ? Result : never> {
|
|
24
24
|
abstract pushRichTextTag(tag: RichTextTagNames, children: Result[], control: Result[]): void;
|
|
25
25
|
abstract pushLiteralText(text: string): void;
|
|
@@ -27,5 +27,5 @@ export declare abstract class FormatBuilder<Result, ObjectType = Result extends
|
|
|
27
27
|
abstract finish(): Result[];
|
|
28
28
|
}
|
|
29
29
|
export type FormatBuilderConstructor<Result> = new () => FormatBuilder<Result>;
|
|
30
|
-
export declare function bindFormatValuesWithBuilder<T, ObjectType, Builder extends FormatBuilder<T, ObjectType
|
|
31
|
-
export declare function bindFormatValues<Result>(Builder: FormatBuilderConstructor<Result>, nodes: string | AstNode[], locales: string | string[],
|
|
30
|
+
export declare function bindFormatValuesWithBuilder<T, ObjectType, Builder extends FormatBuilder<T, ObjectType>, FormatConfig extends FormatConfigType>(builder: Builder, nodes: AstNode[], locales: string | string[], formatters: DataFormatters<FormatConfig>, formatConfig: FormatConfig, values?: Record<string, string | object>, currentPluralValue?: number, originalMessage?: string): void;
|
|
31
|
+
export declare function bindFormatValues<Result, FormatConfig extends FormatConfigType>(Builder: FormatBuilderConstructor<Result>, nodes: string | AstNode[], locales: string | string[], dataFormatters: DataFormatters<FormatConfig>, formatConfig: FormatConfig, values?: Record<string, string | object>, currentPluralValue?: number): Result[];
|
package/dist/format.js
CHANGED
|
@@ -23,7 +23,6 @@ exports.bindFormatValuesWithBuilder = bindFormatValuesWithBuilder;
|
|
|
23
23
|
exports.bindFormatValues = bindFormatValues;
|
|
24
24
|
const icu_skeleton_parser_1 = require("@formatjs/icu-skeleton-parser");
|
|
25
25
|
const intl_ast_1 = require("@discord/intl-ast");
|
|
26
|
-
const intl_messageformat_1 = require("intl-messageformat");
|
|
27
26
|
/**
|
|
28
27
|
* Returns true if the tag name should be considered a rich text tag that
|
|
29
28
|
* applies formatting to a message, rather than being a user-supplied value.
|
|
@@ -34,6 +33,14 @@ function isRichTextTag(name) {
|
|
|
34
33
|
class FormatBuilder {
|
|
35
34
|
}
|
|
36
35
|
exports.FormatBuilder = FormatBuilder;
|
|
36
|
+
class MissingValueError extends Error {
|
|
37
|
+
constructor(variableName, originalMessage, nodeType) {
|
|
38
|
+
super(`No value for variable '${variableName}' was provided for the localized message '${originalMessage}'`);
|
|
39
|
+
this.variableName = variableName;
|
|
40
|
+
this.originalMessage = originalMessage;
|
|
41
|
+
this.nodeType = nodeType;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
37
44
|
function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, formatConfig, values = {}, currentPluralValue, originalMessage) {
|
|
38
45
|
var _a;
|
|
39
46
|
// Hot path for static messages that are just parsed as a single string element.
|
|
@@ -41,6 +48,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
41
48
|
builder.pushLiteralText(nodes[0]);
|
|
42
49
|
return;
|
|
43
50
|
}
|
|
51
|
+
let keyIndex = 0;
|
|
44
52
|
for (const node of nodes) {
|
|
45
53
|
if (typeof node === 'string') {
|
|
46
54
|
builder.pushLiteralText(node);
|
|
@@ -53,7 +61,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
53
61
|
// numeric values are replaced, otherwise the value is completed ignored?
|
|
54
62
|
// Behavior copied from FormatJS directly.
|
|
55
63
|
if (typeof currentPluralValue === 'number') {
|
|
56
|
-
const value = formatters.
|
|
64
|
+
const value = formatters.formatNumber(currentPluralValue);
|
|
57
65
|
builder.pushLiteralText(value);
|
|
58
66
|
}
|
|
59
67
|
continue;
|
|
@@ -62,7 +70,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
62
70
|
// Enforce that all required values are provided by the caller, even if the
|
|
63
71
|
// actual value is falsy/undefined.
|
|
64
72
|
if (!(variableName in values) && !isRichTextTag(variableName)) {
|
|
65
|
-
throw new
|
|
73
|
+
throw new MissingValueError(variableName, originalMessage, nodeType);
|
|
66
74
|
}
|
|
67
75
|
const value = values[variableName];
|
|
68
76
|
switch (nodeType) {
|
|
@@ -91,9 +99,8 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
91
99
|
? formatConfig.date[nodeStyle]
|
|
92
100
|
: nodeStyle != null
|
|
93
101
|
? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(nodeStyle)
|
|
94
|
-
:
|
|
95
|
-
|
|
96
|
-
builder.pushLiteralText(formatters.getDateTimeFormat(locales, style).format(value));
|
|
102
|
+
: undefined;
|
|
103
|
+
builder.pushLiteralText(formatters.formatDate(value, style));
|
|
97
104
|
break;
|
|
98
105
|
}
|
|
99
106
|
case intl_ast_1.FormatJsNodeType.Time: {
|
|
@@ -104,10 +111,8 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
104
111
|
? formatConfig.time[nodeStyle]
|
|
105
112
|
: nodeStyle != null
|
|
106
113
|
? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(nodeStyle)
|
|
107
|
-
: undefined;
|
|
108
|
-
builder.pushLiteralText(
|
|
109
|
-
// @ts-expect-error Cast string values to dates properly.
|
|
110
|
-
formatters.getDateTimeFormat(locales, style).format(value));
|
|
114
|
+
: undefined;
|
|
115
|
+
builder.pushLiteralText(formatters.formatTime(value, style));
|
|
111
116
|
break;
|
|
112
117
|
}
|
|
113
118
|
case intl_ast_1.FormatJsNodeType.Number: {
|
|
@@ -122,7 +127,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
122
127
|
const scaledValue =
|
|
123
128
|
// @ts-expect-error This is a weird cast that's not accurate, but works in the short term.
|
|
124
129
|
typeof value !== 'number' ? value : value * ((_a = style === null || style === void 0 ? void 0 : style.scale) !== null && _a !== void 0 ? _a : 1);
|
|
125
|
-
builder.pushLiteralText(formatters.
|
|
130
|
+
builder.pushLiteralText(formatters.formatNumber(scaledValue, style));
|
|
126
131
|
break;
|
|
127
132
|
}
|
|
128
133
|
case intl_ast_1.FormatJsNodeType.Tag: {
|
|
@@ -139,7 +144,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
139
144
|
if (typeof value !== 'function') {
|
|
140
145
|
throw `expected a function type for a Tag formatting value, ${variableName}. got ${typeof value}: ${value}`;
|
|
141
146
|
}
|
|
142
|
-
let chunks = value(appliedChildren);
|
|
147
|
+
let chunks = value(appliedChildren, `${keyIndex++}`);
|
|
143
148
|
chunks = Array.isArray(chunks) ? chunks : [chunks];
|
|
144
149
|
for (const chunk of chunks) {
|
|
145
150
|
if (typeof chunk === 'string') {
|
|
@@ -171,10 +176,9 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
171
176
|
const exactSelector = `=${value}`;
|
|
172
177
|
if (exactSelector in options)
|
|
173
178
|
return options[exactSelector];
|
|
174
|
-
const rule = formatters
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
.select(value - (offset !== null && offset !== void 0 ? offset : 0));
|
|
179
|
+
const rule = formatters.getPluralRules({ type: pluralType }).select(
|
|
180
|
+
// @ts-expect-error Assert this `as number` properly.
|
|
181
|
+
value - (offset !== null && offset !== void 0 ? offset : 0));
|
|
178
182
|
return (_a = options[rule]) !== null && _a !== void 0 ? _a : options.other;
|
|
179
183
|
})();
|
|
180
184
|
if (option == null) {
|
|
@@ -188,14 +192,14 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
|
-
function bindFormatValues(Builder, nodes, locales,
|
|
195
|
+
function bindFormatValues(Builder, nodes, locales, dataFormatters, formatConfig, values = {}, currentPluralValue) {
|
|
192
196
|
const builder = new Builder();
|
|
193
197
|
if (typeof nodes === 'string') {
|
|
194
198
|
builder.pushLiteralText(nodes);
|
|
195
199
|
return builder.finish();
|
|
196
200
|
}
|
|
197
201
|
else {
|
|
198
|
-
bindFormatValuesWithBuilder(builder, nodes, locales,
|
|
202
|
+
bindFormatValuesWithBuilder(builder, nodes, locales, dataFormatters, formatConfig, values, currentPluralValue);
|
|
199
203
|
return builder.finish();
|
|
200
204
|
}
|
|
201
205
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { astFormatter, type AstFunctionTypes, type RichTextNode, RichTextNodeType } from './ast';
|
|
2
2
|
export { markdownFormatter, type MarkdownFunctionTypes } from './markdown';
|
|
3
|
-
export { reactFormatter, makeReactFormatter, DEFAULT_REACT_RICH_TEXT_ELEMENTS, type ReactFunctionTypes, ReactIntlMessage, } from './react';
|
|
3
|
+
export { reactFormatter, makeReactFormatter, DEFAULT_REACT_RICH_TEXT_ELEMENTS, type ReactFunctionTypes, ReactIntlMessage, ReactIntlPlainString, ReactIntlRichText, } from './react';
|
|
4
4
|
export { stringFormatter, type StringFunctionTypes } from './string';
|
|
@@ -18,7 +18,36 @@ 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
|
-
|
|
21
|
+
/**
|
|
22
|
+
* A type representing any message that has been formatted using one of the
|
|
23
|
+
* default React formatting methods in the `@discord/intl` system. Use this
|
|
24
|
+
* type when you don't care about whether a message is static or contains rich
|
|
25
|
+
* text or has dynamically-formatted values, when you need to handle both in
|
|
26
|
+
* tandem, or when you need a generic return type for a function returning "any
|
|
27
|
+
* already-formatted React message".
|
|
28
|
+
*
|
|
29
|
+
* Generally, this is like `React.ReactNode`, accepting both strings and rich
|
|
30
|
+
* `ReactElement`s, but it is more specific to disallow nullish values and
|
|
31
|
+
* other special React elements like `ReactPortal`. Seeing this type, you can
|
|
32
|
+
* be confident that the value is intended to be the result of formatting an
|
|
33
|
+
* intl message, even if the actual value comes from elsewhere (like a
|
|
34
|
+
* user-generated string).
|
|
35
|
+
*/
|
|
36
|
+
export type ReactIntlMessage = string | ReactIntlRichText;
|
|
37
|
+
/**
|
|
38
|
+
* A branded type representing a plain string that has been rendered by the React formatter. This
|
|
39
|
+
* type should generally _not_ be used as a type constraint unless _absolute certainty_ that a
|
|
40
|
+
* message was formatted is desirable. Instead, accept plain `string`, and this will be compatible.
|
|
41
|
+
*/
|
|
42
|
+
export type ReactIntlPlainString = string & {
|
|
43
|
+
__brand: 'discord-intl';
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* While `ReactIntlMessage` represents the result of rendering _any_ message with the React
|
|
47
|
+
* formatter, `ReactIntlRichText` specifically represents a message that was rendered and contains
|
|
48
|
+
* rich text, meaning the result contains React nodes itself and represents a CST of the message.
|
|
49
|
+
*/
|
|
50
|
+
export type ReactIntlRichText = Array<React.ReactElement | string> & {
|
|
22
51
|
__brand: 'discord-intl';
|
|
23
52
|
};
|
|
24
53
|
export declare function formatReact(this: IntlManager, message: AnyIntlMessage, values: object, Builder: FormatBuilderConstructor<React.ReactElement>): ReactIntlMessage;
|
package/dist/formatters/react.js
CHANGED
|
@@ -49,9 +49,8 @@ function createReactBuilder(richTextElements) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
function formatReact(message, values, Builder) {
|
|
52
|
-
if (typeof message === 'string')
|
|
53
|
-
return
|
|
54
|
-
}
|
|
52
|
+
if (typeof message === 'string')
|
|
53
|
+
return message;
|
|
55
54
|
const parts = this.bindFormatValues(Builder, message, values);
|
|
56
55
|
return parts;
|
|
57
56
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { RichTextNode } from './formatters';
|
|
3
|
-
export * from './formatters';
|
|
1
|
+
export { type DataFormatters, makeDataFormatters } from './data-formatters';
|
|
4
2
|
export { FormatBuilder, FormatBuilderConstructor, bindFormatValues } from './format';
|
|
3
|
+
export * from './formatters';
|
|
5
4
|
export { runtimeHashMessageKey } from './hash';
|
|
6
5
|
export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
|
|
7
6
|
export { createLoader, loadAllMessagesInLocale, waitForAllDefaultIntlMessagesLoaded, MessageLoader, } from './message-loader';
|
|
8
7
|
export type * from './types.d.ts';
|
|
9
|
-
/**
|
|
10
|
-
* A type representing any message that has been formatted using one of the
|
|
11
|
-
* default formatting methods in the `@discord/intl` system. Use this type
|
|
12
|
-
* when you don't care about whether a message is static or contains rich text
|
|
13
|
-
* or has dynamically-formatted values, when you need to handle both in tandem,
|
|
14
|
-
* or when you need a generic return type for a function returning "any
|
|
15
|
-
* any-already formatted message".
|
|
16
|
-
*
|
|
17
|
-
* Generally, this is like `React.ReactNode`, accepting both strings and rich
|
|
18
|
-
* `ReactElement`s, but it is more specific to disallow nullish values and
|
|
19
|
-
* other special React elements like `ReactPortal`. Seeing this type, you can
|
|
20
|
-
* be confident that the value is intended to be the result of formatting an
|
|
21
|
-
* intl message, even if the actual value comes from elsewhere (like a
|
|
22
|
-
* user-generated string).
|
|
23
|
-
*/
|
|
24
|
-
export type ReactIntlMessage = string | React.ReactElement | Array<string | React.ReactElement>;
|
|
25
8
|
/**
|
|
26
9
|
* The return value of `formatToParts` from `@discord/intl`, this type
|
|
27
10
|
* represents any AST structure for a message rendered using this system.
|
|
@@ -32,4 +15,5 @@ export type ReactIntlMessage = string | React.ReactElement | Array<string | Reac
|
|
|
32
15
|
* from a call to `formatToParts`, meaning they are intended to be fully static
|
|
33
16
|
* structures passed around for custom rendering functions to use.
|
|
34
17
|
*/
|
|
18
|
+
import { type RichTextNode } from './formatters';
|
|
35
19
|
export type IntlMessageAst = RichTextNode;
|
package/dist/index.js
CHANGED
|
@@ -14,11 +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.MessageLoader = exports.waitForAllDefaultIntlMessagesLoaded = exports.loadAllMessagesInLocale = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = exports.runtimeHashMessageKey = exports.bindFormatValues = exports.FormatBuilder = void 0;
|
|
18
|
-
|
|
17
|
+
exports.MessageLoader = exports.waitForAllDefaultIntlMessagesLoaded = exports.loadAllMessagesInLocale = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = exports.runtimeHashMessageKey = exports.bindFormatValues = exports.FormatBuilder = exports.makeDataFormatters = void 0;
|
|
18
|
+
var data_formatters_1 = require("./data-formatters");
|
|
19
|
+
Object.defineProperty(exports, "makeDataFormatters", { enumerable: true, get: function () { return data_formatters_1.makeDataFormatters; } });
|
|
19
20
|
var format_1 = require("./format");
|
|
20
21
|
Object.defineProperty(exports, "FormatBuilder", { enumerable: true, get: function () { return format_1.FormatBuilder; } });
|
|
21
22
|
Object.defineProperty(exports, "bindFormatValues", { enumerable: true, get: function () { return format_1.bindFormatValues; } });
|
|
23
|
+
__exportStar(require("./formatters"), exports);
|
|
22
24
|
var hash_1 = require("./hash");
|
|
23
25
|
Object.defineProperty(exports, "runtimeHashMessageKey", { enumerable: true, get: function () { return hash_1.runtimeHashMessageKey; } });
|
|
24
26
|
var intl_manager_1 = require("./intl-manager");
|
package/dist/intl-manager.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_FORMAT_CONFIG, type FormatConfigType } from './data-formatters/config';
|
|
2
|
+
import { FormatBuilderConstructor } from './format';
|
|
2
3
|
import type { FormatterImplementation, IntlMessageGetter, RequiredFormatValues, TypedIntlMessageGetter } from './types';
|
|
3
4
|
import { InternalIntlMessage } from './message';
|
|
4
|
-
import {
|
|
5
|
-
import { FormatConfig } from './format-config';
|
|
5
|
+
import { DataFormatters } from './data-formatters';
|
|
6
6
|
/**
|
|
7
7
|
* Fallback locale used for all internationalization when an operation in the
|
|
8
8
|
* requested locale is not possible.
|
|
@@ -14,7 +14,7 @@ export type FormatFunction<F extends FormatterImplementation<any, any>> = <T ext
|
|
|
14
14
|
type ThisWithFormatters<This, T extends Record<string, FormatterImplementation<any, any>>> = This & {
|
|
15
15
|
[K in keyof T]: FormatFunction<T[K]>;
|
|
16
16
|
};
|
|
17
|
-
export interface IntlManagerOptions {
|
|
17
|
+
export interface IntlManagerOptions<FormatConfig> {
|
|
18
18
|
/**
|
|
19
19
|
* The locale to initially have this manager use. Useful to set when information about the user's
|
|
20
20
|
* likely locale is available sooner than when that information is definitely known (at which
|
|
@@ -30,19 +30,28 @@ export interface IntlManagerOptions {
|
|
|
30
30
|
* @default DEFAULT_LOCALE
|
|
31
31
|
*/
|
|
32
32
|
defaultLocale?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Configuration for the different kinds of data dataFormatters that can be used both inside of
|
|
35
|
+
* messages and in their own functions (like `intl.formatDate`) as shorthands for common sets of
|
|
36
|
+
* options. For example, with the default config, this enables `intl.formatDate(now, 'short')`
|
|
37
|
+
* rather than having to specify the exact properties of each style every time.
|
|
38
|
+
*
|
|
39
|
+
* @default DEFAULT_FORMATTER_CONFIG
|
|
40
|
+
*/
|
|
41
|
+
formatConfig?: FormatConfig;
|
|
33
42
|
}
|
|
34
|
-
export declare class IntlManager {
|
|
43
|
+
export declare class IntlManager<const FormatConfig extends FormatConfigType = typeof DEFAULT_FORMAT_CONFIG> {
|
|
35
44
|
defaultLocale: string;
|
|
36
45
|
currentLocale: string;
|
|
37
|
-
intl: IntlShape;
|
|
38
46
|
formatConfig: FormatConfig;
|
|
47
|
+
data: DataFormatters<FormatConfig>;
|
|
39
48
|
_localeSubscriptions: Set<(locale: string) => void>;
|
|
40
|
-
constructor({ initialLocale, defaultLocale, }: IntlManagerOptions);
|
|
49
|
+
constructor({ initialLocale, defaultLocale, formatConfig, }: IntlManagerOptions<FormatConfig>);
|
|
41
50
|
/**
|
|
42
51
|
* Add a set of formatter implementations to this manager, making each available as a direct
|
|
43
52
|
* property
|
|
44
53
|
*/
|
|
45
|
-
withFormatters<const T extends Record<string, FormatterImplementation<any, any>>>(
|
|
54
|
+
withFormatters<const T extends Record<string, FormatterImplementation<any, any>>>(dataFormatters: T): ThisWithFormatters<this, T>;
|
|
46
55
|
/**
|
|
47
56
|
* Return a new function bound to this manager that uses the given `FormatterImplementation` to
|
|
48
57
|
* format a message after it has been resolved for the current locale and potentially processed in
|
package/dist/intl-manager.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.IntlManager = exports.DEFAULT_LOCALE = void 0;
|
|
4
|
-
const
|
|
4
|
+
const config_1 = require("./data-formatters/config");
|
|
5
5
|
const format_1 = require("./format");
|
|
6
|
-
const
|
|
6
|
+
const data_formatters_1 = require("./data-formatters");
|
|
7
7
|
/**
|
|
8
8
|
* Fallback locale used for all internationalization when an operation in the
|
|
9
9
|
* requested locale is not possible.
|
|
10
10
|
*/
|
|
11
11
|
exports.DEFAULT_LOCALE = 'en-US';
|
|
12
12
|
class IntlManager {
|
|
13
|
-
constructor({ initialLocale = exports.DEFAULT_LOCALE, defaultLocale = exports.DEFAULT_LOCALE,
|
|
13
|
+
constructor({ initialLocale = exports.DEFAULT_LOCALE, defaultLocale = exports.DEFAULT_LOCALE,
|
|
14
|
+
// @ts-expect-error If it's not given, formatConfig defaults to the same
|
|
15
|
+
// value as the type parameters, but typescript things this should be
|
|
16
|
+
// overwritten
|
|
17
|
+
formatConfig = config_1.DEFAULT_FORMAT_CONFIG, }) {
|
|
14
18
|
/**
|
|
15
19
|
* Subscribe to changes in the current locale for this manager.
|
|
16
20
|
*/
|
|
@@ -20,20 +24,16 @@ class IntlManager {
|
|
|
20
24
|
};
|
|
21
25
|
this.currentLocale = initialLocale;
|
|
22
26
|
this.defaultLocale = defaultLocale;
|
|
23
|
-
this.formatConfig =
|
|
24
|
-
this.
|
|
25
|
-
formats: format_config_1.DEFAULT_FORMAT_CONFIG,
|
|
26
|
-
defaultLocale,
|
|
27
|
-
locale: defaultLocale,
|
|
28
|
-
});
|
|
27
|
+
this.formatConfig = formatConfig;
|
|
28
|
+
this.data = (0, data_formatters_1.makeDataFormatters)([this.currentLocale, this.defaultLocale], this.formatConfig);
|
|
29
29
|
this._localeSubscriptions = new Set();
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Add a set of formatter implementations to this manager, making each available as a direct
|
|
33
33
|
* property
|
|
34
34
|
*/
|
|
35
|
-
withFormatters(
|
|
36
|
-
for (const [name, formatter] of Object.entries(
|
|
35
|
+
withFormatters(dataFormatters) {
|
|
36
|
+
for (const [name, formatter] of Object.entries(dataFormatters)) {
|
|
37
37
|
this[name] = this.makeFormatFunction(formatter);
|
|
38
38
|
}
|
|
39
39
|
return this;
|
|
@@ -57,7 +57,7 @@ class IntlManager {
|
|
|
57
57
|
*/
|
|
58
58
|
setLocale(locale) {
|
|
59
59
|
this.currentLocale = locale;
|
|
60
|
-
this.
|
|
60
|
+
this.data = (0, data_formatters_1.makeDataFormatters)([this.currentLocale, this.defaultLocale], this.formatConfig);
|
|
61
61
|
this.emitLocaleChange(locale);
|
|
62
62
|
}
|
|
63
63
|
emitLocaleChange(locale) {
|
|
@@ -92,7 +92,7 @@ class IntlManager {
|
|
|
92
92
|
return resolved.reserialize();
|
|
93
93
|
}
|
|
94
94
|
bindFormatValues(Builder, message, values) {
|
|
95
|
-
return (0, format_1.bindFormatValues)(Builder, message.ast, [this.currentLocale, this.defaultLocale], this.
|
|
95
|
+
return (0, format_1.bindFormatValues)(Builder, message.ast, [this.currentLocale, this.defaultLocale], this.data, this.formatConfig, values);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
exports.IntlManager = IntlManager;
|