def xml2po(resources, translations=None, resfilter=None, warnfunc=dummy_warn): """Return ``resources`` as a Babel .po ``Catalog`` instance. If given, ``translations`` will be used for the translated values. In this case, the returned value is a 2-tuple (catalog, unmatched), with the latter being a list of Android string resource names that are in the translated file, but not in the original. Both ``resources`` and ``translations`` must be ``ResourceTree`` objects, as returned by ``read_xml()``. From the application perspective, it will call this function with a ``translations`` object when initializing a new .po file based on an existing resource file (the 'init' command). For 'export', this function is called without translations. It will thus generate what is essentially a POT file (an empty .po file), and this will be merged into the existing .po catalogs, as per how gettext usually """ assert not translations or translations.language catalog = Catalog() if translations is not None: catalog.locale = translations.language.locale # We cannot let Babel determine the plural expr for the locale by # itself. It will use a custom list of plural expressions rather # than generate them based on CLDR. # See http://babel.edgewall.org/ticket/290. set_catalog_plural_forms(catalog, translations.language) for name, org_value in resources.items(): if resfilter and resfilter(name): continue trans_value = None if translations: trans_value = translations.pop(name, trans_value) if isinstance(org_value, StringArray): # a string-array, write as "name:index" if len(org_value) == 0: warnfunc("Warning: string-array '%s' is empty" % name, 'warning') continue if not isinstance(trans_value, StringArray): if trans_value: warnfunc(('""%s" is a string-array in the reference ' 'file, but not in the translation.') % name, 'warning') trans_value = StringArray() for index, item in enumerate(org_value): item_trans = trans_value[index].text if index < len( trans_value) else '' # If the string has formatting markers, indicate it in # the gettext output flags = [] if item.formatted: flags.append('c-format') ctx = "%s:%d" % (name, index) catalog.add(item.text, item_trans, auto_comments=item.comments, flags=flags, context=ctx) elif isinstance(org_value, Plurals): # a plurals, convert to a gettext plurals if len(org_value) == 0: warnfunc("Warning: plurals '%s' is empty" % name, 'warning') continue if not isinstance(trans_value, Plurals): if trans_value: warnfunc(('""%s" is a plurals in the reference ' 'file, but not in the translation.') % name, 'warning') trans_value = Plurals() # Taking the Translation objects for each quantity in ``org_value``, # we build a list of strings, which is how plurals are represented # in Babel. # # Since gettext only allows comments/flags on the whole # thing at once, we merge the comments/flags of all individual # plural strings into one. formatted = False comments = [] for _, translation in list(org_value.items()): if translation.formatted: formatted = True comments.extend(translation.comments) # For the message id, choose any two plural forms, but prefer # "one" and "other", assuming an English master resource. temp = org_value.copy() singular =\ temp.pop('one') if 'one' in temp else\ temp.pop('other') if 'other' in temp else\ temp.pop(list(temp.keys())[0]) plural =\ temp.pop('other') if 'other' in temp else\ temp[list(temp.keys())[0]] if temp else\ singular msgid = (singular.text, plural.text) del temp, singular, plural # We pick the quantities supported by the language (the rest # would be ignored by Android as well). msgstr = '' if trans_value: allowed_keywords = translations.language.plural_keywords msgstr = ['' for i in range(len(allowed_keywords))] for quantity, translation in list(trans_value.items()): try: index = translations.language.plural_keywords.index( quantity) except ValueError: warnfunc( ('"plurals "%s" uses quantity "%s", which ' 'is not supported for this language. See ' 'the README for an explanation. The ' 'quantity has been ignored') % (name, quantity), 'warning') else: msgstr[index] = translation.text flags = [] if formatted: flags.append('c-format') catalog.add(msgid, tuple(msgstr), flags=flags, auto_comments=comments, context=name) else: # a normal string # If the string has formatting markers, indicate it in # the gettext output # TODO DRY this. flags = [] if org_value.formatted: flags.append('c-format') catalog.add(org_value.text, trans_value.text if trans_value else '', flags=flags, auto_comments=org_value.comments, context=name) if translations is not None: # At this point, trans_strings only contains those for which # no original existed. return catalog, list(translations.keys()) else: return catalog
def xml2po(resources, translations=None, filter=None, warnfunc=dummy_warn): """Return ``resources`` as a Babel .po ``Catalog`` instance. If given, ``translations`` will be used for the translated values. In this case, the returned value is a 2-tuple (catalog, unmatched), with the latter being a list of Android string resource names that are in the translated file, but not in the original. Both ``resources`` and ``translations`` must be ``ResourceTree`` objects, as returned by ``read_xml()``. From the application perspective, it will call this function with a ``translations`` object when initializing a new .po file based on an existing resource file (the 'init' command). For 'export', this function is called without translations. It will thus generate what is essentially a POT file (an empty .po file), and this will be merged into the existing .po catalogs, as per how gettext usually """ assert not translations or translations.language catalog = Catalog() if translations is not None: catalog.locale = translations.language.locale # We cannot let Babel determine the plural expr for the locale by # itself. It will use a custom list of plural expressions rather # than generate them based on CLDR. # See http://babel.edgewall.org/ticket/290. set_catalog_plural_forms(catalog, translations.language) for name, org_value in resources.iteritems(): if filter and filter(name): continue trans_value = None if translations: trans_value = translations.pop(name, trans_value) if isinstance(org_value, StringArray): # a string-array, write as "name:index" if len(org_value) == 0: warnfunc("Warning: string-array '%s' is empty" % name, "warning") continue if not isinstance(trans_value, StringArray): if trans_value: warnfunc( ('""%s" is a string-array in the reference ' "file, but not in the translation.") % name, "warning", ) trans_value = StringArray() for index, item in enumerate(org_value): item_trans = trans_value[index].text if index < len(trans_value) else u"" # If the string has formatting markers, indicate it in # the gettext output flags = [] if item.formatted: flags.append("c-format") ctx = "%s:%d" % (name, index) catalog.add(item.text, item_trans, auto_comments=item.comments, flags=flags, context=ctx) elif isinstance(org_value, Plurals): # a plurals, convert to a gettext plurals if len(org_value) == 0: warnfunc("Warning: plurals '%s' is empty" % name, "warning") continue if not isinstance(trans_value, Plurals): if trans_value: warnfunc( ('""%s" is a plurals in the reference ' "file, but not in the translation.") % name, "warning" ) trans_value = Plurals() # Taking the Translation objects for each quantity in ``org_value``, # we build a list of strings, which is how plurals are represented # in Babel. # # Since gettext only allows comments/flags on the whole # thing at once, we merge the comments/flags of all individual # plural strings into one. formatted = False comments = [] for _, translation in org_value.items(): if translation.formatted: formatted = True comments.extend(translation.comments) # For the message id, choose any two plural forms, but prefer # "one" and "other", assuming an English master resource. temp = org_value.copy() singular = ( temp.pop("one") if "one" in temp else temp.pop("other") if "other" in temp else temp.pop(temp.keys()[0]) ) plural = temp.pop("other") if "other" in temp else temp[temp.keys()[0]] if temp else singular msgid = (singular.text, plural.text) del temp, singular, plural # We pick the quantities supported by the language (the rest # would be ignored by Android as well). msgstr = "" if trans_value: allowed_keywords = translations.language.plural_keywords msgstr = ["" for i in range(len(allowed_keywords))] for quantity, translation in trans_value.items(): try: index = translations.language.plural_keywords.index(quantity) except ValueError: warnfunc( ( '"plurals "%s" uses quantity "%s", which ' "is not supported for this language. See " "the README for an explanation. The " "quantity has been ignored" ) % (name, quantity), "warning", ) else: msgstr[index] = translation.text flags = [] if formatted: flags.append("c-format") catalog.add(msgid, tuple(msgstr), flags=flags, auto_comments=comments, context=name) else: # a normal string # If the string has formatting markers, indicate it in # the gettext output # TODO DRY this. flags = [] if org_value.formatted: flags.append("c-format") catalog.add( org_value.text, trans_value.text if trans_value else u"", flags=flags, auto_comments=org_value.comments, context=name, ) if translations is not None: # At this point, trans_strings only contains those for which # no original existed. return catalog, translations.keys() else: return catalog