saykit 0.0.0-beta-20260309151609

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.
@@ -0,0 +1,210 @@
1
+ //#region src/types.d.ts
2
+ type Tuple = [any, ...any[]];
3
+ type Disallow<T, K extends PropertyKey> = T & Partial<Record<K, never>>;
4
+ type Awaitable<T> = T | PromiseLike<T>;
5
+ interface NumeralOptions extends Omit<Partial<Record<Intl.LDMLPluralRule, string>>, 'other'> {
6
+ other: string;
7
+ [digit: number]: string;
8
+ }
9
+ interface SelectOptions {
10
+ other: string;
11
+ [match: string | number]: string;
12
+ }
13
+ //#endregion
14
+ //#region src/runtime.d.ts
15
+ declare namespace Say {
16
+ type Messages = {
17
+ [key: string]: string;
18
+ };
19
+ type Loader<Locale extends string> = (locale: Locale) => Messages | Promise<Messages>;
20
+ type Options<Locale extends string, Loader extends Say.Loader<Locale> | undefined> = {
21
+ locales: Locale[];
22
+ } & ({
23
+ messages: Record<Locale, Messages>;
24
+ loader?: Loader;
25
+ } | {
26
+ messages?: Partial<Record<Locale, Messages>>;
27
+ loader: Loader;
28
+ });
29
+ }
30
+ interface Say {
31
+ /**
32
+ * Define a message.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * say`Hello, ${name}!`
37
+ * ```
38
+ *
39
+ * @remark This is a macro and must be used with the relevant saykit plugin
40
+ */
41
+ (strings: TemplateStringsArray, ...placeholders: unknown[]): string;
42
+ /**
43
+ * Provide a custom id or context for the message, the latter used to disambiguate
44
+ * identical strings that have different meanings depending on usage.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * say({ context: 'direction' })`Right`
49
+ * say({ context: 'correctness' })`Right`
50
+ * ```
51
+ *
52
+ * @param descriptor Object containing optional `id` and `context` properties
53
+ * @remark This is a macro and must be used with the relevant saykit plugin
54
+ */
55
+ (descriptor: {
56
+ id?: string;
57
+ context?: string;
58
+ }): Say;
59
+ }
60
+ type ReadonlySay<Locale extends string = string, Loader extends Say.Loader<Locale> | undefined = Say.Loader<Locale> | undefined> = Omit<Say<Locale, Loader>, 'activate' | 'load' | 'assign'>;
61
+ declare class Say<Locale extends string = string, Loader extends Say.Loader<Locale> | undefined = Say.Loader<Locale> | undefined> {
62
+ #private;
63
+ constructor(options: Say.Options<Locale, Loader>);
64
+ /**
65
+ * The currently active locale.
66
+ *
67
+ * @throws If no locale is active
68
+ */
69
+ get locale(): Locale;
70
+ /**
71
+ * All available messages mapped by locale.
72
+ *
73
+ * @throws If no locale is active
74
+ * @throws If no messages are available for the active locale
75
+ */
76
+ get messages(): Say.Messages;
77
+ /**
78
+ * Loads messages for the given locales.
79
+ * If no locales are provided, all available locales are loaded.
80
+ * Requires a {@link Say.Loader} to be provided.
81
+ * If `loader` returns a promise, so will this method.
82
+ *
83
+ * @param locales Locales to load messages for, defaults to {@link Say.locales}
84
+ * @returns This
85
+ */
86
+ load(...locales: Locale[]): this | Promise<this>;
87
+ /**
88
+ * Manually bulk assign messages.
89
+ *
90
+ * @param messages Messages map to assign
91
+ */
92
+ assign(messages: Partial<Record<Locale, Say.Messages>>): this;
93
+ /**
94
+ * Manually assign messages to a locale.
95
+ *
96
+ * @param locale Locale to assign messages to
97
+ * @param messages Messages to assign
98
+ * @returns This
99
+ */
100
+ assign(locale: Locale, messages: Say.Messages): this;
101
+ /**
102
+ * Set the active locale.
103
+ *
104
+ * @param locale Locale to set
105
+ * @returns This
106
+ * @throws If locale is not available
107
+ */
108
+ activate(locale: Locale): this;
109
+ /**
110
+ * Creates a clone of the Say instance, with the same locales and messages.
111
+ *
112
+ * @returns A clone of the Say instance
113
+ */
114
+ clone(): this;
115
+ freeze(): ReadonlySay<Locale, Loader>;
116
+ /**
117
+ * Calls a defined callback function on each locale, passing the Say instance and locale to the callback.
118
+ *
119
+ * @param callback Callback function to call on each locale
120
+ */
121
+ map<T>(callbackfn: (value: [this, Locale], index: number, array: [this, Locale][]) => T): T[];
122
+ /**
123
+ * Calls the specified callback function for all the elements in an array, passing the Say instance and locale to the callback.
124
+ *
125
+ * @param callback Callback function to call for each element
126
+ * @param initial Initial value to use as the first argument to the first call of the callback
127
+ */
128
+ reduce<T>(callbackfn: (previousValue: T, currentValue: [this, Locale], currentIndex: number, array: [this, Locale][]) => T, initialValue: T): T;
129
+ [Symbol.iterator](): Generator<readonly [this, Locale], void, unknown>;
130
+ /**
131
+ * Matches the best locale from a list of guesses.
132
+ *
133
+ * @param guesses List of locale guesses
134
+ *
135
+ * @returns The best matching locale, or the first locale if no matches are found
136
+ */
137
+ match(guesses: string[]): Locale;
138
+ /**
139
+ * Get the translation for a descriptor.
140
+ *
141
+ * @param descriptor Descriptor to get the translation for
142
+ * @returns The translation string for the descriptor
143
+ * @throws If no locale is active
144
+ * @throws If no messages are available for the active locale
145
+ * @throws If descriptor id is not found
146
+ */
147
+ call(descriptor: {
148
+ id: string;
149
+ [match: string | number]: unknown;
150
+ }): string;
151
+ /**
152
+ * Define a pluralised message.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * say.plural(count, {
157
+ * one: 'You have 1 item',
158
+ * other: 'You have # items',
159
+ * })
160
+ * ```
161
+ *
162
+ * The `#` symbol inside options is replaced with the numeric value.
163
+ * @param _ Number to determine the plural form of
164
+ * @param options Pluralisation rules keyed by CLDR categories or specific numbers
165
+ * @returns The plural form of the number
166
+ * @remark This is a macro and must be used with the relevant saykit plugin
167
+ */
168
+ plural(_: number, options: Disallow<NumeralOptions, 'id' | 'context'>): string;
169
+ /**
170
+ * Define an ordinal message (e.g. "1st", "2nd", "3rd").
171
+ * The `#` symbol inside options is replaced with the numeric value.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * say.ordinal(position, {
176
+ * 1: '#st',
177
+ * 2: '#nd',
178
+ * 3: '#rd',
179
+ * other: '#th',
180
+ * })
181
+ * ```
182
+ *
183
+ * @param _ Number to determine the ordinal form of
184
+ * @param options Ordinal rules keyed by CLDR categories or specific numbers
185
+ * @returns The ordinal form of the number
186
+ * @remark This is a macro and must be used with the relevant saykit plugin
187
+ */
188
+ ordinal(_: number, options: Disallow<NumeralOptions, 'id' | 'context'>): string;
189
+ /**
190
+ * Define a select message, useful for handling gender, status, or other categories.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * say.select(gender, {
195
+ * male: 'He',
196
+ * female: 'She',
197
+ * other: 'They',
198
+ * })
199
+ * ```
200
+ *
201
+ * @param _ Selector value to determine which option is chosen
202
+ * @param options A mapping of possible selector values to message strings
203
+ * @returns The select form of the value
204
+ * @remark This is a macro and must be used with the relevant saykit plugin
205
+ */
206
+ select(_: string, options: Disallow<SelectOptions, 'id' | 'context'>): string;
207
+ }
208
+ //#endregion
209
+ export { Awaitable, Disallow, NumeralOptions, ReadonlySay, Say, SelectOptions, Tuple };
210
+ //# sourceMappingURL=runtime.d.mts.map
@@ -0,0 +1,266 @@
1
+ import { mf1ToMessage } from "@messageformat/icu-messageformat-1";
2
+
3
+ //#region \0@oxc-project+runtime@0.112.0/helpers/checkPrivateRedeclaration.js
4
+ function _checkPrivateRedeclaration(e, t) {
5
+ if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
6
+ }
7
+
8
+ //#endregion
9
+ //#region \0@oxc-project+runtime@0.112.0/helpers/classPrivateMethodInitSpec.js
10
+ function _classPrivateMethodInitSpec(e, a) {
11
+ _checkPrivateRedeclaration(e, a), a.add(e);
12
+ }
13
+
14
+ //#endregion
15
+ //#region \0@oxc-project+runtime@0.112.0/helpers/classPrivateFieldInitSpec.js
16
+ function _classPrivateFieldInitSpec(e, t, a) {
17
+ _checkPrivateRedeclaration(e, t), t.set(e, a);
18
+ }
19
+
20
+ //#endregion
21
+ //#region \0@oxc-project+runtime@0.112.0/helpers/assertClassBrand.js
22
+ function _assertClassBrand(e, t, n) {
23
+ if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
24
+ throw new TypeError("Private element is not present on this object");
25
+ }
26
+
27
+ //#endregion
28
+ //#region \0@oxc-project+runtime@0.112.0/helpers/classPrivateFieldSet2.js
29
+ function _classPrivateFieldSet2(s, a, r) {
30
+ return s.set(_assertClassBrand(s, a), r), r;
31
+ }
32
+
33
+ //#endregion
34
+ //#region \0@oxc-project+runtime@0.112.0/helpers/classPrivateFieldGet2.js
35
+ function _classPrivateFieldGet2(s, a) {
36
+ return s.get(_assertClassBrand(s, a));
37
+ }
38
+
39
+ //#endregion
40
+ //#region src/runtime.ts
41
+ let _Symbol$iterator, _Symbol$for;
42
+ var _locales = /* @__PURE__ */ new WeakMap();
43
+ var _loader = /* @__PURE__ */ new WeakMap();
44
+ var _messages = /* @__PURE__ */ new WeakMap();
45
+ var _formats = /* @__PURE__ */ new WeakMap();
46
+ var _active = /* @__PURE__ */ new WeakMap();
47
+ var _Say_brand = /* @__PURE__ */ new WeakSet();
48
+ _Symbol$iterator = Symbol.iterator;
49
+ _Symbol$for = Symbol.for("nodejs.util.inspect.custom");
50
+ var Say = class Say {
51
+ constructor(options) {
52
+ _classPrivateMethodInitSpec(this, _Say_brand);
53
+ _classPrivateFieldInitSpec(this, _locales, void 0);
54
+ _classPrivateFieldInitSpec(this, _loader, void 0);
55
+ _classPrivateFieldInitSpec(this, _messages, void 0);
56
+ _classPrivateFieldInitSpec(this, _formats, void 0);
57
+ _classPrivateFieldInitSpec(this, _active, void 0);
58
+ _classPrivateFieldSet2(_locales, this, options.locales);
59
+ _classPrivateFieldSet2(_loader, this, options.loader);
60
+ _classPrivateFieldSet2(_messages, this, /* @__PURE__ */ new Map());
61
+ _classPrivateFieldSet2(_formats, this, /* @__PURE__ */ new Map());
62
+ if (options.messages) this.assign(options.messages);
63
+ }
64
+ /**
65
+ * The currently active locale.
66
+ *
67
+ * @throws If no locale is active
68
+ */
69
+ get locale() {
70
+ if (!_classPrivateFieldGet2(_active, this)) throw new Error("No active locale");
71
+ return _classPrivateFieldGet2(_active, this);
72
+ }
73
+ /**
74
+ * All available messages mapped by locale.
75
+ *
76
+ * @throws If no locale is active
77
+ * @throws If no messages are available for the active locale
78
+ */
79
+ get messages() {
80
+ if (!_classPrivateFieldGet2(_messages, this).has(this.locale)) throw new Error("No messages loaded for locale");
81
+ return _classPrivateFieldGet2(_messages, this).get(this.locale);
82
+ }
83
+ /**
84
+ * Loads messages for the given locales.
85
+ * If no locales are provided, all available locales are loaded.
86
+ * Requires a {@link Say.Loader} to be provided.
87
+ * If `loader` returns a promise, so will this method.
88
+ *
89
+ * @param locales Locales to load messages for, defaults to {@link Say.locales}
90
+ * @returns This
91
+ */
92
+ load(...locales) {
93
+ if (Object.isFrozen(this)) throw new Error("Cannot load messages on a frozen Say");
94
+ if (locales.length === 0) locales = _classPrivateFieldGet2(_locales, this);
95
+ const tasks = [];
96
+ for (const locale of locales) {
97
+ if (_classPrivateFieldGet2(_messages, this).has(locale)) continue;
98
+ if (!_classPrivateFieldGet2(_loader, this)) throw new Error("No loader provided, cannot load messages");
99
+ const result = _classPrivateFieldGet2(_loader, this).call(this, locale);
100
+ if (result instanceof Promise) {
101
+ const task = result.then((m) => this.assign(locale, m));
102
+ tasks.push(task);
103
+ } else this.assign(locale, result);
104
+ }
105
+ return tasks.length > 0 ? Promise.all(tasks).then(() => this) : this;
106
+ }
107
+ assign(localeOrMessages, maybeMessages) {
108
+ if (Object.isFrozen(this)) throw new Error("Cannot assign messages on a frozen Say");
109
+ if (typeof localeOrMessages === "string") _classPrivateFieldGet2(_messages, this).set(localeOrMessages, maybeMessages);
110
+ else for (const locale in localeOrMessages) _classPrivateFieldGet2(_messages, this).set(locale, localeOrMessages[locale]);
111
+ return this;
112
+ }
113
+ /**
114
+ * Set the active locale.
115
+ *
116
+ * @param locale Locale to set
117
+ * @returns This
118
+ * @throws If locale is not available
119
+ */
120
+ activate(locale) {
121
+ if (Object.isFrozen(this)) throw new Error("Cannot activate locale on a frozen Say");
122
+ if (!_classPrivateFieldGet2(_messages, this).has(locale)) throw new Error("No messages loaded for locale");
123
+ _classPrivateFieldSet2(_active, this, locale);
124
+ return this;
125
+ }
126
+ /**
127
+ * Creates a clone of the Say instance, with the same locales and messages.
128
+ *
129
+ * @returns A clone of the Say instance
130
+ */
131
+ clone() {
132
+ return new Say({
133
+ locales: _classPrivateFieldGet2(_locales, this),
134
+ messages: Object.fromEntries(_classPrivateFieldGet2(_messages, this)),
135
+ loader: _classPrivateFieldGet2(_loader, this)
136
+ });
137
+ }
138
+ freeze() {
139
+ return Object.freeze(this);
140
+ }
141
+ /**
142
+ * Calls a defined callback function on each locale, passing the Say instance and locale to the callback.
143
+ *
144
+ * @param callback Callback function to call on each locale
145
+ */
146
+ map(callbackfn) {
147
+ return _classPrivateFieldGet2(_locales, this).map((l) => [this.clone().activate(l), l]).map(callbackfn);
148
+ }
149
+ /**
150
+ * Calls the specified callback function for all the elements in an array, passing the Say instance and locale to the callback.
151
+ *
152
+ * @param callback Callback function to call for each element
153
+ * @param initial Initial value to use as the first argument to the first call of the callback
154
+ */
155
+ reduce(callbackfn, initialValue) {
156
+ return _classPrivateFieldGet2(_locales, this).map((l) => [this.clone().activate(l), l]).reduce(callbackfn, initialValue);
157
+ }
158
+ *[_Symbol$iterator]() {
159
+ for (const l of _classPrivateFieldGet2(_locales, this)) yield [this.clone().activate(l), l];
160
+ }
161
+ /**
162
+ * Matches the best locale from a list of guesses.
163
+ *
164
+ * @param guesses List of locale guesses
165
+ *
166
+ * @returns The best matching locale, or the first locale if no matches are found
167
+ */
168
+ match(guesses) {
169
+ for (const guess of guesses) if (_classPrivateFieldGet2(_locales, this).includes(guess)) return guess;
170
+ for (const guess of guesses) {
171
+ const prefix = guess.split("-")[0];
172
+ const match = _classPrivateFieldGet2(_locales, this).find((l) => l.startsWith(prefix));
173
+ if (match) return match;
174
+ }
175
+ return _classPrivateFieldGet2(_locales, this)[0];
176
+ }
177
+ /**
178
+ * Get the translation for a descriptor.
179
+ *
180
+ * @param descriptor Descriptor to get the translation for
181
+ * @returns The translation string for the descriptor
182
+ * @throws If no locale is active
183
+ * @throws If no messages are available for the active locale
184
+ * @throws If descriptor id is not found
185
+ */
186
+ call(descriptor) {
187
+ return _assertClassBrand(_Say_brand, this, _call).call(this, this.locale, this.messages, descriptor);
188
+ }
189
+ [_Symbol$for](_depth, context, inspect) {
190
+ if (_classPrivateFieldGet2(_active, this)) return `${this.constructor.name}<${inspect(_classPrivateFieldGet2(_active, this), context)}> {}`;
191
+ else return `${this.constructor.name} {}`;
192
+ }
193
+ /**
194
+ * Define a pluralised message.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * say.plural(count, {
199
+ * one: 'You have 1 item',
200
+ * other: 'You have # items',
201
+ * })
202
+ * ```
203
+ *
204
+ * The `#` symbol inside options is replaced with the numeric value.
205
+ * @param _ Number to determine the plural form of
206
+ * @param options Pluralisation rules keyed by CLDR categories or specific numbers
207
+ * @returns The plural form of the number
208
+ * @remark This is a macro and must be used with the relevant saykit plugin
209
+ */
210
+ plural(_, options) {
211
+ throw new Error("'Say#plural' is a macro and must be used with the relevant saykit plugin");
212
+ }
213
+ /**
214
+ * Define an ordinal message (e.g. "1st", "2nd", "3rd").
215
+ * The `#` symbol inside options is replaced with the numeric value.
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * say.ordinal(position, {
220
+ * 1: '#st',
221
+ * 2: '#nd',
222
+ * 3: '#rd',
223
+ * other: '#th',
224
+ * })
225
+ * ```
226
+ *
227
+ * @param _ Number to determine the ordinal form of
228
+ * @param options Ordinal rules keyed by CLDR categories or specific numbers
229
+ * @returns The ordinal form of the number
230
+ * @remark This is a macro and must be used with the relevant saykit plugin
231
+ */
232
+ ordinal(_, options) {
233
+ throw new Error("'Say#ordinal' is a macro and must be used with the relevant saykit plugin");
234
+ }
235
+ /**
236
+ * Define a select message, useful for handling gender, status, or other categories.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * say.select(gender, {
241
+ * male: 'He',
242
+ * female: 'She',
243
+ * other: 'They',
244
+ * })
245
+ * ```
246
+ *
247
+ * @param _ Selector value to determine which option is chosen
248
+ * @param options A mapping of possible selector values to message strings
249
+ * @returns The select form of the value
250
+ * @remark This is a macro and must be used with the relevant saykit plugin
251
+ */
252
+ select(_, options) {
253
+ throw new Error("'Say#select' is a macro and must be used with the relevant saykit plugin");
254
+ }
255
+ };
256
+ function _call(locale, messages, descriptor) {
257
+ const message = messages[descriptor.id];
258
+ if (typeof message !== "string") throw new Error(`Message for ${descriptor.id} is not a string`);
259
+ const key = `${locale}:${descriptor.id}`;
260
+ const format = _classPrivateFieldGet2(_formats, this).get(key) ?? _classPrivateFieldGet2(_formats, this).set(key, mf1ToMessage(locale, message)).get(key);
261
+ return String(format.format(descriptor));
262
+ }
263
+
264
+ //#endregion
265
+ export { Say };
266
+ //# sourceMappingURL=runtime.mjs.map
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "saykit",
3
+ "version": "0.0.0-beta-20260309151609",
4
+ "description": "Type-safe i18n library with compile-time macro transforms",
5
+ "keywords": [
6
+ "i18n",
7
+ "icu",
8
+ "internationalization",
9
+ "localization",
10
+ "macros",
11
+ "typescript"
12
+ ],
13
+ "homepage": "https://github.com/k0d13/saykit#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/k0d13/saykit/issues"
16
+ },
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/k0d13/saykit.git",
21
+ "directory": "packages/integration"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "!dist/**/*.map"
26
+ ],
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/runtime.d.mts",
31
+ "default": "./dist/runtime.mjs"
32
+ }
33
+ },
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "provenance": true
37
+ },
38
+ "dependencies": {
39
+ "@messageformat/icu-messageformat-1": "^0.12.0"
40
+ },
41
+ "scripts": {
42
+ "check": "tsc --noEmit",
43
+ "build": "tsdown"
44
+ }
45
+ }