intor-translator 1.4.8 → 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
@@ -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
@@ -82,163 +82,162 @@ type Locale<M = unknown> = M extends LocaleMessages ? keyof M & string : string;
82
82
  */
83
83
  type FallbackLocalesMap<L extends string = string> = Partial<Record<L, L[]>>;
84
84
 
85
- /**
86
- * Represents a replacement map used for interpolating values
87
- * in message templates.
88
- *
89
- * Replacement values are treated as plain data and interpreted
90
- * by the message formatter at runtime.
91
- *
92
- * @example
93
- * const replacements: Replacement = {
94
- * name: "Alice",
95
- * count: 5,
96
- * nested: {
97
- * score: 100,
98
- * },
99
- * };
100
- */
101
- type Replacement = Record<string, MessageValue>;
102
-
103
85
  /**
104
86
  * Default maximum recursive depth for nested key type computations,
105
87
  * balancing type safety and compiler performance.
106
88
  */
107
89
  type DefaultDepth = 15;
108
90
  /** Countdown tuple for limiting recursive depth (up to 15 levels). */
109
- 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;
110
96
  /**
111
- * 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.
112
98
  *
113
99
  * @example
114
100
  * ```ts
115
- * NodeKeys<{ a: { b: { c: string }, d: string } }> // "a" | "a.b" | "a.b.c" | "a.d"
101
+ * ExpandPath<"user", { name: string }>; // => "user" | "user.name"
116
102
  * ```
103
+ *
117
104
  */
118
- type NodeKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
119
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}` | `${K & string}.${NodeKeys<M[K], Prev[D]>}` : 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;
106
+ /**
107
+ * Generates dot-separated path strings from a nested message object.
108
+ * @example
109
+ * ```ts
110
+ * GeneratePaths<{ user: { name: "Ivan" } }>; // => "user" | "user.name"
111
+ * ```
112
+ */
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>;
120
115
  }[keyof M] : never;
121
116
  /**
122
- * Gets dot-separated keys that resolve to message leaf values in a nested object.
117
+ * Resolves the type located at a dot-separated path.
123
118
  *
124
119
  * @example
125
120
  * ```ts
126
- * LeafKeys<{ a: { b: { c: string }, d: string } }> // → "a.d" | "a.b.c"
121
+ * AtPath<{ a: { b: { c: string } } }, "a.b">; // => { c: string };
127
122
  * ```
128
123
  */
129
- type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
130
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}.${LeafKeys<M[K], Prev[D]>}` : never;
131
- }[keyof M] : 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
+
132
130
  /**
133
- * Resolves the value type at a given dot-separated leaf key
134
- * within a nested message object.
131
+ * Dot-separated leaf keys derived from a single message object.
135
132
  *
136
133
  * @example
137
134
  * ```ts
138
- * LeafValue<{ a: { b: { c: string } } }, "a.b.c"> // → string
135
+ * Key<{ a: { b: { c: string }; z: string } }>; // => "a.z" | "a.b.c"
139
136
  * ```
140
137
  */
141
- 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;
138
+ type Key<M> = GeneratePaths<M, false>;
142
139
  /**
143
- * Resolves the type located at a dot-separated path.
140
+ * Leaf keys resolved from localized messages (union of all locales).
144
141
  *
145
- * The resolved type may be a subtree or a leaf value.
142
+ * @example
143
+ * ```ts
144
+ * LocalizedKey<{ en: { a: { b: { c: string }; z: string } } }>; // => "a.z" | "a.b.c"
145
+ * ```
146
+ */
147
+ type LocalizedKey<M> = IfLocaleMessages<M, Key<IfMessageObject<M[keyof M]>>, string>;
148
+ /**
149
+ * Leaf keys scoped under a given prefix key.
146
150
  *
147
151
  * @example
148
152
  * ```ts
149
- * const messages = { a: { b: { c: "1" }, z: "2" } };
150
- * AtPath<typeof messages, "a">; // → { b: { c: string }; z: string };
151
- * AtPath<typeof messages, "a.b">; // → { c: string };
153
+ * ScopedKey<{ en: { a: { b: { c: string }; z: string } } }, "a">; // => "b.c" | "z"
152
154
  * ```
153
155
  */
