Rev 5873: (vila) Extract messages for translation. (INADA Naoki) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Mon May 16 22:29:07 UTC 2011


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5873 [merge]
revision-id: pqm at pqm.ubuntu.com-20110516222836-prryayncmfqqh6w3
parent: pqm at pqm.ubuntu.com-20110516203329-ou3ukg7r795pxvkc
parent: songofacandy at gmail.com-20110512121935-5repm70fnk5lf3rh
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2011-05-16 22:28:36 +0000
message:
  (vila) Extract messages for translation. (INADA Naoki)
added:
  bzrlib/export_pot.py           bzrgettext-20110429104643-3wjy38532whc21yj-2
  bzrlib/tests/test_export_pot.py test_export_pot.py-20110509102137-efovgz233s9uk2b2-1
  po/                            po-20110505085110-hivwnmd3mt7ygr3p-1
modified:
  Makefile                       Makefile-20050805140406-d96e3498bb61c5bb
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
=== modified file 'Makefile'
--- a/Makefile	2011-04-18 00:44:08 +0000
+++ b/Makefile	2011-05-08 17:58:08 +0000
@@ -420,6 +420,26 @@
 	$(PYTHON) tools/win32/ostools.py remove dist
 
 
+# i18n targets
+
+.PHONY: update-pot po/bzr.pot
+update-pot: po/bzr.pot
+
+TRANSLATABLE_PYFILES:=$(shell find bzrlib -name '*.py' \
+    		| grep -v 'bzrlib/tests/' \
+    		| grep -v 'bzrlib/doc' \
+		)
+
+po/bzr.pot: $(PYFILES) $(DOCFILES)
+	$(PYTHON) ./bzr export-pot > po/bzr.pot
+	echo $(TRANSLATABLE_PYFILES) | xargs \
+	  xgettext --package-name "bzr" \
+	  --msgid-bugs-address "<bazaar at canonical.com>" \
+	  --copyright-holder "Canonical" \
+	  --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
+	  -d bzr -p po -o bzr.pot
+
+
 ### Packaging Targets ###
 
 .PHONY: dist check-dist-tarball

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2011-05-16 13:12:26 +0000
+++ b/bzrlib/builtins.py	2011-05-16 22:28:36 +0000
@@ -6155,6 +6155,16 @@
             self.outf.write('%s %s\n' % (path, location))
 
 
+class cmd_export_pot(Command):
+    __doc__ = """Export command helps and error messages in po format."""
+
+    hidden = True
+
+    def run(self):
+        from bzrlib.export_pot import export_pot
+        export_pot(self.outf)
+
+
 def _register_lazy_builtins():
     # register lazy builtins from other modules; called at startup and should
     # be only called once.

