pythontkintercustomtkintertkinter-button

Error when changing language in Tkinter application: AttributeError: 'dict' object has no attribute '_apply_appearance_mode'


I'm encountering an error when attempting to change the language in my Tkinter application. The application has a button that, when clicked, is supposed to change the language displayed on the UI elements and update the tooltips accordingly. changing UI elements language is working but I'm getting error from tooltips. I have separated file and method which is changing languages for all elements (buttons)I wanted to add part where I change tooltip language too. The method is called when I click on button that is changing language.

main file

lang_manager = LanguageManager(current_language="en")  

for i, addon in enumerate(addon_db_url):
    addon_var = tk.BooleanVar()
    addon_vars.append(addon_var)
    addon_checkbox = cus.CTkCheckBox(addon_frame, fg_color="#0a4a0c", hover_color="#0c6e0f", text=addon["Name"], text_color="#f7f5ed", variable=addon_var)
    addon_checkbox.pack(anchor="w", pady=(0, 2))
    CTkToolTip(addon_checkbox, message=lang_manager.addon_tooltips_en[i], delay=0.2)
    addon_var.trace_add("write", lambda *args: check_addon_selection(addon_db_url, download_button_addon))
    addon["var"] = addon_var



def change_and_map_language():
    lang_manager.change_language(language_button, download_button_addon, download_button_patches, 
                                 default_location_button, check_detection_button, launch_wow_button, addon_db_url)
    lang_manager.tooltip_mapper(addon_checkbox, addon_db_url)


language_button = cus.CTkButton(master=window, command=change_and_map_language)

I'm using CTkToolTip here bcz default language of app is English. I tried to separate methods one for changing ui's language (buttons) and one for tooltips.

file for changing language

class LanguageManager:
    def __init__(self, current_language):
        self.current_language = current_language
        self.addon_tooltips_en = [
        "text_eng",
        "text_eng2",
        "text_eng3",
        "text_eng4",
        ... ] 
        self.current_language = current_language
        self.addon_tooltips_geo = [
        "text_b",
        "text_b2",
        "text_b3",
        "text_b4",
        ... ]

    def tooltip_mapper(self, addon_chckboxes, addon_db_url):
        if self.current_language == "ge":
            for i, addon_chckboxes in enumerate(addon_db_url):
                CTkToolTip(addon_chckboxes, message=self.addon_tooltips_en[i], delay=0.2)    
        else:
            for i, addon_chckboxes in enumerate(addon_db_url):
                CTkToolTip(addon_chckboxes, message=self.addon_tooltips_geo[i], delay=0.2)    
        
    def change_language(self, language_button, download_button_addon, download_button_patches,
                        default_location_button, check_detection_button, launch_wow_button):
        if self.current_language == "ge":
            self.current_language = "en"
            language_button.configure(image=img_lang_en, text="ENG")
            download_button_addon.configure(text="Download Addons", width=150, height=45, font=("cursive", 18))
            download_button_patches.configure(text="Download Patches", width=150, height=45, font=("cursive", 18))
            default_location_button.configure(text="Choose Default Location", width=200, height=50, font=("cursive", 18))
            check_detection_button.configure(text="Check Downloads", width=170, height=45, font=("cursive", 18))
            launch_wow_button.configure(text="Start The Game", width=150, height=50, font=("cursive", 18))

        else:
            self.current_language = "ge"
            language_button.configure(image=img_lang_geo, text="GEO")
            download_button_addon.configure(text=" changed text", width=150, height=45, font=("Arial", 20, 'bold'))
            download_button_patches.configure(text=" changed text", width=150, height=45, font=("Arial", 20, 'bold'))
            default_location_button.configure(text=" changed text", width=200, height=50, font=("Arial", 20, 'bold'))
            check_detection_button.configure(text=" changed text", width=170, height=45, font=("Arial", 20, 'bold'))
            launch_wow_button.configure(text=" changed text", width=150, height=50, font=("Arial", 20, 'bold'))

