@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.
Files changed (46) hide show
  1. package/README.md +177 -0
  2. package/linguo.config.schema.json +53 -0
  3. package/package.json +38 -0
  4. package/src/cli.d.ts +2 -0
  5. package/src/cli.js +287 -0
  6. package/src/cli.js.map +1 -0
  7. package/src/index.d.ts +10 -0
  8. package/src/index.js +18 -0
  9. package/src/index.js.map +1 -0
  10. package/src/interactive.d.ts +12 -0
  11. package/src/interactive.js +679 -0
  12. package/src/interactive.js.map +1 -0
  13. package/src/lib/apply.d.ts +20 -0
  14. package/src/lib/apply.js +43 -0
  15. package/src/lib/apply.js.map +1 -0
  16. package/src/lib/clipboard.d.ts +17 -0
  17. package/src/lib/clipboard.js +96 -0
  18. package/src/lib/clipboard.js.map +1 -0
  19. package/src/lib/compile.d.ts +12 -0
  20. package/src/lib/compile.js +29 -0
  21. package/src/lib/compile.js.map +1 -0
  22. package/src/lib/config.d.ts +104 -0
  23. package/src/lib/config.js +185 -0
  24. package/src/lib/config.js.map +1 -0
  25. package/src/lib/merge.d.ts +13 -0
  26. package/src/lib/merge.js +34 -0
  27. package/src/lib/merge.js.map +1 -0
  28. package/src/lib/normalize.d.ts +15 -0
  29. package/src/lib/normalize.js +21 -0
  30. package/src/lib/normalize.js.map +1 -0
  31. package/src/lib/po.d.ts +25 -0
  32. package/src/lib/po.js +110 -0
  33. package/src/lib/po.js.map +1 -0
  34. package/src/lib/prompt.d.ts +33 -0
  35. package/src/lib/prompt.js +80 -0
  36. package/src/lib/prompt.js.map +1 -0
  37. package/src/lib/runner.d.ts +62 -0
  38. package/src/lib/runner.js +102 -0
  39. package/src/lib/runner.js.map +1 -0
  40. package/src/lib/scan.d.ts +31 -0
  41. package/src/lib/scan.js +183 -0
  42. package/src/lib/scan.js.map +1 -0
  43. package/src/lib/translation-prompt.txt +214 -0
  44. package/src/lib/translator.d.ts +83 -0
  45. package/src/lib/translator.js +91 -0
  46. package/src/lib/translator.js.map +1 -0
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractMessages = extractMessages;
4
+ const normalize_1 = require("./normalize");
5
+ // The `t` pipe: `'Play' | t` with an optional options object (`| t: { ... }`).
6
+ // Group 2 is the key; group 3 is the options object (one level of nesting), from
7
+ // which the context is read.
8
+ const PIPE_PATTERN = /(['"])((?:\\.|(?!\1).)*?)\1\s*\|\s*t\b(?:\s*:\s*(\{(?:[^{}]|\{[^{}]*\})*\}))?/g;
9
+ // `mark('...')` — the extraction marker for messages defined outside a template
10
+ // (e.g. a component field). Not preceded by `.` so `foo.mark(...)` is ignored.
11
+ // Group 2 is the key; group 3 is the optional options object (`mark('…', { … })`,
12
+ // one level of nesting), from which the context is read.
13
+ const MARK_PATTERN = /(?<!\.)\bmark\s*\(\s*(['"])((?:\\.|(?!\1).)*?)\1(?:\s*,\s*(\{(?:[^{}]|\{[^{}]*\})*\}))?/g;
14
+ // The `t('...', { ... })` helper call (from `injectTranslate()`). The lookbehind
15
+ // keeps it from matching `obj.t(`, `$t(`, or the tail of an identifier. Group 2
16
+ // is the key; group 3 is the options object, from which the context is read.
17
+ const T_CALL_PATTERN = /(?<![\w.$])t\s*\(\s*(['"])((?:\\.|(?!\1).)*?)\1(?:\s*,\s*(\{(?:[^{}]|\{[^{}]*\})*\}))?/g;
18
+ // An element carrying the `[t]` directive via a static `t="message"` attribute.
19
+ // `(?<![\w-])t\s*=` matches a standalone `t=` attribute (not `alt`, `data-t`,
20
+ // `tParams`, `tContext`, `tFor`, nor bound `[t]="…"`). Group 2 is the message.
21
+ const DIRECTIVE_TAG_PATTERN = /<[a-zA-Z][\w-]*\b[^>]*?(?<![\w-])t\s*=\s*"(?:\\.|[^"])*"[^>]*>/g;
22
+ const DIRECTIVE_MESSAGE_PATTERN = /(?<![\w-])t\s*=\s*"((?:\\.|[^"])*)"/;
23
+ const DIRECTIVE_CONTEXT_PATTERN = /(?<![\w-])tContext\s*=\s*"((?:\\.|[^"])*)"/;
24
+ // Reads `context: '…'` out of a pipe options object.
25
+ const CONTEXT_IN_OPTIONS = /\bcontext\s*:\s*(['"])((?:\\.|(?!\1).)*?)\1/;
26
+ // Joins context and key for the dedup map (gettext EOT glue).
27
+ const GLUE = String.fromCharCode(4);
28
+ // Source-comment directives that exclude regions from extraction — for
29
+ // documentation samples whose strings would otherwise scan as real messages.
30
+ // Matched as plain substrings, so they work the same in `//`, `/* */`, and
31
+ // `<!-- -->` comments without the scanner having to understand any of them.
32
+ const IGNORE_FILE = 'linguo-ignore-file';
33
+ const IGNORE_MARKER = /linguo-ignore-(next-line|start|end)/g;
34
+ /**
35
+ * Character ranges of `content` excluded from extraction by `linguo-ignore`
36
+ * directives:
37
+ *
38
+ * - `linguo-ignore-file` anywhere — skip the whole file.
39
+ * - `linguo-ignore-start` … `linguo-ignore-end` — skip everything between them;
40
+ * an unmatched `start` skips to end of file.
41
+ * - `linguo-ignore-next-line` — skip the single line after the directive.
42
+ */
43
+ function ignoredRanges(content) {
44
+ if (content.includes(IGNORE_FILE)) {
45
+ return [{ start: 0, end: content.length }];
46
+ }
47
+ const ranges = [];
48
+ let blockStart = null;
49
+ IGNORE_MARKER.lastIndex = 0;
50
+ let match;
51
+ while ((match = IGNORE_MARKER.exec(content)) !== null) {
52
+ const kind = match[1];
53
+ if (kind === 'next-line') {
54
+ const lineEnd = content.indexOf('\n', match.index);
55
+ if (lineEnd === -1) {
56
+ continue; // directive on the last line — nothing follows to skip
57
+ }
58
+ const after = content.indexOf('\n', lineEnd + 1);
59
+ ranges.push({ start: lineEnd + 1, end: after === -1 ? content.length : after });
60
+ }
61
+ else if (kind === 'start') {
62
+ blockStart ??= match.index;
63
+ }
64
+ else if (kind === 'end' && blockStart !== null) {
65
+ ranges.push({ start: blockStart, end: match.index });
66
+ blockStart = null;
67
+ }
68
+ }
69
+ if (blockStart !== null) {
70
+ ranges.push({ start: blockStart, end: content.length });
71
+ }
72
+ return ranges;
73
+ }
74
+ function unescapeJs(value) {
75
+ return value.replace(/\\([\\'"`ntr])/g, (_match, char) => {
76
+ if (char === 'n')
77
+ return '\n';
78
+ if (char === 't')
79
+ return '\t';
80
+ if (char === 'r')
81
+ return '\r';
82
+ return char;
83
+ });
84
+ }
85
+ function lineOf(content, index) {
86
+ let line = 1;
87
+ for (let i = 0; i < index && i < content.length; i += 1) {
88
+ if (content[i] === '\n') {
89
+ line += 1;
90
+ }
91
+ }
92
+ return line;
93
+ }
94
+ function record(into, found, reference) {
95
+ if (found.keyId === '') {
96
+ return;
97
+ }
98
+ const id = found.context === '' ? found.keyId : `${found.context}${GLUE}${found.keyId}`;
99
+ const occurrence = into.get(id) ?? {
100
+ keyId: found.keyId,
101
+ context: found.context,
102
+ refs: new Set(),
103
+ };
104
+ occurrence.refs.add(reference);
105
+ into.set(id, occurrence);
106
+ }
107
+ /**
108
+ * Scan source files for translatable strings used by the `t` pipe
109
+ * (`'Play' | t: { context }`), the `[t]` directive (`t="message"` with an
110
+ * optional `tContext`), the `t('...', { context })` helper call, and the
111
+ * `mark()` marker, returning one {@link ExtractedMessage} per unique
112
+ * (context, key) pair with all of its source references.
113
+ *
114
+ * Pure and DOM-free, so it runs in CI on plain Node (CLAUDE.md §2.1). Entries
115
+ * are returned in order of discovery — files in the order given, and within a
116
+ * file by source position — so re-extraction produces a stable, readable order.
117
+ *
118
+ * Regions marked with `linguo-ignore` comment directives are skipped, so
119
+ * documentation samples containing `mark(`, `'…' | t`, or `t="…"` are not
120
+ * scanned as real messages (see {@link ignoredRanges}).
121
+ */
122
+ function extractMessages(files) {
123
+ const occurrences = new Map();
124
+ for (const file of files) {
125
+ const found = [];
126
+ const fromOptions = (index, key, options) => {
127
+ const contextMatch = options ? CONTEXT_IN_OPTIONS.exec(options) : null;
128
+ found.push({
129
+ index,
130
+ keyId: (0, normalize_1.normalizeMessage)(unescapeJs(key)),
131
+ context: contextMatch ? unescapeJs(contextMatch[2] ?? '').trim() : '',
132
+ });
133
+ };
134
+ PIPE_PATTERN.lastIndex = 0;
135
+ let pipeMatch;
136
+ while ((pipeMatch = PIPE_PATTERN.exec(file.content)) !== null) {
137
+ fromOptions(pipeMatch.index, pipeMatch[2] ?? '', pipeMatch[3]);
138
+ }
139
+ MARK_PATTERN.lastIndex = 0;
140
+ let markMatch;
141
+ while ((markMatch = MARK_PATTERN.exec(file.content)) !== null) {
142
+ fromOptions(markMatch.index, markMatch[2] ?? '', markMatch[3]);
143
+ }
144
+ T_CALL_PATTERN.lastIndex = 0;
145
+ let callMatch;
146
+ while ((callMatch = T_CALL_PATTERN.exec(file.content)) !== null) {
147
+ fromOptions(callMatch.index, callMatch[2] ?? '', callMatch[3]);
148
+ }
149
+ DIRECTIVE_TAG_PATTERN.lastIndex = 0;
150
+ let tagMatch;
151
+ while ((tagMatch = DIRECTIVE_TAG_PATTERN.exec(file.content)) !== null) {
152
+ const tag = tagMatch[0];
153
+ const messageMatch = DIRECTIVE_MESSAGE_PATTERN.exec(tag);
154
+ if (!messageMatch) {
155
+ continue;
156
+ }
157
+ const contextMatch = DIRECTIVE_CONTEXT_PATTERN.exec(tag);
158
+ found.push({
159
+ index: tagMatch.index,
160
+ keyId: (0, normalize_1.normalizeMessage)(messageMatch[1] ?? ''),
161
+ context: contextMatch ? (contextMatch[1]?.trim() ?? '') : '',
162
+ });
163
+ }
164
+ // Interleave pipe and directive matches by source position so discovery
165
+ // order reflects how the file actually reads.
166
+ found.sort((a, b) => a.index - b.index);
167
+ const ignored = ignoredRanges(file.content);
168
+ const isIgnored = (index) => ignored.some((range) => index >= range.start && index < range.end);
169
+ for (const item of found) {
170
+ if (isIgnored(item.index)) {
171
+ continue;
172
+ }
173
+ record(occurrences, item, `${file.path}:${lineOf(file.content, item.index)}`);
174
+ }
175
+ }
176
+ // Map preserves insertion order, so this is discovery order.
177
+ return [...occurrences.values()].map(({ keyId, context, refs }) => ({
178
+ keyId,
179
+ context,
180
+ references: [...refs].sort(),
181
+ }));
182
+ }
183
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ You are a professional software localizer. Translate a gettext `.po`
2
+ catalog produced by **ng-linguo** (an Angular i18n library) from English
3
+ into {{TARGET_LANGUAGE}}.
4
+
5
+ Return the COMPLETE `.po` file and NOTHING else — no commentary, no code
6
+ fences, no explanations. Your output must be a valid `.po` file that I
7
+ can save directly over the input.
8
+
9
+ ================================================================
10
+ 1. WHAT YOU MAY AND MAY NOT CHANGE
11
+ ================================================================
12
+ A `.po` file is a list of entries. A typical entry looks like:
13
+
14
+ #: apps/playground/src/app/app.html:10
15
+ msgctxt "audio player"
16
+ msgid "Play"
17
+ msgstr "<MISSING TRANSLATION> Play"
18
+
19
+ Rules:
20
+ - TRANSLATE ONLY the text inside `msgstr "..."`.
21
+ - NEVER change `msgid` — it is the source key. Copy it as-is.
22
+ - NEVER change, remove, or reorder `#:` reference lines (source
23
+ locations) or `msgctxt` lines. Keep every entry in its original order.
24
+ - Keep the header entry (the first block, `msgid ""` / `msgstr ""`)
25
+ exactly as-is.
26
+ - Output every entry. Do not drop, merge, or add entries.
27
+
28
+ ================================================================
29
+ 2. WHICH ENTRIES NEED WORK — the <MISSING TRANSLATION> marker
30
+ ================================================================
31
+ Any `msgstr` beginning with `<MISSING TRANSLATION>` is an untranslated
32
+ entry seeded with the English source as a fallback.
33
+
34
+ - For each such entry: translate the source and write the result WITHOUT
35
+ the `<MISSING TRANSLATION>` prefix (delete the prefix entirely).
36
+ - If a `msgstr` does NOT start with that prefix, it is already
37
+ human-reviewed — LEAVE IT UNTOUCHED.
38
+
39
+ ================================================================
40
+ 3. CONTEXT — use it, never translate it
41
+ ================================================================
42
+ `msgctxt "..."` carries the message's context. In ng-linguo, context is
43
+ a single property that does two jobs:
44
+ (a) it disambiguates identical source text — the same `msgid "Play"`
45
+ can appear several times with different `msgctxt` ("audio player"
46
+ vs "game") and MUST be translated differently per context;
47
+ (b) it doubles as a translator note describing intent.
48
+
49
+ So: READ the `msgctxt` to pick the right wording, but it is metadata —
50
+ do not translate it and do not copy it into the `msgstr`. Entries with
51
+ the same `msgid` but different `msgctxt` are independent and usually need
52
+ different translations.
53
+
54
+ ================================================================
55
+ 4. SLOT TAGS — `[name]...[/name]`
56
+ ================================================================
57
+ Some sources contain slot tags (a BBCode-like bracket syntax), e.g.:
58
+
59
+ msgid "Read the [docs]documentation[/docs] to get started"
60
+
61
+ These tags map to interactive UI (links, buttons) at runtime.
62
+
63
+ - Preserve every tag EXACTLY: same name, same `[name]` ... `[/name]`
64
+ pairing, same order. Tag names are NOT translated.
65
+ - Translate ONLY the human text, including the text WRAPPED BY a tag.
66
+ - You MAY move a tag pair to wherever the wrapped phrase naturally lands
67
+ in {{TARGET_LANGUAGE}} word order, as long as the pair stays matched
68
+ and wraps the same concept.
69
+ - A literal `[` is written as a doubled `[[` — the ONLY escape in this
70
+ syntax. `[[` is NOT a slot tag; it renders as a single visible `[`.
71
+ Keep every `[[` exactly as a doubled bracket (never collapse it to one
72
+ `[`, never expand it) and translate the prose around it. So
73
+ `[[b]bold[[/b]` displays the literal characters `[b]bold[/b]`, and a
74
+ sentence built around that is describing the escape mechanism itself.
75
+
76
+ Example (→ German):
77
+ msgid "Read the [docs]documentation[/docs] to get started"
78
+ msgstr "Lies die [docs]Dokumentation[/docs], um loszulegen"
79
+
80
+ ================================================================
81
+ 5. ICU MESSAGEFORMAT 2 — the heart of translation quality
82
+ ================================================================
83
+ ng-linguo formats messages with **MessageFormat 2 (MF2)**. Two forms
84
+ appear:
85
+
86
+ 5a. SIMPLE PLACEHOLDERS — `{$name}`
87
+ msgid "Hello {$name}!"
88
+ A `{$var}` is a runtime value substituted into the sentence.
89
+ - Keep the placeholder verbatim, including the `$` and exact name.
90
+ - Reposition it to fit natural target word order.
91
+ - Do NOT invent, rename, or drop placeholders.
92
+ Example (→ Polish):
93
+ msgid "Hello {$name}!"
94
+ msgstr "Cześć {$name}!"
95
+
96
+ 5b. SELECTION / PLURALS — `.input` / `.match` / variants
97
+ msgid ".input {$count :number} .match $count one {{{$count} file}} * {{{$count} files}}"
98
+
99
+ Anatomy:
100
+ .input {$count :number} declares a selector value (a number)
101
+ .match $count selects a variant based on it
102
+ one {{ ... }} variant chosen for the "one" plural category
103
+ * {{ ... }} the catch-all / "other" variant (REQUIRED)
104
+ {{ ... }} a "quoted pattern": its inner text is what
105
+ renders; `{$count}` inside it is a placeholder.
106
+
107
+ THIS IS WHERE QUALITY IS WON OR LOST. Different languages have
108
+ different CLDR plural categories. You MUST emit the categories that
109
+ {{TARGET_LANGUAGE}} actually uses — not copy English's set.
110
+
111
+ Rules:
112
+ - Keep `.input ... .match ...` and the selector exactly as written
113
+ (same `{$count :number}`, same `.match $count`).
114
+ - Replace the English variant KEYS with the correct CLDR plural
115
+ categories for {{TARGET_LANGUAGE}}, chosen from:
116
+ zero · one · two · few · many · other
117
+ Always include the `*` (other) catch-all variant.
118
+ - Translate ONLY the literal text inside each `{{ ... }}` pattern.
119
+ Keep `{$count}` (and any other `{$var}`) intact inside the pattern.
120
+ - Add or remove variants as the language requires (e.g. English has
121
+ one/other; Polish needs one/few/many/other).
122
+
123
+ Example (English `one` + `other` → Polish `one`/`few`/`many`/`other`):
124
+ msgid ".input {$count :number} .match $count one {{{$count} file}} * {{{$count} files}}"
125
+ msgstr ".input {$count :number} .match $count one {{{$count} plik}} few {{{$count} pliki}} many {{{$count} plików}} * {{{$count} pliku}}"
126
+
127
+ Example (German, like English — one/other):
128
+ msgstr ".input {$count :number} .match $count one {{{$count} Datei}} * {{{$count} Dateien}}"
129
+
130
+ If you are unsure which plural categories a language uses, follow the
131
+ Unicode CLDR plural rules for that language. Getting these categories
132
+ right is the single most important part of this task.
133
+
134
+ 5c. GOING BEYOND THE SOURCE STRUCTURE
135
+ The English source is a baseline, not a cage. MF2 lets the target
136
+ read more naturally than a literal mirror of the source — use it.
137
+
138
+ (i) EXACT-VALUE MATCHING — special-case 0 (and sometimes 1).
139
+ `.match` keys may be literal numbers, and an exact literal wins
140
+ over a plural category. If {{TARGET_LANGUAGE}} phrases "none"
141
+ (or "one") with a different construction, ADD a literal variant.
142
+ This is always safe — it reuses the selector that already exists.
143
+
144
+ Example (→ English-style, but adding a natural zero case):
145
+ source variants: one {{...}} * {{...}}
146
+ improved: .match $count
147
+ 0 {{No files}}
148
+ one {{One file}}
149
+ * {{{$count} files}}
150
+
151
+ (ii) ADDING A SELECTOR DIMENSION (gender, case, animacy …).
152
+ Many languages inflect on gender/case even where English does not.
153
+ You MAY add such a dimension ONLY IF a matching `.input {$var ...}`
154
+ is ALREADY DECLARED in the message (i.e. the app passes that value
155
+ at runtime). Never invent a selector for a variable that is not
156
+ already declared — at runtime it would be undefined and break.
157
+
158
+ When a selector IS available, switch to multiple selectors with
159
+ one key per selector on each variant line, and keep a `* * {{...}}`
160
+ catch-all:
161
+ .input {$count :number} .input {$gender :string}
162
+ .match $count $gender
163
+ one feminine {{...}}
164
+ one masculine {{...}}
165
+ * * {{...}}
166
+
167
+ If the variable you would need is NOT declared, translate within
168
+ the source's existing structure instead — do not add it.
169
+
170
+ ================================================================
171
+ 6. MECHANICAL .po RULES
172
+ ================================================================
173
+ - A `msgstr` value is a double-quoted string. Escape a literal double
174
+ quote as `\"` and a newline as `\n` inside the quotes.
175
+ - Preserve any `""` continuation-line formatting if present (a value
176
+ split across multiple quoted lines concatenates).
177
+ - UTF-8 throughout. Use the target language's real characters (ą, ß, é …),
178
+ not ASCII transliterations.
179
+
180
+ ================================================================
181
+ 7. STYLE
182
+ ================================================================
183
+ - Translate meaning, not words. Match the register a UI button / label /
184
+ sentence would use in {{TARGET_LANGUAGE}}.
185
+ - Aim for a result that reads as if ORIGINALLY WRITTEN in
186
+ {{TARGET_LANGUAGE}}, not transcoded from English. You are free to
187
+ restructure: reorder clauses, split one sentence into two or merge two
188
+ into one, change voice, drop filler connectives — whatever natural
189
+ phrasing wants. The source fixes the MEANING and the
190
+ placeholders/slots/tags, not the word order or sentence shape. Output
191
+ that is grammatical but stiff, word-for-word, or obviously translated is
192
+ a FAILURE here. Mirroring English clause order often produces broken
193
+ target grammar — rebuild the sentence instead.
194
+ - Infer each message's DOMAIN and register from its own content and the
195
+ app's other strings — it may be software, medical, legal, finance,
196
+ gaming, marketing, hospitality, anything. Then translate the way a
197
+ domain expert writing in {{TARGET_LANGUAGE}} would, using that field's
198
+ established terminology — not a literal everyday rendering, and not
199
+ jargon borrowed from the wrong field. Technical terms usually have a
200
+ settled target-language form — sometimes a native word, sometimes a
201
+ borrowed or anglicized term that practitioners actually say; use
202
+ whichever is REAL in the field, not a coined literal calque. You do NOT need a
203
+ `msgctxt` to do this — the source text itself is the signal (treat
204
+ `msgctxt`, when present, as a tie-breaker). Example: in "Escaped
205
+ [[b]bold[[/b] stays literal" the surrounding markup marks a software
206
+ context, so "Escaped" is character-escaping — pick the term a developer
207
+ uses, not the word for fleeing/running away.
208
+ - Respect the context (§3) when the same source has several entries.
209
+ - Keep capitalization and punctuation idiomatic for the target language.
210
+
211
+ ================================================================
212
+ NOW TRANSLATE THIS FILE
213
+ ================================================================
214
+ {{PO_FILE}}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * What a consumer's translate function receives. We build the prompt (explaining
3
+ * ng-linguo's context, slot tags and MessageFormat 2 rules) and parse the reply;
4
+ * the function's only job is to send `prompt` to an AI provider and return the
5
+ * model's raw answer — a complete `.po` block. This keeps every provider SDK and
6
+ * API secret on the consumer's side, out of this build-time tool.
7
+ */
8
+ export interface TranslateRequest {
9
+ /** The ready-to-send prompt for the untranslated entries of one catalog. */
10
+ readonly prompt: string;
11
+ /** The catalog's locale code, e.g. `"pl"`. */
12
+ readonly targetLocale: string;
13
+ /** A human-readable label for the locale, e.g. `"Polish (pl)"`. */
14
+ readonly targetLabel: string;
15
+ /** The configured source locale the strings are translated from, e.g. `"en"`. */
16
+ readonly sourceLocale: string;
17
+ }
18
+ /**
19
+ * A consumer-supplied translation function. Linked to the build via the
20
+ * `translator` field in `linguo.config.json` (a path to a module that exports it
21
+ * as `translate` or as the default export). It receives a {@link TranslateRequest}
22
+ * and returns the model's reply — the translated `.po` text — sync or async.
23
+ *
24
+ * @example
25
+ * ```js
26
+ * // linguo.translator.mjs
27
+ * import OpenAI from 'openai';
28
+ * const client = new OpenAI(); // reads OPENAI_API_KEY
29
+ *
30
+ * export async function translate({ prompt }) {
31
+ * const res = await client.chat.completions.create({
32
+ * model: 'gpt-4o',
33
+ * messages: [{ role: 'user', content: prompt }],
34
+ * });
35
+ * return res.choices[0].message.content ?? '';
36
+ * }
37
+ * ```
38
+ */
39
+ export type TranslateFunction = (request: TranslateRequest) => string | Promise<string>;
40
+ /**
41
+ * Extract the {@link TranslateFunction} from an imported translator module,
42
+ * accepting either a named `translate` export or a default function. Pure (no
43
+ * I/O), so it is unit-tested directly.
44
+ *
45
+ * @throws when no usable function is exported.
46
+ */
47
+ export declare function resolveTranslatorExport(mod: unknown, source?: string): TranslateFunction;
48
+ /** How a module specifier is loaded; injectable so the loader is testable. */
49
+ export type ModuleImporter = (specifier: string) => Promise<unknown>;
50
+ /**
51
+ * Dynamically import the translator module at `resolvedPath` (an absolute path)
52
+ * and return its {@link TranslateFunction}. The path is converted to a `file://`
53
+ * URL so ESM `.mjs`/`.js` consumer modules load on every platform.
54
+ *
55
+ * @throws when the module cannot be loaded or exports no usable function.
56
+ */
57
+ export declare function loadTranslator(resolvedPath: string, importer?: ModuleImporter): Promise<TranslateFunction>;
58
+ /** Outcome of {@link autoTranslateCatalog}. */
59
+ export interface AutoTranslateOutcome {
60
+ /** How many entries were sent to the translator (the untranslated ones). */
61
+ readonly untranslated: number;
62
+ /** How many entries received a usable new translation. */
63
+ readonly applied: number;
64
+ /** How many entries are still untranslated after merging the reply. */
65
+ readonly remaining: number;
66
+ /** The merged full catalog as `.po` text (unchanged when nothing applied). */
67
+ readonly po: string;
68
+ }
69
+ /**
70
+ * Translate one catalog's untranslated entries by calling a {@link TranslateFunction}.
71
+ * Builds the prompt from the missing entries only, invokes `translate`, and folds
72
+ * the reply back into the full catalog. Pure of file I/O — the caller reads and
73
+ * writes the `.po` — so it is unit-tested with a fake translate function.
74
+ *
75
+ * @throws when the translator returns an empty reply.
76
+ */
77
+ export declare function autoTranslateCatalog(args: {
78
+ readonly translate: TranslateFunction;
79
+ readonly poText: string;
80
+ readonly targetLocale: string;
81
+ readonly targetLabel: string;
82
+ readonly sourceLocale: string;
83
+ }): Promise<AutoTranslateOutcome>;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTranslatorExport = resolveTranslatorExport;
4
+ exports.loadTranslator = loadTranslator;
5
+ exports.autoTranslateCatalog = autoTranslateCatalog;
6
+ const node_url_1 = require("node:url");
7
+ const apply_1 = require("./apply");
8
+ const po_1 = require("./po");
9
+ const prompt_1 = require("./prompt");
10
+ function pickExport(mod) {
11
+ if (typeof mod !== 'object' || mod === null) {
12
+ return undefined;
13
+ }
14
+ const record = mod;
15
+ if (typeof record['translate'] === 'function') {
16
+ return record['translate'];
17
+ }
18
+ const def = record['default'];
19
+ if (typeof def === 'function') {
20
+ return def;
21
+ }
22
+ // An ESM module whose default export is itself an object with `translate`.
23
+ if (typeof def === 'object' && def !== null) {
24
+ const defRecord = def;
25
+ if (typeof defRecord['translate'] === 'function') {
26
+ return defRecord['translate'];
27
+ }
28
+ }
29
+ return undefined;
30
+ }
31
+ /**
32
+ * Extract the {@link TranslateFunction} from an imported translator module,
33
+ * accepting either a named `translate` export or a default function. Pure (no
34
+ * I/O), so it is unit-tested directly.
35
+ *
36
+ * @throws when no usable function is exported.
37
+ */
38
+ function resolveTranslatorExport(mod, source = 'translator module') {
39
+ const candidate = pickExport(mod);
40
+ if (typeof candidate !== 'function') {
41
+ throw new Error(`${source} must export a "translate" function (named export) or a default function.`);
42
+ }
43
+ return candidate;
44
+ }
45
+ /**
46
+ * Dynamically import the translator module at `resolvedPath` (an absolute path)
47
+ * and return its {@link TranslateFunction}. The path is converted to a `file://`
48
+ * URL so ESM `.mjs`/`.js` consumer modules load on every platform.
49
+ *
50
+ * @throws when the module cannot be loaded or exports no usable function.
51
+ */
52
+ async function loadTranslator(resolvedPath, importer = (specifier) => import(specifier)) {
53
+ const href = (0, node_url_1.pathToFileURL)(resolvedPath).href;
54
+ let mod;
55
+ try {
56
+ mod = await importer(href);
57
+ }
58
+ catch (error) {
59
+ throw new Error(`could not load translator module at ${resolvedPath}: ` +
60
+ `${error instanceof Error ? error.message : String(error)}`);
61
+ }
62
+ return resolveTranslatorExport(mod, resolvedPath);
63
+ }
64
+ /**
65
+ * Translate one catalog's untranslated entries by calling a {@link TranslateFunction}.
66
+ * Builds the prompt from the missing entries only, invokes `translate`, and folds
67
+ * the reply back into the full catalog. Pure of file I/O — the caller reads and
68
+ * writes the `.po` — so it is unit-tested with a fake translate function.
69
+ *
70
+ * @throws when the translator returns an empty reply.
71
+ */
72
+ async function autoTranslateCatalog(args) {
73
+ const untranslated = (0, po_1.parsePo)(args.poText).filter((e) => (0, apply_1.isUntranslated)(e.msgstr));
74
+ if (untranslated.length === 0) {
75
+ return { untranslated: 0, applied: 0, remaining: 0, po: args.poText };
76
+ }
77
+ const prompt = (0, prompt_1.buildTranslationPrompt)(args.targetLabel, (0, po_1.serializePo)(untranslated));
78
+ const reply = await args.translate({
79
+ prompt,
80
+ targetLocale: args.targetLocale,
81
+ targetLabel: args.targetLabel,
82
+ sourceLocale: args.sourceLocale,
83
+ });
84
+ if (typeof reply !== 'string' || reply.trim() === '') {
85
+ throw new Error(`translator returned an empty reply for ${args.targetLabel}`);
86
+ }
87
+ const { po, applied } = (0, apply_1.applyTranslations)(args.poText, reply);
88
+ const remaining = (0, po_1.parsePo)(po).filter((e) => (0, apply_1.isUntranslated)(e.msgstr)).length;
89
+ return { untranslated: untranslated.length, applied, remaining, po };
90
+ }
91
+ //# sourceMappingURL=translator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translator.js","sourceRoot":"","sources":["../../../../../packages/extract/src/lib/translator.ts"],"names":[],"mappings":";;AA4EA,0DAWC;AAYD,wCAeC;AAsBD,oDA0BC;AAlKD,uCAAyC;AAEzC,mCAA4D;AAC5D,6BAA4C;AAC5C,qCAAkD;AA2ClD,SAAS,UAAU,CAAC,GAAY;IAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,GAA8B,CAAC;IAC9C,IAAI,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,UAAU,EAAE,CAAC;QAC9C,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,IAAI,OAAO,GAAG,KAAK,UAAU,EAAE,CAAC;QAC9B,OAAO,GAAG,CAAC;IACb,CAAC;IACD,2EAA2E;IAC3E,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,GAA8B,CAAC;QACjD,IAAI,OAAO,SAAS,CAAC,WAAW,CAAC,KAAK,UAAU,EAAE,CAAC;YACjD,OAAO,SAAS,CAAC,WAAW,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,uBAAuB,CACrC,GAAY,EACZ,MAAM,GAAG,mBAAmB;IAE5B,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,2EAA2E,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,SAA8B,CAAC;AACxC,CAAC;AAKD;;;;;;GAMG;AACI,KAAK,UAAU,cAAc,CAClC,YAAoB,EACpB,WAA2B,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC;IAE3D,MAAM,IAAI,GAAG,IAAA,wBAAa,EAAC,YAAY,CAAC,CAAC,IAAI,CAAC;IAC9C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,uCAAuC,YAAY,IAAI;YACrD,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC9D,CAAC;IACJ,CAAC;IACD,OAAO,uBAAuB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AACpD,CAAC;AAcD;;;;;;;GAOG;AACI,KAAK,UAAU,oBAAoB,CAAC,IAM1C;IACC,MAAM,YAAY,GAAG,IAAA,YAAO,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,sBAAc,EAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAClF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,+BAAsB,EAAC,IAAI,CAAC,WAAW,EAAE,IAAA,gBAAW,EAAC,YAAY,CAAC,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC;QACjC,MAAM;QACN,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;KAChC,CAAC,CAAC;IACH,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,0CAA0C,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,IAAA,yBAAiB,EAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAA,YAAO,EAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,sBAAc,EAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7E,OAAO,EAAE,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AACvE,CAAC"}