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 +41 -8
- package/dist/index.d.cts +74 -52
- package/dist/index.d.ts +74 -52
- package/dist/index.js +41 -8
- package/package.json +2 -2
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 (
|
|
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 (
|
|
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
|
-
|
|
506
|
-
|
|
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
|
-
|
|
513
|
-
|
|
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
|
|
297
|
+
type LoadingHandler = (ctx: HandlerContext) => MessageValue;
|
|
280
298
|
/** Function called when no message is found for the given key. */
|
|
281
|
-
type MissingHandler
|
|
299
|
+
type MissingHandler = (ctx: HandlerContext) => MessageValue;
|
|
282
300
|
/** Function to format a resolved message. */
|
|
283
|
-
type FormatHandler
|
|
301
|
+
type FormatHandler = (ctx: HandlerContext & {
|
|
284
302
|
rawString: string;
|
|
285
|
-
}) =>
|
|
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
|
|
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?:
|
|
337
|
+
formattedMessage?: MessageValue;
|
|
322
338
|
/** Final value produced by the pipeline */
|
|
323
|
-
finalMessage?:
|
|
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: <
|
|
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) =>
|
|
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: <
|
|
432
|
-
tRaw: (key?:
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
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
|
|
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:
|
|
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
|
|
515
|
+
* Render a rich message value into a concrete output using the given renderer.
|
|
494
516
|
*
|
|
495
|
-
* This function
|
|
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
|
-
* -
|
|
498
|
-
* -
|
|
499
|
-
* -
|
|
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
|
|
503
|
-
*
|
|
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:
|
|
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
|
|
297
|
+
type LoadingHandler = (ctx: HandlerContext) => MessageValue;
|
|
280
298
|
/** Function called when no message is found for the given key. */
|
|
281
|
-
type MissingHandler
|
|
299
|
+
type MissingHandler = (ctx: HandlerContext) => MessageValue;
|
|
282
300
|
/** Function to format a resolved message. */
|
|
283
|
-
type FormatHandler
|
|
301
|
+
type FormatHandler = (ctx: HandlerContext & {
|
|
284
302
|
rawString: string;
|
|
285
|
-
}) =>
|
|
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
|
|
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?:
|
|
337
|
+
formattedMessage?: MessageValue;
|
|
322
338
|
/** Final value produced by the pipeline */
|
|
323
|
-
finalMessage?:
|
|
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: <
|
|
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) =>
|
|
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: <
|
|
432
|
-
tRaw: (key?:
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
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
|
|
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:
|
|
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
|
|
515
|
+
* Render a rich message value into a concrete output using the given renderer.
|
|
494
516
|
*
|
|
495
|
-
* This function
|
|
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
|
-
* -
|
|
498
|
-
* -
|
|
499
|
-
* -
|
|
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
|
|
503
|
-
*
|
|
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:
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
504
|
-
|
|
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
|
-
|
|
511
|
-
|
|
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.
|
|
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.
|
|
65
|
+
"rura": "^1.0.8"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "24.10.1",
|