chore(core): allow missing translations on Crowdin
What changed, and why it matters
This commit changes an internal Python helper script used to prepare translation files for the Crowdin localization service. It makes the tool tolerate translation files where some languages or screen layouts do not have every translation key. There is no change to the Trezor firmware that runs on the device, no user-facing behavior change, and no security relevance.
No security action needed. Review as normal build-tooling maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies core/translations/crowdin.py. The split() function now skips translation keys whose resolved value is empty/falsy for a given layout, instead of writing empty entries. The merge() function now uses .get(‘translations’, {}) to avoid KeyError when a layout file lacks the translations object, fills missing layouts with empty strings, and deduplicates only when the stored value is still a dict. These are robustness changes to the localization build tooling.
Changed components
core/translations/crowdin.pyInspect captured patch +22 / −12
diff --git a/core/translations/crowdin.py b/core/translations/crowdin.py
index 07307e08e..39439e055 100644
--- a/core/translations/crowdin.py
+++ b/core/translations/crowdin.py
@@ -32,11 +32,11 @@ def split() -> None:
for lang in tdir.all_languages():
blob_json = tdir.load_lang(lang)
for layout_type in translations.ALL_LAYOUTS:
- # extract translations specific to this layout
- layout_specific_translations = {
- key: translations.get_translation(blob_json, key, layout_type)
- for key in blob_json["translations"].keys()
- }
+ # extract translations specific to this layout and drop empty values
+ layout_specific_translations = {}
+ for key in blob_json["translations"].keys():
+ if value := translations.get_translation(blob_json, key, layout_type):
+ layout_specific_translations[key] = value
# create a JSON file with only the "translations" item
result = {"translations": layout_specific_translations}
with open(CROWDIN_DIR / f"{lang}_{layout_type.name}.json", "w") as f:
@@ -59,22 +59,32 @@ def merge() -> None:
for lang in sorted(tdir.all_languages()):
merged_translations: dict[str, str | dict[str, str]] = collections.defaultdict(dict)
+
for layout_type in translations.ALL_LAYOUTS:
with open(CROWDIN_DIR / f"{lang}_{layout_type.name}.json", "r") as f:
blob_json = json.load(f)
# mapping string name to its translation (for the current layout)
- layout_specific_translations: dict[str, str] = blob_json["translations"]
+ layout_specific_translations: dict[str, str] = blob_json.get("translations", {})
for key, value in layout_specific_translations.items():
- # Clean the translation value
cleaned_value = clean_translation(value)
merged_translations[key][layout_type.name] = cleaned_value
- for key in merged_translations.keys():
- # deduplicate entries if all translations are the same
- unique_translations = set(merged_translations[key].values())
- if len(unique_translations) == 1:
- merged_translations[key] = unique_translations.pop()
+ # Ensure all layouts are present per key, fill missing with empty strings
+ for key, layout_map in merged_translations.items():
+ if isinstance(layout_map, dict):
+ for layout_type in translations.ALL_LAYOUTS:
+ layout_name = layout_type.name
+ if layout_name not in layout_map:
+ layout_map[layout_name] = ""
+
+ # Deduplicate entries if all translations are the same
+ for key in list(merged_translations.keys()):
+ values = merged_translations[key]
+ if isinstance(values, dict):
+ unique_translations = set(values.values())
+ if len(unique_translations) == 1:
+ merged_translations[key] = unique_translations.pop()
blob_json = tdir.load_lang(lang)
blob_json["translations"] = merged_translations
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.