Partially revert "Disable localisation when SOURCE_DATE_EPOCH is set (#10949)" (#11343)

This keeps some of the added tests, and avoids a full revert of ``sphinx.locale``.
This commit is contained in:
Adam Turner 2023-04-21 19:04:26 +01:00 committed by GitHub
parent 186d596f33
commit aee3c0ab75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 18 additions and 74 deletions

View File

@ -97,8 +97,6 @@ Bugs fixed
* #11192: Restore correct parallel search index building. * #11192: Restore correct parallel search index building.
Patch by Jeremy Maitin-Shepard Patch by Jeremy Maitin-Shepard
* Use the new Transifex ``tx`` client * Use the new Transifex ``tx`` client
* #9778: Disable localisation when the ``SOURCE_DATE_EPOCH`` environment
variable is set, to assist with 'reproducible builds'. Patch by James Addison
Testing Testing
-------- --------

View File

@ -502,7 +502,7 @@ 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 = format_date(lufmt or str(_('%b %d, %Y')), self.last_updated = format_date(lufmt or _('%b %d, %Y'),
language=self.config.language) language=self.config.language)
else: else:
self.last_updated = None self.last_updated = None

View File

@ -179,7 +179,7 @@ class LaTeXBuilder(Builder):
if self.config.today: if self.config.today:
self.context['date'] = self.config.today self.context['date'] = self.config.today
else: else:
self.context['date'] = format_date(self.config.today_fmt or str(_('%b %d, %Y')), self.context['date'] = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=self.config.language) language=self.config.language)
if self.config.latex_logo: if self.config.latex_logo:

View File

@ -242,9 +242,9 @@ class Cmdoption(ObjectDescription[str]):
# create an index entry # create an index entry
if currprogram: if currprogram:
descr = str(_('%s command line option') % currprogram) descr = _('%s command line option') % currprogram
else: else:
descr = str(_('command line option')) descr = _('command line option')
for option in signode.get('allnames', []): for option in signode.get('allnames', []):
entry = '; '.join([descr, option]) entry = '; '.join([descr, option])
self.indexnode['entries'].append(('pair', entry, signode['ids'][0], '', None)) self.indexnode['entries'].append(('pair', entry, signode['ids'][0], '', None))

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import locale import locale
from gettext import NullTranslations, translation from gettext import NullTranslations, translation
from os import getenv, path from os import path
from typing import Any, Callable from typing import Any, Callable
@ -105,22 +105,10 @@ def init(
if translator.__class__ is NullTranslations: if translator.__class__ is NullTranslations:
translator = None translator = None
if getenv('SOURCE_DATE_EPOCH') is not None: if language:
# Disable localization during reproducible source builds
# See https://reproducible-builds.org/docs/source-date-epoch/
#
# Note: Providing an empty/none value to gettext.translation causes
# it to consult various language-related environment variables to find
# locale(s). We don't want that during a reproducible build; we want
# to run through the same code path, but to return NullTranslations.
#
# To achieve that, specify the ISO-639-3 'undetermined' language code,
# which should not match any translation catalogs.
languages: list[str] | None = ['und']
elif language:
if '_' in language: if '_' in language:
# for language having country code (like "de_AT") # for language having country code (like "de_AT")
languages = [language, language.split('_')[0]] languages: list[str] | None = [language, language.split('_')[0]]
else: else:
languages = [language] languages = [language]
else: else:
@ -203,7 +191,12 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str],
.. versionadded:: 1.8 .. versionadded:: 1.8
""" """
def gettext(message: str) -> str: def gettext(message: str) -> str:
return _TranslationProxy(catalog, namespace, message) # type: ignore[return-value] if not is_translator_registered(catalog, namespace):
# not initialized yet
return _TranslationProxy(catalog, namespace, message) # type: ignore[return-value] # noqa: E501
else:
translator = get_translator(catalog, namespace)
return translator.gettext(message)
return gettext return gettext

View File

@ -106,7 +106,7 @@ class DefaultSubstitutions(SphinxTransform):
text = self.config[refname] text = self.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 = format_date(self.config.today_fmt or str(_('%b %d, %Y')), text = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=self.config.language) language=self.config.language)
ref.replace_self(nodes.Text(text)) ref.replace_self(nodes.Text(text))

View File

