intor-translator 1.4.7 → 1.4.9

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
@@ -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, ...replacementArgs) => {
298
+ t = (key, replacements) => {
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: replacementArgs[0]
306
+ replacements
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, ...args) => {
335
+ t: (key, replacements) => {
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: args[0]
344
+ replacements
345
345
  });
346
346
  }
347
347
  };
package/dist/index.d.cts CHANGED
@@ -88,182 +88,156 @@ type FallbackLocalesMap<L extends string = string> = Partial<Record<L, L[]>>;
88
88
  */
89
89
  type DefaultDepth = 15;
90
90
  /** Countdown tuple for limiting recursive depth (up to 15 levels). */
91
- type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
91
+ type PrevDepth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
92
+ /** Detects `any` to prevent infinite type recursion. */
93
+ type IsAny<T> = 0 extends 1 & T ? true : false;
94
+ /** Detects `never` for safe conditional branching in type pipelines. */
95
+ type IsNever<T> = [T] extends [never] ? true : false;
92
96
  /**
93
- * Gets all dot-separated keys of a nested object, stopping at message leaf values.
97
+ * Expands a single object property into its dot-separated path form.
94
98
  *
95
99
  * @example
96
100
  * ```ts
97
- * NodeKeys<{ a: { b: { c: string }, d: string } }> // "a" | "a.b" | "a.b.c" | "a.d"
101
+ * ExpandPath<"user", { name: string }>; // => "user" | "user.name"
98
102
  * ```
103
+ *
99
104
  */
100
- type NodeKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
101
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}` | `${K & string}.${NodeKeys<M[K], Prev[D]>}` : never;
102
- }[keyof M] : never;
105
+ type ExpandPath<K extends PropertyKey, V, IncludeSelf extends boolean = true, D extends number = DefaultDepth> = V extends MessageLeaf ? `${K & string}` : V extends MessageObject ? IncludeSelf extends true ? `${K & string}` | `${K & string}.${GeneratePaths<V, IncludeSelf, PrevDepth[D]>}` : `${K & string}.${GeneratePaths<V, IncludeSelf, PrevDepth[D]>}` : never;
103
106
  /**
104
- * Gets dot-separated keys that resolve to message leaf values in a nested object.
105
- *
107
+ * Generates dot-separated path strings from a nested message object.
106
108
  * @example
107
109
  * ```ts
108
- * LeafKeys<{ a: { b: { c: string }, d: string } }> // "a.d" | "a.b.c"
110
+ * GeneratePaths<{ user: { name: "Ivan" } }>; // => "user" | "user.name"
109
111
  * ```
110
112
  */
111
- type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
112
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}.${LeafKeys<M[K], Prev[D]>}` : never;
113
+ type GeneratePaths<M, IncludeSelf extends boolean = true, D extends number = DefaultDepth> = IsAny<M> extends true ? never : [D] extends [never] ? never : M extends MessageObject ? {
114
+ [K in keyof M]: ExpandPath<K, M[K], IncludeSelf, D>;
113
115
  }[keyof M] : never;
