Repository: jmoiron/humanize

Issue:
Documentation shows private functions
The documentation lists some functions that are not available outside the submodule they are defined in. All of the usage examples use:

```python
import humanize

humanize.…
```

but this does not allow access to these methods.

These functions should be exported to the parent module.


Discussion:
hugovk: Any idea why the warning on `_UnitMeta`'s `__getattr__` is causing an error? It's just this one, the others are fine.

Would be nice to get this released soon, the [Python 3.6 EOL](https://endoflife.date/python) is around the corner and dropping it will bring a major version bump, and would be a good time to bundle in removal of these deprecations too.

Thanks!

Patch:
diff --git a/README.md b/README.md
index 94f9b03..3ce8897 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,6 @@ readable size or throughput. It is localized to:
 * Bengali
 * Brazilian Portuguese
 * Catalan
-* Danish
 * Dutch
 * European Portuguese
 * Finnish
@@ -194,7 +193,7 @@ locales in.
 >>> humanize.i18n.activate("xx_XX")
 <...>
 FileNotFoundError: [Errno 2] No translation file found for domain: 'humanize'
->>> humanize.i18n.activate("pt_BR", path="path/to/my/own/translation/")
+>>> humanize.i18n.activate("pt_BR", path="path/to/my/portuguese/translation/")
 <gettext.GNUTranslations instance ...>
 ```
 
diff --git a/src/humanize/i18n.py b/src/humanize/i18n.py
index 71b5eaf..67c0fca 100644
--- a/src/humanize/i18n.py
+++ b/src/humanize/i18n.py
@@ -1,6 +1,7 @@
 """Activate, get and deactivate translations."""
 import gettext as gettext_module
 import os.path
+import warnings
 from threading import local
 
 __all__ = ["activate", "deactivate", "gettext", "ngettext", "thousands_separator"]
@@ -66,7 +67,7 @@ def deactivate():
     _CURRENT.locale = None
 
 
-def gettext(message):
+def _gettext(message):
     """Get translation.
 
     Args:
@@ -78,7 +79,27 @@ def gettext(message):
     return get_translation().gettext(message)
 
 
-def pgettext(msgctxt, message):
+def gettext(message):
+    """Get translation.
+
+    Args:
+        message (str): Text to translate.
+
+    Returns:
+        str: Translated text.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_gettext`.
+    """
+    warnings.warn(
+        "`gettext` has been deprecated. "
+        "It is still available as the private member `_gettext`.",
+        DeprecationWarning,
+    )
+    return _gettext(message)
+
+
+def _pgettext(msgctxt, message):
     """Fetches a particular translation.
 
     It works with `msgctxt` .po modifiers and allows duplicate keys with different
@@ -103,8 +124,32 @@ def pgettext(msgctxt, message):
         return message if translation == key else translation
 
 
-def ngettext(message, plural, num):
-    """Plural version of gettext.
+def pgettext(msgctxt, message):
+    """Fetches a particular translation.
+
+    It works with `msgctxt` .po modifiers and allows duplicate keys with different
+    translations.
+
+    Args:
+        msgctxt (str): Context of the translation.
+        message (str): Text to translate.
+
+    Returns:
+        str: Translated text.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_pgettext`.
+    """
+    warnings.warn(
+        "`pgettext` has been deprecated. "
+        "It is still available as the private member `_pgettext`.",
+        DeprecationWarning,
+    )
+    return _pgettext(msgctxt, message)
+
+
+def _ngettext(message, plural, num):
+    """Plural version of _gettext.
 
     Args:
         message (str): Singular text to translate.
@@ -118,14 +163,37 @@ def ngettext(message, plural, num):
     return get_translation().ngettext(message, plural, num)
 
 
-def gettext_noop(message):
+def ngettext(msgctxt, message):
+    """Plural version of gettext.
+
+    Args:
+        message (str): Singular text to translate.
+        plural (str): Plural text to translate.
+        num (str): The number (e.g. item count) to determine translation for the
+            respective grammatical number.
+
+    Returns:
+        str: Translated text.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_ngettext`.
+    """
+    warnings.warn(
+        "`ngettext` has been deprecated. "
+        "It is still available as the private member `_ngettext`.",
+        DeprecationWarning,
+    )
+    return _ngettext(msgctxt, message)
+
+
+def _gettext_noop(message):
     """Mark a string as a translation string without translating it.
 
     Example usage:
     ```python
-    CONSTANTS = [gettext_noop('first'), gettext_noop('second')]
+    CONSTANTS = [_gettext_noop('first'), _gettext_noop('second')]
     def num_name(n):
-        return gettext(CONSTANTS[n])
+        return _gettext(CONSTANTS[n])
     ```
 
     Args:
@@ -137,14 +205,41 @@ def gettext_noop(message):
     return message
 
 
-def ngettext_noop(singular, plural):
+def gettext_noop(message):
+    """Mark a string as a translation string without translating it.
+
+    Example usage:
+    ```python
+    CONSTANTS = [_gettext_noop('first'), _gettext_noop('second')]
+    def num_name(n):
+        return _gettext(CONSTANTS[n])
+    ```
+
+    Args:
+        message (str): Text to translate in the future.
+
+    Returns:
+        str: Original text, unchanged.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_gettext_noop`.
+    """
+    warnings.warn(
+        "`gettext_noop` has been deprecated. "
+        "It is still available as the private member `_gettext_noop`.",
+        DeprecationWarning,
+    )
+    return _gettext_noop(message)
+
+
+def _ngettext_noop(singular, plural):
     """Mark two strings as pluralized translations without translating them.
 
     Example usage:
     ```python
     CONSTANTS = [ngettext_noop('first', 'firsts'), ngettext_noop('second', 'seconds')]
     def num_name(n):
-        return ngettext(*CONSTANTS[n])
+        return _ngettext(*CONSTANTS[n])
     ```
 
     Args:
@@ -157,6 +252,34 @@ def ngettext_noop(singular, plural):
     return (singular, plural)
 
 
+def ngettext_noop(singular, plural):
+    """Mark two strings as pluralized translations without translating them.
+
+    Example usage:
+    ```python
+    CONSTANTS = [ngettext_noop('first', 'firsts'), ngettext_noop('second', 'seconds')]
+    def num_name(n):
+        return _ngettext(*CONSTANTS[n])
+    ```
+
+    Args:
+        singular (str): Singular text to translate in the future.
+        plural (str): Plural text to translate in the future.
+
+    Returns:
+        tuple: Original text, unchanged.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_ngettext_noop`.
+    """
+    warnings.warn(
+        "`ngettext_noop` has been deprecated. "
+        "It is still available as the private member `_ngettext_noop`.",
+        DeprecationWarning,
+    )
+    return _ngettext_noop(singular, plural)
+
+
 def thousands_separator() -> str:
     """Return the thousands separator for a locale, default to comma.
 
diff --git a/src/humanize/locale/da_DK/LC_MESSAGES/humanize.po b/src/humanize/locale/da_DK/LC_MESSAGES/humanize.po
deleted file mode 100644
index 510f2cc..0000000
--- a/src/humanize/locale/da_DK/LC_MESSAGES/humanize.po
+++ /dev/null
@@ -1,364 +0,0 @@
-# Danish translations for humanize package.
-# Copyright (C) 2021
-# This file is distributed under the same license as the humanize project.
-# YURII DEREVYCH <gkhelloworld@gmail.com>, 2021.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: humanize\n"
-"Report-Msgid-Bugs-To: https://github.com/jmoiron/humanize/issues\n"
-"POT-Creation-Date: 2021-07-05 09:49+0200\n"
-"PO-Revision-Date: 2021-11-24 22:25+0200\n"
-"Last-Translator: YURII DEREVYCH <gkhelloworld@gmail.com>\n"
-"Language-Team: Da\n"
-"Language: da\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 3.0\n"
-
-#: src/humanize/number.py:58
-msgctxt "0 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:59
-msgctxt "1 (male)"
-msgid "st"
-msgstr ":e"
-
-#: src/humanize/number.py:60
-msgctxt "2 (male)"
-msgid "nd"
-msgstr ":t"
-
-#: src/humanize/number.py:61
-msgctxt "3 (male)"
-msgid "rd"
-msgstr ":e"
-
-#: src/humanize/number.py:62
-msgctxt "4 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:63
-msgctxt "5 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:64
-msgctxt "6 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:65
-msgctxt "7 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:66
-msgctxt "8 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:67
-msgctxt "9 (male)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:71
-msgctxt "0 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:72
-msgctxt "1 (female)"
-msgid "st"
-msgstr ":e"
-
-#: src/humanize/number.py:73
-msgctxt "2 (female)"
-msgid "nd"
-msgstr ":t"
-
-#: src/humanize/number.py:74
-msgctxt "3 (female)"
-msgid "rd"
-msgstr ":e"
-
-#: src/humanize/number.py:75
-msgctxt "4 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:76
-msgctxt "5 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:77
-msgctxt "6 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:78
-msgctxt "7 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:79
-msgctxt "8 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:80
-msgctxt "9 (female)"
-msgid "th"
-msgstr ":e"
-
-#: src/humanize/number.py:141
-msgid "thousand"
-msgid_plural "thousand"
-msgstr[0] "tusind"
-msgstr[1] "thousand"
-
-#: src/humanize/number.py:142
-msgid "million"
-msgid_plural "million"
-msgstr[0] "million"
-msgstr[1] "millioner"
-
-#: src/humanize/number.py:143
-msgid "billion"
-msgid_plural "billion"
-msgstr[0] "billion"
-msgstr[1] "billioner"
-
-#: src/humanize/number.py:144
-msgid "trillion"
-msgid_plural "trillion"
-msgstr[0] "billion"
-msgstr[1] "billioner"
-
-#: src/humanize/number.py:145
-msgid "quadrillion"
-msgid_plural "quadrillion"
-msgstr[0] "quadrillion"
-msgstr[1] "quadrillioner"
-
-#: src/humanize/number.py:146
-msgid "quintillion"
-msgid_plural "quintillion"
-msgstr[0] "quintillion"
-msgstr[1] "quintillioner"
-
-#: src/humanize/number.py:147
-msgid "sextillion"
-msgid_plural "sextillion"
-msgstr[0] "sextillion"
-msgstr[1] "sextillioner"
-
-#: src/humanize/number.py:148
-msgid "septillion"
-msgid_plural "septillion"
-msgstr[0] "septillion"
-msgstr[1] "septillioner"
-
-#: src/humanize/number.py:149
-msgid "octillion"
-msgid_plural "octillion"
-msgstr[0] "octillion"
-msgstr[1] "octillioner"
-
-#: src/humanize/number.py:150
-msgid "nonillion"
-msgid_plural "nonillion"
-msgstr[0] "nonillion"
-msgstr[1] "nonillioner"
-
-#: src/humanize/number.py:151
-msgid "decillion"
-msgid_plural "decillion"
-msgstr[0] "decillion"
-msgstr[1] "decillioner"
-
-#: src/humanize/number.py:152
-msgid "googol"
-msgid_plural "googol"
-msgstr[0] "googol"
-msgstr[1] "googol"
-
-#: src/humanize/number.py:247
-msgid "zero"
-msgstr "nul"
-
-#: src/humanize/number.py:248
-msgid "one"
-msgstr "en"
-
-#: src/humanize/number.py:249
-msgid "two"
-msgstr "to"
-
-#: src/humanize/number.py:250
-msgid "three"
-msgstr "tre"
-
-#: src/humanize/number.py:251
-msgid "four"
-msgstr "fire"
-
-#: src/humanize/number.py:252
-msgid "five"
-msgstr "fem"
-
-#: src/humanize/number.py:253
-msgid "six"
-msgstr "seks"
-
-#: src/humanize/number.py:254
-msgid "seven"
-msgstr "syv"
-
-#: src/humanize/number.py:255
-msgid "eight"
-msgstr "otte"
-
-#: src/humanize/number.py:256
-msgid "nine"
-msgstr "ni"
-
-#: src/humanize/time.py:138
-#, python-format
-msgid "%d microsecond"
-msgid_plural "%d microseconds"
-msgstr[0] "%d mikrosekund"
-msgstr[1] "%d mikrosekunder"
-
-#: src/humanize/time.py:147
-#, python-format
-msgid "%d millisecond"
-msgid_plural "%d milliseconds"
-msgstr[0] "%d millsekund"
-msgstr[1] "%d millsekunder"
-
-#: src/humanize/time.py:150 src/humanize/time.py:231
-msgid "a moment"
-msgstr "et øjeblik"
-
-#: src/humanize/time.py:152
-msgid "a second"
-msgstr "en anden"
-
-#: src/humanize/time.py:154
-#, python-format
-msgid "%d second"
-msgid_plural "%d seconds"
-msgstr[0] "%d sekund"
-msgstr[1] "%d sekunder"
-
-#: src/humanize/time.py:156
-msgid "a minute"
-msgstr "en minut"
-
-#: src/humanize/time.py:159
-#, python-format
-msgid "%d minute"
-msgid_plural "%d minutes"
-msgstr[0] "%d minut"
-msgstr[1] "%d minutter"
-
-#: src/humanize/time.py:161
-msgid "an hour"
-msgstr "en time"
-
-#: src/humanize/time.py:164
-#, python-format
-msgid "%d hour"
-msgid_plural "%d hours"
-msgstr[0] "%d time"
-msgstr[1] "%d timer"
-
-#: src/humanize/time.py:167
-msgid "a day"
-msgstr "en dag"
-
-#: src/humanize/time.py:169 src/humanize/time.py:172
-#, python-format
-msgid "%d day"
-msgid_plural "%d days"
-msgstr[0] "%d dag"
-msgstr[1] "%d dage"
-
-#: src/humanize/time.py:174
-msgid "a month"
-msgstr "en måned"
-
-#: src/humanize/time.py:176
-#, python-format
-msgid "%d month"
-msgid_plural "%d months"
-msgstr[0] "%d måned"
-msgstr[1] "%d måneder"
-
-#: src/humanize/time.py:179
-msgid "a year"
-msgstr "et år"
-
-#: src/humanize/time.py:181 src/humanize/time.py:190
-#, python-format
-msgid "1 year, %d day"
-msgid_plural "1 year, %d days"
-msgstr[0] "1 år, %d dag"
-msgstr[1] "1 år, %d dage"
-
-#: src/humanize/time.py:184
-msgid "1 year, 1 month"
-msgstr "1 år, 1 måned"
-
-#: src/humanize/time.py:187
-#, python-format
-msgid "1 year, %d month"
-msgid_plural "1 year, %d months"
-msgstr[0] "1 år, %d måned"
-msgstr[1] "1 år, %d måneder"
-
-#: src/humanize/time.py:192
-#, python-format
-msgid "%d year"
-msgid_plural "%d years"
-msgstr[0] "%d år"
-msgstr[1] "%d år"
-
-#: src/humanize/time.py:228
-#, python-format
-msgid "%s from now"
-msgstr "%s fra nu"
-
-#: src/humanize/time.py:228
-#, python-format
-msgid "%s ago"
-msgstr "%s siden"
-
-#: src/humanize/time.py:232
-msgid "now"
-msgstr "nu"
-
-#: src/humanize/time.py:255
-msgid "today"
-msgstr "i dag"
-
-#: src/humanize/time.py:257
-msgid "tomorrow"
-msgstr "i morgen"
-
-#: src/humanize/time.py:259
-msgid "yesterday"
-msgstr "i går"
-
-#: src/humanize/time.py:545
-#, python-format
-msgid "%s and %s"
-msgstr "%s og %s"
diff --git a/src/humanize/locale/ru_RU/LC_MESSAGES/humanize.po b/src/humanize/locale/ru_RU/LC_MESSAGES/humanize.po
index b03aa5f..15813b7 100644
--- a/src/humanize/locale/ru_RU/LC_MESSAGES/humanize.po
+++ b/src/humanize/locale/ru_RU/LC_MESSAGES/humanize.po
@@ -123,90 +123,90 @@ msgstr "ый"
 #: src/humanize/number.py:140
 msgid "thousand"
 msgid_plural "thousand"
-msgstr[0] "тысяча"
-msgstr[1] "тысячи"
-msgstr[2] "тысяч"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
 
 #: src/humanize/number.py:141
 msgid "million"
 msgid_plural "million"
-msgstr[0] "миллион"
+msgstr[0] "миллиона"
 msgstr[1] "миллиона"
-msgstr[2] "миллионов"
+msgstr[2] "миллиона"
 
 #: src/humanize/number.py:142
 msgid "billion"
 msgid_plural "billion"
-msgstr[0] "миллиард"
+msgstr[0] "миллиарда"
 msgstr[1] "миллиарда"
-msgstr[2] "миллиардов"
+msgstr[2] "миллиарда"
 
 #: src/humanize/number.py:143
 msgid "trillion"
 msgid_plural "trillion"
-msgstr[0] "триллион"
+msgstr[0] "триллиона"
 msgstr[1] "триллиона"
-msgstr[2] "триллионов"
+msgstr[2] "триллиона"
 
 #: src/humanize/number.py:144
 msgid "quadrillion"
 msgid_plural "quadrillion"
-msgstr[0] "квадриллион"
+msgstr[0] "квадриллиона"
 msgstr[1] "квадриллиона"
-msgstr[2] "квадриллионов"
+msgstr[2] "квадриллиона"
 
 #: src/humanize/number.py:145
 msgid "quintillion"
 msgid_plural "quintillion"
-msgstr[0] "квинтиллион"
+msgstr[0] "квинтиллиона"
 msgstr[1] "квинтиллиона"
-msgstr[2] "квинтиллионов"
+msgstr[2] "квинтиллиона"
 
 #: src/humanize/number.py:146
 msgid "sextillion"
 msgid_plural "sextillion"
-msgstr[0] "сикстиллион"
+msgstr[0] "сикстиллиона"
 msgstr[1] "сикстиллиона"
-msgstr[2] "сикстиллионов"
+msgstr[2] "сикстиллиона"
 
 #: src/humanize/number.py:147
 msgid "septillion"
 msgid_plural "septillion"
-msgstr[0] "септиллион"
+msgstr[0] "септиллиона"
 msgstr[1] "септиллиона"
-msgstr[2] "септиллионов"
+msgstr[2] "септиллиона"
 
 #: src/humanize/number.py:148
 msgid "octillion"
 msgid_plural "octillion"
-msgstr[0] "октиллион"
+msgstr[0] "октиллиона"
 msgstr[1] "октиллиона"
-msgstr[2] "октиллионов"
+msgstr[2] "октиллиона"
 
 #: src/humanize/number.py:149
 msgid "nonillion"
 msgid_plural "nonillion"
-msgstr[0] "нониллион"
+msgstr[0] "нониллиона"
 msgstr[1] "нониллиона"
-msgstr[2] "нониллионов"
+msgstr[2] "нониллиона"
 
 #: src/humanize/number.py:150
 msgid "decillion"
 msgid_plural "decillion"
-msgstr[0] "децилион"
+msgstr[0] "децилиона"
 msgstr[1] "децилиона"
-msgstr[2] "децилионов"
+msgstr[2] "децилиона"
 
 #: src/humanize/number.py:151
 msgid "googol"
 msgid_plural "googol"
-msgstr[0] "гогол"
+msgstr[0] "гогола"
 msgstr[1] "гогола"
-msgstr[2] "гоголов"
+msgstr[2] "гогола"
 
 #: src/humanize/number.py:246
 msgid "zero"
-msgstr "ноль"
+msgstr ""
 
 #: src/humanize/number.py:247
 msgid "one"
@@ -248,17 +248,17 @@ msgstr "девять"
 #, python-format
 msgid "%d microsecond"
 msgid_plural "%d microseconds"
-msgstr[0] "%d микросекунда"
-msgstr[1] "%d микросекунды"
-msgstr[2] "%d микросекунд"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
 
 #: src/humanize/time.py:142
 #, python-format
 msgid "%d millisecond"
 msgid_plural "%d milliseconds"
-msgstr[0] "%d миллисекунда"
-msgstr[1] "%d миллисекунды"
-msgstr[2] "%d миллисекунд"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
 
 #: src/humanize/time.py:145 src/humanize/time.py:220
 msgid "a moment"
diff --git a/src/humanize/locale/uk_UA/LC_MESSAGES/humanize.po b/src/humanize/locale/uk_UA/LC_MESSAGES/humanize.po
index d74ca5f..871fd81 100644
--- a/src/humanize/locale/uk_UA/LC_MESSAGES/humanize.po
+++ b/src/humanize/locale/uk_UA/LC_MESSAGES/humanize.po
@@ -118,86 +118,86 @@ msgstr "ий"
 #: src/humanize/number.py:140
 msgid "thousand"
 msgid_plural "thousand"
-msgstr[0] "тисяча"
-msgstr[1] "тисячі"
-msgstr[2] "тисяч"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
 
 #: src/humanize/number.py:141
 msgid "million"
 msgid_plural "million"
-msgstr[0] "мільйон"
-msgstr[1] "мільйона"
+msgstr[0] "мільйонів"
+msgstr[1] "мільйонів"
 msgstr[2] "мільйонів"
 
 #: src/humanize/number.py:142
 msgid "billion"
 msgid_plural "billion"
-msgstr[0] "мільярд"
-msgstr[1] "мільярда"
+msgstr[0] "мільярдів"
+msgstr[1] "мільярдів"
 msgstr[2] "мільярдів"
 
 #: src/humanize/number.py:143
 msgid "trillion"
 msgid_plural "trillion"
-msgstr[0] "трильйон"
-msgstr[1] "трильйона"
+msgstr[0] "трильйонів"
+msgstr[1] "трильйонів"
 msgstr[2] "трильйонів"
 
 #: src/humanize/number.py:144
 msgid "quadrillion"
 msgid_plural "quadrillion"
-msgstr[0] "квадрильйон"
-msgstr[1] "квадрильйона"
+msgstr[0] "квадрильйонів"
+msgstr[1] "квадрильйонів"
 msgstr[2] "квадрильйонів"
 
 #: src/humanize/number.py:145
 msgid "quintillion"
 msgid_plural "quintillion"
-msgstr[0] "квинтиліон"
-msgstr[1] "квинтиліона"
+msgstr[0] "квинтиліонів"
+msgstr[1] "квинтиліонів"
 msgstr[2] "квинтиліонів"
 
 #: src/humanize/number.py:146
 msgid "sextillion"
 msgid_plural "sextillion"
-msgstr[0] "сикстильйон"
-msgstr[1] "сикстильйона"
+msgstr[0] "сикстильйонів"
+msgstr[1] "сикстильйонів"
 msgstr[2] "сикстильйонів"
 
 #: src/humanize/number.py:147
 msgid "septillion"
 msgid_plural "septillion"
-msgstr[0] "септильйон"
-msgstr[1] "септильйона"
+msgstr[0] "септильйонів"
+msgstr[1] "септильйонів"
 msgstr[2] "септильйонів"
 
 #: src/humanize/number.py:148
 msgid "octillion"
 msgid_plural "octillion"
-msgstr[0] "октильйон"
-msgstr[1] "октильйона"
+msgstr[0] "октильйонів"
+msgstr[1] "октильйонів"
 msgstr[2] "октильйонів"
 
 #: src/humanize/number.py:149
 msgid "nonillion"
 msgid_plural "nonillion"
-msgstr[0] "нонильйон"
-msgstr[1] "нонильйона"
+msgstr[0] "нонильйонів"
+msgstr[1] "нонильйонів"
 msgstr[2] "нонильйонів"
 
 #: src/humanize/number.py:150
 msgid "decillion"
 msgid_plural "decillion"
-msgstr[0] "децильйон"
-msgstr[1] "децильйона"
+msgstr[0] "децильйонів"
+msgstr[1] "децильйонів"
 msgstr[2] "децильйонів"
 
 #: src/humanize/number.py:151
 msgid "googol"
 msgid_plural "googol"
-msgstr[0] "гугол"
-msgstr[1] "гугла"
-msgstr[2] "гуглів"
+msgstr[0] "гугола"
+msgstr[1] "гугола"
+msgstr[2] "гугола"
 
 #: src/humanize/number.py:246
 msgid "zero"
@@ -244,8 +244,8 @@ msgstr "дев'ять"
 msgid "%d microsecond"
 msgid_plural "%d microseconds"
 msgstr[0] "%d мікросекунда"
-msgstr[1] "%d мікросекунди"
-msgstr[2] "%d мікросекунд"
+msgstr[1] "%d мікросекунд"
+msgstr[2] "%d мікросекунди"
 
 #: src/humanize/time.py:142
 #, fuzzy, python-format
@@ -253,7 +253,7 @@ msgid "%d millisecond"
 msgid_plural "%d milliseconds"
 msgstr[0] "%d мілісекунда"
 msgstr[1] "%d мілісекунди"
-msgstr[2] "%d мілісекунд"
+msgstr[2] "%d мілісекунди"
 
 #: src/humanize/time.py:145 src/humanize/time.py:220
 msgid "a moment"
@@ -261,14 +261,14 @@ msgstr "у цей момент"
 
 #: src/humanize/time.py:147
 msgid "a second"
-msgstr "секунда"
+msgstr "секунду"
 
 #: src/humanize/time.py:149
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d секунда"
-msgstr[1] "%d секунди"
+msgstr[1] "%d секунд"
 msgstr[2] "%d секунд"
 
 #: src/humanize/time.py:151
@@ -292,7 +292,7 @@ msgstr "година"
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d година"
-msgstr[1] "%d години"
+msgstr[1] "%d годин"
 msgstr[2] "%d годин"
 
 #: src/humanize/time.py:162
@@ -305,7 +305,7 @@ msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d день"
 msgstr[1] "%d дня"
-msgstr[2] "%d днів"
+msgstr[2] "%d дні"
 
 #: src/humanize/time.py:169
 msgid "a month"
@@ -380,4 +380,4 @@ msgstr "вчора"
 #: src/humanize/time.py:534
 #, python-format
 msgid "%s and %s"
-msgstr "%s й %s"
+msgstr ""
diff --git a/src/humanize/number.py b/src/humanize/number.py
index e0a7899..be66b84 100644
--- a/src/humanize/number.py
+++ b/src/humanize/number.py
@@ -6,10 +6,10 @@ import math
 import re
 from fractions import Fraction
 
-from .i18n import gettext as _
-from .i18n import ngettext
-from .i18n import ngettext_noop as NS_
-from .i18n import pgettext as P_
+from .i18n import _gettext as _
+from .i18n import _ngettext
+from .i18n import _ngettext_noop as NS_
+from .i18n import _pgettext as P_
 from .i18n import thousands_separator
 
 
@@ -201,12 +201,12 @@ def intword(value, format="%.1f"):
                 chopped = value / float(powers[ordinal])
                 singular, plural = human_powers[ordinal]
                 return (
-                    " ".join([format, ngettext(singular, plural, math.ceil(chopped))])
+                    " ".join([format, _ngettext(singular, plural, math.ceil(chopped))])
                 ) % chopped
             else:
                 singular, plural = human_powers[ordinal - 1]
                 return (
-                    " ".join([format, ngettext(singular, plural, math.ceil(chopped))])
+                    " ".join([format, _ngettext(singular, plural, math.ceil(chopped))])
                 ) % chopped
     return str(value)
 
diff --git a/src/humanize/time.py b/src/humanize/time.py
index f6b7fd6..249dfcb 100644
--- a/src/humanize/time.py
+++ b/src/humanize/time.py
@@ -7,11 +7,12 @@ These are largely borrowed from Django's `contrib.humanize`.
 
 import datetime as dt
 import math
-from enum import Enum
+import warnings
+from enum import Enum, EnumMeta
 from functools import total_ordering
 
-from .i18n import gettext as _
-from .i18n import ngettext
+from .i18n import _gettext as _
+from .i18n import _ngettext
 
 __all__ = [
     "naturaldelta",
@@ -23,7 +24,7 @@ __all__ = [
 
 
 @total_ordering
-class Unit(Enum):
+class _Unit(Enum):
     MICROSECONDS = 0
     MILLISECONDS = 1
     SECONDS = 2
@@ -39,11 +40,48 @@ class Unit(Enum):
         return NotImplemented
 
 
+class _UnitMeta(EnumMeta):
+    """Metaclass for an enum that emits deprecation warnings when accessed."""
+
+    def __getattribute__(self, name):
+        warnings.warn(
+            "`Unit` has been deprecated. "
+            "The enum is still available as the private member `_Unit`.",
+            DeprecationWarning,
+        )
+        return EnumMeta.__getattribute__(_Unit, name)
+
+    def __getitem__(cls, name):
+        warnings.warn(
+            "`Unit` has been deprecated. "
+            "The enum is still available as the private member `_Unit`.",
+            DeprecationWarning,
+        )
+        return _Unit.__getitem__(name)
+
+    def __call__(
+        cls, value, names=None, *, module=None, qualname=None, type=None, start=1
+    ):
+        warnings.warn(
+            "`Unit` has been deprecated. "
+            "The enum is still available as the private member `_Unit`.",
+            DeprecationWarning,
+        )
+        return _Unit.__call__(
+            value, names, module=module, qualname=qualname, type=type, start=start
+        )
+
+
+class Unit(Enum, metaclass=_UnitMeta):
+    # Temporary alias for _Unit to allow backwards-compatible usage.
+    pass
+
+
 def _now():
     return dt.datetime.now()
 
 
-def abs_timedelta(delta):
+def _abs_timedelta(delta):
     """Return an "absolute" value for a timedelta, always representing a time distance.
 
     Args:
@@ -58,7 +96,27 @@ def abs_timedelta(delta):
     return delta
 
 
-def date_and_delta(value, *, now=None):
+def abs_timedelta(delta):
+    """Return an "absolute" value for a timedelta, always representing a time distance.
+
+    Args:
+        delta (datetime.timedelta): Input timedelta.
+
+    Returns:
+        datetime.timedelta: Absolute timedelta.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_abs_timedelta`.
+    """
+    warnings.warn(
+        "`abs_timedelta` has been deprecated. "
+        "It is still available as the private member `_abs_timedelta`.",
+        DeprecationWarning,
+    )
+    return _abs_timedelta(delta)
+
+
+def _date_and_delta(value, *, now=None):
     """Turn a value into a date and a timedelta which represents how long ago it was.
 
     If that's not possible, return `(None, value)`.
@@ -78,7 +136,23 @@ def date_and_delta(value, *, now=None):
             date = now - delta
         except (ValueError, TypeError):
             return None, value
-    return date, abs_timedelta(delta)
+    return date, _abs_timedelta(delta)
+
+
+def date_and_delta(delta):
+    """Turn a value into a date and a timedelta which represents how long ago it was.
+
+    If that's not possible, return `(None, value)`.
+
+    WARNING: This function has been deprecated. It is still available as the private
+    member `_date_and_delta`.
+    """
+    warnings.warn(
+        "`date_and_delta` has been deprecated. "
+        "It is still available as the private member `_date_and_delta`.",
+        DeprecationWarning,
+    )
+    return _date_and_delta(delta)
 
 
 def naturaldelta(
@@ -114,12 +188,12 @@ def naturaldelta(
 
         assert naturaldelta(later, when=now) == "30 minutes"
     """
-    tmp = Unit[minimum_unit.upper()]
-    if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS):
+    tmp = _Unit[minimum_unit.upper()]
+    if tmp not in (_Unit.SECONDS, _Unit.MILLISECONDS, _Unit.MICROSECONDS):
         raise ValueError(f"Minimum unit '{minimum_unit}' not supported")
     minimum_unit = tmp
 
-    date, delta = date_and_delta(value, now=when)
+    date, delta = _date_and_delta(value, now=when)
     if date is None:
         return value
 
@@ -133,63 +207,63 @@ def naturaldelta(
 
     if not years and days < 1:
         if seconds == 0:
-            if minimum_unit == Unit.MICROSECONDS and delta.microseconds < 1000:
+            if minimum_unit == _Unit.MICROSECONDS and delta.microseconds < 1000:
                 return (
-                    ngettext("%d microsecond", "%d microseconds", delta.microseconds)
+                    _ngettext("%d microsecond", "%d microseconds", delta.microseconds)
                     % delta.microseconds
                 )
-            elif minimum_unit == Unit.MILLISECONDS or (
-                minimum_unit == Unit.MICROSECONDS
+            elif minimum_unit == _Unit.MILLISECONDS or (
+                minimum_unit == _Unit.MICROSECONDS
                 and 1000 <= delta.microseconds < 1_000_000
             ):
                 milliseconds = delta.microseconds / 1000
                 return (
-                    ngettext("%d millisecond", "%d milliseconds", milliseconds)
+                    _ngettext("%d millisecond", "%d milliseconds", milliseconds)
                     % milliseconds
                 )
             return _("a moment")
         elif seconds == 1:
             return _("a second")
         elif seconds < 60:
-            return ngettext("%d second", "%d seconds", seconds) % seconds
+            return _ngettext("%d second", "%d seconds", seconds) % seconds
         elif 60 <= seconds < 120:
             return _("a minute")
         elif 120 <= seconds < 3600:
             minutes = seconds // 60
-            return ngettext("%d minute", "%d minutes", minutes) % minutes
+            return _ngettext("%d minute", "%d minutes", minutes) % minutes
         elif 3600 <= seconds < 3600 * 2:
             return _("an hour")
         elif 3600 < seconds:
             hours = seconds // 3600
-            return ngettext("%d hour", "%d hours", hours) % hours
+            return _ngettext("%d hour", "%d hours", hours) % hours
     elif years == 0:
         if days == 1:
             return _("a day")
         if not use_months:
-            return ngettext("%d day", "%d days", days) % days
+            return _ngettext("%d day", "%d days", days) % days
         else:
             if not months:
-                return ngettext("%d day", "%d days", days) % days
+                return _ngettext("%d day", "%d days", days) % days
             elif months == 1:
                 return _("a month")
             else:
-                return ngettext("%d month", "%d months", months) % months
+                return _ngettext("%d month", "%d months", months) % months
     elif years == 1:
         if not months and not days:
             return _("a year")
         elif not months:
-            return ngettext("1 year, %d day", "1 year, %d days", days) % days
+            return _ngettext("1 year, %d day", "1 year, %d days", days) % days
         elif use_months:
             if months == 1:
                 return _("1 year, 1 month")
             else:
                 return (
-                    ngettext("1 year, %d month", "1 year, %d months", months) % months
+                    _ngettext("1 year, %d month", "1 year, %d months", months) % months
                 )
         else:
-            return ngettext("1 year, %d day", "1 year, %d days", days) % days
+            return _ngettext("1 year, %d day", "1 year, %d days", days) % days
     else:
-        return ngettext("%d year", "%d years", years) % years
+        return _ngettext("%d year", "%d years", years) % years
 
 
 def naturaltime(
@@ -218,7 +292,7 @@ def naturaltime(
         str: A natural representation of the input in a resolution that makes sense.
     """
     now = when or _now()
-    date, delta = date_and_delta(value, now=now)
+    date, delta = _date_and_delta(value, now=now)
     if date is None:
         return value
     # determine tense by value only if datetime/timedelta were passed
@@ -270,7 +344,7 @@ def naturaldate(value) -> str:
     except (OverflowError, ValueError):
         # Date arguments out of range
         return value
-    delta = abs_timedelta(value - dt.date.today())
+    delta = _abs_timedelta(value - dt.date.today())
     if delta.days >= 5 * 365 / 12:
         return naturalday(value, "%b %d %Y")
     return naturalday(value)
@@ -284,20 +358,20 @@ def _quotient_and_remainder(value, divisor, unit, minimum_unit, suppress):
     represent the remainder because it would require a unit smaller than the
     `minimum_unit`.
 
-    >>> from humanize.time import _quotient_and_remainder, Unit
-    >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.DAYS, [])
+    >>> from humanize.time import _quotient_and_remainder, _Unit
+    >>> _quotient_and_remainder(36, 24, _Unit.DAYS, _Unit.DAYS, [])
     (1.5, 0)
 
     If unit is in `suppress`, the quotient will be zero and the remainder will be the
     initial value. The idea is that if we cannot use `unit`, we are forced to use a
     lower unit so we cannot do the division.
 
-    >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [Unit.DAYS])
+    >>> _quotient_and_remainder(36, 24, _Unit.DAYS, _Unit.HOURS, [_Unit.DAYS])
     (0, 36)
 
     In other case return quotient and remainder as `divmod` would do it.
 
-    >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [])
+    >>> _quotient_and_remainder(36, 24, _Unit.DAYS, _Unit.HOURS, [])
     (1, 12)
 
     """
@@ -316,20 +390,20 @@ def _carry(value1, value2, ratio, unit, min_unit, suppress):
     (carry to right). The idea is that if we cannot represent `value1` we need to
     represent it in a lower unit.
 
-    >>> from humanize.time import _carry, Unit
-    >>> _carry(2, 6, 24, Unit.DAYS, Unit.SECONDS, [Unit.DAYS])
+    >>> from humanize.time import _carry, _Unit
+    >>> _carry(2, 6, 24, _Unit.DAYS, _Unit.SECONDS, [_Unit.DAYS])
     (0, 54)
 
     If the unit is the minimum unit, `value2` is divided by `ratio` and added to
     `value1` (carry to left). We assume that `value2` has a lower unit so we need to
     carry it to `value1`.
 
-    >>> _carry(2, 6, 24, Unit.DAYS, Unit.DAYS, [])
+    >>> _carry(2, 6, 24, _Unit.DAYS, _Unit.DAYS, [])
     (2.25, 0)
 
     Otherwise, just return the same input:
 
-    >>> _carry(2, 6, 24, Unit.DAYS, Unit.SECONDS, [])
+    >>> _carry(2, 6, 24, _Unit.DAYS, _Unit.SECONDS, [])
     (2, 6)
     """
     if unit == min_unit:
