diff --git a/docs/howto/specify-locale.md b/docs/howto/specify-locale.md index cfeb46d..1123898 100644 --- a/docs/howto/specify-locale.md +++ b/docs/howto/specify-locale.md @@ -1,6 +1,6 @@ # Specify a locale -`locale` is a two letter [ISO639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code that `git-revision-date-localized` uses to display dates in your preferred language. +`locale` is aa two letter [ISO639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code (f.e. `en`) or [5-letter language code with added territory/region/country](https://www.mkdocs.org/user-guide/localizing-your-theme/#supported-locales) (`en_US`) that `git-revision-date-localized` uses to display dates in your preferred language. For example: @@ -68,6 +68,7 @@ plugins: If no `locale` is specified anywhere, the fallback is English with the US date format (`en`). !!! info "Supported locales" + - When used in combination with `type: date` or `type: datetime`, translation is done using [babel](https://github.com/python-babel/babel) which supports [these locales](http://www.unicode.org/cldr/charts/latest/supplemental/territory_language_information.html) - When used in combination with `type: timeago` then [timeago.js](https://github.com/hustcc/timeago.js) is added to your website, which supports [these locales](https://github.com/hustcc/timeago.js/tree/master/src/lang). If you specify a locale not supported by timeago.js, the fallback is English (`en`) diff --git a/docs/options.md b/docs/options.md index 1f00e52..c0835d6 100644 --- a/docs/options.md +++ b/docs/options.md @@ -53,17 +53,19 @@ Default is `UTC`. Specify a time zone database name ([reference](https://en.wiki ## `locale` -Default is `None`. Specify a two letter [ISO639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code to display dates in your preferred language. Note this plugin supports many different ways to [specify the locale](howto/specify-locale.md), but if not specified anywhere the fallback is English (`en`). +Default is `None`. Specify a two letter [ISO639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code (f.e. `en`) or [5-letter language code with added territory/region/country](https://www.mkdocs.org/user-guide/localizing-your-theme/#supported-locales) (`en_US`) to display dates in your preferred language. Note this plugin supports many different ways to [specify the locale](howto/specify-locale.md), but if not specified _anywhere_ the fallback will be English (`en`). `locale` is used to translate timestamps to dates when `type: date` or `type: datetime` (using [babel](https://github.com/python-babel/babel)) as well as to translate datetimes to a relative timeago string when `type: timeago` (using [timeago.js](https://github.com/hustcc/timeago.js)). Example outputs: ```yaml -April 27, 2021 # `locale: en` with `type: date` (default) -April 27, 2021 13:11:28 # `locale: en` with `type: datetime` -2 weeks ago # `locale: en` with `type: timeago` -27 de marzo de 2021 # `locale: es` with `type: date` -27 de marzo de 2021 13:57:28 # `locale: es` with `type: datetime` -hace 2 semanas # `locale: es` with `type: timeago` +# `locale: en` +April 27, 2021 # with `type: date` (default) +April 27, 2021 13:11:28 # with `type: datetime` +2 weeks ago # with `type: timeago` +# `locale: es` +27 de marzo de 2021 # with `type: date` +27 de marzo de 2021 13:57:28 # with `type: datetime` +hace 2 semanas # with `type: timeago` ``` === ":octicons-file-code-16: mkdocs.yml" @@ -71,9 +73,13 @@ hace 2 semanas # `locale: es` with `type: timeago` ```yaml plugins: - git-revision-date-localized: - locale: en + locale: en_US ``` +!!! note when using `type: timeago` + + When using `type: timeago` then [timeago.js](https://github.com/hustcc/timeago.js) is added to your website, which supports [these locales](https://github.com/hustcc/timeago.js/tree/master/src/lang). If you specify a locale not supported by timeago.js, the fallback is English (`en`). It might happen that your specific locale is supported by babel (used by date formats) but not by timeago. In that case open an issue with this plugin. + ## `fallback_to_build_date` diff --git a/src/mkdocs_git_revision_date_localized_plugin/dates.py b/src/mkdocs_git_revision_date_localized_plugin/dates.py index 4eafe7e..3fd4dd6 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/dates.py +++ b/src/mkdocs_git_revision_date_localized_plugin/dates.py @@ -1,4 +1,4 @@ -from babel.dates import format_date, get_timezone +from babel.dates import format_date, get_timezone, format_datetime from datetime import datetime, timezone from typing import Any, Dict @@ -36,5 +36,56 @@ def get_date_formats( "iso_date": loc_revision_date.strftime("%Y-%m-%d"), "iso_datetime": loc_revision_date.strftime("%Y-%m-%d %H:%M:%S"), "timeago": '' % (loc_revision_date.isoformat(), locale), - "custom": loc_revision_date.strftime(custom_format), + "custom": format_datetime(loc_revision_date, format=strftime_to_babel_format(custom_format), locale=locale), } + + +def strftime_to_babel_format(fmt: str) -> str: + """ + Convert strftime format string to Babel format pattern. + + Args: + fmt (str): strftime format string + + Returns: + str: Babel format pattern + """ + # Dictionary mapping strftime directives to Babel format patterns + mapping = { + '%a': 'EEE', # Weekday abbreviated + '%A': 'EEEE', # Weekday full + '%b': 'MMM', # Month abbreviated + '%B': 'MMMM', # Month full + '%c': '', # Locale's date and time (not directly mappable) + '%d': 'dd', # Day of month zero-padded + '%-d': 'd', # Day of month + '%e': 'd', # Day of month space-padded + '%f': 'SSSSSS', # Microsecond + '%H': 'HH', # Hour 24h zero-padded + '%-H': 'H', # Hour 24h + '%I': 'hh', # Hour 12h zero-padded + '%-I': 'h', # Hour 12h + '%j': 'DDD', # Day of year + '%m': 'MM', # Month zero-padded + '%-m': 'M', # Month + '%M': 'mm', # Minute zero-padded + '%-M': 'm', # Minute + '%p': 'a', # AM/PM + '%S': 'ss', # Second zero-padded + '%-S': 's', # Second + '%w': 'e', # Weekday as number + '%W': 'w', # Week of year + '%x': '', # Locale's date (not directly mappable) + '%X': '', # Locale's time (not directly mappable) + '%y': 'yy', # Year without century + '%Y': 'yyyy', # Year with century + '%z': 'Z', # UTC offset + '%Z': 'z', # Timezone name + '%%': '%' # Literal % + } + + result = fmt + for strftime_code, babel_code in mapping.items(): + result = result.replace(strftime_code, babel_code) + + return result \ No newline at end of file diff --git a/src/mkdocs_git_revision_date_localized_plugin/plugin.py b/src/mkdocs_git_revision_date_localized_plugin/plugin.py index a33f96b..49747da 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/src/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -81,7 +81,7 @@ def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: # Get locale from plugin configuration plugin_locale = self.config.get("locale", None) - # theme locale + # Get locale from theme configuration if "theme" in config and "language" in config.get("theme"): custom_theme = config.get("theme") theme_locale = ( @@ -96,7 +96,6 @@ def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: custom_theme.locale if Version(mkdocs_version) >= Version("1.6.0") else custom_theme._vars.get("locale") ) logging.debug("Locale '%s' extracted from the custom theme: '%s'" % (theme_locale, custom_theme.name)) - else: theme_locale = None logging.debug("No locale found in theme configuration (or no custom theme set)") @@ -116,9 +115,6 @@ def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: # Validate locale locale_set = str(locale_set) - assert len(locale_set) == 2, ( - "locale must be a 2 letter code, see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" - ) # set locale also in plugin configuration self.config["locale"] = locale_set @@ -173,7 +169,6 @@ def on_page_markdown(self, markdown: str, page: Page, config: config_options.Con # First prio is use mkdocs-static-i18n locale if set try: locale = page.file.locale - except AttributeError: locale = None @@ -183,18 +178,12 @@ def on_page_markdown(self, markdown: str, page: Page, config: config_options.Con locale = page.meta["locale"] # Finally, if no page locale set, we take the locale determined on_config() + # (fourth prio is plugin configuration) + # (firth prio is theme configuration) + # (sixth prio is fallback to English) if not locale: locale = self.config.get("locale") - # MkDocs supports 2-letter and 5-letter locales - # https://www.mkdocs.org/user-guide/localizing-your-theme/#supported-locales - # We need the 2 letter variant - if len(locale) == 5: - locale = locale[:2] - assert len(locale) == 2, ( - "locale must be a 2 letter code, see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" - ) - # Retrieve git commit timestamp # Except for generated pages (f.e. by mkdocs-gen-files plugin) if getattr(page.file, "generated_by", None): diff --git a/tests/test_dates.py b/tests/test_dates.py index bbe0e94..c98750c 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -1,7 +1,7 @@ import pytest from datetime import datetime, timezone from babel.dates import get_timezone - +from babel.core import UnknownLocaleError from mkdocs_git_revision_date_localized_plugin.dates import get_date_formats @@ -18,6 +18,37 @@ def test_get_dates(): } assert get_date_formats(0) == expected_output + # Test with 4-letter locale + new_expected_output = expected_output.copy() + new_expected_output["timeago"] = '' + assert get_date_formats(0, locale="en_US") == new_expected_output + + # Test with different locale + expected_output = { + "date": "1 janvier 1970", + "datetime": "1 janvier 1970 00:00:00", + "iso_date": "1970-01-01", + "iso_datetime": "1970-01-01 00:00:00", + "timeago": '', + "custom": "01. janvier 1970" + } + assert get_date_formats(0, locale="fr") == expected_output + + # Test with pt_BR locale + expected_output = { + "date": "1 de janeiro de 1970", + "datetime": "1 de janeiro de 1970 00:00:00", + "iso_date": "1970-01-01", + "iso_datetime": "1970-01-01 00:00:00", + "timeago": '', + "custom": "01. janeiro 1970" + } + assert get_date_formats(0, locale="pt_BR") == expected_output + + # Test with non-existing locale + with pytest.raises(UnknownLocaleError): + get_date_formats(0, locale="abcd") + # Test with custom arguments expected_output = { "date": "January 1, 1970",