114
116
  /**
115
- * Resolves the value type at a given dot-separated leaf key
116
- * within a nested message object.
117
+ * Resolves the type located at a dot-separated path.
117
118
  *
118
119
  * @example
119
120
  * ```ts
120
- * LeafValue<{ a: { b: { c: string } } }, "a.b.c"> // string
121
+ * AtPath<{ a: { b: { c: string } } }, "a.b">; // => { c: string };
121
122
  * ```
122
123
  */
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
+ 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;
125
+ /** Conditional helper for branching on `LocaleMessages`. */
126
+ type IfLocaleMessages<T, Then, Else> = T extends LocaleMessages ? Then : Else;
127
+ /** Narrows a type to `MessageObject`, otherwise resolves to never. */
128
+ type IfMessageObject<T> = T extends MessageObject ? T : never;
129
+
124
130
  /**
125
- * Resolves the type located at a dot-separated path.
126
- *
127
- * The resolved type may be a subtree or a leaf value.
131
+ * Dot-separated leaf keys derived from a single message object.
128
132
  *
129
133
  * @example
130
134
  * ```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 };
135
+ * Key<{ a: { b: { c: string }; z: string } }>; // => "a.z" | "a.b.c"
134
136
  * ```
135
137
  */
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;
137
-
138
+ type Key<M> = GeneratePaths<M, false>;
138
139
  /**
139
- * Extracts all **node keys** from the messages
140
- * of a specified locale (or union of locales).
140
+ * Leaf keys resolved from localized messages (union of all locales).
141
141
  *
142
142
  * @example
143
143
  * ```ts
144
- * const messages = {
145
- * en: { greeting: { morning: "morning" } },
146
- * zh: { greeting: { evening: "晚上好" } },
147
- * };
148
- *
149
- * // 1. Union of all locales
150
- * LocalizedNodeKeys<typeof messages> // → "greeting" | "greeting.morning" | "greeting.evening"
151
- *
152
- * // 2. For a specified locale
153
- * LocalizedNodeKeys<typeof messages, "en"> // → "greeting" | "greeting.morning"
154
- * LocalizedNodeKeys<typeof messages, "zh"> // → "greeting" | "greeting.evening"
155
- *
156
- * // 3. Fallback when M is not LocaleMessages
157
- * LocalizedNodeKeys // → string
144
+ * LocalizedKey<{ en: { a: { b: { c: string }; z: string } } }>; // => "a.z" | "a.b.c"
158
145
  * ```
159
146
  */
160
- type LocalizedNodeKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? NodeKeys<M[keyof M], D> : string;
147
+ type LocalizedKey<M> = IfLocaleMessages<M, Key<IfMessageObject<M[keyof M]>>, string>;
161
148
  /**
162
- * Extracts all **leaf keys** from the messages
163
- * of a specified locale (or union of locales).
149
+ * Leaf keys scoped under a given prefix key.
164
150
  *
165
151
  * @example
166
152
  * ```ts
167
- * const messages = {
168
- * en: { greeting: { morning: "morning" } },
169
- * zh: { greeting: { evening: "晚上好" } },
170
- * };
171
- *
172
- * // 1. Union of all locales
173
- * LocalizedLeafKeys<typeof messages> // "greeting.morning" | "greeting.evening"
174
- *
175
- * // 2. For a specified locale
176
- * LocalizedLeafKeys<typeof messages, "en"> // → "greeting.morning"
177
- * LocalizedLeafKeys<typeof messages, "zh"> // → "greeting.evening"
153
+ * ScopedKey<{ en: { a: { b: { c: string }; z: string } } }, "a">; // => "b.c" | "z"
154
+ * ```
155
+ */
156
+ type ScopedKey<M, PK extends string> = IfLocaleMessages<M, Key<IfMessageObject<AtPath<M[keyof M], PK>>>, string>;
157
+
158
+ /**
159
+ * Prefix keys that may resolve to intermediate or leaf paths.
178
160
  *
179
- * // 3. Fallback if M is not LocaleMessages
180
- * LocalizedLeafKeys // → string
161
+ * @example
162
+ * ```ts
163
+ * PreKey<{ a: { b: { c: string }; z: string } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
181
164
  * ```
182
165
  */
183
- type LocalizedLeafKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? LeafKeys<M[keyof M], D> : string;
166
+ type PreKey<M> = GeneratePaths<M, true>;
184
167
  /**
185
- * Resolves the value type of a **localized leaf key**
186
- * from the messages of a specified locale (or union of locales).
168
+ * Prefix keys resolved from localized messages (union of all locales).
187
169
  *
188
- * - Fallback to `MessageValue` if M is not LocaleMessages
170
+ * @example
171
+ * ```ts
172
+ * LocalizedPreKey<{ en: { a: { b: { c: string }; z: string } } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
173
+ * ```
189
174
  */
