Fixed #21408 — German Translation for “3 days ago”
The problem: “3 days ago” should translate to “vor 3 Tagen” in German, while “3 days” translates to “3 Tage”. #21408 describes that django always translated to “Tage”, even when the dative “Tagen” was correct. The same applies to months (“Monate”/“Monaten”) and years (“Jahre”/“Jahren”). The solution: Let `timesince` caller provide the string dict to use for the time-related strings.
This commit is contained in:
parent
704443acac
commit
78912ccd0e
1
AUTHORS
1
AUTHORS
|
@ -559,6 +559,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Max Derkachev <mderk@yandex.ru>
|
Max Derkachev <mderk@yandex.ru>
|
||||||
Maxime Lorant <maxime.lorant@gmail.com>
|
Maxime Lorant <maxime.lorant@gmail.com>
|
||||||
Maxime Turcotte <maxocub@riseup.net>
|
Maxime Turcotte <maxocub@riseup.net>
|
||||||
|
Maximilian Merz <django@mxmerz.de>
|
||||||
Maximillian Dornseif <md@hudora.de>
|
Maximillian Dornseif <md@hudora.de>
|
||||||
mccutchen@gmail.com
|
mccutchen@gmail.com
|
||||||
Meir Kriheli <http://mksoft.co.il/>
|
Meir Kriheli <http://mksoft.co.il/>
|
||||||
|
|
Binary file not shown.
|
@ -4,12 +4,13 @@
|
||||||
# André Hagenbruch, 2011
|
# André Hagenbruch, 2011
|
||||||
# Claude Paroz <claude@2xlibre.net>, 2013
|
# Claude Paroz <claude@2xlibre.net>, 2013
|
||||||
# Jannis Leidel <jannis@leidel.info>, 2011,2013-2014
|
# Jannis Leidel <jannis@leidel.info>, 2011,2013-2014
|
||||||
|
# Maximilian Merz <django@mxmerz.de>, 2018
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django\n"
|
"Project-Id-Version: django\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-01-17 11:07+0100\n"
|
"POT-Creation-Date: 2015-01-17 11:07+0100\n"
|
||||||
"PO-Revision-Date: 2017-09-23 18:54+0000\n"
|
"PO-Revision-Date: 2018-03-13 16:07+0100\n"
|
||||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: German (http://www.transifex.com/django/django/language/de/)\n"
|
"Language-Team: German (http://www.transifex.com/django/django/language/de/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -202,7 +203,6 @@ msgid "yesterday"
|
||||||
msgstr "gestern"
|
msgstr "gestern"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgctxt "naturaltime"
|
|
||||||
msgid "%(delta)s ago"
|
msgid "%(delta)s ago"
|
||||||
msgstr "vor %(delta)s"
|
msgstr "vor %(delta)s"
|
||||||
|
|
||||||
|
@ -234,10 +234,56 @@ msgstr[0] "vor einer Stunde"
|
||||||
msgstr[1] "vor %(count)s Stunden"
|
msgstr[1] "vor %(count)s Stunden"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgctxt "naturaltime"
|
|
||||||
msgid "%(delta)s from now"
|
msgid "%(delta)s from now"
|
||||||
msgstr "in %(delta)s"
|
msgstr "in %(delta)s"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-future"
|
||||||
|
msgid "%(delta)s from now"
|
||||||
|
msgstr "in %(delta)s"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-past"
|
||||||
|
msgid "%d day"
|
||||||
|
msgid_plural "%d days"
|
||||||
|
msgstr[0] "%d Tag"
|
||||||
|
msgstr[1] "%d Tagen"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-past"
|
||||||
|
msgid "%d month"
|
||||||
|
msgid_plural "%d months"
|
||||||
|
msgstr[0] "%d Monat"
|
||||||
|
msgstr[1] "%d Monaten"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-past"
|
||||||
|
msgid "%d year"
|
||||||
|
msgid_plural "%d years"
|
||||||
|
msgstr[0] "%d Jahr"
|
||||||
|
msgstr[1] "%d Jahren"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-future"
|
||||||
|
msgid "%d day"
|
||||||
|
msgid_plural "%d days"
|
||||||
|
msgstr[0] "%d Tag"
|
||||||
|
msgstr[1] "%d Tagen"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-future"
|
||||||
|
msgid "%d month"
|
||||||
|
msgid_plural "%d months"
|
||||||
|
msgstr[0] "%d Monat"
|
||||||
|
msgstr[1] "%d Monaten"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgctxt "naturaltime-future"
|
||||||
|
msgid "%d year"
|
||||||
|
msgid_plural "%d years"
|
||||||
|
msgstr[0] "%d Jahr"
|
||||||
|
msgstr[1] "%d Jahren"
|
||||||
|
|
||||||
#. Translators: please keep a non-breaking space (U+00A0)
|
#. Translators: please keep a non-breaking space (U+00A0)
|
||||||
#. between count and time unit.
|
#. between count and time unit.
|
||||||
#, python-format
|
#, python-format
|
||||||
|
|
|
@ -8,7 +8,9 @@ from django.template import defaultfilters
|
||||||
from django.utils.formats import number_format
|
from django.utils.formats import number_format
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.timezone import is_aware, utc
|
from django.utils.timezone import is_aware, utc
|
||||||
from django.utils.translation import gettext as _, ngettext, pgettext
|
from django.utils.translation import (
|
||||||
|
gettext as _, ngettext, npgettext_lazy, pgettext,
|
||||||
|
)
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
@ -220,7 +222,16 @@ def naturaltime(value):
|
||||||
delta = now - value
|
delta = now - value
|
||||||
if delta.days != 0:
|
if delta.days != 0:
|
||||||
# Translators: delta will contain a string like '2 months' or '1 month, 2 weeks'
|
# Translators: delta will contain a string like '2 months' or '1 month, 2 weeks'
|
||||||
return _('%(delta)s ago') % {'delta': defaultfilters.timesince(value, now)}
|
return _('%(delta)s ago') % {'delta': defaultfilters.timesince(value, now, time_strings={
|
||||||
|
# Translators: 'naturaltime-past' strings will be included in
|
||||||
|
# '%(delta)s ago'
|
||||||
|
'year': npgettext_lazy('naturaltime-past', '%d year', '%d years'),
|
||||||
|
'month': npgettext_lazy('naturaltime-past', '%d month', '%d months'),
|
||||||
|
'week': npgettext_lazy('naturaltime-past', '%d week', '%d weeks'),
|
||||||
|
'day': npgettext_lazy('naturaltime-past', '%d day', '%d days'),
|
||||||
|
'hour': npgettext_lazy('naturaltime-past', '%d hour', '%d hours'),
|
||||||
|
'minute': npgettext_lazy('naturaltime-past', '%d minute', '%d minutes')
|
||||||
|
})}
|
||||||
elif delta.seconds == 0:
|
elif delta.seconds == 0:
|
||||||
return _('now')
|
return _('now')
|
||||||
elif delta.seconds < 60:
|
elif delta.seconds < 60:
|
||||||
|
@ -247,7 +258,16 @@ def naturaltime(value):
|
||||||
delta = value - now
|
delta = value - now
|
||||||
if delta.days != 0:
|
if delta.days != 0:
|
||||||
# Translators: delta will contain a string like '2 months' or '1 month, 2 weeks'
|
# Translators: delta will contain a string like '2 months' or '1 month, 2 weeks'
|
||||||
return _('%(delta)s from now') % {'delta': defaultfilters.timeuntil(value, now)}
|
return _('%(delta)s from now') % {'delta': defaultfilters.timeuntil(value, now, time_strings={
|
||||||
|
# Translators: 'naturaltime-future' strings will be included in
|
||||||
|
# '%(delta)s from now'
|
||||||
|
'year': npgettext_lazy('naturaltime-future', '%d year', '%d years'),
|
||||||
|
'month': npgettext_lazy('naturaltime-future', '%d month', '%d months'),
|
||||||
|
'week': npgettext_lazy('naturaltime-future', '%d week', '%d weeks'),
|
||||||
|
'day': npgettext_lazy('naturaltime-future', '%d day', '%d days'),
|
||||||
|
'hour': npgettext_lazy('naturaltime-future', '%d hour', '%d hours'),
|
||||||
|
'minute': npgettext_lazy('naturaltime-future', '%d minute', '%d minutes')
|
||||||
|
})}
|
||||||
elif delta.seconds == 0:
|
elif delta.seconds == 0:
|
||||||
return _('now')
|
return _('now')
|
||||||
elif delta.seconds < 60:
|
elif delta.seconds < 60:
|
||||||
|
|
|
@ -5,17 +5,26 @@ from django.utils.html import avoid_wrapping
|
||||||
from django.utils.timezone import is_aware, utc
|
from django.utils.timezone import is_aware, utc
|
||||||
from django.utils.translation import gettext, ngettext_lazy
|
from django.utils.translation import gettext, ngettext_lazy
|
||||||
|
|
||||||
|
TIME_STRINGS = {
|
||||||
|
'year': ngettext_lazy('%d year', '%d years'),
|
||||||
|
'month': ngettext_lazy('%d month', '%d months'),
|
||||||
|
'week': ngettext_lazy('%d week', '%d weeks'),
|
||||||
|
'day': ngettext_lazy('%d day', '%d days'),
|
||||||
|
'hour': ngettext_lazy('%d hour', '%d hours'),
|
||||||
|
'minute': ngettext_lazy('%d minute', '%d minutes'),
|
||||||
|
}
|
||||||
|
|
||||||
TIMESINCE_CHUNKS = (
|
TIMESINCE_CHUNKS = (
|
||||||
(60 * 60 * 24 * 365, ngettext_lazy('%d year', '%d years')),
|
(60 * 60 * 24 * 365, 'year'),
|
||||||
(60 * 60 * 24 * 30, ngettext_lazy('%d month', '%d months')),
|
(60 * 60 * 24 * 30, 'month'),
|
||||||
(60 * 60 * 24 * 7, ngettext_lazy('%d week', '%d weeks')),
|
(60 * 60 * 24 * 7, 'week'),
|
||||||
(60 * 60 * 24, ngettext_lazy('%d day', '%d days')),
|
(60 * 60 * 24, 'day'),
|
||||||
(60 * 60, ngettext_lazy('%d hour', '%d hours')),
|
(60 * 60, 'hour'),
|
||||||
(60, ngettext_lazy('%d minute', '%d minutes'))
|
(60, 'minute'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def timesince(d, now=None, reversed=False):
|
def timesince(d, now=None, reversed=False, time_strings=None):
|
||||||
"""
|
"""
|
||||||
Take two datetime objects and return the time between d and now as a nicely
|
Take two datetime objects and return the time between d and now as a nicely
|
||||||
formatted string, e.g. "10 minutes". If d occurs after now, return
|
formatted string, e.g. "10 minutes". If d occurs after now, return
|
||||||
|
@ -26,9 +35,15 @@ def timesince(d, now=None, reversed=False):
|
||||||
displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
|
displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
|
||||||
possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
|
possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
|
||||||
|
|
||||||
|
`time_strings` is an optional dict of strings to replace the default
|
||||||
|
TIME_STRINGS dict.
|
||||||
|
|
||||||
Adapted from
|
Adapted from
|
||||||
http://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
|
http://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
|
||||||
"""
|
"""
|
||||||
|
if time_strings is None:
|
||||||
|
time_strings = TIME_STRINGS
|
||||||
|
|
||||||
# Convert datetime.date to datetime.datetime for comparison.
|
# Convert datetime.date to datetime.datetime for comparison.
|
||||||
if not isinstance(d, datetime.datetime):
|
if not isinstance(d, datetime.datetime):
|
||||||
d = datetime.datetime(d.year, d.month, d.day)
|
d = datetime.datetime(d.year, d.month, d.day)
|
||||||
|
@ -59,18 +74,18 @@ def timesince(d, now=None, reversed=False):
|
||||||
count = since // seconds
|
count = since // seconds
|
||||||
if count != 0:
|
if count != 0:
|
||||||
break
|
break
|
||||||
result = avoid_wrapping(name % count)
|
result = avoid_wrapping(time_strings[name] % count)
|
||||||
if i + 1 < len(TIMESINCE_CHUNKS):
|
if i + 1 < len(TIMESINCE_CHUNKS):
|
||||||
# Now get the second item
|
# Now get the second item
|
||||||
seconds2, name2 = TIMESINCE_CHUNKS[i + 1]
|
seconds2, name2 = TIMESINCE_CHUNKS[i + 1]
|
||||||
count2 = (since - (seconds * count)) // seconds2
|
count2 = (since - (seconds * count)) // seconds2
|
||||||
if count2 != 0:
|
if count2 != 0:
|
||||||
result += gettext(', ') + avoid_wrapping(name2 % count2)
|
result += gettext(', ') + avoid_wrapping(time_strings[name2] % count2)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def timeuntil(d, now=None):
|
def timeuntil(d, now=None, time_strings=None):
|
||||||
"""
|
"""
|
||||||
Like timesince, but return a string measuring the time until the given time.
|
Like timesince, but return a string measuring the time until the given time.
|
||||||
"""
|
"""
|
||||||
return timesince(d, now, reversed=True)
|
return timesince(d, now, reversed=True, time_strings=time_strings)
|
||||||
|
|
|
@ -289,3 +289,29 @@ class HumanizeTests(SimpleTestCase):
|
||||||
self.assertEqual(expected_natural_time, natural_time)
|
self.assertEqual(expected_natural_time, natural_time)
|
||||||
finally:
|
finally:
|
||||||
humanize.datetime = orig_humanize_datetime
|
humanize.datetime = orig_humanize_datetime
|
||||||
|
|
||||||
|
def test_dative_inflection_for_timedelta(self):
|
||||||
|
"""Translation may differ depending on the string it is inserted in."""
|
||||||
|
test_list = [
|
||||||
|
now - datetime.timedelta(days=1),
|
||||||
|
now - datetime.timedelta(days=2),
|
||||||
|
now - datetime.timedelta(days=30),
|
||||||
|
now - datetime.timedelta(days=60),
|
||||||
|
now - datetime.timedelta(days=500),
|
||||||
|
now - datetime.timedelta(days=865),
|
||||||
|
]
|
||||||
|
result_list = [
|
||||||
|
'vor 1\xa0Tag',
|
||||||
|
'vor 2\xa0Tagen',
|
||||||
|
'vor 1\xa0Monat',
|
||||||
|
'vor 2\xa0Monaten',
|
||||||
|
'vor 1\xa0Jahr, 4\xa0Monaten',
|
||||||
|
'vor 2\xa0Jahren, 4\xa0Monaten',
|
||||||
|
]
|
||||||
|
|
||||||
|
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
|
||||||
|
try:
|
||||||
|
with translation.override('de'), self.settings(USE_L10N=True):
|
||||||
|
self.humanize_tester(test_list, result_list, 'naturaltime')
|
||||||
|
finally:
|
||||||
|
humanize.datetime = orig_humanize_datetime
|
||||||
|
|
Loading…
Reference in New Issue