@@ -345,21 +419,21 @@ def _suitable_minimum_unit(min_unit, suppress):
 
     If not suppressed, return the same unit:
 
-    >>> from humanize.time import _suitable_minimum_unit, Unit
-    >>> _suitable_minimum_unit(Unit.HOURS, []).name
+    >>> from humanize.time import _suitable_minimum_unit, _Unit
+    >>> _suitable_minimum_unit(_Unit.HOURS, []).name
     'HOURS'
 
     But if suppressed, find a unit greather than the original one that is not
     suppressed:
 
-    >>> _suitable_minimum_unit(Unit.HOURS, [Unit.HOURS]).name
+    >>> _suitable_minimum_unit(_Unit.HOURS, [_Unit.HOURS]).name
     'DAYS'
 
-    >>> _suitable_minimum_unit(Unit.HOURS, [Unit.HOURS, Unit.DAYS]).name
+    >>> _suitable_minimum_unit(_Unit.HOURS, [_Unit.HOURS, _Unit.DAYS]).name
     'MONTHS'
     """
     if min_unit in suppress:
-        for unit in Unit:
+        for unit in _Unit:
             if unit > min_unit and unit not in suppress:
                 return unit
 
@@ -373,12 +447,12 @@ def _suitable_minimum_unit(min_unit, suppress):
 def _suppress_lower_units(min_unit, suppress):
     """Extend suppressed units (if any) with all units lower than the minimum unit.
 