190
- type LocalizedLeafValue<M = unknown, K extends string = string> = M extends LocaleMessages ? LeafValue<M[keyof M], K> : MessageValue;
175
+ type LocalizedPreKey<M> = IfLocaleMessages<M, PreKey<IfMessageObject<M[keyof M]>>, string>;
191
176
 
192
177
  /**
193
- * Extracts all **leaf keys** under a scoped path (`PK`) from the messages
194
- * of a specified locale (`L`) (or union of locales).
178
+ * Resolves the value type at a dot-separated path.
195
179
  *
196
180
  * @example
197
181
  * ```ts
198
- * const messages = {
199
- * en: { a: { b: { c: "hello" }, z: "world" } },
200
- * zh: { a: { b: "hello" },
201
- * };
202
- *
203
- * ScopedLeafKeys<typeof messages, "a">; // → "b.c" | "z"
204
- * ScopedLeafKeys<typeof messages, "a.b">; // → "c"
205
- * ScopedLeafKeys<typeof messages, "a", "zh">; // → "b"
182
+ * Value<{ a: { b: { c: string } } }, "a.b.c">; // => string
206
183
  * ```
207
184
  */
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;
185
+ type Value<M, K extends string> = K extends `${infer Head}.${infer Tail}` ? Head extends keyof M ? Value<M[Head], Tail> : never : K extends keyof M ? M[K] : never;
209
186
  /**
210
- * Resolves the value type of a scoped leaf key (`K`)
211
- * under a prefix path (`PK`) from localized messages.
187
+ * Value resolved from localized messages (union of all locales).
212
188
  *
213
189
  * @example
214
190
  * ```ts
215
- * const messages = {
216
- * en: { a: { b: { c: "hello" }, z: [123] } },
217
- * };
191
+ * LocalizedValue<{ en: { a: { b: { c: string }; z: string } } }, "a.b.c">; // => string
192
+ * ```
193
+ */
194
+ type LocalizedValue<M, K extends string> = IfLocaleMessages<M, Value<M[keyof M], K>, MessageValue>;
195
+ /**
196
+ * Value resolved under a scoped prefix key.
218
197
  *
219
- * ScopedLeafValue<typeof messages, "a", "z">; // number[]
220
- * ScopedLeafValue<typeof messages, "a.b", "c">; // string
198
+ * @example
199
+ * ```ts
200
+ * ScopedValue<{ en: { a: { b: { c: string }; z: string } } }, "a", "b.c">; // => string
221
201
  * ```
222
202
  */
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;
203
+ type ScopedValue<M, PK extends string, K extends string> = IfLocaleMessages<M, Value<AtPath<M[keyof M], PK>, K>, MessageValue>;
224
204
 
225
205
  /**
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.
206
+ * Generic replacement object used when no schema is available.
231
207
  *
232
- * @example
233
- * const replacements: Replacement = {
234
- * name: "Alice",
235
- * count: 5,
236
- * nested: {
237
- * score: 100,
238
- * },
239
- * };
208
+ * Acts as a safe fallback for dynamic or unknown replacement shapes.
240
209
  */
241
210
  type Replacement = Record<string, unknown>;
242
211
  /**
243
- * Resolves the expected replacement type for a localized message key.
212
+ * Replacement object resolved from a localized replacement schema.
244
213
  *
245
- * Uses the canonical (default-locale) replacement schema when available,
246
- * otherwise falls back to `Replacement`.
214
+ * - If the key exists in the schema, resolves to the declared object shape
215
+ * - Otherwise falls back to generic `Replacement`
247
216
  *
248
217
  * @example
249
218
  * ```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
219
+ * type ReplacementSchema = { "{locale}": { greeting: { name: string } } };
220
+ * LocalizedReplacement<ReplacementSchema, "greeting">; // => { name: string }
221
+ * LocalizedReplacement<ReplacementSchema, "missing">; // => Replacement
262
222
  * ```
263
223
  */
