Skip to content

Commit 703943c

Browse files
author
Cristhian Garcia
authored
Merge pull request #641 from openedx/cag/translations
feat: add dataset columns/metrics translation support
2 parents bc80cb1 + 348f5bc commit 703943c

29 files changed

+630
-578
lines changed

scripts/translate_utils.py

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,87 @@
33
import shutil
44
import yaml
55

6+
class TranslatableAsset:
7+
translatable_attributes = []
8+
9+
def __init__(self, asset: dict):
10+
self.asset = asset
11+
for key in ASSET_FOLDER_MAPPING:
12+
if key in asset:
13+
self.asset_type = ASSET_FOLDER_MAPPING[key]
14+
break
15+
16+
def extract_text(self):
17+
"""
18+
Extract text from an asset.
19+
"""
20+
strings = []
21+
for var_path in self.translatable_attributes:
22+
strings.extend(self.translate_var(self.asset, var_path.split(".")))
23+
24+
return strings
25+
26+
def translate_var(self, content, var_path):
27+
"""
28+
Helper method to remove content from the content dict.
29+
"""
30+
if not content:
31+
return []
32+
if len(var_path) == 1:
33+
if isinstance(content, list):
34+
strings = []
35+
for item in content:
36+
strings.append(item.get(var_path[0], ""))
37+
return strings
38+
string = [content.get(var_path[0], "")]
39+
return string or []
40+
else:
41+
if isinstance(content, list):
42+
strings = []
43+
for item in content:
44+
strings.extend(self.translate_var(item, var_path[1:]))
45+
return strings
46+
if isinstance(content, dict):
47+
if var_path[0] == "*":
48+
strings = []
49+
for key, value in content.items():
50+
strings.extend(self.translate_var(value, var_path[1:]))
51+
return strings
52+
return self.translate_var(content.get(var_path[0]), var_path[1:])
53+
else:
54+
print("Could not translate var_path: ", var_path, content)
55+
return []
56+
57+
58+
class DashboardAsset(TranslatableAsset):
59+
translatable_attributes = [
60+
"dashboard_title",
61+
"description",
62+
"metadata.native_filter_configuration.name",
63+
"metadata.native_filter_configuration.description",
64+
"position.*.meta.text",
65+
"position.*.meta.code",
66+
]
67+
68+
class ChartAsset(TranslatableAsset):
69+
translatable_attributes = [
70+
"slice_name",
71+
"description",
72+
"params.x_axis_label",
73+
"params.y_axis_label",
74+
]
75+
76+
class DatasetAsset(TranslatableAsset):
77+
translatable_attributes = [
78+
"metrics.verbose_name",
79+
"columns.verbose_name",
80+
]
81+
82+
683
ASSET_FOLDER_MAPPING = {
7-
"dashboard_title": "dashboards",
8-
"slice_name": "charts",
84+
"dashboard_title": ("dashboards", DashboardAsset),
85+
"slice_name": ("charts", ChartAsset),
86+
"table_name": ("datasets", DatasetAsset),
987
}
1088

1189

