Repository: timvink/mkdocs-git-revision-date-localized-plugin

Issue:
Formatted date is not localized
## Reproduce

Lets' use the [fixtures used in tests](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/blob/4e36cbee97dce3895f1e6e272fb5b65efa1f32b2/tests/basic_setup/mkdocs_locale.yml), just moving from `nl` to `fr` to see more obvious difference:

1. set locale in config:

```yaml
site_name: test gitrevisiondatelocalized_plugin
use_directory_urls: true

locale: fr

plugins:
    - search
    - git-revision-date-localized
```

2. Serve site : `mkdocs serve -f .\tests\basic_setup\mkdocs_locale.yml`
3. Date is not localized:

![image](https://user-images.githubusercontent.com/1596222/77743639-ab149400-7018-11ea-8f25-bf41b155af1c.png)

## Expected result

![image](https://user-images.githubusercontent.com/1596222/77743734-ced7da00-7018-11ea-820b-8e888bab5b2f.png)

## Debug

The error is located in this line:

https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/blob/2f8dc73ffc033d68e310441f0f8fc79ba27e6858/mkdocs_git_revision_date_localized_plugin/util.py#L81

Should pass the inherited `locale`:

```python
return self._date_formats(unix_timestamp=unix_timestamp, locale=locale)
```

Discussion:

Patch:
diff --git a/mkdocs_git_revision_date_localized_plugin/plugin.py b/mkdocs_git_revision_date_localized_plugin/plugin.py
index 53198b6..6ed10b7 100644
--- a/mkdocs_git_revision_date_localized_plugin/plugin.py
+++ b/mkdocs_git_revision_date_localized_plugin/plugin.py
@@ -1,4 +1,5 @@
 # standard lib
+import logging
 import re
 
 # 3rd party
@@ -7,18 +8,17 @@ from mkdocs.plugins import BasePlugin
 from mkdocs.structure.nav import Page
 
 # package modules
-from .util import Util
+from mkdocs_git_revision_date_localized_plugin.util import Util
 
 
 class GitRevisionDateLocalizedPlugin(BasePlugin):
     config_scheme = (
         ("fallback_to_build_date", config_options.Type(bool, default=False)),
-        ("locale", config_options.Type(str, default="")),
+        ("locale", config_options.Type(str, default=None)),
         ("type", config_options.Type(str, default="date")),
     )
 
     def __init__(self):
-        self.locale = "en"
         self.util = Util()
 
     def on_config(self, config: config_options.Config) -> dict:
@@ -37,24 +37,56 @@ class GitRevisionDateLocalizedPlugin(BasePlugin):
             dict: global configuration object
         """
 
-        # Get locale settings
-        mkdocs_locale = config.get("locale", "")
-        plugin_locale = self.config["locale"]
-        theme_locale = vars(config["theme"]).get("_vars", {}).get("locale", "")
-        if theme_locale == "":
-            theme_locale = vars(config["theme"]).get("_vars", {}).get("language", "")
+        # Get locale settings - might be added in future mkdocs versions
+        # see: https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/issues/24
+        mkdocs_locale = config.get("locale", None)
+
+        # Get locale from plugin configuration
+        plugin_locale = self.config.get("locale", None)
+
+        # theme locale
+        if "theme" in config and "locale" in config.get("theme"):
+            custom_theme = config.get("theme")
+            theme_locale = custom_theme._vars.get("locale")
+            logging.debug(
+                "Locale '%s' extracted from the custom theme: '%s'"
+                % (theme_locale, custom_theme.name)
+            )
+        elif "theme" in config and "language" in config.get("theme"):
+            custom_theme = config.get("theme")
+            theme_locale = custom_theme._vars.get("language")
+            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)"
+            )
 
         # First prio: plugin locale
-        if plugin_locale != "":
-            self.locale = plugin_locale
+        if plugin_locale:
+            locale_set = plugin_locale
+            logging.debug("Using locale from plugin configuration: %s" % locale_set)
         # Second prio: theme locale
-        elif theme_locale != "":
-            self.locale = theme_locale
+        elif theme_locale:
+            locale_set = theme_locale
+            logging.debug(
+                "Locale not set in plugin. Fallback to theme configuration: %s"
+                % locale_set
+            )
         # Third prio is mkdocs locale (which might be added in the future)
-        elif mkdocs_locale != "":
-            self.locale = mkdocs_locale
+        elif mkdocs_locale:
+            locale_set = mkdocs_locale
+            logging.debug("Using locale from mkdocs configuration: %s" % locale_set)
         else:
-            self.locale = "en"
+            locale_set = "en"
+            logging.debug("No locale set. Fallback to: %s" % locale_set)
+
+        # set locale also in plugin configuration
+        self.config["locale"] = locale_set
 
         return config
 
@@ -78,7 +110,7 @@ class GitRevisionDateLocalizedPlugin(BasePlugin):
             str: output of rendered template as string
         """
 
-        if self.config["type"] != "timeago":
+        if self.config.get("type") != "timeago":
             return output_content
 
         extra_js = """
@@ -118,7 +150,7 @@ class GitRevisionDateLocalizedPlugin(BasePlugin):
 
         revision_dates = self.util.get_revision_date_for_file(
             path=page.file.abs_src_path,
-            locale=self.locale,
+            locale=self.config.get("locale", "en"),
             fallback_to_build_date=self.config.get("fallback_to_build_date"),
         )
         revision_date = revision_dates[self.config["type"]]
@@ -129,3 +161,11 @@ class GitRevisionDateLocalizedPlugin(BasePlugin):
             markdown,
             flags=re.IGNORECASE,
         )
+
+
+# ##############################################################################
+# ##### Stand alone program ########
+# ##################################
+if __name__ == "__main__":
+    """Standalone execution and quick tests."""
+    pass
diff --git a/mkdocs_git_revision_date_localized_plugin/util.py b/mkdocs_git_revision_date_localized_plugin/util.py
index 2f04261..d7880b0 100644
--- a/mkdocs_git_revision_date_localized_plugin/util.py
+++ b/mkdocs_git_revision_date_localized_plugin/util.py
@@ -56,6 +56,7 @@ class Util:
         timestamp_in_ms = unix_timestamp * 1000
 
         revision_date = datetime.utcfromtimestamp(unix_timestamp)
+        logging.debug("Revision date: %s - Locale: %s" % (revision_date, locale))
 
         return {
             "date": format_date(revision_date, format="long", locale=locale),
@@ -118,7 +119,7 @@ class Util:
             unix_timestamp = datetime.utcnow().timestamp()
             logging.warning("%s has no git logs, using current timestamp" % path)
 
-        return self._date_formats(unix_timestamp)
+        return self._date_formats(unix_timestamp=unix_timestamp, locale=locale)
 
 
 def is_shallow_clone(repo: Git) -> bool:


Test patch:
diff --git a/tests/basic_setup/mkdocs_datetime.yml b/tests/basic_setup/mkdocs_datetime.yml
index d839cff..8de0d2b 100644
--- a/tests/basic_setup/mkdocs_datetime.yml
+++ b/tests/basic_setup/mkdocs_datetime.yml
@@ -4,4 +4,4 @@ use_directory_urls: true
 plugins:
     - search
     - git-revision-date-localized:
-        type: datetime
\ No newline at end of file
+        type: datetime
diff --git a/tests/basic_setup/mkdocs_locale.yml b/tests/basic_setup/mkdocs_locale.yml
index 51bd56b..ef0d81a 100644
--- a/tests/basic_setup/mkdocs_locale.yml
+++ b/tests/basic_setup/mkdocs_locale.yml
@@ -1,7 +1,7 @@
 site_name: test gitrevisiondatelocalized_plugin
 use_directory_urls: true
 
-locale: nl
+locale: fr
 
 plugins:
     - search
diff --git a/tests/basic_setup/mkdocs_plugin_locale.yml b/tests/basic_setup/mkdocs_plugin_locale.yml
index b714e06..0bc9fc7 100644
--- a/tests/basic_setup/mkdocs_plugin_locale.yml
+++ b/tests/basic_setup/mkdocs_plugin_locale.yml
@@ -4,4 +4,4 @@ use_directory_urls: true
 plugins:
     - search
     - git-revision-date-localized:
-        locale: nl
\ No newline at end of file
+        locale: nl
diff --git a/tests/basic_setup/mkdocs_theme_no_locale.yml b/tests/basic_setup/mkdocs_theme_no_locale.yml
new file mode 100644
index 0000000..38a543c
--- /dev/null
+++ b/tests/basic_setup/mkdocs_theme_no_locale.yml
@@ -0,0 +1,9 @@
+site_name: test gitrevisiondatelocalized_plugin
+use_directory_urls: true
+
+theme:
+    name: 'material'
+
+plugins:
+    - search
+    - git-revision-date-localized
\ No newline at end of file
diff --git a/tests/test_basic.py b/tests/test_basic.py
index b352566..6f4ea39 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -9,25 +9,60 @@ You can reproduce locally with:
 >>> os.mkdir(tmp_path)
 """
 
+# #############################################################################
+# ########## Libraries #############
+# ##################################
+
 # standard lib
 import logging
 import os
 import re
 import shutil
 
-# 3rd partu
+# MkDocs
+from mkdocs.__main__ import build_command
+from mkdocs.config import load_config
+
+# other 3rd party
 import git
 import pytest
-import yaml
 from click.testing import CliRunner
-from mkdocs.__main__ import build_command
 
 # package module
 from mkdocs_git_revision_date_localized_plugin.util import Util
 
+# #############################################################################
+# ######## Globals #################
+# ##################################
+
+PLUGIN_NAME = "git-revision-date-localized"
 
-def load_config(mkdocs_path):
-    return yaml.load(open(mkdocs_path, "rb"), Loader=yaml.Loader)
+# custom log level to get plugin info messages
+logging.basicConfig(level=logging.INFO)
+
+
+# #############################################################################
+# ########## Helpers ###############
+# ##################################
+def get_plugin_config_from_mkdocs(mkdocs_path) -> dict:
+    # instanciate plugin
+    cfg_mkdocs = load_config(mkdocs_path)
+
+    plugins = cfg_mkdocs.get("plugins")
+    plugin_loaded = plugins.get(PLUGIN_NAME)
+
+    cfg = plugin_loaded.on_config(cfg_mkdocs)
+    logging.info("Fixture configuration loaded: " + str(cfg))
+
+    assert (
+        plugin_loaded.config.get("locale") is not None
+    ), "Locale should never be None after plugin is loaded"
+
+    logging.info(
+        "Locale '%s' determined from %s"
+        % (plugin_loaded.config.get("locale"), mkdocs_path)
+    )
+    return plugin_loaded.config
 
 
 def setup_clean_mkdocs_folder(mkdocs_yml_path, output_path):
@@ -141,7 +176,7 @@ def build_docs_setup(testproject_path):
         raise
 
 
-def validate_build(testproject_path):
+def validate_build(testproject_path, plugin_config: dict):
     """
     Validates a build from a testproject
 
@@ -157,19 +192,21 @@ def validate_build(testproject_path):
     # Make sure with markdown tag has valid
     # git revision date tag
     page_with_tag = testproject_path / "site/page_with_tag/index.html"
-    contents = page_with_tag.read_text()
+    contents = page_with_tag.read_text(encoding="utf8")
     assert re.search(r"Markdown tag\:\s[<span>|\w].+", contents)
 
     repo = Util(testproject_path)
     date_formats = repo.get_revision_date_for_file(
-        testproject_path / "docs/page_with_tag.md"
+        path=testproject_path / "docs/page_with_tag.md",
+        locale=plugin_config.get("locale"),
+        fallback_to_build_date=plugin_config.get("fallback_to_build_date"),
     )
 
     searches = [re.search(x, contents) for x in date_formats.values()]
     assert any(searches), "No correct date formats output was found"
 
 
-def validate_mkdocs_file(temp_path, mkdocs_yml_file):
+def validate_mkdocs_file(temp_path: str, mkdocs_yml_file: str):
     """
     Creates a clean mkdocs project
     for a mkdocs YML file, builds and validates it.
@@ -184,14 +221,18 @@ def validate_mkdocs_file(temp_path, mkdocs_yml_file):
     setup_commit_history(testproject_path)
     result = build_docs_setup(testproject_path)
     assert result.exit_code == 0, "'mkdocs build' command failed"
-    validate_build(testproject_path)
-
-    return testproject_path
 
+    # validate build with locale retrieved from mkdocs config file
+    validate_build(
+        testproject_path, plugin_config=get_plugin_config_from_mkdocs(mkdocs_yml_file)
+    )
 
-#### Tests ####
+    return testproject_path
 
 
+# #############################################################################
+# ########### Tests ################
+# ##################################
 def test_date_formats():
     u = Util()
     assert u._date_formats(1582397529) == {
@@ -217,19 +258,6 @@ def test_missing_git_repo(tmp_path):
     ), "'mkdocs build' command succeeded while there is no GIT repo"
 
 
-def test_missing_git_repo_ignored(tmp_path):
-    """
-    When there is no GIT repo, the git error should be ignored.
-    """
-    testproject_path = setup_clean_mkdocs_folder(
-        mkdocs_yml_path="tests/basic_setup/mkdocs_fallback_to_build_date.yml",
-        output_path=tmp_path,
-    )
-
-    result = build_docs_setup(testproject_path)
-    assert result.exit_code == 0
-
-
 def test_build_no_options(tmp_path):
     # Enable plugin with no extra options set
     validate_mkdocs_file(tmp_path, "tests/basic_setup/mkdocs.yml")
@@ -241,7 +269,7 @@ def test_build_locale_plugin(tmp_path):
 
 
 def test_build_locale_mkdocs(tmp_path):
-    # Enable plugin with mkdocs locale set to 'nl'
+    # Enable plugin with mkdocs locale set to 'fr'
     validate_mkdocs_file(tmp_path, "tests/basic_setup/mkdocs_locale.yml")
 
 
@@ -257,10 +285,25 @@ def test_material_theme(tmp_path):
     # In mkdocs-material, a 'last update' should appear
     # in German because locale is set to 'de'
     index_file = testproject_path / "site/index.html"
-    contents = index_file.read_text()
+    contents = index_file.read_text(encoding="utf8")
     assert re.search(r"Letztes Update\:\s[\w].+", contents)
 
 
+def test_material_theme_no_locale(tmp_path):
+    """
+    When using mkdocs-material theme, test correct working
+    """
+    # theme set to 'material' with 'language' set to 'de'
+    testproject_path = validate_mkdocs_file(
+        tmp_path, "tests/basic_setup/mkdocs_theme_no_locale.yml"
+    )
+
+    # In mkdocs-material, a 'last update' should appear
+    # in German because locale is set to 'de'
+    index_file = testproject_path / "site/index.html"
+    contents = index_file.read_text(encoding="utf8")
+    assert re.search(r"Last update\:\s[\w].+", contents)
+
 def test_type_timeago(tmp_path):
     # type: 'timeago'
     testproject_path = validate_mkdocs_file(
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
new file mode 100644
index 0000000..3d60de8
--- /dev/null
+++ b/tests/test_plugin.py
@@ -0,0 +1,216 @@
+#! python3  # noqa E265
+
+"""
+    Test for the plugin class (subclass of mkdocs.BasePlugin).
+
+    Usage from the repo root folder:
+
+        # all tests
+        python -m unittest tests.test_plugin
+
+        # specific test
+        python -m unittest tests.test_plugin.TestMkdocsPlugin.
+"""
+
+# #############################################################################
+# ########## Libraries #############
+# ##################################
+
+# Standard library
+from pathlib import Path
+import logging
+import unittest
+
+# MkDocs
+from mkdocs.config import load_config
+
+# package
+from mkdocs_git_revision_date_localized_plugin.plugin import (
+    GitRevisionDateLocalizedPlugin,
+)
+
+
+# #############################################################################
+# ######## Globals #################
+# ##################################
+
+# make this test module easily reusable
+PLUGIN_NAME = "git-revision-date-localized"
+
+# custom log level to get plugin info messages
+logging.basicConfig(level=logging.INFO)
+
+# #############################################################################
+# ########## Helpers ###############
+# ##################################
+
+
+# #############################################################################
+# ########## Classes ###############
+# ##################################
+
+
+class TestMkdocsPlugin(unittest.TestCase):
+    """MkDocs plugin module."""
+
+    # -- Standard methods --------------------------------------------------------
+    @classmethod
+    def setUpClass(cls):
+        """Executed when module is loaded before any test."""
+        cls.fixtures_mkdocs_config_files = sorted(
+            Path("tests/basic_setup").glob("*.yml")
+        )
+
+        cls.fixtures_config_cases_ok = {
+            "default_explicit": {
+                "type": "date",
+                "locale": "en",
+                "fallback_to_build_date": False,
+            },
+            # locale variations
+            "default_no_locale": {"type": "date", "fallback_to_build_date": False},
+            "custom_locale": {"locale": "fr"},
+            # type variations
+            "type_datetime": {"type": "datetime"},
+            "type_iso_date": {"type": "iso_date"},
+            "type_iso_datetime": {"type": "iso_datetime"},
+            "type_timeago": {"type": "timeago"},
+            # falbback variations
+            "fallback_true": {"fallback_to_build_date": True},
+        }
+
+        cls.fixtures_config_cases_bad = {
+            "invalid_option_name": {"language": "en",},
+            # "invalid_value": {"type": "calendar", "locale": "nl"},
+            "invalid_value_type": {"type": 1, "locale": "de"},
+        }
+
+    def setUp(self):
+        """Executed before each test."""
+        pass
+
+    def tearDown(self):
+        """Executed after each test."""
+        pass
+
+    @classmethod
+    def tearDownClass(cls):
+        """Executed after the last test."""
+        pass
+
+    # -- TESTS ---------------------------------------------------------
+
+    # -- GET --
+    def test_plugin_instanciation(self):
+        """Simple test plugin instanciation"""
+        # instanciate
+        plg = GitRevisionDateLocalizedPlugin()
+
+        # default values
+        self.assertIsInstance(plg.config, dict)
+        self.assertEqual(plg.config, {})
+
+    def test_plugin_load_configs_ok(self):
+        """Test inherited plugin load_config method on good configurations"""
+        # instanciate
+        plg = GitRevisionDateLocalizedPlugin()
+
+        # parse fixtures configurations alone
+        for i in self.fixtures_config_cases_ok:
+            cfg = self.fixtures_config_cases_ok.get(i)
+            out_cfg = plg.load_config(options=cfg)
+
+            # check if config loader returned no errors
+            self.assertIsInstance(out_cfg, tuple)
+            [self.assertListEqual(v, []) for v in out_cfg]
+            self.assertEqual(all([len(i) == 0 for i in out_cfg]), True)
+
+            # try associating mkdocs configuration
+            for i in self.fixtures_mkdocs_config_files:
+                out_cfg_mkdocs = plg.load_config(
+                    options=cfg, config_file_path=str(i.resolve())
+                )
+
+                # check if config loader returned no errors
+                self.assertIsInstance(out_cfg_mkdocs, tuple)
+                [self.assertListEqual(v, []) for v in out_cfg_mkdocs]
+                self.assertEqual(all([len(i) == 0 for i in out_cfg_mkdocs]), True)
+
+    def test_plugin_load_configs_bad(self):
+        """Test inherited plugin load_config method on bad configurations"""
+        # instanciate
+        plg = GitRevisionDateLocalizedPlugin()
+
+        # simulate a complete configuration
+        for i in self.fixtures_config_cases_bad:
+            cfg = self.fixtures_config_cases_bad.get(i)
+            out_cfg = plg.load_config(options=cfg)
+
+            # check if config loader returned no errors
+            self.assertIsInstance(out_cfg, tuple)
+            self.assertEqual(all([len(i) == 0 for i in out_cfg]), False)
+
+            # try associating mkdocs configuration
+            for i in self.fixtures_mkdocs_config_files:
+                out_cfg_mkdocs = plg.load_config(
+                    options=cfg, config_file_path=str(i.resolve())
+                )
+
+                # check if config loader returned no errors
+                self.assertIsInstance(out_cfg_mkdocs, tuple)
+                self.assertEqual(all([len(i) == 0 for i in out_cfg_mkdocs]), False)
+
+    def test_plugin_on_config(self):
+        """Test inherited plugin on_config method"""
+        # load try associating mkdocs configuration
+        for i in self.fixtures_mkdocs_config_files:
+            # logging.info("Using Mkdocs configuration: %s " % i.resolve())
+            cfg_mkdocs = load_config(str(i))
+
+            # get mkdocs locale config - expected as future feature
+            mkdocs_locale = cfg_mkdocs.get("locale", None)
+
+            # get our plugin config and copy it
+            plugin_loaded_from_mkdocs = cfg_mkdocs.get("plugins").get(PLUGIN_NAME)
+            cfg_before_on_config = plugin_loaded_from_mkdocs.config.copy()
+
+            # get theme configuration
+            theme = cfg_mkdocs.get("theme")  # -> Theme
+
+            # look for the theme locale/language
+            if "locale" in theme._vars:
+                theme_locale = theme._vars.get("locale")
+            elif "language" in theme._vars:
+                theme_locale = theme._vars.get("language")
+            else:
+                theme_locale = None
+
+            # execute on_config with global mkdocs loaded configuration and save config
+            plugin_loaded_from_mkdocs.on_config(cfg_mkdocs)
+            cfg_after_on_config = plugin_loaded_from_mkdocs.config.copy()
+
+            # -- CASES for LOCALE ---------------------------------------
+            result_locale = cfg_after_on_config.get("locale")
+            # if locale set in plugin configuration = it the one!
+            if cfg_before_on_config.get("locale"):
+                self.assertEqual(result_locale, cfg_before_on_config.get("locale"))
+            # if locale set in theme: it should be used
+            elif theme_locale and not cfg_before_on_config.get("locale"):
+                self.assertEqual(result_locale, theme_locale)
+            # if locale not set in plugin nor in theme but in mkdocs = mkdocs
+            elif (
+                mkdocs_locale
+                and not cfg_before_on_config.get("locale")
+                and not theme_locale
+            ):
+                self.assertEqual(result_locale, mkdocs_locale)
+            # if locale is not set at all = default = "en"
+            else:
+                self.assertEqual(result_locale, "en")
+
+
+# ##############################################################################
+# ##### Stand alone program ########
+# ##################################
+if __name__ == "__main__":
+    unittest.main()