mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Closes #976: Fix gettext does not extract index entries.
This commit is contained in:
parent
42c90ee178
commit
b17c588b0d
2
CHANGES
2
CHANGES
@ -16,6 +16,8 @@ Release 1.2 (in development)
|
|||||||
* #869: sphinx-build now has the option :option:`-T` for printing the full
|
* #869: sphinx-build now has the option :option:`-T` for printing the full
|
||||||
traceback after an unhandled exception.
|
traceback after an unhandled exception.
|
||||||
|
|
||||||
|
* #976: Fix gettext does not extract index entries.
|
||||||
|
|
||||||
* #940: Fix gettext does not extract figure caption.
|
* #940: Fix gettext does not extract figure caption.
|
||||||
|
|
||||||
* #1067: Improve the ordering of the JavaScript search results: matches in titles
|
* #1067: Improve the ordering of the JavaScript search results: matches in titles
|
||||||
|
@ -15,9 +15,11 @@ from datetime import datetime
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from sphinx.builders import Builder
|
from sphinx.builders import Builder
|
||||||
from sphinx.util.nodes import extract_messages
|
from sphinx.util import split_index_msg
|
||||||
|
from sphinx.util.nodes import extract_messages, traverse_translatable_index
|
||||||
from sphinx.util.osutil import SEP, safe_relpath, ensuredir, find_catalog
|
from sphinx.util.osutil import SEP, safe_relpath, ensuredir, find_catalog
|
||||||
from sphinx.util.console import darkgreen
|
from sphinx.util.console import darkgreen
|
||||||
|
from sphinx.locale import pairindextypes
|
||||||
|
|
||||||
POHEADER = ur"""
|
POHEADER = ur"""
|
||||||
# SOME DESCRIPTIVE TITLE.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
@ -82,6 +84,16 @@ class I18nBuilder(Builder):
|
|||||||
for node, msg in extract_messages(doctree):
|
for node, msg in extract_messages(doctree):
|
||||||
catalog.add(msg, node)
|
catalog.add(msg, node)
|
||||||
|
|
||||||
|
# Extract translatable messages from index entries.
|
||||||
|
for node, entries in traverse_translatable_index(doctree):
|
||||||
|
for typ, msg, tid, main in entries:
|
||||||
|
for m in split_index_msg(typ, msg):
|
||||||
|
if typ == 'pair' and m in pairindextypes.values():
|
||||||
|
# avoid built-in translated message was incorporated
|
||||||
|
# in 'sphinx.util.nodes.process_index_entry'
|
||||||
|
continue
|
||||||
|
catalog.add(m, node)
|
||||||
|
|
||||||
|
|
||||||
class MessageCatalogBuilder(I18nBuilder):
|
class MessageCatalogBuilder(I18nBuilder):
|
||||||
"""
|
"""
|
||||||
|
@ -168,6 +168,7 @@ class Index(Directive):
|
|||||||
indexnode = addnodes.index()
|
indexnode = addnodes.index()
|
||||||
indexnode['entries'] = ne = []
|
indexnode['entries'] = ne = []
|
||||||
indexnode['inline'] = False
|
indexnode['inline'] = False
|
||||||
|
set_source_info(self, indexnode)
|
||||||
for entry in arguments:
|
for entry in arguments:
|
||||||
ne.extend(process_index_entry(entry, targetid))
|
ne.extend(process_index_entry(entry, targetid))
|
||||||
return [indexnode, targetnode]
|
return [indexnode, targetnode]
|
||||||
|
@ -38,9 +38,9 @@ from docutils.transforms.parts import ContentsFilter
|
|||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \
|
from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \
|
||||||
FilenameUniqDict
|
split_index_msg, FilenameUniqDict
|
||||||
from sphinx.util.nodes import clean_astext, make_refnode, extract_messages, \
|
from sphinx.util.nodes import clean_astext, make_refnode, extract_messages, \
|
||||||
WarningStream
|
traverse_translatable_index, WarningStream
|
||||||
from sphinx.util.osutil import movefile, SEP, ustrftime, find_catalog, \
|
from sphinx.util.osutil import movefile, SEP, ustrftime, find_catalog, \
|
||||||
fs_encoding
|
fs_encoding
|
||||||
from sphinx.util.matching import compile_matchers
|
from sphinx.util.matching import compile_matchers
|
||||||
@ -303,6 +303,23 @@ class Locale(Transform):
|
|||||||
child.parent = node
|
child.parent = node
|
||||||
node.children = patch.children
|
node.children = patch.children
|
||||||
|
|
||||||
|
# Extract and translate messages for index entries.
|
||||||
|
for node, entries in traverse_translatable_index(self.document):
|
||||||
|
new_entries = []
|
||||||
|
for type, msg, tid, main in entries:
|
||||||
|
msg_parts = split_index_msg(type, msg)
|
||||||
|
msgstr_parts = []
|
||||||
|
for part in msg_parts:
|
||||||
|
msgstr = catalog.gettext(part)
|
||||||
|
if not msgstr:
|
||||||
|
msgstr = part
|
||||||
|
msgstr_parts.append(msgstr)
|
||||||
|
|
||||||
|
new_entries.append((type, ';'.join(msgstr_parts), tid, main))
|
||||||
|
|
||||||
|
node['raw_entries'] = entries
|
||||||
|
node['entries'] = new_entries
|
||||||
|
|
||||||
|
|
||||||
class SphinxStandaloneReader(standalone.Reader):
|
class SphinxStandaloneReader(standalone.Reader):
|
||||||
"""
|
"""
|
||||||
|
@ -293,6 +293,7 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
|||||||
entries = [('single', target, targetid, main)]
|
entries = [('single', target, targetid, main)]
|
||||||
indexnode = addnodes.index()
|
indexnode = addnodes.index()
|
||||||
indexnode['entries'] = entries
|
indexnode['entries'] = entries
|
||||||
|
set_role_source_info(inliner, lineno, indexnode)
|
||||||
textnode = nodes.Text(title, title)
|
textnode = nodes.Text(title, title)
|
||||||
return [indexnode, targetnode, textnode], []
|
return [indexnode, targetnode, textnode], []
|
||||||
|
|
||||||
|
@ -360,6 +360,29 @@ def split_into(n, type, value):
|
|||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|
||||||
|
def split_index_msg(type, value):
|
||||||
|
# new entry types must be listed in directives/other.py!
|
||||||
|
result = []
|
||||||
|
try:
|
||||||
|
if type == 'single':
|
||||||
|
try:
|
||||||
|
result = split_into(2, 'single', value)
|
||||||
|
except ValueError:
|
||||||
|
result = split_into(1, 'single', value)
|
||||||
|
elif type == 'pair':
|
||||||
|
result = split_into(2, 'pair', value)
|
||||||
|
elif type == 'triple':
|
||||||
|
result = split_into(3, 'triple', value)
|
||||||
|
elif type == 'see':
|
||||||
|
result = split_into(2, 'see', value)
|
||||||
|
elif type == 'seealso':
|
||||||
|
result = split_into(2, 'see', value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def format_exception_cut_frames(x=1):
|
def format_exception_cut_frames(x=1):
|
||||||
"""Format an exception with traceback, but only the last x frames."""
|
"""Format an exception with traceback, but only the last x frames."""
|
||||||
typ, val, tb = sys.exc_info()
|
typ, val, tb = sys.exc_info()
|
||||||
|
@ -74,6 +74,19 @@ def extract_messages(doctree):
|
|||||||
yield node, msg
|
yield node, msg
|
||||||
|
|
||||||
|
|
||||||
|
def traverse_translatable_index(doctree):
|
||||||
|
"""Traverse translatable index node from a document tree."""
|
||||||
|
def is_block_index(node):
|
||||||
|
return isinstance(node, addnodes.index) and \
|
||||||
|
node.get('inline') == False
|
||||||
|
for node in doctree.traverse(is_block_index):
|
||||||
|
if 'raw_entries' in node:
|
||||||
|
entries = node['raw_entries']
|
||||||
|
else:
|
||||||
|
entries = node['entries']
|
||||||
|
yield node, entries
|
||||||
|
|
||||||
|
|
||||||
def nested_parse_with_titles(state, content, node):
|
def nested_parse_with_titles(state, content, node):
|
||||||
"""Version of state.nested_parse() that allows titles and does not require
|
"""Version of state.nested_parse() that allows titles and does not require
|
||||||
titles to have the same decoration as the calling document.
|
titles to have the same decoration as the calling document.
|
||||||
|
@ -8,3 +8,4 @@
|
|||||||
literalblock
|
literalblock
|
||||||
definition_terms
|
definition_terms
|
||||||
figure_caption
|
figure_caption
|
||||||
|
index_entries
|
||||||
|
77
tests/root/i18n/index_entries.po
Normal file
77
tests/root/i18n/index_entries.po
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) 2013, foo
|
||||||
|
# This file is distributed under the same license as the foo package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: foo foo\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-01-05 18:10\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
msgid "i18n with index entries"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "index target section"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "this is :index:`Newsletter` target paragraph."
|
||||||
|
msgstr "THIS IS :index:`NEWSLETTER` TARGET PARAGRAPH."
|
||||||
|
|
||||||
|
msgid "various index entries"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "That's all."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Mailing List"
|
||||||
|
msgstr "MAILING LIST"
|
||||||
|
|
||||||
|
msgid "Newsletter"
|
||||||
|
msgstr "NEWSLETTER"
|
||||||
|
|
||||||
|
msgid "Recipients List"
|
||||||
|
msgstr "RECIPIENTS LIST"
|
||||||
|
|
||||||
|
msgid "First"
|
||||||
|
msgstr "FIRST"
|
||||||
|
|
||||||
|
msgid "Second"
|
||||||
|
msgstr "SECOND"
|
||||||
|
|
||||||
|
msgid "Third"
|
||||||
|
msgstr "THIRD"
|
||||||
|
|
||||||
|
msgid "Entry"
|
||||||
|
msgstr "ENTRY"
|
||||||
|
|
||||||
|
msgid "See"
|
||||||
|
msgstr "SEE"
|
||||||
|
|
||||||
|
msgid "Module"
|
||||||
|
msgstr "MODULE"
|
||||||
|
|
||||||
|
msgid "Keyword"
|
||||||
|
msgstr "KEYWORD"
|
||||||
|
|
||||||
|
msgid "Operator"
|
||||||
|
msgstr "OPERATOR"
|
||||||
|
|
||||||
|
msgid "Object"
|
||||||
|
msgstr "OBJECT"
|
||||||
|
|
||||||
|
msgid "Exception"
|
||||||
|
msgstr "EXCEPTION"
|
||||||
|
|
||||||
|
msgid "Statement"
|
||||||
|
msgstr "STATEMENT"
|
||||||
|
|
||||||
|
msgid "Builtin"
|
||||||
|
msgstr "BUILTIN"
|
31
tests/root/i18n/index_entries.txt
Normal file
31
tests/root/i18n/index_entries.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
:tocdepth: 2
|
||||||
|
|
||||||
|
i18n with index entries
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: Mailing List
|
||||||
|
pair: Newsletter; Recipients List
|
||||||
|
|
||||||
|
index target section
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
this is :index:`Newsletter` target paragraph.
|
||||||
|
|
||||||
|
|
||||||
|
various index entries
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
triple: First; Second; Third
|
||||||
|
see: Entry; Mailing List
|
||||||
|
seealso: See; Newsletter
|
||||||
|
module: Module
|
||||||
|
keyword: Keyword
|
||||||
|
operator: Operator
|
||||||
|
object: Object
|
||||||
|
exception: Exception
|
||||||
|
statement: Statement
|
||||||
|
builtin: Builtin
|
||||||
|
|
||||||
|
That's all.
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
from util import *
|
from util import *
|
||||||
@ -79,3 +80,49 @@ def test_gettext(app):
|
|||||||
|
|
||||||
_ = gettext.translation('test_root', app.outdir, languages=['en']).gettext
|
_ = gettext.translation('test_root', app.outdir, languages=['en']).gettext
|
||||||
assert _("Testing various markup") == u"Testing various markup"
|
assert _("Testing various markup") == u"Testing various markup"
|
||||||
|
|
||||||
|
|
||||||
|
@with_app(buildername='gettext',
|
||||||
|
confoverrides={'gettext_compact': False})
|
||||||
|
def test_gettext_index_entries(app):
|
||||||
|
# regression test for #976
|
||||||
|
app.builder.build(['i18n/index_entries'])
|
||||||
|
|
||||||
|
_msgid_getter = re.compile(r'msgid "(.*)"').search
|
||||||
|
def msgid_getter(msgid):
|
||||||
|
m = _msgid_getter(msgid)
|
||||||
|
if m:
|
||||||
|
return m.groups()[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
pot = (app.outdir / 'i18n' / 'index_entries.pot').text(encoding='utf-8')
|
||||||
|
msgids = filter(None, map(msgid_getter, pot.splitlines()))
|
||||||
|
|
||||||
|
expected_msgids = [
|
||||||
|
"i18n with index entries",
|
||||||
|
"index target section",
|
||||||
|
"this is :index:`Newsletter` target paragraph.",
|
||||||
|
"various index entries",
|
||||||
|
"That's all.",
|
||||||
|
"Mailing List",
|
||||||
|
"Newsletter",
|
||||||
|
"Recipients List",
|
||||||
|
"First",
|
||||||
|
"Second",
|
||||||
|
"Third",
|
||||||
|
"Entry",
|
||||||
|
"See",
|
||||||
|
"Module",
|
||||||
|
"Keyword",
|
||||||
|
"Operator",
|
||||||
|
"Object",
|
||||||
|
"Exception",
|
||||||
|
"Statement",
|
||||||
|
"Builtin",
|
||||||
|
]
|
||||||
|
for expect in expected_msgids:
|
||||||
|
assert expect in msgids
|
||||||
|
msgids.remove(expect)
|
||||||
|
|
||||||
|
# unexpected msgid existent
|
||||||
|
assert msgids == []
|
||||||
|
@ -259,3 +259,37 @@ def test_i18n_figure_caption(app):
|
|||||||
u"\n MY DESCRIPTION PARAGRAPH2 OF THE FIGURE.\n")
|
u"\n MY DESCRIPTION PARAGRAPH2 OF THE FIGURE.\n")
|
||||||
|
|
||||||
assert result == expect
|
assert result == expect
|
||||||
|
|
||||||
|
|
||||||
|
@with_app(buildername='html',
|
||||||
|
confoverrides={'language': 'xx', 'locale_dirs': ['.'],
|
||||||
|
'gettext_compact': False})
|
||||||
|
def test_i18n_index_entries(app):
|
||||||
|
# regression test for #976
|
||||||
|
app.builder.build(['i18n/index_entries'])
|
||||||
|
result = (app.outdir / 'genindex.html').text(encoding='utf-8')
|
||||||
|
|
||||||
|
def wrap(tag, keyword):
|
||||||
|
start_tag = "<%s[^>]*>" % tag
|
||||||
|
end_tag = "</%s>" % tag
|
||||||
|
return r"%s\s*%s\s*%s" % (start_tag, keyword, end_tag)
|
||||||
|
|
||||||
|
expected_exprs = [
|
||||||
|
wrap('a', 'NEWSLETTER'),
|
||||||
|
wrap('a', 'MAILING LIST'),
|
||||||
|
wrap('a', 'RECIPIENTS LIST'),
|
||||||
|
wrap('a', 'FIRST SECOND'),
|
||||||
|
wrap('a', 'SECOND THIRD'),
|
||||||
|
wrap('a', 'THIRD, FIRST'),
|
||||||
|
wrap('dt', 'ENTRY'),
|
||||||
|
wrap('dt', 'SEE'),
|
||||||
|
wrap('a', 'MODULE'),
|
||||||
|
wrap('a', 'KEYWORD'),
|
||||||
|
wrap('a', 'OPERATOR'),
|
||||||
|
wrap('a', 'OBJECT'),
|
||||||
|
wrap('a', 'EXCEPTION'),
|
||||||
|
wrap('a', 'STATEMENT'),
|
||||||
|
wrap('a', 'BUILTIN'),
|
||||||
|
]
|
||||||
|
for expr in expected_exprs:
|
||||||
|
assert re.search(expr, result, re.M)
|
||||||
|
Loading…
Reference in New Issue
Block a user