154
- 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;
156
+ type ScopedKey<M, PK extends string> = IfLocaleMessages<M, Key<IfMessageObject<AtPath<M[keyof M], PK>>>, string>;
155
157
 
156
158
  /**
157
- * Extracts all **node keys** from the messages
158
- * of a specified locale (or union of locales).
159
+ * Prefix keys that may resolve to intermediate or leaf paths.
159
160
  *
160
161
  * @example
161
162
  * ```ts
162
- * const messages = {
163
- * en: { greeting: { morning: "morning" } },
164
- * zh: { greeting: { evening: "晚上好" } },
165
- * };
166
- *
167
- * // 1. Union of all locales
168
- * LocalizedNodeKeys<typeof messages> // → "greeting" | "greeting.morning" | "greeting.evening"
169
- *
170
- * // 2. For a specified locale
171
- * LocalizedNodeKeys<typeof messages, "en"> // → "greeting" | "greeting.morning"
172
- * LocalizedNodeKeys<typeof messages, "zh"> // → "greeting" | "greeting.evening"
173
- *
174
- * // 3. Fallback when M is not LocaleMessages
175
- * LocalizedNodeKeys // → string
163
+ * PreKey<{ a: { b: { c: string }; z: string } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
176
164
  * ```
177
165
  */
178
- type LocalizedNodeKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? NodeKeys<M[keyof M], D> : string;
166
+ type PreKey<M> = GeneratePaths<M, true>;
179
167
  /**
180
- * Extracts all **leaf keys** from the messages
181
- * of a specified locale (or union of locales).
168
+ * Prefix keys resolved from localized messages (union of all locales).
182
169
  *
183
170
  * @example
184
171
  * ```ts
185
- * const messages = {
186
- * en: { greeting: { morning: "morning" } },
187
- * zh: { greeting: { evening: "晚上好" } },
188
- * };
189
- *
190
- * // 1. Union of all locales
191
- * LocalizedLeafKeys<typeof messages> // "greeting.morning" | "greeting.evening"
172
+ * LocalizedPreKey<{ en: { a: { b: { c: string }; z: string } } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
173
+ * ```
174
+ */
175
+ type LocalizedPreKey<M> = IfLocaleMessages<M, PreKey<IfMessageObject<M[keyof M]>>, string>;
176
+
177
+ /**
178
+ * Resolves the value type at a dot-separated path.
192
179
  *
193
- * // 2. For a specified locale
194
- * LocalizedLeafKeys<typeof messages, "en"> // → "greeting.morning"
195
- * LocalizedLeafKeys<typeof messages, "zh"> // "greeting.evening"
180
+ * @example
181
+ * ```ts
182
+ * Value<{ a: { b: { c: string } } }, "a.b.c">; // => string
183
+ * ```
184
+ */
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;
186
+ /**
187
+ * Value resolved from localized messages (union of all locales).
196
188
  *
197
- * // 3. Fallback if M is not LocaleMessages
198
- * LocalizedLeafKeys // → string
189
+ * @example
190
+ * ```ts
191
+ * LocalizedValue<{ en: { a: { b: { c: string }; z: string } } }, "a.b.c">; // => string
199
192
  * ```
200
193
  */
201
- type LocalizedLeafKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? LeafKeys<M[keyof M], D> : string;
194
+ type LocalizedValue<M, K extends string> = IfLocaleMessages<M, Value<M[keyof M], K>, MessageValue>;
202
195
  /**
203
- * Resolves the value type of a **localized leaf key**
204
- * from the messages of a specified locale (or union of locales).
196
+ * Value resolved under a scoped prefix key.
205
197
  *
206
- * - Fallback to `MessageValue` if M is not LocaleMessages
198
+ * @example
199
+ * ```ts
200
+ * ScopedValue<{ en: { a: { b: { c: string }; z: string } } }, "a", "b.c">; // => string
201
+ * ```
207
202
  */
