Repository: robotframework/PythonLibCore

Issue:
If @keyword deco has custom name, original name leaks to keywords
If `@keyword` deco has custom name, then original and not translated method name leaks to keywords.

Discussion:

Patch:
diff --git a/src/robotlibcore.py b/src/robotlibcore.py
index 47668bd..e652daf 100644
--- a/src/robotlibcore.py
+++ b/src/robotlibcore.py
@@ -57,35 +57,47 @@ def _translation(translation: Optional[Path] = None):
         return {}
 
 
+def _translated_keywords(translation_data: dict) -> list:
+    return [item.get("name") for item in translation_data.values() if item.get("name")]
+
+
 class HybridCore:
     def __init__(self, library_components: List, translation: Optional[Path] = None) -> None:
         self.keywords = {}
         self.keywords_spec = {}
         self.attributes = {}
         translation_data = _translation(translation)
-        self.add_library_components(library_components, translation_data)
-        self.add_library_components([self], translation_data)
+        translated_kw_names = _translated_keywords(translation_data)
+        self.add_library_components(library_components, translation_data, translated_kw_names)
+        self.add_library_components([self], translation_data, translated_kw_names)
         self.__set_library_listeners(library_components)
 
-    def add_library_components(self, library_components: List, translation: Optional[dict] = None):
+    def add_library_components(
+        self,
+        library_components: List,
+        translation: Optional[dict] = None,
+        translated_kw_names: Optional[list] = None,
+    ):
         translation = translation if translation else {}
+        translated_kw_names = translated_kw_names if translated_kw_names else []
         self.keywords_spec["__init__"] = KeywordBuilder.build(self.__init__, translation)  # type: ignore
         self.__replace_intro_doc(translation)
         for component in library_components:
             for name, func in self.__get_members(component):
                 if callable(func) and hasattr(func, "robot_name"):
                     kw = getattr(component, name)
-                    kw_name = self.__get_keyword_name(func, name, translation)
+                    kw_name = self.__get_keyword_name(func, name, translation, translated_kw_names)
                     self.keywords[kw_name] = kw
                     self.keywords_spec[kw_name] = KeywordBuilder.build(kw, translation)
                     # Expose keywords as attributes both using original
                     # method names as well as possible custom names.
                     self.attributes[name] = self.attributes[kw_name] = kw
 
-    def __get_keyword_name(self, func: Callable, name: str, translation: dict):
-        if name in translation:  # noqa: SIM102
-            if new_name := translation[name].get("name"):
-                return new_name
+    def __get_keyword_name(self, func: Callable, name: str, translation: dict, translated_kw_names: list):
+        if name in translated_kw_names:
+            return name
+        if name in translation and translation[name].get("name"):
+            return translation[name].get("name")
         return func.robot_name or name
 
     def __replace_intro_doc(self, translation: dict):


Test patch:
diff --git a/atest/SmallLibrary.py b/atest/SmallLibrary.py
index e576368..3a93661 100644
--- a/atest/SmallLibrary.py
+++ b/atest/SmallLibrary.py
@@ -4,6 +4,13 @@ from typing import Optional
 from robot.api import logger
 from robotlibcore import DynamicCore, keyword
 
+class KeywordClass:
+
+    @keyword(name="Execute SomeThing")
+    def execute_something(self):
+        """This is old"""
+        print("Name is here")
+
 class SmallLibrary(DynamicCore):
     """Library documentation."""
 
@@ -12,10 +19,7 @@ class SmallLibrary(DynamicCore):
         if not isinstance(translation, Path):
             logger.warn("Convert to Path")
             translation = Path(translation)
-        logger.warn(translation.absolute())
-        logger.warn(type(translation))
-
-        DynamicCore.__init__(self, [], translation.absolute())
+        DynamicCore.__init__(self, [KeywordClass()], translation.absolute())
 
     @keyword(tags=["tag1", "tag2"])
     def normal_keyword(self, arg: int, other: str) -> str:
@@ -32,7 +36,7 @@ class SmallLibrary(DynamicCore):
         print(data)
         return data
 
-    @keyword(name="This Is New Name", tags=["tag1", "tag2"])
+    @keyword(name="Name ChanGed", tags=["tag1", "tag2"])
     def name_changed(self, some: int, other: int) -> int:
         """This one too"""
         print(f"{some} {type(some)}, {other} {type(other)}")
diff --git a/atest/translation.json b/atest/translation.json
index dbdab73..a3b2585 100644
--- a/atest/translation.json
+++ b/atest/translation.json
@@ -21,5 +21,9 @@
     ,
     "kw_not_translated": {
         "doc": "Here is new doc"
+    },
+    "execute_something": {
+        "name": "tee_jotain",
+        "doc": "Uusi kirja."
     }
 }
diff --git a/utest/test_translations.py b/utest/test_translations.py
index 2d009b0..b9b9e3b 100644
--- a/utest/test_translations.py
+++ b/utest/test_translations.py
@@ -57,3 +57,11 @@ def test_kw_not_translated_but_doc_is(lib: SmallLibrary):
     assert "kw_not_translated" in keywords
     doc = lib.get_keyword_documentation("kw_not_translated")
     assert doc == "Here is new doc"
+
+
+def test_rf_name_not_in_keywords():
+    translation = Path(__file__).parent.parent / "atest" / "translation.json"
+    lib = SmallLibrary(translation=translation)
+    kw = lib.keywords
+    assert "Execute SomeThing" not in kw, f"Execute SomeThing should not be present: {kw}"
+    assert len(kw) == 6, f"Too many keywords: {kw}"