=== added file 'bzrlib/export_pot.py'
--- a/bzrlib/export_pot.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/export_pot.py	2011-05-12 12:19:35 +0000
@@ -0,0 +1,241 @@
+# Copyright (C) 2011 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+# The normalize function is taken from pygettext which is distributed
+# with Python under the Python License, which is GPL compatible.
+
+"""Extract docstrings from Bazaar commands.
+"""
+
+import inspect
+import os
+
+from bzrlib import (
+    commands as _mod_commands,
+    errors,
+    help_topics,
+    plugin,
+    )
+from bzrlib.trace import (
+    mutter,
+    note,
+    )
+
+
+def _escape(s):
+    s = (s.replace('\\', '\\\\')
+        .replace('\n', '\\n')
+        .replace('\r', '\\r')
+        .replace('\t', '\\t')
+        .replace('"', '\\"')
+        )
+    return s
+
+def _normalize(s):
+    # This converts the various Python string types into a format that
+    # is appropriate for .po files, namely much closer to C style.
+    lines = s.split('\n')
+    if len(lines) == 1:
+        s = '"' + _escape(s) + '"'
+    else:
+        if not lines[-1]:
+            del lines[-1]
+            lines[-1] = lines[-1] + '\n'
+        lines = map(_escape, lines)
+        lineterm = '\\n"\n"'
+        s = '""\n"' + lineterm.join(lines) + '"'
+    return s
+
+
+_FOUND_MSGID = None # set by entry function.
+
+def _poentry(outf, path, lineno, s, comment=None):
+    if s in _FOUND_MSGID:
+        return
+    _FOUND_MSGID.add(s)
+    if comment is None:
+        comment = ''
+    else:
+        comment = "# %s\n" % comment
+    mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
+    print >>outf, ('#: %s:%d\n' % (path, lineno) +
+           comment+
+           'msgid %s\n' % _normalize(s) +
+           'msgstr ""\n')
+
+def _poentry_per_paragraph(outf, path, lineno, msgid):
+    # TODO: How to split long help?
+    paragraphs = msgid.split('\n\n')
+    for p in paragraphs:
+        _poentry(outf, path, lineno, p)
+        lineno += p.count('\n') + 2
+
+_LAST_CACHE = _LAST_CACHED_SRC = None
+
+def _offsets_of_literal(src):
+    global _LAST_CACHE, _LAST_CACHED_SRC
+    if src == _LAST_CACHED_SRC:
+        return _LAST_CACHE.copy()
+
+    import ast
+    root = ast.parse(src)
+    offsets = {}
+    for node in ast.walk(root):
+        if not isinstance(node, ast.Str):
+            continue
+        offsets[node.s] = node.lineno - node.s.count('\n')
+
+    _LAST_CACHED_SRC = src
+    _LAST_CACHE = offsets.copy()
+    return offsets
+
+def _standard_options(outf):
+    from bzrlib.option import Option
+    src = inspect.findsource(Option)[0]
+    src = ''.join(src)
+    path = 'bzrlib/option.py'
+    offsets = _offsets_of_literal(src)
+
+    for name in sorted(Option.OPTIONS.keys()):
+        opt = Option.OPTIONS[name]
+        if getattr(opt, 'hidden', False):
+            continue
+        if getattr(opt, 'title', None):
+            lineno = offsets.get(opt.title, 9999)
+            if lineno == 9999:
+                note("%r is not found in bzrlib/option.py" % opt.title)
+            _poentry(outf, path, lineno, opt.title,
+                     'title of %r option' % name)
+        if getattr(opt, 'help', None):
+            lineno = offsets.get(opt.help, 9999)
+            if lineno == 9999:
+                note("%r is not found in bzrlib/option.py" % opt.help)
+            _poentry(outf, path, lineno, opt.help,
+                     'help of %r option' % name)
+
+def _command_options(outf, path, cmd):
+    src, default_lineno = inspect.findsource(cmd.__class__)
+    offsets = _offsets_of_literal(''.join(src))
+    for opt in cmd.takes_options:
+        if isinstance(opt, str):
+            continue
+        if getattr(opt, 'hidden', False):
+            continue
+        name = opt.name
+        if getattr(opt, 'title', None):
+            lineno = offsets.get(opt.title, default_lineno)
+            _poentry(outf, path, lineno, opt.title,
+                     'title of %r option of %r command' % (name, cmd.name()))
+        if getattr(opt, 'help', None):
+            lineno = offsets.get(opt.help, default_lineno)
+            _poentry(outf, path, lineno, opt.help,
+                     'help of %r option of %r command' % (name, cmd.name()))
+
+
+def _write_command_help(outf, cmd_name, cmd):
+    path = inspect.getfile(cmd.__class__)
+    if path.endswith('.pyc'):
+        path = path[:-1]
+    path = os.path.relpath(path)
+    src, lineno = inspect.findsource(cmd.__class__)
+    offsets = _offsets_of_literal(''.join(src))
+    lineno = offsets[cmd.__doc__]
+    doc = inspect.getdoc(cmd)
+
+    _poentry_per_paragraph(outf, path, lineno, doc)
+    _command_options(outf, path, cmd)
+
+def _command_helps(outf):
+    """Extract docstrings from path.
+
+    This respects the Bazaar cmdtable/table convention and will
+    only extract docstrings from functions mentioned in these tables.
+    """
+    from glob import glob
+
+    # builtin commands
+    for cmd_name in _mod_commands.builtin_command_names():
+        command = _mod_commands.get_cmd_object(cmd_name, False)
+        if command.hidden:
+            continue
+        note("Exporting messages from builtin command: %s", cmd_name)
+        _write_command_help(outf, cmd_name, command)
+
+    plugin_path = plugin.get_core_plugin_path()
+    core_plugins = glob(plugin_path + '/*/__init__.py')
+    core_plugins = [os.path.basename(os.path.dirname(p))
+                        for p in core_plugins]
+    # core plugins
+    for cmd_name in _mod_commands.plugin_command_names():
+        command = _mod_commands.get_cmd_object(cmd_name, False)
+        if command.hidden:
+            continue
+        if command.plugin_name() not in core_plugins:
+            # skip non-core plugins
+            # TODO: Support extracting from third party plugins.
+            continue
+        note("Exporting messages from plugin command: %s in %s",
+             cmd_name, command.plugin_name())
+        _write_command_help(outf, cmd_name, command)
+
+
+def _error_messages(outf):
+    """Extract fmt string from bzrlib.errors."""
+    path = errors.__file__
+    if path.endswith('.pyc'):
+        path = path[:-1]
+    offsets = _offsets_of_literal(open(path).read())
+
+    base_klass = errors.BzrError
+    for name in dir(errors):
+        klass = getattr(errors, name)
+        if not inspect.isclass(klass):
+            continue
+        if not issubclass(klass, base_klass):
+            continue
+        if klass is base_klass:
+            continue
+        if klass.internal_error:
+            continue
+        fmt = getattr(klass, "_fmt", None)
+        if fmt:
+            note("Exporting message from error: %s", name)
+            _poentry(outf, 'bzrlib/errors.py',
+                     offsets.get(fmt, 9999), fmt)
+
+def _help_topics(outf):
+    topic_registry = help_topics.topic_registry
+    for key in topic_registry.keys():
+        doc = topic_registry.get(key)
+        if isinstance(doc, str):
+            _poentry_per_paragraph(
+                    outf,
+                    'dummy/help_topics/'+key+'/detail.txt',
+                    1, doc)
+
+        summary = topic_registry.get_summary(key)
+        if summary is not None:
+            _poentry(outf, 'dummy/help_topics/'+key+'/summary.txt',
+                     1, summary)
+
+def export_pot(outf):
+    global _FOUND_MSGID
+    _FOUND_MSGID = set()
+    _standard_options(outf)
+    _command_helps(outf)
+    _error_messages(outf)
+    # disable exporting help topics until we decide  how to translate it.
+    #_help_topics(outf)

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2011-05-13 12:51:05 +0000
+++ b/bzrlib/tests/__init__.py	2011-05-16 22:28:36 +0000
@@ -3783,6 +3783,7 @@
         'bzrlib.tests.test_eol_filters',
         'bzrlib.tests.test_errors',
         'bzrlib.tests.test_export',
