summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2018-03-01 23:51:23 +0900
committerGitHub <noreply@github.com>2018-03-01 23:51:23 +0900
commit8dbc1b53d1c40fc3b0d90a9b7c0ca55ea28305c8 (patch)
tree26274fb52ebd6a7ba55a9e3a2628e9239801b253
parentd48e1ef9e5d2fda22b48e25d5f2289b19b919b31 (diff)
parent278577c2ef6832dfae96d970ba5703ec98fc60d7 (diff)
downloadsphinx-git-8dbc1b53d1c40fc3b0d90a9b7c0ca55ea28305c8.tar.gz
Merge pull request #4674 from tk0miya/improve_i18n
Improve i18n
-rw-r--r--CHANGES6
-rw-r--r--doc/extdev/appapi.rst2
-rw-r--r--doc/extdev/i18n.rst17
-rw-r--r--doc/extdev/index.rst1
-rw-r--r--sphinx/application.py15
-rw-r--r--sphinx/builders/html.py6
-rw-r--r--sphinx/cmd/build.py7
-rw-r--r--sphinx/cmd/quickstart.py5
-rw-r--r--sphinx/cmdline.py1
-rw-r--r--sphinx/config.py14
-rw-r--r--sphinx/domains/c.py18
-rw-r--r--sphinx/domains/cpp.py24
-rw-r--r--sphinx/domains/javascript.py22
-rw-r--r--sphinx/domains/python.py34
-rw-r--r--sphinx/domains/rst.py6
-rw-r--r--sphinx/domains/std.py22
-rw-r--r--sphinx/ext/apidoc.py7
-rw-r--r--sphinx/ext/autosummary/generate.py5
-rw-r--r--sphinx/ext/napoleon/__init__.py5
-rw-r--r--sphinx/locale/__init__.py226
-rw-r--r--sphinx/testing/util.py3
-rw-r--r--sphinx/transforms/__init__.py2
-rw-r--r--sphinx/transforms/i18n.py8
-rw-r--r--sphinx/writers/latex.py2
-rw-r--r--sphinx/writers/manpage.py2
-rw-r--r--sphinx/writers/texinfo.py2
-rw-r--r--tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.mobin0 -> 80 bytes
-rw-r--r--tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.po2
-rw-r--r--tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.mobin0 -> 82 bytes
-rw-r--r--tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.po2
-rw-r--r--tests/test_locale.py66
31 files changed, 360 insertions, 172 deletions
diff --git a/CHANGES b/CHANGES
index edaa5d060..100bab1a5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -31,6 +31,7 @@ Deprecated
* ``sphinx.cmdline`` is deprecated. Please use ``sphinx.cmd.build`` instead.
* All ``env.update()``, ``env._read_serial()`` and ``env._read_parallel()`` are
deprecated. Please use ``builder.read()`` instead.
+* ``sphinx.locale.l_()`` is deprecated. Please use ``_()`` instead
Features added
--------------
@@ -41,10 +42,15 @@ Features added
* :confval:`source_suffix` allows a mapping fileext to file types
* Add :confval:`author` as a configuration value
* #2852: imgconverter: Support to convert GIF to PNG
+* ``sphinx-build`` command supports i18n console output
+* Add ``app.add_message_catalog()`` and ``sphinx.locale.get_translations()`` to
+ support translation for 3rd party extensions
Bugs fixed
----------
+* i18n: message catalogs were reset on each initialization
+
Testing
--------
diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst
index 3143bf4f2..584cf375b 100644
--- a/doc/extdev/appapi.rst
+++ b/doc/extdev/appapi.rst
@@ -93,6 +93,8 @@ package.
.. automethod:: Sphinx.add_html_theme(name, theme_path)
+.. automethod:: Sphinx.add_message_catalog(catalog, locale_dir)
+
.. automethod:: Sphinx.is_parallel_allowed(typ)
.. exception:: ExtensionError
diff --git a/doc/extdev/i18n.rst b/doc/extdev/i18n.rst
new file mode 100644
index 000000000..c8c54da36
--- /dev/null
+++ b/doc/extdev/i18n.rst
@@ -0,0 +1,17 @@
+.. _i18n-api:
+
+i18n API
+========
+
+.. currentmodule:: sphinx.locale
+
+.. autofunction:: init
+
+.. autofunction:: init_console
+
+.. autofunction:: get_translation
+
+.. autofunction:: _
+
+.. autofunction:: __
+
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index 3380c31ea..e04aa15c0 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -93,3 +93,4 @@ APIs used for writing extensions
parserapi
nodes
logging
+ i18n
diff --git a/sphinx/application.py b/sphinx/application.py
index 7ac6b810c..b39996106 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -273,7 +273,7 @@ class Sphinx(object):
locale_dirs = [None, path.join(package_dir, 'locale')] + user_locale_dirs # type: ignore # NOQA
else:
locale_dirs = []
- self.translator, has_translation = locale.init(locale_dirs, self.config.language)
+ self.translator, has_translation = locale.init(locale_dirs, self.config.language) # type: ignore # NOQA
if self.config.language is not None:
if has_translation or self.config.language == 'en':
# "en" never needs to be translated
@@ -1113,6 +1113,19 @@ class Sphinx(object):
logger.debug('[app] adding HTML theme: %r, %r', name, theme_path)
self.html_themes[name] = theme_path
+ def add_message_catalog(self, catalog, locale_dir):
+ # type: (unicode, unicode) -> None
+ """Register a message catalog.
+
+ The *catalog* is a name of catalog, and *locale_dir* is a base path
+ of message catalog. For more details, see
+ :func:`sphinx.locale.get_translation()`.
+
+ .. versionadded:: 1.8
+ """
+ locale.init([locale_dir], self.config.language, catalog)
+ locale.init_console(locale_dir, catalog)
+
# ---- other methods -------------------------------------------------
def is_parallel_allowed(self, typ):
# type: (unicode) -> bool
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index 86dc23540..b8c43d08c 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -37,7 +37,7 @@ from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree
from sphinx.highlighting import PygmentsBridge
-from sphinx.locale import _, l_
+from sphinx.locale import _
from sphinx.search import js_index
from sphinx.theming import HTMLThemeFactory
from sphinx.util import jsonimpl, logging, status_iterator
@@ -446,7 +446,7 @@ class StandaloneHTMLBuilder(Builder):
# typically doesn't include the time of day
lufmt = self.config.html_last_updated_fmt
if lufmt is not None:
- self.last_updated = format_date(lufmt or _('%b %d, %Y'), # type: ignore
+ self.last_updated = format_date(lufmt or _('%b %d, %Y'),
language=self.config.language)
else:
self.last_updated = None
@@ -1427,7 +1427,7 @@ def setup(app):
app.add_config_value('html_theme_path', [], 'html')
app.add_config_value('html_theme_options', {}, 'html')
app.add_config_value('html_title',
- lambda self: l_('%s %s documentation') % (self.project, self.release),
+ lambda self: _('%s %s documentation') % (self.project, self.release),
'html', string_classes)
app.add_config_value('html_short_title', lambda self: self.html_title, 'html')
app.add_config_value('html_style', None, 'html', string_classes)
diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py
index 5851b0958..496adab67 100644
--- a/sphinx/cmd/build.py
+++ b/sphinx/cmd/build.py
@@ -11,6 +11,7 @@
from __future__ import print_function
import argparse
+import locale
import multiprocessing
import os
import sys
@@ -20,7 +21,8 @@ from typing import TYPE_CHECKING
from docutils.utils import SystemMessage
from six import text_type, binary_type
-from sphinx import __display_version__
+import sphinx.locale
+from sphinx import __display_version__, package_dir
from sphinx.application import Sphinx
from sphinx.errors import SphinxError
from sphinx.util import Tee, format_exception_cut_frames, save_traceback
@@ -304,6 +306,9 @@ def build_main(argv=sys.argv[1:]): # type: ignore
def main(argv=sys.argv[1:]): # type: ignore
# type: (List[unicode]) -> int
+ locale.setlocale(locale.LC_ALL, '')
+ sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')
+
if sys.argv[1:2] == ['-M']:
return make_main(argv)
else:
diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py
index 6a53b40f2..91f7ef350 100644
--- a/sphinx/cmd/quickstart.py
+++ b/sphinx/cmd/quickstart.py
@@ -12,6 +12,7 @@ from __future__ import absolute_import
from __future__ import print_function
import argparse
+import locale
import os
import re
import sys
@@ -36,6 +37,7 @@ from six import PY2, PY3, text_type, binary_type
from six.moves import input
from six.moves.urllib.parse import quote as urlquote
+import sphinx.locale
from sphinx import __display_version__, package_dir
from sphinx.util import texescape
from sphinx.util.console import ( # type: ignore
@@ -598,6 +600,9 @@ Makefile to be used with sphinx-build.
def main(argv=sys.argv[1:]):
# type: (List[str]) -> int
+ locale.setlocale(locale.LC_ALL, '')
+ sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')
+
if not color_terminal():
nocolor()
diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py
index e05bbec63..3ad16d041 100644
--- a/sphinx/cmdline.py
+++ b/sphinx/cmdline.py
@@ -8,6 +8,7 @@
:copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+from __future__ import absolute_import
from __future__ import print_function
import sys
diff --git a/sphinx/config.py b/sphinx/config.py
index 46e57fd6b..5c7e2bb73 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple, Union
from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integer_types
from sphinx.errors import ConfigError
-from sphinx.locale import l_, __
+from sphinx.locale import _, __
from sphinx.util import logging
from sphinx.util.i18n import format_date
from sphinx.util.osutil import cd
@@ -143,10 +143,10 @@ class Config(object):
nitpick_ignore = ([], None),
numfig = (False, 'env'),
numfig_secnum_depth = (1, 'env'),
- numfig_format = ({'section': l_('Section %s'),
- 'figure': l_('Fig. %s'),
- 'table': l_('Table %s'),
- 'code-block': l_('Listing %s')},
+ numfig_format = ({'section': _('Section %s'),
+ 'figure': _('Fig. %s'),
+ 'table': _('Table %s'),
+ 'code-block': _('Listing %s')},
'env'),
tls_verify = (True, 'env'),
@@ -204,7 +204,7 @@ class Config(object):
# type: () -> None
# check all values for deviation from the default value's type, since
# that can result in TypeErrors all over the place
- # NB. since config values might use l_() we have to wait with calling
+ # NB. since config values might use _() we have to wait with calling
# this method until i18n is initialized
for name in self._raw_config:
if name not in self.values:
@@ -214,7 +214,7 @@ class Config(object):
permitted = settings[2] if len(settings) == 3 else ()
if hasattr(default, '__call__'):
- default = default(self) # could invoke l_()
+ default = default(self) # could invoke _()
if default is None and not permitted:
continue # neither inferrable nor expliclitly permitted types
current = self[name]
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index c06651eb4..709395710 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -18,7 +18,7 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
-from sphinx.locale import l_, _
+from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util.docfields import Field, TypedField
from sphinx.util.nodes import make_refnode
@@ -62,12 +62,12 @@ class CObject(ObjectDescription):
"""
doc_field_types = [
- TypedField('parameter', label=l_('Parameters'),
+ TypedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
typerolename='type', typenames=('type',)),
- Field('returnvalue', label=l_('Returns'), has_arg=False,
+ Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
- Field('returntype', label=l_('Return type'), has_arg=False,
+ Field('returntype', label=_('Return type'), has_arg=False,
names=('rtype',)),
]
@@ -254,11 +254,11 @@ class CDomain(Domain):
name = 'c'
label = 'C'
object_types = {
- 'function': ObjType(l_('function'), 'func'),
- 'member': ObjType(l_('member'), 'member'),
- 'macro': ObjType(l_('macro'), 'macro'),
- 'type': ObjType(l_('type'), 'type'),
- 'var': ObjType(l_('variable'), 'data'),
+ 'function': ObjType(_('function'), 'func'),
+ 'member': ObjType(_('member'), 'member'),
+ 'macro': ObjType(_('macro'), 'macro'),
+ 'type': ObjType(_('type'), 'type'),
+ 'var': ObjType(_('variable'), 'data'),
}
directives = {
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 0bd883a30..dfad97a6c 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -21,7 +21,7 @@ from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.environment import NoUri
-from sphinx.locale import l_, _
+from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField
@@ -5563,16 +5563,16 @@ class CPPObject(ObjectDescription):
"""Description of a C++ language object."""
doc_field_types = [
- GroupedField('parameter', label=l_('Parameters'),
+ GroupedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
can_collapse=True),
- GroupedField('template parameter', label=l_('Template Parameters'),
+ GroupedField('template parameter', label=_('Template Parameters'),
names=('tparam', 'template parameter'),
can_collapse=True),
- GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class',
+ GroupedField('exceptions', label=_('Throws'), rolename='cpp:class',
names=('throws', 'throw', 'exception'),
can_collapse=True),
- Field('returnvalue', label=l_('Returns'), has_arg=False,
+ Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
]
@@ -5985,13 +5985,13 @@ class CPPDomain(Domain):
name = 'cpp'
label = 'C++'
object_types = {
- 'class': ObjType(l_('class'), 'class', 'type', 'identifier'),
- 'function': ObjType(l_('function'), 'function', 'func', 'type', 'identifier'),
- 'member': ObjType(l_('member'), 'member', 'var'),
- 'type': ObjType(l_('type'), 'type', 'identifier'),
- 'concept': ObjType(l_('concept'), 'concept', 'identifier'),
- 'enum': ObjType(l_('enum'), 'enum', 'type', 'identifier'),
- 'enumerator': ObjType(l_('enumerator'), 'enumerator')
+ 'class': ObjType(_('class'), 'class', 'type', 'identifier'),
+ 'function': ObjType(_('function'), 'function', 'func', 'type', 'identifier'),
+ 'member': ObjType(_('member'), 'member', 'var'),
+ 'type': ObjType(_('type'), 'type', 'identifier'),
+ 'concept': ObjType(_('concept'), 'concept', 'identifier'),
+ 'enum': ObjType(_('enum'), 'enum', 'type', 'identifier'),
+ 'enumerator': ObjType(_('enumerator'), 'enumerator')
}
directives = {
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index ac68a295b..7f21adf65 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -18,7 +18,7 @@ from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.domains.python import _pseudo_parse_arglist
-from sphinx.locale import l_, _
+from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.nodes import make_refnode
@@ -202,15 +202,15 @@ class JSCallable(JSObject):
has_arguments = True
doc_field_types = [
- TypedField('arguments', label=l_('Arguments'),
+ TypedField('arguments', label=_('Arguments'),
names=('argument', 'arg', 'parameter', 'param'),
typerolename='func', typenames=('paramtype', 'type')),
- GroupedField('errors', label=l_('Throws'), rolename='err',
+ GroupedField('errors', label=_('Throws'), rolename='err',
names=('throws', ),
can_collapse=True),
- Field('returnvalue', label=l_('Returns'), has_arg=False,
+ Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
- Field('returntype', label=l_('Return type'), has_arg=False,
+ Field('returntype', label=_('Return type'), has_arg=False,
names=('rtype',)),
]
@@ -297,12 +297,12 @@ class JavaScriptDomain(Domain):
label = 'JavaScript'
# if you add a new object type make sure to edit JSObject.get_index_string
object_types = {
- 'function': ObjType(l_('function'), 'func'),
- 'method': ObjType(l_('method'), 'meth'),
- 'class': ObjType(l_('class'), 'class'),
- 'data': ObjType(l_('data'), 'data'),
- 'attribute': ObjType(l_('attribute'), 'attr'),
- 'module': ObjType(l_('module'), 'mod'),
+ 'function': ObjType(_('function'), 'func'),
+ 'method': ObjType(_('method'), 'meth'),
+ 'class': ObjType(_('class'), 'class'),
+ 'data': ObjType(_('data'), 'data'),
+ 'attribute': ObjType(_('attribute'), 'attr'),
+ 'module': ObjType(_('module'), 'mod'),
}
directives = {
'function': JSCallable,
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index fb864a7a0..6f3c27c89 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -19,7 +19,7 @@ from six import iteritems
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType, Index
-from sphinx.locale import l_, _
+from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField
@@ -173,21 +173,21 @@ class PyObject(ObjectDescription):
}
doc_field_types = [
- PyTypedField('parameter', label=l_('Parameters'),
+ PyTypedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument',
'keyword', 'kwarg', 'kwparam'),
typerolename='class', typenames=('paramtype', 'type'),
can_collapse=True),
- PyTypedField('variable', label=l_('Variables'), rolename='obj',
+ PyTypedField('variable', label=_('Variables'), rolename='obj',
names=('var', 'ivar', 'cvar'),
typerolename='class', typenames=('vartype',),
can_collapse=True),
- PyGroupedField('exceptions', label=l_('Raises'), rolename='exc',
+ PyGroupedField('exceptions', label=_('Raises'), rolename='exc',
names=('raises', 'raise', 'exception', 'except'),
can_collapse=True),
- Field('returnvalue', label=l_('Returns'), has_arg=False,
+ Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
- PyField('returntype', label=l_('Return type'), has_arg=False,
+ PyField('returntype', label=_('Return type'), has_arg=False,
names=('rtype',), bodyrolename='class'),
]
@@ -631,8 +631,8 @@ class PythonModuleIndex(Index):
"""
name = 'modindex'
- localname = l_('Python Module Index')
- shortname = l_('modules')
+ localname = _('Python Module Index')
+ shortname = _('modules')
def generate(self, docnames=None):
# type: (Iterable[unicode]) -> Tuple[List[Tuple[unicode, List[List[Union[unicode, int]]]]], bool] # NOQA
@@ -702,15 +702,15 @@ class PythonDomain(Domain):
name = 'py'
label = 'Python'
object_types = {
- 'function': ObjType(l_('function'), 'func', 'obj'),
- 'data': ObjType(l_('data'), 'data', 'obj'),
- 'class': ObjType(l_('class'), 'class', 'exc', 'obj'),
- 'exception': ObjType(l_('exception'), 'exc', 'class', 'obj'),
- 'method': ObjType(l_('method'), 'meth', 'obj'),
- 'classmethod': ObjType(l_('class method'), 'meth', 'obj'),
- 'staticmethod': ObjType(l_('static method'), 'meth', 'obj'),
- 'attribute': ObjType(l_('attribute'), 'attr', 'obj'),
- 'module': ObjType(l_('module'), 'mod', 'obj'),
+ 'function': ObjType(_('function'), 'func', 'obj'),
+ 'data': ObjType(_('data'), 'data', 'obj'),
+ 'class': ObjType(_('class'), 'class', 'exc', 'obj'),
+ 'exception': ObjType(_('exception'), 'exc', 'class', 'obj'),
+ 'method': ObjType(_('method'), 'meth', 'obj'),
+ 'classmethod': ObjType(_('class method'), 'meth', 'obj'),
+ 'staticmethod': ObjType(_('static method'), 'meth', 'obj'),
+ 'attribute': ObjType(_('attribute'), 'attr', 'obj'),
+ 'module': ObjType(_('module'), 'mod', 'obj'),
} # type: Dict[unicode, ObjType]
directives = {
diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py
index 3c053a9d1..ef3a94d6a 100644
--- a/sphinx/domains/rst.py
+++ b/sphinx/domains/rst.py
@@ -17,7 +17,7 @@ from six import iteritems
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
-from sphinx.locale import l_, _
+from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util.nodes import make_refnode
@@ -116,8 +116,8 @@ class ReSTDomain(Domain):
label = 'reStructuredText'
object_types = {
- 'directive': ObjType(l_('directive'), 'dir'),
- 'role': ObjType(l_('role'), 'role'),
+ 'directive': ObjType(_('directive'), 'dir'),
+ 'role': ObjType(_('role'), 'role'),
}
directives = {
'directive': ReSTDirective,
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 801f54b14..54d0e5de5 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -22,7 +22,7 @@ from six import iteritems
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
-from sphinx.locale import l_, _
+from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util import ws_re, logging, docname_join
from sphinx.util.nodes import clean_astext, make_refnode
@@ -81,7 +81,7 @@ class GenericObject(ObjectDescription):
class EnvVar(GenericObject):
- indextemplate = l_('environment variable; %s')
+ indextemplate = _('environment variable; %s')
class EnvVarXRefRole(XRefRole):
@@ -452,13 +452,13 @@ class StandardDomain(Domain):
label = 'Default'
object_types = {
- 'term': ObjType(l_('glossary term'), 'term', searchprio=-1),
- 'token': ObjType(l_('grammar token'), 'token', searchprio=-1),
- 'label': ObjType(l_('reference label'), 'ref', 'keyword',
+ 'term': ObjType(_('glossary term'), 'term', searchprio=-1),
+ 'token': ObjType(_('grammar token'), 'token', searchprio=-1),
+ 'label': ObjType(_('reference label'), 'ref', 'keyword',
searchprio=-1),
- 'envvar': ObjType(l_('environment variable'), 'envvar'),
- 'cmdoption': ObjType(l_('program option'), 'option'),
- 'doc': ObjType(l_('document'), 'doc', searchprio=-1)
+ 'envvar': ObjType(_('environment variable'), 'envvar'),
+ 'cmdoption': ObjType(_('program option'), 'option'),
+ 'doc': ObjType(_('document'), 'doc', searchprio=-1)
} # type: Dict[unicode, ObjType]
directives = {
@@ -495,9 +495,9 @@ class StandardDomain(Domain):
'citations': {}, # citation_name -> docname, labelid, lineno
'citation_refs': {}, # citation_name -> list of docnames
'labels': { # labelname -> docname, labelid, sectionname
- 'genindex': ('genindex', '', l_('Index')),
- 'modindex': ('py-modindex', '', l_('Module Index')),
- 'search': ('search', '', l_('Search Page')),
+ 'genindex': ('genindex', '', _('Index')),
+ 'modindex': ('py-modindex', '', _('Module Index')),
+ 'search': ('search', '', _('Search Page')),
},
'anonlabels': { # labelname -> docname, labelid
'genindex': ('genindex', ''),
diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py
index 8a93271c1..87f55b7cc 100644
--- a/sphinx/ext/apidoc.py
+++ b/sphinx/ext/apidoc.py
@@ -19,6 +19,7 @@ from __future__ import print_function
import argparse
import glob
+import locale
import os
import sys
from fnmatch import fnmatch
@@ -27,7 +28,8 @@ from typing import TYPE_CHECKING
from six import binary_type
-from sphinx import __display_version__
+import sphinx.locale
+from sphinx import __display_version__, package_dir
from sphinx.cmd.quickstart import EXTENSIONS
from sphinx.util import rst
from sphinx.util.osutil import FileAvoidWrite, ensuredir, walk
@@ -382,6 +384,9 @@ Note: By default this script will not overwrite already created files.""")
def main(argv=sys.argv[1:]):
# type: (List[str]) -> int
"""Parse and check the command line arguments."""
+ locale.setlocale(locale.LC_ALL, '')
+ sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')
+
parser = get_parser()
args = parser.parse_args(argv)
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 9946ab037..d266e675c 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -21,6 +21,7 @@ from __future__ import print_function
import argparse
import codecs
+import locale
import os
import pydoc
import re
@@ -30,6 +31,7 @@ from typing import TYPE_CHECKING
from jinja2 import FileSystemLoader, TemplateNotFound
from jinja2.sandbox import SandboxedEnvironment
+import sphinx.locale
from sphinx import __display_version__
from sphinx import package_dir
from sphinx.ext.autosummary import import_by_name, get_documenter
@@ -402,6 +404,9 @@ The format of the autosummary directive is documented in the
def main(argv=sys.argv[1:]):
# type: (List[str]) -> None
+ locale.setlocale(locale.LC_ALL, '')
+ sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')
+
app = DummyApplication()
setup_documenters(app)
args = get_parser().parse_args(argv)
diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py
index 28e48d13a..cd7831946 100644
--- a/sphinx/ext/napoleon/__init__.py
+++ b/sphinx/ext/napoleon/__init__.py
@@ -325,14 +325,13 @@ def _patch_python_domain():
pass
else:
import sphinx.domains.python
- import sphinx.locale
- l_ = sphinx.locale.lazy_gettext
+ from sphinx.locale import _
for doc_field in sphinx.domains.python.PyObject.doc_field_types:
if doc_field.name == 'parameter':
doc_field.names = ('param', 'parameter', 'arg', 'argument')
break
sphinx.domains.python.PyObject.doc_field_types.append(
- PyTypedField('keyword', label=l_('Keyword Arguments'),
+ PyTypedField('keyword', label=_('Keyword Arguments'),
names=('keyword', 'kwarg', 'kwparam'),
typerolename='obj', typenames=('paramtype', 'kwtype'),
can_collapse=True))
diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py
index c97624f72..e79db3ec2 100644
--- a/sphinx/locale/__init__.py
+++ b/sphinx/locale/__init__.py
@@ -10,11 +10,17 @@
"""
import gettext
+import locale
+import warnings
+from collections import defaultdict
+from gettext import NullTranslations
from typing import TYPE_CHECKING
-from six import PY3, text_type
+from six import text_type
from six.moves import UserString
+from sphinx.deprecation import RemovedInSphinx30Warning
+
if TYPE_CHECKING:
from typing import Any, Callable, Dict, Iterator, List, Tuple # NOQA
@@ -180,6 +186,8 @@ def mygettext(string):
"""Used instead of _ when creating TranslationProxies, because _ is
not bound yet at that time.
"""
+ warnings.warn('sphinx.locale.mygettext() is deprecated. Please use `_()` instead.',
+ RemovedInSphinx30Warning)
return _(string)
@@ -188,119 +196,167 @@ def lazy_gettext(string):
"""A lazy version of `gettext`."""
# if isinstance(string, _TranslationProxy):
# return string
+ warnings.warn('sphinx.locale.laxy_gettext() is deprecated. Please use `_()` instead.',
+ RemovedInSphinx30Warning)
return _TranslationProxy(mygettext, string) # type: ignore
-l_ = lazy_gettext
-
-
-admonitionlabels = {
- 'attention': l_('Attention'),
- 'caution': l_('Caution'),
- 'danger': l_('Danger'),
- 'error': l_('Error'),
- 'hint': l_('Hint'),
- 'important': l_('Important'),
- 'note': l_('Note'),
- 'seealso': l_('See also'),
- 'tip': l_('Tip'),
- 'warning': l_('Warning'),
-} # type: Dict[unicode, unicode]
-
-versionlabels = {
- 'versionadded': l_('New in version %s'),
- 'versionchanged': l_('Changed in version %s'),
- 'deprecated': l_('Deprecated since version %s'),
-} # type: Dict[unicode, unicode]
-
-# XXX Python specific
-pairindextypes = {
- 'module': l_('module'),
- 'keyword': l_('keyword'),
- 'operator': l_('operator'),
- 'object': l_('object'),
- 'exception': l_('exception'),
- 'statement': l_('statement'),
- 'builtin': l_('built-in function'),
-} # Dict[unicode, _TranslationProxy]
+translators = defaultdict(NullTranslations) # type: Dict[Tuple[unicode, unicode], NullTranslations] # NOQA
-translators = {} # type: Dict[unicode, Any]
-if PY3:
- def _(message, *args):
- # type: (unicode, *Any) -> unicode
- try:
- if len(args) <= 1:
- return translators['sphinx'].gettext(message)
- else: # support pluralization
- return translators['sphinx'].ngettext(message, args[0], args[1])
- except KeyError:
- return message
-else:
- def _(message, *args):
- # type: (unicode, *Any) -> unicode
- try:
- if len(args) <= 1:
- return translators['sphinx'].ugettext(message)
- else: # support pluralization
- return translators['sphinx'].ungettext(message, args[0], args[1])
- except KeyError:
- return message
-
-
-def __(message, *args):
- # type: (unicode, *Any) -> unicode
- """A dummy wrapper to i18n'ize exceptions and command line messages.
-
- In future, the messages are translated using LC_MESSAGES or any other
- locale settings.
- """
- return message if len(args) <= 1 else args[0]
-
-
-def init(locale_dirs, language, catalog='sphinx'):
- # type: (List, unicode, unicode) -> Tuple[Any, bool]
+def init(locale_dirs, language, catalog='sphinx', namespace='general'):
+ # type: (List[unicode], unicode, unicode, unicode) -> Tuple[NullTranslations, bool]
"""Look for message catalogs in `locale_dirs` and *ensure* that there is at
least a NullTranslations catalog set in `translators`. If called multiple
times or if several ``.mo`` files are found, their contents are merged
together (thus making ``init`` reentrable).
"""
global translators
- translator = translators.get(catalog)
+ translator = translators.get((namespace, catalog))
# ignore previously failed attempts to find message catalogs
- if isinstance(translator, gettext.NullTranslations):
+ if translator.__class__ is NullTranslations:
translator = None
# the None entry is the system's default locale path
has_translation = True
+ if language and '_' in language:
+ # for language having country code (like "de_AT")
+ languages = [language, language.split('_')[0]]
+ else:
+ languages = [language]
+
# loading
for dir_ in locale_dirs:
try:
trans = gettext.translation(catalog, localedir=dir_, # type: ignore
- languages=[language]) # type: ignore
+ languages=languages)
if translator is None:
translator = trans
else:
- translator._catalog.update(trans._catalog) # type: ignore
+ translator.add_fallback(trans)
except Exception:
# Language couldn't be found in the specified path
pass
- # guarantee translators[catalog] exists
+ # guarantee translators[(namespace, catalog)] exists
if translator is None:
- translator = gettext.NullTranslations()
+ translator = NullTranslations()
has_translation = False
- translators[catalog] = translator
+ translators[(namespace, catalog)] = translator
if hasattr(translator, 'ugettext'):
- translator.gettext = translator.ugettext
+ translator.gettext = translator.ugettext # type: ignore
return translator, has_translation
-def get_translator(catalog='sphinx'):
- # type: (unicode) -> gettext.NullTranslations
- global translators
- translator = translators.get(catalog)
- if translator is None:
- translator = gettext.NullTranslations()
- if hasattr(translator, 'ugettext'):
- translator.gettext = translator.ugettext
- return translator
+def init_console(locale_dir, catalog):
+ # type: (unicode, unicode) -> Tuple[NullTranslations, bool]
+ """Initialize locale for console.
+
+ .. versionadded:: 1.8
+ """
+ language, _ = locale.getlocale(locale.LC_MESSAGES) # encoding is ignored
+ return init([locale_dir], language, catalog, 'console')
+
+
+def get_translator(catalog='sphinx', namespace='general'):
+ # type: (unicode, unicode) -> NullTranslations
+ return translators[(namespace, catalog)]
+
+
+def is_translator_registered(catalog='sphinx', namespace='general'):
+ # type: (unicode, unicode) -> bool
+ return (namespace, catalog) in translators
+
+
+def _lazy_translate(catalog, namespace, message):
+ # type: (unicode, unicode, unicode) -> unicode
+ """Used instead of _ when creating TranslationProxy, because _ is
+ not bound yet at that time.
+ """
+ translator = get_translator(catalog, namespace)
+ return translator.gettext(message) # type: ignore
+
+
+def get_translation(catalog, namespace='general'):
+ """Get a translation function based on the *catalog* and *namespace*.
+
+ The extension can use this API to translate the messages on the
+ extension::
+
+ import os
+ from sphinx.locale import get_translation
+
+ _ = get_translation(__name__)
+ text = _('Hello Sphinx!')
+
+
+ def setup(app):
+ package_dir = path.abspath(path.dirname(__file__))
+ locale_dir = os.path.join(package_dir, 'locales')
+ app.add_message_catalog(__name__, locale_dir)
+
+ With this code, sphinx searches a message catalog from
+ ``${package_dir}/locales/${language}/LC_MESSAGES/${__name__}.mo``
+ The :confval:`language` is used for the searching.
+
+ .. versionadded:: 1.8
+ """
+ def gettext(message, *args):
+ # type: (unicode, *Any) -> unicode
+ if not is_translator_registered(catalog, namespace):
+ # not initialized yet
+ return _TranslationProxy(_lazy_translate, catalog, namespace, message) # type: ignore # NOQA
+ else:
+ translator = get_translator(catalog, namespace)
+ if len(args) <= 1:
+ return translator.gettext(message) # type: ignore
+ else: # support pluralization
+ return translator.ngettext(message, args[0], args[1]) # type: ignore
+
+ return gettext
+
+
+# A shortcut for sphinx-core
+#: Translation function for messages on documentation (menu, labels, themes and so on).
+#: This function follows :confval:`language` setting.
+_ = get_translation('sphinx')
+#: Translation function for console messages
+#: This function follows locale setting (`LC_ALL`, `LC_MESSAGES` and so on).
+__ = get_translation('sphinx', 'console')
+
+
+def l_(*args):
+ warnings.warn('sphinx.locale.l_() is deprecated. Please use `_()` instead.',
+ RemovedInSphinx30Warning)
+ return _(*args)
+
+
+# labels
+admonitionlabels = {
+ 'attention': _('Attention'),
+ 'caution': _('Caution'),
+ 'danger': _('Danger'),
+ 'error': _('Error'),
+ 'hint': _('Hint'),
+ 'important': _('Important'),
+ 'note': _('Note'),
+ 'seealso': _('See also'),
+ 'tip': _('Tip'),
+ 'warning': _('Warning'),
+} # type: Dict[unicode, unicode]
+
+versionlabels = {
+ 'versionadded': _('New in version %s'),
+ 'versionchanged': _('Changed in version %s'),
+ 'deprecated': _('Deprecated since version %s'),
+} # type: Dict[unicode, unicode]
+
+# XXX Python specific
+pairindextypes = {
+ 'module': _('module'),
+ 'keyword': _('keyword'),
+ 'operator': _('operator'),
+ 'object': _('object'),
+ 'exception': _('exception'),
+ 'statement': _('statement'),
+ 'builtin': _('built-in function'),
+} # Dict[unicode, _TranslationProxy]
diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py
index 6fbcd26d9..1a9ea3bc9 100644
--- a/sphinx/testing/util.py
+++ b/sphinx/testing/util.py
@@ -19,7 +19,7 @@ from docutils import nodes
from docutils.parsers.rst import directives, roles
from six import string_types
-from sphinx import application
+from sphinx import application, locale
from sphinx.builders.latex import LaTeXBuilder
from sphinx.ext.autodoc import AutoDirective
from sphinx.pycode import ModuleAnalyzer
@@ -147,6 +147,7 @@ class SphinxTestApp(application.Sphinx):
AutoDirective._registry.clear()
ModuleAnalyzer.cache.clear()
LaTeXBuilder.usepackages = []
+ locale.translators.clear()
sys.path[:] = self._saved_path
sys.modules.pop('autodoc_fodder', None)
directives._directives = self._saved_directives
diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py
index 24fe8660e..8367dcde5 100644
--- a/sphinx/transforms/__init__.py
+++ b/sphinx/transforms/__init__.py
@@ -125,7 +125,7 @@ class DefaultSubstitutions(SphinxTransform):
text = self.config[refname]
if refname == 'today' and not text:
# special handling: can also specify a strftime format
- text = format_date(self.config.today_fmt or _('%b %d, %Y'), # type: ignore
+ text = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=self.config.language)
ref.replace_self(nodes.Text(text, text))
diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py
index 63a362ac4..9dddce343 100644
--- a/sphinx/transforms/i18n.py
+++ b/sphinx/transforms/i18n.py
@@ -86,6 +86,8 @@ class Locale(SphinxTransform):
def apply(self):
# type: () -> None
settings, source = self.document.settings, self.document['source']
+ msgstr = u''
+
# XXX check if this is reliable
assert source.startswith(self.env.srcdir)
docname = path.splitext(relative_path(path.join(self.env.srcdir, 'dummy'),
@@ -102,7 +104,7 @@ class Locale(SphinxTransform):
# phase1: replace reference ids with translated names
for node, msg in extract_messages(self.document):
- msgstr = catalog.gettext(msg)
+ msgstr = catalog.gettext(msg) # type: ignore
# XXX add marker to untranslated parts
if not msgstr or msgstr == msg or not msgstr.strip():
# as-of-yet untranslated
@@ -219,7 +221,7 @@ class Locale(SphinxTransform):
if node.get('translated', False): # to avoid double translation
continue # skip if the node is already translated by phase1
- msgstr = catalog.gettext(msg)
+ msgstr = catalog.gettext(msg) # type: ignore
# XXX add marker to untranslated parts
if not msgstr or msgstr == msg: # as-of-yet untranslated
continue
@@ -438,7 +440,7 @@ class Locale(SphinxTransform):
msg_parts = split_index_msg(type, msg)
msgstr_parts = []
for part in msg_parts:
- msgstr = catalog.gettext(part)
+ msgstr = catalog.gettext(part) # type: ignore
if not msgstr:
msgstr = part
msgstr_parts.append(msgstr)
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index daa6e1be7..8112a16b1 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -580,7 +580,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
if builder.config.today:
self.elements['date'] = builder.config.today
else:
- self.elements['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'), # type: ignore # NOQA
+ self.elements['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'),
language=builder.config.language)
if builder.config.numfig:
diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py
index f647a7cf9..df2f74c02 100644
--- a/sphinx/writers/manpage.py
+++ b/sphinx/writers/manpage.py
@@ -108,7 +108,7 @@ class ManualPageTranslator(BaseTranslator):
if builder.config.today:
self._docinfo['date'] = builder.config.today
else:
- self._docinfo['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'), # type: ignore # NOQA
+ self._docinfo['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'),
language=builder.config.language)
self._docinfo['copyright'] = builder.config.copyright
self._docinfo['version'] = builder.config.version
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index 3767ba3fc..d17566429 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -237,7 +237,7 @@ class TexinfoTranslator(nodes.NodeVisitor):
'project': self.escape(self.builder.config.project),
'copyright': self.escape(self.builder.config.copyright),
'date': self.escape(self.builder.config.today or
- format_date(self.builder.config.today_fmt or _('%b %d, %Y'), # type: ignore # NOQA
+ format_date(self.builder.config.today_fmt or _('%b %d, %Y'),
language=self.builder.config.language))
})
# title
diff --git a/tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.mo b/tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.mo
new file mode 100644
index 000000000..6aa00f77a
--- /dev/null
+++ b/tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.mo
Binary files differ
diff --git a/tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.po b/tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.po
new file mode 100644
index 000000000..ee1f6c23f
--- /dev/null
+++ b/tests/roots/test-locale/locale1/en/LC_MESSAGES/myext.po
@@ -0,0 +1,2 @@
+msgid "Hello world"
+msgstr "HELLO WORLD"
diff --git a/tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.mo b/tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.mo
new file mode 100644
index 000000000..14c34d0b7
--- /dev/null
+++ b/tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.mo
Binary files differ
diff --git a/tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.po b/tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.po
new file mode 100644
index 000000000..d376cf9bd
--- /dev/null
+++ b/tests/roots/test-locale/locale2/en/LC_MESSAGES/myext.po
@@ -0,0 +1,2 @@
+msgid "Hello sphinx"
+msgstr "HELLO SPHINX"
diff --git a/tests/test_locale.py b/tests/test_locale.py
new file mode 100644
index 000000000..9b1921bd6
--- /dev/null
+++ b/tests/test_locale.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+"""
+ test_locale
+ ~~~~~~~~~~
+
+ Test locale.
+
+ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import pytest
+
+from sphinx import locale
+
+
+@pytest.fixture(autouse=True)
+def cleanup_translations():
+ yield
+ locale.translators.clear()
+
+
+def test_init(rootdir):
+ # not initialized yet
+ _ = locale.get_translation('myext')
+ assert _('Hello world') == 'Hello world'
+ assert _('Hello sphinx') == 'Hello sphinx'
+ assert _('Hello reST') == 'Hello reST'
+
+ # load locale1
+ locale.init([rootdir / 'test-locale' / 'locale1'], 'en', 'myext')
+ _ = locale.get_translation('myext')
+ assert _('Hello world') == 'HELLO WORLD'
+ assert _('Hello sphinx') == 'Hello sphinx'
+ assert _('Hello reST') == 'Hello reST'
+
+ # load a catalog to unrelated namespace
+ locale.init([rootdir / 'test-locale' / 'locale2'], 'en', 'myext', 'mynamespace')
+ _ = locale.get_translation('myext')
+ assert _('Hello world') == 'HELLO WORLD'
+ assert _('Hello sphinx') == 'Hello sphinx' # nothing changed here
+ assert _('Hello reST') == 'Hello reST'
+
+ # load locale2 in addition
+ locale.init([rootdir / 'test-locale' / 'locale2'], 'en', 'myext')
+ _ = locale.get_translation('myext')
+ assert _('Hello world') == 'HELLO WORLD'
+ assert _('Hello sphinx') == 'HELLO SPHINX'
+ assert _('Hello reST') == 'Hello reST'
+
+
+def test_init_with_unknown_language(rootdir):
+ locale.init([rootdir / 'test-locale' / 'locale1'], 'unknown', 'myext')
+ _ = locale.get_translation('myext')
+ assert _('Hello world') == 'Hello world'
+ assert _('Hello sphinx') == 'Hello sphinx'
+ assert _('Hello reST') == 'Hello reST'
+
+
+def test_add_message_catalog(app, rootdir):
+ app.config.language = 'en'
+ app.add_message_catalog('myext', rootdir / 'test-locale' / 'locale1')
+ _ = locale.get_translation('myext')
+ assert _('Hello world') == 'HELLO WORLD'
+ assert _('Hello sphinx') == 'Hello sphinx'
+ assert _('Hello reST') == 'Hello reST'