Repository: 12rambau/sepal_ui

Issue:
create a translator function to check the use of the keys
If you are updating many time the same application you may end up removing some or all the existing keys. It complex to visually assess if all the remaining keys in the dict are used. 

Maybe a parser could be interesting to check all the folder files and validate the keys that are used. 

Usage will be of course for developer only 


Discussion:

Patch:
diff --git a/sepal_ui/mapping/sepal_map.py b/sepal_ui/mapping/sepal_map.py
index 57693e56..e2860daf 100644
--- a/sepal_ui/mapping/sepal_map.py
+++ b/sepal_ui/mapping/sepal_map.py
@@ -227,8 +227,8 @@ class SepalMap(ipl.Map):
         # Center map to the centroid of the layer(s)
         self.center = [(maxy - miny) / 2 + miny, (maxx - minx) / 2 + minx]
 
-        # create the tuples for each corner
-        tl, br, bl, tr = (minx, maxy), (maxx, miny), (minx, miny), (maxx, maxy)
+        # create the tuples for each corner in (lat/lng) convention
+        tl, br, bl, tr = (maxy, minx), (miny, maxx), (miny, minx), (maxy, maxx)
 
         # find zoom level to display the biggest diagonal (in km)
         lg, zoom = 40075, 1  # number of displayed km at zoom 1
diff --git a/sepal_ui/message/en/locale.json b/sepal_ui/message/en/locale.json
index 5989dee5..b689db70 100644
--- a/sepal_ui/message/en/locale.json
+++ b/sepal_ui/message/en/locale.json
@@ -37,7 +37,6 @@
 			"custom": "Custom",
 			"no_access": "It seems like you do not have access to the input asset or it does not exist.",
 			"wrong_type": "The type of the selected asset ({}) does not match authorized asset type ({}).",
