lingui-po-translate 1.0.2 → 1.0.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.
@@ -66,6 +66,7 @@ async function runTranslationService(rawInputs, args) {
66
66
  serviceConfig: args.serviceConfig,
67
67
  prompt: args.prompt,
68
68
  baseUrl: args.baseUrl,
69
+ debug: args.debug,
69
70
  };
70
71
  console.info(`Invoke '${args.service}' from '${args.srcLng}' to '${args.targetLng}' with ${serviceArgs.strings.length} inputs...`);
71
72
  const translationService = await (0, service_definitions_1.instantiateTService)(args.service);
@@ -32,7 +32,7 @@ function formatCliOptions(options) {
32
32
  }
33
33
  exports.formatCliOptions = formatCliOptions;
34
34
  async function translateCli(cliArgs) {
35
- var _a, _b, _c;
35
+ var _a, _b, _c, _d;
36
36
  checkForEmptyStringOptions(cliArgs);
37
37
  const fileFormats = (0, file_format_definitions_1.getTFileFormatList)();
38
38
  const services = (0, service_definitions_1.getTServiceList)();
@@ -73,6 +73,7 @@ async function translateCli(cliArgs) {
73
73
  prompt: (_b = cliArgs.prompt) !== null && _b !== void 0 ? _b : "",
74
74
  sourceOverride: (0, core_definitions_1.parseSourceOverride)(cliArgs.sourceOverride),
75
75
  baseUrl: (_c = cliArgs.baseUrl) !== null && _c !== void 0 ? _c : null,
76
+ debug: (_d = cliArgs.debug) !== null && _d !== void 0 ? _d : false,
76
77
  };
77
78
  const result = await (0, translate_core_1.translateCore)(coreArgs);
78
79
  const flushTarget = !oldTarget || !(0, tset_ops_1.areEqual)(oldTarget, result.newTarget);
@@ -21,6 +21,7 @@ function filterByManualMarking(inputs, args) {
21
21
  const toCopyOriginal = new Map();
22
22
  const toTranslateFromOverride = new Map();
23
23
  inputs.forEach((value, key) => {
24
+ var _a;
24
25
  const parsed = parsedComments.get(key);
25
26
  if (!parsed || !(0, comment_parser_1.hasManualMarking)(parsed)) {
26
27
  // No @manual marking, translate normally
@@ -30,6 +31,12 @@ function filterByManualMarking(inputs, args) {
30
31
  if ((0, comment_parser_1.shouldSkipForManual)(parsed, args.targetLng)) {
31
32
  // Target language is in @manual list, skip
32
33
  toSkip.set(key, value);
34
+ // Log @manual skip info
35
+ const targetTranslation = (_a = args.oldTarget) === null || _a === void 0 ? void 0 : _a.get(key);
36
+ const translationStatus = targetTranslation
37
+ ? `already translated: "${targetTranslation}"`
38
+ : "not yet translated";
39
+ console.info(`@manual:${args.targetLng} - skip "${key}" (${translationStatus})`);
33
40
  return;
34
41
  }
35
42
  // Has @manual but target not in list
@@ -8,11 +8,13 @@ exports.hasManualMarking = exports.shouldSkipForManual = exports.parseExtractedC
8
8
  * - @manual:zh-Hans,zh-Hant - languages requiring manual translation
9
9
  * - @context:text - context for AI translation
10
10
  * - Plain text (without @) - also treated as context
11
+ * - Multiple directives can be separated by ; or newline
11
12
  *
12
13
  * Example:
14
+ * #. @manual:zh-Hans; @context:This is a save button
15
+ * Or:
13
16
  * #. @manual:zh-Hans
14
17
  * #. @context:This is a save button
15
- * #. Some additional context
16
18
  *
17
19
  * @param extracted - The extracted comment string from PO file
18
20
  * @returns ParsedComment with manual languages and context
@@ -25,30 +27,38 @@ function parseExtractedComment(extracted) {
25
27
  if (!extracted) {
26
28
  return result;
27
29
  }
28
- const lines = extracted.split("\n");
30
+ // Split by newline first, then by ; for each line
31
+ const parts = [];
32
+ for (const line of extracted.split("\n")) {
33
+ for (const part of line.split(";")) {
34
+ const trimmed = part.trim();
35
+ if (trimmed) {
36
+ parts.push(trimmed);
37
+ }
38
+ }
39
+ }
29
40
  const contextParts = [];
30
- for (const line of lines) {
31
- const trimmedLine = line.trim();
32
- if (trimmedLine.startsWith("@manual:")) {
41
+ for (const part of parts) {
42
+ if (part.startsWith("@manual:")) {
33
43
  // Parse @manual:zh-Hans,zh-Hant
34
- const langList = trimmedLine.substring("@manual:".length).trim();
44
+ const langList = part.substring("@manual:".length).trim();
35
45
  if (langList) {
36
46
  result.manual = langList.split(",").map((lang) => lang.trim()).filter(Boolean);
37
47
  }
38
48
  }
39
- else if (trimmedLine.startsWith("@context:")) {
49
+ else if (part.startsWith("@context:")) {
40
50
  // Parse @context:text
41
- const contextText = trimmedLine.substring("@context:".length).trim();
51
+ const contextText = part.substring("@context:".length).trim();
42
52
  if (contextText) {
43
53
  contextParts.push(contextText);
44
54
  }
45
55
  }
46
- else if (trimmedLine && !trimmedLine.startsWith("@")) {
56
+ else if (!part.startsWith("@")) {
47
57
  // Plain text without @ prefix is also context
48
- contextParts.push(trimmedLine);
58
+ contextParts.push(part);
49
59
  }
50
60
  }
51
- result.context = contextParts.join("\n");
61
+ result.context = contextParts.join(" ");
52
62
  return result;
53
63
  }
54
64
  exports.parseExtractedComment = parseExtractedComment;
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ function run(process, cliBinDir) {
32
32
  .option("--prompt <prompt>", "supply a prompt for the AI translation service")
33
33
  .option("--sourceOverride <mapping>", "override source language for specific targets (e.g., 'zh-Hant:zh-Hans,pt-BR:pt-PT')")
34
34
  .option("--baseUrl <url>", "custom API base URL for OpenAI-compatible services (e.g., 'https://api.openai.com/v1')")
35
+ .option("--debug", "print debug information including API requests")
35
36
  .version((0, extract_version_1.extractVersion)({ cliBinDir }), "-v, --version")
36
37
  .parse(process.argv);
37
38
  if ((_a = commander_1.default.args) === null || _a === void 0 ? void 0 : _a.length) {
@@ -51,6 +52,7 @@ function run(process, cliBinDir) {
51
52
  prompt: commander_1.default.opts().prompt,
52
53
  sourceOverride: commander_1.default.opts().sourceOverride,
53
54
  baseUrl: commander_1.default.opts().baseUrl,
55
+ debug: commander_1.default.opts().debug,
54
56
  };
55
57
  (0, translate_cli_1.translateCli)(args)
56
58
  .then(() => {
@@ -15,13 +15,23 @@ async function translateSingleString(tString, args) {
15
15
  basePath: (_b = (_a = args.baseUrl) !== null && _a !== void 0 ? _a : process.env.OPENAI_BASE_URL) !== null && _b !== void 0 ? _b : undefined,
16
16
  });
17
17
  const openai = new openai_1.OpenAIApi(configuration);
18
- const prompt = generatePrompt(tString, args);
18
+ const { systemPrompt, userPrompt } = generatePrompt(tString, args);
19
19
  const messages = [
20
+ {
21
+ role: "system",
22
+ content: systemPrompt,
23
+ },
20
24
  {
21
25
  role: "user",
22
- content: prompt,
26
+ content: userPrompt,
23
27
  },
24
28
  ];
29
+ if (args.debug) {
30
+ console.log("\n[DEBUG] OpenAI Request:");
31
+ console.log(" Model: gpt-4o-mini-2024-07-18");
32
+ console.log(" System:", systemPrompt);
33
+ console.log(" User:", userPrompt);
34
+ }
25
35
  /**
26
36
  * https://platform.openai.com/docs/api-reference/completions/create
27
37
  * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
@@ -34,6 +44,9 @@ async function translateSingleString(tString, args) {
34
44
  temperature: 0,
35
45
  max_tokens: 2048,
36
46
  });
47
+ if (args.debug) {
48
+ console.log("\n[DEBUG] OpenAI Full Response:", JSON.stringify(completion.data, null, 2));
49
+ }
37
50
  const text = (_c = completion.data.choices[0].message) === null || _c === void 0 ? void 0 : _c.content;
38
51
  if (text == undefined) {
39
52
  (0, util_1.logFatal)("OpenAI returned undefined for prompt " + prompt);
@@ -53,16 +66,22 @@ async function translateSingleString(tString, args) {
53
66
  }
54
67
  }
55
68
  function generatePrompt(tString, args) {
56
- const capitalizedText = tString.value;
57
- const initialPrompt = `only translate my software string from ${args.srcLng} to ${args.targetLng}. don't chat or explain. Using the correct terms for computer software in the target language, only show target language never repeat string. if you don't find something to translate, don't respond`;
58
- let contextInfo = `\n\nkey (used for context): ${tString.key}`;
69
+ // System: Role, language pair, rules, context, and custom instructions
70
+ let systemPrompt = `You are a software UI translator.
71
+
72
+ Task: Translate from ${args.srcLng} to ${args.targetLng}.
73
+ Input: User provides text in <source> tags.
74
+ Output: Return only the translated text, without any tags or explanations.
75
+ Rules: Keep all placeholders ({name}, %s, %d), HTML tags, and formatting unchanged.`;
59
76
  if (tString.context) {
60
- contextInfo += `\n\ncontext: ${tString.context}`;
77
+ systemPrompt += `\nContext: ${tString.context}`;
78
+ }
79
+ if (args.prompt) {
80
+ systemPrompt += `\nNote: ${args.prompt}`;
61
81
  }
62
- return (initialPrompt +
63
- (args.prompt ? `\n\n${args.prompt}` : "") +
64
- contextInfo +
65
- `\n\nstring to translate: ${capitalizedText}`);
82
+ // User: Text wrapped in XML tags
83
+ const userPrompt = `<source>${tString.value}</source>`;
84
+ return { systemPrompt, userPrompt };
66
85
  }
67
86
  async function translateBatch(batch, args) {
68
87
  console.log("Translate a batch of " + batch.length + " strings with OpenAI...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lingui-po-translate",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "AI-powered translation tool for Lingui PO files with context-aware translations",
5
5
  "repository": {
6
6
  "type": "git",