././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4177892 babelgladeextractor-0.7.2/0000775000175000017500000000000015162740647015134 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1510656099.0 babelgladeextractor-0.7.2/LICENSE0000644000175000017500000000261713202544143016127 0ustar00muellimuelliCopyright (C) 2007 Unfinished Software, UfSoft.org All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578317792.0 babelgladeextractor-0.7.2/MANIFEST.in0000644000175000017500000000017013604633740016660 0ustar00muellimuelliinclude babelglade/tests/test.raw.* include babelglade/tests/test_extract.* recursive-include babelglade/tests/locale * ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4177892 babelgladeextractor-0.7.2/PKG-INFO0000644000175000017500000000653115162740647016234 0ustar00muellimuelliMetadata-Version: 2.4 Name: babelgladeextractor Version: 0.7.2 Summary: Babel l10n support for Glade, GtkBuilder, and .desktop files Author-email: Pedro Algarvio Maintainer-email: Tobias Mueller License: BSD Project-URL: Homepage, https://github.com/GNOME-Keysign/babel-glade Project-URL: Repository, https://github.com/GNOME-Keysign/babel-glade Keywords: PyGTK,PyGObject,Glade,GtkBuilder,gettext,Babel,I18n,L10n Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Internationalization Classifier: Topic :: Software Development :: Localization Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: Babel Dynamic: license-file # BabelGladeExtractor This package contains message catalog extractors for the following formats, extending [Babel][babel] so it can handle them. * The new XML format used by [Glade][glade] 3.8 and above, properly known as the [GtkBuilder UI Definitions][uixml] format; * The older "GladeXML" format used by libglade and older versions of Glade; * The GNOME [AppData XML][appdataxml] dialect, because it's similar; * FreeDesktop.org [Desktop Entry][desktopfile] files. ## Getting started To make these formats translatable, install this package using pip: ```shell pip3 install BabelGladeExtractor ``` Then in your own projects, map some source and data files to the simple extractor names "glade" and "desktop" that are provided by this package. In your `setup.py`, add a section like ```ini [extract_messages] mapping_file = babel.cfg output_file = subdir/myproject.pot input_dirs = . ``` Next, create a separate `babel.cfg` file, and add sections to it for each format you want to translate. ```ini [glade: **.ui] [desktop: **.desktop] ``` You can then use Babel's [setuptools integration][babelsetuptools] or its [command line interface][babelcli] for your routine i18n lifecycle tasks. ```shell python3 setup.py extract_messages ``` There's a lot more to it than this, naturally. See Babel's extensive [Working with Message Catalogs][babelpo] documentation for a detailed explanation of how to get translatable strings into your Python code. In Glade 3.22, when you are editing a string property in a sidebar, click the edit icon on the right hand side of the text entry. In the dialog that pops up, enter the text in the main text box, and make sure that the Translatable checkbox is ticked. You can also add some helpful [context][pocontext] or comments for your translators if you need to give them a hint. BabelGladeExtractor will handle the corresponding XML attributes appropriately when it extracts strings for translation. [babel]: http://babel.pocoo.org/ [glade]: https://glade.gnome.org/ [uixml]: https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI [appdataxml]: https://wiki.gnome.org/Initiatives/GnomeGoals/AppDataGnomeSoftware [desktopfile]: https://specifications.freedesktop.org/desktop-entry-spec/ [babelsetuptools]: http://babel.pocoo.org/en/latest/setup.html [babelcli]: http://babel.pocoo.org/en/latest/cmdline.html [babelpo]: http://babel.pocoo.org/en/latest/messages.html [pocontext]: https://www.gnu.org/software/gettext/manual/html_node/Contexts.html#Contexts ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1579262706.0 babelgladeextractor-0.7.2/README.md0000644000175000017500000000473613610321362016404 0ustar00muellimuelli# BabelGladeExtractor This package contains message catalog extractors for the following formats, extending [Babel][babel] so it can handle them. * The new XML format used by [Glade][glade] 3.8 and above, properly known as the [GtkBuilder UI Definitions][uixml] format; * The older "GladeXML" format used by libglade and older versions of Glade; * The GNOME [AppData XML][appdataxml] dialect, because it's similar; * FreeDesktop.org [Desktop Entry][desktopfile] files. ## Getting started To make these formats translatable, install this package using pip: ```shell pip3 install BabelGladeExtractor ``` Then in your own projects, map some source and data files to the simple extractor names "glade" and "desktop" that are provided by this package. In your `setup.py`, add a section like ```ini [extract_messages] mapping_file = babel.cfg output_file = subdir/myproject.pot input_dirs = . ``` Next, create a separate `babel.cfg` file, and add sections to it for each format you want to translate. ```ini [glade: **.ui] [desktop: **.desktop] ``` You can then use Babel's [setuptools integration][babelsetuptools] or its [command line interface][babelcli] for your routine i18n lifecycle tasks. ```shell python3 setup.py extract_messages ``` There's a lot more to it than this, naturally. See Babel's extensive [Working with Message Catalogs][babelpo] documentation for a detailed explanation of how to get translatable strings into your Python code. In Glade 3.22, when you are editing a string property in a sidebar, click the edit icon on the right hand side of the text entry. In the dialog that pops up, enter the text in the main text box, and make sure that the Translatable checkbox is ticked. You can also add some helpful [context][pocontext] or comments for your translators if you need to give them a hint. BabelGladeExtractor will handle the corresponding XML attributes appropriately when it extracts strings for translation. [babel]: http://babel.pocoo.org/ [glade]: https://glade.gnome.org/ [uixml]: https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI [appdataxml]: https://wiki.gnome.org/Initiatives/GnomeGoals/AppDataGnomeSoftware [desktopfile]: https://specifications.freedesktop.org/desktop-entry-spec/ [babelsetuptools]: http://babel.pocoo.org/en/latest/setup.html [babelcli]: http://babel.pocoo.org/en/latest/cmdline.html [babelpo]: http://babel.pocoo.org/en/latest/messages.html [pocontext]: https://www.gnu.org/software/gettext/manual/html_node/Contexts.html#Contexts ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4127889 babelgladeextractor-0.7.2/babelglade/0000775000175000017500000000000015162740647017176 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1536222055.0 babelgladeextractor-0.7.2/babelglade/__init__.py0000644000175000017500000000007613344161547021304 0ustar00muellimuellifrom babelglade.extract import extract_glade, extract_desktop ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1579262706.0 babelgladeextractor-0.7.2/babelglade/extract.py0000644000175000017500000001113413610321362021201 0ustar00muellimuelli# -*- coding: utf-8 -*- # vim: sw=4 ts=4 fenc=utf-8 # ============================================================================= # $Id$ # ============================================================================= # $URL$ # $LastChangedDate$ # $Rev$ # $LastChangedBy$ # ============================================================================= # Copyright (C) 2006 Ufsoft.org - Pedro Algarvio # # Please view LICENSE for additional licensing information. # ============================================================================= from __future__ import unicode_literals import xml.etree.ElementTree as etree def extract_glade(fileobj, keywords, comment_tags, options): """Extracts translatable strings from Glade files or GtkBuilder UI XML. :param fileobj: the file-like object to extract from, iterable by lines :param keywords: a list of translation keywords to extract, with the same names and meanings as C/Python i18n function names. :param comment_tags: a list of translator tags to search for and include in the results. This is ignored. :param options: a dictionary of additional options (optional) :return: An iterator over ``(lineno, funcname, message, comments)`` tuples whose interpretation depends on ``funcname``. :rtype: iterator Properties must be marked translatable="yes". The "context" and "comments" attributes are respected. The yielded tuples are returned as if you used ``gettext()`` or ``pgettext()`` in C or Python code. This means that translatable XML strings with contexts are are only extracted if the string ``"pgettext"`` is present in ``keywords``, and XML strings without context are only extracted if ``"gettext"`` is present. The shorthand ``_`` and ``C_`` aliases from ``g18n.h`` are valid ``keywords`` too. By default, Babel passes both these function names in ``keywords``, amongst others, so you don't normally need to worry about this. See also: * babel.messages.extract.extract() * http://babel.pocoo.org/en/latest/messages.html#writing-extraction-methods * https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html """ parser = etree.XMLPullParser(["end"]) pgettext_wanted = ("pgettext" in keywords) or ("C_" in keywords) gettext_wanted = "gettext" in keywords truthy_values = [s.casefold() for s in ["yes", "true", "1", "y", "t"]] for line_idx, line_data in enumerate(fileobj): parser.feed(line_data) for event, elem in parser.read_events(): assert event == "end" translatable_attr = elem.attrib.get("translatable", "no") if not translatable_attr.casefold() in truthy_values: continue comments = [] if "comments" in elem.attrib: comments.append(elem.attrib["comments"]) # Babel's interpretation of the yielded tuple depends on the # function name returned as part of it. This tells Babel what # the elements of the returned messages list or tuple mean. func_name = None if "context" in elem.attrib and pgettext_wanted: func_name = "pgettext" context = elem.attrib["context"] messages = [context, elem.text] elif gettext_wanted and "context" not in elem.attrib: # Returned strings are equivalent to a list or tuple # of length 1, like the arguments to C gettext()/_(). func_name = "gettext" messages = elem.text if func_name is None: continue yield (line_idx + 1, func_name, messages, comments) # All localestrings from https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s05.html TRANSLATABLE = ( 'Name', 'GenericName', 'Comment', 'Icon', 'Keywords', ) def extract_desktop(fileobj, keywords, comment_tags, options): for lineno, line in enumerate(fileobj, 1): if line.startswith(b'[Desktop Entry]'): continue for t in TRANSLATABLE: if not line.startswith(t.encode('utf-8')): continue else: l = line.decode('utf-8') comments = [] key_value = l.split('=', 1) key, value = key_value[0:2] funcname = key # FIXME: Why can I not assign that name to funcname? funcname = '' message = value comments.append(key) yield (lineno, funcname, message.strip(), comments) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1774961063.414789 babelgladeextractor-0.7.2/babelglade/tests/0000775000175000017500000000000015162740647020340 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1575022587.0 babelgladeextractor-0.7.2/babelglade/tests/__init__.py0000644000175000017500000000165413570167773022461 0ustar00muellimuelli# -*- coding: utf-8 -*- # vim: sw=4 ts=4 fenc=utf-8 # ============================================================================= # $Id$ # ============================================================================= # $URL$ # $LastChangedDate$ # $Rev$ # $LastChangedBy$ # ============================================================================= # Copyright (C) 2007 Ufsoft.org - Pedro Algarvio # # Please view LICENSE for additional licensing information. # ============================================================================= import unittest def suite(): from babelglade.tests import test_extract, test_translate suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(test_extract.GladeExtractTests)) suite.addTest(unittest.makeSuite(test_translate.TranslateTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4107888 babelgladeextractor-0.7.2/babelglade/tests/locale/0000775000175000017500000000000015162740647021577 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4097886 babelgladeextractor-0.7.2/babelglade/tests/locale/fr/0000775000175000017500000000000015162740647022206 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1774961063.415789 babelgladeextractor-0.7.2/babelglade/tests/locale/fr/LC_MESSAGES/0000775000175000017500000000000015162740647023773 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1574887626.0 babelgladeextractor-0.7.2/babelglade/tests/locale/fr/LC_MESSAGES/test.po0000644000175000017500000000051213567560312025302 0ustar00muellimuellimsgid "" msgstr "" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "This should be translated." msgstr "Cela devrait être traduit." msgid "" "\n" " Multi line\n" " text.\n" " " msgstr "" "\n" " Texte\n" " multi-ligne.\n" " "././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4107888 babelgladeextractor-0.7.2/babelglade/tests/locale/nl/0000775000175000017500000000000015162740647022210 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1774961063.415789 babelgladeextractor-0.7.2/babelglade/tests/locale/nl/LC_MESSAGES/0000775000175000017500000000000015162740647023775 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1574887626.0 babelgladeextractor-0.7.2/babelglade/tests/locale/nl/LC_MESSAGES/test.po0000644000175000017500000000051113567560312025303 0ustar00muellimuellimsgid "" msgstr "" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "This should be translated." msgstr "Dit moet worden vertaald." msgid "" "\n" " Multi line\n" " text.\n" " " msgstr "" "\n" " Meerregelige\n" " tekst.\n" " "././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1574887626.0 babelgladeextractor-0.7.2/babelglade/tests/test.raw.appdata.xml0000644000175000017500000000050513567560312024234 0ustar00muellimuelli babelglade.test

