intor-translator 1.2.2 → 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 +40 -7
- package/dist/index.d.ts +40 -7
- package/dist/index.js +136 -14
- package/package.json +16 -8
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
|
@@ -188,7 +188,7 @@ type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never
|
|
|
188
188
|
* LocalizedNodeKeys // → string
|
|
189
189
|
* ```
|
|
190
190
|
*/
|
|
191
|
-
type LocalizedNodeKeys<M
|
|
191
|
+
type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? L extends "union" ? NodeKeys<M[keyof M], D> : NodeKeys<M[Extract<L, keyof M>], D> : string;
|
|
192
192
|
/**
|
|
193
193
|
* Extracts all **leaf keys** from the messages
|
|
194
194
|
* of a specified locale (or union of locales).
|
|
@@ -211,7 +211,7 @@ type LocalizedNodeKeys<M extends LocaleMessages | undefined = undefined, L exten
|
|
|
211
211
|
* LocalizedLeafKeys // → string
|
|
212
212
|
* ```
|
|
213
213
|
*/
|
|
214
|
-
type LocalizedLeafKeys<M
|
|
214
|
+
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;
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
217
|
* Resolves the type at a dot-separated key in a nested object.
|
|
@@ -351,7 +351,7 @@ interface BaseTranslatorOptions<M = unknown> {
|
|
|
351
351
|
*
|
|
352
352
|
* @template M - Shape of the messages object.
|
|
353
353
|
*/
|
|
354
|
-
declare class BaseTranslator<M extends LocaleMessages> {
|
|
354
|
+
declare class BaseTranslator<M extends LocaleMessages | unknown = unknown> {
|
|
355
355
|
/** Current messages for translation */
|
|
356
356
|
protected _messages: Readonly<M>;
|
|
357
357
|
/** Current active locale */
|
|
@@ -400,7 +400,7 @@ interface TranslatorPlugin {
|
|
|
400
400
|
* @template M - Shape of the messages object.
|
|
401
401
|
* @template L - Locale selection strategy ("union" or specific locale keys).
|
|
402
402
|
*/
|
|
403
|
-
declare class CoreTranslator<M extends LocaleMessages, L extends keyof M | "union" = "union"> extends BaseTranslator<M> {
|
|
403
|
+
declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends BaseTranslator<M> {
|
|
404
404
|
/** User-provided options including messages, locale, and config. */
|
|
405
405
|
protected translateConfig: TranslateConfig<M>;
|
|
406
406
|
/** Active pipeline hooks applied during translation. */
|
|
@@ -419,15 +419,48 @@ declare class CoreTranslator<M extends LocaleMessages, L extends keyof M | "unio
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
|
|
422
|
-
type ScopeTranslatorMethods<M extends LocaleMessages |
|
|
422
|
+
type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union", K = LocalizedLeafKeys<M, L>> = {
|
|
423
423
|
hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
|
|
424
424
|
t: <Result = string>(key?: K, replacements?: Replacement) => Result;
|
|
425
425
|
};
|
|
426
426
|
|
|
427
|
-
declare class ScopeTranslator<M extends LocaleMessages, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
|
|
427
|
+
declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
|
|
428
428
|
constructor(options: ScopeTranslatorOptions<M>);
|
|
429
429
|
/** Create a scoped translator with a prefix key, providing `t` and `hasKey` for nested keys. */
|
|
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
|
@@ -188,7 +188,7 @@ type LeafKeys<M, D extends number = DefaultDepth> = [D] extends [never] ? never
|
|
|
188
188
|
* LocalizedNodeKeys // → string
|
|
189
189
|
* ```
|
|
190
190
|
*/
|
|
191
|
-
type LocalizedNodeKeys<M
|
|
191
|
+
type LocalizedNodeKeys<M = unknown, L extends keyof M | "union" = "union", D extends number = DefaultDepth> = M extends LocaleMessages ? L extends "union" ? NodeKeys<M[keyof M], D> : NodeKeys<M[Extract<L, keyof M>], D> : string;
|
|
192
192
|
/**
|
|
193
193
|
* Extracts all **leaf keys** from the messages
|
|
194
194
|
* of a specified locale (or union of locales).
|
|
@@ -211,7 +211,7 @@ type LocalizedNodeKeys<M extends LocaleMessages | undefined = undefined, L exten
|
|
|
211
211
|
* LocalizedLeafKeys // → string
|
|
212
212
|
* ```
|
|
213
213
|
*/
|
|
214
|
-
type LocalizedLeafKeys<M
|
|
214
|
+
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;
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
217
|
* Resolves the type at a dot-separated key in a nested object.
|
|
@@ -351,7 +351,7 @@ interface BaseTranslatorOptions<M = unknown> {
|
|
|
351
351
|
*
|
|
352
352
|
* @template M - Shape of the messages object.
|
|
353
353
|
*/
|
|
354
|
-
declare class BaseTranslator<M extends LocaleMessages> {
|
|
354
|
+
declare class BaseTranslator<M extends LocaleMessages | unknown = unknown> {
|
|
355
355
|
/** Current messages for translation */
|
|
356
356
|
protected _messages: Readonly<M>;
|
|
357
357
|
/** Current active locale */
|
|
@@ -400,7 +400,7 @@ interface TranslatorPlugin {
|
|
|
400
400
|
* @template M - Shape of the messages object.
|
|
401
401
|
* @template L - Locale selection strategy ("union" or specific locale keys).
|
|
402
402
|
*/
|
|
403
|
-
declare class CoreTranslator<M extends LocaleMessages, L extends keyof M | "union" = "union"> extends BaseTranslator<M> {
|
|
403
|
+
declare class CoreTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends BaseTranslator<M> {
|
|
404
404
|
/** User-provided options including messages, locale, and config. */
|
|
405
405
|
protected translateConfig: TranslateConfig<M>;
|
|
406
406
|
/** Active pipeline hooks applied during translation. */
|
|
@@ -419,15 +419,48 @@ declare class CoreTranslator<M extends LocaleMessages, L extends keyof M | "unio
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
type ScopeTranslatorOptions<M> = CoreTranslatorOptions<M>;
|
|
422
|
-
type ScopeTranslatorMethods<M extends LocaleMessages |
|
|
422
|
+
type ScopeTranslatorMethods<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union", K = LocalizedLeafKeys<M, L>> = {
|
|
423
423
|
hasKey: (key?: K, targetLocale?: Locale<M>) => boolean;
|
|
424
424
|
t: <Result = string>(key?: K, replacements?: Replacement) => Result;
|
|
425
425
|
};
|
|
426
426
|
|
|
427
|
-
declare class ScopeTranslator<M extends LocaleMessages, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
|
|
427
|
+
declare class ScopeTranslator<M extends LocaleMessages | unknown = unknown, L extends keyof M | "union" = "union"> extends CoreTranslator<M> {
|
|
428
428
|
constructor(options: ScopeTranslatorOptions<M>);
|
|
429
429
|
/** Create a scoped translator with a prefix key, providing `t` and `hasKey` for nested keys. */
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intor-translator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "🤖 A modern, type-safe i18n engine.",
|
|
5
|
-
"author":
|
|
6
|
-
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Yiming Liao",
|
|
7
|
+
"email": "yimingliao.official@gmail.com",
|
|
8
|
+
"url": "https://github.com/yiming-liao"
|
|
9
|
+
},
|
|
7
10
|
"homepage": "https://github.com/yiming-liao/intor-translator#readme",
|
|
8
11
|
"repository": {
|
|
9
12
|
"type": "git",
|
|
@@ -12,6 +15,10 @@
|
|
|
12
15
|
"bugs": {
|
|
13
16
|
"url": "https://github.com/yiming-liao/intor-translator/issues"
|
|
14
17
|
},
|
|
18
|
+
"funding": {
|
|
19
|
+
"type": "github",
|
|
20
|
+
"url": "https://github.com/sponsors/yiming-liao"
|
|
21
|
+
},
|
|
15
22
|
"keywords": [
|
|
16
23
|
"i18n",
|
|
17
24
|
"internationalization",
|
|
@@ -21,6 +28,8 @@
|
|
|
21
28
|
"i18n engine",
|
|
22
29
|
"translator"
|
|
23
30
|
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"type": "module",
|
|
24
33
|
"exports": {
|
|
25
34
|
".": {
|
|
26
35
|
"types": "./dist/index.d.ts",
|
|
@@ -36,7 +45,10 @@
|
|
|
36
45
|
"README.md",
|
|
37
46
|
"LICENSE"
|
|
38
47
|
],
|
|
39
|
-
"
|
|
48
|
+
"sideEffects": false,
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=16.0.0"
|
|
51
|
+
},
|
|
40
52
|
"scripts": {
|
|
41
53
|
"build": "tsup",
|
|
42
54
|
"prepublishOnly": "yarn build",
|
|
@@ -47,10 +59,6 @@
|
|
|
47
59
|
"knip": "knip --config .config/knip.config.ts",
|
|
48
60
|
"examples:html": "vite --config examples/html/vite.config.ts"
|
|
49
61
|
},
|
|
50
|
-
"sideEffects": false,
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=16.0.0"
|
|
53
|
-
},
|
|
54
62
|
"dependencies": {
|
|
55
63
|
"rura": "1.0.7"
|
|
56
64
|
},
|