intor-translator 1.4.2 → 1.4.4

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
@@ -121,7 +121,7 @@ var loading = rura.rura.createHook(
121
121
  };
122
122
  }
123
123
  const { loadingMessage } = config;
124
- if ("loadingMessage" in config) {
124
+ if (loadingMessage !== void 0) {
125
125
  return { early: true, output: loadingMessage };
126
126
  }
127
127
  },
@@ -140,7 +140,7 @@ var missing = rura.rura.createHook(
140
140
  };
141
141
  }
142
142
  const { missingMessage } = config;
143
- if ("missingMessage" in config) {
143
+ if (missingMessage !== void 0) {
144
144
  return { early: true, output: missingMessage };
145
145
  }
146
146
  return { early: true, output: key };
@@ -176,6 +176,7 @@ var DEFAULT_HOOKS = [
176
176
  format,
177
177
  interpolate
178
178
  ];
179
+ rura.rura.createPipeline(DEFAULT_HOOKS).debugHooks();
179
180
 
180
181
  // src/translators/base-translator/base-translator.ts
181
182
  var BaseTranslator = class {
@@ -250,12 +251,18 @@ function runTranslate(options) {
250
251
  function translate(options) {
251
252
  const { early, ctx, output } = runTranslate(options);
252
253
  if (early === true) return output;
254
+ if (ctx.finalMessage === void 0) {
255
+ throw new Error("Invariant violated: missing hook did not produce output");
256
+ }
253
257
  return ctx.finalMessage;
254
258
  }
255
259
 
256
260
  // src/translators/methods/translate-raw.ts
257
261
  function translateRaw(options) {
258
262
  const { ctx } = runTranslate(options);
263
+ if (ctx.rawValue === void 0) {
264
+ throw new Error("Invariant violated: missing hook did not produce output");
265
+ }
259
266
  return ctx.rawValue;
260
267
  }
261
268
 
@@ -502,18 +509,44 @@ function buildAST(tokens) {
502
509
 
503
510
  // src/message/parse-rich-message.ts
504
511
  function parseRichMessage(message) {
505
- const tokens = tokenize(message);
506
- return buildAST(tokens);
512
+ if (message == null) return [];
513
+ if (typeof message === "string") {
514
+ const tokens = tokenize(message);
515
+ return buildAST(tokens);
516
+ }
517
+ if (typeof message === "number" || typeof message === "boolean") {
518
+ const tokens = tokenize(String(message));
519
+ return buildAST(tokens);
520
+ }
521
+ if (Array.isArray(message)) {
522
+ return message.flatMap((m) => parseRichMessage(m));
523
+ }
524
+ return [
525
+ {
526
+ type: "raw",
527
+ value: message
528
+ }
529
+ ];
507
530
  }
508
531
 
509
532
  // src/message/render/render.ts
510
533
  function render(nodes, renderer) {
511
534
  return nodes.map((node) => {
512
- if (node.type === "text") {
513
- return renderer.text(node.value);
535
+ switch (node.type) {
536
+ // Plain text node
537
+ case "text": {
538
+ return renderer.text(node.value);
539
+ }
540
+ // Semantic tag node
541
+ case "tag": {
542
+ const children = render(node.children, renderer);
543
+ return renderer.tag(node.name, node.attributes, children);
544
+ }
545
+ // Raw message value
546
+ case "raw": {
547
+ return renderer.raw(node.value);
548
+ }
514
549
  }
515
- const children = render(node.children, renderer);
516
- return renderer.tag(node.name, node.attributes, children);
517
550
  });
518
551
  }
519
552
 
package/dist/index.d.cts CHANGED
@@ -1,19 +1,5 @@
1
1
  import { RuraHook } from 'rura';
2
2
 
3
- type MessagePrimitive$1 = string | number | boolean | null;
4
- type MessageArray$1 = readonly MessageValue$1[];
5
- /**
6
- * A recursive message tree object.
7
- *
8
- * Represents the root message structure for a single locale
9
- * (i.e. the value of `LocaleMessages[locale]`).
10
- */
11
- interface MessageObject$1 {
12
- [key: string]: MessageValue$1;
13
- }
14
- /** A message value in the locale message tree. */
15
- type MessageValue$1 = MessagePrimitive$1 | MessageObject$1 | MessageArray$1;
16
-
17
3
  type MessagePrimitive = string | number | boolean | null;
18
4
  type MessageArray = readonly MessageValue[];
19
5
  /**
@@ -168,6 +154,16 @@ type NodeKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never
168
154
  type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
169
155
  [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}.${LeafKeys<M[K], Prev[D]>}` : never;
170
156
  }[keyof M] : never;
157
+ /**
158
+ * Resolves the value type at a given dot-separated leaf key
159
+ * within a nested message object.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * LeafValue<{ a: { b: { c: string } } }, "a.b.c"> // → string
164
+ * ```
165
+ */
166
+ 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;
171
167
 
172
168
  /**
173
169
  * Extracts all **node keys** from the messages
@@ -215,6 +211,13 @@ type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D ext
215
211
  * ```
216
212
  */
217
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;
214
+ /**
215
+ * Resolves the value type of a **localized leaf key**
216
+ * from the messages of a specified locale (or union of locales).
217
+ *
218
+ * - Fallback to `MessageValue` if M is not LocaleMessages
219
+ */
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;
218
221
 
219
222
  /**
220
223
  * Resolves the type at a dot-separated key in a nested object.
@@ -250,6 +253,21 @@ type MessagesAtPreKey<T, PK extends string> = PK extends `${infer Head}.${infer
250
253
  * ```
251
254
  */
252
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;
256
+ /**
257
+ * Resolves the value type of a scoped leaf key (`K`)
258
+ * under a prefix path (`PK`) from localized messages.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const messages = {
263
+ * en: { a: { b: { c: "hello" }, z: [123] } },
264
+ * };
265
+ *
266
+ * ScopedLeafValue<typeof messages, "a", "z">; // number[]
267
+ * ScopedLeafValue<typeof messages, "a.b", "c">; // string
268
+ * ```
269
+ */
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;
253
271
 
254
272
  /**
255
273
  * Configuration options for translation behavior.
@@ -276,13 +294,13 @@ type TranslateHandlers = {
276
294
  formatHandler?: FormatHandler;
277
295
  };
278
296
  /** Function called when translation is still loading. */
279
- type LoadingHandler<Result = unknown> = (ctx: HandlerContext) => Result;
297
+ type LoadingHandler = (ctx: HandlerContext) => MessageValue;
280
298
  /** Function called when no message is found for the given key. */
281
- type MissingHandler<Result = unknown> = (ctx: HandlerContext) => Result;
299
+ type MissingHandler = (ctx: HandlerContext) => MessageValue;
282
300
  /** Function to format a resolved message. */
283
- type FormatHandler<Result = unknown> = (ctx: HandlerContext & {
301
+ type FormatHandler = (ctx: HandlerContext & {
284
302
  rawString: string;
285
- }) => Result;
303
+ }) => MessageValue;
286
304
  /**
287
305
  * Snapshot of the translate pipeline context exposed to handlers.
288
306
  *
@@ -295,10 +313,8 @@ type HandlerContext = Readonly<Omit<TranslateContext, "finalMessage">>;
295
313
 
296
314
  /**
297
315
  * Context object shared across the translate pipeline.
298
- *
299
- * @template Result - Final translated value type.
300
316
  */
301
- interface TranslateContext<Result = unknown> {
317
+ interface TranslateContext {
302
318
  /** Configuration for the translate pipeline. */
303
319
  config: TranslateConfig;
304
320
  /** Current messages for translation */
@@ -318,16 +334,16 @@ interface TranslateContext<Result = unknown> {
318
334
  /** Raw string message before formatting. */
319
335
  rawString?: string;
320
336
  /** Message after formatting (e.g. ICU, custom formatters) */
321
- formattedMessage?: unknown;
337
+ formattedMessage?: MessageValue;
322
338
  /** Final value produced by the pipeline */
323
- finalMessage?: Result;
339
+ finalMessage?: MessageValue;
324
340
  /** Free-form metadata shared between hooks. */
325
341
  meta: Record<string, unknown>;
326
342
  }
327
343
  /**
328
344
  * A single step in the translate pipeline.
329
345
  */
330
- type TranslateHook = RuraHook<TranslateContext>;
346
+ type TranslateHook = RuraHook<TranslateContext, MessageValue>;
331
347
 
332
348
  /**
333
349
  * Options for initializing a translator
@@ -420,29 +436,29 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L ext
420
436
  /** Check if a key exists in the specified locale or current locale. */
421
437
  hasKey: <K extends LocalizedLeafKeys<M, L>>(key: K, targetLocale?: Locale<M>) => boolean;
422
438
  /** Get the translated message for a key, with optional replacements. */
423
- t: <Result = string, K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => Result;
439
+ t: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K, L>;
424
440
  /** Get the raw message value for a key without formatting or interpolation. */
425
- tRaw: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => MessageValue | undefined;
441
+ tRaw: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K, L>;
426
442
  }
427
443
 
428
444
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
429
- type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union", K = LocalizedLeafKeys<M, L>> = {
445
+ 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>> = {
430
446
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
431
- t: <Result = string>(key?: K, replacements?: Replacement) => Result;
432
- tRaw: (key?: K, replacements?: Replacement) => MessageValue$1 | undefined;
447
+ t: <Key extends K>(key?: Key, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, Key, L> : LocalizedLeafValue<M, Key, L>;
448
+ tRaw: <Key extends K>(key?: Key, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, Key, L> : LocalizedLeafValue<M, Key, L>;
433
449
  };
434
450
 
435
451
  declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
436
452
  constructor(options: ScopeTranslatorOptions<M>);
437
453
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
438
- scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, ScopedLeafKeys<M, PK, L>> : ScopeTranslatorMethods<M, L>;
454
+ scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, PK> : ScopeTranslatorMethods<M, L>;
439
455
  }
440
456
 
441
457
  /** Semantic tag attributes map. */
442
458
  type Attributes = Record<string, string>;
443
459
 
444
460
  /** Semantic node produced by the AST builder. */
445
- type ASTNode = TextNode | TagNode;
461
+ type ASTNode = TextNode | TagNode | RawNode;
446
462
  /** Plain text node in the semantic AST. */
447
463
  interface TextNode {
448
464
  type: "text";
@@ -455,23 +471,27 @@ interface TagNode {
455
471
  attributes: Attributes;
456
472
  children: ASTNode[];
457
473
  }
474
+ /** Raw node representing a non-tokenizable message value. */
475
+ interface RawNode {
476
+ type: "raw";
477
+ value: Exclude<MessageValue, string>;
478
+ }
458
479
 
459
480
  /**
460
- * Parse a rich-formatted message string into a semantic AST.
461
- *
462
- * This function is a high-level entry point for processing translated
463
- * messages that contain semantic tags (e.g. <b>, <a>, <i>).
481
+ * Parse a rich message value into a semantic AST.
464
482
  *
465
- * Internally, it performs the following steps:
483
+ * This is the main entry point for processing translated messages that may
484
+ * contain semantic markup (e.g. <b>, <a>, <i>) or non-string values.
466
485
  *
467
- * - message (string) ⬇
468
- * - tokenize
469
- * - build AST
486
+ * Behavior by message type:
487
+ * - string → tokenized and parsed into semantic AST
488
+ * - number/boolean → stringified, then tokenized and parsed
489
+ * - array → recursively flattened and parsed
490
+ * - object → preserved as a raw AST node
470
491
  *
471
- * The returned AST represents the semantic structure of the message
472
- * and is intended to be consumed by renderers or further processing stages.
492
+ * The returned AST is renderer-agnostic and represents semantic structure only.
473
493
  */
474
- declare function parseRichMessage(message: string): ASTNode[];
494
+ declare function parseRichMessage(message: MessageValue): ASTNode[];
475
495
 
476
496
  /**
477
497
  * Renderer interface for semantic message ASTs.
@@ -487,21 +507,23 @@ interface Renderer<Output> {
487
507
  text(value: string): Output;
488
508
  /** Render a semantic tag node with attributes and rendered children. */
489
509
  tag(name: string, attributes: Attributes, children: Output[]): Output;
510
+ /** Render a raw (non-tokenized) message value. */
511
+ raw(value: Exclude<MessageValue, string>): Output;
490
512
  }
491
513
 
492
514
  /**
493
- * Render a rich-formatted message into a concrete output using a renderer.
515
+ * Render a rich message value into a concrete output using the given renderer.
494
516
  *
495
- * This function orchestrates the full rich message pipeline:
517
+ * This function is the main entry point of the rich message pipeline.
518
+ * It orchestrates the full flow from message value to rendered output:
496
519
  *
497
- * - message (string)
498
- * - tokenize
499
- * - build AST
500
- * - render via provided renderer
520
+ * - MessageValue
521
+ * - parse into semantic AST
522
+ * - render AST via the provided renderer
501
523
  *
502
- * All rendering behavior is defined by the given renderer, making this
503
- * function environment-agnostic (string, DOM, React, etc.).
524
+ * All rendering behavior is delegated to the renderer, making this function
525
+ * fully environment-agnostic (e.g. string, DOM, React).
504
526
  */
505
- declare function renderRichMessage<Output>(message: string, renderer: Renderer<Output>): Output[];
527
+ declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
506
528
 
507
- export { type ASTNode, type Attributes, type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedMessagesUnion, type LocalizedNodeKeys, type MessageObject, type MessageValue, type MissingHandler, type NodeKeys, type Renderer, type Replacement, type ScopedLeafKeys, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, parseRichMessage, renderRichMessage };
529
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,5 @@
1
1
  import { RuraHook } from 'rura';
2
2
 
3
- type MessagePrimitive$1 = string | number | boolean | null;
4
- type MessageArray$1 = readonly MessageValue$1[];
5
- /**
6
- * A recursive message tree object.
7
- *
8
- * Represents the root message structure for a single locale
9
- * (i.e. the value of `LocaleMessages[locale]`).
10
- */
11
- interface MessageObject$1 {
12
- [key: string]: MessageValue$1;
13
- }
14
- /** A message value in the locale message tree. */
15
- type MessageValue$1 = MessagePrimitive$1 | MessageObject$1 | MessageArray$1;
16
-
17
3
  type MessagePrimitive = string | number | boolean | null;
18
4
  type MessageArray = readonly MessageValue[];
19
5
  /**
@@ -168,6 +154,16 @@ type NodeKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never
168
154
  type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never : M extends object ? {
169
155
  [K in keyof M]: M[K] extends MessageLeaf ? `${K & string}` : M[K] extends object ? `${K & string}.${LeafKeys<M[K], Prev[D]>}` : never;
170
156
  }[keyof M] : never;
157
+ /**
158
+ * Resolves the value type at a given dot-separated leaf key
159
+ * within a nested message object.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * LeafValue<{ a: { b: { c: string } } }, "a.b.c"> // → string
164
+ * ```
165
+ */
166
+ 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;
171
167
 
172
168
  /**
173
169
  * Extracts all **node keys** from the messages
@@ -215,6 +211,13 @@ type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D ext
215
211
  * ```
216
212
  */
217
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;
214
+ /**
215
+ * Resolves the value type of a **localized leaf key**
216
+ * from the messages of a specified locale (or union of locales).
217
+ *
218
+ * - Fallback to `MessageValue` if M is not LocaleMessages
219
+ */
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;
218
221
 
219
222
  /**
220
223
  * Resolves the type at a dot-separated key in a nested object.
@@ -250,6 +253,21 @@ type MessagesAtPreKey<T, PK extends string> = PK extends `${infer Head}.${infer
250
253
  * ```
251
254
  */
252
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;
256
+ /**
257
+ * Resolves the value type of a scoped leaf key (`K`)
258
+ * under a prefix path (`PK`) from localized messages.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const messages = {
263
+ * en: { a: { b: { c: "hello" }, z: [123] } },
264
+ * };
265
+ *
266
+ * ScopedLeafValue<typeof messages, "a", "z">; // number[]
267
+ * ScopedLeafValue<typeof messages, "a.b", "c">; // string
268
+ * ```
269
+ */
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;
253
271
 
254
272
  /**
255
273
  * Configuration options for translation behavior.
@@ -276,13 +294,13 @@ type TranslateHandlers = {
276
294
  formatHandler?: FormatHandler;
277
295
  };
278
296
  /** Function called when translation is still loading. */
279
- type LoadingHandler<Result = unknown> = (ctx: HandlerContext) => Result;
297
+ type LoadingHandler = (ctx: HandlerContext) => MessageValue;
280
298
  /** Function called when no message is found for the given key. */
281
- type MissingHandler<Result = unknown> = (ctx: HandlerContext) => Result;
299
+ type MissingHandler = (ctx: HandlerContext) => MessageValue;
282
300
  /** Function to format a resolved message. */
283
- type FormatHandler<Result = unknown> = (ctx: HandlerContext & {
301
+ type FormatHandler = (ctx: HandlerContext & {
284
302
  rawString: string;
285
- }) => Result;
303
+ }) => MessageValue;
286
304
  /**
287
305
  * Snapshot of the translate pipeline context exposed to handlers.
288
306
  *
@@ -295,10 +313,8 @@ type HandlerContext = Readonly<Omit<TranslateContext, "finalMessage">>;
295
313
 
296
314
  /**
297
315
  * Context object shared across the translate pipeline.
298
- *
299
- * @template Result - Final translated value type.
300
316
  */
301
- interface TranslateContext<Result = unknown> {
317
+ interface TranslateContext {
302
318
  /** Configuration for the translate pipeline. */
303
319
  config: TranslateConfig;
304
320
  /** Current messages for translation */
@@ -318,16 +334,16 @@ interface TranslateContext<Result = unknown> {
318
334
  /** Raw string message before formatting. */
319
335
  rawString?: string;
320
336
  /** Message after formatting (e.g. ICU, custom formatters) */
321
- formattedMessage?: unknown;
337
+ formattedMessage?: MessageValue;
322
338
  /** Final value produced by the pipeline */
323
- finalMessage?: Result;
339
+ finalMessage?: MessageValue;
324
340
  /** Free-form metadata shared between hooks. */
325
341
  meta: Record<string, unknown>;
326
342
  }
327
343
  /**
328
344
  * A single step in the translate pipeline.
329
345
  */
330
- type TranslateHook = RuraHook<TranslateContext>;
346
+ type TranslateHook = RuraHook<TranslateContext, MessageValue>;
331
347
 
332
348
  /**
333
349
  * Options for initializing a translator
@@ -420,29 +436,29 @@ declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L ext
420
436
  /** Check if a key exists in the specified locale or current locale. */
421
437
  hasKey: <K extends LocalizedLeafKeys<M, L>>(key: K, targetLocale?: Locale<M>) => boolean;
422
438
  /** Get the translated message for a key, with optional replacements. */
423
- t: <Result = string, K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => Result;
439
+ t: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K, L>;
424
440
  /** Get the raw message value for a key without formatting or interpolation. */
425
- tRaw: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => MessageValue | undefined;
441
+ tRaw: <K extends LocalizedLeafKeys<M, L> = LocalizedLeafKeys<M, L>>(key: K, replacements?: Replacement) => LocalizedLeafValue<M, K, L>;
426
442
  }
427
443
 
428
444
  type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
429
- type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union", K = LocalizedLeafKeys<M, L>> = {
445
+ 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>> = {
430
446
  hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
431
- t: <Result = string>(key?: K, replacements?: Replacement) => Result;
432
- tRaw: (key?: K, replacements?: Replacement) => MessageValue$1 | undefined;
447
+ t: <Key extends K>(key?: Key, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, Key, L> : LocalizedLeafValue<M, Key, L>;
448
+ tRaw: <Key extends K>(key?: Key, replacements?: Replacement) => PK extends string ? ScopedLeafValue<M, PK, Key, L> : LocalizedLeafValue<M, Key, L>;
433
449
  };
434
450
 
435
451
  declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
436
452
  constructor(options: ScopeTranslatorOptions<M>);
437
453
  /** Create a scoped translator with a prefix key for resolving nested message paths. */
438
- scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, ScopedLeafKeys<M, PK, L>> : ScopeTranslatorMethods<M, L>;
454
+ scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, PK> : ScopeTranslatorMethods<M, L>;
439
455
  }
440
456
 
441
457
  /** Semantic tag attributes map. */
442
458
  type Attributes = Record<string, string>;
443
459
 
444
460
  /** Semantic node produced by the AST builder. */
445
- type ASTNode = TextNode | TagNode;
461
+ type ASTNode = TextNode | TagNode | RawNode;
446
462
  /** Plain text node in the semantic AST. */
447
463
  interface TextNode {
448
464
  type: "text";
@@ -455,23 +471,27 @@ interface TagNode {
455
471
  attributes: Attributes;
456
472
  children: ASTNode[];
457
473
  }
474
+ /** Raw node representing a non-tokenizable message value. */
475
+ interface RawNode {
476
+ type: "raw";
477
+ value: Exclude<MessageValue, string>;
478
+ }
458
479
 
459
480
  /**
460
- * Parse a rich-formatted message string into a semantic AST.
461
- *
462
- * This function is a high-level entry point for processing translated
463
- * messages that contain semantic tags (e.g. <b>, <a>, <i>).
481
+ * Parse a rich message value into a semantic AST.
464
482
  *
465
- * Internally, it performs the following steps:
483
+ * This is the main entry point for processing translated messages that may
484
+ * contain semantic markup (e.g. <b>, <a>, <i>) or non-string values.
466
485
  *
467
- * - message (string) ⬇
468
- * - tokenize
469
- * - build AST
486
+ * Behavior by message type:
487
+ * - string → tokenized and parsed into semantic AST
488
+ * - number/boolean → stringified, then tokenized and parsed
489
+ * - array → recursively flattened and parsed
490
+ * - object → preserved as a raw AST node
470
491
  *
471
- * The returned AST represents the semantic structure of the message
472
- * and is intended to be consumed by renderers or further processing stages.
492
+ * The returned AST is renderer-agnostic and represents semantic structure only.
473
493
  */
474
- declare function parseRichMessage(message: string): ASTNode[];
494
+ declare function parseRichMessage(message: MessageValue): ASTNode[];
475
495
 
476
496
  /**
477
497
  * Renderer interface for semantic message ASTs.
@@ -487,21 +507,23 @@ interface Renderer<Output> {
487
507
  text(value: string): Output;
488
508
  /** Render a semantic tag node with attributes and rendered children. */
489
509
  tag(name: string, attributes: Attributes, children: Output[]): Output;
510
+ /** Render a raw (non-tokenized) message value. */
511
+ raw(value: Exclude<MessageValue, string>): Output;
490
512
  }
491
513
 
492
514
  /**
493
- * Render a rich-formatted message into a concrete output using a renderer.
515
+ * Render a rich message value into a concrete output using the given renderer.
494
516
  *
495
- * This function orchestrates the full rich message pipeline:
517
+ * This function is the main entry point of the rich message pipeline.
518
+ * It orchestrates the full flow from message value to rendered output:
496
519
  *
497
- * - message (string)
498
- * - tokenize
499
- * - build AST
500
- * - render via provided renderer
520
+ * - MessageValue
521
+ * - parse into semantic AST
522
+ * - render AST via the provided renderer
501
523
  *
502
- * All rendering behavior is defined by the given renderer, making this
503
- * function environment-agnostic (string, DOM, React, etc.).
524
+ * All rendering behavior is delegated to the renderer, making this function
525
+ * fully environment-agnostic (e.g. string, DOM, React).
504
526
  */
505
- declare function renderRichMessage<Output>(message: string, renderer: Renderer<Output>): Output[];
527
+ declare function renderRichMessage<Output>(message: MessageValue, renderer: Renderer<Output>): Output[];
506
528
 
507
- export { type ASTNode, type Attributes, type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedMessagesUnion, type LocalizedNodeKeys, type MessageObject, type MessageValue, type MissingHandler, type NodeKeys, type Renderer, type Replacement, type ScopedLeafKeys, type TranslateConfig, type TranslateContext, type TranslateHandlers, type TranslateHook, ScopeTranslator as Translator, type ScopeTranslatorMethods as TranslatorMethods, type ScopeTranslatorOptions as TranslatorOptions, type TranslatorPlugin, parseRichMessage, renderRichMessage };
529
+ 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 };
package/dist/index.js CHANGED
@@ -119,7 +119,7 @@ var loading = rura.createHook(
119
119
  };
120
120
  }
121
121
  const { loadingMessage } = config;
122
- if ("loadingMessage" in config) {
122
+ if (loadingMessage !== void 0) {
123
123
  return { early: true, output: loadingMessage };
124
124
  }
125
125
  },
@@ -138,7 +138,7 @@ var missing = rura.createHook(
138
138
  };
139
139
  }
140
140
  const { missingMessage } = config;
141
- if ("missingMessage" in config) {
141
+ if (missingMessage !== void 0) {
142
142
  return { early: true, output: missingMessage };
143
143
  }
144
144
  return { early: true, output: key };
@@ -174,6 +174,7 @@ var DEFAULT_HOOKS = [
174
174
  format,
175
175
  interpolate
176
176
  ];
177
+ rura.createPipeline(DEFAULT_HOOKS).debugHooks();
177
178
 
178
179
  // src/translators/base-translator/base-translator.ts
179
180
  var BaseTranslator = class {
@@ -248,12 +249,18 @@ function runTranslate(options) {
248
249
  function translate(options) {
249
250
  const { early, ctx, output } = runTranslate(options);
250
251
  if (early === true) return output;
252
+ if (ctx.finalMessage === void 0) {
253
+ throw new Error("Invariant violated: missing hook did not produce output");
254
+ }
251
255
  return ctx.finalMessage;
252
256
  }
253
257
 
254
258
  // src/translators/methods/translate-raw.ts
255
259
  function translateRaw(options) {
256
260
  const { ctx } = runTranslate(options);
261
+ if (ctx.rawValue === void 0) {
262
+ throw new Error("Invariant violated: missing hook did not produce output");
263
+ }
257
264
  return ctx.rawValue;
258
265
  }
259
266
 
@@ -500,18 +507,44 @@ function buildAST(tokens) {
500
507
 
501
508
  // src/message/parse-rich-message.ts
502
509
  function parseRichMessage(message) {
503
- const tokens = tokenize(message);
504
- return buildAST(tokens);
510
+ if (message == null) return [];
511
+ if (typeof message === "string") {
512
+ const tokens = tokenize(message);
513
+ return buildAST(tokens);
514
+ }
515
+ if (typeof message === "number" || typeof message === "boolean") {
516
+ const tokens = tokenize(String(message));
517
+ return buildAST(tokens);
518
+ }
519
+ if (Array.isArray(message)) {
520
+ return message.flatMap((m) => parseRichMessage(m));
521
+ }
522
+ return [
523
+ {
524
+ type: "raw",
525
+ value: message
526
+ }
527
+ ];
505
528
  }
506
529
 
507
530
  // src/message/render/render.ts
508
531
  function render(nodes, renderer) {
509
532
  return nodes.map((node) => {
510
- if (node.type === "text") {
511
- return renderer.text(node.value);
533
+ switch (node.type) {
534
+ // Plain text node
535
+ case "text": {
536
+ return renderer.text(node.value);
537
+ }
538
+ // Semantic tag node
539
+ case "tag": {
540
+ const children = render(node.children, renderer);
541
+ return renderer.tag(node.name, node.attributes, children);
542
+ }
543
+ // Raw message value
544
+ case "raw": {
545
+ return renderer.raw(node.value);
546
+ }
512
547
  }
513
- const children = render(node.children, renderer);
514
- return renderer.tag(node.name, node.attributes, children);
515
548
  });
516
549
  }
517
550
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intor-translator",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "🤖 A modern, type-safe i18n engine.",
5
5
  "author": {
6
6
  "name": "Yiming Liao",
@@ -62,7 +62,7 @@
62
62
  "examples:html:rich-message": "vite --config examples/html/rich-message/vite.config.ts"
63
63
  },
64
64
  "dependencies": {
65
- "rura": "1.0.7"
65
+ "rura": "^1.0.8"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "24.10.1",