C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
translate_po.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2import sys
3import json
4import re
5import urllib.request
6
7OLLAMA_API_URL = "http://localhost:11434/api/generate"
8MODEL_NAME = "mistral" # Change to your preferred local model (e.g., qwen2.5-coder)
9
10def ask_ollama(text, target_lang):
11 if not text.strip():
12 return ""
13
14 prompt = (
15 f"You are a professional software localization tool translating text for a C terminal utility.\n"
16 f"Translate the following string into fluent {target_lang}.\n"
17 f"CRITICAL RULES:\n"
18 f"1. Preserve all printf specifiers exactly as they are (e.g., %s, %d, %x).\n"
19 f"2. Keep layout spaces, escape codes (like \\n or \\t) identical.\n"
20 f"3. Output ONLY the raw translated string. Do not include quotes, markdown, or explanations.\n\n"
21 f"String to translate: \"{text}\"\n"
22 f"Translation:"
23 )
24
25 data = {
26 "model": MODEL_NAME,
27 "prompt": prompt,
28 "stream": False,
29 "options": {"temperature": 0.1}
30 }
31
32 try:
33 req = urllib.request.Request(
34 OLLAMA_API_URL,
35 data=json.dumps(data).encode('utf-8'),
36 headers={'Content-Type': 'application/json'}
37 )
38 with urllib.request.urlopen(req) as response:
39 res_data = json.loads(response.read().decode('utf-8'))
40 return res_data['response'].strip().strip('"')
41 except Exception as e:
42 print(f"Error calling Ollama: {e}", file=sys.stderr)
43 return ""
44
45def split_to_po_format(text, indent=0):
46 """Splits a long translated string into multi-line PO syntax if it contains real newlines."""
47 # Split by actual literal '\n' characters embedded in the translation string
48 parts = text.split("\\n")
49 if len(parts) == 1:
50 return f'"{text}"\n'
51
52 out = '""\n'
53 for i, part in enumerate(parts):
54 if i < len(parts) - 1:
55 out += f'"{part}\\n"\n'
56 elif part: # Append the trailing segment if it isn't empty
57 out += f'"{part}"\n'
58 return out
59
60def parse_and_translate_po(filepath, target_lang):
61 print(f"Translating {filepath} into {target_lang} using Ollama...")
62 with open(filepath, 'r', encoding='utf-8') as f:
63 content = f.read()
64
65 # Regex block to match entire structural PO translation blocks, capturing multi-lines cleanly
66 entry_pattern = re.compile(
67 r'(msgid\s+(?:""\s*\n)?(?:".*"\s*\n)*)\s*(msgstr\s+(?:""\s*\n)?(?:".*"\s*\n)*)'
68 )
69
70 def replace_entry(match):
71 msgid_block = match.group(1)
72 msgstr_block = match.group(2)
73
74 # Extract content inside quotes
75 msgid_strings = re.findall(r'"(.*)"', msgid_block)
76 msgstr_strings = re.findall(r'"(.*)"', msgstr_block)
77
78 full_msgid = "".join(msgid_strings)
79 full_msgstr = "".join(msgstr_strings)
80
81 # Only process if the ID exists and the translation string is currently completely empty
82 if full_msgid and not full_msgstr:
83 translated = ask_ollama(full_msgid, target_lang)
84 print(f" '{full_msgid}'\n -> '{translated}'")
85
86 # Format the output matching PO line regulations
87 formatted_msgstr = "msgstr " + split_to_po_format(translated)
88 return f"{msgid_block}{formatted_msgstr}"
89
90 return match.group(0)
91
92 updated_content = entry_pattern.sub(replace_entry, content)
93
94 with open(filepath, 'w', encoding='utf-8') as f:
95 f.write(updated_content)
96
97if __name__ == "__main__":
98 if len(sys.argv) < 3:
99 print("Usage: ./translate_po.py <path_to_po_file> <target_language>")
100 sys.exit(1)
101 parse_and_translate_po(sys.argv[1], sys.argv[2])
102
ask_ollama(text, target_lang)
split_to_po_format(text, indent=0)
parse_and_translate_po(filepath, target_lang)