Skip to content

Support 5 letter locale settings #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/howto/specify-locale.md
Original file line number Diff line number Diff line change
@@ -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:

Expand Down Expand Up @@ -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`)
22 changes: 14 additions & 8 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,33 @@ 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"

```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`

Expand Down
55 changes: 53 additions & 2 deletions src/mkdocs_git_revision_date_localized_plugin/dates.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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": '<span class="timeago" datetime="%s" locale="%s"></span>' % (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
19 changes: 4 additions & 15 deletions src/mkdocs_git_revision_date_localized_plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand All @@ -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)")
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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):
Expand Down
33 changes: 32 additions & 1 deletion tests/test_dates.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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"] = '<span class="timeago" datetime="1970-01-01T00:00:00+00:00" locale="en_US"></span>'
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": '<span class="timeago" datetime="1970-01-01T00:00:00+00:00" locale="fr"></span>',
"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": '<span class="timeago" datetime="1970-01-01T00:00:00+00:00" locale="pt_BR"></span>',
"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",
Expand Down
Loading