This should be translated.

Multi line text.

././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1574887626.0 babelgladeextractor-0.7.2/babelglade/tests/test.raw.desktop0000644000175000017500000000032713567560312023476 0ustar00muellimuelli[Desktop Entry] Name=Keysign Comment=This should be translated. Keywords=python;gpg;gnupg;key;openpgp; Type=Application Exec=python3 -m keysign Icon=org.gnome.Keysign Categories=GTK;GNOME;Utility; StartupNotify=true././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578317792.0 babelgladeextractor-0.7.2/babelglade/tests/test_extract.glade2.xml0000644000175000017500000000324313604633740024722 0ustar00muellimuelli GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK This is a Label True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK A button 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578317792.0 babelgladeextractor-0.7.2/babelglade/tests/test_extract.gtkbuilder.xml0000644000175000017500000000550613604633740025724 0ustar00muellimuelli False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK This is a Label True True 0 True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK This is a Label True True 1 A button True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True 2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578317792.0 babelgladeextractor-0.7.2/babelglade/tests/test_extract.py0000644000175000017500000000636213604633740023422 0ustar00muellimuelli# -*- coding: utf-8 -*- # vim: sw=4 ts=4 fenc=utf-8 # ============================================================================= # $Id$ # ============================================================================= # $URL$ # $LastChangedDate$ # $Rev$ # $LastChangedBy$ # ============================================================================= # Copyright (C) 2007 Ufsoft.org - Pedro Algarvio # # Please view LICENSE for additional licensing information. # ============================================================================= import os import unittest from babelglade.extract import extract_glade # from babel.messages.extract import DEFAULT_KEYWORDS DEFAULT_KEYWORDS = ("gettext", "pgettext") GLADE2_FILE = "test_extract.glade2.xml" GTKBUILDER_FILE = "test_extract.gtkbuilder.xml" def relative(test_file): return os.path.join(os.path.dirname(__file__), test_file) class GladeExtractTests(unittest.TestCase): def test_glade2_yield_four_item_tuples_with_keywords(self): with open(relative(GLADE2_FILE), "r") as fp: extracted = extract_glade(fp, DEFAULT_KEYWORDS, None, {}) extracted = list(extracted) assert len(extracted) > 0, "extract_glade is not respecting presence of keywords" for entry in extracted: assert len(entry) == 4, "extract_glade did not return a tuple of length 4" def test_glade2_yield_no_tuples_without_keywords(self): with open(relative(GLADE2_FILE), "r") as fp: extracted = extract_glade(fp, (), None, {}) extracted = list(extracted) assert len(extracted) == 0, "extract_glade is not respecting absence of keywords" def test_gtkbuilder_yield_pgettext_tuples_for_elems_with_context(self): with open(relative(GTKBUILDER_FILE), "r") as fp: extracted = extract_glade(fp, ["pgettext"], None, {}) extracted = list(extracted) assert len(extracted) == 2, "extract_glade is not handling elems with context like pgettext" for entry in extracted: assert len(entry) == 4, "extract_glade did not return a tuple of length 4" lineno, funcname, message, comments = entry assert funcname == "pgettext", "extract_glade is not returning the right funcname (pgettext) for elems with context" assert isinstance(message, tuple) or isinstance(message, list), "extract_glade is not returning a tuple or a list when pretending it extracted from pgettext()" assert len(message) == 2, "extract_glade is not returning (ctx, msg) when pretending it extracted from pgettext()" def test_gtkbuilder_yield_gettext_tuples_for_elems_without_context(self): with open(relative(GTKBUILDER_FILE), "r") as fp: extracted = extract_glade(fp, ["gettext"], None, {}) extracted = list(extracted) assert len(extracted) == 1, "extract_glade is not handling elems without context like gettext" for entry in extracted: assert len(entry) == 4, "extract_glade did not return a tuple of length 4" lineno, funcname, message, comments = entry assert funcname == "gettext", "extract_glade is not returning the right funcname (gettext) for elems without any context" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1575022587.0 babelgladeextractor-0.7.2/babelglade/tests/test_translate.py0000644000175000017500000000250513570167773023752 0ustar00muellimuelli import os import unittest from babelglade.translate import translate_desktop_file, translate_appdata_file def relative(test_file): return os.path.join(os.path.dirname(__file__), test_file) class TranslateTestCase(unittest.TestCase): def test_desktop_file(self): translate_desktop_file(relative("test.raw.desktop"), "test.desktop", relative("locale")) with open("test.desktop") as desktop: content = desktop.read() assert "Comment=This should be translated." in content assert "Comment[nl]=Dit moet worden vertaald." in content assert "Comment[fr]=Cela devrait être traduit." in content def test_appdata_xml(self): translate_appdata_file(relative("test.raw.appdata.xml"), "test.appdata.xml", relative("locale")) with open("test.appdata.xml") as desktop: content = desktop.read() assert "