208
- type LocalizedLeafValue<M = unknown, K extends string = string> = M extends LocaleMessages ? LeafValue<M[keyof M], K> : MessageValue;
203
+ type ScopedValue<M, PK extends string, K extends string> = IfLocaleMessages<M, Value<AtPath<M[keyof M], PK>, K>, MessageValue>;
209
204
 
210
205
  /**
211
- * Extracts all **leaf keys** under a scoped path (`PK`) from the messages
212
- * of a specified locale (`L`) (or union of locales).
206
+ * Generic replacement object used when no schema is available.
207
+ *
208
+ * Acts as a safe fallback for dynamic or unknown replacement shapes.
209
+ */
210
+ type Replacement = Record<string, unknown>;
211
+ /**
212
+ * Replacement object resolved from a localized replacement schema.
213
+ *
214
+ * - If the key exists in the schema, resolves to the declared object shape
215
+ * - Otherwise falls back to generic `Replacement`
213
216
  *
214
217
  * @example
215
218
  * ```ts
216
- * const messages = {
217
- * en: { a: { b: { c: "hello" }, z: "world" } },
218
- * zh: { a: { b: "hello" },
219
- * };
220
- *
221
- * ScopedLeafKeys<typeof messages, "a">; // → "b.c" | "z"
222
- * ScopedLeafKeys<typeof messages, "a.b">; // → "c"
223
- * ScopedLeafKeys<typeof messages, "a", "zh">; // → "b"
219
+ * type ReplacementSchema = { "{locale}": { greeting: { name: string } } };
220
+ * LocalizedReplacement<ReplacementSchema, "greeting">; // => { name: string }
221
+ * LocalizedReplacement<ReplacementSchema, "missing">; // => Replacement
224
222
  * ```
225
223
  */
226
- 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;
224
+ type LocalizedReplacement<ReplacementSchema, K extends string> = ReplacementSchema extends {
225
+ "{locale}": infer LM;
226
+ } ? IsNever<AtPath<LM, K>> extends true ? Replacement : AtPath<LM, K> extends MessageObject ? AtPath<LM, K> : Replacement : Replacement;
227
227
  /**
228
- * Resolves the value type of a scoped leaf key (`K`)
229
- * under a prefix path (`PK`) from localized messages.
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`.
230
232
  *
231
233
  * @example
232
234
  * ```ts
233
- * const messages = {
234
- * en: { a: { b: { c: "hello" }, z: [123] } },
235
- * };
236
- *
237
- * ScopedLeafValue<typeof messages, "a", "z">; // number[]
238
- * ScopedLeafValue<typeof messages, "a.b", "c">; // string
235
+ * type ReplacementSchema = { "{locale}": { user: { info: { name: string } } } };
236
+ * ScopedReplacement<ReplacementSchema, "user", "info">; // => { name: string }
237
+ * ScopedReplacement<ReplacementSchema, "user", "missing">; // => Replacement
239
238
  * ```
240
239
  */
241
- 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;
240
+ type ScopedReplacement<ReplacementSchema, PK extends string | undefined, K extends string> = LocalizedReplacement<ReplacementSchema, `${PK}.${K}`>;
242
241
 
243
242
  /**
244
243
  * Options for initializing a translator
@@ -389,7 +388,7 @@ interface TranslatorPlugin {
389
388
  *
390
389
  * @template M - Shape of the messages object.
391
390
  */