-    >>> from humanize.time import _suppress_lower_units, Unit
-    >>> [x.name for x in sorted(_suppress_lower_units(Unit.SECONDS, [Unit.DAYS]))]
+    >>> from humanize.time import _suppress_lower_units, _Unit
+    >>> [x.name for x in sorted(_suppress_lower_units(_Unit.SECONDS, [_Unit.DAYS]))]
     ['MICROSECONDS', 'MILLISECONDS', 'DAYS']
     """
     suppress = set(suppress)
-    for u in Unit:
+    for u in _Unit:
         if u == min_unit:
             break
         suppress.add(u)
@@ -453,15 +527,15 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
 
     ```
     """
-    date, delta = date_and_delta(value)
+    date, delta = _date_and_delta(value)
     if date is None:
         return value
 
-    suppress = [Unit[s.upper()] for s in suppress]
+    suppress = [_Unit[s.upper()] for s in suppress]
 
     # Find a suitable minimum unit (it can be greater the one that the
     # user gave us if it is suppressed).
-    min_unit = Unit[minimum_unit.upper()]
+    min_unit = _Unit[minimum_unit.upper()]
     min_unit = _suitable_minimum_unit(min_unit, suppress)
     del minimum_unit
 
@@ -475,7 +549,7 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
     usecs = delta.microseconds
 
     MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS, MONTHS, YEARS = list(
-        Unit
+        _Unit
     )
 
     # Given DAYS compute YEARS and the remainder of DAYS as follows:
