|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Generate NSIS language file from PO translations. |
| 4 | +""" |
| 5 | + |
| 6 | +import glob |
| 7 | +import os |
| 8 | +import re |
| 9 | +import sys |
| 10 | +import polib |
| 11 | +from collections import defaultdict |
| 12 | + |
| 13 | +# Mapping from gettext language codes to NSIS language codes |
| 14 | +LANGUAGE_MAP = { |
| 15 | + 'af': 'AFRIKAANS', |
| 16 | + 'sq': 'ALBANIAN', |
| 17 | + 'ar': 'ARABIC', |
| 18 | + 'be': 'BELARUSIAN', |
| 19 | + 'bs': 'BOSNIAN', |
| 20 | + 'br': 'BRETON', |
| 21 | + 'bg': 'BULGARIAN', |
| 22 | + 'ca': 'CATALAN', |
| 23 | + 'hr': 'CROATIAN', |
| 24 | + 'cs': 'CZECH', |
| 25 | + 'da': 'DANISH', |
| 26 | + 'nl': 'DUTCH', |
| 27 | + 'eo': 'ESPERANTO', |
| 28 | + 'et': 'ESTONIAN', |
| 29 | + 'fa': 'FARSI', |
| 30 | + 'fi': 'FINNISH', |
| 31 | + 'fr': 'FRENCH', |
| 32 | + 'gl': 'GALICIAN', |
| 33 | + 'de': 'GERMAN', |
| 34 | + 'el': 'GREEK', |
| 35 | + 'he': 'HEBREW', |
| 36 | + 'hu': 'HUNGARIAN', |
| 37 | + 'is': 'ICELANDIC', |
| 38 | + 'id': 'INDONESIAN', |
| 39 | + 'ga': 'IRISH', |
| 40 | + 'it': 'ITALIAN', |
| 41 | + 'ja': 'JAPANESE', |
| 42 | + 'ko': 'KOREAN', |
| 43 | + 'ku': 'KURDISH', |
| 44 | + 'lv': 'LATVIAN', |
| 45 | + 'lt': 'LITHUANIAN', |
| 46 | + 'lb': 'LUXEMBOURGISH', |
| 47 | + 'mk': 'MACEDONIAN', |
| 48 | + 'ms': 'MALAY', |
| 49 | + 'mn': 'MONGOLIAN', |
| 50 | + 'nb': 'NORWEGIAN', |
| 51 | + 'nn': 'NORWEGIANNYNORSK', |
| 52 | + 'pl': 'POLISH', |
| 53 | + 'pt': 'PORTUGUESE', |
| 54 | + 'pt-BR': 'PORTUGUESEBR', |
| 55 | + 'pt_BR': 'PORTUGUESEBR', |
| 56 | + 'ro': 'ROMANIAN', |
| 57 | + 'ru': 'RUSSIAN', |
| 58 | + 'sr': 'SERBIAN', |
| 59 | + 'sr-Latn': 'SERBIANLATIN', |
| 60 | + 'zh-CN': 'SIMPCHINESE', |
| 61 | + 'zh_CN': 'SIMPCHINESE', |
| 62 | + 'sk': 'SLOVAK', |
| 63 | + 'sl': 'SLOVENIAN', |
| 64 | + 'es': 'SPANISH', |
| 65 | + 'es-ES': 'SPANISHINTERNATIONAL', |
| 66 | + 'sv': 'SWEDISH', |
| 67 | + 'th': 'THAI', |
| 68 | + 'zh-TW': 'TRADCHINESE', |
| 69 | + 'zh_TW': 'TRADCHINESE', |
| 70 | + 'tr': 'TURKISH', |
| 71 | + 'uk': 'UKRAINIAN', |
| 72 | + 'uz': 'UZBEK', |
| 73 | +} |
| 74 | + |
| 75 | +# All NSIS languages (for complete coverage) |
| 76 | +ALL_NSIS_LANGUAGES = [ |
| 77 | + 'AFRIKAANS', 'ALBANIAN', 'ARABIC', 'BELARUSIAN', 'BOSNIAN', 'BRETON', |
| 78 | + 'BULGARIAN', 'CATALAN', 'CROATIAN', 'CZECH', 'DANISH', 'DUTCH', |
| 79 | + 'ESPERANTO', 'ESTONIAN', 'FARSI', 'FINNISH', 'FRENCH', 'GALICIAN', |
| 80 | + 'GERMAN', 'GREEK', 'HEBREW', 'HUNGARIAN', 'ICELANDIC', 'INDONESIAN', |
| 81 | + 'IRISH', 'ITALIAN', 'JAPANESE', 'KOREAN', 'KURDISH', 'LATVIAN', |
| 82 | + 'LITHUANIAN', 'LUXEMBOURGISH', 'MACEDONIAN', 'MALAY', 'MONGOLIAN', |
| 83 | + 'NORWEGIAN', 'NORWEGIANNYNORSK', 'POLISH', 'PORTUGUESE', 'PORTUGUESEBR', |
| 84 | + 'ROMANIAN', 'RUSSIAN', 'SERBIAN', 'SERBIANLATIN', 'SIMPCHINESE', |
| 85 | + 'SLOVAK', 'SLOVENIAN', 'SPANISH', 'SPANISHINTERNATIONAL', 'SWEDISH', |
| 86 | + 'THAI', 'TRADCHINESE', 'TURKISH', 'UKRAINIAN', 'UZBEK', |
| 87 | +] |
| 88 | + |
| 89 | +def read_po_files(po_dir, english_strings): |
| 90 | + """Read all PO files and extract NSIS translations.""" |
| 91 | + |
| 92 | + # Initialize with English strings |
| 93 | + translations = defaultdict(lambda: defaultdict(str)) |
| 94 | + for string_id, english_text in english_strings.items(): |
| 95 | + translations[string_id]['ENGLISH'] = english_text |
| 96 | + |
| 97 | + # Read all PO files |
| 98 | + po_files = glob.glob(os.path.join(po_dir, 'mediawriter_*.po')) |
| 99 | + for po_file in po_files: |
| 100 | + # Extract language code from filename |
| 101 | + basename = os.path.basename(po_file) |
| 102 | + lang_code = basename.replace('mediawriter_', '').replace('.po', '') |
| 103 | + |
| 104 | + # Map to NSIS language code |
| 105 | + nsis_lang = LANGUAGE_MAP.get(lang_code) |
| 106 | + if not nsis_lang: |
| 107 | + print(f'Warning: No NSIS mapping for language {lang_code}, skipping {po_file}', file=sys.stderr) |
| 108 | + continue |
| 109 | + |
| 110 | + # Parse PO file |
| 111 | + try: |
| 112 | + po = polib.pofile(po_file) |
| 113 | + for entry in po: |
| 114 | + # Check if this is an NSIS string |
| 115 | + if entry.msgctxt and entry.msgctxt.startswith('NSIS:'): |
| 116 | + string_id = entry.msgctxt.replace('NSIS:', '') |
| 117 | + if entry.msgstr: |
| 118 | + translations[string_id][nsis_lang] = entry.msgstr |
| 119 | + except Exception as e: |
| 120 | + print(f'Error reading {po_file}: {e}', file=sys.stderr) |
| 121 | + |
| 122 | + return translations |
| 123 | + |
| 124 | +def write_nsis_file(translations, output_file): |
| 125 | + """Write translations to NSIS language file.""" |
| 126 | + # Get list of all string IDs |
| 127 | + string_ids = sorted(translations.keys()) |
| 128 | + |
| 129 | + # Create list with ENGLISH first, then all others |
| 130 | + all_languages = ['ENGLISH'] + ALL_NSIS_LANGUAGES |
| 131 | + |
| 132 | + with open(output_file, 'w', encoding='utf-8') as f: |
| 133 | + for i, string_id in enumerate(string_ids): |
| 134 | + if i > 0: |
| 135 | + f.write('\n') |
| 136 | + |
| 137 | + # Get English string as fallback |
| 138 | + english_text = translations[string_id].get('ENGLISH', '') |
| 139 | + |
| 140 | + # Write LangString for each language (ENGLISH first, then others) |
| 141 | + for nsis_lang in all_languages: |
| 142 | + # Get translation or fall back to English |
| 143 | + text = translations[string_id].get(nsis_lang, english_text) |
| 144 | + |
| 145 | + # Escape for NSIS |
| 146 | + text = text.replace('\\n', '$\\n') |
| 147 | + text = text.replace('\\t', '$\\t') |
| 148 | + # Escape unescaped double quotes, but leave existing NSIS escapes ($\\") intact |
| 149 | + text = re.sub(r'(?<!\$\\)"', r'$\\\"', text) |
| 150 | + |
| 151 | + # Write LangString with fixed 2 spaces between language and quote |
| 152 | + f.write(f'LangString {string_id} ${{LANG_{nsis_lang}}} "{text}"\n') |
| 153 | + |
| 154 | +def extract_english_strings(nsis_file): |
| 155 | + """Extract English strings from existing NSIS file.""" |
| 156 | + strings = {} |
| 157 | + |
| 158 | + with open(nsis_file, 'r', encoding='utf-8') as f: |
| 159 | + for line in f: |
| 160 | + match = re.match(r'\s*LangString\s+(\w+)\s+\$\{LANG_ENGLISH\}\s+"(.+)"', line) |
| 161 | + if match: |
| 162 | + string_id = match.group(1) |
| 163 | + string_value = match.group(2) |
| 164 | + # Unescape NSIS escaped characters |
| 165 | + string_value = string_value.replace('$\\n', '\\n') |
| 166 | + string_value = string_value.replace('$\\t', '\\t') |
| 167 | + string_value = string_value.replace('$\\"', '"') |
| 168 | + strings[string_id] = string_value |
| 169 | + |
| 170 | + return strings |
| 171 | + |
| 172 | +if __name__ == '__main__': |
| 173 | + if len(sys.argv) != 4: |
| 174 | + print(f'Usage: {sys.argv[0]} <original-nsis-file> <po-directory> <output-nsis-file>') |
| 175 | + sys.exit(1) |
| 176 | + |
| 177 | + original_nsis = sys.argv[1] |
| 178 | + po_dir = sys.argv[2] |
| 179 | + output_file = sys.argv[3] |
| 180 | + |
| 181 | + # Extract English strings from original file |
| 182 | + english_strings = extract_english_strings(original_nsis) |
| 183 | + print(f'Extracted {len(english_strings)} English strings from {original_nsis}') |
| 184 | + |
| 185 | + # Read translations from PO files |
| 186 | + translations = read_po_files(po_dir, english_strings) |
| 187 | + |
| 188 | + # Write output file |
| 189 | + write_nsis_file(translations, output_file) |
| 190 | + |
| 191 | + print(f'Generated {output_file} with translations') |
0 commit comments