From 94334fbe5c1d27b14a1d7f2f3259cc0acdd2f874 Mon Sep 17 00:00:00 2001 From: Wurschdhaud Date: Mon, 1 Dec 2025 21:32:50 +0100 Subject: [PATCH 1/2] initial commit --- .../bundle_translations/apply_translations.py | 108 ++++++++++++++++++ .../extract_translations.py | 90 +++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 extras/bundle_translations/apply_translations.py create mode 100644 extras/bundle_translations/extract_translations.py diff --git a/extras/bundle_translations/apply_translations.py b/extras/bundle_translations/apply_translations.py new file mode 100644 index 0000000000..73ac06074c --- /dev/null +++ b/extras/bundle_translations/apply_translations.py @@ -0,0 +1,108 @@ +import csv +from pathlib import Path +from ruamel.yaml import YAML # pip install ruamel.yaml + +# -------- CONFIG -------- +TRANSLATION_CSV = r"C:\temp\translations.csv" +LANGUAGE_KEY = "chinese_s" +SOURCE_LANG = "en_us" +# ------------------------ + +yaml = YAML() + + +def build_lookup(csv_path): + """Build dictionary: {yaml_file: {key_type: {"en_us":..., "translation":...}}}""" + lookup = {} + + with open(csv_path, encoding="utf-8") as f: + reader = csv.DictReader(f) + + for row in reader: + file = row["yaml_file"] + key = row["key_type"] + + en = row[SOURCE_LANG] + tr = row[LANGUAGE_KEY] + + lookup.setdefault(file, {}).setdefault(key, {}) + lookup[file][key] = {"en": en, "tr": tr} + + return lookup + + +def insert_translation(data, key_type, trans, node_path=""): + """Insert translation from CSV. + Rule: write ONLY if CSV provides a non-empty translation; always overwrite.""" + if not isinstance(data, dict): + return + + translation = trans["tr"].strip() + + if key_type in data: + value = data[key_type] + + # CASE A: scalar -> convert to dict + if isinstance(value, str): + print(f"[Scalar detected during import] {node_path}/{key_type} | Converting to multilingual") + new_dict = {SOURCE_LANG: value} + + if translation: + new_dict[LANGUAGE_KEY] = translation + print(f"[Write] {node_path}/{key_type} | {LANGUAGE_KEY} = '{translation}'") + else: + print(f"[Skip] CSV translation empty for {node_path}/{key_type}") + + data[key_type] = new_dict + return + + # CASE B: dict + elif isinstance(value, dict): + # ensure en_us exists + if SOURCE_LANG not in value: + if len(value): + first_key = next(iter(value.keys())) + value[SOURCE_LANG] = value[first_key] + print(f"[Promotion] No {SOURCE_LANG} → using '{first_key}' value") + + # write only if CSV translation provided + if translation: + value[LANGUAGE_KEY] = translation + print(f"[Write] {node_path}/{key_type} | Overwriting {LANGUAGE_KEY} = '{translation}'") + else: + print(f"[Skip] CSV translation empty for {node_path}/{key_type}") + + # recurse deeper + for k, v in data.items(): + child_path = f"{node_path}/{k}" if node_path else k + insert_translation(v, key_type, trans, child_path) + + +def main(): + lookup = build_lookup(TRANSLATION_CSV) + + for yaml_file, fields in lookup.items(): + yaml_file = Path(yaml_file) + + if not yaml_file.exists(): + print(f"[Missing] {yaml_file}") + continue + + try: + with open(yaml_file, "r", encoding="utf-8") as f: + data = yaml.load(f) + + for key_type, trans in fields.items(): + insert_translation(data, key_type, trans) + + with open(yaml_file, "w", encoding="utf-8") as f: + yaml.dump(data, f) + + print(f"[Updating] {yaml_file}") + + except Exception as e: + print(f"[ERROR] writing {yaml_file}: {e}") + + +if __name__ == "__main__": + main() diff --git a/extras/bundle_translations/extract_translations.py b/extras/bundle_translations/extract_translations.py new file mode 100644 index 0000000000..d23813679f --- /dev/null +++ b/extras/bundle_translations/extract_translations.py @@ -0,0 +1,90 @@ +import os +from pathlib import Path +from ruamel.yaml import YAML # pip install ruamel.yaml +import csv + +# -------- CONFIG -------- +BASE_DIR = r"C:\Program Files\pyRevit-Master\extensions\pyRevitTools.extension" +OUTPUT_CSV = r"C:\temp\translations.csv" +LANGUAGE_KEY = "chinese_s" # translation key to extract/merge +SOURCE_LANG = "en_us" # main source language +# ------------------------ + +yaml = YAML() + + +def find_yaml_files(base_dir): + for root, _, files in os.walk(base_dir): + for f in files: + if f.endswith(".yaml"): + yield Path(root) / f + + +def extract_field(path, field_name, value, results): + """Extracts English + existing translated value from dict or scalar.""" + # CASE 1: multilingual dict + if isinstance(value, dict): + # English (preferred) + en = value.get(SOURCE_LANG) + + # fallback to first language if no en_us exists + if not en and len(value): + first_key = next(iter(value.keys())) + en = value[first_key] + + # Existing translation to preserve + tr = value.get(LANGUAGE_KEY, "") + + if en: + results.append([path, field_name, en, tr]) + return + + # CASE 2: scalar string + if isinstance(value, str): + print( + f"[Scalar detected] File: {path} | Field: {field_name} | Value: '{value}'" + ) + results.append([path, field_name, value, ""]) + return + + +def extract_values(data, path, results): + """Recursively walk through structure and extract fields.""" + if not isinstance(data, dict): + return + + for field_name in ("title", "tooltip"): + if field_name in data: + extract_field(path, field_name, data[field_name], results) + + # recurse into children + for k, v in data.items(): + child_path = f"{path}/{k}" + extract_values(v, child_path, results) + + +def main(): + results = [] + + for yaml_file in find_yaml_files(BASE_DIR): + try: + with open(yaml_file, "r", encoding="utf-8") as f: + data = yaml.load(f) + + extract_values(data, str(yaml_file), results) + + except Exception as e: + print(f"ERROR reading {yaml_file}: {e}") + + # write CSV + with open(OUTPUT_CSV, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerow(["yaml_file", "key_type", SOURCE_LANG, LANGUAGE_KEY]) + for row in results: + writer.writerow(row) + + print(f"\nExtracted {len(results)} records to {OUTPUT_CSV}") + + +if __name__ == "__main__": + main() From fb68728a53d16bd47abe5297cd4414ab2735148d Mon Sep 17 00:00:00 2001 From: Wurschdhaud Date: Wed, 3 Dec 2025 07:13:47 +0100 Subject: [PATCH 2/2] docstring added, comments added as per copilot suggestion --- .../bundle_translations/apply_translations.py | 19 +++++++++++++++---- .../extract_translations.py | 16 ++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/extras/bundle_translations/apply_translations.py b/extras/bundle_translations/apply_translations.py index 73ac06074c..1ede3717c0 100644 --- a/extras/bundle_translations/apply_translations.py +++ b/extras/bundle_translations/apply_translations.py @@ -1,11 +1,22 @@ +"""Apply translations from CSV back to pyRevit bundle YAML files. + +This script reads a CSV file containing translations (generated by +extract_translations.py) and applies them to the corresponding bundle.yaml +files, updating or creating multilingual field dictionaries. + +Configuration: + TRANSLATION_CSV: Path to the CSV file containing translations + LANGUAGE_KEY: Translation language key to apply (e.g., 'chinese_s') + SOURCE_LANG: Source language key (default: 'en_us') +""" import csv from pathlib import Path from ruamel.yaml import YAML # pip install ruamel.yaml # -------- CONFIG -------- -TRANSLATION_CSV = r"C:\temp\translations.csv" -LANGUAGE_KEY = "chinese_s" -SOURCE_LANG = "en_us" +TRANSLATION_CSV = r"C:\temp\translations.csv" # same path as in other script +LANGUAGE_KEY = "chinese_s" # same as in other script +SOURCE_LANG = "en_us" # same as in other script # ------------------------ yaml = YAML() @@ -98,7 +109,7 @@ def main(): with open(yaml_file, "w", encoding="utf-8") as f: yaml.dump(data, f) - print(f"[Updating] {yaml_file}") + print(f"[Processing] {yaml_file}") except Exception as e: print(f"[ERROR] writing {yaml_file}: {e}") diff --git a/extras/bundle_translations/extract_translations.py b/extras/bundle_translations/extract_translations.py index d23813679f..28cbd3452d 100644 --- a/extras/bundle_translations/extract_translations.py +++ b/extras/bundle_translations/extract_translations.py @@ -1,11 +1,23 @@ +"""Extract translation strings from pyRevit bundle YAML files. + +This script scans bundle.yaml files for title and tooltip fields, +extracting English source text and any existing translations to a CSV file +for easier translation workflow. + +Configuration: + BASE_DIR: Root directory containing bundle YAML files + OUTPUT_CSV: Path where the CSV file will be written + LANGUAGE_KEY: Translation language key (e.g., 'chinese_s') + SOURCE_LANG: Source language key (default: 'en_us') +""" import os from pathlib import Path from ruamel.yaml import YAML # pip install ruamel.yaml import csv # -------- CONFIG -------- -BASE_DIR = r"C:\Program Files\pyRevit-Master\extensions\pyRevitTools.extension" -OUTPUT_CSV = r"C:\temp\translations.csv" +BASE_DIR = r"C:\Program Files\pyRevit-Master\extensions\pyRevitTools.extension" # adjust for custom installation +OUTPUT_CSV = r"C:\temp\translations.csv" # same path as in other script LANGUAGE_KEY = "chinese_s" # translation key to extract/merge SOURCE_LANG = "en_us" # main source language # ------------------------