Files
sabnzbd/tools/make_mo.py

212 lines
7.1 KiB
Python
Executable File

#!/usr/bin/python3 -OO
# -*- coding: utf-8 -*-
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
make_mo - Compile PO files to MO files
"""
import concurrent.futures
import gettext
import glob
import os
import re
import sys
import msgfmt
PO_DIR = "po/main"
PO_EMAIL_DIR = "po/email"
PO_NSIS_DIR = "po/nsis"
MO_DIR = "locale"
EMAIL_DIR = "email"
DOMAIN = "SABnzbd"
DOMAIN_EMAIL = "SABemail"
DOMAIN_NSIS = "SABnsis"
NSIS = "builder/win/NSIS_Installer.nsi"
# Maps locale codes to NSIS MUI_LANGUAGE names (only languages with active translations).
# To add a new language:
# * Add the locale code here with the matching MUI_LANGUAGE name
# * Add the corresponding !insertmacro MUI_LANGUAGE line in NSIS_Installer.nsi
# * Create po/nsis/<locale>.po.
# * Available MUI language names are listed in the NSIS source:
# https://github.com/NSIS-Dev/nsis/tree/master/Contrib/Language%20files
NSIS_LANGUAGE_NAMES = {
"cs": "Czech",
"da": "Danish",
"de": "German",
"es": "Spanish",
"fi": "Finnish",
"fr": "French",
"he": "Hebrew",
"it": "Italian",
"nb": "Norwegian",
"nl": "Dutch",
"pl": "Polish",
"pt_BR": "PortugueseBR",
"ro": "Romanian",
"ru": "Russian",
"sr": "Serbian",
"sv": "Swedish",
"tr": "Turkish",
"zh_CN": "SimpChinese",
}
RE_NSIS = re.compile(r'^(\s*LangString\s+)(\w+)(\s+\$\{LANG_)(\w+)\}\s+(".*)', re.I)
def _compile_po_file(po_file, mo_file):
"""Compile a single PO file to MO — runs in a worker process"""
msgfmt.MESSAGES = {}
try:
msgfmt.make(po_file, mo_file)
return True
except SystemExit:
return False
def process_po_folder(domain, folder):
"""Compile all PO files in folder to MO files in parallel"""
po_files, mo_files = [], []
for po_file in glob.glob(os.path.join(folder, "*.po")):
name = os.path.splitext(os.path.basename(po_file))[0]
mo_path = os.path.join(MO_DIR, name, "LC_MESSAGES")
os.makedirs(mo_path, exist_ok=True)
mo_file = os.path.join(mo_path, f"{domain}.mo")
print(f"Compile {mo_file}")
po_files.append(po_file)
mo_files.append(mo_file)
with concurrent.futures.ProcessPoolExecutor() as executor:
if not all(executor.map(_compile_po_file, po_files, mo_files)):
sys.exit(1)
def translate_tmpl(trans, prefix, lng):
"""Write a translated copy of an email template"""
src = os.path.join(EMAIL_DIR, f"{prefix}-en.tmpl")
dst = os.path.join(EMAIL_DIR, f"{prefix}-{lng}.tmpl")
with open(src, encoding="utf-8") as fp:
data = trans.gettext(fp.read())
with open(dst, "w", encoding="utf-8") as fp:
if not -1 < data.find("UTF-8") < 30:
fp.write("#encoding UTF-8\n")
fp.write(data)
def make_templates():
"""Create translated email templates for all available languages"""
os.makedirs(EMAIL_DIR, exist_ok=True)
for lng in os.listdir(MO_DIR):
if lng == "en" or not os.path.exists(os.path.join(PO_EMAIL_DIR, f"{lng}.po")):
continue
print(f"Create email template for {lng}")
trans = gettext.translation(DOMAIN_EMAIL, MO_DIR, [lng], fallback=False)
for prefix in ("email", "rss", "badfetch"):
translate_tmpl(trans, prefix, lng)
# Remove the email MO file — only needed for template generation
mo_file = os.path.join(MO_DIR, lng, "LC_MESSAGES", f"{DOMAIN_EMAIL}.mo")
if os.path.exists(mo_file):
os.remove(mo_file)
def remove_mo_files():
"""Remove any remaining non-main MO files from locale"""
for mo_file in glob.glob(os.path.join(MO_DIR, "**", "*.mo"), recursive=True):
if not os.path.splitext(os.path.basename(mo_file))[0].startswith(DOMAIN):
os.remove(mo_file)
def patch_nsis():
"""Patch translations into the NSIS installer script"""
languages = [e.name for e in os.scandir(MO_DIR) if e.is_dir()]
languages.sort()
# Pre-load all NSIS translations once
nsis_translations = {
lcode: gettext.translation(DOMAIN_NSIS, MO_DIR, [lcode], fallback=False) for lcode in languages
}
output_lines = []
with open(NSIS, encoding="utf-8-sig") as src:
for line in src:
if not (m := RE_NSIS.search(line)):
output_lines.append(line)
continue
leader, item, rest = m.group(1), m.group(2), m.group(3)
langname = m.group(4).upper()
text = m.group(5).strip('"\n')
# Drop previously generated non-English langstrings; they will be regenerated below
if langname != "ENGLISH":
continue
# Write back the English line, then append a translation for each language
output_lines.append(line)
text = text.replace('$\\"', '"').replace("$\\", "\\")
for lcode in languages:
if lcode == "en":
continue
if lcode not in NSIS_LANGUAGE_NAMES:
raise RuntimeWarning(f"Unsupported language {lcode}! Check NSIS_LANGUAGE_NAMES how to add.")
lng_name = NSIS_LANGUAGE_NAMES[lcode].upper()
if item == "MsgLangCode":
# The language code will be stored in the registry
text_trans = lcode
else:
text_trans = nsis_translations[lcode].gettext(text)
text_trans = text_trans.replace("\r", "").replace("\n", "\\r\\n")
text_trans = text_trans.replace("\\", "$\\").replace('"', '$\\"')
output_lines.append(f'{leader}{item}{rest}{lng_name}}} "{text_trans}"\n')
# Write with UTF-8 BOM so NSIS picks up the translations correctly
with open(NSIS, "w", encoding="utf-8") as dst:
dst.write("\ufeff")
dst.writelines(output_lines)
def main():
if not os.path.exists("po"):
raise RuntimeError("Make sure to run from root directory of SABnzbd")
print("Email MO files")
process_po_folder(DOMAIN_EMAIL, PO_EMAIL_DIR)
print("Create email templates from MO files")
make_templates()
print("Main program MO files")
process_po_folder(DOMAIN, PO_DIR)
if os.path.exists(NSIS):
print("NSIS MO files")
process_po_folder(DOMAIN_NSIS, PO_NSIS_DIR)
print("Patch NSIS script")
patch_nsis()
print("Remove temporary MO files")
remove_mo_files()
if __name__ == "__main__":
main()