Skip to content

Commit 56b1c6e

Browse files
authored
Merge pull request #2948 from Wurschdhaud/new-extras-translation-helper
New helper script for translations
2 parents 69dea0f + fb68728 commit 56b1c6e

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Apply translations from CSV back to pyRevit bundle YAML files.
2+
3+
This script reads a CSV file containing translations (generated by
4+
extract_translations.py) and applies them to the corresponding bundle.yaml
5+
files, updating or creating multilingual field dictionaries.
6+
7+
Configuration:
8+
TRANSLATION_CSV: Path to the CSV file containing translations
9+
LANGUAGE_KEY: Translation language key to apply (e.g., 'chinese_s')
10+
SOURCE_LANG: Source language key (default: 'en_us')
11+
"""
12+
import csv
13+
from pathlib import Path
14+
from ruamel.yaml import YAML # pip install ruamel.yaml
15+
16+
# -------- CONFIG --------
17+
TRANSLATION_CSV = r"C:\temp\translations.csv" # same path as in other script
18+
LANGUAGE_KEY = "chinese_s" # same as in other script
19+
SOURCE_LANG = "en_us" # same as in other script
20+
# ------------------------
21+
22+
yaml = YAML()
23+
24+
25+
def build_lookup(csv_path):
26+
"""Build dictionary: {yaml_file: {key_type: {"en_us":..., "translation":...}}}"""
27+
lookup = {}
28+
29+
with open(csv_path, encoding="utf-8") as f:
30+
reader = csv.DictReader(f)
31+
32+
for row in reader:
33+
file = row["yaml_file"]
34+
key = row["key_type"]
35+
36+
en = row[SOURCE_LANG]
37+
tr = row[LANGUAGE_KEY]
38+
39+
lookup.setdefault(file, {}).setdefault(key, {})
40+
lookup[file][key] = {"en": en, "tr": tr}
41+
42+
return lookup
43+
44+
45+
def insert_translation(data, key_type, trans, node_path=""):
46+
"""Insert translation from CSV.
47+
Rule: write ONLY if CSV provides a non-empty translation; always overwrite."""
48+
if not isinstance(data, dict):
49+
return
50+
51+
translation = trans["tr"].strip()
52+
53+
if key_type in data:
54+
value = data[key_type]
55+
56+
# CASE A: scalar -> convert to dict
57+
if isinstance(value, str):
58+
print(f"[Scalar detected during import] {node_path}/{key_type} | Converting to multilingual")
59+
new_dict = {SOURCE_LANG: value}
60+
61+
if translation:
62+
new_dict[LANGUAGE_KEY] = translation
63+
print(f"[Write] {node_path}/{key_type} | {LANGUAGE_KEY} = '{translation}'")
64+
else:
65+
print(f"[Skip] CSV translation empty for {node_path}/{key_type}")
66+
67+
data[key_type] = new_dict
68+
return
69+
70+
# CASE B: dict
71+
elif isinstance(value, dict):
72+
# ensure en_us exists
73+
if SOURCE_LANG not in value:
74+
if len(value):
75+
first_key = next(iter(value.keys()))
76+
value[SOURCE_LANG] = value[first_key]
77+
print(f"[Promotion] No {SOURCE_LANG} → using '{first_key}' value")
78+
79+
# write only if CSV translation provided
80+
if translation:
81+
value[LANGUAGE_KEY] = translation
82+
print(f"[Write] {node_path}/{key_type} | Overwriting {LANGUAGE_KEY} = '{translation}'")
83+
else:
84+
print(f"[Skip] CSV translation empty for {node_path}/{key_type}")
85+
86+
# recurse deeper
87+
for k, v in data.items():
88+
child_path = f"{node_path}/{k}" if node_path else k
89+
insert_translation(v, key_type, trans, child_path)
90+
91+
92+
def main():
93+
lookup = build_lookup(TRANSLATION_CSV)
94+
95+
for yaml_file, fields in lookup.items():
96+
yaml_file = Path(yaml_file)
97+
98+
if not yaml_file.exists():
99+
print(f"[Missing] {yaml_file}")
100+
continue
101+
102+
try:
103+
with open(yaml_file, "r", encoding="utf-8") as f:
104+
data = yaml.load(f)
105+
106+
for key_type, trans in fields.items():
107+
insert_translation(data, key_type, trans)
108+
109+
with open(yaml_file, "w", encoding="utf-8") as f:
110+
yaml.dump(data, f)
111+
112+
print(f"[Processing] {yaml_file}")
113+
114+
except Exception as e:
115+
print(f"[ERROR] writing {yaml_file}: {e}")
116+
117+
118+
if __name__ == "__main__":
119+
main()
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Extract translation strings from pyRevit bundle YAML files.
2+
3+
This script scans bundle.yaml files for title and tooltip fields,
4+
extracting English source text and any existing translations to a CSV file
5+
for easier translation workflow.
6+
7+
Configuration:
8+
BASE_DIR: Root directory containing bundle YAML files
9+
OUTPUT_CSV: Path where the CSV file will be written
10+
LANGUAGE_KEY: Translation language key (e.g., 'chinese_s')
11+
SOURCE_LANG: Source language key (default: 'en_us')
12+
"""
13+
import os
14+
from pathlib import Path
15+
from ruamel.yaml import YAML # pip install ruamel.yaml
16+
import csv
17+
18+
# -------- CONFIG --------
19+
BASE_DIR = r"C:\Program Files\pyRevit-Master\extensions\pyRevitTools.extension" # adjust for custom installation
20+
OUTPUT_CSV = r"C:\temp\translations.csv" # same path as in other script
21+
LANGUAGE_KEY = "chinese_s" # translation key to extract/merge
22+
SOURCE_LANG = "en_us" # main source language
23+
# ------------------------
24+
25+
yaml = YAML()
26+
27+
28+
def find_yaml_files(base_dir):
29+
for root, _, files in os.walk(base_dir):
30+
for f in files:
31+
if f.endswith(".yaml"):
32+
yield Path(root) / f
33+
34+
35+
def extract_field(path, field_name, value, results):
36+
"""Extracts English + existing translated value from dict or scalar."""
37+
# CASE 1: multilingual dict
38+
if isinstance(value, dict):
39+
# English (preferred)
40+
en = value.get(SOURCE_LANG)
41+
42+
# fallback to first language if no en_us exists
43+
if not en and len(value):
44+
first_key = next(iter(value.keys()))
45+
en = value[first_key]
46+
47+
# Existing translation to preserve
48+
tr = value.get(LANGUAGE_KEY, "")
49+
50+
if en:
51+
results.append([path, field_name, en, tr])
52+
return
53+
54+
# CASE 2: scalar string
55+
if isinstance(value, str):
56+
print(
57+
f"[Scalar detected] File: {path} | Field: {field_name} | Value: '{value}'"
58+
)
59+
results.append([path, field_name, value, ""])
60+
return
61+
62+
63+
def extract_values(data, path, results):
64+
"""Recursively walk through structure and extract fields."""
65+
if not isinstance(data, dict):
66+
return
67+
68+
for field_name in ("title", "tooltip"):
69+
if field_name in data:
70+
extract_field(path, field_name, data[field_name], results)
71+
72+
# recurse into children
73+
for k, v in data.items():
74+
child_path = f"{path}/{k}"
75+
extract_values(v, child_path, results)
76+
77+
78+
def main():
79+
results = []
80+
81+
for yaml_file in find_yaml_files(BASE_DIR):
82+
try:
83+
with open(yaml_file, "r", encoding="utf-8") as f:
84+
data = yaml.load(f)
85+
86+
extract_values(data, str(yaml_file), results)
87+
88+
except Exception as e:
89+
print(f"ERROR reading {yaml_file}: {e}")
90+
91+
# write CSV
92+
with open(OUTPUT_CSV, "w", newline="", encoding="utf-8") as f:
93+
writer = csv.writer(f)
94+
writer.writerow(["yaml_file", "key_type", SOURCE_LANG, LANGUAGE_KEY])
95+
for row in results:
96+
writer.writerow(row)
97+
98+
print(f"\nExtracted {len(results)} records to {OUTPUT_CSV}")
99+
100+
101+
if __name__ == "__main__":
102+
main()

0 commit comments

Comments
 (0)