264
224
  type LocalizedReplacement<ReplacementSchema, K extends string> = ReplacementSchema extends {
265
225
  "{locale}": infer LM;
266
- } ? AtPath<LM, K> extends infer R ? R extends MessageObject ? R : Replacement : Replacement : Replacement;
226
+ } ? IsNever<AtPath<LM, K>> extends true ? Replacement : AtPath<LM, K> extends MessageObject ? AtPath<LM, K> : Replacement : Replacement;
227
+ /**
228
+ * Replacement object resolved under a scoped prefix key.
229
+ *
230
+ * Internally composes the full dot-path (`"${PK}.${K}"`)
231
+ * and delegates resolution to `LocalizedReplacement`.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * type ReplacementSchema = { "{locale}": { user: { info: { name: string } } } };
236
+ * ScopedReplacement<ReplacementSchema, "user", "info">; // => { name: string }
237
+ * ScopedReplacement<ReplacementSchema, "user", "missing">; // => Replacement
238
+ * ```
239
+ */
240
+ type ScopedReplacement<ReplacementSchema, PK extends string | undefined, K extends string> = LocalizedReplacement<ReplacementSchema, `${PK}.${K}`>;
267
241
 
268
242
  /**
269
243
  * Options for initializing a translator
@@ -427,23 +401,21 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, Repla
427
401
  /** Outputs a debug overview of the active pipeline. */
428
402
  debugHooks(): void;
429
403
  /** Check if a key exists in the specified locale or current locale. */
430
- hasKey: <K extends LocalizedLeafKeys<M>>(key: K, targetLocale?: Locale<M>) => boolean;
404
+ hasKey: <K extends LocalizedKey<M>>(key: K, targetLocale?: Locale<M>) => boolean;
431
405
  /** Get the translated message for a key, with optional replacements. */
432
- t: <K extends LocalizedLeafKeys<M> = LocalizedLeafKeys<M>>(key: K, ...replacementArgs: [LocalizedReplacement<ReplacementSchema, K>?]) => LocalizedLeafValue<M, K>;
406
+ t: <K extends LocalizedKey<M> = LocalizedKey<M>>(key: K, replacements?: LocalizedReplacement<ReplacementSchema, K>) => LocalizedValue<M, K>;
433
407
  }
434
408
 