@@ -524,10 +598,10 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
     ]
 
     texts = []
-    for unit, fmt in zip(reversed(Unit), fmts):
+    for unit, fmt in zip(reversed(_Unit), fmts):
         singular_txt, plural_txt, value = fmt
         if value > 0 or (not texts and unit == min_unit):
-            fmt_txt = ngettext(singular_txt, plural_txt, value)
+            fmt_txt = _ngettext(singular_txt, plural_txt, value)
             if unit == min_unit and math.modf(value)[0] > 0:
                 fmt_txt = fmt_txt.replace("%d", format)
 


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d796115..604f874 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,7 +11,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["pypy-3.8", "3.6", "3.7", "3.8", "3.9", "3.10"]
+        python-version: ["pypy3", "3.6", "3.7", "3.8", "3.9", "3.10"]
         os: [ubuntu-latest, macos-latest, windows-latest]
         include:
           # Include new variables for Codecov
diff --git a/tests/test_time.py b/tests/test_time.py
index 19e0ce6..6328374 100644
--- a/tests/test_time.py
+++ b/tests/test_time.py
@@ -62,10 +62,10 @@ def test_date_and_delta():
     results = [(now - td(seconds=x), td(seconds=x)) for x in int_tests]
     for t in (int_tests, date_tests, td_tests):
         for arg, result in zip(t, results):
-            date, d = time.date_and_delta(arg)
+            date, d = time._date_and_delta(arg)
             assertEqualDatetime(date, result[0])
             assertEqualTimedelta(d, result[1])
-    assert time.date_and_delta("NaN") == (None, "NaN")
+    assert time._date_and_delta("NaN") == (None, "NaN")
 
 
 # Tests for the public interface of humanize.time
@@ -645,7 +645,7 @@ def test_precisedelta_bogus_call():
 
 
 def test_time_unit():
-    years, minutes = time.Unit["YEARS"], time.Unit["MINUTES"]
+    years, minutes = time._Unit["YEARS"], time._Unit["MINUTES"]
     assert minutes < years
     assert years > minutes
     assert minutes == minutes