diff --git a/doc/intl.rst b/doc/intl.rst index d8c5ec78c..b60367675 100644 --- a/doc/intl.rst +++ b/doc/intl.rst @@ -6,9 +6,8 @@ Internationalization .. versionadded:: 1.1 Complementary to translations provided for Sphinx-generated messages such as -navigation bars Sphinx provides mechanisms facilitating *document* translations +navigation bars, Sphinx provides mechanisms facilitating *document* translations in itself. It relies on the existing configuration values :confval:`language` and :confval:`locale_dirs`. - .. XXX write more! diff --git a/sphinx/builders/intl.py b/sphinx/builders/intl.py index 41943b9be..49993b802 100644 --- a/sphinx/builders/intl.py +++ b/sphinx/builders/intl.py @@ -5,17 +5,19 @@ The MessageCatalogBuilder class. - :copyright: Copyright 2010 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import collections -from datetime import datetime from os import path +from codecs import open +from datetime import datetime +from collections import defaultdict from sphinx.builders import Builder +from sphinx.builders.versioning import VersioningBuilderMixin from sphinx.util.nodes import extract_messages -from sphinx.util.osutil import SEP +from sphinx.util.osutil import SEP, copyfile from sphinx.util.console import darkgreen POHEADER = ur""" @@ -39,14 +41,17 @@ msgstr "" """[1:] -class MessageCatalogBuilder(Builder): + +class I18nBuilder(Builder, VersioningBuilderMixin): """ - Builds gettext-style message catalogs (.pot files). + General i18n builder. """ - name = 'gettext' + name = 'i18n' def init(self): - self.catalogs = collections.defaultdict(list) + Builder.init(self) + VersioningBuilderMixin.init(self) + self.catalogs = defaultdict(dict) def get_target_uri(self, docname, typ=None): return '' @@ -58,18 +63,26 @@ class MessageCatalogBuilder(Builder): return def write_doc(self, docname, doctree): - """ - Store a document's translatable strings in the message catalog of its - section. For this purpose a document's *top-level directory* -- or - otherwise its *name* -- is considered its section. - """ catalog = self.catalogs[docname.split(SEP, 1)[0]] - for _, msg in extract_messages(doctree): - # XXX msgctxt for duplicate messages - if msg not in catalog: - catalog.append(msg) + + self.handle_versioning(docname, doctree, nodes.TextElement) + + for node, msg in extract_messages(doctree): + catalog.setdefault(node.uid, msg) def finish(self): + Builder.finish(self) + VersioningBuilderMixin.finish(self) + + +class MessageCatalogBuilder(I18nBuilder): + """ + Builds gettext-style message catalogs (.pot files). + """ + name = 'gettext' + + def finish(self): + I18nBuilder.finish(self) data = dict( version = self.config.version, copyright = self.config.copyright, @@ -81,13 +94,15 @@ class MessageCatalogBuilder(Builder): self.catalogs.iteritems(), "writing message catalogs... ", lambda (section, _):darkgreen(section), len(self.catalogs)): - pofile = open(path.join(self.outdir, '%s.pot' % section), 'w') + pofn = path.join(self.outdir, section + '.pot') + pofile = open(pofn, 'w', encoding='utf-8') try: pofile.write(POHEADER % data) - for message in messages: + for uid, message in messages.iteritems(): # message contains *one* line of text ready for translation - message = message.replace(u'\\', ur'\\').replace(u'"', ur'\"') - pomsg = u'msgid "%s"\nmsgstr ""\n\n' % message - pofile.write(pomsg.encode('utf-8')) + message = message.replace(u'\\', ur'\\'). \ + replace(u'"', ur'\"') + pomsg = u'#%s\nmsgid "%s"\nmsgstr ""\n\n' % (uid, message) + pofile.write(pomsg) finally: pofile.close() diff --git a/sphinx/environment.py b/sphinx/environment.py index f5d77a1e6..e9e984c35 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -211,7 +211,7 @@ class Locale(Transform): # XXX ctx not used #ctx = node.parent patch = new_document(source, settings) - msgstr = catalog.ugettext(msg) + msgstr = catalog.gettext(msg) # XXX add marker to untranslated parts if not msgstr or msgstr == msg: # as-of-yet untranslated continue @@ -793,6 +793,7 @@ class BuildEnvironment: if node['level'] < filterlevel: node.parent.remove(node) + def process_dependencies(self, docname, doctree): """ Process docutils-generated dependency info. diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 472fbed61..de106bb9b 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -218,4 +218,6 @@ def init(locale_dirs, language, catalog='sphinx'): translator = gettext.NullTranslations() has_translation = False translators[catalog] = translator + if hasattr(translator, 'ugettext'): + translator.gettext = translator.ugettext return translator, has_translation diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index 9ca2351f5..032911111 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -46,7 +46,8 @@ def test_gettext(app): if p.returncode != 0: print stdout print stderr - assert False, 'msginit exited with return code %s' % p.returncode + assert False, 'msginit exited with return code %s' % \ + p.returncode assert (app.outdir / 'en_US.po').isfile(), 'msginit failed' try: p = Popen(['msgfmt', 'en_US.po', '-o', @@ -59,12 +60,14 @@ def test_gettext(app): if p.returncode != 0: print stdout print stderr - assert False, 'msgfmt exited with return code %s' % p.returncode - assert (app.outdir / 'en' / 'LC_MESSAGES' / 'test_root.mo').isfile(), 'msgfmt failed' + assert False, 'msgfmt exited with return code %s' % \ + p.returncode + assert (app.outdir / 'en' / 'LC_MESSAGES' / 'test_root.mo').isfile(), \ + 'msgfmt failed' finally: os.chdir(cwd) - _ = gettext.translation('test_root', app.outdir, languages=['en']).ugettext + _ = gettext.translation('test_root', app.outdir, languages=['en']).gettext assert _("Testing various markup") == u"Testing various markup" @with_app(buildername='gettext') @@ -95,7 +98,9 @@ def setup_patch(): print stdout print stderr assert False, 'msgfmt exited with return code %s' % p.returncode - assert (test_root / 'xx' / 'LC_MESSAGES' / 'bom.mo').isfile(), 'msgfmt failed' + assert (test_root / 'xx' / 'LC_MESSAGES' / 'bom.mo').isfile(), \ + 'msgfmt failed' + def teardown_patch(): (test_root / 'xx').rmtree() test_patch.setup = setup_patch