435
409
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
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>> = {
410
+ type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown, PK extends string | undefined = undefined, K extends string = PK extends string ? ScopedKey<M, PK> : LocalizedKey<M>> = {
437
411
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
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>;
412
+ t: <Key extends K>(key?: Key, replacements?: ScopedReplacement<ReplacementSchema, PK, K>) => PK extends string ? ScopedValue<M, PK, Key> : LocalizedValue<M, Key>;
441
413
  };
442
414
 
443
415
  declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends CoreTranslator<M, ReplacementSchema> {
444
416
  constructor(options: ScopeTranslatorOptions<M>);
445
417
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
446
- scoped<PK extends LocalizedNodeKeys<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
418
+ scoped<PK extends LocalizedPreKey<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
447
419
  }
448
420
 
449
421
  /** Semantic tag attributes map. */
@@ -518,4 +490,4 @@ interface Renderer<Output> {
518
490
  */
519
491
  declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
520
492
 
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 };
493
+ export { type ASTNode, type AtPath, type Attributes, type FallbackLocalesMap, type FormatHandler, type GeneratePaths, type HandlerContext, type IfLocaleMessages, type IfMessageObject, type Key, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedKey, type LocalizedPreKey, type LocalizedReplacement, type LocalizedValue, type MessageObject, type MessageValue, type MissingHandler, type PreKey, type Renderer, type Replacement, type ScopedKey, type ScopedReplacement, type ScopedValue, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, type Value, parseRichMessage, renderRichMessage };
package/dist/index.d.ts CHANGED
@@ -88,182 +88,156 @@ type FallbackLocalesMap<L extends string = string> = Partial<Record<L, L[]>>;
88
88
  */
89
89
  type DefaultDepth = 15;
90
90
  /** Countdown tuple for limiting recursive depth (up to 15 levels). */
91
- type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
91
+ type PrevDepth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
92
+ /** Detects `any` to prevent infinite type recursion. */
93
+ type IsAny<T> = 0 extends 1 & T ? true : false;
94
+ /** Detects `never` for safe conditional branching in type pipelines. */
95
+ type IsNever<T> = [T] extends [never] ? true : false;
92
96
  /**
93
- * Gets all dot-separated keys of a nested object, stopping at message leaf values.
97
+ * Expands a single object property into its dot-separated path form.
94
98
  *
95
99
  * @example
96
100
  * ```ts
97
- * NodeKeys<{ a: { b: { c: string }, d: string } }> // "a" | "a.b" | "a.b.c" | "a.d"
101
+ * ExpandPath<"user", { name: string }>; // => "user" | "user.name"
98
102
  * ```
103
+ *
99
104
  */
100
- type NodeKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
101
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}` | `${K & string}.${NodeKeys<M[K], Prev[D]>}` : never;
102
- }[keyof M] : never;
105
+ type ExpandPath<K extends PropertyKey, V, IncludeSelf extends boolean = true, D extends number = DefaultDepth> = V extends MessageLeaf ? `${K & string}` : V extends MessageObject ? IncludeSelf extends true ? `${K & string}` | `${K & string}.${GeneratePaths<V, IncludeSelf, PrevDepth[D]>}` : `${K & string}.${GeneratePaths<V, IncludeSelf, PrevDepth[D]>}` : never;
103
106
  /**
104
- * Gets dot-separated keys that resolve to message leaf values in a nested object.
105
- *
107
+ * Generates dot-separated path strings from a nested message object.
106
108
  * @example
107
109
  * ```ts
108
- * LeafKeys<{ a: { b: { c: string }, d: string } }> // "a.d" | "a.b.c"
110
+ * GeneratePaths<{ user: { name: "Ivan" } }>; // => "user" | "user.name"
109
111
  * ```
110
112
  */
111
- type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
112
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}.${LeafKeys<M[K], Prev[D]>}` : never;
113
+ type GeneratePaths<M, IncludeSelf extends boolean = true, D extends number = DefaultDepth> = IsAny<M> extends true ? never : [D] extends [never] ? never : M extends MessageObject ? {
114
+ [K in keyof M]: ExpandPath<K, M[K], IncludeSelf, D>;
113
115
  }[keyof M] : never;
114
116
  /**
115
- * Resolves the value type at a given dot-separated leaf key
116
- * within a nested message object.
117
+ * Resolves the type located at a dot-separated path.
117
118
  *
118
119
  * @example
119
120
  * ```ts
120
- * LeafValue<{ a: { b: { c: string } } }, "a.b.c"> // string
121
+ * AtPath<{ a: { b: { c: string } } }, "a.b">; // => { c: string };
121
122
  * ```
122
123
  */
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
+ 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;
125
+ /** Conditional helper for branching on `LocaleMessages`. */
126
+ type IfLocaleMessages<T, Then, Else> = T extends LocaleMessages ? Then : Else;
127
+ /** Narrows a type to `MessageObject`, otherwise resolves to never. */
128
+ type IfMessageObject<T> = T extends MessageObject ? T : never;
129
+
124
130
  /**
125
- * Resolves the type located at a dot-separated path.
126
- *
127
- * The resolved type may be a subtree or a leaf value.
131
+ * Dot-separated leaf keys derived from a single message object.
128
132
  *
129
133
  * @example
130
134
  * ```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 };
135
+ * Key<{ a: { b: { c: string }; z: string } }>; // => "a.z" | "a.b.c"
134
136
  * ```
135
137
  */
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;
137
-
138
+ type Key<M> = GeneratePaths<M, false>;
138
139
  /**
139
- * Extracts all **node keys** from the messages
140
- * of a specified locale (or union of locales).
140
+ * Leaf keys resolved from localized messages (union of all locales).
141
141
  *
142
142
  * @example
143
143
  * ```ts
144
- * const messages = {
145
- * en: { greeting: { morning: "morning" } },
146
- * zh: { greeting: { evening: "晚上好" } },
147
- * };
148
- *
149
- * // 1. Union of all locales
150
- * LocalizedNodeKeys<typeof messages> // → "greeting" | "greeting.morning" | "greeting.evening"
151
- *
152
- * // 2. For a specified locale
153
- * LocalizedNodeKeys<typeof messages, "en"> // → "greeting" | "greeting.morning"
154
- * LocalizedNodeKeys<typeof messages, "zh"> // → "greeting" | "greeting.evening"
155
- *
156
- * // 3. Fallback when M is not LocaleMessages
157
- * LocalizedNodeKeys // → string
144
+ * LocalizedKey<{ en: { a: { b: { c: string }; z: string } } }>; // => "a.z" | "a.b.c"
158
145
  * ```
159
146
  */