-			"hint": "Select an asset in the list or write a custom asset name. Be careful, you need to have access to this asset to use it",
 			"placeholder": "users/custom_user/custom_asset"
 		},
 		"load_table": {
@@ -65,20 +64,6 @@
 		"asset": "GEE Asset name",
 		"btn": "Select AOI",
 		"complete": "The AOI has been selected",
-		"shape_drawn": "A shape has been drawn",
-		"file_pattern": "aoi_{}",
-		"no_selection": "No selection method has been picked up",
-		"no_country": "No Country has been selected",
-		"asset_already_exist": "The asset was already existing you can continue to use it. It's also available at :{}",
-		"asset_created": "The asset has been created under the name : {}",
-		"name_used": "The name was already in used, change it or delete the previous asset in your GEE acount",
-		"no_asset": "No Asset has been provided",
-		"check_if_asset": "Check carefully that your string is an assetId",
-		"not_available": "This function is not yet available",
-		"no_shape": "No shape has been drawn on the map",
-		"shp_error": "An error occured with provided .shp file",
-		"aoi_message": "click on \"selet these inputs\" to validate your AOI",
-		"geojson_to_ee": "Convert your .csv file into a ee_object",
 		"exception" : {
 			"no_inputs": "Please provide fully qualified inputs before validating your AOI",
 			"no_asset" : "Please select an asset.",
@@ -98,7 +83,6 @@
     "planet" : {
         "exception" : {
             "empty": "Please fill the required field(s).",
-            "format" : "Please check the format of your inputs.",
             "invalid" : "Invalid email or password",
             "nosubs" : "Your credentials do not have any valid planet subscription."
         },
@@ -143,7 +127,7 @@
 					"0": "New element",
 					"1": "Modify element"
 				},
-				"btn": {
+                "btn": {
 					"save": {
 						"name": "save",
 						"tooltip": "create new class"
diff --git a/sepal_ui/reclassify/table_view.py b/sepal_ui/reclassify/table_view.py
index 0f8bf1cd..c3f8a35a 100644
--- a/sepal_ui/reclassify/table_view.py
+++ b/sepal_ui/reclassify/table_view.py
@@ -212,15 +212,24 @@ class EditDialog(v.Dialog):
         self.title = v.CardTitle(children=[self.TITLES[0]])
 
         # Action buttons
-        btn_txt = ms.rec.table.edit_dialog.btn
-        self.save = sw.Btn(btn_txt.save.name)
-        save_tool = sw.Tooltip(self.save, btn_txt.save.tooltip, bottom=True)
+        self.save = sw.Btn(ms.rec.table.edit_dialog.btn.save.name)
+        save_tool = sw.Tooltip(
+            self.save, ms.rec.table.edit_dialog.btn.save.tooltip, bottom=True
+        )
 
-        self.modify = sw.Btn(btn_txt.modify.name).hide()  # by default modify is hidden
-        modify_tool = sw.Tooltip(self.modify, btn_txt.modify.tooltip, bottom=True)
+        self.modify = sw.Btn(
+            ms.rec.table.edit_dialog.btn.modify.name
+        ).hide()  # by default modify is hidden
+        modify_tool = sw.Tooltip(
+            self.modify, ms.rec.table.edit_dialog.btn.modify.tooltip, bottom=True
+        )
 
-        self.cancel = sw.Btn(btn_txt.cancel.name, outlined=True, class_="ml-2")
-        cancel_tool = sw.Tooltip(self.cancel, btn_txt.cancel.tooltip, bottom=True)
+        self.cancel = sw.Btn(
+            ms.rec.table.edit_dialog.btn.cancel.name, outlined=True, class_="ml-2"
+        )
+        cancel_tool = sw.Tooltip(
+            self.cancel, ms.rec.table.edit_dialog.btn.cancel.tooltip, bottom=True
+        )
 
         actions = v.CardActions(children=[save_tool, modify_tool, cancel_tool])
 
diff --git a/sepal_ui/sepalwidgets/alert.py b/sepal_ui/sepalwidgets/alert.py
index 6d869aaa..e143170e 100644
--- a/sepal_ui/sepalwidgets/alert.py
+++ b/sepal_ui/sepalwidgets/alert.py
@@ -380,8 +380,11 @@ class Banner(v.Snackbar, SepalWidget):
         Args:
             nb_banner (int): the number of banners in the queue
         """
-        msg = ms.widgets.banner
-        txt = msg.close if nb_banner == 0 else msg.next.format(nb_banner)
+        # do not wrap ms.widget.banner. If you do it won't be recognized by the key-checker of the Translator
+        if nb_banner == 0:
+            txt = ms.widgets.banner.close
+        else:
+            txt = ms.widgets.banner.next.format(nb_banner)
         self.btn_close.children = [txt]
 
         return
diff --git a/sepal_ui/translator/translator.py b/sepal_ui/translator/translator.py
index 5cf26320..f3a4b791 100644
--- a/sepal_ui/translator/translator.py
+++ b/sepal_ui/translator/translator.py
@@ -3,6 +3,7 @@ from collections import abc
 from configparser import ConfigParser
 from pathlib import Path
 
+import pandas as pd
 from box import Box
 from deprecated.sphinx import deprecated, versionadded
 
@@ -262,3 +263,58 @@ class Translator(Box):
                 del d[k]
 
         return d
+
+    @versionadded(version="2.10.0")
+    def key_use(self, folder, name):
+        """
+        Parse all the files in the folder and check if keys are all used at least once.
+        Return the unused key names.
+
+        .. warning::
+
+            Don't forget that there are many ways of calling Translator variables
+            (getattr, save.cm.xxx in another variable etc...) SO don't forget to check
+            manually the variables suggested by this method before deleting them
+
+        Args:
+            folder (pathlib.Path): The application folder using this translator data
+            name (str): the name use by the translator in this app (usually "cm")
+
+        Return:
+            (list): the list of unused keys
+        """
+        # cannot set FORBIDDEN_KEY in the Box as it would lock another key
+        FORBIDDEN_KEYS = ["_folder", "_default", "_target", "_targeted", "_match"]
+
+        # sanitize folder
+        folder = Path(folder)
+
+        # get all the python files recursively
+        py_files = [
+            f for f in folder.glob("**/*.py") if ".ipynb_checkpoints" not in str(f)
+        ]
+
+        # get the flat version of all keys
+        keys = list(set(pd.json_normalize(self).columns) ^ set(FORBIDDEN_KEYS))
+
+        # init the unused keys list
+        unused_keys = []
+
+        for k in keys:
+
+            # by default we consider that the is never used
+            is_present = False
+
+            # read each python file and search for the pattern of the key
+            # if it's find change status of the counter and exit the search
+            for f in py_files:
+                tmp = f.read_text()
+                if f"{name}.{k}" in tmp:
+                    is_present = True
+                    break
+
+            # if nothing is find, the value is still False and the key can be
+            # added to the list
+            is_present or unused_keys.append(k)
+
+        return unused_keys


Test patch:
diff --git a/tests/test_Translator.py b/tests/test_Translator.py
index 7c8e7242..48d34519 100644
--- a/tests/test_Translator.py
+++ b/tests/test_Translator.py
@@ -6,6 +6,7 @@ from pathlib import Path
 import pytest
 
 from sepal_ui import config_file
+from sepal_ui.message import ms
 from sepal_ui.translator import Translator
 
 
@@ -121,6 +122,16 @@ class TestTranslator:
 
         return
 
+    def test_key_use(self):
+
+        # check key usage method and the lib content at the same time
+        expected = ["test_key"]
+        lib_folder = Path(__file__).parents[1]
+        res = ms.key_use(lib_folder, "ms")
+        assert res == expected
+
+        return
+
     @pytest.fixture(scope="class")
     def translation_folder(self):
         """