mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-24 07:03:56 -06:00
Use py file instead for extracting strings
This commit is contained in:
parent
912429bb18
commit
bcf715193a
4 changed files with 138 additions and 187 deletions
|
@ -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
|
||||
|
|
135
scripts/translations/extract_strings.py
Normal file
135
scripts/translations/extract_strings.py
Normal 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)
|
|
@ -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.
|
|
@ -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",
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue