def update_case_list_translations(sheet, rows, app):
    """
    Modify the translations of a module case list and detail display properties
    given a sheet of translation data. The properties in the sheet must be in
    the exact same order that they appear in the bulk app translation download.
    This function does not save the modified app to the database.

    :param sheet:
    :param rows: The rows of the sheet (we can't get this from the sheet
    because sheet.__iter__ can only be called once)
    :param app:
    :return:  Returns a list of message tuples. The first item in each tuple is
    a function like django.contrib.messages.error, and the second is a string.
    """
    # The list might contain DetailColumn instances in them that have exactly
    # the same attributes (but are in different positions). Therefore we must
    # match sheet rows to DetailColumns by position.
    msgs = []

    module_index = 1 - int(sheet.worksheet.title.replace("module", ""))
    module = app.get_module(module_index)

    if isinstance(module, ReportModule):
        return msgs

    # It is easier to process the translations if mapping and graph config
    # rows are nested under their respective DetailColumns.

    condensed_rows = []
    case_list_form_label = None
    index_of_last_enum_in_condensed = -1
    index_of_last_graph_in_condensed = -1
    for i, row in enumerate(rows):
        # If it's an enum case property, set index_of_last_enum_in_condensed
        if row['case_property'].endswith(" (ID Mapping Text)"):
            row['id'] = row['case_property'].split(" ")[0]
            condensed_rows.append(row)
            index_of_last_enum_in_condensed = len(condensed_rows) - 1

        # If it's an enum value, add it to it's parent enum property
        elif row['case_property'].endswith(" (ID Mapping Value)"):
            row['id'] = row['case_property'].split(" ")[0]
            parent = condensed_rows[index_of_last_enum_in_condensed]
            parent['mappings'] = parent.get('mappings', []) + [row]

        # If it's a graph case property, set index_of_last_graph_in_condensed
        elif row['case_property'].endswith(" (graph)"):
            row['id'] = row['case_property'].split(" ")[0]
            condensed_rows.append(row)
            index_of_last_graph_in_condensed = len(condensed_rows) - 1

        # If it's a graph configuration item, add it to its parent
        elif row['case_property'].endswith(" (graph config)"):
            row['id'] = row['case_property'].split(" ")[0]
            parent = condensed_rows[index_of_last_graph_in_condensed]
            parent['configs'] = parent.get('configs', []) + [row]

        # If it's a graph series configuration item, add it to its parent
        elif row['case_property'].endswith(" (graph series config)"):
            row['id'] = row['case_property'].split(" ")[0]
            row['series_index'] = row['case_property'].split(" ")[1]
            parent = condensed_rows[index_of_last_graph_in_condensed]
            parent['series_configs'] = parent.get('series_configs', []) + [row]

        # If it's a graph annotation, add it to its parent
        elif row['case_property'].startswith("graph annotation "):
            row['id'] = int(row['case_property'].split(" ")[-1])
            parent = condensed_rows[index_of_last_graph_in_condensed]
            parent['annotations'] = parent.get('annotations', []) + [row]

        # It's a case list registration form label. Don't add it to condensed rows
        elif row['case_property'] == 'case_list_form_label':
            case_list_form_label = row

        # It's a normal case property
        else:
            row['id'] = row['case_property']
            condensed_rows.append(row)

    list_rows = [
        row for row in condensed_rows if row['list_or_detail'] == 'list'
    ]
    detail_rows = [
        row for row in condensed_rows if row['list_or_detail'] == 'detail'
    ]
    short_details = list(module.case_details.short.get_columns())
    long_details = list(module.case_details.long.get_columns())

    # Check length of lists
    for expected_list, received_list, word in [
        (short_details, list_rows, "list"),
        (long_details, detail_rows, "detail")
    ]:
        if len(expected_list) != len(received_list):
            msgs.append((
                messages.error,
                "Expected {0} case {3} properties in sheet {2}, found {1}. "
                "No case list or detail properties for sheet {2} were "
                "updated".format(
                    len(expected_list),
                    len(received_list),
                    sheet.worksheet.title,
                    word
                )
            ))
    if msgs:
        return msgs

    # Update the translations
    def _update_translation(row, language_dict, require_translation=True):
        ok_to_delete_translations = (
            not require_translation or has_at_least_one_translation(
                    row, 'default', app.langs
            ))
        if ok_to_delete_translations:
            for lang in app.langs:
                translation = row['default_%s' % lang]
                if translation:
                    language_dict[lang] = translation
                else:
                    language_dict.pop(lang, None)
        else:
            msgs.append((
                messages.error,
                "You must provide at least one translation" +
                " of the case property '%s'" % row['case_property']
            ))

    for row, detail in \
            zip(list_rows, short_details) + zip(detail_rows, long_details):

        # Check that names match (user is not allowed to change property in the
        # upload). Mismatched names indicate the user probably botched the sheet.
        if row.get('id', None) != detail.field:
            msgs.append((
                messages.error,
                'A row in sheet {sheet} has an unexpected value of "{field}" '
                'in the case_property column. Case properties must appear in '
                'the same order as they do in the bulk app translation '
                'download. No translations updated for this row.'.format(
                    sheet=sheet.worksheet.title,
                    field=row.get('case_property', "")
                )
            ))
            continue

        # Update the translations for the row and all its child rows
        _update_translation(row, detail.header)
        for i, enum_value_row in enumerate(row.get('mappings', [])):
            _update_translation(enum_value_row, detail['enum'][i].value)
        for i, graph_annotation_row in enumerate(row.get('annotations', [])):
            _update_translation(
                graph_annotation_row,
                detail['graph_configuration']['annotations'][i].display_text,
                False
            )
        for graph_config_row in row.get('configs', []):
            config_key = graph_config_row['id']
            _update_translation(
                graph_config_row,
                detail['graph_configuration']['locale_specific_config'][config_key],
                False
            )
        for graph_config_row in row.get('series_configs', []):
            config_key = graph_config_row['id']
            series_index = int(graph_config_row['series_index'])
            _update_translation(
                graph_config_row,
                detail['graph_configuration']['series'][series_index]['locale_specific_config'][config_key],
                False
            )
    if case_list_form_label:
        _update_translation(case_list_form_label, module.case_list_form.label)

    return msgs
dataset/ETHPy150Open dimagi/commcare-hq/corehq/apps/app_manager/translations.py/update_case_list_translations/swapped operands of `-`