mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #2322 from tk0miya/794_babel_format_date
Fix #794: Date formatting in latex output is not localized
This commit is contained in:
commit
3e6f608778
6
CHANGES
6
CHANGES
@ -29,6 +29,11 @@ Incompatible changes
|
|||||||
* The default highlight language is now Python 3. This means that source code
|
* The default highlight language is now Python 3. This means that source code
|
||||||
is highlighted as Python 3 (which is mostly a superset of Python 2), and no
|
is highlighted as Python 3 (which is mostly a superset of Python 2), and no
|
||||||
parsing is attempted to distinguish valid code.
|
parsing is attempted to distinguish valid code.
|
||||||
|
* `Locale Date Markup Language
|
||||||
|
<http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>`_ like
|
||||||
|
``"MMMM dd, YYYY"`` is default format for `today_fmt` and `html_last_updated_fmt`.
|
||||||
|
However strftime format like ``"%B %d, %Y"`` is also supported for backward
|
||||||
|
compatibility until Sphinx-1.5. Later format will be disabled from Sphinx-1.5.
|
||||||
|
|
||||||
Features added
|
Features added
|
||||||
--------------
|
--------------
|
||||||
@ -99,6 +104,7 @@ Bugs fixed
|
|||||||
anatoly techtonik.
|
anatoly techtonik.
|
||||||
* #2311: Fix sphinx.ext.inheritance_diagram raises AttributeError
|
* #2311: Fix sphinx.ext.inheritance_diagram raises AttributeError
|
||||||
* #2251: Line breaks in .rst files are transferred to .pot files in a wrong way.
|
* #2251: Line breaks in .rst files are transferred to .pot files in a wrong way.
|
||||||
|
* #794: Fix date formatting in latex output is not localized
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
|
@ -310,12 +310,19 @@ Project information
|
|||||||
replacement for ``|today|``.
|
replacement for ``|today|``.
|
||||||
|
|
||||||
* If you set :confval:`today` to a non-empty value, it is used.
|
* If you set :confval:`today` to a non-empty value, it is used.
|
||||||
* Otherwise, the current time is formatted using :func:`time.strftime` and
|
* Otherwise, the current time is formatted using `Locale Data Markup Language
|
||||||
the format given in :confval:`today_fmt`.
|
<http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>`_
|
||||||
|
and the format given in :confval:`today_fmt`.
|
||||||
|
|
||||||
The default is no :confval:`today` and a :confval:`today_fmt` of ``'%B %d,
|
The default is no :confval:`today` and a :confval:`today_fmt` of ``'MMMM dd,
|
||||||
%Y'`` (or, if translation is enabled with :confval:`language`, an equivalent
|
YYYY'`` (or, if translation is enabled with :confval:`language`, an
|
||||||
%format for the selected locale).
|
equivalent %format for the selected locale).
|
||||||
|
|
||||||
|
.. versionchanged:: 1.4
|
||||||
|
|
||||||
|
Format specification was changed from strftime to Locale Data Markup
|
||||||
|
Language. strftime format is also supported for backward compatibility
|
||||||
|
until Sphinx-1.5.
|
||||||
|
|
||||||
.. confval:: highlight_language
|
.. confval:: highlight_language
|
||||||
|
|
||||||
@ -653,10 +660,17 @@ that use Sphinx's HTMLWriter class.
|
|||||||
.. confval:: html_last_updated_fmt
|
.. confval:: html_last_updated_fmt
|
||||||
|
|
||||||
If this is not None, a 'Last updated on:' timestamp is inserted
|
If this is not None, a 'Last updated on:' timestamp is inserted
|
||||||
at every page bottom, using the given :func:`strftime` format.
|
at every page bottom, using the given `Locale Data Markup Language
|
||||||
The empty string is equivalent to ``'%b %d, %Y'`` (or a
|
<http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>`_
|
||||||
|
format. The empty string is equivalent to ``'MMM dd, YYYY'`` (or a
|
||||||
locale-dependent equivalent).
|
locale-dependent equivalent).
|
||||||
|
|
||||||
|
.. versionchanged:: 1.4
|
||||||
|
|
||||||
|
Format specification was changed from strftime to Locale Data Markup
|
||||||
|
Language. strftime format is also supported for backward compatibility
|
||||||
|
until Sphinx-1.5.
|
||||||
|
|
||||||
.. confval:: html_use_smartypants
|
.. confval:: html_use_smartypants
|
||||||
|
|
||||||
If true, `SmartyPants <http://daringfireball.net/projects/smartypants/>`_
|
If true, `SmartyPants <http://daringfireball.net/projects/smartypants/>`_
|
||||||
|
@ -28,7 +28,8 @@ from docutils import nodes
|
|||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||||
from sphinx.util.osutil import ensuredir, copyfile, ustrftime, EEXIST
|
from sphinx.util.i18n import format_date
|
||||||
|
from sphinx.util.osutil import ensuredir, copyfile, EEXIST
|
||||||
from sphinx.util.smartypants import sphinx_smarty_pants as ssp
|
from sphinx.util.smartypants import sphinx_smarty_pants as ssp
|
||||||
from sphinx.util.console import brown
|
from sphinx.util.console import brown
|
||||||
|
|
||||||
@ -529,7 +530,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
|||||||
metadata['copyright'] = self.esc(self.config.epub_copyright)
|
metadata['copyright'] = self.esc(self.config.epub_copyright)
|
||||||
metadata['scheme'] = self.esc(self.config.epub_scheme)
|
metadata['scheme'] = self.esc(self.config.epub_scheme)
|
||||||
metadata['id'] = self.esc(self.config.epub_identifier)
|
metadata['id'] = self.esc(self.config.epub_identifier)
|
||||||
metadata['date'] = self.esc(ustrftime('%Y-%m-%d'))
|
metadata['date'] = self.esc(format_date('YYYY-MM-dd', language=self.config.language))
|
||||||
metadata['files'] = files
|
metadata['files'] = files
|
||||||
metadata['spine'] = spine
|
metadata['spine'] = spine
|
||||||
metadata['guide'] = guide
|
metadata['guide'] = guide
|
||||||
|
@ -28,8 +28,9 @@ from docutils.readers.doctree import Reader as DoctreeReader
|
|||||||
|
|
||||||
from sphinx import package_dir, __display_version__
|
from sphinx import package_dir, __display_version__
|
||||||
from sphinx.util import jsonimpl, copy_static_entry, copy_extra_entry
|
from sphinx.util import jsonimpl, copy_static_entry, copy_extra_entry
|
||||||
|
from sphinx.util.i18n import format_date
|
||||||
from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
|
from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
|
||||||
movefile, ustrftime, copyfile
|
movefile, copyfile
|
||||||
from sphinx.util.nodes import inline_all_toctrees
|
from sphinx.util.nodes import inline_all_toctrees
|
||||||
from sphinx.util.matching import patmatch, compile_matchers
|
from sphinx.util.matching import patmatch, compile_matchers
|
||||||
from sphinx.locale import _
|
from sphinx.locale import _
|
||||||
@ -291,7 +292,8 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
# typically doesn't include the time of day
|
# typically doesn't include the time of day
|
||||||
lufmt = self.config.html_last_updated_fmt
|
lufmt = self.config.html_last_updated_fmt
|
||||||
if lufmt is not None:
|
if lufmt is not None:
|
||||||
self.last_updated = ustrftime(lufmt or _('%b %d, %Y'))
|
self.last_updated = format_date(lufmt or _('MMM dd, YYYY'),
|
||||||
|
language=self.config.language)
|
||||||
else:
|
else:
|
||||||
self.last_updated = None
|
self.last_updated = None
|
||||||
|
|
||||||
|
@ -24,8 +24,7 @@ from sphinx.util.nodes import (
|
|||||||
traverse_translatable_index, extract_messages, LITERAL_TYPE_NODES, IMAGE_TYPE_NODES,
|
traverse_translatable_index, extract_messages, LITERAL_TYPE_NODES, IMAGE_TYPE_NODES,
|
||||||
apply_source_workaround,
|
apply_source_workaround,
|
||||||
)
|
)
|
||||||
from sphinx.util.osutil import ustrftime
|
from sphinx.util.i18n import find_catalog, format_date
|
||||||
from sphinx.util.i18n import find_catalog
|
|
||||||
from sphinx.util.pycompat import indent
|
from sphinx.util.pycompat import indent
|
||||||
from sphinx.domains.std import make_glossary_term, split_term_classifiers
|
from sphinx.domains.std import make_glossary_term, split_term_classifiers
|
||||||
|
|
||||||
@ -54,7 +53,8 @@ class DefaultSubstitutions(Transform):
|
|||||||
text = config[refname]
|
text = config[refname]
|
||||||
if refname == 'today' and not text:
|
if refname == 'today' and not text:
|
||||||
# special handling: can also specify a strftime format
|
# special handling: can also specify a strftime format
|
||||||
text = ustrftime(config.today_fmt or _('%B %d, %Y'))
|
text = format_date(config.today_fmt or _('MMMM dd, YYYY'),
|
||||||
|
language=config.language)
|
||||||
ref.replace_self(nodes.Text(text, text))
|
ref.replace_self(nodes.Text(text, text))
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,9 +10,15 @@
|
|||||||
"""
|
"""
|
||||||
import gettext
|
import gettext
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import warnings
|
||||||
from os import path
|
from os import path
|
||||||
|
from time import time
|
||||||
|
from datetime import datetime
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import babel.dates
|
||||||
from babel.messages.pofile import read_po
|
from babel.messages.pofile import read_po
|
||||||
from babel.messages.mofile import write_mo
|
from babel.messages.mofile import write_mo
|
||||||
|
|
||||||
@ -118,3 +124,69 @@ def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact
|
|||||||
catalogs.add(cat)
|
catalogs.add(cat)
|
||||||
|
|
||||||
return catalogs
|
return catalogs
|
||||||
|
|
||||||
|
# date_format mappings: ustrftime() to bable.dates.format_date()
|
||||||
|
date_format_mappings = {
|
||||||
|
'%a': 'EEE', # Weekday as locale’s abbreviated name.
|
||||||
|
'%A': 'EEEE', # Weekday as locale’s full name.
|
||||||
|
'%b': 'MMM', # Month as locale’s abbreviated name.
|
||||||
|
'%B': 'MMMM', # Month as locale’s full name.
|
||||||
|
'%d': 'dd', # Day of the month as a zero-padded decimal number.
|
||||||
|
'%j': 'DDD', # Day of the year as a zero-padded decimal number.
|
||||||
|
'%m': 'MM', # Month as a zero-padded decimal number.
|
||||||
|
'%U': 'WW', # Week number of the year (Sunday as the first day of the week)
|
||||||
|
# as a zero padded decimal number. All days in a new year preceding
|
||||||
|
# the first Sunday are considered to be in week 0.
|
||||||
|
'%w': 'e', # Weekday as a decimal number, where 0 is Sunday and 6 is Saturday.
|
||||||
|
'%W': 'WW', # Week number of the year (Monday as the first day of the week)
|
||||||
|
# as a decimal number. All days in a new year preceding the first
|
||||||
|
# Monday are considered to be in week 0.
|
||||||
|
'%x': 'medium', # Locale’s appropriate date representation.
|
||||||
|
'%y': 'YY', # Year without century as a zero-padded decimal number.
|
||||||
|
'%Y': 'YYYY', # Year with century as a decimal number.
|
||||||
|
'%%': '%',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def babel_format_date(date, format, locale):
|
||||||
|
if locale is None:
|
||||||
|
locale = 'en'
|
||||||
|
|
||||||
|
try:
|
||||||
|
return babel.dates.format_date(date, format, locale=locale)
|
||||||
|
except babel.core.UnknownLocaleError:
|
||||||
|
# fallback to English
|
||||||
|
return babel.dates.format_date(date, format, locale='en')
|
||||||
|
|
||||||
|
|
||||||
|
def format_date(format, date=None, language=None):
|
||||||
|
if format is None:
|
||||||
|
format = 'medium'
|
||||||
|
|
||||||
|
if date is None:
|
||||||
|
# If time is not specified, try to use $SOURCE_DATE_EPOCH variable
|
||||||
|
# See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
|
||||||
|
source_date_epoch = os.getenv('SOURCE_DATE_EPOCH')
|
||||||
|
if source_date_epoch is not None:
|
||||||
|
date = time.gmtime(float(source_date_epoch))
|
||||||
|
else:
|
||||||
|
date = datetime.now()
|
||||||
|
|
||||||
|
if '%' not in format:
|
||||||
|
# consider the format as babel's
|
||||||
|
return babel_format_date(date, format, locale=language)
|
||||||
|
else:
|
||||||
|
warnings.warn('ustrftime format support will be dropped at Sphinx-1.5',
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
|
# consider the format as ustrftime's and try to convert it to babel's
|
||||||
|
result = []
|
||||||
|
tokens = re.split('(%.)', format)
|
||||||
|
for token in tokens:
|
||||||
|
if token in date_format_mappings:
|
||||||
|
babel_format = date_format_mappings.get(token, '')
|
||||||
|
result.append(babel_format_date(date, babel_format, locale=language))
|
||||||
|
else:
|
||||||
|
result.append(token)
|
||||||
|
|
||||||
|
return "".join(result)
|
||||||
|
@ -158,7 +158,8 @@ def make_filename(string):
|
|||||||
|
|
||||||
|
|
||||||
def ustrftime(format, *args):
|
def ustrftime(format, *args):
|
||||||
# strftime for unicode strings
|
# [DEPRECATED] strftime for unicode strings
|
||||||
|
# It will be removed at Sphinx-1.5
|
||||||
if not args:
|
if not args:
|
||||||
# If time is not specified, try to use $SOURCE_DATE_EPOCH variable
|
# If time is not specified, try to use $SOURCE_DATE_EPOCH variable
|
||||||
# See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
|
# See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
|
||||||
|
@ -26,8 +26,8 @@ from sphinx import highlighting
|
|||||||
from sphinx.errors import SphinxError
|
from sphinx.errors import SphinxError
|
||||||
from sphinx.locale import admonitionlabels, _
|
from sphinx.locale import admonitionlabels, _
|
||||||
from sphinx.util import split_into
|
from sphinx.util import split_into
|
||||||
|
from sphinx.util.i18n import format_date
|
||||||
from sphinx.util.nodes import clean_astext, traverse_parent
|
from sphinx.util.nodes import clean_astext, traverse_parent
|
||||||
from sphinx.util.osutil import ustrftime
|
|
||||||
from sphinx.util.texescape import tex_escape_map, tex_replace_map
|
from sphinx.util.texescape import tex_escape_map, tex_replace_map
|
||||||
from sphinx.util.smartypants import educate_quotes_latex
|
from sphinx.util.smartypants import educate_quotes_latex
|
||||||
|
|
||||||
@ -353,8 +353,9 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
if builder.config.today:
|
if builder.config.today:
|
||||||
self.elements['date'] = builder.config.today
|
self.elements['date'] = builder.config.today
|
||||||
else:
|
else:
|
||||||
self.elements['date'] = ustrftime(builder.config.today_fmt or
|
self.elements['date'] = format_date(builder.config.today_fmt or
|
||||||
_('%B %d, %Y'))
|
_('MMMM dd, YYYY'),
|
||||||
|
language=builder.config.language)
|
||||||
if builder.config.latex_logo:
|
if builder.config.latex_logo:
|
||||||
self.elements['logo'] = '\\includegraphics{%s}\\par' % \
|
self.elements['logo'] = '\\includegraphics{%s}\\par' % \
|
||||||
path.basename(builder.config.latex_logo)
|
path.basename(builder.config.latex_logo)
|
||||||
|
@ -20,8 +20,8 @@ from docutils.writers.manpage import (
|
|||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.locale import admonitionlabels, _
|
from sphinx.locale import admonitionlabels, _
|
||||||
from sphinx.util.osutil import ustrftime
|
|
||||||
from sphinx.util.compat import docutils_version
|
from sphinx.util.compat import docutils_version
|
||||||
|
from sphinx.util.i18n import format_date
|
||||||
|
|
||||||
|
|
||||||
class ManualPageWriter(Writer):
|
class ManualPageWriter(Writer):
|
||||||
@ -97,8 +97,9 @@ class ManualPageTranslator(BaseTranslator):
|
|||||||
if builder.config.today:
|
if builder.config.today:
|
||||||
self._docinfo['date'] = builder.config.today
|
self._docinfo['date'] = builder.config.today
|
||||||
else:
|
else:
|
||||||
self._docinfo['date'] = ustrftime(builder.config.today_fmt or
|
self._docinfo['date'] = format_date(builder.config.today_fmt or
|
||||||
_('%B %d, %Y'))
|
_('MMMM dd, YYYY'),
|
||||||
|
language=builder.config.language)
|
||||||
self._docinfo['copyright'] = builder.config.copyright
|
self._docinfo['copyright'] = builder.config.copyright
|
||||||
self._docinfo['version'] = builder.config.version
|
self._docinfo['version'] = builder.config.version
|
||||||
self._docinfo['manual_group'] = builder.config.project
|
self._docinfo['manual_group'] = builder.config.project
|
||||||
|
@ -20,7 +20,7 @@ from docutils import nodes, writers
|
|||||||
|
|
||||||
from sphinx import addnodes, __display_version__
|
from sphinx import addnodes, __display_version__
|
||||||
from sphinx.locale import admonitionlabels, _
|
from sphinx.locale import admonitionlabels, _
|
||||||
from sphinx.util import ustrftime
|
from sphinx.util.i18n import format_date
|
||||||
from sphinx.writers.latex import collected_footnote
|
from sphinx.writers.latex import collected_footnote
|
||||||
|
|
||||||
|
|
||||||
@ -218,8 +218,9 @@ class TexinfoTranslator(nodes.NodeVisitor):
|
|||||||
'project': self.escape(self.builder.config.project),
|
'project': self.escape(self.builder.config.project),
|
||||||
'copyright': self.escape(self.builder.config.copyright),
|
'copyright': self.escape(self.builder.config.copyright),
|
||||||
'date': self.escape(self.builder.config.today or
|
'date': self.escape(self.builder.config.today or
|
||||||
ustrftime(self.builder.config.today_fmt or
|
format_date(self.builder.config.today_fmt or
|
||||||
_('%B %d, %Y')))
|
_('MMMM dd, YYYY'),
|
||||||
|
language=self.builder.config.language))
|
||||||
})
|
})
|
||||||
# title
|
# title
|
||||||
title = elements['title']
|
title = elements['title']
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import datetime
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
from babel.messages.mofile import read_mo
|
from babel.messages.mofile import read_mo
|
||||||
@ -162,3 +163,20 @@ def test_get_catalogs_with_compact(dir):
|
|||||||
catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', gettext_compact=True)
|
catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', gettext_compact=True)
|
||||||
domains = set(c.domain for c in catalogs)
|
domains = set(c.domain for c in catalogs)
|
||||||
assert domains == set(['test1', 'test2', 'sub'])
|
assert domains == set(['test1', 'test2', 'sub'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_date():
|
||||||
|
date = datetime.date(2016, 2, 7)
|
||||||
|
|
||||||
|
format = None
|
||||||
|
assert i18n.format_date(format, date=date) == 'Feb 7, 2016'
|
||||||
|
assert i18n.format_date(format, date=date, language='en') == 'Feb 7, 2016'
|
||||||
|
assert i18n.format_date(format, date=date, language='ja') == '2016/02/07'
|
||||||
|
assert i18n.format_date(format, date=date, language='de') == '07.02.2016'
|
||||||
|
|
||||||
|
format = '%B %d, %Y'
|
||||||
|
print(i18n.format_date(format, date=date))
|
||||||
|
assert i18n.format_date(format, date=date) == 'February 07, 2016'
|
||||||
|
assert i18n.format_date(format, date=date, language='en') == 'February 07, 2016'
|
||||||
|
assert i18n.format_date(format, date=date, language='ja') == u'2月 07, 2016'
|
||||||
|
assert i18n.format_date(format, date=date, language='de') == 'Februar 07, 2016'
|
||||||
|
Loading…
Reference in New Issue
Block a user