Skip to content

Commit

Permalink
Merge pull request #35064 from dimagi/gh/bulk-translations/update-def…
Browse files Browse the repository at this point in the history
…ault-lang

Update default language when bulk uploading translations
  • Loading branch information
gherceg authored Sep 17, 2024
2 parents 58e2d01 + cbea8b7 commit 6b4be3f
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 28 deletions.
72 changes: 44 additions & 28 deletions corehq/apps/translations/app_translations/upload_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ def update(self, rows):

# Setup
rows = get_unicode_dicts(rows)
template_translation_el = self._get_template_translation_el()
self._add_missing_translation_elements_to_itext(template_translation_el)
self._update_or_create_translation_elements()
self._populate_markdown_stats(rows)
self.msgs = []

Expand All @@ -87,8 +86,8 @@ def update(self, rows):

# Update the translations
for lang in self.langs:
translation_node = self.itext.find("./{f}translation[@lang='%s']" % lang)
assert translation_node.exists()
translation_element = self.itext.find("./{f}translation[@lang='%s']" % lang)
assert translation_element.exists()

for row in rows:
if row['label'] in label_ids_to_skip:
Expand Down Expand Up @@ -117,37 +116,54 @@ def update(self, rows):

return [(t, _('Error in {sheet}: {msg}').format(sheet=self.sheet_name, msg=m)) for (t, m) in self.msgs]

def _get_template_translation_el(self):
# Make language nodes for each language if they don't yet exist
#
def _update_or_create_translation_elements(self):
"""
Create new elements if necessary and ensure existing elements are up to date with the current app config
"""
template_element = self._get_template_translation_element()
for lang in self.langs:
translation_element = self.itext.find("./{f}translation[@lang='%s']" % lang)
if translation_element.exists():
self._update_default_attr_if_needed(translation_element, lang)
else:
self._create_translation_element(template_element, lang)

def _get_template_translation_element(self):
# Currently operating under the assumption that every xForm has at least
# one translation element, that each translation element has a text node
# for each question and that each text node has a value node under it.
# one translation element, that each translation element has a text element
# for each question and that each text element has a value element under it.
# Get a translation element to be used as a template for new elements, preferably of default lang
default_lang = self.app.default_language
default_trans_el = self.itext.find("./{f}translation[@lang='%s']" % default_lang)
if default_trans_el.exists():
return default_trans_el
default_element = self.itext.find("./{f}translation[@lang='%s']" % default_lang)
if default_element.exists():
return default_element

# If no default element found, fallback to finding any translation element
non_default_langs = copy.copy(self.app.langs)
non_default_langs.remove(default_lang)
for lang in non_default_langs:
trans_el = self.itext.find("./{f}translation[@lang='%s']" % lang)
if trans_el.exists():
return trans_el
raise Exception(_("Form has no translation node present to be used as a template."))
translation_element = self.itext.find("./{f}translation[@lang='%s']" % lang)
if translation_element.exists():
return translation_element

def _add_missing_translation_elements_to_itext(self, template_translation_el):
for lang in self.langs:
trans_el = self.itext.find("./{f}translation[@lang='%s']" % lang)
if not trans_el.exists():
new_trans_el = copy.deepcopy(template_translation_el.xml)
new_trans_el.set('lang', lang)
if lang != self.app.langs[0]:
# If the language isn't the default language
new_trans_el.attrib.pop('default', None)
else:
new_trans_el.set('default', '')
self.itext.xml.append(new_trans_el)
raise XFormException(_("Form has no translation element present to be used as a template."))

def _create_translation_element(self, template, lang):
translation_element = copy.deepcopy(template.xml)
translation_element.set('lang', lang)
self._update_default_attr_if_needed(translation_element, lang)
self.itext.xml.append(translation_element)

def _update_default_attr_if_needed(self, element, element_lang):
"""
A default language is set at both the app and form level (in translation elements), and it is
possible that they can get out of sync. This ensures the translation element in a form is up to date
with the app's default language.
"""
if element_lang != self.app.default_language:
element.attrib.pop('default', None)
else:
element.set('default', '')

def _populate_markdown_stats(self, rows):
# Aggregate Markdown vetoes, and translations that currently have Markdown
Expand Down
14 changes: 14 additions & 0 deletions corehq/apps/translations/tests/test_bulk_app_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,20 @@ def test_case_search_labels_on_upload(self):
self.assertEqual(module.search_config.description,
{'en': 'More information', 'fra': "Plus d'information"})

def test_translation_elements_are_up_to_date_with_app_langs(self):
self.assertEqual(self.app.langs, ["en", "fra"])
# upload translations to ensure all translation elements are created
self.upload_raw_excel_translations(self.multi_sheet_upload_headers, self.multi_sheet_upload_data)

# change default language and upload again
self.app.langs = ["fra", "en"]
self.upload_raw_excel_translations(self.multi_sheet_upload_headers, self.multi_sheet_upload_data)

xform = self.app.get_module(0).get_form(0).wrapped_xform()
itext_node = xform.model_node.find("{f}itext")
self.assertEqual(itext_node.find("./{f}translation[@lang='fra']").get("default"), "")
self.assertIsNone(itext_node.find("./{f}translation[@lang='en']").get('default'))


class BulkAppTranslationPartialsTest(BulkAppTranslationTestBase):

Expand Down

0 comments on commit 6b4be3f

Please sign in to comment.