392
- declare class CoreTranslator<M extends LocaleMessages | unknown = unknown> extends BaseTranslator<M> {
391
+ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends BaseTranslator<M> {
393
392
  /** User-provided options including messages, locale, and config. */
394
393
  protected translateConfig: TranslateConfig<M>;
395
394
  /** Active pipeline hooks applied during translation. */
@@ -402,21 +401,21 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown> exten
402
401
  /** Outputs a debug overview of the active pipeline. */
403
402
  debugHooks(): void;
404
403
  /** Check if a key exists in the specified locale or current locale. */
405
- hasKey: <K extends LocalizedLeafKeys<M>>(key: K, targetLocale?: Locale<M>) => boolean;
404
+ hasKey: <K extends LocalizedKey<M>>(key: K, targetLocale?: Locale<M>) => boolean;
406
405
  /** Get the translated message for a key, with optional replacements. */
407
- t: <K extends LocalizedLeafKeys<M> = LocalizedLeafKeys<M>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K>;
406
+ t: <K extends LocalizedKey<M> = LocalizedKey<M>>(key: K, replacements?: LocalizedReplacement<ReplacementSchema, K>) => LocalizedValue<M, K>;
408
407
  }
409
408
 
410
409
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
411
- type ScopeTranslatorMethods<M extends LocaleMessages | unknown = 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>> = {
412
411
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
413
- t: (key?: K, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, K> : LocalizedLeafValue<M, K>;
412
+ t: <Key extends K>(key?: Key, replacements?: ScopedReplacement<ReplacementSchema, PK, K>) => PK extends string ? ScopedValue<M, PK, Key> : LocalizedValue<M, Key>;
414
413
  };
415
414
 
416
- declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown> extends CoreTranslator<M> {
415
+ declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends CoreTranslator<M, ReplacementSchema> {
417
416
  constructor(options: ScopeTranslatorOptions<M>);
418
417
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
419
- scoped<PK extends LocalizedNodeKeys<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, PK> : ScopeTranslatorMethods<M>;
418
+ scoped<PK extends LocalizedPreKey<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
420
419
  }
421
420
 
422
421
  /** Semantic tag attributes map. */
@@ -491,4 +490,4 @@ interface Renderer<Output> {
491
490
  */
492
491
  declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
493
492
 
494
- 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 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
@@ -82,163 +82,162 @@ type Locale<M = unknown> = M extends LocaleMessages ? keyof M & string : string;
82
82
  */
83
83
  type FallbackLocalesMap<L extends string = string> = Partial<Record<L, L[]>>;
84
84
 
85
- /**
86
- * Represents a replacement map used for interpolating values
87
- * in message templates.
88
- *
89
- * Replacement values are treated as plain data and interpreted
90
- * by the message formatter at runtime.
91
- *
92
- * @example
93
- * const replacements: Replacement = {
94
- * name: "Alice",
95
- * count: 5,
96
- * nested: {
97
- * score: 100,
98
- * },
99
- * };
100
- */
101
- type Replacement = Record<string, MessageValue>;
102
-
103
85
  /**
104
86
  * Default maximum recursive depth for nested key type computations,
105
87
  * balancing type safety and compiler performance.
106
88
  */
107
89
  type DefaultDepth = 15;
108
90
  /** Countdown tuple for limiting recursive depth (up to 15 levels). */
109
- 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;
110
96
  /**
111
- * 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.
112
98
  *
113
99
  * @example
114
100
  * ```ts
115
- * NodeKeys<{ a: { b: { c: string }, d: string } }> // "a" | "a.b" | "a.b.c" | "a.d"
101
+ * ExpandPath<"user", { name: string }>; // => "user" | "user.name"
116
102
  * ```
103
+ *
117
104
  */
118
- type NodeKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
119
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}` | `${K & string}.${NodeKeys<M[K], Prev[D]>}` : 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;
106
+ /**
107
+ * Generates dot-separated path strings from a nested message object.
108
+ * @example
109
+ * ```ts
110
+ * GeneratePaths<{ user: { name: "Ivan" } }>; // => "user" | "user.name"
111
+ * ```
112
+ */
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>;
120
115
  }[keyof M] : never;
121
116
  /**
122
- * Gets dot-separated keys that resolve to message leaf values in a nested object.
117
+ * Resolves the type located at a dot-separated path.
123
118
  *
124
119
  * @example
125
120
  * ```ts
126
- * LeafKeys<{ a: { b: { c: string }, d: string } }> // → "a.d" | "a.b.c"
121
+ * AtPath<{ a: { b: { c: string } } }, "a.b">; // => { c: string };
127
122
  * ```
128
123
  */
129
- type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
130
- [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}.${LeafKeys<M[K], Prev[D]>}` : never;
131
- }[keyof M] : 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
+
132
130
  /**
133
- * Resolves the value type at a given dot-separated leaf key
134
- * within a nested message object.
131
+ * Dot-separated leaf keys derived from a single message object.
135
132
  *
136
133
  * @example
137
134
  * ```ts
138
- * LeafValue<{ a: { b: { c: string } } }, "a.b.c"> // → string
135
+ * Key<{ a: { b: { c: string }; z: string } }>; // => "a.z" | "a.b.c"
139
136
  * ```
140
137
  */
141
- 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;
138
+ type Key<M> = GeneratePaths<M, false>;
142
139
  /**
143
- * Resolves the type located at a dot-separated path.
140
+ * Leaf keys resolved from localized messages (union of all locales).
144
141
  *
145
- * The resolved type may be a subtree or a leaf value.
142
+ * @example
143
+ * ```ts
144
+ * LocalizedKey<{ en: { a: { b: { c: string }; z: string } } }>; // => "a.z" | "a.b.c"
145
+ * ```
146
+ */
147
+ type LocalizedKey<M> = IfLocaleMessages<M, Key<IfMessageObject<M[keyof M]>>, string>;
148
+ /**
149
+ * Leaf keys scoped under a given prefix key.
146
150
  *
147
151
  * @example
148
152
  * ```ts
149
- * const messages = { a: { b: { c: "1" }, z: "2" } };
150
- * AtPath<typeof messages, "a">; // → { b: { c: string }; z: string };
151
- * AtPath<typeof messages, "a.b">; // → { c: string };
153
+ * ScopedKey<{ en: { a: { b: { c: string }; z: string } } }, "a">; // => "b.c" | "z"
152
154
  * ```
153
155
  */
154
- 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;
156
+ type ScopedKey<M, PK extends string> = IfLocaleMessages<M, Key<IfMessageObject<AtPath<M[keyof M], PK>>>, string>;
155
157
 
156
158
  /**
157
- * Extracts all **node keys** from the messages
158
- * of a specified locale (or union of locales).
159
+ * Prefix keys that may resolve to intermediate or leaf paths.
159
160
  *
160
161
  * @example
161
162
  * ```ts
162
- * const messages = {
163
- * en: { greeting: { morning: "morning" } },
164
- * zh: { greeting: { evening: "晚上好" } },
165
- * };
166
- *
167
- * // 1. Union of all locales
168
- * LocalizedNodeKeys<typeof messages> // → "greeting" | "greeting.morning" | "greeting.evening"
169
- *
170
- * // 2. For a specified locale
171
- * LocalizedNodeKeys<typeof messages, "en"> // → "greeting" | "greeting.morning"
172
- * LocalizedNodeKeys<typeof messages, "zh"> // → "greeting" | "greeting.evening"
173
- *
174
- * // 3. Fallback when M is not LocaleMessages
175
- * LocalizedNodeKeys // → string
163
+ * PreKey<{ a: { b: { c: string }; z: string } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
176
164
  * ```
177
165
  */
178
- type LocalizedNodeKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? NodeKeys<M[keyof M], D> : string;
166
+ type PreKey<M> = GeneratePaths<M, true>;
179
167
  /**
180
- * Extracts all **leaf keys** from the messages
181
- * of a specified locale (or union of locales).
168
+ * Prefix keys resolved from localized messages (union of all locales).
182
169
  *
183
170
  * @example
184
171
  * ```ts
185
- * const messages = {
186
- * en: { greeting: { morning: "morning" } },
187
- * zh: { greeting: { evening: "晚上好" } },
188
- * };
189
- *
190
- * // 1. Union of all locales
191
- * LocalizedLeafKeys<typeof messages> // "greeting.morning" | "greeting.evening"
172
+ * LocalizedPreKey<{ en: { a: { b: { c: string }; z: string } } }>; // → "a" | "a.b" | "a.z" | "a.b.c"
173
+ * ```
174
+ */
175
+ type LocalizedPreKey<M> = IfLocaleMessages<M, PreKey<IfMessageObject<M[keyof M]>>, string>;
176
+
177
+ /**
178
+ * Resolves the value type at a dot-separated path.
192
179
  *
193
- * // 2. For a specified locale
194
- * LocalizedLeafKeys<typeof messages, "en"> // → "greeting.morning"
195
- * LocalizedLeafKeys<typeof messages, "zh"> // "greeting.evening"
180
+ * @example
181
+ * ```ts
182
+ * Value<{ a: { b: { c: string } } }, "a.b.c">; // => string
183
+ * ```
184
+ */
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;
186
+ /**
187
+ * Value resolved from localized messages (union of all locales).
196
188
  *
197
- * // 3. Fallback if M is not LocaleMessages
198
- * LocalizedLeafKeys // → string
189
+ * @example
190
+ * ```ts
191
+ * LocalizedValue<{ en: { a: { b: { c: string }; z: string } } }, "a.b.c">; // => string
199
192
  * ```
200
193
  */
201
- type LocalizedLeafKeys<M = unknown, D extends number = DefaultDepth> = M extends LocaleMessages ? LeafKeys<M[keyof M], D> : string;
194
+ type LocalizedValue<M, K extends string> = IfLocaleMessages<M, Value<M[keyof M], K>, MessageValue>;
202
195
  /**
203
- * Resolves the value type of a **localized leaf key**
204
- * from the messages of a specified locale (or union of locales).
196
+ * Value resolved under a scoped prefix key.
205
197
  *
206
- * - Fallback to `MessageValue` if M is not LocaleMessages
198
+ * @example
199
+ * ```ts
200
+ * ScopedValue<{ en: { a: { b: { c: string }; z: string } } }, "a", "b.c">; // => string
201
+ * ```
207
202
  */
208
- type LocalizedLeafValue<M = unknown, K extends string = string> = M extends LocaleMessages ? LeafValue<M[keyof M], K> : MessageValue;
203
+ type ScopedValue<M, PK extends string, K extends string> = IfLocaleMessages<M, Value<AtPath<M[keyof M], PK>, K>, MessageValue>;
209
204
 
210
205
  /**
211
- * Extracts all **leaf keys** under a scoped path (`PK`) from the messages
212
- * of a specified locale (`L`) (or union of locales).
206
+ * Generic replacement object used when no schema is available.
207
+ *
208
+ * Acts as a safe fallback for dynamic or unknown replacement shapes.
209
+ */
210
+ type Replacement = Record<string, unknown>;
211
+ /**
212
+ * Replacement object resolved from a localized replacement schema.
213
+ *
214
+ * - If the key exists in the schema, resolves to the declared object shape
215
+ * - Otherwise falls back to generic `Replacement`
213
216
  *
214
217
  * @example
215
218
  * ```ts
216
- * const messages = {
217
- * en: { a: { b: { c: "hello" }, z: "world" } },
218
- * zh: { a: { b: "hello" },
219
- * };
220
- *
221
- * ScopedLeafKeys<typeof messages, "a">; // → "b.c" | "z"
222
- * ScopedLeafKeys<typeof messages, "a.b">; // → "c"
223
- * ScopedLeafKeys<typeof messages, "a", "zh">; // → "b"
219
+ * type ReplacementSchema = { "{locale}": { greeting: { name: string } } };
220
+ * LocalizedReplacement<ReplacementSchema, "greeting">; // => { name: string }
221
+ * LocalizedReplacement<ReplacementSchema, "missing">; // => Replacement
224
222
  * ```
225
223
  */
226
- 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;
224
+ type LocalizedReplacement<ReplacementSchema, K extends string> = ReplacementSchema extends {
225
+ "{locale}": infer LM;
226
+ } ? IsNever<AtPath<LM, K>> extends true ? Replacement : AtPath<LM, K> extends MessageObject ? AtPath<LM, K> : Replacement : Replacement;
227
227
  /**
228
- * Resolves the value type of a scoped leaf key (`K`)
229
- * under a prefix path (`PK`) from localized messages.
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`.
230
232
  *
231
233
  * @example
232
234
  * ```ts
233
- * const messages = {
234
- * en: { a: { b: { c: "hello" }, z: [123] } },
235
- * };
236
- *
237
- * ScopedLeafValue<typeof messages, "a", "z">; // number[]
238
- * ScopedLeafValue<typeof messages, "a.b", "c">; // string
235
+ * type ReplacementSchema = { "{locale}": { user: { info: { name: string } } } };
236
+ * ScopedReplacement<ReplacementSchema, "user", "info">; // => { name: string }
237
+ * ScopedReplacement<ReplacementSchema, "user", "missing">; // => Replacement
239
238
  * ```
240
239
  */
241
- 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;
240
+ type ScopedReplacement<ReplacementSchema, PK extends string | undefined, K extends string> = LocalizedReplacement<ReplacementSchema, `${PK}.${K}`>;
242
241
 
243
242
  /**
244
243
  * Options for initializing a translator
@@ -389,7 +388,7 @@ interface TranslatorPlugin {
389
388
  *
390
389
  * @template M - Shape of the messages object.
391
390
  */
392
- declare class CoreTranslator<M extends LocaleMessages | unknown = unknown> extends BaseTranslator<M> {
391
+ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends BaseTranslator<M> {
393
392
  /** User-provided options including messages, locale, and config. */
394
393
  protected translateConfig: TranslateConfig<M>;
395
394
  /** Active pipeline hooks applied during translation. */
@@ -402,21 +401,21 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown> exten
402
401
  /** Outputs a debug overview of the active pipeline. */
403
402
  debugHooks(): void;
404
403
  /** Check if a key exists in the specified locale or current locale. */
405
- hasKey: <K extends LocalizedLeafKeys<M>>(key: K, targetLocale?: Locale<M>) => boolean;
404
+ hasKey: <K extends LocalizedKey<M>>(key: K, targetLocale?: Locale<M>) => boolean;
406
405
  /** Get the translated message for a key, with optional replacements. */
407
- t: <K extends LocalizedLeafKeys<M> = LocalizedLeafKeys<M>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K>;
406
+ t: <K extends LocalizedKey<M> = LocalizedKey<M>>(key: K, replacements?: LocalizedReplacement<ReplacementSchema, K>) => LocalizedValue<M, K>;
408
407
  }
409
408
 
410
409
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
411
- type ScopeTranslatorMethods<M extends LocaleMessages | unknown = 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>> = {
412
411
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
413
- t: (key?: K, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, K> : LocalizedLeafValue<M, K>;
412
+ t: <Key extends K>(key?: Key, replacements?: ScopedReplacement<ReplacementSchema, PK, K>) => PK extends string ? ScopedValue<M, PK, Key> : LocalizedValue<M, Key>;
414
413
  };
415
414
 
416
- declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown> extends CoreTranslator<M> {
415
+ declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, ReplacementSchema = unknown> extends CoreTranslator<M, ReplacementSchema> {
417
416
  constructor(options: ScopeTranslatorOptions<M>);
418
417
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
419
- scoped<PK extends LocalizedNodeKeys<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, PK> : ScopeTranslatorMethods<M>;
418
+ scoped<PK extends LocalizedPreKey<M> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, ReplacementSchema, PK> : ScopeTranslatorMethods<M, ReplacementSchema>;
420
419
  }
421
420
 
422
421
  /** Semantic tag attributes map. */
@@ -491,4 +490,4 @@ interface Renderer<Output> {
491
490
  */
492
491
  declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
493
492
 
494
- 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 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
@@ -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.8",
3
+ "version": "1.4.9",
4
4
  "description": "🤖 A modern, type-safe i18n engine.",
5
5
  "author": {
6
6
  "name": "Yiming Liao",