Use py file instead for extracting strings

This commit is contained in:
Joey de l'Arago 2023-01-17 17:55:51 +01:00
parent 912429bb18
commit bcf715193a
4 changed files with 138 additions and 187 deletions

View file

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

View file

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

View file

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

View file

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