Move translation scripts to Cura from Uranium

CURA-9814
This commit is contained in:
Joey de l'Arago 2023-01-16 15:46:25 +01:00
parent 24cb9e16a8
commit 20778f3d10
11 changed files with 603 additions and 0 deletions

View File

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2014 Burkhard Lück <lueck@hube-lueck.de>
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 <EMAIL@ADDRESS>\\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)

View File

@ -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 <filename>` or `--private <filename>` will store the generated private key to <filename>
`-p <filename>` or `--public <filename>` will store the generated public key to <filename>
`-w <password>` or `--password <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())

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2014 Burkhard Lück <lueck@hube-lueck.de>
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)

View File

@ -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)

View File

@ -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 <lueck@hube-lueck.de>
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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 <agateau@kde.org>
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 <<EOF
Usage: $(basename $0) [src_files]... -o [pot_file]
Creates a .pot file for code translated using Qt translation system.
EOF
exit 1
}
src_files=""
pot_file=""
while [ $# -gt 0 ] ; do
case "$1" in
-h|--help)
usage
;;
-o|--output)
pot_file="$2"
shift 2
;;
-*)
die "Unknown option $1"
;;
*)
src_files="$src_files $1"
shift
;;
esac
done
if [ -z "$src_files" ] ; then
die "No source files provided"
fi
if [ -z "$pot_file" ] ; then
die "No pot file provided, please provide one with the -o option"
fi
# "Reserve" a name for a temporary .ts file where lupdate can store its output.
# The .ts file is created in the directory where Messages.sh is to ensure
# lupdate creates file paths relative to this directory.
# lupdate fails if we pass it an existing but empty file, so we have to rm the
# temporary file we just created. It is not completely safe, but since this
# script is always running in a trusted environment we can assume it is OK.
tmp_ts=$(mktemp $PWD/extract-tr-strings-XXXXXX.ts)
trap "rm -rf $tmp_ts" 0
trap "exit 2" 1 2 3 13 15
rm $tmp_ts
$LUPDATE -silent $src_files -ts $tmp_ts
$LCONVERT $tmp_ts --sort-contexts --output-format pot -o $pot_file

View File

@ -0,0 +1,108 @@
#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.

View File

@ -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",
}