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:
Takeshi KOMIYA 2016-02-14 22:01:05 +09:00
commit 3e6f608778
11 changed files with 141 additions and 24 deletions

View File

@ -29,6 +29,11 @@ Incompatible changes
* 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
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
--------------
@ -99,6 +104,7 @@ Bugs fixed
anatoly techtonik.
* #2311: Fix sphinx.ext.inheritance_diagram raises AttributeError
* #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

View File

@ -310,12 +310,19 @@ Project information
replacement for ``|today|``.
* If you set :confval:`today` to a non-empty value, it is used.
* Otherwise, the current time is formatted using :func:`time.strftime` and
the format given in :confval:`today_fmt`.
* Otherwise, the current time is formatted using `Locale Data Markup Language
<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,
%Y'`` (or, if translation is enabled with :confval:`language`, an equivalent
%format for the selected locale).
The default is no :confval:`today` and a :confval:`today_fmt` of ``'MMMM dd,
YYYY'`` (or, if translation is enabled with :confval:`language`, an
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
@ -653,10 +660,17 @@ that use Sphinx's HTMLWriter class.
.. confval:: html_last_updated_fmt
If this is not None, a 'Last updated on:' timestamp is inserted
at every page bottom, using the given :func:`strftime` format.
The empty string is equivalent to ``'%b %d, %Y'`` (or a
at every page bottom, using the given `Locale Data Markup Language
<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).
.. 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
If true, `SmartyPants <http://daringfireball.net/projects/smartypants/>`_

View File

@ -28,7 +28,8 @@ from docutils import nodes
from sphinx import addnodes
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.console import brown
@ -529,7 +530,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
metadata['copyright'] = self.esc(self.config.epub_copyright)
metadata['scheme'] = self.esc(self.config.epub_scheme)
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['spine'] = spine
metadata['guide'] = guide

View File

@ -28,8 +28,9 @@ from docutils.readers.doctree import Reader as DoctreeReader
from sphinx import package_dir, __display_version__
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, \
movefile, ustrftime, copyfile
movefile, copyfile
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.matching import patmatch, compile_matchers
from sphinx.locale import _
@ -291,7 +292,8 @@ 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 = ustrftime(lufmt or _('%b %d, %Y'))
self.last_updated = format_date(lufmt or _('MMM dd, YYYY'),
language=self.config.language)
else:
self.last_updated = None

View File

@ -24,8 +24,7 @@ from sphinx.util.nodes import (
traverse_translatable_index, extract_messages, LITERAL_TYPE_NODES, IMAGE_TYPE_NODES,
apply_source_workaround,
)
from sphinx.util.osutil import ustrftime
from sphinx.util.i18n import find_catalog
from sphinx.util.i18n import find_catalog, format_date
from sphinx.util.pycompat import indent
from sphinx.domains.std import make_glossary_term, split_term_classifiers
@ -54,7 +53,8 @@ class DefaultSubstitutions(Transform):
text = config[refname]
if refname == 'today' and not text:
# 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))

View File

@ -10,9 +10,15 @@
"""
import gettext
import io
import os
import re
import warnings
from os import path
from time import time
from datetime import datetime
from collections import namedtuple
import babel.dates
from babel.messages.pofile import read_po
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)
return catalogs
# date_format mappings: ustrftime() to bable.dates.format_date()
date_format_mappings = {
'%a': 'EEE', # Weekday as locales abbreviated name.
'%A': 'EEEE', # Weekday as locales full name.
'%b': 'MMM', # Month as locales abbreviated name.
'%B': 'MMMM', # Month as locales 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', # Locales 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)

View File

@ -158,7 +158,8 @@ def make_filename(string):
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 time is not specified, try to use $SOURCE_DATE_EPOCH variable
# See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal

View File

@ -26,8 +26,8 @@ from sphinx import highlighting
from sphinx.errors import SphinxError
from sphinx.locale import admonitionlabels, _
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.osutil import ustrftime
from sphinx.util.texescape import tex_escape_map, tex_replace_map
from sphinx.util.smartypants import educate_quotes_latex
@ -353,8 +353,9 @@ class LaTeXTranslator(nodes.NodeVisitor):
if builder.config.today:
self.elements['date'] = builder.config.today
else:
self.elements['date'] = ustrftime(builder.config.today_fmt or
_('%B %d, %Y'))
self.elements['date'] = format_date(builder.config.today_fmt or
_('MMMM dd, YYYY'),
language=builder.config.language)
if builder.config.latex_logo:
self.elements['logo'] = '\\includegraphics{%s}\\par' % \
path.basename(builder.config.latex_logo)

View File

@ -20,8 +20,8 @@ from docutils.writers.manpage import (
from sphinx import addnodes
from sphinx.locale import admonitionlabels, _
from sphinx.util.osutil import ustrftime
from sphinx.util.compat import docutils_version
from sphinx.util.i18n import format_date
class ManualPageWriter(Writer):
@ -97,8 +97,9 @@ class ManualPageTranslator(BaseTranslator):
if builder.config.today:
self._docinfo['date'] = builder.config.today
else:
self._docinfo['date'] = ustrftime(builder.config.today_fmt or
_('%B %d, %Y'))
self._docinfo['date'] = format_date(builder.config.today_fmt or
_('MMMM dd, YYYY'),
language=builder.config.language)
self._docinfo['copyright'] = builder.config.copyright
self._docinfo['version'] = builder.config.version
self._docinfo['manual_group'] = builder.config.project

View File

@ -20,7 +20,7 @@ from docutils import nodes, writers
from sphinx import addnodes, __display_version__
from sphinx.locale import admonitionlabels, _
from sphinx.util import ustrftime
from sphinx.util.i18n import format_date
from sphinx.writers.latex import collected_footnote
@ -218,8 +218,9 @@ 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
ustrftime(self.builder.config.today_fmt or
_('%B %d, %Y')))
format_date(self.builder.config.today_fmt or
_('MMMM dd, YYYY'),
language=self.builder.config.language))
})
# title
title = elements['title']

View File

@ -11,6 +11,7 @@
from __future__ import print_function
import os
import datetime
from os import path
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)
domains = set(c.domain for c in catalogs)
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'