160
- type LocalizedNodeKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? NodeKeys<M[keyof M], D> : string;
147
+ type LocalizedKey<M> = IfLocaleMessages<M, Key<IfMessageObject<M[keyof M]>>, string>;
161
148
  /**
162
- * Extracts all **leaf keys** from the messages
163
- * of a specified locale (or union of locales).
149
+ * Leaf keys scoped under a given prefix key.
164
150
  *
165
151
  * @example
166
152
  * ```ts
167
- * const messages = {
168
- * en: { greeting: { morning: "morning" } },
169
- * zh: { greeting: { evening: "晚上好" } },
170
- * };
171
- *
172
- * // 1. Union of all locales
173
- * LocalizedLeafKeys<typeof messages> // "greeting.morning" | "greeting.evening"
174
- *
175
- * // 2. For a specified locale
176
- * LocalizedLeafKeys<typeof messages, "en"> // → "greeting.morning"
177
- * LocalizedLeafKeys<typeof messages, "zh"> // → "greeting.evening"
153
+ * ScopedKey<{ en: { a: { b: { c: string }; z: string } } }, "a">; // => "b.c" | "z"
154
+ * ```
155
+ */
156
+ type ScopedKey<M, PK extends string> = IfLocaleMessages<M, Key<IfMessageObject<AtPath<M[keyof M], PK>>>, string>;
157
+
158
+ /**
159
+ * Prefix keys that may resolve to intermediate or leaf paths.
178
160
  *
179
- * // 3. Fallback if M is not LocaleMessages
180
- * LocalizedLeafKeys // → string
161
+ * @example
162
+ * ```ts
163
+ * PreKey<{ a: { b: { c: string }; z: string } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
181
164
  * ```
182
165
  */
183
- type LocalizedLeafKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? LeafKeys<M[keyof M], D> : string;
166
+ type PreKey<M> = GeneratePaths<M, true>;
184
167
  /**
185
- * Resolves the value type of a **localized leaf key**
186
- * from the messages of a specified locale (or union of locales).
168
+ * Prefix keys resolved from localized messages (union of all locales).
187
169
  *
188
- * - Fallback to `MessageValue` if M is not LocaleMessages
170
+ * @example
171
+ * ```ts
172
+ * LocalizedPreKey<{ en: { a: { b: { c: string }; z: string } } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
173
+ * ```
189
174
  */
190
- type LocalizedLeafValue<M = unknown, K extends string = string> = M extends LocaleMessages ? LeafValue<M[keyof M], K> : MessageValue;
175
+ type LocalizedPreKey<M> = IfLocaleMessages<M, PreKey<IfMessageObject<M[keyof M]>>, string>;
191
176
 