When I run app tooltip on everycheckbox working fine but when I click on language_button I'm getting this error

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1967, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 554, in _clicked
    self._command()
  File "C:\Users\user\Desktop\app", line 293, in change_and_map_language
    lang_manager.tooltip_mapper(addon_checkbox, addon_db_url)
  File "C:\Users\user\Desktop\-main\Lang_manager.py", line 73, in tooltip_mapper
    CTkToolTip(addon_chckboxes, message=self.addon_tooltips_en[i], delay=0.2)
  File "C:\Users\user\AppData\Local\Programs\Python\Python312\Lib\site-packages\CTkToolTip\ctk_tooltip.py", line 42, in __init__
    self.transparent_color = self.widget._apply_appearance_mode(
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'dict' object has no attribute '_apply_appearance_mode'

addon_db_url is a .json file which I using like this:

core.py class utils

class Utils:
    def __init__(self, lang_manager):
        self.lang_manager = lang_manager
        self.config_file = "config.json"
        self.config = self.load_config()


    def load_addon_urls(self):
        bundle_dir = self.get_bundle_dir()
        addon_json_path = os.path.join(bundle_dir, 'json', 'addon_wotlk_db_url.json')
        with open(addon_json_path, 'r') as addon_file:
            return json.load(addon_file)

main file

import Core

core_utils_instance = Core.Utils() 
addon_db_url = core_utils_instance.load_addon_urls()

addon_wotlk_db_url.json

[
 {"Name": "addon name 1", "Url": "direct download link"},
    {"Name": "addon name 2", "Url": "direct download link"},
    {"Name": "addon name 3", "Url": "direct download link"},
    {"Name": "addon name 4", "Url": "direct download link"},
    ...
]

Solution

  • Inside tooltip_mapper():

    for i, addon_chckboxes in enumerate(addon_db_url):
        CTkToolTip(addon_chckboxes, message=self.addon_tooltips_en[i], delay=0.2)    
    

    Since addon_db_url is a list of dictionaries, so addon_chckboxes is a dictionary. Exception is raised when it is passed as the first argument to CTkToolTip() which expects the first argument is a widget.

    To change the tooltip, you should use the already created instance of CTkToolTip() instead of creating new one.

    Suggestions:

    1. use dictionary inside LanguageManager instead of separate instance variables:
    class LanguageManager:
        def __init__(self, current_language="en"):
            self.current_language = current_language
            # use a dictionary instead of separate variables
            self.addon_tooltips = {
                "en": [
                    "text_eng",
                    "text_eng2",
                    "text_eng3",
                    "text_eng4",
                ],
                "ge": [
                    "text_b",
                    "text_b2",
                    "text_b3",
                    "text_b4",
                ],
            }
            ...
    
    1. use a list to store those instances of CTkToolTip():
    addon_tooltips = []
    
    for i, addon in enumerate(addon_db_url):
        addon_var = cus.BooleanVar()
        addon_vars.append(addon_var)
        addon_checkbox = cus.CTkCheckBox(addon_frame, fg_color="#0a4a0c", hover_color="#0c6e0f",
                                         text=addon["Name"], text_color="#f7f5ed", variable=addon_var)
        addon_checkbox.pack(anchor="w", pady=(0, 2))
        # save the instance of CTkToolTip
        addon_tooltips.append(CTkToolTip(addon_checkbox, message=lang_manager.addon_tooltips["en"][i], delay=0.2))
        ...
    

    Then rewrite tooltip_mapper() and change_and_map_language() as below:

    class LanugageManager:
        ...
    
        def tooltip_mapper(self, addon_tooltips):
            for i, tooltip in enumerate(addon_tooltips):
                 tooltip.configure(message=self.addon_tooltips[self.current_language][i])
    
    def change_and_map_language():
        ...
        lang_manager.tooltip_mapper(addon_tooltips)
    

    Below is a simplified example:

    import customtkinter as cus
    from CTkToolTip import CTkToolTip
    
    addon_db_url = [
        {"Name": "addon name 1", "Url": "direct download link 1"},
        {"Name": "addon name 2", "Url": "direct download link 2"},
        {"Name": "addon name 3", "Url": "direct download link 3"},
        {"Name": "addon name 4", "Url": "direct download link 4"},
    ]
    
    class LanguageManager:
        def __init__(self, current_language="en"):
            self.current_language = current_language
            self.addon_tooltips = {
                "en": [
                    "text_eng",
                    "text_eng2",
                    "text_eng3",
                    "text_eng4",
                ],
                "ge": [
                    "text_b",
                    "text_b2",
                    "text_b3",
                    "text_b4",
                ],
            }
    
        def change_language(self, language_button):
            if self.current_language == "ge":
                self.current_language = "en"
                language_button.configure(text="ENG")
            else:
                self.current_language = "ge"
                language_button.configure(text="GEO")
    
        def tooltip_mapper(self, addon_tooltips):
            for i, tooltip in enumerate(addon_tooltips):
                tooltip.configure(message=self.addon_tooltips[self.current_language][i])
    
    window = cus.CTk()
    
    addon_frame = cus.CTkFrame(window)
    addon_frame.pack(padx=100, pady=100)
    
    addon_vars = []
    addon_tooltips = []
    
    lang_manager = LanguageManager(current_language="en")
    
    for i, addon in enumerate(addon_db_url):
        addon_var = cus.BooleanVar()
        addon_vars.append(addon_var)
        addon_checkbox = cus.CTkCheckBox(addon_frame, fg_color="#0a4a0c", hover_color="#0c6e0f",
                                         text=addon["Name"], text_color="#f7f5ed", variable=addon_var)
        addon_checkbox.pack(anchor="w", pady=(0, 2))
        addon_tooltips.append(CTkToolTip(addon_checkbox, message=lang_manager.addon_tooltips["en"][i], delay=0.2))
    
    def change_and_map_language():
        lang_manager.change_language(language_button)
        lang_manager.tooltip_mapper(addon_tooltips)
    
    language_button = cus.CTkButton(master=window, text="ENG", command=change_and_map_language)
    language_button.pack(pady=10)
    
    window.mainloop()