@ng-linguo/extract 0.9.0
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/README.md +177 -0
- package/linguo.config.schema.json +53 -0
- package/package.json +38 -0
- package/src/cli.d.ts +2 -0
- package/src/cli.js +287 -0
- package/src/cli.js.map +1 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/interactive.d.ts +12 -0
- package/src/interactive.js +679 -0
- package/src/interactive.js.map +1 -0
- package/src/lib/apply.d.ts +20 -0
- package/src/lib/apply.js +43 -0
- package/src/lib/apply.js.map +1 -0
- package/src/lib/clipboard.d.ts +17 -0
- package/src/lib/clipboard.js +96 -0
- package/src/lib/clipboard.js.map +1 -0
- package/src/lib/compile.d.ts +12 -0
- package/src/lib/compile.js +29 -0
- package/src/lib/compile.js.map +1 -0
- package/src/lib/config.d.ts +104 -0
- package/src/lib/config.js +185 -0
- package/src/lib/config.js.map +1 -0
- package/src/lib/merge.d.ts +13 -0
- package/src/lib/merge.js +34 -0
- package/src/lib/merge.js.map +1 -0
- package/src/lib/normalize.d.ts +15 -0
- package/src/lib/normalize.js +21 -0
- package/src/lib/normalize.js.map +1 -0
- package/src/lib/po.d.ts +25 -0
- package/src/lib/po.js +110 -0
- package/src/lib/po.js.map +1 -0
- package/src/lib/prompt.d.ts +33 -0
- package/src/lib/prompt.js +80 -0
- package/src/lib/prompt.js.map +1 -0
- package/src/lib/runner.d.ts +62 -0
- package/src/lib/runner.js +102 -0
- package/src/lib/runner.js.map +1 -0
- package/src/lib/scan.d.ts +31 -0
- package/src/lib/scan.js +183 -0
- package/src/lib/scan.js.map +1 -0
- package/src/lib/translation-prompt.txt +214 -0
- package/src/lib/translator.d.ts +83 -0
- package/src/lib/translator.js +91 -0
- package/src/lib/translator.js.map +1 -0
package/src/lib/merge.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeCatalog = mergeCatalog;
|
|
4
|
+
// Joins context and key when matching entries (gettext EOT glue).
|
|
5
|
+
const GLUE = String.fromCharCode(4);
|
|
6
|
+
function identity(context, msgid) {
|
|
7
|
+
return context === '' ? msgid : `${context}${GLUE}${msgid}`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Merge freshly extracted messages into an existing catalog.
|
|
11
|
+
*
|
|
12
|
+
* The existing `translation` (msgstr) is preserved for entries that still appear
|
|
13
|
+
* in the source — matched by their (context, key) identity — and `references`
|
|
14
|
+
* are refreshed to the current locations. Entries no longer
|
|
15
|
+
* present are dropped; new ones are added with an empty translation. The result
|
|
16
|
+
* follows the extraction order (order of discovery), so re-extraction keeps the
|
|
17
|
+
* catalog stable and readable.
|
|
18
|
+
*/
|
|
19
|
+
function mergeCatalog(existing, extracted) {
|
|
20
|
+
const previous = new Map();
|
|
21
|
+
for (const entry of existing) {
|
|
22
|
+
previous.set(identity(entry.context, entry.msgid), entry);
|
|
23
|
+
}
|
|
24
|
+
return extracted.map((message) => {
|
|
25
|
+
const prior = previous.get(identity(message.context, message.keyId));
|
|
26
|
+
return {
|
|
27
|
+
context: message.context,
|
|
28
|
+
msgid: message.keyId,
|
|
29
|
+
msgstr: prior?.msgstr ?? '',
|
|
30
|
+
references: message.references,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/merge.ts"],"names":[],"mappings":";;AAoBA,oCAkBC;AAnCD,kEAAkE;AAClE,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AAEpC,SAAS,QAAQ,CAAC,OAAe,EAAE,KAAa;IAC9C,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAC1B,QAA4B,EAC5B,SAAsC;IAEtC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAW,EAAE;QACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,KAAK,EAAE,MAAM,IAAI,EAAE;YAC3B,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a translator-facing source message into its canonical form.
|
|
3
|
+
*
|
|
4
|
+
* Leading/trailing whitespace is trimmed and any internal run of whitespace
|
|
5
|
+
* (including newlines from multi-line templates) is collapsed to a single
|
|
6
|
+
* space. This is the build-time half of the runtime/extractor parity contract
|
|
7
|
+
* (CLAUDE.md §5.2): the runtime normalizer must produce identical output for
|
|
8
|
+
* the same input, verified against the shared fixture.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* normalizeMessage(' Hello\n world '); // 'Hello world'
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function normalizeMessage(source: string): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeMessage = normalizeMessage;
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a translator-facing source message into its canonical form.
|
|
6
|
+
*
|
|
7
|
+
* Leading/trailing whitespace is trimmed and any internal run of whitespace
|
|
8
|
+
* (including newlines from multi-line templates) is collapsed to a single
|
|
9
|
+
* space. This is the build-time half of the runtime/extractor parity contract
|
|
10
|
+
* (CLAUDE.md §5.2): the runtime normalizer must produce identical output for
|
|
11
|
+
* the same input, verified against the shared fixture.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* normalizeMessage(' Hello\n world '); // 'Hello world'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
function normalizeMessage(source) {
|
|
19
|
+
return source.trim().replace(/\s+/g, ' ');
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/normalize.ts"],"names":[],"mappings":";;AAcA,4CAEC;AAhBD;;;;;;;;;;;;;GAaG;AACH,SAAgB,gBAAgB,CAAC,MAAc;IAC7C,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC"}
|
package/src/lib/po.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single gettext `.po` catalog entry.
|
|
3
|
+
*
|
|
4
|
+
* `msgid` is the normalized source string and `msgstr` is the translation
|
|
5
|
+
* (empty until a translator fills it in). `context` is the `msgctxt` (empty when
|
|
6
|
+
* none) — it lets the same source text carry different translations and becomes
|
|
7
|
+
* part of the compiled runtime key. `references` are the `#:` source locations.
|
|
8
|
+
*/
|
|
9
|
+
export interface PoEntry {
|
|
10
|
+
readonly context: string;
|
|
11
|
+
readonly msgid: string;
|
|
12
|
+
readonly msgstr: string;
|
|
13
|
+
readonly references: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Serialize catalog entries to gettext `.po` text, including a minimal UTF-8
|
|
17
|
+
* header so standard translator tooling accepts the file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function serializePo(entries: readonly PoEntry[]): string;
|
|
20
|
+
/**
|
|
21
|
+
* Parse gettext `.po` text into catalog entries. The header entry (empty
|
|
22
|
+
* `msgid`) is dropped. Tolerant of multi-line quoted continuations and ignores
|
|
23
|
+
* comment kinds it does not use (`#`, `#,`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function parsePo(content: string): PoEntry[];
|
package/src/lib/po.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializePo = serializePo;
|
|
4
|
+
exports.parsePo = parsePo;
|
|
5
|
+
function escapePo(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/\\/g, '\\\\')
|
|
8
|
+
.replace(/"/g, '\\"')
|
|
9
|
+
.replace(/\n/g, '\\n')
|
|
10
|
+
.replace(/\t/g, '\\t');
|
|
11
|
+
}
|
|
12
|
+
function unescapePo(value) {
|
|
13
|
+
return value.replace(/\\([\\"nt])/g, (_match, char) => char === 'n' ? '\n' : char === 't' ? '\t' : char);
|
|
14
|
+
}
|
|
15
|
+
function quoted(value) {
|
|
16
|
+
const start = value.indexOf('"');
|
|
17
|
+
const end = value.lastIndexOf('"');
|
|
18
|
+
if (start < 0 || end <= start) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
return unescapePo(value.slice(start + 1, end));
|
|
22
|
+
}
|
|
23
|
+
const HEADER = ['msgid ""', 'msgstr ""', '"Content-Type: text/plain; charset=UTF-8\\n"'].join('\n');
|
|
24
|
+
/**
|
|
25
|
+
* Serialize catalog entries to gettext `.po` text, including a minimal UTF-8
|
|
26
|
+
* header so standard translator tooling accepts the file.
|
|
27
|
+
*/
|
|
28
|
+
function serializePo(entries) {
|
|
29
|
+
const blocks = [HEADER];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
for (const reference of entry.references) {
|
|
33
|
+
lines.push(`#: ${reference}`);
|
|
34
|
+
}
|
|
35
|
+
if (entry.context.length > 0) {
|
|
36
|
+
lines.push(`msgctxt "${escapePo(entry.context)}"`);
|
|
37
|
+
}
|
|
38
|
+
lines.push(`msgid "${escapePo(entry.msgid)}"`);
|
|
39
|
+
lines.push(`msgstr "${escapePo(entry.msgstr)}"`);
|
|
40
|
+
blocks.push(lines.join('\n'));
|
|
41
|
+
}
|
|
42
|
+
return `${blocks.join('\n\n')}\n`;
|
|
43
|
+
}
|
|
44
|
+
function emptyDraft() {
|
|
45
|
+
return { context: null, references: [], msgid: null, msgstr: null };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse gettext `.po` text into catalog entries. The header entry (empty
|
|
49
|
+
* `msgid`) is dropped. Tolerant of multi-line quoted continuations and ignores
|
|
50
|
+
* comment kinds it does not use (`#`, `#,`).
|
|
51
|
+
*/
|
|
52
|
+
function parsePo(content) {
|
|
53
|
+
const entries = [];
|
|
54
|
+
let draft = emptyDraft();
|
|
55
|
+
let mode = null;
|
|
56
|
+
const flush = () => {
|
|
57
|
+
if (draft.msgid !== null && draft.msgid !== '') {
|
|
58
|
+
entries.push({
|
|
59
|
+
context: draft.context ?? '',
|
|
60
|
+
msgid: draft.msgid,
|
|
61
|
+
msgstr: draft.msgstr ?? '',
|
|
62
|
+
references: draft.references,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
draft = emptyDraft();
|
|
66
|
+
mode = null;
|
|
67
|
+
};
|
|
68
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
69
|
+
const line = rawLine.trimEnd();
|
|
70
|
+
if (line.trim() === '') {
|
|
71
|
+
flush();
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (line.startsWith('#:')) {
|
|
75
|
+
draft.references.push(line.slice(2).trim());
|
|
76
|
+
mode = null;
|
|
77
|
+
}
|
|
78
|
+
else if (line.startsWith('#')) {
|
|
79
|
+
// Other comment kinds (`#.`, `#,`, translator comments) are ignored.
|
|
80
|
+
mode = null;
|
|
81
|
+
}
|
|
82
|
+
else if (line.startsWith('msgctxt ')) {
|
|
83
|
+
draft.context = quoted(line.slice('msgctxt '.length));
|
|
84
|
+
mode = 'msgctxt';
|
|
85
|
+
}
|
|
86
|
+
else if (line.startsWith('msgid ')) {
|
|
87
|
+
draft.msgid = quoted(line.slice('msgid '.length));
|
|
88
|
+
mode = 'msgid';
|
|
89
|
+
}
|
|
90
|
+
else if (line.startsWith('msgstr ')) {
|
|
91
|
+
draft.msgstr = quoted(line.slice('msgstr '.length));
|
|
92
|
+
mode = 'msgstr';
|
|
93
|
+
}
|
|
94
|
+
else if (line.startsWith('"')) {
|
|
95
|
+
const continuation = quoted(line);
|
|
96
|
+
if (mode === 'msgctxt') {
|
|
97
|
+
draft.context = (draft.context ?? '') + continuation;
|
|
98
|
+
}
|
|
99
|
+
else if (mode === 'msgid') {
|
|
100
|
+
draft.msgid = (draft.msgid ?? '') + continuation;
|
|
101
|
+
}
|
|
102
|
+
else if (mode === 'msgstr') {
|
|
103
|
+
draft.msgstr = (draft.msgstr ?? '') + continuation;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
flush();
|
|
108
|
+
return entries;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=po.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"po.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/po.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a single, self-contained prompt that instructs an LLM to translate a
|
|
3
|
+
* whole `.po` catalog. The template explains ng-linguo's concepts (context,
|
|
4
|
+
* slot tags, MessageFormat 2 plurals/selection) and is filled with the target
|
|
5
|
+
* language label and the catalog contents.
|
|
6
|
+
*
|
|
7
|
+
* @param targetLanguage human-readable label for the language to translate
|
|
8
|
+
* into, e.g. `"Polish (pl)"`.
|
|
9
|
+
* @param poContents the entire `.po` file to translate.
|
|
10
|
+
* @returns the ready-to-send prompt.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildTranslationPrompt(targetLanguage: string, poContents: string): string;
|
|
13
|
+
/** A configured locale matched from user input, with a label for the prompt. */
|
|
14
|
+
export interface ResolvedLocale {
|
|
15
|
+
/** The locale code whose `<locale>.po` catalog should be translated. */
|
|
16
|
+
readonly locale: string;
|
|
17
|
+
/** Human-readable label injected into the prompt, e.g. `"Polish (pl)"`. */
|
|
18
|
+
readonly label: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A human-readable label for a locale, e.g. `"Polish (pl)"`, used in prompts
|
|
22
|
+
* and menus. Falls back to the bare code when no display name is available.
|
|
23
|
+
*/
|
|
24
|
+
export declare function localeLabel(locale: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a user-supplied language to one of the configured locales. The input
|
|
27
|
+
* may be the locale code (`pl`), the English name (`Polish`), or the endonym —
|
|
28
|
+
* the language's own name (`Polski`) — all matched case-insensitively.
|
|
29
|
+
*
|
|
30
|
+
* @returns the matched locale and a `"<English name> (<code>)"` label, or
|
|
31
|
+
* `undefined` when nothing matches.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveTargetLocale(input: string, locales: readonly string[]): ResolvedLocale | undefined;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTranslationPrompt = buildTranslationPrompt;
|
|
4
|
+
exports.localeLabel = localeLabel;
|
|
5
|
+
exports.resolveTargetLocale = resolveTargetLocale;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
/** Placeholder replaced with the human-readable target language label. */
|
|
9
|
+
const LANGUAGE_SLOT = '{{TARGET_LANGUAGE}}';
|
|
10
|
+
/** Placeholder replaced with the full `.po` catalog contents. */
|
|
11
|
+
const PO_SLOT = '{{PO_FILE}}';
|
|
12
|
+
/**
|
|
13
|
+
* The translation prompt template, shipped as a sibling `.txt` asset so the
|
|
14
|
+
* long instructions stay readable (and editable) without escaping. Read once
|
|
15
|
+
* and cached for the process.
|
|
16
|
+
*/
|
|
17
|
+
let cachedTemplate;
|
|
18
|
+
function loadTemplate() {
|
|
19
|
+
if (cachedTemplate === undefined) {
|
|
20
|
+
cachedTemplate = (0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, 'translation-prompt.txt'), 'utf8');
|
|
21
|
+
}
|
|
22
|
+
return cachedTemplate;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a single, self-contained prompt that instructs an LLM to translate a
|
|
26
|
+
* whole `.po` catalog. The template explains ng-linguo's concepts (context,
|
|
27
|
+
* slot tags, MessageFormat 2 plurals/selection) and is filled with the target
|
|
28
|
+
* language label and the catalog contents.
|
|
29
|
+
*
|
|
30
|
+
* @param targetLanguage human-readable label for the language to translate
|
|
31
|
+
* into, e.g. `"Polish (pl)"`.
|
|
32
|
+
* @param poContents the entire `.po` file to translate.
|
|
33
|
+
* @returns the ready-to-send prompt.
|
|
34
|
+
*/
|
|
35
|
+
function buildTranslationPrompt(targetLanguage, poContents) {
|
|
36
|
+
return loadTemplate().split(LANGUAGE_SLOT).join(targetLanguage).split(PO_SLOT).join(poContents);
|
|
37
|
+
}
|
|
38
|
+
function displayName(locale, inLocale) {
|
|
39
|
+
try {
|
|
40
|
+
return new Intl.DisplayNames([inLocale], { type: 'language' }).of(locale);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Unknown locale or no ICU data — fall back to code-only matching.
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A human-readable label for a locale, e.g. `"Polish (pl)"`, used in prompts
|
|
49
|
+
* and menus. Falls back to the bare code when no display name is available.
|
|
50
|
+
*/
|
|
51
|
+
function localeLabel(locale) {
|
|
52
|
+
const english = displayName(locale, 'en');
|
|
53
|
+
return english ? `${english} (${locale})` : locale;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a user-supplied language to one of the configured locales. The input
|
|
57
|
+
* may be the locale code (`pl`), the English name (`Polish`), or the endonym —
|
|
58
|
+
* the language's own name (`Polski`) — all matched case-insensitively.
|
|
59
|
+
*
|
|
60
|
+
* @returns the matched locale and a `"<English name> (<code>)"` label, or
|
|
61
|
+
* `undefined` when nothing matches.
|
|
62
|
+
*/
|
|
63
|
+
function resolveTargetLocale(input, locales) {
|
|
64
|
+
const needle = input.trim().toLowerCase();
|
|
65
|
+
if (needle === '') {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
for (const locale of locales) {
|
|
69
|
+
const english = displayName(locale, 'en');
|
|
70
|
+
const endonym = displayName(locale, locale);
|
|
71
|
+
const matches = locale.toLowerCase() === needle ||
|
|
72
|
+
english?.toLowerCase() === needle ||
|
|
73
|
+
endonym?.toLowerCase() === needle;
|
|
74
|
+
if (matches) {
|
|
75
|
+
return { locale, label: localeLabel(locale) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/prompt.ts"],"names":[],"mappings":";;AAiCA,wDAEC;AAuBD,kCAGC;AAUD,kDAuBC;AA9FD,qCAAuC;AACvC,yCAAiC;AAEjC,0EAA0E;AAC1E,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAC5C,iEAAiE;AACjE,MAAM,OAAO,GAAG,aAAa,CAAC;AAE9B;;;;GAIG;AACH,IAAI,cAAkC,CAAC;AAEvC,SAAS,YAAY;IACnB,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,cAAc,GAAG,IAAA,sBAAY,EAAC,IAAA,gBAAI,EAAC,SAAS,EAAE,wBAAwB,CAAC,EAAE,MAAM,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,sBAAsB,CAAC,cAAsB,EAAE,UAAkB;IAC/E,OAAO,YAAY,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAClG,CAAC;AAUD,SAAS,WAAW,CAAC,MAAc,EAAE,QAAgB;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,MAAc;IACxC,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;AACrD,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CACjC,KAAa,EACb,OAA0B;IAE1B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAClB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,OAAO,GACX,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM;YAC/B,OAAO,EAAE,WAAW,EAAE,KAAK,MAAM;YACjC,OAAO,EAAE,WAAW,EAAE,KAAK,MAAM,CAAC;QAEpC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefix marking a translation that has not been provided yet. New entries in
|
|
3
|
+
* non-source locales are seeded with this followed by the source text, so the
|
|
4
|
+
* string is visible and obviously flagged in the running app until translated.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MISSING_PREFIX = "<MISSING TRANSLATION>";
|
|
7
|
+
/** Per-language summary of an extraction run. */
|
|
8
|
+
export interface LocaleExtractStats {
|
|
9
|
+
readonly locale: string;
|
|
10
|
+
/** Total entries in the catalog after the run. */
|
|
11
|
+
readonly total: number;
|
|
12
|
+
/** Entries newly added this run. */
|
|
13
|
+
readonly added: number;
|
|
14
|
+
/** Entries dropped this run (no longer in the source). */
|
|
15
|
+
readonly removed: number;
|
|
16
|
+
/** Entries still untranslated (empty or `<MISSING TRANSLATION>`-prefixed). */
|
|
17
|
+
readonly missing: number;
|
|
18
|
+
}
|
|
19
|
+
/** Summary of an extraction run across all locales. */
|
|
20
|
+
export interface ExtractStats {
|
|
21
|
+
readonly files: number;
|
|
22
|
+
readonly messages: number;
|
|
23
|
+
readonly locales: readonly LocaleExtractStats[];
|
|
24
|
+
}
|
|
25
|
+
/** Options for {@link extractToCatalogs}. */
|
|
26
|
+
export interface ExtractOptions {
|
|
27
|
+
/** Directory to scan recursively for translatable strings. */
|
|
28
|
+
readonly srcDir: string;
|
|
29
|
+
/** Directory to write `<locale>.po` catalogs into. */
|
|
30
|
+
readonly outDir: string;
|
|
31
|
+
/** Locales to create or update, e.g. `['en', 'pl']`. */
|
|
32
|
+
readonly locales: readonly string[];
|
|
33
|
+
/** Base used to make `#:` references relative. Defaults to `process.cwd()`. */
|
|
34
|
+
readonly cwd?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Locale whose new entries are seeded with the source text itself (the
|
|
37
|
+
* canonical value) instead of a `<MISSING TRANSLATION>` placeholder. Defaults
|
|
38
|
+
* to `'en'`.
|
|
39
|
+
*/
|
|
40
|
+
readonly sourceLocale?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Scan `srcDir`, then create or update one `.po` catalog per locale in
|
|
44
|
+
* `outDir`, merging with any existing catalog so translations are preserved.
|
|
45
|
+
*
|
|
46
|
+
* Newly discovered entries are seeded: the source locale gets the source text,
|
|
47
|
+
* other locales get `"<MISSING TRANSLATION> <source>"` so the string renders
|
|
48
|
+
* visibly flagged until translated. Returns per-language stats.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractToCatalogs(options: ExtractOptions): ExtractStats;
|
|
51
|
+
/** Options for {@link compileCatalogs}. */
|
|
52
|
+
export interface CompileOptions {
|
|
53
|
+
/** Directory containing `<locale>.po` catalogs. */
|
|
54
|
+
readonly poDir: string;
|
|
55
|
+
/** Directory to write `<locale>.json` runtime dictionaries into. */
|
|
56
|
+
readonly outDir: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compile every `.po` catalog in `poDir` into a runtime `<locale>.json`
|
|
60
|
+
* dictionary in `outDir` (the format the loader consumes).
|
|
61
|
+
*/
|
|
62
|
+
export declare function compileCatalogs(options: CompileOptions): void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MISSING_PREFIX = void 0;
|
|
4
|
+
exports.extractToCatalogs = extractToCatalogs;
|
|
5
|
+
exports.compileCatalogs = compileCatalogs;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const compile_1 = require("./compile");
|
|
9
|
+
const merge_1 = require("./merge");
|
|
10
|
+
const po_1 = require("./po");
|
|
11
|
+
const scan_1 = require("./scan");
|
|
12
|
+
const SCANNED_EXTENSIONS = ['.html', '.ts'];
|
|
13
|
+
/**
|
|
14
|
+
* Prefix marking a translation that has not been provided yet. New entries in
|
|
15
|
+
* non-source locales are seeded with this followed by the source text, so the
|
|
16
|
+
* string is visible and obviously flagged in the running app until translated.
|
|
17
|
+
*/
|
|
18
|
+
exports.MISSING_PREFIX = '<MISSING TRANSLATION>';
|
|
19
|
+
function walk(dir, files = []) {
|
|
20
|
+
for (const name of (0, node_fs_1.readdirSync)(dir)) {
|
|
21
|
+
if (name === 'node_modules' || name.startsWith('.')) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const full = (0, node_path_1.join)(dir, name);
|
|
25
|
+
if ((0, node_fs_1.statSync)(full).isDirectory()) {
|
|
26
|
+
walk(full, files);
|
|
27
|
+
}
|
|
28
|
+
else if (SCANNED_EXTENSIONS.some((ext) => name.endsWith(ext)) && !name.endsWith('.spec.ts')) {
|
|
29
|
+
files.push(full);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
function identity(entry) {
|
|
35
|
+
return entry.context === ''
|
|
36
|
+
? entry.msgid
|
|
37
|
+
: `${entry.context}${String.fromCharCode(4)}${entry.msgid}`;
|
|
38
|
+
}
|
|
39
|
+
function isMissing(msgstr) {
|
|
40
|
+
return msgstr === '' || msgstr.startsWith(exports.MISSING_PREFIX);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Scan `srcDir`, then create or update one `.po` catalog per locale in
|
|
44
|
+
* `outDir`, merging with any existing catalog so translations are preserved.
|
|
45
|
+
*
|
|
46
|
+
* Newly discovered entries are seeded: the source locale gets the source text,
|
|
47
|
+
* other locales get `"<MISSING TRANSLATION> <source>"` so the string renders
|
|
48
|
+
* visibly flagged until translated. Returns per-language stats.
|
|
49
|
+
*/
|
|
50
|
+
function extractToCatalogs(options) {
|
|
51
|
+
const base = options.cwd ?? process.cwd();
|
|
52
|
+
const sourceLocale = options.sourceLocale ?? 'en';
|
|
53
|
+
const files = walk(options.srcDir).map((path) => ({
|
|
54
|
+
path: (0, node_path_1.relative)(base, path).split('\\').join('/'),
|
|
55
|
+
content: (0, node_fs_1.readFileSync)(path, 'utf8'),
|
|
56
|
+
}));
|
|
57
|
+
const messages = (0, scan_1.extractMessages)(files);
|
|
58
|
+
(0, node_fs_1.mkdirSync)(options.outDir, { recursive: true });
|
|
59
|
+
const locales = [];
|
|
60
|
+
for (const locale of options.locales) {
|
|
61
|
+
const poPath = (0, node_path_1.join)(options.outDir, `${locale}.po`);
|
|
62
|
+
const existing = (0, node_fs_1.existsSync)(poPath) ? (0, po_1.parsePo)((0, node_fs_1.readFileSync)(poPath, 'utf8')) : [];
|
|
63
|
+
const existingIds = new Set(existing.map(identity));
|
|
64
|
+
const merged = (0, merge_1.mergeCatalog)(existing, messages);
|
|
65
|
+
let added = 0;
|
|
66
|
+
const seeded = merged.map((entry) => {
|
|
67
|
+
const isNew = !existingIds.has(identity(entry));
|
|
68
|
+
if (!isNew) {
|
|
69
|
+
return entry;
|
|
70
|
+
}
|
|
71
|
+
added += 1;
|
|
72
|
+
const fallback = locale === sourceLocale ? entry.msgid : `${exports.MISSING_PREFIX} ${entry.msgid}`;
|
|
73
|
+
return entry.msgstr === '' ? { ...entry, msgstr: fallback } : entry;
|
|
74
|
+
});
|
|
75
|
+
(0, node_fs_1.writeFileSync)(poPath, (0, po_1.serializePo)(seeded));
|
|
76
|
+
const mergedIds = new Set(seeded.map(identity));
|
|
77
|
+
locales.push({
|
|
78
|
+
locale,
|
|
79
|
+
total: seeded.length,
|
|
80
|
+
added,
|
|
81
|
+
removed: [...existingIds].filter((id) => !mergedIds.has(id)).length,
|
|
82
|
+
missing: seeded.filter((entry) => isMissing(entry.msgstr)).length,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return { files: files.length, messages: messages.length, locales };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compile every `.po` catalog in `poDir` into a runtime `<locale>.json`
|
|
89
|
+
* dictionary in `outDir` (the format the loader consumes).
|
|
90
|
+
*/
|
|
91
|
+
function compileCatalogs(options) {
|
|
92
|
+
(0, node_fs_1.mkdirSync)(options.outDir, { recursive: true });
|
|
93
|
+
for (const name of (0, node_fs_1.readdirSync)(options.poDir)) {
|
|
94
|
+
if (!name.endsWith('.po')) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const locale = name.slice(0, -'.po'.length);
|
|
98
|
+
const entries = (0, po_1.parsePo)((0, node_fs_1.readFileSync)((0, node_path_1.join)(options.poDir, name), 'utf8'));
|
|
99
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(options.outDir, `${locale}.json`), `${JSON.stringify((0, compile_1.compileEntries)(entries), null, 2)}\n`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A source file to scan for translatable strings. */
|
|
2
|
+
export interface SourceFile {
|
|
3
|
+
/** Path used in `#:` references, e.g. `src/app/player.html`. */
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly content: string;
|
|
6
|
+
}
|
|
7
|
+
/** A translatable string found during scanning, with its source locations. */
|
|
8
|
+
export interface ExtractedMessage {
|
|
9
|
+
/** Normalized source string — the catalog `msgid`. */
|
|
10
|
+
readonly keyId: string;
|
|
11
|
+
/** Disambiguating context (`msgctxt`), or `''` for the default entry. */
|
|
12
|
+
readonly context: string;
|
|
13
|
+
/** Sorted, de-duplicated `path:line` references. */
|
|
14
|
+
readonly references: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Scan source files for translatable strings used by the `t` pipe
|
|
18
|
+
* (`'Play' | t: { context }`), the `[t]` directive (`t="message"` with an
|
|
19
|
+
* optional `tContext`), the `t('...', { context })` helper call, and the
|
|
20
|
+
* `mark()` marker, returning one {@link ExtractedMessage} per unique
|
|
21
|
+
* (context, key) pair with all of its source references.
|
|
22
|
+
*
|
|
23
|
+
* Pure and DOM-free, so it runs in CI on plain Node (CLAUDE.md §2.1). Entries
|
|
24
|
+
* are returned in order of discovery — files in the order given, and within a
|
|
25
|
+
* file by source position — so re-extraction produces a stable, readable order.
|
|
26
|
+
*
|
|
27
|
+
* Regions marked with `linguo-ignore` comment directives are skipped, so
|
|
28
|
+
* documentation samples containing `mark(`, `'…' | t`, or `t="…"` are not
|
|
29
|
+
* scanned as real messages (see {@link ignoredRanges}).
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractMessages(files: readonly SourceFile[]): ExtractedMessage[];
|