diff --git a/src/pendulum/locales/locale.py b/src/pendulum/locales/locale.py index 951ba8be..70fd397b 100644 --- a/src/pendulum/locales/locale.py +++ b/src/pendulum/locales/locale.py @@ -53,7 +53,11 @@ def normalize_locale(cls, locale: str) -> str: if m: return f"{m.group(1).lower()}_{m.group(2).lower()}" else: - return locale.lower() + lower = locale.lower() + if lower == 'ua': + # Backward compatibility. Ukrainian was 'ua' initially. + lower = 'uk' + return lower def get(self, key: str, default: Any | None = None) -> Any: if key in self._key_cache: diff --git a/src/pendulum/locales/ua/locale.py b/src/pendulum/locales/ua/locale.py deleted file mode 100644 index b7435832..00000000 --- a/src/pendulum/locales/ua/locale.py +++ /dev/null @@ -1,281 +0,0 @@ -from __future__ import annotations - -from pendulum.locales.ua.custom import translations as custom_translations - - -""" -ua locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "few" - if ( - ( - (0 == 0 and (0 == 0)) - and ((n % 10) == (n % 10) and ((n % 10) >= 2 and (n % 10) <= 4)) - ) - and (not ((n % 100) == (n % 100) and ((n % 100) >= 12 and (n % 100) <= 14))) - ) - else "many" - if ( - ( - ((0 == 0 and (0 == 0)) and ((n % 10) == (n % 10) and ((n % 10) == 0))) - or ( - (0 == 0 and (0 == 0)) - and ((n % 10) == (n % 10) and ((n % 10) >= 5 and (n % 10) <= 9)) - ) - ) - or ( - (0 == 0 and (0 == 0)) - and ((n % 100) == (n % 100) and ((n % 100) >= 11 and (n % 100) <= 14)) - ) - ) - else "one" - if ( - ((0 == 0 and (0 == 0)) and ((n % 10) == (n % 10) and ((n % 10) == 1))) - and (not ((n % 100) == (n % 100) and ((n % 100) == 11))) - ) - else "other", - "ordinal": lambda n: "other", - "translations": { - "days": { - "abbreviated": { - 0: "пн", - 1: "вт", - 2: "ср", - 3: "чт", - 4: "пт", - 5: "сб", - 6: "нд", - }, - "narrow": {0: "пн", 1: "вт", 2: "ср", 3: "чт", 4: "пт", 5: "сб", 6: "нд"}, - "short": {0: "пн", 1: "вт", 2: "ср", 3: "чт", 4: "пт", 5: "сб", 6: "нд"}, - "wide": { - 0: "понеділок", - 1: "вівторок", - 2: "середа", - 3: "четвер", - 4: "п'ятниця", - 5: "субота", - 6: "неділя", - }, - }, - "months": { - "abbreviated": { - 1: "січ.", - 2: "лют.", - 3: "бер.", - 4: "квіт.", - 5: "трав.", - 6: "черв.", - 7: "лип.", - 8: "серп.", - 9: "вер.", - 10: "жовт.", - 11: "лист.", - 12: "груд.", - }, - "narrow": { - 1: "С", - 2: "Л", - 3: "Б", - 4: "К", - 5: "Т", - 6: "Ч", - 7: "Л", - 8: "С", - 9: "В", - 10: "Ж", - 11: "Л", - 12: "Г", - }, - "wide": { - 1: "січня", - 2: "лютого", - 3: "березня", - 4: "квітня", - 5: "травня", - 6: "червня", - 7: "липня", - 8: "серпня", - 9: "вересня", - 10: "жовтня", - 11: "листопада", - 12: "грудня", - }, - }, - "units": { - "year": { - "one": "{0} рік", - "few": "{0} роки", - "many": "{0} років", - "other": "{0} роки", - }, - "month": { - "one": "{0} місяць", - "few": "{0} місяця", - "many": "{0} місяців", - "other": "{0} місяці", - }, - "week": { - "one": "{0} тиждень", - "few": "{0} тижня", - "many": "{0} тижнів", - "other": "{0} тижні", - }, - "day": { - "one": "{0} день", - "few": "{0} дні", - "many": "{0} днів", - "other": "{0} дні", - }, - "hour": { - "one": "{0} година", - "few": "{0} години", - "many": "{0} годин", - "other": "{0} години", - }, - "minute": { - "one": "{0} хвилина", - "few": "{0} хвилини", - "many": "{0} хвилин", - "other": "{0} хвилини", - }, - "second": { - "one": "{0} секунда", - "few": "{0} секунди", - "many": "{0} секунд", - "other": "{0} секунди", - }, - "microsecond": { - "one": "{0} мікросекунда", - "few": "{0} мікросекунди", - "many": "{0} мікросекунд", - "other": "{0} мікросекунд", - }, - }, - "relative": { - "year": { - "future": { - "other": "за {0} роки", - "one": "за {0} рік", - "few": "за {0} роки", - "many": "за {0} років", - }, - "past": { - "other": "{0} роки тому", - "one": "{0} рік тому", - "few": "{0} роки тому", - "many": "{0} років тому", - }, - }, - "month": { - "future": { - "other": "за {0} місяці", - "one": "за {0} місяць", - "few": "за {0} місяця", - "many": "за {0} місяців", - }, - "past": { - "other": "{0} місяці тому", - "one": "{0} місяц тому", - "few": "{0} місяця тому", - "many": "{0} місяців тому", - }, - }, - "week": { - "future": { - "other": "за {0} тижні", - "one": "за {0} тиждень", - "few": "за {0} тижня", - "many": "за {0} тижднів", - }, - "past": { - "other": "{0} тижні тому", - "one": "{0} тиждень тому", - "few": "{0} тижня тому", - "many": "{0} тижнів тому", - }, - }, - "day": { - "future": { - "other": "за {0} дні", - "one": "за {0} день", - "few": "за {0} дні", - "many": "за {0} днів", - }, - "past": { - "other": "{0} дні тому", - "one": "{0} день тому", - "few": "{0} дні тому", - "many": "{0} днів тому", - }, - }, - "hour": { - "future": { - "other": "за {0} години", - "one": "за {0} година", - "few": "за {0} години", - "many": "за {0} годин", - }, - "past": { - "other": "{0} години тому", - "one": "{0} година тому", - "few": "{0} години тому", - "many": "{0} годин тому", - }, - }, - "minute": { - "future": { - "other": "за {0} хвилини", - "one": "за {0} хвилина", - "few": "за {0} хвилини", - "many": "за {0} хвилин", - }, - "past": { - "other": "{0} хвилини тому", - "one": "{0} хвилина тому", - "few": "{0} хвилини тому", - "many": "{0} хвилин тому", - }, - }, - "second": { - "future": { - "other": "за {0} секунди", - "one": "за {0} секунду", - "few": "за {0} секунди", - "many": "за {0} секунд", - }, - "past": { - "other": "{0} секунди тому", - "one": "{0} секунду тому", - "few": "{0} секунди тому", - "many": "{0} секунд тому", - }, - }, - }, - "day_periods": { - "midnight": "опівночі", - "am": "AM", - "noon": "полудень", - "pm": "PM", - "morning1": "ранку", - "morning2": "до півдня", - "afternoon1": "дня", - "afternoon2": "пополуднє", - "evening1": "ввечері", - "evening2": "увечері", - "night1": "в ніч", - }, - "week_data": { - "min_days": 1, - "first_day": 0, - "weekend_start": 5, - "weekend_end": 6, - }, - }, - "custom": custom_translations, -} diff --git a/src/pendulum/locales/ua/__init__.py b/src/pendulum/locales/uk/__init__.py similarity index 100% rename from src/pendulum/locales/ua/__init__.py rename to src/pendulum/locales/uk/__init__.py diff --git a/src/pendulum/locales/ua/custom.py b/src/pendulum/locales/uk/custom.py similarity index 53% rename from src/pendulum/locales/ua/custom.py rename to src/pendulum/locales/uk/custom.py index ea96d8d0..2d558432 100644 --- a/src/pendulum/locales/ua/custom.py +++ b/src/pendulum/locales/uk/custom.py @@ -1,23 +1,23 @@ -""" -ua custom locale file. -""" -from __future__ import annotations - - -translations = { - "units": {"few_second": "кілька секунд"}, - # Relative time - "ago": "{} тому", - "from_now": "за {}", - "after": "{0} посіля", - "before": "{0} до", - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "L": "DD.MM.YYYY", - "LL": "D MMMM YYYY р.", - "LLL": "D MMMM YYYY р., HH:mm", - "LLLL": "dddd, D MMMM YYYY р., HH:mm", - }, -} +""" +uk custom locale file. +""" +from __future__ import annotations + + +translations = { + "units": {"few_second": "кілька секунд"}, + # Relative time + "ago": "{} тому", + "from_now": "через {}", + "after": "через {0}", + "before": "{0} тому", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "L": "DD.MM.YYYY", + "LL": "D MMMM YYYY", + "LLL": "D MMMM YYYY, HH:mm", + "LLLL": "ddd, D MMMM YYYY, HH:mm", + }, +} diff --git a/src/pendulum/locales/uk/locale.py b/src/pendulum/locales/uk/locale.py new file mode 100644 index 00000000..fc1da3e5 --- /dev/null +++ b/src/pendulum/locales/uk/locale.py @@ -0,0 +1,257 @@ +from .custom import translations as custom_translations + + +""" +uk locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + 'plural': lambda n: 'few' if (((0 == 0 and ((0 == 0))) and ((n % 10) == (n % 10) and (((n % 10) >= 2 and (n % 10) <= 4)))) and (not ((n % 100) == (n % 100) and (((n % 100) >= 12 and (n % 100) <= 14))))) else 'many' if ((((0 == 0 and ((0 == 0))) and ((n % 10) == (n % 10) and (((n % 10) == 0)))) or ((0 == 0 and ((0 == 0))) and ((n % 10) == (n % 10) and (((n % 10) >= 5 and (n % 10) <= 9))))) or ((0 == 0 and ((0 == 0))) and ((n % 100) == (n % 100) and (((n % 100) >= 11 and (n % 100) <= 14))))) else 'one' if (((0 == 0 and ((0 == 0))) and ((n % 10) == (n % 10) and (((n % 10) == 1)))) and (not ((n % 100) == (n % 100) and (((n % 100) == 11))))) else 'other', + 'ordinal': lambda n: 'few' if (((n % 10) == (n % 10) and (((n % 10) == 3))) and (not ((n % 100) == (n % 100) and (((n % 100) == 13))))) else 'other', + 'translations': { + 'days': { + 'abbreviated': { + 0: 'пн', + 1: 'вт', + 2: 'ср', + 3: 'чт', + 4: 'пт', + 5: 'сб', + 6: 'нд', + }, + 'narrow': { + 0: 'П', + 1: 'В', + 2: 'С', + 3: 'Ч', + 4: 'П', + 5: 'С', + 6: 'Н', + }, + 'short': { + 0: 'пн', + 1: 'вт', + 2: 'ср', + 3: 'чт', + 4: 'пт', + 5: 'сб', + 6: 'нд', + }, + 'wide': { + 0: 'понеділок', + 1: 'вівторок', + 2: 'середу', + 3: 'четвер', + 4: 'пʼятницю', + 5: 'суботу', + 6: 'неділю', + }, + }, + 'months': { + 'abbreviated': { + 1: 'січ.', + 2: 'лют.', + 3: 'бер.', + 4: 'квіт.', + 5: 'трав.', + 6: 'черв.', + 7: 'лип.', + 8: 'серп.', + 9: 'вер.', + 10: 'жовт.', + 11: 'лист.', + 12: 'груд.', + }, + 'narrow': { + 1: 'с', + 2: 'л', + 3: 'б', + 4: 'к', + 5: 'т', + 6: 'ч', + 7: 'л', + 8: 'с', + 9: 'в', + 10: 'ж', + 11: 'л', + 12: 'г', + }, + 'wide': { + 1: 'січня', + 2: 'лютого', + 3: 'березня', + 4: 'квітня', + 5: 'травня', + 6: 'червня', + 7: 'липня', + 8: 'серпня', + 9: 'вересня', + 10: 'жовтня', + 11: 'листопада', + 12: 'грудня', + }, + }, + 'units': { + 'year': { + 'one': '{0} рік', + 'few': '{0} роки', + 'many': '{0} років', + 'other': '{0} року', + }, + 'month': { + 'one': '{0} місяць', + 'few': '{0} місяці', + 'many': '{0} місяців', + 'other': '{0} місяця', + }, + 'week': { + 'one': '{0} тиждень', + 'few': '{0} тижні', + 'many': '{0} тижнів', + 'other': '{0} тижня', + }, + 'day': { + 'one': '{0} день', + 'few': '{0} дні', + 'many': '{0} днів', + 'other': '{0} дня', + }, + 'hour': { + 'one': '{0} година', + 'few': '{0} години', + 'many': '{0} годин', + 'other': '{0} години', + }, + 'minute': { + 'one': '{0} хвилина', + 'few': '{0} хвилини', + 'many': '{0} хвилин', + 'other': '{0} хвилини', + }, + 'second': { + 'one': '{0} секунда', + 'few': '{0} секунди', + 'many': '{0} секунд', + 'other': '{0} секунди', + }, + 'microsecond': { + 'one': '{0} мікросекунда', + 'few': '{0} мікросекунди', + 'many': '{0} мікросекунд', + 'other': '{0} мікросекунди', + }, + }, + 'relative': { + 'year': { + 'future': { + 'other': 'через {0} року', + 'one': 'через {0} рік', + 'few': 'через {0} роки', + 'many': 'через {0} років', + }, + 'past': { + 'other': '{0} року тому', + 'one': '{0} рік тому', + 'few': '{0} роки тому', + 'many': '{0} років тому', + }, + }, + 'month': { + 'future': { + 'other': 'через {0} місяця', + 'one': 'через {0} місяць', + 'few': 'через {0} місяці', + 'many': 'через {0} місяців', + }, + 'past': { + 'other': '{0} місяця тому', + 'one': '{0} місяць тому', + 'few': '{0} місяці тому', + 'many': '{0} місяців тому', + }, + }, + 'week': { + 'future': { + 'other': 'через {0} тижня', + 'one': 'через {0} тиждень', + 'few': 'через {0} тижні', + 'many': 'через {0} тижнів', + }, + 'past': { + 'other': '{0} тижня тому', + 'one': '{0} тиждень тому', + 'few': '{0} тижні тому', + 'many': '{0} тижнів тому', + }, + }, + 'day': { + 'future': { + 'other': 'через {0} дня', + 'one': 'через {0} день', + 'few': 'через {0} дні', + 'many': 'через {0} днів', + }, + 'past': { + 'other': '{0} дня тому', + 'one': '{0} день тому', + 'few': '{0} дні тому', + 'many': '{0} днів тому', + }, + }, + 'hour': { + 'future': { + 'other': 'через {0} години', + 'one': 'через {0} годину', + 'few': 'через {0} години', + 'many': 'через {0} годин', + }, + 'past': { + 'other': '{0} години тому', + 'one': '{0} годину тому', + 'few': '{0} години тому', + 'many': '{0} годин тому', + }, + }, + 'minute': { + 'future': { + 'other': 'через {0} хвилини', + 'one': 'через {0} хвилину', + 'few': 'через {0} хвилини', + 'many': 'через {0} хвилин', + }, + 'past': { + 'other': '{0} хвилини тому', + 'one': '{0} хвилину тому', + 'few': '{0} хвилини тому', + 'many': '{0} хвилин тому', + }, + }, + 'second': { + 'future': { + 'other': 'через {0} секунди', + 'one': 'через {0} секунду', + 'few': 'через {0} секунди', + 'many': 'через {0} секунд', + }, + 'past': { + 'other': '{0} секунди тому', + 'one': '{0} секунду тому', + 'few': '{0} секунди тому', + 'many': '{0} секунд тому', + }, + }, + }, + 'day_periods': { + }, + 'week_data': { + 'min_days': 1, + 'first_day': 0, + 'weekend_start': 5, + 'weekend_end': 6, + }, + }, + 'custom': custom_translations +} diff --git a/tests/localization/test_uk.py b/tests/localization/test_uk.py new file mode 100644 index 00000000..a57f26d6 --- /dev/null +++ b/tests/localization/test_uk.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import pendulum + + +locale = "uk" + + +def test_diff_for_humans(): + with pendulum.travel_to(pendulum.datetime(2026, 4, 3), freeze=True): + diff_for_humans() + + +def diff_for_humans(): + d = pendulum.now().subtract(seconds=1) + assert d.diff_for_humans(locale=locale) == "кілька секунд тому" + + # Backward compatibility. + assert d.diff_for_humans(locale=locale) == d.diff_for_humans(locale='ua') + + d = pendulum.now().subtract(seconds=2) + assert d.diff_for_humans(locale=locale) == "кілька секунд тому" + + d = pendulum.now().subtract(seconds=21) + assert d.diff_for_humans(locale=locale) == "21 секунду тому" + + d = pendulum.now().subtract(seconds=22) + assert d.diff_for_humans(locale=locale) == "22 секунди тому" + + d = pendulum.now().subtract(seconds=25) + assert d.diff_for_humans(locale=locale) == "25 секунд тому" + + d = pendulum.now().subtract(minutes=1) + assert d.diff_for_humans(locale=locale) == "1 хвилину тому" + + d = pendulum.now().subtract(minutes=2) + assert d.diff_for_humans(locale=locale) == "2 хвилини тому" + + d = pendulum.now().subtract(minutes=5) + assert d.diff_for_humans(locale=locale) == "5 хвилин тому" + + d = pendulum.now().subtract(hours=1) + assert d.diff_for_humans(locale=locale) == "1 годину тому" + + d = pendulum.now().subtract(hours=2) + assert d.diff_for_humans(locale=locale) == "2 години тому" + + d = pendulum.now().subtract(hours=5) + assert d.diff_for_humans(locale=locale) == "5 годин тому" + + d = pendulum.now().subtract(days=1) + assert d.diff_for_humans(locale=locale) == "1 день тому" + + d = pendulum.now().subtract(days=2) + assert d.diff_for_humans(locale=locale) == "2 дні тому" + + d = pendulum.now().subtract(weeks=1) + assert d.diff_for_humans(locale=locale) == "1 тиждень тому" + + d = pendulum.now().subtract(weeks=2) + assert d.diff_for_humans(locale=locale) == "2 тижні тому" + + d = pendulum.now().subtract(months=1) + assert d.diff_for_humans(locale=locale) == "1 місяць тому" + + d = pendulum.now().subtract(months=2) + assert d.diff_for_humans(locale=locale) == "2 місяці тому" + + d = pendulum.now().subtract(months=5) + assert d.diff_for_humans(locale=locale) == "5 місяців тому" + + d = pendulum.now().subtract(years=1) + assert d.diff_for_humans(locale=locale) == "1 рік тому" + + d = pendulum.now().subtract(years=2) + assert d.diff_for_humans(locale=locale) == "2 роки тому" + + d = pendulum.now().subtract(years=5) + assert d.diff_for_humans(locale=locale) == "5 років тому" + + d = pendulum.now().add(seconds=1) + assert d.diff_for_humans(locale=locale) == "через кілька секунд" + + d = pendulum.now().add(seconds=1) + d2 = pendulum.now() + assert d.diff_for_humans(d2, locale=locale) == "через кілька секунд" + assert d2.diff_for_humans(d, locale=locale) == "кілька секунд тому" + + assert d.diff_for_humans(d2, True, locale=locale) == "кілька секунд" + assert d2.diff_for_humans(d.add(seconds=1), True, locale=locale) == "кілька секунд" + + d = pendulum.now().add(seconds=20) + d2 = pendulum.now() + assert d.diff_for_humans(d2, locale=locale) == "через 20 секунд" + assert d2.diff_for_humans(d, locale=locale) == "20 секунд тому" + + d = pendulum.now().add(seconds=10) + d2 = pendulum.now() + assert d.diff_for_humans(d2, True, locale=locale) == "кілька секунд" + assert d2.diff_for_humans(d.add(seconds=1), True, locale=locale) == "11 секунд" + + +def test_format(): + d = pendulum.datetime(2026, 4, 3, 1, 2, 5, 123456) + assert d.format("dddd", locale=locale) == "пʼятницю" # Suboptimal, should be п'ятниця, but clock defines it like that. + assert d.format("ddd", locale=locale) == "пт" + assert d.format("MMMM", locale=locale) == "квітня" + assert d.format("MMM", locale=locale) == "квіт." + assert d.format("A", locale=locale) == "" + assert d.format("Qo", locale=locale) == "2" + assert d.format("Mo", locale=locale) == "4" + assert d.format("Do", locale=locale) == "3" + + assert d.format("LT", locale=locale) == "01:02" + assert d.format("LTS", locale=locale) == "01:02:05" + assert d.format("L", locale=locale) == "03.04.2026" + assert d.format("LL", locale=locale) == "3 квітня 2026" + assert d.format("LLL", locale=locale) == "3 квітня 2026, 01:02" + assert d.format("LLLL", locale=locale) == "пт, 3 квітня 2026, 01:02" # See note about dddd above.