intor-translator 1.4.6 → 1.4.7

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/index.cjs CHANGED
@@ -3,6 +3,15 @@
3
3
  var rura = require('rura');
4
4
 
5
5
  // src/translators/core-translator/core-translator.ts
6
+ function runTranslate(options) {
7
+ const context = {
8
+ ...options,
9
+ config: options.translateConfig,
10
+ candidateLocales: [],
11
+ meta: {}
12
+ };
13
+ return rura.rura.run(context, options.hooks);
14
+ }
6
15
 
7
16
  // src/shared/utils/find-message-in-locales.ts
8
17
  var findMessageInLocales = ({
@@ -231,15 +240,6 @@ var hasKey = ({
231
240
  const message = findMessageInLocales({ messages, candidateLocales, key });
232
241
  return message !== void 0;
233
242
  };
234
- function runTranslate(options) {
235
- const context = {
236
- ...options,
237
- config: options.translateConfig,
238
- candidateLocales: [],
239
- meta: {}
240
- };
241
- return rura.rura.run(context, options.hooks);
242
- }
243
243
 
244
244
  // src/translators/methods/translate.ts
245
245
  function translate(options) {
@@ -295,7 +295,7 @@ var CoreTranslator = class extends BaseTranslator {
295
295
  });
296
296
  };
297
297
  /** Get the translated message for a key, with optional replacements. */
298
- t = (key, replacements) => {
298
+ t = (key, ...replacementArgs) => {
299
299
  return translate({
300
300
  hooks: this.hooks,
301
301
  messages: this._messages,
@@ -303,7 +303,7 @@ var CoreTranslator = class extends BaseTranslator {
303
303
  isLoading: this._isLoading,
304
304
  translateConfig: this.translateConfig,
305
305
  key,
306
- replacements
306
+ replacements: replacementArgs[0]
307
307
  });
308
308
  };
309
309
  };
@@ -332,7 +332,7 @@ var ScopeTranslator = class extends CoreTranslator {
332
332
  targetLocale
333
333
  });
334
334
  },
335
- t: (key, replacements) => {
335
+ t: (key, ...args) => {
336
336
  const fullKey = getFullKey(preKey, key);
337
337
  return translate({
338
338
  hooks: this.hooks,
@@ -341,7 +341,7 @@ var ScopeTranslator = class extends CoreTranslator {
341
341
  isLoading: this._isLoading,
342
342
  translateConfig: this.translateConfig,
343
343
  key: fullKey,
344
- replacements
344
+ replacements: args[0]
345
345
  });
346
346
  }
347
347
  };
package/dist/index.d.cts CHANGED
@@ -42,30 +42,6 @@ type MessageLeaf = MessagePrimitive | MessageArray;
42
42
  * ```
43
43
  */
44
44
  type LocaleMessages = Record<string, MessageObject>;
45
- /**
46
- * Merges messages from all locales into a single unified structure,
47
- * or extracts messages for a specific locale if `L` is provided.
48
- *
49
- * @example
50
- * ```ts
51
- * const messages = {
52
- * en: { greeting: { morning: "morning" } },
53
- * zh: { greeting: { evening: "晚上好" } },
54
- * };
55
- *
56
- * // 1. Union of all locales
57
- * UnionLocaleMessages<typeof messages>;
58
- * // → { greeting: { morning: string; }; } | { greeting: { evening: string; }; }
59
- *
60
- * // 2. Messages for a specified locale
61
- * UnionLocaleMessages<typeof messages, "en">; // → { greeting: { morning: string; }; }
62
- * UnionLocaleMessages<typeof messages, "zh">; // → { greeting: { evening: string; }; }
63
- *
64
- * // 3. Fallback if M is not LocaleMessages
65
- * UnionLocaleMessages // → unknown
66
- * ```
67
- */
68
- type LocalizedMessagesUnion<M = unknown, L extends keyof M | "union" = "union"> = M extends LocaleMessages ? L extends "union" ? M[Locale<M>] : M[L & keyof M] : unknown;
69
45
 
70
46
  /**
71
47
  * Extracts locale keys only when M is a valid messages object.
@@ -106,25 +82,6 @@ type Locale<M = unknown> = M extends LocaleMessages ? keyof M & string : string;
106
82
  */
107
83
  type FallbackLocalesMap<L extends string = string> = Partial<Record<L, L[]>>;
108
84
 
109
- /**
110
- * Represents a recursive replacement object used for interpolating values in message templates.
111
- *
112
- * - Each key can be any value (`string`, `number`, `boolean`, `Date`, `function`, nested object, etc.).
113
- * - Supports nested structures for complex interpolation.
114
- * - Can be used directly with `intl-messageformat` or custom format handlers.
115
- *
116
- * @example
117
- * const replacements: Replacement = {
118
- * name: "Alice",
119
- * count: 5,
120
- * nested: {
121
- * score: 100
122
- * },
123
- * formatter: (value: unknown) => `<b>${value}</b>`
124
- * };
125
- */
126
- type Replacement = Record<string, unknown>;
127
-
128
85
  /**
129
86
  * Default maximum recursive depth for nested key type computations,
130
87
  * balancing type safety and compiler performance.
@@ -164,6 +121,19 @@ type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never
164
121
  * ```
165
122
  */
166
123
  type LeafValue<M, K extends string> = K extends `${infer Seg}.${infer Rest}` ? Seg extends keyof M ? LeafValue<M[Seg], Rest> : never : K extends keyof M ? M[K] : never;
124
+ /**
125
+ * Resolves the type located at a dot-separated path.
126
+ *
127
+ * The resolved type may be a subtree or a leaf value.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const messages = { a: { b: { c: "1" }, z: "2" } };
132
+ * AtPath<typeof messages, "a">; // → { b: { c: string }; z: string };
133
+ * AtPath<typeof messages, "a.b">; // → { c: string };
134
+ * ```
135
+ */
136
+ type AtPath<MessageSchema, PK extends string> = PK extends `${infer Head}.${infer Tail}` ? Head extends keyof MessageSchema ? AtPath<MessageSchema[Head], Tail> : never : PK extends keyof MessageSchema ? MessageSchema[PK] : never;
167
137
 
168
138
  /**
169
139
  * Extracts all **node keys** from the messages
@@ -187,7 +157,7 @@ type LeafValue<M, K extends string> = K extends `${infer Seg}.${infer Rest}` ? S
187
157
  * LocalizedNodeKeys // → string
188
158
  * ```
189
159
  */
190
- type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? L extends "union" ? NodeKeys<M[keyof M], D> : NodeKeys<M[Extract<L, keyof M>], D> : string;
160
+ type LocalizedNodeKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? NodeKeys<M[keyof M], D> : string;
191
161
  /**
192
162
  * Extracts all **leaf keys** from the messages
193
163
  * of a specified locale (or union of locales).
@@ -210,32 +180,15 @@ type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D ext
210
180
  * LocalizedLeafKeys // → string
211
181
  * ```
212
182
  */
213
- type LocalizedLeafKeys<M = unknown, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? L extends "union" ? LeafKeys<M[keyof M], D> : LeafKeys<M[Extract<L, keyof M>], D> : string;
183
+ type LocalizedLeafKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? LeafKeys<M[keyof M], D> : string;
214
184
  /**
215
185
  * Resolves the value type of a **localized leaf key**
216
186
  * from the messages of a specified locale (or union of locales).
217
187
  *
218
188
  * - Fallback to `MessageValue` if M is not LocaleMessages
219
189
  */
220
- type LocalizedLeafValue<M = unknown, K extends string = string, L extends keyof M | "union" = "union"> = M extends LocaleMessages ? L extends "union" ? LeafValue<M[keyof M], K> : LeafValue<M[Extract<L, keyof M>], K> : MessageValue;
190
+ type LocalizedLeafValue<M = unknown, K extends string = string> = M extends LocaleMessages ? LeafValue<M[keyof M], K> : MessageValue;
221
191
 
222
- /**
223
- * Resolves the type at a dot-separated key in a nested object.
224
- *
225
- * @example
226
- * ```ts
227
- * const structure = {
228
- * a: {
229
- * b: { c: "hello" },
230
- * z: "world",
231
- * },
232
- * };
233
- *
234
- * MessagesAtPreKey<typeof structure, "a"> // → { b: { c: string; }; z: string;}
235
- * MessagesAtPreKey<typeof structure, "a.b"> // → { c: string; };
236
- * ```
237
- */
238
- type MessagesAtPreKey<T, PK extends string> = PK extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? MessagesAtPreKey<T[Head], Tail> : never : PK extends keyof T ? T[PK] : never;
239
192
  /**
240
193
  * Extracts all **leaf keys** under a scoped path (`PK`) from the messages
241
194
  * of a specified locale (`L`) (or union of locales).
@@ -252,7 +205,7 @@ type MessagesAtPreKey<T, PK extends string> = PK extends `${infer Head}.${infer
252
205
  * ScopedLeafKeys<typeof messages, "a", "zh">; // → "b"
253
206
  * ```
254
207
  */
255
- type ScopedLeafKeys<M, PK extends string, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? LocalizedMessagesUnion<M, L> extends infer Messages ? Messages extends MessageValue ? MessagesAtPreKey<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafKeys<Scoped, D> : never : never : never : never : string;
208
+ type ScopedLeafKeys<M, PK extends string, D extends number = DefaultDepth> = M extends LocaleMessages ? M[Locale<M>] extends infer Messages ? Messages extends MessageValue ? AtPath<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafKeys<Scoped, D> : never : never : never : never : string;
256
209
  /**
257
210
  * Resolves the value type of a scoped leaf key (`K`)
258
211
  * under a prefix path (`PK`) from localized messages.
@@ -267,7 +220,108 @@ type ScopedLeafKeys<M, PK extends string, L extends keyof M | "union" = "union",
267
220
  * ScopedLeafValue<typeof messages, "a.b", "c">; // string
268
221
  * ```
269
222
  */
270
- type ScopedLeafValue<M, PK extends string, K extends string, L extends keyof M | "union" = "union"> = M extends LocaleMessages ? LocalizedMessagesUnion<M, L> extends infer Messages ? Messages extends MessageValue ? MessagesAtPreKey<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafValue<Scoped, K> : never : never : never : never : MessageValue;
223
+ type ScopedLeafValue<M, PK extends string, K extends string> = M extends LocaleMessages ? M[Locale<M>] extends infer Messages ? Messages extends MessageValue ? AtPath<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafValue<Scoped, K> : never : never : never : never : MessageValue;
224
+
225
+ /**
226
+ * Represents a replacement map used for interpolating values
227
+ * in message templates.
228
+ *
229
+ * Replacement values are treated as plain data and interpreted
230
+ * by the message formatter at runtime.
231
+ *
232
+ * @example
233
+ * const replacements: Replacement = {
234
+ * name: "Alice",
235
+ * count: 5,
236
+ * nested: {
237
+ * score: 100,
238
+ * },
239
+ * };
240
+ */
241
+ type Replacement = Record<string, unknown>;
242
+ /**
243
+ * Resolves the expected replacement type for a localized message key.
244
+ *
245
+ * Uses the canonical (default-locale) replacement schema when available,
246
+ * otherwise falls back to `Replacement`.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * type RMap = {
251
+ * "{locale}": {
252
+ * welcome: { name: MessageValue };
253
+ * total: { count: MessageValue };
254
+ * };
255
+ * };
256
+ *
257
+ * type WelcomeReplacement = LocalizedReplacement<RMap, "welcome">;
258
+ * // => { name: MessageValue }
259
+ *
260
+ * type UnknownReplacement = LocalizedReplacement<RMap, "unknown">;
261
+ * // => Replacement
262
+ * ```
263
+ */
264
+ type LocalizedReplacement<ReplacementSchema, K extends string> = ReplacementSchema extends {
265
+ "{locale}": infer LM;
266
+ } ? AtPath<LM, K> extends infer R ? R extends MessageObject ? R : Replacement : Replacement : Replacement;
267
+
268
+ /**
269
+ * Options for initializing a translator
270
+ *
271
+ * @template M - type of messages object
272
+ */
273
+ interface BaseTranslatorOptions<M = unknown> {
274
+ /**
275
+ * Messages object for translations.
276
+ * - Use `LocaleMessages` type to enable key inference for `hasKey` and `t`.
277
+ */
278
+ messages?: Readonly<M>;
279
+ /**
280
+ * Current locale key.
281
+ * - If `messages` is typed as `LocaleMessages`, this can be inferred automatically.
282
+ */
283
+ locale: Locale<M>;
284
+ /**
285
+ * Indicates whether the translator is in a loading state.
286
+ */
287
+ isLoading?: boolean;
288
+ }
289
+
290
+ /**
291
+ * The minimal, shared foundation for all translators.
292
+ *
293
+ * @template M - Shape of the messages object.
294
+ */
295
+ declare class BaseTranslator<M extends LocaleMessages | unknown = unknown> {
296
+ /** Current messages for translation */
297
+ protected _messages: Readonly<M>;
298
+ /** Current active locale */
299
+ protected _locale: Locale<M>;
300
+ /** Current loading state */
301
+ protected _isLoading: boolean;
302
+ constructor(options: BaseTranslatorOptions<M>);
303
+ /** Get messages. */
304
+ get messages(): M;
305
+ /** Get the current active locale. */
306
+ get locale(): Locale<M>;
307
+ /** Get the current loading state. */
308
+ get isLoading(): boolean;
309
+ /**
310
+ * Replace messages with new ones.
311
+ *
312
+ * - Note: This allows runtime setting of messages even if `M` is inferred as `never`.
313
+ * The type cast bypasses TypeScript restrictions on dynamic messages.
314
+ */
315
+ setMessages<N extends LocaleMessages>(messages: N): void;
316
+ /**
317
+ * Set the active locale.
318
+ *
319
+ * - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
320
+ */
321
+ setLocale(newLocale: Locale<M>): void;
322
+ /** Set the loading state. */
323
+ setLoading(state: boolean): void;
324
+ }
271
325
 
272
326
  /**
273
327
  * Configuration options for translation behavior.
@@ -343,64 +397,6 @@ interface TranslateContext {
343
397
  */
344
398
  type TranslateHook = RuraHook<TranslateContext, MessageValue>;
345
399
 
346
- /**
347
- * Options for initializing a translator
348
- *
349
- * @template M - type of messages object
350
- */
351
- interface BaseTranslatorOptions<M = unknown> {
352
- /**
353
- * Messages object for translations.
354
- * - Use `LocaleMessages` type to enable key inference for `hasKey` and `t`.
355
- */
356
- messages?: Readonly<M>;
357
- /**
358
- * Current locale key.
359
- * - If `messages` is typed as `LocaleMessages`, this can be inferred automatically.
360
- */
361
- locale: Locale<M>;
362
- /**
363
- * Indicates whether the translator is in a loading state.
364
- */
365
- isLoading?: boolean;
366
- }
367
-
368
- /**
369
- * The minimal, shared foundation for all translators.
370
- *
371
- * @template M - Shape of the messages object.
372
- */
373
- declare class BaseTranslator<M extends LocaleMessages | unknown = unknown> {
374
- /** Current messages for translation */
375
- protected _messages: Readonly<M>;
376
- /** Current active locale */
377
- protected _locale: Locale<M>;
378
- /** Current loading state */
379
- protected _isLoading: boolean;
380
- constructor(options: BaseTranslatorOptions<M>);
381
- /** Get messages. */
382
- get messages(): M;
383
- /** Get the current active locale. */
384
- get locale(): Locale<M>;
385
- /** Get the current loading state. */
386
- get isLoading(): boolean;
387
- /**
388
- * Replace messages with new ones.
389
- *
390
- * - Note: This allows runtime setting of messages even if `M` is inferred as `never`.
391
- * The type cast bypasses TypeScript restrictions on dynamic messages.
392
- */
393
- setMessages<N extends LocaleMessages>(messages: N): void;
394
- /**
395
- * Set the active locale.
396
- *
397
- * - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
398
- */
399
- setLocale(newLocale: Locale<M>): void;
400
- /** Set the loading state. */
401
- setLoading(state: boolean): void;
402
- }
403
-
404
400
  interface CoreTranslatorOptions<M> extends BaseTranslatorOptions<M>, TranslateConfig<M> {
405
401
  /** Optional plugins or raw hooks to extend the translation pipeline */
406
402
  plugins?: Array<TranslatorPlugin | TranslateHook>;
@@ -417,9 +413,8 @@ interface TranslatorPlugin {
417
413
  * using the pipeline engine and built-in hooks.
418
414
  *
419
415
  * @template M - Shape of the messages object.
420
- * @template L - Locale selection strategy ("union" or specific locale keys).
421
416
  */
422
- declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends BaseTranslator<M> {
417
+ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends BaseTranslator<M> {
423
418
  /** User-provided options including messages, locale, and config. */
424
419
  protected translateConfig: TranslateConfig<M>;
425
420
  /** Active pipeline hooks applied during translation. */
@@ -432,21 +427,23 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L ext
432
427
  /** Outputs a debug overview of the active pipeline. */
433
428
  debugHooks(): void;
434
429
  /** Check if a key exists in the specified locale or current locale. */
435
- hasKey: <K extends LocalizedLeafKeys<M, L>>(key: K, targetLocale?: Locale<M>) => boolean;
430
+ hasKey: <K extends LocalizedLeafKeys<M>>(key: K, targetLocale?: Locale<M>) => boolean;
436
431
  /** Get the translated message for a key, with optional replacements. */
437
- t: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K, L>;
432
+ t: <K extends LocalizedLeafKeys<M> = LocalizedLeafKeys<M>>(key: K, ...replacementArgs: [LocalizedReplacement<ReplacementSchema, K>?]) => LocalizedLeafValue<M, K>;
438
433
  }
439
434
 
440
435
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
441
- type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union", PK extends string | undefined = undefined, K extends string = PK extends string ? ScopedLeafKeys<M, PK, L> : LocalizedLeafKeys<M, L>> = {
436
+ type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown, PK extends string | undefined = undefined, K extends string = PK extends string ? ScopedLeafKeys<M, PK> : LocalizedLeafKeys<M>> = {
442
437
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
443
- t: <Key extends K>(key?: Key, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, Key, L> : LocalizedLeafValue<M, Key, L>;
438
+ t: <Key extends K>(key?: Key, ...replacementArgs: [
439
+ LocalizedReplacement<ReplacementSchema, PK extends string ? `${PK}.${Key & string}` : Key>?
440
+ ]) => PK extends string ? ScopedLeafValue<M, PK, Key> : LocalizedLeafValue<M, Key>;
444
441
  };
445
442
 
446
- declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
443
+ declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends CoreTranslator<M, ReplacementSchema> {
447
444
  constructor(options: ScopeTranslatorOptions<M>);
448
445
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
449
- scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, PK> : ScopeTranslatorMethods<M, L>;
446
+ scoped<PK extends LocalizedNodeKeys<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
450
447
  }
451
448
 
452
449
  /** Semantic tag attributes map. */
@@ -521,4 +518,4 @@ interface Renderer<Output> {
521
518
  */
522
519
  declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
523
520
 
524
- export { type ASTNode, type Attributes, type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LeafValue, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedLeafValue, type LocalizedMessagesUnion, type LocalizedNodeKeys, type MessageObject, type MessageValue, type MissingHandler, type NodeKeys, type Renderer, type Replacement, type ScopedLeafKeys, type ScopedLeafValue, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, parseRichMessage, renderRichMessage };
521
+ export { type ASTNode, type AtPath, type Attributes, type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LeafValue, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedLeafValue, type LocalizedNodeKeys, type LocalizedReplacement, type MessageObject, type MessageValue, type MissingHandler, type NodeKeys, type Renderer, type Replacement, type ScopedLeafKeys, type ScopedLeafValue, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, parseRichMessage, renderRichMessage };
package/dist/index.d.ts CHANGED
@@ -42,30 +42,6 @@ type MessageLeaf = MessagePrimitive | MessageArray;
42
42
  * ```
43
43
  */
44
44
  type LocaleMessages = Record<string, MessageObject>;
45
- /**
46
- * Merges messages from all locales into a single unified structure,
47
- * or extracts messages for a specific locale if `L` is provided.
48
- *
49
- * @example
50
- * ```ts
51
- * const messages = {
52
- * en: { greeting: { morning: "morning" } },
53
- * zh: { greeting: { evening: "晚上好" } },
54
- * };
55
- *
56
- * // 1. Union of all locales
57
- * UnionLocaleMessages<typeof messages>;
58
- * // → { greeting: { morning: string; }; } | { greeting: { evening: string; }; }
59
- *
60
- * // 2. Messages for a specified locale
61
- * UnionLocaleMessages<typeof messages, "en">; // → { greeting: { morning: string; }; }
62
- * UnionLocaleMessages<typeof messages, "zh">; // → { greeting: { evening: string; }; }
63
- *
64
- * // 3. Fallback if M is not LocaleMessages
65
- * UnionLocaleMessages // → unknown
66
- * ```
67
- */
68
- type LocalizedMessagesUnion<M = unknown, L extends keyof M | "union" = "union"> = M extends LocaleMessages ? L extends "union" ? M[Locale<M>] : M[L & keyof M] : unknown;
69
45
 
70
46
  /**
71
47
  * Extracts locale keys only when M is a valid messages object.
@@ -106,25 +82,6 @@ type Locale<M = unknown> = M extends LocaleMessages ? keyof M & string : string;
106
82
  */
107
83
  type FallbackLocalesMap<L extends string = string> = Partial<Record<L, L[]>>;
108
84
 
109
- /**
110
- * Represents a recursive replacement object used for interpolating values in message templates.
111
- *
112
- * - Each key can be any value (`string`, `number`, `boolean`, `Date`, `function`, nested object, etc.).
113
- * - Supports nested structures for complex interpolation.
114
- * - Can be used directly with `intl-messageformat` or custom format handlers.
115
- *
116
- * @example
117
- * const replacements: Replacement = {
118
- * name: "Alice",
119
- * count: 5,
120
- * nested: {
121
- * score: 100
122
- * },
123
- * formatter: (value: unknown) => `<b>${value}</b>`
124
- * };
125
- */
126
- type Replacement = Record<string, unknown>;
127
-
128
85
  /**
129
86
  * Default maximum recursive depth for nested key type computations,
130
87
  * balancing type safety and compiler performance.
@@ -164,6 +121,19 @@ type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never
164
121
  * ```
165
122
  */
166
123
  type LeafValue<M, K extends string> = K extends `${infer Seg}.${infer Rest}` ? Seg extends keyof M ? LeafValue<M[Seg], Rest> : never : K extends keyof M ? M[K] : never;
124
+ /**
125
+ * Resolves the type located at a dot-separated path.
126
+ *
127
+ * The resolved type may be a subtree or a leaf value.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const messages = { a: { b: { c: "1" }, z: "2" } };
132
+ * AtPath<typeof messages, "a">; // → { b: { c: string }; z: string };
133
+ * AtPath<typeof messages, "a.b">; // → { c: string };
134
+ * ```
135
+ */
136
+ type AtPath<MessageSchema, PK extends string> = PK extends `${infer Head}.${infer Tail}` ? Head extends keyof MessageSchema ? AtPath<MessageSchema[Head], Tail> : never : PK extends keyof MessageSchema ? MessageSchema[PK] : never;
167
137
 
168
138
  /**
169
139
  * Extracts all **node keys** from the messages
@@ -187,7 +157,7 @@ type LeafValue<M, K extends string> = K extends `${infer Seg}.${infer Rest}` ? S
187
157
  * LocalizedNodeKeys // → string
188
158
  * ```
189
159
  */
190
- type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? L extends "union" ? NodeKeys<M[keyof M], D> : NodeKeys<M[Extract<L, keyof M>], D> : string;
160
+ type LocalizedNodeKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? NodeKeys<M[keyof M], D> : string;
191
161
  /**
192
162
  * Extracts all **leaf keys** from the messages
193
163
  * of a specified locale (or union of locales).
@@ -210,32 +180,15 @@ type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D ext
210
180
  * LocalizedLeafKeys // → string
211
181
  * ```
212
182
  */
213
- type LocalizedLeafKeys<M = unknown, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? L extends "union" ? LeafKeys<M[keyof M], D> : LeafKeys<M[Extract<L, keyof M>], D> : string;
183
+ type LocalizedLeafKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? LeafKeys<M[keyof M], D> : string;
214
184
  /**
215
185
  * Resolves the value type of a **localized leaf key**
216
186
  * from the messages of a specified locale (or union of locales).
217
187
  *
218
188
  * - Fallback to `MessageValue` if M is not LocaleMessages
219
189
  */
220
- type LocalizedLeafValue<M = unknown, K extends string = string, L extends keyof M | "union" = "union"> = M extends LocaleMessages ? L extends "union" ? LeafValue<M[keyof M], K> : LeafValue<M[Extract<L, keyof M>], K> : MessageValue;
190
+ type LocalizedLeafValue<M = unknown, K extends string = string> = M extends LocaleMessages ? LeafValue<M[keyof M], K> : MessageValue;
221
191
 
222
- /**
223
- * Resolves the type at a dot-separated key in a nested object.
224
- *
225
- * @example
226
- * ```ts
227
- * const structure = {
228
- * a: {
229
- * b: { c: "hello" },
230
- * z: "world",
231
- * },
232
- * };
233
- *
234
- * MessagesAtPreKey<typeof structure, "a"> // → { b: { c: string; }; z: string;}
235
- * MessagesAtPreKey<typeof structure, "a.b"> // → { c: string; };
236
- * ```
237
- */
238
- type MessagesAtPreKey<T, PK extends string> = PK extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? MessagesAtPreKey<T[Head], Tail> : never : PK extends keyof T ? T[PK] : never;
239
192
  /**
240
193
  * Extracts all **leaf keys** under a scoped path (`PK`) from the messages
241
194
  * of a specified locale (`L`) (or union of locales).
@@ -252,7 +205,7 @@ type MessagesAtPreKey<T, PK extends string> = PK extends `${infer Head}.${infer
252
205
  * ScopedLeafKeys<typeof messages, "a", "zh">; // → "b"
253
206
  * ```
254
207
  */
255
- type ScopedLeafKeys<M, PK extends string, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? LocalizedMessagesUnion<M, L> extends infer Messages ? Messages extends MessageValue ? MessagesAtPreKey<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafKeys<Scoped, D> : never : never : never : never : string;
208
+ type ScopedLeafKeys<M, PK extends string, D extends number = DefaultDepth> = M extends LocaleMessages ? M[Locale<M>] extends infer Messages ? Messages extends MessageValue ? AtPath<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafKeys<Scoped, D> : never : never : never : never : string;
256
209
  /**
257
210
  * Resolves the value type of a scoped leaf key (`K`)
258
211
  * under a prefix path (`PK`) from localized messages.
@@ -267,7 +220,108 @@ type ScopedLeafKeys<M, PK extends string, L extends keyof M | "union" = "union",
267
220
  * ScopedLeafValue<typeof messages, "a.b", "c">; // string
268
221
  * ```
269
222
  */
270
- type ScopedLeafValue<M, PK extends string, K extends string, L extends keyof M | "union" = "union"> = M extends LocaleMessages ? LocalizedMessagesUnion<M, L> extends infer Messages ? Messages extends MessageValue ? MessagesAtPreKey<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafValue<Scoped, K> : never : never : never : never : MessageValue;
223
+ type ScopedLeafValue<M, PK extends string, K extends string> = M extends LocaleMessages ? M[Locale<M>] extends infer Messages ? Messages extends MessageValue ? AtPath<Messages, PK> extends infer Scoped ? Scoped extends MessageObject ? LeafValue<Scoped, K> : never : never : never : never : MessageValue;
224
+
225
+ /**
226
+ * Represents a replacement map used for interpolating values
227
+ * in message templates.
228
+ *
229
+ * Replacement values are treated as plain data and interpreted
230
+ * by the message formatter at runtime.
231
+ *
232
+ * @example
233
+ * const replacements: Replacement = {
234
+ * name: "Alice",
235
+ * count: 5,
236
+ * nested: {
237
+ * score: 100,
238
+ * },
239
+ * };
240
+ */
241
+ type Replacement = Record<string, unknown>;
242
+ /**
243
+ * Resolves the expected replacement type for a localized message key.
244
+ *
245
+ * Uses the canonical (default-locale) replacement schema when available,
246
+ * otherwise falls back to `Replacement`.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * type RMap = {
251
+ * "{locale}": {
252
+ * welcome: { name: MessageValue };
253
+ * total: { count: MessageValue };
254
+ * };
255
+ * };
256
+ *
257
+ * type WelcomeReplacement = LocalizedReplacement<RMap, "welcome">;
258
+ * // => { name: MessageValue }
259
+ *
260
+ * type UnknownReplacement = LocalizedReplacement<RMap, "unknown">;
261
+ * // => Replacement
262
+ * ```
263
+ */
264
+ type LocalizedReplacement<ReplacementSchema, K extends string> = ReplacementSchema extends {
265
+ "{locale}": infer LM;
266
+ } ? AtPath<LM, K> extends infer R ? R extends MessageObject ? R : Replacement : Replacement : Replacement;
267
+
268
+ /**
269
+ * Options for initializing a translator
270
+ *
271
+ * @template M - type of messages object
272
+ */
273
+ interface BaseTranslatorOptions<M = unknown> {
274
+ /**
275
+ * Messages object for translations.
276
+ * - Use `LocaleMessages` type to enable key inference for `hasKey` and `t`.
277
+ */
278
+ messages?: Readonly<M>;
279
+ /**
280
+ * Current locale key.
281
+ * - If `messages` is typed as `LocaleMessages`, this can be inferred automatically.
282
+ */
283
+ locale: Locale<M>;
284
+ /**
285
+ * Indicates whether the translator is in a loading state.
286
+ */
287
+ isLoading?: boolean;
288
+ }
289
+
290
+ /**
291
+ * The minimal, shared foundation for all translators.
292
+ *
293
+ * @template M - Shape of the messages object.
294
+ */
295
+ declare class BaseTranslator<M extends LocaleMessages | unknown = unknown> {
296
+ /** Current messages for translation */
297
+ protected _messages: Readonly<M>;
298
+ /** Current active locale */
299
+ protected _locale: Locale<M>;
300
+ /** Current loading state */
301
+ protected _isLoading: boolean;
302
+ constructor(options: BaseTranslatorOptions<M>);
303
+ /** Get messages. */
304
+ get messages(): M;
305
+ /** Get the current active locale. */
306
+ get locale(): Locale<M>;
307
+ /** Get the current loading state. */
308
+ get isLoading(): boolean;
309
+ /**
310
+ * Replace messages with new ones.
311
+ *
312
+ * - Note: This allows runtime setting of messages even if `M` is inferred as `never`.
313
+ * The type cast bypasses TypeScript restrictions on dynamic messages.
314
+ */
315
+ setMessages<N extends LocaleMessages>(messages: N): void;
316
+ /**
317
+ * Set the active locale.
318
+ *
319
+ * - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
320
+ */
321
+ setLocale(newLocale: Locale<M>): void;
322
+ /** Set the loading state. */
323
+ setLoading(state: boolean): void;
324
+ }
271
325
 
272
326
  /**
273
327
  * Configuration options for translation behavior.
@@ -343,64 +397,6 @@ interface TranslateContext {
343
397
  */
344
398
  type TranslateHook = RuraHook<TranslateContext, MessageValue>;
345
399
 
346
- /**
347
- * Options for initializing a translator
348
- *
349
- * @template M - type of messages object
350
- */
351
- interface BaseTranslatorOptions<M = unknown> {
352
- /**
353
- * Messages object for translations.
354
- * - Use `LocaleMessages` type to enable key inference for `hasKey` and `t`.
355
- */
356
- messages?: Readonly<M>;
357
- /**
358
- * Current locale key.
359
- * - If `messages` is typed as `LocaleMessages`, this can be inferred automatically.
360
- */
361
- locale: Locale<M>;
362
- /**
363
- * Indicates whether the translator is in a loading state.
364
- */
365
- isLoading?: boolean;
366
- }
367
-
368
- /**
369
- * The minimal, shared foundation for all translators.
370
- *
371
- * @template M - Shape of the messages object.
372
- */
373
- declare class BaseTranslator<M extends LocaleMessages | unknown = unknown> {
374
- /** Current messages for translation */
375
- protected _messages: Readonly<M>;
376
- /** Current active locale */
377
- protected _locale: Locale<M>;
378
- /** Current loading state */
379
- protected _isLoading: boolean;
380
- constructor(options: BaseTranslatorOptions<M>);
381
- /** Get messages. */
382
- get messages(): M;
383
- /** Get the current active locale. */
384
- get locale(): Locale<M>;
385
- /** Get the current loading state. */
386
- get isLoading(): boolean;
387
- /**
388
- * Replace messages with new ones.
389
- *
390
- * - Note: This allows runtime setting of messages even if `M` is inferred as `never`.
391
- * The type cast bypasses TypeScript restrictions on dynamic messages.
392
- */
393
- setMessages<N extends LocaleMessages>(messages: N): void;
394
- /**
395
- * Set the active locale.
396
- *
397
- * - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
398
- */
399
- setLocale(newLocale: Locale<M>): void;
400
- /** Set the loading state. */
401
- setLoading(state: boolean): void;
402
- }
403
-
404
400
  interface CoreTranslatorOptions<M> extends BaseTranslatorOptions<M>, TranslateConfig<M> {
405
401
  /** Optional plugins or raw hooks to extend the translation pipeline */
406
402
  plugins?: Array<TranslatorPlugin | TranslateHook>;
@@ -417,9 +413,8 @@ interface TranslatorPlugin {
417
413
  * using the pipeline engine and built-in hooks.
418
414
  *
419
415
  * @template M - Shape of the messages object.
420
- * @template L - Locale selection strategy ("union" or specific locale keys).
421
416
  */
422
- declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends BaseTranslator<M> {
417
+ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends BaseTranslator<M> {
423
418
  /** User-provided options including messages, locale, and config. */
424
419
  protected translateConfig: TranslateConfig<M>;
425
420
  /** Active pipeline hooks applied during translation. */
@@ -432,21 +427,23 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L ext
432
427
  /** Outputs a debug overview of the active pipeline. */
433
428
  debugHooks(): void;
434
429
  /** Check if a key exists in the specified locale or current locale. */
435
- hasKey: <K extends LocalizedLeafKeys<M, L>>(key: K, targetLocale?: Locale<M>) => boolean;
430
+ hasKey: <K extends LocalizedLeafKeys<M>>(key: K, targetLocale?: Locale<M>) => boolean;
436
431
  /** Get the translated message for a key, with optional replacements. */
437
- t: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K, L>;
432
+ t: <K extends LocalizedLeafKeys<M> = LocalizedLeafKeys<M>>(key: K, ...replacementArgs: [LocalizedReplacement<ReplacementSchema, K>?]) => LocalizedLeafValue<M, K>;
438
433
  }
439
434
 
440
435
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
441
- type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union", PK extends string | undefined = undefined, K extends string = PK extends string ? ScopedLeafKeys<M, PK, L> : LocalizedLeafKeys<M, L>> = {
436
+ type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown, PK extends string | undefined = undefined, K extends string = PK extends string ? ScopedLeafKeys<M, PK> : LocalizedLeafKeys<M>> = {
442
437
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
443
- t: <Key extends K>(key?: Key, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, Key, L> : LocalizedLeafValue<M, Key, L>;
438
+ t: <Key extends K>(key?: Key, ...replacementArgs: [
439
+ LocalizedReplacement<ReplacementSchema, PK extends string ? `${PK}.${Key & string}` : Key>?
440
+ ]) => PK extends string ? ScopedLeafValue<M, PK, Key> : LocalizedLeafValue<M, Key>;
444
441
  };
445
442
 
446
- declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
443
+ declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends CoreTranslator<M, ReplacementSchema> {
447
444
  constructor(options: ScopeTranslatorOptions<M>);
448
445
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
449
- scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, PK> : ScopeTranslatorMethods<M, L>;
446
+ scoped<PK extends LocalizedNodeKeys<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
450
447
  }
451
448
 
452
449
  /** Semantic tag attributes map. */
@@ -521,4 +518,4 @@ interface Renderer<Output> {
521
518
  */
522
519
  declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
523
520
 
524
- export { type ASTNode, type Attributes, type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LeafValue, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedLeafValue, type LocalizedMessagesUnion, type LocalizedNodeKeys, type MessageObject, type MessageValue, type MissingHandler, type NodeKeys, type Renderer, type Replacement, type ScopedLeafKeys, type ScopedLeafValue, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, parseRichMessage, renderRichMessage };
521
+ export { type ASTNode, type AtPath, type Attributes, type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LeafValue, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedLeafValue, type LocalizedNodeKeys, type LocalizedReplacement, type MessageObject, type MessageValue, type MissingHandler, type NodeKeys, type Renderer, type Replacement, type ScopedLeafKeys, type ScopedLeafValue, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, parseRichMessage, renderRichMessage };
package/dist/index.js CHANGED
@@ -1,6 +1,15 @@
1
1
  import { rura } from 'rura';
2
2
 
3
3
  // src/translators/core-translator/core-translator.ts
4
+ function runTranslate(options) {
5
+ const context = {
6
+ ...options,
7
+ config: options.translateConfig,
8
+ candidateLocales: [],
9
+ meta: {}
10
+ };
11
+ return rura.run(context, options.hooks);
12
+ }
4
13
 
5
14
  // src/shared/utils/find-message-in-locales.ts
6
15
  var findMessageInLocales = ({
@@ -229,15 +238,6 @@ var hasKey = ({
229
238
  const message = findMessageInLocales({ messages, candidateLocales, key });
230
239
  return message !== void 0;
231
240
  };
232
- function runTranslate(options) {
233
- const context = {
234
- ...options,
235
- config: options.translateConfig,
236
- candidateLocales: [],
237
- meta: {}
238
- };
239
- return rura.run(context, options.hooks);
240
- }
241
241
 
242
242
  // src/translators/methods/translate.ts
243
243
  function translate(options) {
@@ -293,7 +293,7 @@ var CoreTranslator = class extends BaseTranslator {
293
293
  });
294
294
  };
295
295
  /** Get the translated message for a key, with optional replacements. */
296
- t = (key, replacements) => {
296
+ t = (key, ...replacementArgs) => {
297
297
  return translate({
298
298
  hooks: this.hooks,
299
299
  messages: this._messages,
@@ -301,7 +301,7 @@ var CoreTranslator = class extends BaseTranslator {
301
301
  isLoading: this._isLoading,
302
302
  translateConfig: this.translateConfig,
303
303
  key,
304
- replacements
304
+ replacements: replacementArgs[0]
305
305
  });
306
306
  };
307
307
  };
@@ -330,7 +330,7 @@ var ScopeTranslator = class extends CoreTranslator {
330
330
  targetLocale
331
331
  });
332
332
  },
333
- t: (key, replacements) => {
333
+ t: (key, ...args) => {
334
334
  const fullKey = getFullKey(preKey, key);
335
335
  return translate({
336
336
  hooks: this.hooks,
@@ -339,7 +339,7 @@ var ScopeTranslator = class extends CoreTranslator {
339
339
  isLoading: this._isLoading,
340
340
  translateConfig: this.translateConfig,
341
341
  key: fullKey,
342
- replacements
342
+ replacements: args[0]
343
343
  });
344
344
  }
345
345
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intor-translator",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "🤖 A modern, type-safe i18n engine.",
5
5
  "author": {
6
6
  "name": "Yiming Liao",