@ -93,7 +93,7 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
if self.config.today: if self.config.today:
self._docinfo['date'] = self.config.today self._docinfo['date'] = self.config.today
else: else:
self._docinfo['date'] = format_date(self.config.today_fmt or str(_('%b %d, %Y')), self._docinfo['date'] = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=self.config.language) language=self.config.language)
self._docinfo['copyright'] = self.config.copyright self._docinfo['copyright'] = self.config.copyright
self._docinfo['version'] = self.config.version self._docinfo['version'] = self.config.version

View File

@ -220,7 +220,7 @@ class TexinfoTranslator(SphinxTranslator):
'project': self.escape(self.config.project), 'project': self.escape(self.config.project),
'copyright': self.escape(self.config.copyright), 'copyright': self.escape(self.config.copyright),
'date': self.escape(self.config.today or 'date': self.escape(self.config.today or
format_date(self.config.today_fmt or str(_('%b %d, %Y')), format_date(self.config.today_fmt or _('%b %d, %Y'),
language=self.config.language)), language=self.config.language)),
}) })
# title # title

View File

@ -791,8 +791,8 @@ class TextTranslator(SphinxTranslator):
def visit_image(self, node: Element) -> None: def visit_image(self, node: Element) -> None:
if 'alt' in node.attributes: if 'alt' in node.attributes:
self.add_text(str(_('[image: %s]') % node['alt'])) self.add_text(_('[image: %s]') % node['alt'])
self.add_text(str(_('[image]'))) self.add_text(_('[image]'))
raise nodes.SkipNode raise nodes.SkipNode
def visit_transition(self, node: Element) -> None: def visit_transition(self, node: Element) -> None:

View File

@ -74,25 +74,3 @@ def test_init_environment_language(rootdir, monkeypatch):
m.setenv("LANGUAGE", "et_EE:et") m.setenv("LANGUAGE", "et_EE:et")
_ = _empty_language_translation(rootdir) _ = _empty_language_translation(rootdir)
assert _('Hello world') == 'Tere maailm' assert _('Hello world') == 'Tere maailm'
def test_init_reproducible_build_language(rootdir, monkeypatch):
with monkeypatch.context() as m:
m.setenv("SOURCE_DATE_EPOCH", "0")
m.setenv("LANGUAGE", "en_US:en")
_ = _empty_language_translation(rootdir)
sde_en_translation = str(_('Hello world')) # str cast to evaluate lazy method
with monkeypatch.context() as m:
m.setenv("SOURCE_DATE_EPOCH", "0")
m.setenv("LANGUAGE", "et_EE:et")
_ = _empty_language_translation(rootdir)
sde_et_translation = str(_('Hello world')) # str cast to evaluate lazy method
with monkeypatch.context() as m:
m.setenv("LANGUAGE", "et_EE:et")
_ = _empty_language_translation(rootdir)
loc_et_translation = str(_('Hello world')) # str cast to evaluate lazy method
assert sde_en_translation == sde_et_translation
assert sde_et_translation != loc_et_translation

View File

@ -114,28 +114,3 @@ def test_inventory_localization(tempdir):
# Ensure that the inventory contents differ # Ensure that the inventory contents differ
assert inventory_et.read_bytes() != inventory_en.read_bytes() assert inventory_et.read_bytes() != inventory_en.read_bytes()
def test_inventory_reproducible(tempdir, monkeypatch):
with monkeypatch.context() as m:
# Configure reproducible builds
# See: https://reproducible-builds.org/docs/source-date-epoch/
m.setenv("SOURCE_DATE_EPOCH", "0")
# Build an app using Estonian (EE) locale
srcdir_et = _write_appconfig(tempdir, "et")
reproducible_inventory_et = _build_inventory(srcdir_et)
# Build the same app using English (US) locale
srcdir_en = _write_appconfig(tempdir, "en")
reproducible_inventory_en = _build_inventory(srcdir_en)
# Also build the app using Estonian (EE) locale without build reproducibility enabled
srcdir_et = _write_appconfig(tempdir, "et", prefix="localized")
localized_inventory_et = _build_inventory(srcdir_et)
# Ensure that the reproducible inventory contents are identical
assert reproducible_inventory_et.read_bytes() == reproducible_inventory_en.read_bytes()
# Ensure that inventory contents are different between a localized and non-localized build
assert reproducible_inventory_et.read_bytes() != localized_inventory_et.read_bytes()