This should be translated.

" in content assert '

Cela devrait être traduit.

' in content assert '

Dit moet worden vertaald.

' in content assert """

Multi line text.

""" in content assert """

Texte multi-ligne.

""" in content assert """

Meerregelige tekst.

""" in content././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1575022587.0 babelgladeextractor-0.7.2/babelglade/translate.py0000644000175000017500000001031713570167773021551 0ustar00muellimuelliimport codecs import logging import os import xml.etree.ElementTree as etree from babel.messages.pofile import read_po log = logging.getLogger(__name__) def translate_desktop_file(infile, outfile, localedir): infp = codecs.open(infile, 'rb', encoding='utf-8') outfp = codecs.open(outfile, 'wb', encoding='utf-8') catalogs = get_catalogs(localedir) for line in (x.strip() for x in infp): log.debug('Found in original (%s): %r', type(line), line) # We intend to ignore the first line if line.startswith('[Desktop'): additional_lines = [] else: additional_lines = [] # This is a rather primitive approach to generating the translated # desktop file. For example we don't really care about all the # keys in the file. But its simplicity is a feature and we # ignore the runtime overhead, because it should only run centrally # once. key, value = line.split('=', 1) log.debug("Found key: %r", key) for locale, catalog in catalogs.items(): translated = catalog.get(value) log.debug("Translated %r[%r]=%r: %r (%r)", key, locale, value, translated, translated.string if translated else '') if translated and translated.string \ and translated.string != value: additional_line = u'{keyword}[{locale}]={translated}'.format( keyword=key, locale=locale, translated=translated.string, ) additional_lines.append(additional_line) log.debug("Writing more lines: %s", additional_lines) # Write the new file. # First the original line found it in the file, then the translations. outfp.writelines((outline+'\n' for outline in ([line] + additional_lines))) infp.close() outfp.close() def translate_appdata_file(infile, outfile, localedir): catalogs = get_catalogs(localedir) tree = etree.parse(infile) root = tree.getroot() add_translations(root, catalogs) tree.write(outfile, encoding='utf-8', xml_declaration=True) def add_translations(parent, catalogs): tail = parent.text last_tail = None for pos, elem in enumerate(parent, start=1): if elem.get("translatable") == "yes": elem.attrib.pop("translatable", None) elem.attrib.pop("comments", None) # Are comments allowed? last_tail = elem.tail elem.tail = tail message = elem.text for locale, catalog in catalogs.items(): translated = catalog.get(message) if translated and translated.string \ and translated.string != message: log.debug("Translated [%s]%r: %r (%r)", locale, message, translated, translated.string) tr = etree.Element(elem.tag) attrib = tr.attrib attrib["{http://www.w3.org/XML/1998/namespace}lang"] = str(locale) tr.text = translated.string tr.tail = tail parent.insert(pos, tr) else: add_translations(elem, catalogs) if last_tail: parent[-1].tail = last_tail def get_catalogs(localedir): # glob in Python 3.5 takes ** syntax # pofiles = glob.glob(os.path.join(localedir, '**.po', recursive=True)) pofiles = sorted([os.path.join(dirpath, f) for dirpath, dirnames, files in os.walk(localedir) for f in files if f.endswith('.po')]) logging.debug('Loading %r', pofiles) catalogs = {} for pofile in pofiles: with open(pofile, 'r') as f: catalog = read_po(f) catalogs[catalog.locale] = catalog logging.info("Found %d strings for %s", len(catalog), catalog.locale) # logging.debug("Strings for %r", catalog, catalog.values()) if not catalogs: logging.warning("Could not find pofiles in %r", pofiles) return catalogs ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1774961063.416789 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/0000775000175000017500000000000015162740647022624 5ustar00muellimuelli././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961063.0 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/PKG-INFO0000644000175000017500000000653115162740647023724 0ustar00muellimuelliMetadata-Version: 2.4 Name: babelgladeextractor Version: 0.7.2 Summary: Babel l10n support for Glade, GtkBuilder, and .desktop files Author-email: Pedro Algarvio Maintainer-email: Tobias Mueller License: BSD Project-URL: Homepage, https://github.com/GNOME-Keysign/babel-glade Project-URL: Repository, https://github.com/GNOME-Keysign/babel-glade Keywords: PyGTK,PyGObject,Glade,GtkBuilder,gettext,Babel,I18n,L10n Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Internationalization Classifier: Topic :: Software Development :: Localization Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: Babel Dynamic: license-file # BabelGladeExtractor This package contains message catalog extractors for the following formats, extending [Babel][babel] so it can handle them. * The new XML format used by [Glade][glade] 3.8 and above, properly known as the [GtkBuilder UI Definitions][uixml] format; * The older "GladeXML" format used by libglade and older versions of Glade; * The GNOME [AppData XML][appdataxml] dialect, because it's similar; * FreeDesktop.org [Desktop Entry][desktopfile] files. ## Getting started To make these formats translatable, install this package using pip: ```shell pip3 install BabelGladeExtractor ``` Then in your own projects, map some source and data files to the simple extractor names "glade" and "desktop" that are provided by this package. In your `setup.py`, add a section like ```ini [extract_messages] mapping_file = babel.cfg output_file = subdir/myproject.pot input_dirs = . ``` Next, create a separate `babel.cfg` file, and add sections to it for each format you want to translate. ```ini [glade: **.ui] [desktop: **.desktop] ``` You can then use Babel's [setuptools integration][babelsetuptools] or its [command line interface][babelcli] for your routine i18n lifecycle tasks. ```shell python3 setup.py extract_messages ``` There's a lot more to it than this, naturally. See Babel's extensive [Working with Message Catalogs][babelpo] documentation for a detailed explanation of how to get translatable strings into your Python code. In Glade 3.22, when you are editing a string property in a sidebar, click the edit icon on the right hand side of the text entry. In the dialog that pops up, enter the text in the main text box, and make sure that the Translatable checkbox is ticked. You can also add some helpful [context][pocontext] or comments for your translators if you need to give them a hint. BabelGladeExtractor will handle the corresponding XML attributes appropriately when it extracts strings for translation. [babel]: http://babel.pocoo.org/ [glade]: https://glade.gnome.org/ [uixml]: https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI [appdataxml]: https://wiki.gnome.org/Initiatives/GnomeGoals/AppDataGnomeSoftware [desktopfile]: https://specifications.freedesktop.org/desktop-entry-spec/ [babelsetuptools]: http://babel.pocoo.org/en/latest/setup.html [babelcli]: http://babel.pocoo.org/en/latest/cmdline.html [babelpo]: http://babel.pocoo.org/en/latest/messages.html [pocontext]: https://www.gnu.org/software/gettext/manual/html_node/Contexts.html#Contexts ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961063.0 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/SOURCES.txt0000664000175000017500000000132215162740647024506 0ustar00muellimuelliLICENSE MANIFEST.in README.md pyproject.toml babelglade/__init__.py babelglade/extract.py babelglade/translate.py babelglade/tests/__init__.py babelglade/tests/test.raw.appdata.xml babelglade/tests/test.raw.desktop babelglade/tests/test_extract.glade2.xml babelglade/tests/test_extract.gtkbuilder.xml babelglade/tests/test_extract.py babelglade/tests/test_translate.py babelglade/tests/locale/fr/LC_MESSAGES/test.po babelglade/tests/locale/nl/LC_MESSAGES/test.po babelgladeextractor.egg-info/PKG-INFO babelgladeextractor.egg-info/SOURCES.txt babelgladeextractor.egg-info/dependency_links.txt babelgladeextractor.egg-info/entry_points.txt babelgladeextractor.egg-info/requires.txt babelgladeextractor.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961063.0 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/dependency_links.txt0000664000175000017500000000000115162740647026672 0ustar00muellimuelli ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961063.0 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/entry_points.txt0000664000175000017500000000027115162740647026122 0ustar00muellimuelli[babel.extractors] desktop = babelglade.extract:extract_desktop glade = babelglade.extract:extract_glade [distutils.commands] compile_catalog = babel.messages.frontend:compile_catalog ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961063.0 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/requires.txt0000664000175000017500000000000615162740647025220 0ustar00muellimuelliBabel ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961063.0 babelgladeextractor-0.7.2/babelgladeextractor.egg-info/top_level.txt0000664000175000017500000000001315162740647025350 0ustar00muellimuellibabelglade ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1774961047.0 babelgladeextractor-0.7.2/pyproject.toml0000664000175000017500000000253415162740627020052 0ustar00muellimuelli[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "babelgladeextractor" version = "0.7.2" description = "Babel l10n support for Glade, GtkBuilder, and .desktop files" readme = "README.md" license = {text = "BSD"} authors = [ {name = "Pedro Algarvio", email = "ufs@ufsoft.org"} ] maintainers = [ {name = "Tobias Mueller", email = "tobiasmue@gnome.org"} ] keywords = [ "PyGTK", "PyGObject", "Glade", "GtkBuilder", "gettext", "Babel", "I18n", "L10n" ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", "Topic :: Software Development :: Internationalization", "Topic :: Software Development :: Localization" ] requires-python = ">=3.7" dependencies = [ "Babel" ] [project.urls] Homepage = "https://github.com/GNOME-Keysign/babel-glade" Repository = "https://github.com/GNOME-Keysign/babel-glade" [project.entry-points."babel.extractors"] glade = "babelglade.extract:extract_glade" desktop = "babelglade.extract:extract_desktop" [project.entry-points."distutils.commands"] compile_catalog = "babel.messages.frontend:compile_catalog" [tool.setuptools] packages = ["babelglade", "babelglade.tests"] include-package-data = true ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1774961063.4177892 babelgladeextractor-0.7.2/setup.cfg0000664000175000017500000000004615162740647016755 0ustar00muellimuelli[egg_info] tag_build = tag_date = 0