intor-translator 1.2.3 → 1.2.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 +136 -13
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +136 -14
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ var rura = require('rura');
|
|
|
4
4
|
|
|
5
5
|
// src/translators/core-translator/core-translator.ts
|
|
6
6
|
|
|
7
|
-
// src/
|
|
7
|
+
// src/shared/utils/find-message-in-locales.ts
|
|
8
8
|
var findMessageInLocales = ({
|
|
9
9
|
messages,
|
|
10
10
|
candidateLocales,
|
|
@@ -43,13 +43,13 @@ var findMessage = rura.rura.createHook(
|
|
|
43
43
|
// src/pipeline/utils/make-handler-context.ts
|
|
44
44
|
function makeHandlerContext(ctx) {
|
|
45
45
|
return Object.freeze({
|
|
46
|
+
config: ctx.config,
|
|
47
|
+
messages: ctx.messages,
|
|
46
48
|
locale: ctx.locale,
|
|
49
|
+
isLoading: ctx.isLoading,
|
|
47
50
|
key: ctx.key,
|
|
48
51
|
replacements: ctx.replacements,
|
|
49
|
-
messages: ctx.messages,
|
|
50
52
|
candidateLocales: ctx.candidateLocales,
|
|
51
|
-
config: ctx.config,
|
|
52
|
-
isLoading: ctx.isLoading,
|
|
53
53
|
rawMessage: ctx.rawMessage,
|
|
54
54
|
formattedMessage: ctx.formattedMessage,
|
|
55
55
|
meta: ctx.meta
|
|
@@ -70,7 +70,7 @@ var format = rura.rura.createHook(
|
|
|
70
70
|
500
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
-
// src/
|
|
73
|
+
// src/pipeline/hooks/interpolate/replace-values.ts
|
|
74
74
|
var replaceValues = (message, params) => {
|
|
75
75
|
if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
|
|
76
76
|
return message;
|
|
@@ -89,7 +89,7 @@ var replaceValues = (message, params) => {
|
|
|
89
89
|
return replaced;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
// src/pipeline/hooks/interpolate.ts
|
|
92
|
+
// src/pipeline/hooks/interpolate/interpolate.ts
|
|
93
93
|
var interpolate = rura.rura.createHook(
|
|
94
94
|
"interpolate",
|
|
95
95
|
(ctx) => {
|
|
@@ -143,7 +143,7 @@ var missing = rura.rura.createHook(
|
|
|
143
143
|
400
|
|
144
144
|
);
|
|
145
145
|
|
|
146
|
-
// src/
|
|
146
|
+
// src/shared/utils/resolve-candidate-locales.ts
|
|
147
147
|
var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
|
|
148
148
|
const fallbacks = fallbackLocalesMap?.[locale] || [];
|
|
149
149
|
const filteredFallbacks = fallbacks.filter((l) => l !== locale);
|
|
@@ -220,7 +220,7 @@ var BaseTranslator = class {
|
|
|
220
220
|
}
|
|
221
221
|
};
|
|
222
222
|
|
|
223
|
-
// src/translators/
|
|
223
|
+
// src/translators/methods/has-key.ts
|
|
224
224
|
var hasKey = ({
|
|
225
225
|
messages,
|
|
226
226
|
locale,
|
|
@@ -228,11 +228,7 @@ var hasKey = ({
|
|
|
228
228
|
targetLocale
|
|
229
229
|
}) => {
|
|
230
230
|
const candidateLocales = resolveCandidateLocales(targetLocale || locale);
|
|
231
|
-
const message = findMessageInLocales({
|
|
232
|
-
messages,
|
|
233
|
-
candidateLocales,
|
|
234
|
-
key
|
|
235
|
-
});
|
|
231
|
+
const message = findMessageInLocales({ messages, candidateLocales, key });
|
|
236
232
|
return !!message;
|
|
237
233
|
};
|
|
238
234
|
function translate(options) {
|
|
@@ -344,4 +340,131 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
344
340
|
}
|
|
345
341
|
};
|
|
346
342
|
|
|
343
|
+
// src/message/tokenize/parse-attributes.ts
|
|
344
|
+
var ATTR_REGEX = /\s+([a-zA-Z_][a-zA-Z0-9_]*)="([^"]*)"/g;
|
|
345
|
+
var parseAttributes = (input) => {
|
|
346
|
+
const attributes = {};
|
|
347
|
+
let match;
|
|
348
|
+
let consumed = "";
|
|
349
|
+
while (match = ATTR_REGEX.exec(input)) {
|
|
350
|
+
const [, key, value] = match;
|
|
351
|
+
attributes[key] = value;
|
|
352
|
+
consumed += match[0];
|
|
353
|
+
}
|
|
354
|
+
if (consumed.length !== input.length) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
return attributes;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// src/message/tokenize/tokenize.ts
|
|
361
|
+
var OPEN_TAG_REGEX = /^<([a-zA-Z0-9_]+)([^>]*)>/;
|
|
362
|
+
var CLOSE_TAG_REGEX = /^<\/([a-zA-Z0-9_]+)>/;
|
|
363
|
+
var tokenize = (message) => {
|
|
364
|
+
const tokens = [];
|
|
365
|
+
let pos = 0;
|
|
366
|
+
let buffer = "";
|
|
367
|
+
const flushText = () => {
|
|
368
|
+
if (!buffer) return;
|
|
369
|
+
tokens.push({
|
|
370
|
+
type: "text",
|
|
371
|
+
value: buffer,
|
|
372
|
+
position: pos - buffer.length
|
|
373
|
+
});
|
|
374
|
+
buffer = "";
|
|
375
|
+
};
|
|
376
|
+
while (pos < message.length) {
|
|
377
|
+
const char = message[pos];
|
|
378
|
+
if (char === "<") {
|
|
379
|
+
const openMatch = message.slice(pos).match(OPEN_TAG_REGEX);
|
|
380
|
+
if (openMatch) {
|
|
381
|
+
const [, name, rawAttributes] = openMatch;
|
|
382
|
+
const attributes = parseAttributes(rawAttributes);
|
|
383
|
+
if (attributes) {
|
|
384
|
+
flushText();
|
|
385
|
+
tokens.push({
|
|
386
|
+
type: "tag-open",
|
|
387
|
+
name,
|
|
388
|
+
attributes,
|
|
389
|
+
position: pos
|
|
390
|
+
});
|
|
391
|
+
pos += openMatch[0].length;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const closeMatch = message.slice(pos).match(CLOSE_TAG_REGEX);
|
|
396
|
+
if (closeMatch) {
|
|
397
|
+
flushText();
|
|
398
|
+
tokens.push({
|
|
399
|
+
type: "tag-close",
|
|
400
|
+
name: closeMatch[1],
|
|
401
|
+
position: pos
|
|
402
|
+
});
|
|
403
|
+
pos += closeMatch[0].length;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
buffer += char;
|
|
408
|
+
pos += 1;
|
|
409
|
+
}
|
|
410
|
+
flushText();
|
|
411
|
+
return tokens;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/message/build-ast/build-ast.ts
|
|
415
|
+
function buildAST(tokens) {
|
|
416
|
+
const root = [];
|
|
417
|
+
const stack = [];
|
|
418
|
+
const pushNode = (node) => {
|
|
419
|
+
const parent = stack.at(-1);
|
|
420
|
+
if (parent) {
|
|
421
|
+
parent.children.push(node);
|
|
422
|
+
} else {
|
|
423
|
+
root.push(node);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
for (const token of tokens) {
|
|
427
|
+
switch (token.type) {
|
|
428
|
+
case "text": {
|
|
429
|
+
pushNode({
|
|
430
|
+
type: "text",
|
|
431
|
+
value: token.value
|
|
432
|
+
});
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case "tag-open": {
|
|
436
|
+
const node = {
|
|
437
|
+
type: "tag",
|
|
438
|
+
name: token.name,
|
|
439
|
+
attributes: token.attributes,
|
|
440
|
+
children: []
|
|
441
|
+
};
|
|
442
|
+
pushNode(node);
|
|
443
|
+
stack.push(node);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case "tag-close": {
|
|
447
|
+
const last = stack.pop();
|
|
448
|
+
if (!last || last.name !== token.name) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`Unmatched closing tag </${token.name}> at position ${token.position}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (stack.length > 0) {
|
|
458
|
+
throw new Error(`Unclosed tag detected: <${stack.at(-1)?.name}>`);
|
|
459
|
+
}
|
|
460
|
+
return root;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/message/parse-rich-message.ts
|
|
464
|
+
function parseRichMessage(message) {
|
|
465
|
+
const tokens = tokenize(message);
|
|
466
|
+
return buildAST(tokens);
|
|
467
|
+
}
|
|
468
|
+
|
|
347
469
|
exports.Translator = ScopeTranslator;
|
|
470
|
+
exports.parseRichMessage = parseRichMessage;
|
package/dist/index.d.cts
CHANGED
|
@@ -430,4 +430,37 @@ declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L ex
|
|
|
430
430
|
scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, ScopedLeafKeys<M, PK, L>> : ScopeTranslatorMethods<M, L>;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
/** Parsed tag attributes map. */
|
|
434
|
+
type Attributes = Record<string, string>;
|
|
435
|
+
|
|
436
|
+
/** Semantic node produced by the AST builder. */
|
|
437
|
+
type ASTNode = TextNode | TagNode;
|
|
438
|
+
/** Plain text node in the semantic AST. */
|
|
439
|
+
interface TextNode {
|
|
440
|
+
type: "text";
|
|
441
|
+
value: string;
|
|
442
|
+
}
|
|
443
|
+
/** Semantic tag node with attributes and nested children. */
|
|
444
|
+
interface TagNode {
|
|
445
|
+
type: "tag";
|
|
446
|
+
name: string;
|
|
447
|
+
attributes: Attributes;
|
|
448
|
+
children: ASTNode[];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Parse a rich-formatted message string into a semantic AST.
|
|
453
|
+
*
|
|
454
|
+
* This is the high-level entry point for processing translated messages
|
|
455
|
+
* that contain semantic tags (e.g. <b>, <a>, <i>).
|
|
456
|
+
*
|
|
457
|
+
* Internally, this function:
|
|
458
|
+
* - Tokenizes the input string into semantic tokens
|
|
459
|
+
* - Builds a nested AST from the token stream
|
|
460
|
+
*
|
|
461
|
+
* Consumers should treat the returned AST as a semantic structure
|
|
462
|
+
* suitable for rendering or further processing.
|
|
463
|
+
*/
|
|
464
|
+
declare function parseRichMessage(message: string): ASTNode[];
|
|
465
|
+
|
|
466
|
+
export { type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedMessagesUnion, type LocalizedNodeKeys, type MissingHandler, type NestedMessage, type NodeKeys, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -430,4 +430,37 @@ declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L ex
|
|
|
430
430
|
scoped<PK extends LocalizedNodeKeys<M, L> | undefined = undefined>(preKey?: PK): PK extends string ? ScopeTranslatorMethods<M, L, ScopedLeafKeys<M, PK, L>> : ScopeTranslatorMethods<M, L>;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
/** Parsed tag attributes map. */
|
|
434
|
+
type Attributes = Record<string, string>;
|
|
435
|
+
|
|
436
|
+
/** Semantic node produced by the AST builder. */
|
|
437
|
+
type ASTNode = TextNode | TagNode;
|
|
438
|
+
/** Plain text node in the semantic AST. */
|
|
439
|
+
interface TextNode {
|
|
440
|
+
type: "text";
|
|
441
|
+
value: string;
|
|
442
|
+
}
|
|
443
|
+
/** Semantic tag node with attributes and nested children. */
|
|
444
|
+
interface TagNode {
|
|
445
|
+
type: "tag";
|
|
446
|
+
name: string;
|
|
447
|
+
attributes: Attributes;
|
|
448
|
+
children: ASTNode[];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Parse a rich-formatted message string into a semantic AST.
|
|
453
|
+
*
|
|
454
|
+
* This is the high-level entry point for processing translated messages
|
|
455
|
+
* that contain semantic tags (e.g. <b>, <a>, <i>).
|
|
456
|
+
*
|
|
457
|
+
* Internally, this function:
|
|
458
|
+
* - Tokenizes the input string into semantic tokens
|
|
459
|
+
* - Builds a nested AST from the token stream
|
|
460
|
+
*
|
|
461
|
+
* Consumers should treat the returned AST as a semantic structure
|
|
462
|
+
* suitable for rendering or further processing.
|
|
463
|
+
*/
|
|
464
|
+
declare function parseRichMessage(message: string): ASTNode[];
|
|
465
|
+
|
|
466
|
+
export { type DefaultDepth, type FallbackLocalesMap, type FormatHandler, type HandlerContext, type LeafKeys, type LoadingHandler, type Locale, type LocaleMessages, type LocalizedLeafKeys, type LocalizedMessagesUnion, type LocalizedNodeKeys, type MissingHandler, type NestedMessage, type NodeKeys, 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 };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { rura } from 'rura';
|
|
|
2
2
|
|
|
3
3
|
// src/translators/core-translator/core-translator.ts
|
|
4
4
|
|
|
5
|
-
// src/
|
|
5
|
+
// src/shared/utils/find-message-in-locales.ts
|
|
6
6
|
var findMessageInLocales = ({
|
|
7
7
|
messages,
|
|
8
8
|
candidateLocales,
|
|
@@ -41,13 +41,13 @@ var findMessage = rura.createHook(
|
|
|
41
41
|
// src/pipeline/utils/make-handler-context.ts
|
|
42
42
|
function makeHandlerContext(ctx) {
|
|
43
43
|
return Object.freeze({
|
|
44
|
+
config: ctx.config,
|
|
45
|
+
messages: ctx.messages,
|
|
44
46
|
locale: ctx.locale,
|
|
47
|
+
isLoading: ctx.isLoading,
|
|
45
48
|
key: ctx.key,
|
|
46
49
|
replacements: ctx.replacements,
|
|
47
|
-
messages: ctx.messages,
|
|
48
50
|
candidateLocales: ctx.candidateLocales,
|
|
49
|
-
config: ctx.config,
|
|
50
|
-
isLoading: ctx.isLoading,
|
|
51
51
|
rawMessage: ctx.rawMessage,
|
|
52
52
|
formattedMessage: ctx.formattedMessage,
|
|
53
53
|
meta: ctx.meta
|
|
@@ -68,7 +68,7 @@ var format = rura.createHook(
|
|
|
68
68
|
500
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
-
// src/
|
|
71
|
+
// src/pipeline/hooks/interpolate/replace-values.ts
|
|
72
72
|
var replaceValues = (message, params) => {
|
|
73
73
|
if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
|
|
74
74
|
return message;
|
|
@@ -87,7 +87,7 @@ var replaceValues = (message, params) => {
|
|
|
87
87
|
return replaced;
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
// src/pipeline/hooks/interpolate.ts
|
|
90
|
+
// src/pipeline/hooks/interpolate/interpolate.ts
|
|
91
91
|
var interpolate = rura.createHook(
|
|
92
92
|
"interpolate",
|
|
93
93
|
(ctx) => {
|
|
@@ -141,7 +141,7 @@ var missing = rura.createHook(
|
|
|
141
141
|
400
|
|
142
142
|
);
|
|
143
143
|
|
|
144
|
-
// src/
|
|
144
|
+
// src/shared/utils/resolve-candidate-locales.ts
|
|
145
145
|
var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
|
|
146
146
|
const fallbacks = fallbackLocalesMap?.[locale] || [];
|
|
147
147
|
const filteredFallbacks = fallbacks.filter((l) => l !== locale);
|
|
@@ -218,7 +218,7 @@ var BaseTranslator = class {
|
|
|
218
218
|
}
|
|
219
219
|
};
|
|
220
220
|
|
|
221
|
-
// src/translators/
|
|
221
|
+
// src/translators/methods/has-key.ts
|
|
222
222
|
var hasKey = ({
|
|
223
223
|
messages,
|
|
224
224
|
locale,
|
|
@@ -226,11 +226,7 @@ var hasKey = ({
|
|
|
226
226
|
targetLocale
|
|
227
227
|
}) => {
|
|
228
228
|
const candidateLocales = resolveCandidateLocales(targetLocale || locale);
|
|
229
|
-
const message = findMessageInLocales({
|
|
230
|
-
messages,
|
|
231
|
-
candidateLocales,
|
|
232
|
-
key
|
|
233
|
-
});
|
|
229
|
+
const message = findMessageInLocales({ messages, candidateLocales, key });
|
|
234
230
|
return !!message;
|
|
235
231
|
};
|
|
236
232
|
function translate(options) {
|
|
@@ -342,4 +338,130 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
342
338
|
}
|
|
343
339
|
};
|
|
344
340
|
|
|
345
|
-
|
|
341
|
+
// src/message/tokenize/parse-attributes.ts
|
|
342
|
+
var ATTR_REGEX = /\s+([a-zA-Z_][a-zA-Z0-9_]*)="([^"]*)"/g;
|
|
343
|
+
var parseAttributes = (input) => {
|
|
344
|
+
const attributes = {};
|
|
345
|
+
let match;
|
|
346
|
+
let consumed = "";
|
|
347
|
+
while (match = ATTR_REGEX.exec(input)) {
|
|
348
|
+
const [, key, value] = match;
|
|
349
|
+
attributes[key] = value;
|
|
350
|
+
consumed += match[0];
|
|
351
|
+
}
|
|
352
|
+
if (consumed.length !== input.length) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
return attributes;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// src/message/tokenize/tokenize.ts
|
|
359
|
+
var OPEN_TAG_REGEX = /^<([a-zA-Z0-9_]+)([^>]*)>/;
|
|
360
|
+
var CLOSE_TAG_REGEX = /^<\/([a-zA-Z0-9_]+)>/;
|
|
361
|
+
var tokenize = (message) => {
|
|
362
|
+
const tokens = [];
|
|
363
|
+
let pos = 0;
|
|
364
|
+
let buffer = "";
|
|
365
|
+
const flushText = () => {
|
|
366
|
+
if (!buffer) return;
|
|
367
|
+
tokens.push({
|
|
368
|
+
type: "text",
|
|
369
|
+
value: buffer,
|
|
370
|
+
position: pos - buffer.length
|
|
371
|
+
});
|
|
372
|
+
buffer = "";
|
|
373
|
+
};
|
|
374
|
+
while (pos < message.length) {
|
|
375
|
+
const char = message[pos];
|
|
376
|
+
if (char === "<") {
|
|
377
|
+
const openMatch = message.slice(pos).match(OPEN_TAG_REGEX);
|
|
378
|
+
if (openMatch) {
|
|
379
|
+
const [, name, rawAttributes] = openMatch;
|
|
380
|
+
const attributes = parseAttributes(rawAttributes);
|
|
381
|
+
if (attributes) {
|
|
382
|
+
flushText();
|
|
383
|
+
tokens.push({
|
|
384
|
+
type: "tag-open",
|
|
385
|
+
name,
|
|
386
|
+
attributes,
|
|
387
|
+
position: pos
|
|
388
|
+
});
|
|
389
|
+
pos += openMatch[0].length;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const closeMatch = message.slice(pos).match(CLOSE_TAG_REGEX);
|
|
394
|
+
if (closeMatch) {
|
|
395
|
+
flushText();
|
|
396
|
+
tokens.push({
|
|
397
|
+
type: "tag-close",
|
|
398
|
+
name: closeMatch[1],
|
|
399
|
+
position: pos
|
|
400
|
+
});
|
|
401
|
+
pos += closeMatch[0].length;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
buffer += char;
|
|
406
|
+
pos += 1;
|
|
407
|
+
}
|
|
408
|
+
flushText();
|
|
409
|
+
return tokens;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// src/message/build-ast/build-ast.ts
|
|
413
|
+
function buildAST(tokens) {
|
|
414
|
+
const root = [];
|
|
415
|
+
const stack = [];
|
|
416
|
+
const pushNode = (node) => {
|
|
417
|
+
const parent = stack.at(-1);
|
|
418
|
+
if (parent) {
|
|
419
|
+
parent.children.push(node);
|
|
420
|
+
} else {
|
|
421
|
+
root.push(node);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
for (const token of tokens) {
|
|
425
|
+
switch (token.type) {
|
|
426
|
+
case "text": {
|
|
427
|
+
pushNode({
|
|
428
|
+
type: "text",
|
|
429
|
+
value: token.value
|
|
430
|
+
});
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case "tag-open": {
|
|
434
|
+
const node = {
|
|
435
|
+
type: "tag",
|
|
436
|
+
name: token.name,
|
|
437
|
+
attributes: token.attributes,
|
|
438
|
+
children: []
|
|
439
|
+
};
|
|
440
|
+
pushNode(node);
|
|
441
|
+
stack.push(node);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case "tag-close": {
|
|
445
|
+
const last = stack.pop();
|
|
446
|
+
if (!last || last.name !== token.name) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Unmatched closing tag </${token.name}> at position ${token.position}`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (stack.length > 0) {
|
|
456
|
+
throw new Error(`Unclosed tag detected: <${stack.at(-1)?.name}>`);
|
|
457
|
+
}
|
|
458
|
+
return root;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/message/parse-rich-message.ts
|
|
462
|
+
function parseRichMessage(message) {
|
|
463
|
+
const tokens = tokenize(message);
|
|
464
|
+
return buildAST(tokens);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export { ScopeTranslator as Translator, parseRichMessage };
|