From 20778f3d10f16888c836e644b52a299cd7467cef Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 16 Jan 2023 15:46:25 +0100 Subject: [PATCH 01/14] Move translation scripts to Cura from Uranium CURA-9814 --- scripts/translations/createjsoncontext.py | 103 +++++++++++++++++++ scripts/translations/createkeypair.py | 47 +++++++++ scripts/translations/createplugincontext.py | 72 +++++++++++++ scripts/translations/extract-all | 13 +++ scripts/translations/extract-json | 42 ++++++++ scripts/translations/extract-messages | 42 ++++++++ scripts/translations/extract-plugins | 14 +++ scripts/translations/extract-python | 12 +++ scripts/translations/extract-tr-strings | 73 +++++++++++++ scripts/translations/pirate.py | 108 ++++++++++++++++++++ scripts/translations/pirateofdoom.py | 77 ++++++++++++++ 11 files changed, 603 insertions(+) create mode 100644 scripts/translations/createjsoncontext.py create mode 100644 scripts/translations/createkeypair.py create mode 100644 scripts/translations/createplugincontext.py create mode 100755 scripts/translations/extract-all create mode 100755 scripts/translations/extract-json create mode 100755 scripts/translations/extract-messages create mode 100755 scripts/translations/extract-plugins create mode 100755 scripts/translations/extract-python create mode 100755 scripts/translations/extract-tr-strings create mode 100644 scripts/translations/pirate.py create mode 100644 scripts/translations/pirateofdoom.py diff --git a/scripts/translations/createjsoncontext.py b/scripts/translations/createjsoncontext.py new file mode 100644 index 0000000000..a9f6019005 --- /dev/null +++ b/scripts/translations/createjsoncontext.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Copyright 2014 Burkhard Lück + +Permission to use, copy, modify, and distribute this software +and its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name of the author not be used in +advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +The author disclaim all warranties with regard to this +software, including all implied warranties of merchantability +and fitness. In no event shall the author be liable for any +special, indirect or consequential damages or any damages +whatsoever resulting from loss of use, data or profits, whether +in an action of contract, negligence or other tortious action, +arising out of or in connection with the use or performance of +this software. +""" + +# This script generates a POT file from a JSON settings file. It +# has been adapted from createjsoncontext.py of KDE's translation +# scripts. It extracts the "label" and "description" values of +# the JSON file using the structure as used by Uranium settings files. + +import sys +import os +import json +import time +import os.path +import collections + +debugoutput = False #set True to print debug output in scripty's logs + +basedir = sys.argv[-1] +pottxt = "" + +def appendMessage(file, setting, field, value): + global pottxt + pottxt += "#: {0}\nmsgctxt \"{1} {2}\"\nmsgid \"{3}\"\nmsgstr \"\"\n\n".format(file, setting, field, value.replace("\n", "\\n").replace("\"", "\\\"")) + +def processSettings(file, settings): + for name, value in settings.items(): + appendMessage(file, name, "label", value["label"]) + if "description" in value: + appendMessage(file, name, "description", value["description"]) + + if "warning_description" in value: + appendMessage(file, name, "warning_description", value["warning_description"]) + + if "error_description" in value: + appendMessage(file, name, "error_description", value["error_description"]) + + if "options" in value: + for item, description in value["options"].items(): + appendMessage(file, name, "option {0}".format(item), description) + + if "children" in value: + processSettings(file, value["children"]) + +def potheader(): + headertxt = "#, fuzzy\n" + headertxt += "msgid \"\"\n" + headertxt += "msgstr \"\"\n" + headertxt += "\"Project-Id-Version: Uranium json setting files\\n\"\n" + headertxt += "\"Report-Msgid-Bugs-To: plugins@ultimaker.com\\n\"\n" + headertxt += "\"POT-Creation-Date: %s+0000\\n\"\n" %time.strftime("%Y-%m-%d %H:%M") + headertxt += "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n" + headertxt += "\"Last-Translator: FULL NAME \\n\"\n" + headertxt += "\"Language-Team: LANGUAGE\\n\"\n" + headertxt += "\"MIME-Version: 1.0\\n\"\n" + headertxt += "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" + headertxt += "\"Content-Transfer-Encoding: 8bit\\n\"\n" + headertxt += "\n" + return headertxt + +if len(sys.argv) < 3: + print("wrong number of args: %s" % sys.argv) + print("\nUsage: python %s jsonfilenamelist basedir" % os.path.basename(sys.argv[0])) +else: + jsonfilename = sys.argv[1] + basedir = sys.argv[2] + outputfilename = sys.argv[3] + + with open(jsonfilename, "r", encoding = "utf-8") as data_file: + error = False + + jsondatadict = json.load(data_file, object_pairs_hook=collections.OrderedDict) + if "settings" not in jsondatadict: + print("No settings item found, nothing to translate") + exit(1) + + processSettings(jsonfilename.replace(basedir, ""), jsondatadict["settings"]) + + if pottxt != "": + with open(outputfilename, "w", encoding = "utf-8") as output_file: + output_file.write(potheader()) + output_file.write(pottxt) diff --git a/scripts/translations/createkeypair.py b/scripts/translations/createkeypair.py new file mode 100644 index 0000000000..e01c9c2a0b --- /dev/null +++ b/scripts/translations/createkeypair.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import argparse +from typing import Optional +import sys + +from UM.Trust import TrustBasics + +# Default arguments, if arguments to the script are omitted, these values are used: +DEFAULT_PRIVATE_KEY_PATH = "./private_key.pem" +DEFAULT_PUBLIC_KEY_PATH = "./public_key.pem" +DEFAULT_PASSWORD = "" + + +def createAndStoreNewKeyPair(private_filename: str, public_filename: str, optional_password: Optional[str]) -> None: + """Creates a new public and private key, and saves them to the provided filenames. + + See also 'Trust.py' in the main library and the related scripts; 'signfile.py', 'signfolder.py' in this folder. + + :param private_filename: Filename to save the private key to. + :param public_filename: Filename to save the public key to. + :param optional_password: Private keys can have a password (or not). + """ + + password = None if optional_password == "" else optional_password + private_key, public_key = TrustBasics.generateNewKeyPair() + TrustBasics.saveKeyPair(private_key, private_filename, public_filename, password) + + +def mainfunc(): + """Arguments: + + `-k ` or `--private ` will store the generated private key to + `-p ` or `--public ` will store the generated public key to + `-w ` or `--password ` will give the private key a password (none if omitted, which is default) + """ + + parser = argparse.ArgumentParser() + parser.add_argument("-k", "--private", type = str, default = DEFAULT_PRIVATE_KEY_PATH) + parser.add_argument("-p", "--public", type = str, default = DEFAULT_PUBLIC_KEY_PATH) + parser.add_argument("-w", "--password", type = str, default = DEFAULT_PASSWORD) + args = parser.parse_args() + createAndStoreNewKeyPair(args.private, args.public, args.password) + + +if __name__ == "__main__": + sys.exit(mainfunc()) diff --git a/scripts/translations/createplugincontext.py b/scripts/translations/createplugincontext.py new file mode 100644 index 0000000000..25a086357e --- /dev/null +++ b/scripts/translations/createplugincontext.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Copyright 2014 Burkhard Lück + +Permission to use, copy, modify, and distribute this software +and its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name of the author not be used in +advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +The author disclaim all warranties with regard to this +software, including all implied warranties of merchantability +and fitness. In no event shall the author be liable for any +special, indirect or consequential damages or any damages +whatsoever resulting from loss of use, data or profits, whether +in an action of contract, negligence or other tortious action, +arising out of or in connection with the use or performance of +this software. +""" + +# This script generates a POT file from a JSON settings file. It +# has been adapted from createjsoncontext.py of KDE's translation +# scripts. It extracts the "label" and "description" values of +# the JSON file using the structure as used by Uranium settings files. + +import sys +import os.path +import collections +import json + +debugoutput = False #set True to print debug output in scripty's logs + +basedir = sys.argv[-1] +pottxt = "" + + +def appendMessage(file, field, value): + global pottxt + pottxt += "#: {0}\nmsgctxt \"{1}\"\nmsgid \"{2}\"\nmsgstr \"\"\n\n".format(file, field, value.replace("\n", "\\n").replace("\"", "\\\"")) + + +if len(sys.argv) < 3: + print("wrong number of args: %s" % sys.argv) + print("\nUsage: python %s jsonfilenamelist basedir" % os.path.basename(sys.argv[0])) +else: + json_filename = sys.argv[1] + basedir = sys.argv[2] + output_filename = sys.argv[3] + + with open(json_filename, "r", encoding = "utf-8") as data_file: + error = False + + jsondatadict = json.load(data_file, object_pairs_hook=collections.OrderedDict) + if "name" not in jsondatadict or ("api" not in jsondatadict and "supported_sdk_versions" not in jsondatadict) or "version" not in jsondatadict: + print("The plugin.json file found on %s is invalid, ignoring it" % json_filename) + exit(1) + + file = json_filename.replace(basedir, "") + + if "description" in jsondatadict: + appendMessage(file, "description", jsondatadict["description"]) + if "name" in jsondatadict: + appendMessage(file, "name", jsondatadict["name"]) + + if pottxt != "": + with open(output_filename, "a", encoding = "utf-8") as output_file: + output_file.write(pottxt) diff --git a/scripts/translations/extract-all b/scripts/translations/extract-all new file mode 100755 index 0000000000..ea0ac63f58 --- /dev/null +++ b/scripts/translations/extract-all @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Use xgettext to extract all strings from a set of python files. +# Argument 1 is the directory to search for python files, argument 2 +# is the destination file. +# +# This script will extract strings marked using i18n or i18nc methods. +# See UM/i18n.py for the relevant methods. +# +dir=$1 +dest=$2 +xgettext --from-code=UTF-8 --language=python -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $(find -L "$dir" -name \*.py) +xgettext --from-code=UTF-8 --join-existing --language=javascript -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $(find -L "$dir" -name \*.qml) diff --git a/scripts/translations/extract-json b/scripts/translations/extract-json new file mode 100755 index 0000000000..01048bb5cb --- /dev/null +++ b/scripts/translations/extract-json @@ -0,0 +1,42 @@ +#! /bin/bash + +# Extract strings from a list of JSON files. +# +# This script will extract strings from all JSON files in the directory +# passed as first argument. The second argument is the destination +# directory for the extracted message file. +# +# This script uses createjsoncontext to generate the actual message file +# from the JSON file. +# +# This script is based on handle_json_files.sh from KDE's translation +# scripts. +# handle_json_files.sh is copyright 2014 Burkhard Lück +scriptdir=$(dirname $0) + +extract() { + basedir=$1 + dest=$2 + file=$3 + + python3 $scriptdir/createjsoncontext.py $file $basedir json.$$.tmp + if test $? -eq 1; then + return + fi + + echo "Extracted messages from $file" + + msguniq --to-code=UTF-8 -o json.$$ json.$$.tmp + if test -f json.$$; then + destfile="$dest/$(basename $file).pot" + mv json.$$ $destfile + fi + rm -f json.$$ json.$$.tmp +} + +dir=$1; shift +dest=$1; shift + +for file in $(find -L "$dir" -name *.json | grep -v 'tests'); do + extract $dir $dest $file +done diff --git a/scripts/translations/extract-messages b/scripts/translations/extract-messages new file mode 100755 index 0000000000..315009bcdf --- /dev/null +++ b/scripts/translations/extract-messages @@ -0,0 +1,42 @@ +#!/bin/bash + +scriptdir=$(dirname $0) +basedir=$1 +catalogname=$2 + +# This script processes the source files using several other scripts to extract strings. +# The strings are extracted to $basedir/resources/i18n/ and then post processed. After that +# It generates english translation files and testing files that are pre- and sufficed with +# xx. These can be used by setting the LANGUAGE environment variable to x-test. +# +# This script uses extract-tr-strings to extract strings from QML files, extract-json to +# extract strings from JSON files and extract-python to extract strings from Python files. +# +mkdir -p $basedir/resources/i18n +$scriptdir/extract-json $basedir/resources/definitions/ $basedir/resources/i18n +$scriptdir/extract-all $basedir $basedir/resources/i18n/$catalogname.pot +$scriptdir/extract-plugins $basedir/plugins/ $basedir/resources/i18n/$catalogname.pot +msgconv --to-code=UTF-8 $basedir/resources/i18n/$catalogname.pot -o $basedir/resources/i18n/$catalogname.pot + +for pot in $basedir/resources/i18n/*.pot; do + filename=$(basename $pot) + + dir=$basedir/resources/i18n/en_US + mkdir -p $dir + po=$dir/${filename/.pot/.po} + msginit --no-translator -l en_US -i $pot -o $po + + dir=$basedir/resources/i18n/x-test + mkdir -p $dir + po=$dir/${filename/.pot/.po} + msginit --no-translator -l en_US -i $pot -o $po + msgfilter --keep-header -i $po -o $po sed -e "s/.*/xx&xx/" + msgfilter -i $po -o $po sed -e "s/Language: en_US/Language: x-test/" + + #Auto-translate the translation files to Pirate. + dir=$basedir/resources/i18n/en_7S + mkdir -p $dir + po=$dir/${filename/.pot/.po} + python3 $scriptdir/pirate.py $pot $po + echo Created $po. +done diff --git a/scripts/translations/extract-plugins b/scripts/translations/extract-plugins new file mode 100755 index 0000000000..d5b3674968 --- /dev/null +++ b/scripts/translations/extract-plugins @@ -0,0 +1,14 @@ +#! /bin/bash + +# Extract strings from all plugins +# + +scriptdir=$(dirname $0) +dir=$1; shift +dest=$1; shift + +for file in $(find -L "$dir" -name plugin.json | grep -v 'tests'); do + python3 $scriptdir/createplugincontext.py $file $dir $dest +done + + diff --git a/scripts/translations/extract-python b/scripts/translations/extract-python new file mode 100755 index 0000000000..34cf332f99 --- /dev/null +++ b/scripts/translations/extract-python @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Use xgettext to extract all strings from a set of python files. +# Argument 1 is the directory to search for python files, argument 2 +# is the destination file. +# +# This script will extract strings marked using i18n or i18nc methods. +# See UM/i18n.py for the relevant methods. +# +dir=$1 +dest=$2 +xgettext --from-code=UTF-8 --language=python -ki18n:1 -ki18nc:1c,2 -o $dest $(find -L "$dir" -name \*.py) diff --git a/scripts/translations/extract-tr-strings b/scripts/translations/extract-tr-strings new file mode 100755 index 0000000000..378f50bb40 --- /dev/null +++ b/scripts/translations/extract-tr-strings @@ -0,0 +1,73 @@ +#!/bin/sh +# +# This script extracts strings from a set of files using Qt's translation system. +# It then converts the extracted .ts file in a .po file that can be used with +# tools that expect Gettext's po file format. +# +# This script was adapted from extract-tr-strings from KDE's translation scripts. +# extract-tr-strings is Copyright 2014 Aurélien Gateau +set -e + +OLD_PWD=$PWD +cd $(dirname $0) +SCRIPTS_DIR=$PWD +cd $OLD_PWD + +LUPDATE=${LUPDATE:-lupdate} +LCONVERT=${LCONVERT:-lconvert} + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +usage() { + cat <= 2: #There's an ID on this line! + line = line[line.find('"') + 1:] #Strip everything before the first ". + line = line[:line.rfind('"')] #And after the last ". + + if state == "ctxt": + last_ctxt += line #What's left is the context. + elif state == "idplural": + last_id_plural += line #Or the plural ID. + elif state == "id": + last_id += line #Or the ID. + elif state == "str": + last_str += line #Or the actual string. + +for key, _ in translations.items(): + context, english, english_plural = key + pirate = translate(english) + pirate_plural = translate(english_plural) + translations[key] = (pirate, pirate_plural) + +with open(po_file, "w", encoding = "utf-8") as f: + f.write("""msgid "" +msgstr "" +"Project-Id-Version: Pirate\\n" +"Report-Msgid-Bugs-To: plugins@ultimaker.com\\n" +"POT-Creation-Date: 1492\\n" +"PO-Revision-Date: 1492\\n" +"Last-Translator: Ghostkeeper and Awhiemstra\\n" +"Language-Team: Ghostkeeper and Awhiemstra\\n" +"Language: Pirate\\n" +"Lang-Code: en\\n" +"Country-Code: en_7S\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\\n" +""") + for key, value in translations.items(): + context, english, english_plural = key + pirate, pirate_plural = value + f.write('msgctxt "{context}"\n'.format(context = context)) + if english_plural == "": #No plurals in this item. + f.write('msgid "{english}"\n'.format(english = english)) + f.write('msgstr "{pirate}"\n'.format(pirate = pirate)) + else: + f.write('msgid "{english}"\n'.format(english = english)) + f.write('msgid_plural "{english_plural}"\n'.format(english_plural = english_plural)) + f.write('msgstr[0] "{pirate}"\n'.format(pirate = pirate)) + f.write('msgstr[1] "{pirate_plural}"\n'.format(pirate_plural = pirate_plural)) + f.write("\n") #Empty line. \ No newline at end of file diff --git a/scripts/translations/pirateofdoom.py b/scripts/translations/pirateofdoom.py new file mode 100644 index 0000000000..e8b8a28958 --- /dev/null +++ b/scripts/translations/pirateofdoom.py @@ -0,0 +1,77 @@ +pirate = { + "build plate": "deck", + "buildplate": "deck", + "quit": "abandon ship", + "back": "avast", + "nozzle": "muzzle", + "nozzles": "muzzles", + "extruder": "cannon", + "extruders": "cannons", + "yes": "aye", + "no": "nay", + "loading": "haulin'", + "you": "ye", + "you're": "ye are", + "ok": "aye", + "machine": "ship", + "machines": "ships", + "mm/s²": "knots/s", + "mm/s": "knots", + "printer": "ship", + "printers": "ships", + "view": "spyglass", + "support": "peg legs", + "fan": "wind", + "file": "treasure", + "file(s)": "treasure(s)", + "files": "treasures", + "profile": "map", + "profiles": "maps", + "setting": "knob", + "settings": "knobs", + "shield": "sail", + "your": "yer", + "the": "th'", + "travel": "journey", + "wireframe": "ropey", + "wire": "rope", + "are": "be", + "is": "be", + "there": "thar", + "not": "nay", + "delete": "send to Davy Jones' locker", + "remove": "send to Davy Jones' locker", + "print": "scribble", + "printing": "scribblin'", + "load": "haul", + "connect to": "board", + "connecting": "boarding", + "collects": "hoards", + "prime tower": "buoy", + "change log": "captain's log", + "my": "me", + "removable drive": "life boat", + "print core": "scribbler", + "printcore": "scribbler", + "abort": ["maroon", "abandon"], + "aborting": ["marooning", "abandoning"], + "aborted": ["marooned", "abandoned"], + "connected": ["anchored", "moored"], + "developer": "scurvy dog", + "wizard": "cap'n", + "active leveling": "keelhauling", + "download": "plunder", + "downloaded": "plundered", + "caution hot surface": "fire in the hole!", + "type": "sort", + "spool": "barrel", + "surface": "lacquer", + "zigzag": "heave-to", + "bottom": "bilge", + "top": "deck", + "ironing": "deck swabbing", + "adhesion": "anchorage", + "blob": "barnacle", + "blobs": "barnacles", + "slice": "set sail", +} From d24bd3f979ce53d32202d4741069abc5a638ac77 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 16 Jan 2023 15:50:19 +0100 Subject: [PATCH 02/14] Use a loop to go over files instead of passing them all in as arguments so that we don't go over the argument size limit. CURA-9814 --- scripts/translations/extract-all | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/translations/extract-all b/scripts/translations/extract-all index ea0ac63f58..4c5fa09219 100755 --- a/scripts/translations/extract-all +++ b/scripts/translations/extract-all @@ -9,5 +9,14 @@ # dir=$1 dest=$2 -xgettext --from-code=UTF-8 --language=python -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $(find -L "$dir" -name \*.py) -xgettext --from-code=UTF-8 --join-existing --language=javascript -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $(find -L "$dir" -name \*.qml) +for f in $(find -L "$dir" -name \*.py) +do + echo "Extracting strings from python file: $f" + xgettext --from-code=UTF-8 --language=python -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f +done + +for f in $(find -L "$dir" -name \*.qml) +do + echo "Extracting strings from qml file: $f" + xgettext --from-code=UTF-8 --join-existing --language=javascript -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f +done From ec26cd509ea66e274e000c8cd4e61eb997f60a61 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 16 Jan 2023 15:50:52 +0100 Subject: [PATCH 03/14] Extend comment CURA-9814 --- scripts/translations/extract-json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/translations/extract-json b/scripts/translations/extract-json index 01048bb5cb..00cef1e866 100755 --- a/scripts/translations/extract-json +++ b/scripts/translations/extract-json @@ -1,6 +1,6 @@ #! /bin/bash -# Extract strings from a list of JSON files. +# Extract strings from all JSON files in a directory into files with matching names ending with .pot. # # This script will extract strings from all JSON files in the directory # passed as first argument. The second argument is the destination From c8ba9bccdb614933c179d3fa490c9cbcf0bd3c1e Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 16 Jan 2023 15:52:53 +0100 Subject: [PATCH 04/14] Remove xtest as we will no longer be running this locally so it is not useful. CURA-9814 --- scripts/translations/extract-messages | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/translations/extract-messages b/scripts/translations/extract-messages index 315009bcdf..31014c8e90 100755 --- a/scripts/translations/extract-messages +++ b/scripts/translations/extract-messages @@ -26,13 +26,6 @@ for pot in $basedir/resources/i18n/*.pot; do po=$dir/${filename/.pot/.po} msginit --no-translator -l en_US -i $pot -o $po - dir=$basedir/resources/i18n/x-test - mkdir -p $dir - po=$dir/${filename/.pot/.po} - msginit --no-translator -l en_US -i $pot -o $po - msgfilter --keep-header -i $po -o $po sed -e "s/.*/xx&xx/" - msgfilter -i $po -o $po sed -e "s/Language: en_US/Language: x-test/" - #Auto-translate the translation files to Pirate. dir=$basedir/resources/i18n/en_7S mkdir -p $dir From 3478c0af6cbecaef6292fe006753658480b696cd Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 16 Jan 2023 15:56:42 +0100 Subject: [PATCH 05/14] Remove unused translation script CURA-9814 --- scripts/translations/extract-tr-strings | 73 ------------------------- 1 file changed, 73 deletions(-) delete mode 100755 scripts/translations/extract-tr-strings diff --git a/scripts/translations/extract-tr-strings b/scripts/translations/extract-tr-strings deleted file mode 100755 index 378f50bb40..0000000000 --- a/scripts/translations/extract-tr-strings +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh -# -# This script extracts strings from a set of files using Qt's translation system. -# It then converts the extracted .ts file in a .po file that can be used with -# tools that expect Gettext's po file format. -# -# This script was adapted from extract-tr-strings from KDE's translation scripts. -# extract-tr-strings is Copyright 2014 Aurélien Gateau -set -e - -OLD_PWD=$PWD -cd $(dirname $0) -SCRIPTS_DIR=$PWD -cd $OLD_PWD - -LUPDATE=${LUPDATE:-lupdate} -LCONVERT=${LCONVERT:-lconvert} - -die() { - echo "ERROR: $*" >&2 - exit 1 -} - -usage() { - cat < Date: Mon, 16 Jan 2023 16:04:40 +0100 Subject: [PATCH 06/14] make print statement more informative CURA-9814 --- scripts/translations/createjsoncontext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/translations/createjsoncontext.py b/scripts/translations/createjsoncontext.py index a9f6019005..a4f02012d4 100644 --- a/scripts/translations/createjsoncontext.py +++ b/scripts/translations/createjsoncontext.py @@ -92,7 +92,7 @@ else: jsondatadict = json.load(data_file, object_pairs_hook=collections.OrderedDict) if "settings" not in jsondatadict: - print("No settings item found, nothing to translate") + print(f"Nothing to translate in file: {jsondatadict}") exit(1) processSettings(jsonfilename.replace(basedir, ""), jsondatadict["settings"]) From bcf715193add2078f9a20a9ed82e3c8d4f1ec1be Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Tue, 17 Jan 2023 17:55:51 +0100 Subject: [PATCH 07/14] Use py file instead for extracting strings --- scripts/translations/extract-all | 5 +- scripts/translations/extract_strings.py | 135 ++++++++++++++++++++++++ scripts/translations/pirate.py | 108 ------------------- scripts/translations/pirateofdoom.py | 77 -------------- 4 files changed, 138 insertions(+), 187 deletions(-) create mode 100644 scripts/translations/extract_strings.py delete mode 100644 scripts/translations/pirate.py delete mode 100644 scripts/translations/pirateofdoom.py diff --git a/scripts/translations/extract-all b/scripts/translations/extract-all index 4c5fa09219..98e748f4ee 100755 --- a/scripts/translations/extract-all +++ b/scripts/translations/extract-all @@ -9,14 +9,15 @@ # dir=$1 dest=$2 +touch $dest for f in $(find -L "$dir" -name \*.py) do echo "Extracting strings from python file: $f" - xgettext --from-code=UTF-8 --language=python -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f + xgettext --from-code=UTF-8 --join-existing --sort-by-file --language=python -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f done for f in $(find -L "$dir" -name \*.qml) do echo "Extracting strings from qml file: $f" - xgettext --from-code=UTF-8 --join-existing --language=javascript -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f + xgettext --from-code=UTF-8 --join-existing --sort-by-file --language=javascript -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f done diff --git a/scripts/translations/extract_strings.py b/scripts/translations/extract_strings.py new file mode 100644 index 0000000000..30608eeeb4 --- /dev/null +++ b/scripts/translations/extract_strings.py @@ -0,0 +1,135 @@ +# Copyright (c) 2023 UltiMaker. +# Cura is released under the terms of the LGPLv3 or higher. + +import argparse +import os +import subprocess +from os.path import isfile + +from pathlib import Path + +def extract_all_strings(root_path: Path, script_path: Path, translations_root_path: Path, all_strings_pot_path: Path): + """ Extracts all strings into a pot file with empty translations. + + Strings are extracted everywhere that i18n is used in python and qml in the project. It also checks the project + for JSON files with 'settings' in the root node and extracts these for translation as well. + + @param root_path: The root path of the project. This is the root for string searching. + @param script_path: The location of the bash scripts used for translating. + @param translations_root_path: The root of the translations folder (resources/i18n). + @param all_strings_pot_path: The path of the pot file where all strings will be outputted (resources/i8n/cura.pot). + """ + + # # Extract the setting strings from any json file with settings at its root + # extract_json_arguments = [ + # script_path.joinpath("extract-json"), + # root_path.joinpath("resources", "definitions"), + # translations_root_path + # ] + # subprocess.run(extract_json_arguments) + # + # Extract all strings from qml and py files + extract_qml_py_arguments = [ + script_path.joinpath("extract-all"), + root_path, + all_strings_pot_path + ] + subprocess.run(extract_qml_py_arguments) + + # Extract all the name and description from all plugins + extract_plugin_arguments = [ + script_path.joinpath("extract-plugins"), + root_path.joinpath("plugins"), + all_strings_pot_path + ] + subprocess.run(extract_plugin_arguments) + + # Convert the output file to utf-8 + convert_encoding_arguments = [ + "msgconv", + "--to-code=UTF-8", + all_strings_pot_path, + "-o", + all_strings_pot_path + ] + subprocess.run(convert_encoding_arguments) + + +def update_po_files_all_languages(translation_root_path: Path) -> None: + """ Updates all po files in translation_root_path with new strings mapped to blank translations. + + This will take all newly generated po files in the root of the translations path (i18n/cura.pot, i18n/fdmextruder.json.def.pot) + and merge them with the existing po files for every language. This will create new po files with empty translations + for all new words added to the project. + + @param translation_root_path: Root of the translations folder (resources/i18n). + """ + new_pot_files = [] + + for file in os.listdir(translation_root_path): + path = translations_root_path.joinpath(file) + if path.suffix == ".pot": + new_pot_files.append(path) + print(new_pot_files) + + for directory, _, po_files in os.walk(translation_root_path): + print(directory) + print(po_files) + for pot in new_pot_files: + + po_filename = pot.name.rstrip("t") + if po_filename not in po_files: + continue # We only want to merge files that have matching names + + pot_file = pot + po_file = Path(directory, po_filename).absolute() + + # # Initialize the new po file + # init_files_arguments = [ + # "msginit", + # "--no-wrap", + # "--no-translator", + # "-l", language, + # "-i", pot_file, + # "-o", po_file + # ] + # + # subprocess.run(init_files_arguments) + + merge_files_arguments = [ + "msgmerge", + "--no-wrap", + "--no-fuzzy-matching", + "--update", + "--sort-by-file", # Sort by file location, this is better than pure sorting for translators + po_file, # po file that will be updated + pot_file # source of new strings + ] + + subprocess.run(merge_files_arguments) + + return + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Extract strings from project into .po files") + parser.add_argument("root_path", type=str, help="The root of the project to extract translatable strings from") + parser.add_argument("translation_file_name", type=str, help="The .pot file that all strings from python/qml files will be inserted into") + parser.add_argument("script_path", type=str, help="The path containing the scripts for translating files") + args = parser.parse_args() + + root_path = Path(args.root_path) # root of the project + script_path = Path(args.script_path) # location of bash scripts + + # Path where all translation file are + translations_root_path = root_path.joinpath("resources", "i18n") + translations_root_path.mkdir(parents=True, exist_ok=True) # Make sure we have an output path + + all_strings_pot_path = translations_root_path.joinpath(args.translation_file_name) # pot file containing all strings untranslated + + # Clear the output file, otherwise deleted strings will still be in the output. + if os.path.exists(all_strings_pot_path): + os.remove(all_strings_pot_path) + + extract_all_strings(root_path, script_path, translations_root_path, all_strings_pot_path) + update_po_files_all_languages(translations_root_path) diff --git a/scripts/translations/pirate.py b/scripts/translations/pirate.py deleted file mode 100644 index 6c0c170537..0000000000 --- a/scripts/translations/pirate.py +++ /dev/null @@ -1,108 +0,0 @@ -#Creates the Pirate translation files. - -import sys #To get command line arguments. -import pirateofdoom #Contains our translation dictionary. -import re #Case insensitive search and replace. -import random # Take random translation candidates - -pot_file = sys.argv[1] -po_file = sys.argv[2] - -#Translates English to Pirate. -def translate(english): - english = english.replace("&", "") #Pirates don't take shortcuts. - for eng, pir in pirateofdoom.pirate.items(): - matches = list(re.finditer(r"\b" + eng.lower() + r"\b", english.lower())) - matches = [match.start(0) for match in matches] - matches = reversed(sorted(matches)) - for position in matches: - #Make sure the case is correct. - uppercase = english[position].lower() != english[position] - - if isinstance(pir, list): - pir = random.choice(pir) - - first_character = pir[0] - rest_characters = pir[1:] - if uppercase: - first_character = first_character.upper() - else: - first_character = first_character.lower() - pir = first_character + rest_characters - - english = english[:position] + pir + english[position + len(eng):] - return english - -translations = {} - -last_id = "" -last_id_plural = "" -last_ctxt = "" -last_str = "" -state = "unknown" -with open(pot_file, encoding = "utf-8") as f: - for line in f: - if line.startswith("msgctxt"): - state = "ctxt" - if last_id != "": - translations[(last_ctxt, last_id, last_id_plural)] = last_str - last_ctxt = "" - last_id = "" - last_id_plural = "" - last_str = "" - elif line.startswith("msgid_plural"): - state = "idplural" - elif line.startswith("msgid"): - state = "id" - elif line.startswith("msgstr"): - state = "str" - - if line.count('"') >= 2: #There's an ID on this line! - line = line[line.find('"') + 1:] #Strip everything before the first ". - line = line[:line.rfind('"')] #And after the last ". - - if state == "ctxt": - last_ctxt += line #What's left is the context. - elif state == "idplural": - last_id_plural += line #Or the plural ID. - elif state == "id": - last_id += line #Or the ID. - elif state == "str": - last_str += line #Or the actual string. - -for key, _ in translations.items(): - context, english, english_plural = key - pirate = translate(english) - pirate_plural = translate(english_plural) - translations[key] = (pirate, pirate_plural) - -with open(po_file, "w", encoding = "utf-8") as f: - f.write("""msgid "" -msgstr "" -"Project-Id-Version: Pirate\\n" -"Report-Msgid-Bugs-To: plugins@ultimaker.com\\n" -"POT-Creation-Date: 1492\\n" -"PO-Revision-Date: 1492\\n" -"Last-Translator: Ghostkeeper and Awhiemstra\\n" -"Language-Team: Ghostkeeper and Awhiemstra\\n" -"Language: Pirate\\n" -"Lang-Code: en\\n" -"Country-Code: en_7S\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\\n" -""") - for key, value in translations.items(): - context, english, english_plural = key - pirate, pirate_plural = value - f.write('msgctxt "{context}"\n'.format(context = context)) - if english_plural == "": #No plurals in this item. - f.write('msgid "{english}"\n'.format(english = english)) - f.write('msgstr "{pirate}"\n'.format(pirate = pirate)) - else: - f.write('msgid "{english}"\n'.format(english = english)) - f.write('msgid_plural "{english_plural}"\n'.format(english_plural = english_plural)) - f.write('msgstr[0] "{pirate}"\n'.format(pirate = pirate)) - f.write('msgstr[1] "{pirate_plural}"\n'.format(pirate_plural = pirate_plural)) - f.write("\n") #Empty line. \ No newline at end of file diff --git a/scripts/translations/pirateofdoom.py b/scripts/translations/pirateofdoom.py deleted file mode 100644 index e8b8a28958..0000000000 --- a/scripts/translations/pirateofdoom.py +++ /dev/null @@ -1,77 +0,0 @@ -pirate = { - "build plate": "deck", - "buildplate": "deck", - "quit": "abandon ship", - "back": "avast", - "nozzle": "muzzle", - "nozzles": "muzzles", - "extruder": "cannon", - "extruders": "cannons", - "yes": "aye", - "no": "nay", - "loading": "haulin'", - "you": "ye", - "you're": "ye are", - "ok": "aye", - "machine": "ship", - "machines": "ships", - "mm/s²": "knots/s", - "mm/s": "knots", - "printer": "ship", - "printers": "ships", - "view": "spyglass", - "support": "peg legs", - "fan": "wind", - "file": "treasure", - "file(s)": "treasure(s)", - "files": "treasures", - "profile": "map", - "profiles": "maps", - "setting": "knob", - "settings": "knobs", - "shield": "sail", - "your": "yer", - "the": "th'", - "travel": "journey", - "wireframe": "ropey", - "wire": "rope", - "are": "be", - "is": "be", - "there": "thar", - "not": "nay", - "delete": "send to Davy Jones' locker", - "remove": "send to Davy Jones' locker", - "print": "scribble", - "printing": "scribblin'", - "load": "haul", - "connect to": "board", - "connecting": "boarding", - "collects": "hoards", - "prime tower": "buoy", - "change log": "captain's log", - "my": "me", - "removable drive": "life boat", - "print core": "scribbler", - "printcore": "scribbler", - "abort": ["maroon", "abandon"], - "aborting": ["marooning", "abandoning"], - "aborted": ["marooned", "abandoned"], - "connected": ["anchored", "moored"], - "developer": "scurvy dog", - "wizard": "cap'n", - "active leveling": "keelhauling", - "download": "plunder", - "downloaded": "plundered", - "caution hot surface": "fire in the hole!", - "type": "sort", - "spool": "barrel", - "surface": "lacquer", - "zigzag": "heave-to", - "bottom": "bilge", - "top": "deck", - "ironing": "deck swabbing", - "adhesion": "anchorage", - "blob": "barnacle", - "blobs": "barnacles", - "slice": "set sail", -} From ee97962c3192320be41e91a41973e695a1efccfe Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Wed, 18 Jan 2023 10:36:09 +0100 Subject: [PATCH 08/14] Remove unused scripts --- scripts/translations/extract-messages | 35 --------------------------- scripts/translations/extract-python | 12 --------- 2 files changed, 47 deletions(-) delete mode 100755 scripts/translations/extract-messages delete mode 100755 scripts/translations/extract-python diff --git a/scripts/translations/extract-messages b/scripts/translations/extract-messages deleted file mode 100755 index 31014c8e90..0000000000 --- a/scripts/translations/extract-messages +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -scriptdir=$(dirname $0) -basedir=$1 -catalogname=$2 - -# This script processes the source files using several other scripts to extract strings. -# The strings are extracted to $basedir/resources/i18n/ and then post processed. After that -# It generates english translation files and testing files that are pre- and sufficed with -# xx. These can be used by setting the LANGUAGE environment variable to x-test. -# -# This script uses extract-tr-strings to extract strings from QML files, extract-json to -# extract strings from JSON files and extract-python to extract strings from Python files. -# -mkdir -p $basedir/resources/i18n -$scriptdir/extract-json $basedir/resources/definitions/ $basedir/resources/i18n -$scriptdir/extract-all $basedir $basedir/resources/i18n/$catalogname.pot -$scriptdir/extract-plugins $basedir/plugins/ $basedir/resources/i18n/$catalogname.pot -msgconv --to-code=UTF-8 $basedir/resources/i18n/$catalogname.pot -o $basedir/resources/i18n/$catalogname.pot - -for pot in $basedir/resources/i18n/*.pot; do - filename=$(basename $pot) - - dir=$basedir/resources/i18n/en_US - mkdir -p $dir - po=$dir/${filename/.pot/.po} - msginit --no-translator -l en_US -i $pot -o $po - - #Auto-translate the translation files to Pirate. - dir=$basedir/resources/i18n/en_7S - mkdir -p $dir - po=$dir/${filename/.pot/.po} - python3 $scriptdir/pirate.py $pot $po - echo Created $po. -done diff --git a/scripts/translations/extract-python b/scripts/translations/extract-python deleted file mode 100755 index 34cf332f99..0000000000 --- a/scripts/translations/extract-python +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# -# Use xgettext to extract all strings from a set of python files. -# Argument 1 is the directory to search for python files, argument 2 -# is the destination file. -# -# This script will extract strings marked using i18n or i18nc methods. -# See UM/i18n.py for the relevant methods. -# -dir=$1 -dest=$2 -xgettext --from-code=UTF-8 --language=python -ki18n:1 -ki18nc:1c,2 -o $dest $(find -L "$dir" -name \*.py) From 67301a5b5491f1bf698a15e336e30579df7f8bdf Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Wed, 18 Jan 2023 10:36:45 +0100 Subject: [PATCH 09/14] cleanup debugging changes script CURA-9814 --- scripts/translations/extract_strings.py | 39 +++++++++---------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/scripts/translations/extract_strings.py b/scripts/translations/extract_strings.py index 30608eeeb4..2387959ba8 100644 --- a/scripts/translations/extract_strings.py +++ b/scripts/translations/extract_strings.py @@ -8,6 +8,7 @@ from os.path import isfile from pathlib import Path + def extract_all_strings(root_path: Path, script_path: Path, translations_root_path: Path, all_strings_pot_path: Path): """ Extracts all strings into a pot file with empty translations. @@ -20,14 +21,14 @@ def extract_all_strings(root_path: Path, script_path: Path, translations_root_pa @param all_strings_pot_path: The path of the pot file where all strings will be outputted (resources/i8n/cura.pot). """ - # # Extract the setting strings from any json file with settings at its root - # extract_json_arguments = [ - # script_path.joinpath("extract-json"), - # root_path.joinpath("resources", "definitions"), - # translations_root_path - # ] - # subprocess.run(extract_json_arguments) - # + # Extract the setting strings from any json file with settings at its root + extract_json_arguments = [ + script_path.joinpath("extract-json"), + root_path.joinpath("resources", "definitions"), + translations_root_path + ] + subprocess.run(extract_json_arguments) + # Extract all strings from qml and py files extract_qml_py_arguments = [ script_path.joinpath("extract-all"), @@ -84,18 +85,6 @@ def update_po_files_all_languages(translation_root_path: Path) -> None: pot_file = pot po_file = Path(directory, po_filename).absolute() - # # Initialize the new po file - # init_files_arguments = [ - # "msginit", - # "--no-wrap", - # "--no-translator", - # "-l", language, - # "-i", pot_file, - # "-o", po_file - # ] - # - # subprocess.run(init_files_arguments) - merge_files_arguments = [ "msgmerge", "--no-wrap", @@ -108,8 +97,6 @@ def update_po_files_all_languages(translation_root_path: Path) -> None: subprocess.run(merge_files_arguments) - return - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Extract strings from project into .po files") @@ -121,15 +108,15 @@ if __name__ == "__main__": root_path = Path(args.root_path) # root of the project script_path = Path(args.script_path) # location of bash scripts - # Path where all translation file are + # All the translation files should be in this path. Each language in a folder corresponding with its lang code (resource/i18n/en_US/) translations_root_path = root_path.joinpath("resources", "i18n") translations_root_path.mkdir(parents=True, exist_ok=True) # Make sure we have an output path all_strings_pot_path = translations_root_path.joinpath(args.translation_file_name) # pot file containing all strings untranslated - - # Clear the output file, otherwise deleted strings will still be in the output. if os.path.exists(all_strings_pot_path): - os.remove(all_strings_pot_path) + os.remove(all_strings_pot_path) # Clear the output file, otherwise deleted strings will still be in the output. extract_all_strings(root_path, script_path, translations_root_path, all_strings_pot_path) + + update_po_files_all_languages(translations_root_path) From 18bfcd60baf753fa920b273e2817f222878eff35 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Wed, 18 Jan 2023 17:20:55 +0100 Subject: [PATCH 10/14] Add the extractTool to the Cura conanfile CURA-9814 --- conanfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 9a2c1773ed..51616cf450 100644 --- a/conanfile.py +++ b/conanfile.py @@ -27,7 +27,7 @@ class CuraConan(ConanFile): # FIXME: Remove specific branch once merged to main # Extending the conanfile with the UMBaseConanfile https://github.com/Ultimaker/conan-ultimaker-index/tree/CURA-9177_Fix_CI_CD/recipes/umbase - python_requires = "umbase/[>=0.1.7]@ultimaker/stable" + python_requires = "umbase/[>=0.1.7]@ultimaker/stable", "translationextractor/[>=1.0.0]@ultimaker/stable" python_requires_extend = "umbase.UMBaseConanfile" options = { @@ -327,6 +327,11 @@ class CuraConan(ConanFile): if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str): # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement cpp_info = self.dependencies["gettext"].cpp_info + + # Extract all the new strings and update the existing po files + extractTool = self.python_requires["translationextractor"].module.ExtractTranslations(self) + extractTool.extract(self.source_path, self.source_path.joinpath("resources", "i18n"), "cura.pot") + for po_file in self.source_path.joinpath("resources", "i18n").glob("**/*.po"): pot_file = self.source_path.joinpath("resources", "i18n", po_file.with_suffix('.pot').name) mkdir(self, str(unix_path(self, pot_file.parent))) From 5186b234dc94a037d690506c0df61a7609ddba7b Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Wed, 18 Jan 2023 17:33:39 +0100 Subject: [PATCH 11/14] Remove redundant code for merging po files CURA-9814 --- conanfile.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/conanfile.py b/conanfile.py index 51616cf450..c3de62f1b2 100644 --- a/conanfile.py +++ b/conanfile.py @@ -326,18 +326,10 @@ class CuraConan(ConanFile): # Update the po files if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str): # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement - cpp_info = self.dependencies["gettext"].cpp_info - # Extract all the new strings and update the existing po files extractTool = self.python_requires["translationextractor"].module.ExtractTranslations(self) extractTool.extract(self.source_path, self.source_path.joinpath("resources", "i18n"), "cura.pot") - for po_file in self.source_path.joinpath("resources", "i18n").glob("**/*.po"): - pot_file = self.source_path.joinpath("resources", "i18n", po_file.with_suffix('.pot').name) - mkdir(self, str(unix_path(self, pot_file.parent))) - self.run(f"{cpp_info.bindirs[0]}/msgmerge --no-wrap --no-fuzzy-matching -width=140 -o {po_file} {po_file} {pot_file}", - env = "conanbuild", ignore_errors = True) - def imports(self): self.copy("CuraEngine.exe", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False) self.copy("CuraEngine", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False) From 5af1f6e2767707aa98511ac53d9318514398b3d4 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Thu, 19 Jan 2023 13:32:58 +0100 Subject: [PATCH 12/14] Rename extract function to generate to match better with conan style. CURA-9814 --- conanfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index c3de62f1b2..38e9cdce21 100644 --- a/conanfile.py +++ b/conanfile.py @@ -327,8 +327,8 @@ class CuraConan(ConanFile): if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str): # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement # Extract all the new strings and update the existing po files - extractTool = self.python_requires["translationextractor"].module.ExtractTranslations(self) - extractTool.extract(self.source_path, self.source_path.joinpath("resources", "i18n"), "cura.pot") + extractTool = self.python_requires["translationextractor"].module.ExtractTranslations(self, self.source_path, self.source_path.joinpath("resources", "i18n"), "cura.pot") + extractTool.generate() def imports(self): self.copy("CuraEngine.exe", root_package = "curaengine", src = "@bindirs", dst = "", keep_path = False) From 0f52521be203932bff874f609c5626496b94ab49 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Thu, 19 Jan 2023 14:02:06 +0100 Subject: [PATCH 13/14] Remove source path from extract tool creation. This can be derived from "self" that is being passed in. CURA-9814 --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 38e9cdce21..74c25fd2e2 100644 --- a/conanfile.py +++ b/conanfile.py @@ -327,7 +327,7 @@ class CuraConan(ConanFile): if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type = str): # FIXME: once m4, autoconf, automake are Conan V2 ready use self.win_bash and add gettext as base tool_requirement # Extract all the new strings and update the existing po files - extractTool = self.python_requires["translationextractor"].module.ExtractTranslations(self, self.source_path, self.source_path.joinpath("resources", "i18n"), "cura.pot") + extractTool = self.python_requires["translationextractor"].module.ExtractTranslations(self, self.source_path.joinpath("resources", "i18n"), "cura.pot") extractTool.generate() def imports(self): From c99ddb30e57f6b6d1471bf6e593dd972cedfe150 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Fri, 20 Jan 2023 11:49:34 +0100 Subject: [PATCH 14/14] Remove unused scripts CURA-9814 --- scripts/translations/createjsoncontext.py | 103 ----------------- scripts/translations/createkeypair.py | 47 -------- scripts/translations/createplugincontext.py | 72 ------------ scripts/translations/extract-all | 23 ---- scripts/translations/extract-json | 42 ------- scripts/translations/extract-plugins | 14 --- scripts/translations/extract_strings.py | 122 -------------------- 7 files changed, 423 deletions(-) delete mode 100644 scripts/translations/createjsoncontext.py delete mode 100644 scripts/translations/createkeypair.py delete mode 100644 scripts/translations/createplugincontext.py delete mode 100755 scripts/translations/extract-all delete mode 100755 scripts/translations/extract-json delete mode 100755 scripts/translations/extract-plugins delete mode 100644 scripts/translations/extract_strings.py diff --git a/scripts/translations/createjsoncontext.py b/scripts/translations/createjsoncontext.py deleted file mode 100644 index a4f02012d4..0000000000 --- a/scripts/translations/createjsoncontext.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -Copyright 2014 Burkhard Lück - -Permission to use, copy, modify, and distribute this software -and its documentation for any purpose and without fee is hereby -granted, provided that the above copyright notice appear in all -copies and that both that the copyright notice and this -permission notice and warranty disclaimer appear in supporting -documentation, and that the name of the author not be used in -advertising or publicity pertaining to distribution of the -software without specific, written prior permission. - -The author disclaim all warranties with regard to this -software, including all implied warranties of merchantability -and fitness. In no event shall the author be liable for any -special, indirect or consequential damages or any damages -whatsoever resulting from loss of use, data or profits, whether -in an action of contract, negligence or other tortious action, -arising out of or in connection with the use or performance of -this software. -""" - -# This script generates a POT file from a JSON settings file. It -# has been adapted from createjsoncontext.py of KDE's translation -# scripts. It extracts the "label" and "description" values of -# the JSON file using the structure as used by Uranium settings files. - -import sys -import os -import json -import time -import os.path -import collections - -debugoutput = False #set True to print debug output in scripty's logs - -basedir = sys.argv[-1] -pottxt = "" - -def appendMessage(file, setting, field, value): - global pottxt - pottxt += "#: {0}\nmsgctxt \"{1} {2}\"\nmsgid \"{3}\"\nmsgstr \"\"\n\n".format(file, setting, field, value.replace("\n", "\\n").replace("\"", "\\\"")) - -def processSettings(file, settings): - for name, value in settings.items(): - appendMessage(file, name, "label", value["label"]) - if "description" in value: - appendMessage(file, name, "description", value["description"]) - - if "warning_description" in value: - appendMessage(file, name, "warning_description", value["warning_description"]) - - if "error_description" in value: - appendMessage(file, name, "error_description", value["error_description"]) - - if "options" in value: - for item, description in value["options"].items(): - appendMessage(file, name, "option {0}".format(item), description) - - if "children" in value: - processSettings(file, value["children"]) - -def potheader(): - headertxt = "#, fuzzy\n" - headertxt += "msgid \"\"\n" - headertxt += "msgstr \"\"\n" - headertxt += "\"Project-Id-Version: Uranium json setting files\\n\"\n" - headertxt += "\"Report-Msgid-Bugs-To: plugins@ultimaker.com\\n\"\n" - headertxt += "\"POT-Creation-Date: %s+0000\\n\"\n" %time.strftime("%Y-%m-%d %H:%M") - headertxt += "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n" - headertxt += "\"Last-Translator: FULL NAME \\n\"\n" - headertxt += "\"Language-Team: LANGUAGE\\n\"\n" - headertxt += "\"MIME-Version: 1.0\\n\"\n" - headertxt += "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" - headertxt += "\"Content-Transfer-Encoding: 8bit\\n\"\n" - headertxt += "\n" - return headertxt - -if len(sys.argv) < 3: - print("wrong number of args: %s" % sys.argv) - print("\nUsage: python %s jsonfilenamelist basedir" % os.path.basename(sys.argv[0])) -else: - jsonfilename = sys.argv[1] - basedir = sys.argv[2] - outputfilename = sys.argv[3] - - with open(jsonfilename, "r", encoding = "utf-8") as data_file: - error = False - - jsondatadict = json.load(data_file, object_pairs_hook=collections.OrderedDict) - if "settings" not in jsondatadict: - print(f"Nothing to translate in file: {jsondatadict}") - exit(1) - - processSettings(jsonfilename.replace(basedir, ""), jsondatadict["settings"]) - - if pottxt != "": - with open(outputfilename, "w", encoding = "utf-8") as output_file: - output_file.write(potheader()) - output_file.write(pottxt) diff --git a/scripts/translations/createkeypair.py b/scripts/translations/createkeypair.py deleted file mode 100644 index e01c9c2a0b..0000000000 --- a/scripts/translations/createkeypair.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -from typing import Optional -import sys - -from UM.Trust import TrustBasics - -# Default arguments, if arguments to the script are omitted, these values are used: -DEFAULT_PRIVATE_KEY_PATH = "./private_key.pem" -DEFAULT_PUBLIC_KEY_PATH = "./public_key.pem" -DEFAULT_PASSWORD = "" - - -def createAndStoreNewKeyPair(private_filename: str, public_filename: str, optional_password: Optional[str]) -> None: - """Creates a new public and private key, and saves them to the provided filenames. - - See also 'Trust.py' in the main library and the related scripts; 'signfile.py', 'signfolder.py' in this folder. - - :param private_filename: Filename to save the private key to. - :param public_filename: Filename to save the public key to. - :param optional_password: Private keys can have a password (or not). - """ - - password = None if optional_password == "" else optional_password - private_key, public_key = TrustBasics.generateNewKeyPair() - TrustBasics.saveKeyPair(private_key, private_filename, public_filename, password) - - -def mainfunc(): - """Arguments: - - `-k ` or `--private ` will store the generated private key to - `-p ` or `--public ` will store the generated public key to - `-w ` or `--password ` will give the private key a password (none if omitted, which is default) - """ - - parser = argparse.ArgumentParser() - parser.add_argument("-k", "--private", type = str, default = DEFAULT_PRIVATE_KEY_PATH) - parser.add_argument("-p", "--public", type = str, default = DEFAULT_PUBLIC_KEY_PATH) - parser.add_argument("-w", "--password", type = str, default = DEFAULT_PASSWORD) - args = parser.parse_args() - createAndStoreNewKeyPair(args.private, args.public, args.password) - - -if __name__ == "__main__": - sys.exit(mainfunc()) diff --git a/scripts/translations/createplugincontext.py b/scripts/translations/createplugincontext.py deleted file mode 100644 index 25a086357e..0000000000 --- a/scripts/translations/createplugincontext.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -Copyright 2014 Burkhard Lück - -Permission to use, copy, modify, and distribute this software -and its documentation for any purpose and without fee is hereby -granted, provided that the above copyright notice appear in all -copies and that both that the copyright notice and this -permission notice and warranty disclaimer appear in supporting -documentation, and that the name of the author not be used in -advertising or publicity pertaining to distribution of the -software without specific, written prior permission. - -The author disclaim all warranties with regard to this -software, including all implied warranties of merchantability -and fitness. In no event shall the author be liable for any -special, indirect or consequential damages or any damages -whatsoever resulting from loss of use, data or profits, whether -in an action of contract, negligence or other tortious action, -arising out of or in connection with the use or performance of -this software. -""" - -# This script generates a POT file from a JSON settings file. It -# has been adapted from createjsoncontext.py of KDE's translation -# scripts. It extracts the "label" and "description" values of -# the JSON file using the structure as used by Uranium settings files. - -import sys -import os.path -import collections -import json - -debugoutput = False #set True to print debug output in scripty's logs - -basedir = sys.argv[-1] -pottxt = "" - - -def appendMessage(file, field, value): - global pottxt - pottxt += "#: {0}\nmsgctxt \"{1}\"\nmsgid \"{2}\"\nmsgstr \"\"\n\n".format(file, field, value.replace("\n", "\\n").replace("\"", "\\\"")) - - -if len(sys.argv) < 3: - print("wrong number of args: %s" % sys.argv) - print("\nUsage: python %s jsonfilenamelist basedir" % os.path.basename(sys.argv[0])) -else: - json_filename = sys.argv[1] - basedir = sys.argv[2] - output_filename = sys.argv[3] - - with open(json_filename, "r", encoding = "utf-8") as data_file: - error = False - - jsondatadict = json.load(data_file, object_pairs_hook=collections.OrderedDict) - if "name" not in jsondatadict or ("api" not in jsondatadict and "supported_sdk_versions" not in jsondatadict) or "version" not in jsondatadict: - print("The plugin.json file found on %s is invalid, ignoring it" % json_filename) - exit(1) - - file = json_filename.replace(basedir, "") - - if "description" in jsondatadict: - appendMessage(file, "description", jsondatadict["description"]) - if "name" in jsondatadict: - appendMessage(file, "name", jsondatadict["name"]) - - if pottxt != "": - with open(output_filename, "a", encoding = "utf-8") as output_file: - output_file.write(pottxt) diff --git a/scripts/translations/extract-all b/scripts/translations/extract-all deleted file mode 100755 index 98e748f4ee..0000000000 --- a/scripts/translations/extract-all +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# -# Use xgettext to extract all strings from a set of python files. -# Argument 1 is the directory to search for python files, argument 2 -# is the destination file. -# -# This script will extract strings marked using i18n or i18nc methods. -# See UM/i18n.py for the relevant methods. -# -dir=$1 -dest=$2 -touch $dest -for f in $(find -L "$dir" -name \*.py) -do - echo "Extracting strings from python file: $f" - xgettext --from-code=UTF-8 --join-existing --sort-by-file --language=python -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f -done - -for f in $(find -L "$dir" -name \*.qml) -do - echo "Extracting strings from qml file: $f" - xgettext --from-code=UTF-8 --join-existing --sort-by-file --language=javascript -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -o $dest $f -done diff --git a/scripts/translations/extract-json b/scripts/translations/extract-json deleted file mode 100755 index 00cef1e866..0000000000 --- a/scripts/translations/extract-json +++ /dev/null @@ -1,42 +0,0 @@ -#! /bin/bash - -# Extract strings from all JSON files in a directory into files with matching names ending with .pot. -# -# This script will extract strings from all JSON files in the directory -# passed as first argument. The second argument is the destination -# directory for the extracted message file. -# -# This script uses createjsoncontext to generate the actual message file -# from the JSON file. -# -# This script is based on handle_json_files.sh from KDE's translation -# scripts. -# handle_json_files.sh is copyright 2014 Burkhard Lück -scriptdir=$(dirname $0) - -extract() { - basedir=$1 - dest=$2 - file=$3 - - python3 $scriptdir/createjsoncontext.py $file $basedir json.$$.tmp - if test $? -eq 1; then - return - fi - - echo "Extracted messages from $file" - - msguniq --to-code=UTF-8 -o json.$$ json.$$.tmp - if test -f json.$$; then - destfile="$dest/$(basename $file).pot" - mv json.$$ $destfile - fi - rm -f json.$$ json.$$.tmp -} - -dir=$1; shift -dest=$1; shift - -for file in $(find -L "$dir" -name *.json | grep -v 'tests'); do - extract $dir $dest $file -done diff --git a/scripts/translations/extract-plugins b/scripts/translations/extract-plugins deleted file mode 100755 index d5b3674968..0000000000 --- a/scripts/translations/extract-plugins +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash - -# Extract strings from all plugins -# - -scriptdir=$(dirname $0) -dir=$1; shift -dest=$1; shift - -for file in $(find -L "$dir" -name plugin.json | grep -v 'tests'); do - python3 $scriptdir/createplugincontext.py $file $dir $dest -done - - diff --git a/scripts/translations/extract_strings.py b/scripts/translations/extract_strings.py deleted file mode 100644 index 2387959ba8..0000000000 --- a/scripts/translations/extract_strings.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (c) 2023 UltiMaker. -# Cura is released under the terms of the LGPLv3 or higher. - -import argparse -import os -import subprocess -from os.path import isfile - -from pathlib import Path - - -def extract_all_strings(root_path: Path, script_path: Path, translations_root_path: Path, all_strings_pot_path: Path): - """ Extracts all strings into a pot file with empty translations. - - Strings are extracted everywhere that i18n is used in python and qml in the project. It also checks the project - for JSON files with 'settings' in the root node and extracts these for translation as well. - - @param root_path: The root path of the project. This is the root for string searching. - @param script_path: The location of the bash scripts used for translating. - @param translations_root_path: The root of the translations folder (resources/i18n). - @param all_strings_pot_path: The path of the pot file where all strings will be outputted (resources/i8n/cura.pot). - """ - - # Extract the setting strings from any json file with settings at its root - extract_json_arguments = [ - script_path.joinpath("extract-json"), - root_path.joinpath("resources", "definitions"), - translations_root_path - ] - subprocess.run(extract_json_arguments) - - # Extract all strings from qml and py files - extract_qml_py_arguments = [ - script_path.joinpath("extract-all"), - root_path, - all_strings_pot_path - ] - subprocess.run(extract_qml_py_arguments) - - # Extract all the name and description from all plugins - extract_plugin_arguments = [ - script_path.joinpath("extract-plugins"), - root_path.joinpath("plugins"), - all_strings_pot_path - ] - subprocess.run(extract_plugin_arguments) - - # Convert the output file to utf-8 - convert_encoding_arguments = [ - "msgconv", - "--to-code=UTF-8", - all_strings_pot_path, - "-o", - all_strings_pot_path - ] - subprocess.run(convert_encoding_arguments) - - -def update_po_files_all_languages(translation_root_path: Path) -> None: - """ Updates all po files in translation_root_path with new strings mapped to blank translations. - - This will take all newly generated po files in the root of the translations path (i18n/cura.pot, i18n/fdmextruder.json.def.pot) - and merge them with the existing po files for every language. This will create new po files with empty translations - for all new words added to the project. - - @param translation_root_path: Root of the translations folder (resources/i18n). - """ - new_pot_files = [] - - for file in os.listdir(translation_root_path): - path = translations_root_path.joinpath(file) - if path.suffix == ".pot": - new_pot_files.append(path) - print(new_pot_files) - - for directory, _, po_files in os.walk(translation_root_path): - print(directory) - print(po_files) - for pot in new_pot_files: - - po_filename = pot.name.rstrip("t") - if po_filename not in po_files: - continue # We only want to merge files that have matching names - - pot_file = pot - po_file = Path(directory, po_filename).absolute() - - merge_files_arguments = [ - "msgmerge", - "--no-wrap", - "--no-fuzzy-matching", - "--update", - "--sort-by-file", # Sort by file location, this is better than pure sorting for translators - po_file, # po file that will be updated - pot_file # source of new strings - ] - - subprocess.run(merge_files_arguments) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Extract strings from project into .po files") - parser.add_argument("root_path", type=str, help="The root of the project to extract translatable strings from") - parser.add_argument("translation_file_name", type=str, help="The .pot file that all strings from python/qml files will be inserted into") - parser.add_argument("script_path", type=str, help="The path containing the scripts for translating files") - args = parser.parse_args() - - root_path = Path(args.root_path) # root of the project - script_path = Path(args.script_path) # location of bash scripts - - # All the translation files should be in this path. Each language in a folder corresponding with its lang code (resource/i18n/en_US/) - translations_root_path = root_path.joinpath("resources", "i18n") - translations_root_path.mkdir(parents=True, exist_ok=True) # Make sure we have an output path - - all_strings_pot_path = translations_root_path.joinpath(args.translation_file_name) # pot file containing all strings untranslated - if os.path.exists(all_strings_pot_path): - os.remove(all_strings_pot_path) # Clear the output file, otherwise deleted strings will still be in the output. - - extract_all_strings(root_path, script_path, translations_root_path, all_strings_pot_path) - - - update_po_files_all_languages(translations_root_path)