Skip to content

fix: pass grammatical context through naturaldelta for i18n case-aware translations - #371

Open
deepakganesh78 wants to merge 2 commits into
python-humanize:mainfrom
deepakganesh78:fix/issue263-naturaltime-grammatical-context
Open

fix: pass grammatical context through naturaldelta for i18n case-aware translations#371
deepakganesh78 wants to merge 2 commits into
python-humanize:mainfrom
deepakganesh78:fix/issue263-naturaltime-grammatical-context

Conversation

@deepakganesh78

Copy link
Copy Markdown

Fixes #263

Problem

naturaltime() composes naturaldelta() output with format strings like %s ago / %s from now. Languages such as German require different grammatical cases depending on the surrounding preposition (e.g. nominative eine Stunde vs dative einer Stunde after vor). The two-step composition made it impossible for translators to provide case-correct forms because naturaldelta had no knowledge of the tense context in which its result would be used.

Root cause

naturaldelta() uses _ngettext() / _() for all its translation strings. naturaltime() then wraps the result with _("%s ago"). There is no mechanism for translators to supply alternate grammatical forms of the time-unit strings depending on context.

Fix

  1. humanize.i18n – added _npgettext() (plural + msgctxt gettext, mirrors the existing _pgettext).
  2. naturaldelta() – added an optional context parameter. When set, all internal _() / _ngettext() calls are replaced with _pgettext(context, …) / _npgettext(context, …) so translators can provide context-specific forms via msgctxt entries in .po files.
  3. naturaltime() – now passes "naturaltime-past" or "naturaltime-future" as context to naturaldelta().

How translators use this

German translators can now add msgctxt entries like:

`po
msgctxt "naturaltime-past"
msgid "a second"
msgstr "einer Sekunde"

msgctxt "naturaltime-future"
msgid "a second"
msgstr "einer Sekunde"
`

Without matching msgctxt translations, output is identical to before (full backward compatibility).

Validation

  • Full test suite: 724 passed, 74 skipped
  • ruff check clean on all touched files
  • Added regression tests verifying:
    • context parameter produces identical English output (no translation active)
    • _npgettext / _pgettext are called with correct context strings
    • naturaltime passes correct tense context to naturaldelta

deepakganesh78 and others added 2 commits August 1, 2026 22:27
…e translations

naturaltime() composes naturaldelta() output with format strings like
'%s ago' / '%s from now'.  Languages such as German require different
grammatical cases depending on the surrounding preposition (e.g.
nominative 'eine Stunde' vs dative 'einer Stunde' after 'vor').

The two-step composition (naturaldelta → wrap) made it impossible for
translators to provide case-correct forms because naturaldelta had no
knowledge of the context in which its result would be used.

Changes:
- Add _npgettext() to humanize.i18n (plural + context gettext).
- Add an optional 'context' parameter to naturaldelta().  When set,
  all internal gettext/ngettext calls are replaced with their
  pgettext/npgettext counterparts, keyed by the supplied context.
- naturaltime() now passes 'naturaltime-past' or 'naturaltime-future'
  as context so translators can provide case-aware forms via msgctxt
  entries in .po files.

The change is fully backward-compatible: without a context (or without
matching msgctxt translations), output is identical to before.

Fixes python-humanize#263

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

German requires different cases for ‘%s ago’ and ‘%s from now’

1 participant