192
177
  /**
193
- * Extracts all **leaf keys** under a scoped path (`PK`) from the messages
194
- * of a specified locale (`L`) (or union of locales).
178
+ * Resolves the value type at a dot-separated path.
195
179
  *
196
180
  * @example
197
181
  * ```ts
198
- * const messages = {
199
- * en: { a: { b: { c: "hello" }, z: "world" } },
200
- * zh: { a: { b: "hello" },
201
- * };
202
- *
203
- * ScopedLeafKeys<typeof messages, "a">; // → "b.c" | "z"
204
- * ScopedLeafKeys<typeof messages, "a.b">; // → "c"
205
- * ScopedLeafKeys<typeof messages, "a", "zh">; // → "b"
182
+ * Value<{ a: { b: { c: string } } }, "a.b.c">; // => string
206
183
  * ```
207
184
  */
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;
185
+ type Value<M, K extends string> = K extends `${infer Head}.${infer Tail}` ? Head extends keyof M ? Value<M[Head], Tail> : never : K extends keyof M ? M[K] : never;
209
186
  /**
210
- * Resolves the value type of a scoped leaf key (`K`)
211
- * under a prefix path (`PK`) from localized messages.
187
+ * Value resolved from localized messages (union of all locales).
212
188
  *
213
189
  * @example
214
190
  * ```ts
215
- * const messages = {
216
- * en: { a: { b: { c: "hello" }, z: [123] } },
217
- * };
191
+ * LocalizedValue<{ en: { a: { b: { c: string }; z: string } } }, "a.b.c">; // => string
192
+ * ```
193
+ */
194
+ type LocalizedValue<M, K extends string> = IfLocaleMessages<M, Value<M[keyof M], K>, MessageValue>;
195
+ /**
196
+ * Value resolved under a scoped prefix key.
218
197
  *
219
- * ScopedLeafValue<typeof messages, "a", "z">; // number[]
220
- * ScopedLeafValue<typeof messages, "a.b", "c">; // string
198
+ * @example
199
+ * ```ts
200
+ * ScopedValue<{ en: { a: { b: { c: string }; z: string } } }, "a", "b.c">; // => string
221
201
  * ```
222
202
  */
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;
203
+ type ScopedValue<M, PK extends string, K extends string> = IfLocaleMessages<M, Value<AtPath<M[keyof M], PK>, K>, MessageValue>;
224
204
 
225
205
  /**
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.
206
+ * Generic replacement object used when no schema is available.
231
207
  *
232
- * @example
233
- * const replacements: Replacement = {
234
- * name: "Alice",
235
- * count: 5,
236
- * nested: {
237
- * score: 100,
238
- * },
239
- * };
208
+ * Acts as a safe fallback for dynamic or unknown replacement shapes.
240
209
  */
241
210
  type Replacement = Record<string, unknown>;
242
211
  /**
243
- * Resolves the expected replacement type for a localized message key.
212
+ * Replacement object resolved from a localized replacement schema.
244
213
  *
245
- * Uses the canonical (default-locale) replacement schema when available,
246
- * otherwise falls back to `Replacement`.
214
+ * - If the key exists in the schema, resolves to the declared object shape
215
+ * - Otherwise falls back to generic `Replacement`
247
216
  *
248
217
  * @example
249
218
  * ```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
219
+ * type ReplacementSchema = { "{locale}": { greeting: { name: string } } };
220
+ * LocalizedReplacement<ReplacementSchema, "greeting">; // => { name: string }
221
+ * LocalizedReplacement<ReplacementSchema, "missing">; // => Replacement
262
222
  * ```
263
223
  */
