diff --git a/src/Tools/updatets.py b/src/Tools/updatets.py index 2c24556a9c..0398d29e63 100755 --- a/src/Tools/updatets.py +++ b/src/Tools/updatets.py @@ -26,6 +26,7 @@ #*************************************************************************** # Changelog: +# 0.5 Add support for Qt 6 lupdate (which fixes MANY bugs and should be preferred) # 0.4 Refactor to always try both C++ and Python translations # 0.3 User-friendly output # Corrections @@ -47,10 +48,13 @@ Authors: Licence: LGPL Version: - 0.4 + 0.5 """ import os, sys +import subprocess +import re +import pathlib directories = [ {"tsname":"FreeCAD", "workingdir":"./src/Gui", "tsdir":"Language"}, @@ -84,46 +88,63 @@ QMAKE = "" LUPDATE = "" PYLUPDATE = "" LCONVERT = "" +QT_VERSION = "" def find_tools(noobsolete=True): print(Usage + "\nFirst, lets find all necessary tools on your system") - global QMAKE, LUPDATE, PYLUPDATE, LCONVERT - if (os.system("qmake -version") == 0): - QMAKE = "qmake" - elif (os.system("qmake-qt5 -version") == 0): - QMAKE = "qmake-qt5" + global QMAKE, LUPDATE, PYLUPDATE, LCONVERT, QT_VERSION + + lupdate_version = subprocess.check_output(f"lupdate -version").decode() + QT_VERSION = int(re.search(r'.* ([456])\.', lupdate_version).group(1)) + + if QT_VERSION < 6: + + if (os.system("lupdate -version") == 0): + LUPDATE = "lupdate" + # TODO: we suppose lupdate is a symlink to lupdate-qt4 for now + if noobsolete: + LUPDATE += " -no-obsolete" + elif (os.system("lupdate-qt5 -version") == 0): + LUPDATE = "lupdate-qt5" + if noobsolete: + LUPDATE += " -no-obsolete" + else: + raise Exception("Cannot find lupdate") else: - raise Exception("Cannot find qmake") - if (os.system("lupdate -version") == 0): LUPDATE = "lupdate" - # TODO: we suppose lupdate is a symlink to lupdate-qt4 for now - if noobsolete: - LUPDATE += " -no-obsolete" - elif (os.system("lupdate-qt5 -version") == 0): - LUPDATE = "lupdate-qt5" - if noobsolete: - LUPDATE += " -no-obsolete" + + lupdate_version = subprocess.check_output(f"{LUPDATE} -version").decode() + QT_VERSION = int(re.search(r'.* ([456])\.', lupdate_version).group(1)) + + if QT_VERSION < 6: + if (os.system("qmake -version") == 0): + QMAKE = "qmake" + elif (os.system("qmake-qt5 -version") == 0): + QMAKE = "qmake-qt5" + else: + raise Exception("Cannot find qmake") + if (os.system("pylupdate -version") == 0): + PYLUPDATE = "pylupdate" + elif (os.system("pylupdate5 -version") == 0): + PYLUPDATE = "pylupdate5" + if noobsolete: + PYLUPDATE += " -noobsolete" + elif (os.system("pylupdate4 -version") == 0): + PYLUPDATE = "pylupdate4" + if noobsolete: + PYLUPDATE += " -noobsolete" + elif (os.system("pyside2-lupdate -version") == 0): + PYLUPDATE = "pyside2-lupdate" + raise Exception("Please do not use pyside2-lupdate at the moment, as it shows encoding problems. Please use pylupdate5 instead.") + else: + raise Exception("Cannot find pylupdate") else: - raise Exception("Cannot find lupdate") - if (os.system("pylupdate -version") == 0): - PYLUPDATE = "pylupdate" - elif (os.system("pylupdate5 -version") == 0): - PYLUPDATE = "pylupdate5" - if noobsolete: - PYLUPDATE += " -noobsolete" - elif (os.system("pylupdate4 -version") == 0): - PYLUPDATE = "pylupdate4" - if noobsolete: - PYLUPDATE += " -noobsolete" - elif (os.system("pyside2-lupdate -version") == 0): - PYLUPDATE = "pyside2-lupdate" - raise Exception("Please do not use pyside2-lupdate at the moment, as it shows encoding problems. Please use pylupdate5 instead.") - else: - raise Exception("Cannot find pylupdate") + QMAKE = "(qmake not needed for Qt 6 and later)" + PYLUPDATE = "(pylupdate not needed for Qt 6 and later)" if (os.system("lconvert -h") == 0): LCONVERT = "lconvert" - if noobsolete: + if noobsolete and QT_VERSION < 6: LCONVERT += " -no-obsolete" else: raise Exception("Cannot find lconvert") @@ -136,10 +157,7 @@ def find_tools(noobsolete=True): def update_translation(entry): - global QMAKE, LUPDATE - print ("\n\n=============================================") - print (f"EXTRACTING STRINGS FOR {entry['tsname']}") - print ("=============================================") + global QMAKE, LUPDATE, LCONVERT, QT_VERSION cur = os.getcwd() log_redirect = f" 2>> {cur}/tsupdate_stderr.log 1>> {cur}/tsupdate_stdout.log" os.chdir(entry["workingdir"]) @@ -147,28 +165,57 @@ def update_translation(entry): project_filename = entry["tsname"] + ".pro" tsBasename = os.path.join(entry["tsdir"],entry["tsname"]) - execline = [] - execline.append (f"touch dummy_cpp_file_for_lupdate.cpp") #lupdate requires at least one source file to process the UI files - execline.append (f"{QMAKE} -project -o {project_filename} -r") - execline.append (f"{LUPDATE} {project_filename} -ts {tsBasename}.ts {log_redirect}") - execline.append (f"sed 's/.*<\/translation>/<\/translation>/g' {tsBasename}.ts > {tsBasename}.ts.temp") - execline.append (f"mv {tsBasename}.ts.temp {tsBasename}.ts") - execline.append (f"{PYLUPDATE} `find ./ -name \"*.py\"` -ts {tsBasename}py.ts {log_redirect}") - execline.append (f"{LCONVERT} -i {tsBasename}py.ts {tsBasename}.ts -o {tsBasename}.ts {log_redirect}") - execline.append (f"rm {tsBasename}py.ts") - execline.append (f"rm dummy_cpp_file_for_lupdate.cpp") - print(f"Executing commands in {entry['workingdir']}:") - for line in execline: - print (line) - os.system(line) - print() + if QT_VERSION < 6: + print ("\n\n=============================================") + print (f"EXTRACTING STRINGS FOR {entry['tsname']}") + print ("=============================================",flush=True) + execline = [] + execline.append (f"touch dummy_cpp_file_for_lupdate.cpp") #lupdate 5.x requires at least one source file to process the UI files + execline.append (f"{QMAKE} -project -o {project_filename} -r") + execline.append (f"{LUPDATE} {project_filename} -ts {tsBasename}.ts {log_redirect}") + execline.append (f"sed 's/.*<\/translation>/<\/translation>/g' {tsBasename}.ts > {tsBasename}.ts.temp") + execline.append (f"mv {tsBasename}.ts.temp {tsBasename}.ts") + execline.append (f"{PYLUPDATE} `find ./ -name \"*.py\"` -ts {tsBasename}py.ts {log_redirect}") + execline.append (f"{LCONVERT} -i {tsBasename}py.ts {tsBasename}.ts -o {tsBasename}.ts {log_redirect}") + execline.append (f"rm {tsBasename}py.ts") + execline.append (f"rm dummy_cpp_file_for_lupdate.cpp") + + print(f"Executing commands in {entry['workingdir']}:") + for line in execline: + print (line) + os.system(line) + print() + + os.remove(project_filename) + # lupdate creates json files since Qt5.something. Remove them here too + for jsonfile in [f for f in os.listdir(".") if f.endswith(".json")]: + if not jsonfile in existingjsons: + os.remove(jsonfile) + + elif QT_VERSION == 6: + # In Qt6, QMake project files are deprecated, and lupdate directly scans for source files. The same executable is + # used for all supported programming languages, so it's just a single function call + + # For Windows compatibility, do most of the work in Python: + try: + print (f"Extracting recursively for {entry['tsname']} starting at {entry['workingdir']} into {tsBasename}.ts",flush=True) + p = subprocess.run([LUPDATE, "./","-I","./", "-recursive", "-ts", f"{tsBasename}.ts"], capture_output=True, timeout=60) + except Exception as e: + print(str(e)) + + with open (f"{cur}/tsupdate_stdout.log","a") as f: + f.write(p.stdout.decode()) + print(p.stdout.decode()) + with open (f"{cur}/tsupdate_stderr.log","a") as f: + f.write(p.stderr.decode()) + + # Strip out obsolete strings, and make sure there are no translations in the main *.ts file + subprocess.run([LCONVERT, "-drop-translations", "-i", f"{tsBasename}.ts", "-o", f"{tsBasename}.ts"]) + + else: + print("ERROR: unrecognized version of lupdate -- found Qt {QT_VERSION}, we only support 4, 5 and 6") + exit(1) - os.remove(project_filename) - # lupdate creates json files since Qt5.something. Remove them here too - for jsonfile in [f for f in os.listdir(".") if f.endswith(".json")]: - if not jsonfile in existingjsons: - os.remove(jsonfile) - os.chdir(cur) def main(mod=None):