+        'bzrlib.tests.test_export_pot',
         'bzrlib.tests.test_extract',
         'bzrlib.tests.test_fetch',
         'bzrlib.tests.test_fixtures',

=== added file 'bzrlib/tests/test_export_pot.py'
--- a/bzrlib/tests/test_export_pot.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_export_pot.py	2011-05-11 17:44:45 +0000
@@ -0,0 +1,147 @@
+# Copyright (C) 2011 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+from cStringIO import StringIO
+import textwrap
+
+from bzrlib import (
+    export_pot,
+    tests,
+    )
+
+class TestEscape(tests.TestCase):
+
+    def test_simple_escape(self):
+        self.assertEqual(
+                export_pot._escape('foobar'),
+                'foobar')
+
+        s = '''foo\nbar\r\tbaz\\"spam"'''
+        e = '''foo\\nbar\\r\\tbaz\\\\\\"spam\\"'''
+        self.assertEqual(export_pot._escape(s), e)
+
+    def test_complex_escape(self):
+        s = '''\\r \\\n'''
+        e = '''\\\\r \\\\\\n'''
+        self.assertEqual(export_pot._escape(s), e)
+
+
+class TestNormalize(tests.TestCase):
+
+    def test_single_line(self):
+        s = 'foobar'
+        e = '"foobar"'
+        self.assertEqual(export_pot._normalize(s), e)
+
+        s = 'foo"bar'
+        e = '"foo\\"bar"'
+        self.assertEqual(export_pot._normalize(s), e)
+
+    def test_multi_lines(self):
+        s = 'foo\nbar\n'
+        e = '""\n"foo\\n"\n"bar\\n"'
+        self.assertEqual(export_pot._normalize(s), e)
+
+        s = '\nfoo\nbar\n'
+        e = ('""\n'
+             '"\\n"\n'
+             '"foo\\n"\n'
+             '"bar\\n"')
+        self.assertEqual(export_pot._normalize(s), e)
+
+
+class PoEntryTestCase(tests.TestCase):
+
+    def setUp(self):
+        self.overrideAttr(export_pot, '_FOUND_MSGID', set())
+        self._outf = StringIO()
+        super(PoEntryTestCase, self).setUp()
+
+    def check_output(self, expected):
+        self.assertEqual(
+                self._outf.getvalue(),
+                textwrap.dedent(expected)
+                )
+
+class TestPoEntry(PoEntryTestCase):
+
+    def test_simple(self):
+        export_pot._poentry(self._outf, 'dummy', 1, "spam")
+        export_pot._poentry(self._outf, 'dummy', 2, "ham", 'EGG')
+        self.check_output('''\
+                #: dummy:1
+                msgid "spam"
+                msgstr ""
+
+                #: dummy:2
+                # EGG
+                msgid "ham"
+                msgstr ""
+
+                ''')
+
+    def test_duplicate(self):
+        export_pot._poentry(self._outf, 'dummy', 1, "spam")
+        # This should be ignored.
+        export_pot._poentry(self._outf, 'dummy', 2, "spam", 'EGG')
+
+        self.check_output('''\
+                #: dummy:1
+                msgid "spam"
+                msgstr ""\n
+                ''')
+
+
+class TestPoentryPerPergraph(PoEntryTestCase):
+
+    def test_single(self):
+        export_pot._poentry_per_paragraph(
+                self._outf,
+                'dummy',
+                10,
+                '''foo\nbar\nbaz\n'''
+                )
+        self.check_output('''\
+                #: dummy:10
+                msgid ""
+                "foo\\n"
+                "bar\\n"
+                "baz\\n"
+                msgstr ""\n
+                ''')
+
+    def test_multi(self):
+        export_pot._poentry_per_paragraph(
+                self._outf,
+                'dummy',
+                10,
+                '''spam\nham\negg\n\nSPAM\nHAM\nEGG\n'''
+                )
+        self.check_output('''\
+                #: dummy:10
+                msgid ""
+                "spam\\n"
+                "ham\\n"
+                "egg"
+                msgstr ""
+
+                #: dummy:14
+                msgid ""
+                "SPAM\\n"
+                "HAM\\n"
+                "EGG\\n"
+                msgstr ""\n
+                ''')

=== added directory 'po'



More information about the bazaar-commits mailing list