264
224
  type LocalizedReplacement<ReplacementSchema, K extends string> = ReplacementSchema extends {
265
225
  "{locale}": infer LM;
266
- } ? AtPath<LM, K> extends infer R ? R extends MessageObject ? R : Replacement : Replacement : Replacement;
226
+ } ? IsNever<AtPath<LM, K>> extends true ? Replacement : AtPath<LM, K> extends MessageObject ? AtPath<LM, K> : Replacement : Replacement;
227
+ /**
228
+ * Replacement object resolved under a scoped prefix key.
229
+ *
230
+ * Internally composes the full dot-path (`"${PK}.${K}"`)
231
+ * and delegates resolution to `LocalizedReplacement`.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * type ReplacementSchema = { "{locale}": { user: { info: { name: string } } } };
236
+ * ScopedReplacement<ReplacementSchema, "user", "info">; // => { name: string }
237
+ * ScopedReplacement<ReplacementSchema, "user", "missing">; // => Replacement
238
+ * ```
239
+ */
240
+ type ScopedReplacement<ReplacementSchema, PK extends string | undefined, K extends string> = LocalizedReplacement<ReplacementSchema, `${PK}.${K}`>;
267
241
 
268
242
  /**
269
243
  * Options for initializing a translator
@@ -427,23 +401,21 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, Repla
427
401
  /** Outputs a debug overview of the active pipeline. */
428
402
  debugHooks(): void;
429
403
  /** Check if a key exists in the specified locale or current locale. */
430
- hasKey: <K extends LocalizedLeafKeys<M>>(key: K, targetLocale?: Locale<M>) => boolean;
404
+ hasKey: <K extends LocalizedKey<M>>(key: K, targetLocale?: Locale<M>) => boolean;
431
405
  /** Get the translated message for a key, with optional replacements. */
432
- t: <K extends LocalizedLeafKeys<M> = LocalizedLeafKeys<M>>(key: K, ...replacementArgs: [LocalizedReplacement<ReplacementSchema, K>?]) => LocalizedLeafValue<M, K>;
406
+ t: <K extends LocalizedKey<M> = LocalizedKey<M>>(key: K, replacements?: LocalizedReplacement<ReplacementSchema, K>) => LocalizedValue<M, K>;
433
407
  }
434
408
 
435
409
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
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>> = {
410
+ type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown, PK extends string | undefined = undefined, K extends string = PK extends string ? ScopedKey<M, PK> : LocalizedKey<M>> = {
437
411
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
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>;
412
+ t: <Key extends K>(key?: Key, replacements?: ScopedReplacement<ReplacementSchema, PK, K>) => PK extends string ? ScopedValue<M, PK, Key> : LocalizedValue<M, Key>;
441
413
  };
442
414
 
443
415
  declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends CoreTranslator<M, ReplacementSchema> {
444
416
  constructor(options: ScopeTranslatorOptions<M>);
445
417
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
446
- scoped<PK extends LocalizedNodeKeys<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
418
+ scoped<PK extends LocalizedPreKey<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
447
419
  }
448
420
 
449
421
  /** Semantic tag attributes map. */
@@ -518,4 +490,4 @@ interface Renderer<Output> {
518
490
  */
519
491
  declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
520
492
 
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 };
493
+ export { type ASTNode, type AtPath, type Attributes, type FallbackLocalesMap, type FormatHandler, type GeneratePaths, type HandlerContext, type IfLocaleMessages, type IfMessageObject, type Key, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedKey, type LocalizedPreKey, type LocalizedReplacement, type LocalizedValue, type MessageObject, type MessageValue, type MissingHandler, type PreKey, type Renderer, type Replacement, type ScopedKey, type ScopedReplacement, type ScopedValue, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, type Value, parseRichMessage, renderRichMessage };
package/dist/index.js CHANGED
@@ -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, ...replacementArgs) => {
296
+ t = (key, replacements) => {
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: replacementArgs[0]
304
+ replacements
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, ...args) => {
333
+ t: (key, replacements) => {
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: args[0]
342
+ replacements
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.7",
3
+ "version": "1.4.9",
4
4
  "description": "🤖 A modern, type-safe i18n engine.",
5
5
  "author": {
6
6
  "name": "Yiming Liao",