@@ -42,50 +120,11 @@ def mark_text_for_translation(asset):
42120
For every asset extract the text and mark it for translation
43121
"""
44122

45-
def extract_text(asset, type):
46-
"""
47-
Extract text from an asset
48-
"""
49-
strings = []
50-
if type == "dashboards":
51-
strings.append(asset["dashboard_title"])
52-
53-
# Gets translatable fields from filters
54-
for filter in asset["metadata"]["native_filter_configuration"]:
55-
strings.append(filter["name"])
56-
57-
# Gets translatable fields from charts
58-
for element in asset.get("position", {}).values():
59-
if not isinstance(element, dict):
60-
continue
61-
62-
meta = element.get("meta", {})
63-
64-
if meta.get("text"):
65-
strings.append(meta["text"])
66-
67-
if meta.get("code"):
68-
strings.append(meta["code"])
69-
70-
elif type == "charts":
71-
strings.append(asset["slice_name"])
72-
73-
if asset.get("description"):
74-
strings.append(asset["description"])
75-
76-
if asset["params"].get("x_axis_label"):
77-
strings.append(asset["params"]["x_axis_label"])
78-
79-
if asset["params"].get("y_axis_label"):
80-
strings.append(asset["params"]["y_axis_label"])
81-
82-
return strings
83-
84-
for key, value in ASSET_FOLDER_MAPPING.items():
123+
for key, (asset_type, Asset) in ASSET_FOLDER_MAPPING.items():
85124
if key in asset:
86-
strings = extract_text(asset, value)
125+
strings = Asset(asset).extract_text()
87126
print(
88-
f"Extracted {len(strings)} strings from {value} {asset.get('uuid')}"
127+
f"Extracted {len(strings)} strings from {asset_type} {asset.get('uuid')}"
89128
)
90129
return strings
91130

@@ -138,7 +177,7 @@ def compile_translations(root_path):
138177
outfile.write("---\n")
139178
# If we don't use an extremely large width, the jinja in our translations
140179
# can be broken by newlines. So we use the largest number there is.
141-
yaml.dump(all_translations, outfile, width=math.inf)
180+
yaml.dump(all_translations, outfile, width=math.inf, sort_keys=True)
142181
outfile.write("\n{{ patch('superset-extra-asset-translations')}}\n")
143182

144183
# We remove these files to avoid confusion about where translations are coming
@@ -162,6 +201,7 @@ def extract_translations(root_path):
162201

163202
print("Gathering text for translations...")
164203
STRINGS = set(get_text_for_translations(root_path))
204+
print(f"Extracted {len(STRINGS)} strings for translation.")
165205
translations = {'en': {}}
166206

167207
for string in STRINGS:

tutoraspects/templates/aspects/apps/superset/pythonpath/create_assets.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def write_asset_to_file(asset, asset_name, folder, file_name, roles):
128128
if folder == "databases":
129129
create_superset_db(asset["database_name"], asset["sqlalchemy_uri"])
130130

131-
if folder in ["charts", "dashboards"]:
131+
if folder in ["charts", "dashboards", "datasets"]:
132132
for locale in DASHBOARD_LOCALES:
133133
updated_asset = generate_translated_asset(
134134
asset, asset_name, folder, locale, roles
@@ -171,6 +171,7 @@ def generate_translated_asset(asset, asset_name, folder, language, roles):
171171

172172
if folder == "dashboards":
173173
copy["slug"] = f"{copy['slug']}-{language}"
174+
copy["description"] = get_translation(copy["description"], language)
174175

175176
dashboard_roles = copy.pop("_roles", [])
176177
translated_dashboard_roles = []
@@ -184,6 +185,20 @@ def generate_translated_asset(asset, asset_name, folder, language, roles):
184185

185186
generate_translated_dashboard_elements(copy, language)
186187
generate_translated_dashboard_filters(copy, language)
188+
189+
if folder == "datasets" and copy.get("schema") == "main":
190+
# Only virtual datasets can be translated
191+
for column in copy.get("columns", []):
192+
column["verbose_name"] = get_translation(column["verbose_name"], language)
193+
194+
for metric in copy.get("metrics", []):
195+
metric["verbose_name"] = get_translation(metric["verbose_name"], language)
196+
197+
copy["table_name"] = f"{copy['table_name']}_{language}"
198+
199+
if folder == "charts":
200+
copy["dataset_uuid"] = str(get_uuid5(copy["dataset_uuid"], language))
201+
187202
return copy
188203

189204

@@ -200,12 +215,9 @@ def generate_translated_dashboard_elements(copy, language):
200215
meta = element.get("meta", {})
201216
original_uuid = meta.get("uuid", None)
202217

203-
element_type = element.get("type", "Unknown")
204-
205-
translation, element_type, element_id = None, None, None
218+
translation, element_id = None, None
206219

207220
if original_uuid:
208-
element_type = "Chart"
209221
element_id = str(get_uuid5(original_uuid, language))
210222
translation = get_translation(meta["sliceName"], language)
211223

@@ -214,7 +226,6 @@ def generate_translated_dashboard_elements(copy, language):
214226

215227
elif element.get("type") in SUPPORTED_TYPES.keys():
216228
text_key = SUPPORTED_TYPES.get(element["type"])
217-
chart_body_id = element.get("id")
218229
if not meta or not meta.get(text_key):
219230
continue
220231

0 commit comments

Comments
 (0)