Sub Menu “ Tema Tampilan"

 Isi Menu "Setting"

(Konten Sub Menu "Tema Tampilan")


5. Sub Menu  “ Tema Tampilan"

Sekarang kita masuk ke   Menu "Pengaturan "   kita lanjut membuat konten Sub Menu   "Tema Tampilan".

Mantap 🚀, berarti kita sudah punya struktur Menu Utama → Pengaturan → Tema Tampilan.
Saya bikinkan dulu sub_menu_tema_tampilan.py yang rapi dan sinkron dengan sistem translasi (get_text / set_language).

Nanti pengguna bisa pilih tema terang atau tema gelap, terus disimpan ke state (setting_state.py) supaya saat aplikasi dibuka lagi, tema tetap konsisten.


📂 File: sub_menu_tema_tampilan.py

import tkinter as tk
from tkinter import ttk, messagebox

from Setting.utils_text import get_text
from Setting.setting_state import get_tema_aktif, set_tema_aktif


class SubMenuTemaTampilan(tk.Frame):
    def __init__(self, parent, controller=None):
        super().__init__(parent)
        self.controller = controller

        # Judul Halaman
        lbl_title = tk.Label(
            self,
            text=get_text("Tema Tampilan"),
            font=("Arial", 16, "bold")
        )
        lbl_title.pack(pady=15)

        # Label Pilih Tema
        lbl_pilih = tk.Label(self, text=get_text("Pilih Tema:"))
        lbl_pilih.pack(pady=5)

        # Combobox Tema
        self.var_tema = tk.StringVar(value=get_tema_aktif() or "Terang")
        self.cmb_tema = ttk.Combobox(
            self,
            textvariable=self.var_tema,
            values=["Terang", "Gelap"],
            state="readonly",
            width=15
        )
        self.cmb_tema.pack(pady=5)

        # Tombol Simpan
        btn_simpan = tk.Button(
            self,
            text=get_text("Simpan"),
            command=self.simpan_tema,
            bg="#4CAF50",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_simpan.pack(pady=10)

        # Tombol Kembali
        btn_back = tk.Button(
            self,
            text=get_text("Kembali"),
            command=self.kembali,
            bg="#f44336",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_back.pack(pady=5)

    def simpan_tema(self):
        """Simpan pilihan tema ke state"""
        tema = self.var_tema.get()
        set_tema_aktif(tema)
        messagebox.showinfo(
            get_text("Informasi"),
            get_text("Tema berhasil diubah ke {tema}", tema=tema)
        )

    def kembali(self):
        """Kembali ke menu pengaturan"""
        if self.controller:
            self.controller.show_frame("SubMenuPengaturan")
        else:
            self.destroy()

📂 Tambahan di utils_text.py

Supaya sinkron, kita tambahkan teks translasi untuk Tema Tampilan:

# Tambahan di kamus translations
"Tema Tampilan": "Tema Tampilan",
"Pilih Tema:": "Pilih Tema:",
"Tema berhasil diubah ke {tema}": "Tema berhasil diubah ke {tema}",

📌 Versi bahasa lain juga sama:

  • EN → "Display Theme", "Choose Theme:", "Theme successfully changed to {tema}".
  • MS → "Tema Tampilan", "Pilih Tema:", "Tema berjaya ditukar ke {tema}".

📂 Tambahan di setting_state.py

Untuk simpan state tema:

# setting_state.py

_tema_aktif = "Terang"  # default

def get_tema_aktif():
    return _tema_aktif

def set_tema_aktif(tema: str):
    global _tema_aktif
    _tema_aktif = tema

⚡ Dengan ini:

  • Di Menu Pengaturan, kalau klik Tema Tampilan, terbuka halaman pilihan tema.
  • Tema bisa dipilih → Simpan → muncul notifikasi pakai get_text (multibahasa).
  • Tersimpan di setting_state.

Mau saya bikinkan contoh integrasi sub_menu_tema_tampilan.py ini ke dalam SubMenuPengaturan, biar tombol "Tema Tampilan" langsung jalan?


Saya sudah membuat kode isi file menu_pengaturan.py berikut:

menu_pengaturan.py

import tkinter as tk

from Setting.sub_menu_ubah_profil_pengguna import show_sub_menu_profil_pengguna
from Setting.sub_menu_preferensi_nutrisi_mmag import show_sub_menu_preferensi_nutrisi_mma
from Setting.sub_menu_bahasa_aplikasi import open_sub_menu_bahasa_aplikasi
from Setting.sub_menu_tema_tampilan import SubMenuTemaTampilan

def show_menu_pengaturan(root_frame):
from menu_utama import tampilkan_halaman_utama # Import lokal agar aman

# Bersihkan frame sebelum tampilkan isi baru  
for widget in root_frame.winfo_children():  
    widget.destroy()  

# Judul  
label_title = tk.Label(  
    root_frame,  
    text="Pengaturan Aplikasi",  
    font=("Arial", 16, "bold"),  
    bg="light grey"  
)  
label_title.pack(pady=10)  

# Submenu daftar  
submenus = [  
    ("Ubah Profil Pengguna", lambda: show_sub_menu_profil_pengguna(root_frame)),  
    ("Setelan Preferensi Nutrisi MMAG", lambda: show_sub_menu_preferensi_nutrisi_mmag(root_frame)),  
    ("Filter Resep Sesuai Preferensi", lambda: show_sub_menu_filter_resep_mmag(root_frame)),     
    ("Bahasa Aplikasi", lambda: open_sub_menu_bahasa_aplikasi(root_frame, lambda: show_menu_pengaturan(root_frame))),  
      
    ("Tema Tampilan", lambda: SubMenuTemaTampilan(root_frame)),  
]  

for submenu in submenus:  
    if isinstance(submenu, tuple):  
        text, command = submenu  
        btn = tk.Button(  
            root_frame,  
            text=text,  
            font=("Arial", 12),  
            width=35,  
            height=2,  
            bg="#fce4ec",  
            fg="dark green",  
            command=command  
        )  
    else:  
        # Jika hanya string biasa  
        btn = tk.Button(  
            root_frame,  
            text=submenu,  
            font=("Arial", 12),  
            width=35,  
            height=2,  
            bg="#fce4ec",  
            fg="dark green"  
        )  
    btn.pack(pady=5)  

# Tombol kembali  
tombol_kembali = tk.Button(  
    root_frame,  
    text="Kembali ke Menu Utama",  
    font=("Arial", 12),  
    bg="#d9534f",  
    fg="white",  
    command=lambda: tampilkan_halaman_utama(root_frame)  
)  
tombol_kembali.pack(pady=30)
 

👍 Saya lihat struktur menu_pengaturan.py-nya sudah rapi, tapi ada beberapa hal yang perlu direvisi agar integrasi dengan SubMenuTemaTampilan berjalan mulus:

1. Bug Import

Di bagian ini ada typo:

from Setting.sub_menu_preferensi_nutrisi_mmag import show_sub_menu_preferensi_nutrisi_mma

➡️ harusnya konsisten namanya show_sub_menu_preferensi_nutrisi_mmag (pakai mmag, bukan mma).

2. Integrasi SubMenuTemaTampilan

Kelas SubMenuTemaTampilan adalah tk.Frame, jadi kalau langsung dipanggil dengan SubMenuTemaTampilan(root_frame) saja, dia belum ditampilkan ke layar. Kita perlu:

  • Bersihkan frame (destroy children)
  • Instansiasi SubMenuTemaTampilan
  • pack() supaya muncul

3. Sinkron Translasi

Karena kita sudah pakai utils_text.get_text, sebaiknya label dan tombol juga pakai itu biar otomatis ikut bahasa.


🔧 Revisi menu_pengaturan.py

# menu_pengaturan.py
import tkinter as tk

from Setting.sub_menu_ubah_profil_pengguna import show_sub_menu_profil_pengguna
from Setting.sub_menu_preferensi_nutrisi_mmag import show_sub_menu_preferensi_nutrisi_mmag
from Setting.sub_menu_bahasa_aplikasi import open_sub_menu_bahasa_aplikasi
from Setting.sub_menu_tema_tampilan import SubMenuTemaTampilan
from Setting.utils_text import get_text


def show_menu_pengaturan(root_frame):
    from menu_utama import tampilkan_halaman_utama  # Import lokal agar aman

    # Bersihkan frame sebelum tampilkan isi baru
    for widget in root_frame.winfo_children():
        widget.destroy()

    # Judul
    label_title = tk.Label(
        root_frame,
        text=get_text("Pengaturan Aplikasi"),
        font=("Arial", 16, "bold"),
        bg="light grey"
    )
    label_title.pack(pady=10)

    # Handler buka Tema Tampilan
    def open_tema_tampilan():
        for widget in root_frame.winfo_children():
            widget.destroy()
        frame_tema = SubMenuTemaTampilan(root_frame)
        frame_tema.pack(fill="both", expand=True)

    # Submenu daftar
    submenus = [
        (get_text("Ubah Profil Pengguna"), lambda: show_sub_menu_profil_pengguna(root_frame)),
        (get_text("Setelan Preferensi Nutrisi MMAG"), lambda: show_sub_menu_preferensi_nutrisi_mmag(root_frame)),
        (get_text("Filter Resep Sesuai Preferensi"), lambda: show_sub_menu_filter_resep_mmag(root_frame)),
        (get_text("Bahasa Aplikasi"), lambda: open_sub_menu_bahasa_aplikasi(root_frame, lambda: show_menu_pengaturan(root_frame))),
        (get_text("Tema Tampilan"), open_tema_tampilan),
    ]

    for text, command in submenus:
        btn = tk.Button(
            root_frame,
            text=text,
            font=("Arial", 12),
            width=35,
            height=2,
            bg="#fce4ec",
            fg="dark green",
            command=command
        )
        btn.pack(pady=5)

    # Tombol kembali
    tombol_kembali = tk.Button(
        root_frame,
        text=get_text("Kembali ke Menu Utama"),
        font=("Arial", 12),
        bg="#d9534f",
        fg="white",
        command=lambda: tampilkan_halaman_utama(root_frame)
    )
    tombol_kembali.pack(pady=30)

📌 Dengan revisi ini:

  • Semua teks ikut translasi (get_text).
  • Submenu Tema Tampilan sekarang tampil penuh dan bisa kembali ke Menu Pengaturan.
  • Typo import sudah beres.

Mau saya bikinkan juga show_sub_menu_filter_resep_mmag (yang sekarang dipanggil tapi belum ada di kodenya) supaya nggak error saat klik Filter Resep Sesuai Preferensi?


Kode isi file utils_text.py apakah begini:

Setting/utils_text.py

from Setting.translations import translations
from Setting.setting_state import get_bahasa_aktif

==============================

Kamus Translasi Lengkap

==============================

translations = {
"id": {
# Umum
"Informasi": "Informasi",
"Berhasil": "Berhasil",
"Gagal": "Gagal",
"Kembali": "Kembali",
"Simpan": "Simpan",
"Reset": "Reset",
"Ya": "Ya",
"Tidak": "Tidak",

    # Menu Utama  
    "Menu Utama": "Menu Utama",  
    "Rekomendasi Resep": "Rekomendasi Resep",  
    "Fitur Lengkap Resep MMAG_Human": "Fitur Lengkap Resep MMAG_Human",  
    "Pengaturan": "Pengaturan",  
    "Keluar": "Keluar",  

    # Menu Pengaturan  
    "Pengaturan Aplikasi": "Pengaturan Aplikasi",  
    "Ubah Profil Pengguna": "Ubah Profil Pengguna",  
    "Setelan Preferensi Nutrisi MMAG": "Setelan Preferensi Nutrisi MMAG",  
    "Filter Resep Sesuai Preferensi": "Filter Resep Sesuai Preferensi",  
    "Bahasa Aplikasi": "Bahasa Aplikasi",  
    "Tema Tampilan": "Tema Tampilan",  

    # Bahasa  
    "Pengaturan Bahasa": "Pengaturan Bahasa",  
    "Menu Bahasa Aplikasi": "Menu Bahasa Aplikasi",  
    "Pilih Bahasa:": "Pilih Bahasa:",  
    "Bahasa berhasil diubah ke {lang}": "Bahasa berhasil diubah ke {lang}",  

    # Resep  
    "Hitung Nutrisi": "Hitung Nutrisi",  
    "Preferensi Nutrisi": "Preferensi Nutrisi",  
    "Cari Resep": "Cari Resep",  
    "Hasil Rekomendasi": "Hasil Rekomendasi",  
    "Kategori Resep": "Kategori Resep",  
    "Bahan": "Bahan",  
    "Takaran": "Takaran",  
    "Tambah Bahan": "Tambah Bahan",  
    "Hapus Bahan": "Hapus Bahan",  
    "Simpan Resep": "Simpan Resep",  
    "Resep Tersimpan": "Resep Tersimpan",  
    "Tidak ada resep ditemukan": "Tidak ada resep ditemukan",  

    # Nutrisi  
    "Kalori": "Kalori",  
    "Protein": "Protein",  
    "Karbohidrat": "Karbohidrat",  
    "Lemak": "Lemak",  
    "Vitamin": "Vitamin",  
    "Mineral": "Mineral",  
    "Total Nutrisi": "Total Nutrisi",  
    "Preferensi Nutrisi Tersimpan": "Preferensi Nutrisi Tersimpan",  

    # Contoh format dinamis  
    "{jumlah} kalori": "{jumlah} kalori",  
    "Resep ditemukan: {n}": "Resep ditemukan: {n}",  
    "Total protein: {p} gram": "Total protein: {p} gram",  
},  
"en": {  
    # General  
    "Informasi": "Information",  
    "Berhasil": "Success",  
    "Gagal": "Failed",  
    "Kembali": "Back",  
    "Simpan": "Save",  
    "Reset": "Reset",  
    "Ya": "Yes",  
    "Tidak": "No",  

    # Main Menu  
    "Menu Utama": "Main Menu",  
    "Rekomendasi Resep": "Recipe Recommendations",  
    "Fitur Lengkap Resep MMAG_Human": "Full Recipe Features MMAG_Human",  
    "Pengaturan": "Settings",  
    "Keluar": "Exit",  

    # Settings Menu  
    "Pengaturan Aplikasi": "Application Settings",  
    "Ubah Profil Pengguna": "Edit User Profile",  
    "Setelan Preferensi Nutrisi MMAG": "MMAG Nutrition Preferences",  
    "Filter Resep Sesuai Preferensi": "Filter Recipes by Preference",  
    "Bahasa Aplikasi": "Application Language",  
    "Tema Tampilan": "Theme",  

    # Language  
    "Pengaturan Bahasa": "Language Settings",  
    "Menu Bahasa Aplikasi": "Application Language Menu",  
    "Pilih Bahasa:": "Choose Language:",  
    "Bahasa berhasil diubah ke {lang}": "Language successfully changed to {lang}",  

    # Recipes  
    "Hitung Nutrisi": "Calculate Nutrition",  
    "Preferensi Nutrisi": "Nutrition Preferences",  
    "Cari Resep": "Search Recipe",  
    "Hasil Rekomendasi": "Recommendation Results",  
    "Kategori Resep": "Recipe Category",  
    "Bahan": "Ingredient",  
    "Takaran": "Portion",  
    "Tambah Bahan": "Add Ingredient",  
    "Hapus Bahan": "Remove Ingredient",  
    "Simpan Resep": "Save Recipe",  
    "Resep Tersimpan": "Recipe Saved",  
    "Tidak ada resep ditemukan": "No recipes found",  

    # Nutrition  
    "Kalori": "Calories",  
    "Protein": "Protein",  
    "Karbohidrat": "Carbohydrates",  
    "Lemak": "Fat",  
    "Vitamin": "Vitamins",  
    "Mineral": "Minerals",  
    "Total Nutrisi": "Total Nutrition",  
    "Preferensi Nutrisi Tersimpan": "Nutrition Preferences Saved",  

    # Dynamic text  
    "{jumlah} kalori": "{jumlah} calories",  
    "Resep ditemukan: {n}": "Recipes found: {n}",  
    "Total protein: {p} gram": "Total protein: {p} grams",  
},  
"ms": {  
    # Umum  
    "Informasi": "Maklumat",  
    "Berhasil": "Berjaya",  
    "Gagal": "Gagal",  
    "Kembali": "Kembali",  
    "Simpan": "Simpan",  
    "Reset": "Set Semula",  
    "Ya": "Ya",  
    "Tidak": "Tidak",  

    # Menu Utama  
    "Menu Utama": "Menu Utama",  
    "Rekomendasi Resep": "Cadangan Resipi",  
    "Fitur Lengkap Resep MMAG_Human": "Ciri Lengkap Resipi MMAG_Human",  
    "Pengaturan": "Tetapan",  
    "Keluar": "Keluar",  

    # Menu Pengaturan  
    "Pengaturan Aplikasi": "Tetapan Aplikasi",  
    "Ubah Profil Pengguna": "Ubah Profil Pengguna",  
    "Setelan Preferensi Nutrisi MMAG": "Tetapan Preferensi Nutrisi MMAG",  
    "Filter Resep Sesuai Preferensi": "Tapis Resipi Ikut Preferensi",  
    "Bahasa Aplikasi": "Bahasa Aplikasi",  
    "Tema Tampilan": "Tema",  

    # Bahasa  
    "Pengaturan Bahasa": "Tetapan Bahasa",  
    "Menu Bahasa Aplikasi": "Menu Bahasa Aplikasi",  
    "Pilih Bahasa:": "Pilih Bahasa:",  
    "Bahasa berhasil diubah ke {lang}": "Bahasa berjaya ditukar ke {lang}",  

    # Resipi  
    "Hitung Nutrisi": "Kira Nutrisi",  
    "Preferensi Nutrisi": "Preferensi Nutrisi",  
    "Cari Resep": "Cari Resipi",  
    "Hasil Rekomendasi": "Hasil Cadangan",  
    "Kategori Resep": "Kategori Resipi",  
    "Bahan": "Bahan",  
    "Takaran": "Sukatan",  
    "Tambah Bahan": "Tambah Bahan",  
    "Hapus Bahan": "Padam Bahan",  
    "Simpan Resep": "Simpan Resipi",  
    "Resep Tersimpan": "Resipi Disimpan",  
    "Tidak ada resep ditemukan": "Tiada resipi dijumpai",  

    # Nutrisi  
    "Kalori": "Kalori",  
    "Protein": "Protein",  
    "Karbohidrat": "Karbohidrat",  
    "Lemak": "Lemak",  
    "Vitamin": "Vitamin",  
    "Mineral": "Mineral",  
    "Total Nutrisi": "Jumlah Nutrisi",  
    "Preferensi Nutrisi Tersimpan": "Preferensi Nutrisi Disimpan",  

    # Dynamic text  
    "{jumlah} kalori": "{jumlah} kalori",  
    "Resep ditemukan: {n}": "Resipi dijumpai: {n}",  
    "Total protein: {p} gram": "Jumlah protein: {p} gram",  
}  

}

def set_language(lang_code: str):
"""Set bahasa aplikasi"""
global current_lang
if lang_code in translations:
current_lang = lang_code
else:
current_lang = "id" # fallback

==============================

Fungsi Akses

==============================

def get_text(key: str, **kwargs) -> str:
"""Ambil teks sesuai bahasa aktif dengan dukungan format dinamis"""
lang = get_bahasa_aktif() or "id"
lang_dict = translations.get(lang, translations["id"])
text = lang_dict.get(key, key) # fallback ke key jika tidak ada

if kwargs:  
    try:  
        return text.format(**kwargs)  
    except Exception:  
        return text  # kalau format gagal, kembalikan plain  

return text  

Tambahan di kamus translations

"Tema Tampilan": "Tema Tampilan",
"Pilih Tema:": "Pilih Tema:",
"Tema berhasil diubah ke {tema}": "Tema berhasil diubah ke {tema}",


Saya sudah review kode utils_text.py yang Anda tuliskan. Struktur utamanya sudah benar dan lengkap, hanya ada beberapa poin kecil yang perlu dirapikan:

Catatan:

  1. Duplikasi import translations
    Di awal Anda tulis:

    from Setting.translations import translations
    

    lalu di bawah Anda definisikan ulang translations = {...}.
    Ini akan menimpa import tadi. Jadi, sebaiknya hapus baris import pertama agar tidak membingungkan.

  2. Penambahan entri tema
    Di bagian paling bawah Anda tambahkan string seperti ini:

    "Tema Tampilan": "Tema Tampilan",
    "Pilih Tema:": "Pilih Tema:",
    "Tema berhasil diubah ke {tema}": "Tema berhasil diubah ke {tema}",
    

    Penempatan ini keliru, karena ada di luar dictionary translations.
    Harusnya masuk ke masing-masing blok bahasa (id, en, ms).


Revisi utils_text.py yang lebih rapi:

# Setting/utils_text.py
from Setting.setting_state import get_bahasa_aktif

# ==============================
# Kamus Translasi Lengkap
# ==============================
translations = {
    "id": {
        # Umum
        "Informasi": "Informasi",
        "Berhasil": "Berhasil",
        "Gagal": "Gagal",
        "Kembali": "Kembali",
        "Simpan": "Simpan",
        "Reset": "Reset",
        "Ya": "Ya",
        "Tidak": "Tidak",

        # Menu Utama
        "Menu Utama": "Menu Utama",
        "Rekomendasi Resep": "Rekomendasi Resep",
        "Fitur Lengkap Resep MMAG_Human": "Fitur Lengkap Resep MMAG_Human",
        "Pengaturan": "Pengaturan",
        "Keluar": "Keluar",

        # Menu Pengaturan
        "Pengaturan Aplikasi": "Pengaturan Aplikasi",
        "Ubah Profil Pengguna": "Ubah Profil Pengguna",
        "Setelan Preferensi Nutrisi MMAG": "Setelan Preferensi Nutrisi MMAG",
        "Filter Resep Sesuai Preferensi": "Filter Resep Sesuai Preferensi",
        "Bahasa Aplikasi": "Bahasa Aplikasi",
        "Tema Tampilan": "Tema Tampilan",

        # Bahasa
        "Pengaturan Bahasa": "Pengaturan Bahasa",
        "Menu Bahasa Aplikasi": "Menu Bahasa Aplikasi",
        "Pilih Bahasa:": "Pilih Bahasa:",
        "Bahasa berhasil diubah ke {lang}": "Bahasa berhasil diubah ke {lang}",

        # Tema
        "Pilih Tema:": "Pilih Tema:",
        "Tema berhasil diubah ke {tema}": "Tema berhasil diubah ke {tema}",

        # Resep
        "Hitung Nutrisi": "Hitung Nutrisi",
        "Preferensi Nutrisi": "Preferensi Nutrisi",
        "Cari Resep": "Cari Resep",
        "Hasil Rekomendasi": "Hasil Rekomendasi",
        "Kategori Resep": "Kategori Resep",
        "Bahan": "Bahan",
        "Takaran": "Takaran",
        "Tambah Bahan": "Tambah Bahan",
        "Hapus Bahan": "Hapus Bahan",
        "Simpan Resep": "Simpan Resep",
        "Resep Tersimpan": "Resep Tersimpan",
        "Tidak ada resep ditemukan": "Tidak ada resep ditemukan",

        # Nutrisi
        "Kalori": "Kalori",
        "Protein": "Protein",
        "Karbohidrat": "Karbohidrat",
        "Lemak": "Lemak",
        "Vitamin": "Vitamin",
        "Mineral": "Mineral",
        "Total Nutrisi": "Total Nutrisi",
        "Preferensi Nutrisi Tersimpan": "Preferensi Nutrisi Tersimpan",

        # Contoh format dinamis
        "{jumlah} kalori": "{jumlah} kalori",
        "Resep ditemukan: {n}": "Resep ditemukan: {n}",
        "Total protein: {p} gram": "Total protein: {p} gram",
    },
    "en": {
        # General
        "Informasi": "Information",
        "Berhasil": "Success",
        "Gagal": "Failed",
        "Kembali": "Back",
        "Simpan": "Save",
        "Reset": "Reset",
        "Ya": "Yes",
        "Tidak": "No",

        # Main Menu
        "Menu Utama": "Main Menu",
        "Rekomendasi Resep": "Recipe Recommendations",
        "Fitur Lengkap Resep MMAG_Human": "Full Recipe Features MMAG_Human",
        "Pengaturan": "Settings",
        "Keluar": "Exit",

        # Settings Menu
        "Pengaturan Aplikasi": "Application Settings",
        "Ubah Profil Pengguna": "Edit User Profile",
        "Setelan Preferensi Nutrisi MMAG": "MMAG Nutrition Preferences",
        "Filter Resep Sesuai Preferensi": "Filter Recipes by Preference",
        "Bahasa Aplikasi": "Application Language",
        "Tema Tampilan": "Theme",

        # Language
        "Pengaturan Bahasa": "Language Settings",
        "Menu Bahasa Aplikasi": "Application Language Menu",
        "Pilih Bahasa:": "Choose Language:",
        "Bahasa berhasil diubah ke {lang}": "Language successfully changed to {lang}",

        # Theme
        "Pilih Tema:": "Choose Theme:",
        "Tema berhasil diubah ke {tema}": "Theme successfully changed to {tema}",

        # Recipes
        "Hitung Nutrisi": "Calculate Nutrition",
        "Preferensi Nutrisi": "Nutrition Preferences",
        "Cari Resep": "Search Recipe",
        "Hasil Rekomendasi": "Recommendation Results",
        "Kategori Resep": "Recipe Category",
        "Bahan": "Ingredient",
        "Takaran": "Portion",
        "Tambah Bahan": "Add Ingredient",
        "Hapus Bahan": "Remove Ingredient",
        "Simpan Resep": "Save Recipe",
        "Resep Tersimpan": "Recipe Saved",
        "Tidak ada resep ditemukan": "No recipes found",

        # Nutrition
        "Kalori": "Calories",
        "Protein": "Protein",
        "Karbohidrat": "Carbohydrates",
        "Lemak": "Fat",
        "Vitamin": "Vitamins",
        "Mineral": "Minerals",
        "Total Nutrisi": "Total Nutrition",
        "Preferensi Nutrisi Tersimpan": "Nutrition Preferences Saved",

        # Dynamic text
        "{jumlah} kalori": "{jumlah} calories",
        "Resep ditemukan: {n}": "Recipes found: {n}",
        "Total protein: {p} gram": "Total protein: {p} grams",
    },
    "ms": {
        # Umum
        "Informasi": "Maklumat",
        "Berhasil": "Berjaya",
        "Gagal": "Gagal",
        "Kembali": "Kembali",
        "Simpan": "Simpan",
        "Reset": "Set Semula",
        "Ya": "Ya",
        "Tidak": "Tidak",

        # Menu Utama
        "Menu Utama": "Menu Utama",
        "Rekomendasi Resep": "Cadangan Resipi",
        "Fitur Lengkap Resep MMAG_Human": "Ciri Lengkap Resipi MMAG_Human",
        "Pengaturan": "Tetapan",
        "Keluar": "Keluar",

        # Menu Pengaturan
        "Pengaturan Aplikasi": "Tetapan Aplikasi",
        "Ubah Profil Pengguna": "Ubah Profil Pengguna",
        "Setelan Preferensi Nutrisi MMAG": "Tetapan Preferensi Nutrisi MMAG",
        "Filter Resep Sesuai Preferensi": "Tapis Resipi Ikut Preferensi",
        "Bahasa Aplikasi": "Bahasa Aplikasi",
        "Tema Tampilan": "Tema",

        # Bahasa
        "Pengaturan Bahasa": "Tetapan Bahasa",
        "Menu Bahasa Aplikasi": "Menu Bahasa Aplikasi",
        "Pilih Bahasa:": "Pilih Bahasa:",
        "Bahasa berhasil diubah ke {lang}": "Bahasa berjaya ditukar ke {lang}",

        # Tema
        "Pilih Tema:": "Pilih Tema:",
        "Tema berhasil diubah ke {tema}": "Tema berjaya ditukar ke {tema}",

        # Resipi
        "Hitung Nutrisi": "Kira Nutrisi",
        "Preferensi Nutrisi": "Preferensi Nutrisi",
        "Cari Resep": "Cari Resipi",
        "Hasil Rekomendasi": "Hasil Cadangan",
        "Kategori Resep": "Kategori Resipi",
        "Bahan": "Bahan",
        "Takaran": "Sukatan",
        "Tambah Bahan": "Tambah Bahan",
        "Hapus Bahan": "Padam Bahan",
        "Simpan Resep": "Simpan Resipi",
        "Resep Tersimpan": "Resipi Disimpan",
        "Tidak ada resep ditemukan": "Tiada resipi dijumpai",

        # Nutrisi
        "Kalori": "Kalori",
        "Protein": "Protein",
        "Karbohidrat": "Karbohidrat",
        "Lemak": "Lemak",
        "Vitamin": "Vitamin",
        "Mineral": "Mineral",
        "Total Nutrisi": "Jumlah Nutrisi",
        "Preferensi Nutrisi Tersimpan": "Preferensi Nutrisi Disimpan",

        # Dynamic text
        "{jumlah} kalori": "{jumlah} kalori",
        "Resep ditemukan: {n}": "Resipi dijumpai: {n}",
        "Total protein: {p} gram": "Jumlah protein: {p} gram",
    }
}

# ==============================
# Fungsi Set dan Get Bahasa
# ==============================
current_lang = "id"

def set_language(lang_code: str):
    """Set bahasa aplikasi"""
    global current_lang
    if lang_code in translations:
        current_lang = lang_code
    else:
        current_lang = "id"  # fallback

def get_text(key: str, **kwargs) -> str:
    """Ambil teks sesuai bahasa aktif dengan dukungan format dinamis"""
    lang = get_bahasa_aktif() or current_lang
    lang_dict = translations.get(lang, translations["id"])
    text = lang_dict.get(key, key)  # fallback ke key jika tidak ada

    if kwargs:
        try:
            return text.format(**kwargs)
        except Exception:
            return text  # kalau format gagal, kembalikan plain

    return text

Mau saya bikinkan juga contoh nyata integrasi get_text untuk submenu Tema Tampilan supaya langsung terlihat bagaimana Pilih Tema: dan Tema berhasil diubah ke {tema} bekerja?


Ketika tombol "Kembali" ditekan, program tidak mau kembali ke Menu Pengaturan.

Oke 👍 saya paham masalahnya.
Penyebab utama biasanya karena tombol "Kembali" di dalam sub_menu_bahasa_aplikasi.py (atau sub_menu_tema_tampilan.py) tidak diarahkan ke show_menu_pengaturan(root_frame), melainkan hanya root_frame.destroy() atau tidak ada callback yang benar.


Cara perbaikan

Supaya tombol Kembali bisa benar-benar balik ke Menu Pengaturan, kita perlu:

  1. Pastikan show_menu_pengaturan di-import secara lokal agar tidak menimbulkan circular import.
    Misalnya:

    from menu_pengaturan import show_menu_pengaturan
    
  2. Pada tombol "Kembali", ganti command menjadi:

    command=lambda: show_menu_pengaturan(root_frame)
    

Contoh revisi di sub_menu_bahasa_aplikasi.py

import tkinter as tk
from tkinter import ttk, messagebox
from Setting.utils_text import get_text, set_language
from Setting.setting_state import set_bahasa_aktif, get_bahasa_aktif

def open_sub_menu_bahasa_aplikasi(root_frame, on_back_callback=None):
    # Bersihkan frame
    for widget in root_frame.winfo_children():
        widget.destroy()

    # Judul
    label_title = tk.Label(
        root_frame,
        text=get_text("Pengaturan Bahasa"),
        font=("Arial", 16, "bold")
    )
    label_title.pack(pady=10)

    # Dropdown bahasa
    label_bahasa = tk.Label(root_frame, text=get_text("Pilih Bahasa:"))
    label_bahasa.pack(pady=5)

    combo = ttk.Combobox(
        root_frame,
        values=["id", "en", "ms"],
        state="readonly"
    )
    combo.set(get_bahasa_aktif() or "id")
    combo.pack(pady=5)

    def simpan_bahasa():
        selected = combo.get()
        set_bahasa_aktif(selected)
        set_language(selected)
        messagebox.showinfo(get_text("Informasi"), get_text("Bahasa berhasil diubah ke {lang}", lang=selected))

    btn_simpan = tk.Button(root_frame, text=get_text("Simpan"), command=simpan_bahasa)
    btn_simpan.pack(pady=10)

    # Tombol kembali
    if on_back_callback:
        btn_back = tk.Button(
            root_frame,
            text=get_text("Kembali"),
            bg="#d9534f", fg="white",
            command=on_back_callback   # ← langsung panggil callback dari menu_pengaturan
        )
    else:
        from menu_pengaturan import show_menu_pengaturan
        btn_back = tk.Button(
            root_frame,
            text=get_text("Kembali"),
            bg="#d9534f", fg="white",
            command=lambda: show_menu_pengaturan(root_frame)   # fallback
        )
    btn_back.pack(pady=20)

👉 Jadi ketika Anda buka sub_menu_bahasa_aplikasi dari menu_pengaturan.py, panggil begini:

("Bahasa Aplikasi", lambda: open_sub_menu_bahasa_aplikasi(root_frame, lambda: show_menu_pengaturan(root_frame))),

Mau saya buatkan juga revisi serupa untuk sub_menu_tema_tampilan.py supaya tombol "Kembali" di sana juga benar-benar balik ke Menu Pengaturan?


Maksud saya, pada sub_menu_tema_tampilan.py in, Ketika tombol "Kembali" ditekan, program tidak mau kembali ke Menu Pengaturan.
Kode:
import tkinter as tk
from tkinter import ttk, messagebox

from Setting.utils_text import get_text
from Setting.setting_state import get_tema_aktif, set_tema_aktif

class SubMenuTemaTampilan(tk.Frame):
def init(self, parent, controller=None):
super().init(parent)
self.controller = controller

    # Judul Halaman  
    lbl_title = tk.Label(  
        self,  
        text=get_text("Tema Tampilan"),  
        font=("Arial", 16, "bold")  
    )  
    lbl_title.pack(pady=15)  

    # Label Pilih Tema  
    lbl_pilih = tk.Label(self, text=get_text("Pilih Tema:"))  
    lbl_pilih.pack(pady=5)  

    # Combobox Tema  
    self.var_tema = tk.StringVar(value=get_tema_aktif() or "Terang")  
    self.cmb_tema = ttk.Combobox(  
        self,  
        textvariable=self.var_tema,  
        values=["Terang", "Gelap"],  
        state="readonly",  
        width=15  
    )  
    self.cmb_tema.pack(pady=5)  

    # Tombol Simpan  
    btn_simpan = tk.Button(  
        self,  
        text=get_text("Simpan"),  
        command=self.simpan_tema,  
        bg="#4CAF50",  
        fg="white",  
        relief="raised",  
        padx=10, pady=5  
    )  
    btn_simpan.pack(pady=10)  

    # Tombol Kembali  
    btn_back = tk.Button(  
        self,  
        text=get_text("Kembali"),  
        command=self.kembali,  
        bg="#f44336",  
        fg="white",  
        relief="raised",  
        padx=10, pady=5  
    )  
    btn_back.pack(pady=5)  

def simpan_tema(self):  
    """Simpan pilihan tema ke state"""  
    tema = self.var_tema.get()  
    set_tema_aktif(tema)  
    messagebox.showinfo(  
        get_text("Informasi"),  
        get_text("Tema berhasil diubah ke {tema}", tema=tema)  
    )  

def kembali(self):  
    """Kembali ke menu pengaturan"""  
    if self.controller:  
        self.controller.show_frame("SubMenuPengaturan")  
    else:  
        self.destroy()

Oke 👍 saya paham masalahnya.
Penyebab tombol "Kembali" tidak berfungsi biasanya ada di bagian controller. Dari kode yang kamu kirim, self.controller.show_frame("SubMenuPengaturan") dipanggil, tapi kalau:

  1. controller tidak pernah diberikan saat membuat SubMenuTemaTampilan, maka self.controller = None.
  2. Atau nama frame yang dipanggil ("SubMenuPengaturan") tidak sama dengan nama key yang terdaftar di controller.frames.

Solusi

Pastikan di menu_utama.py atau modul utama yang mengatur navigasi frame:

  • SubMenuPengaturan memang sudah ditambahkan ke dalam frames.
  • Nama key yang dipakai cocok (case sensitive).
  • Saat membuat SubMenuTemaTampilan, controller benar-benar diberikan.

Contoh revisi sub_menu_tema_tampilan.py supaya lebih aman:

import tkinter as tk
from tkinter import ttk, messagebox

from Setting.utils_text import get_text
from Setting.setting_state import get_tema_aktif, set_tema_aktif


class SubMenuTemaTampilan(tk.Frame):
    def __init__(self, parent, controller=None):
        super().__init__(parent)
        self.controller = controller

        # Judul Halaman
        lbl_title = tk.Label(
            self,
            text=get_text("Tema Tampilan"),
            font=("Arial", 16, "bold")
        )
        lbl_title.pack(pady=15)

        # Label Pilih Tema
        lbl_pilih = tk.Label(self, text=get_text("Pilih Tema:"))
        lbl_pilih.pack(pady=5)

        # Combobox Tema
        self.var_tema = tk.StringVar(value=get_tema_aktif() or "Terang")
        self.cmb_tema = ttk.Combobox(
            self,
            textvariable=self.var_tema,
            values=["Terang", "Gelap"],
            state="readonly",
            width=15
        )
        self.cmb_tema.pack(pady=5)

        # Tombol Simpan
        btn_simpan = tk.Button(
            self,
            text=get_text("Simpan"),
            command=self.simpan_tema,
            bg="#4CAF50",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_simpan.pack(pady=10)

        # Tombol Kembali
        btn_back = tk.Button(
            self,
            text=get_text("Kembali"),
            command=self.kembali,
            bg="#f44336",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_back.pack(pady=5)

    def simpan_tema(self):
        """Simpan pilihan tema ke state"""
        tema = self.var_tema.get()
        set_tema_aktif(tema)
        messagebox.showinfo(
            get_text("Informasi"),
            get_text("Tema berhasil diubah ke {tema}", tema=tema)
        )

    def kembali(self):
        """Kembali ke menu pengaturan"""
        if self.controller:
            try:
                self.controller.show_frame("SubMenuPengaturan")
            except KeyError:
                messagebox.showwarning(
                    get_text("Peringatan"),
                    get_text("Frame 'SubMenuPengaturan' tidak ditemukan di controller")
                )
        else:
            self.destroy()

👉 Jadi yang perlu dicek:

  1. Apakah SubMenuPengaturan sudah ada di controller?
    Misalnya di menu_utama.py:
    self.frames["SubMenuPengaturan"] = SubMenuPengaturan(parent=self.container, controller=self)
    
  2. Apakah nama string "SubMenuPengaturan" sesuai? (jangan salah tulis "submenuPengaturan" atau lain).

Mau saya bikinkan contoh potongan menu_utama.py biar jelas gimana cara mendaftarkan SubMenuPengaturan dan SubMenuTemaTampilan di controller?


Untuk Fungsi tombol "Kembali" gunakan kode yang ini saja:
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)

tk.Button(frame_btn_bottom, text="↩ Kembali", width=12, bg="#757575", fg="white", command=kembali).pack(pady=5)

  

Sip ✅ kalau begitu berarti konsep navigasi aplikasimu bukan pakai controller/show_frame, tapi pakai destroy + load ulang frame (manual switching).

Maka fungsi kembali() di sub_menu_tema_tampilan.py sebaiknya langsung pakai pola yang kamu kasih. Contohnya:

import tkinter as tk
from tkinter import ttk, messagebox

from Setting.utils_text import get_text
from Setting.setting_state import get_tema_aktif, set_tema_aktif


class SubMenuTemaTampilan(tk.Frame):
    def __init__(self, root_frame):
        super().__init__(root_frame)
        self.root_frame = root_frame

        # Judul Halaman
        lbl_title = tk.Label(
            self,
            text=get_text("Tema Tampilan"),
            font=("Arial", 16, "bold")
        )
        lbl_title.pack(pady=15)

        # Label Pilih Tema
        lbl_pilih = tk.Label(self, text=get_text("Pilih Tema:"))
        lbl_pilih.pack(pady=5)

        # Combobox Tema
        self.var_tema = tk.StringVar(value=get_tema_aktif() or "Terang")
        self.cmb_tema = ttk.Combobox(
            self,
            textvariable=self.var_tema,
            values=["Terang", "Gelap"],
            state="readonly",
            width=15
        )
        self.cmb_tema.pack(pady=5)

        # Tombol Simpan
        btn_simpan = tk.Button(
            self,
            text=get_text("Simpan"),
            command=self.simpan_tema,
            bg="#4CAF50",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_simpan.pack(pady=10)

        # Tombol Kembali
        btn_back = tk.Button(
            self,
            text="↩ " + get_text("Kembali"),
            command=self.kembali,
            bg="#757575",
            fg="white",
            width=12,
            relief="raised",
            padx=10, pady=5
        )
        btn_back.pack(pady=5)

    def simpan_tema(self):
        """Simpan pilihan tema ke state"""
        tema = self.var_tema.get()
        set_tema_aktif(tema)
        messagebox.showinfo(
            get_text("Informasi"),
            get_text("Tema berhasil diubah ke {tema}", tema=tema)
        )

    def kembali(self):
        """Kembali ke menu pengaturan"""
        for w in self.root_frame.winfo_children():
            w.destroy()
        from menu_pengaturan import show_menu_pengaturan
        show_menu_pengaturan(self.root_frame)

🔑 Perbedaan utama:

  • Konstruktor hanya butuh root_frame (nggak pakai controller lagi).
  • Tombol kembali menghancurkan (destroy) semua widget di root_frame, lalu load ulang menu_pengaturan.

Mau saya bikinkan juga contoh file menu_pengaturan.py dengan fungsi show_menu_pengaturan(root_frame) supaya langsung nyambung dengan kode ini?


Pada sub_menu_tema_tampilan.py, Ketika tema di combobox dipilih, misalnya: tema gelap, lalu tombol "Simpan" ditekan, maka tidak ada perubahan apa apa yang terjadi

Betul 🔍, penyebabnya karena di kode sub_menu_tema_tampilan.py kamu sudah simpan state tema (set_tema_aktif(tema)) dan munculkan popup, tapi belum ada fungsi yang benar-benar mengubah warna tema pada seluruh elemen Tkinter.

Jadi meskipun nilai tersimpan, UI tetap sama.


📌 Solusi:
Kita perlu tambahkan fungsi untuk menerapkan tema (ubah background, warna teks, warna tombol, dsb.) setelah tombol Simpan ditekan.

Contoh revisi sub_menu_tema_tampilan.py:

import tkinter as tk
from tkinter import ttk, messagebox

from Setting.utils_text import get_text
from Setting.setting_state import get_tema_aktif, set_tema_aktif


class SubMenuTemaTampilan(tk.Frame):
    def __init__(self, root_frame):
        super().__init__(root_frame)
        self.root_frame = root_frame

        # Judul Halaman
        lbl_title = tk.Label(
            self,
            text=get_text("Tema Tampilan"),
            font=("Arial", 16, "bold")
        )
        lbl_title.pack(pady=15)

        # Label Pilih Tema
        lbl_pilih = tk.Label(self, text=get_text("Pilih Tema:"))
        lbl_pilih.pack(pady=5)

        # Combobox Tema
        self.var_tema = tk.StringVar(value=get_tema_aktif() or "Terang")
        self.cmb_tema = ttk.Combobox(
            self,
            textvariable=self.var_tema,
            values=["Terang", "Gelap"],
            state="readonly",
            width=15
        )
        self.cmb_tema.pack(pady=5)

        # Tombol Simpan
        btn_simpan = tk.Button(
            self,
            text=get_text("Simpan"),
            command=self.simpan_tema,
            bg="#4CAF50",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_simpan.pack(pady=10)

        # Tombol Kembali
        btn_back = tk.Button(
            self,
            text=get_text("Kembali"),
            command=lambda: self.kembali(root_frame),
            bg="#f44336",
            fg="white",
            relief="raised",
            padx=10, pady=5
        )
        btn_back.pack(pady=5)

    def simpan_tema(self):
        """Simpan pilihan tema ke state dan terapkan"""
        tema = self.var_tema.get()
        set_tema_aktif(tema)

        # Terapkan tema ke root
        self.terapkan_tema(tema)

        # Info popup
        messagebox.showinfo(
            get_text("Informasi"),
            get_text("Tema berhasil diubah ke {tema}", tema=tema)
        )

    def terapkan_tema(self, tema):
        """Update warna background/foreground seluruh root_frame"""
        if tema == "Gelap":
            bg_color = "#2e2e2e"
            fg_color = "white"
            btn_bg = "#444"
            btn_fg = "white"
        else:  # default terang
            bg_color = "white"
            fg_color = "black"
            btn_bg = "#f0f0f0"
            btn_fg = "black"

        # Update root_frame dan anak-anaknya
        root = self.root_frame.winfo_toplevel()
        root.configure(bg=bg_color)
        self.update_colors(self.root_frame, bg_color, fg_color, btn_bg, btn_fg)

    def update_colors(self, widget, bg, fg, btn_bg, btn_fg):
        """Recursive: update semua child widgets"""
        for child in widget.winfo_children():
            if isinstance(child, tk.Frame):
                child.configure(bg=bg)
                self.update_colors(child, bg, fg, btn_bg, btn_fg)
            elif isinstance(child, tk.Label):
                child.configure(bg=bg, fg=fg)
            elif isinstance(child, tk.Button):
                child.configure(bg=btn_bg, fg=btn_fg)
            elif isinstance(child, ttk.Combobox):
                # ttk widget agak terbatas warnanya
                style = ttk.Style()
                style.theme_use("default")
                style.configure("TCombobox", fieldbackground=bg, foreground=fg)
            else:
                try:
                    child.configure(bg=bg, fg=fg)
                except:
                    pass

    def kembali(self, root_frame):
        """Kembali ke menu pengaturan"""
        for w in root_frame.winfo_children():
            w.destroy()
        from menu_pengaturan import show_menu_pengaturan
        show_menu_pengaturan(root_frame)

📌 Yang ditambahkan:

  • terapkan_tema(tema) → fungsi utama ubah warna root + widget.
  • update_colors(widget, ...) → rekursif supaya semua widget anak juga ikut berubah.
  • Panggil self.terapkan_tema(tema) setelah tombol Simpan ditekan.

Mau saya bikinkan juga contoh integrasi awal tema (supaya saat app pertama kali dibuka, tema aktif otomatis diterapkan sesuai setting_state)?


Mau kamu bikinkan contoh integrasi awal tema (supaya saat app pertama kali dibuka, tema aktif otomatis diterapkan sesuai setting_state).

Oke 👍
Kalau kamu ingin tema otomatis diterapkan sejak awal aplikasi dibuka, berarti setelah root = tk.Tk() dibuat, kamu harus cek tema aktif dari setting_state → lalu langsung jalankan fungsi terapkan_tema.

Berikut contoh integrasi awalnya:


🔹 File app_main.py (contoh entry point utama)

import tkinter as tk

from Setting.setting_state import get_tema_aktif
from Setting.sub_menu_tema_tampilan import SubMenuTemaTampilan
from menu_utama import tampilkan_halaman_utama


def terapkan_tema_awal(root, tema):
    """Terapkan tema aktif saat startup"""
    if tema == "Gelap":
        bg_color = "#2e2e2e"
        fg_color = "white"
        btn_bg = "#444"
        btn_fg = "white"
    else:  # default Terang
        bg_color = "white"
        fg_color = "black"
        btn_bg = "#f0f0f0"
        btn_fg = "black"

    root.configure(bg=bg_color)

    # Fungsi helper rekursif
    def update_colors(widget):
        for child in widget.winfo_children():
            if isinstance(child, tk.Frame):
                child.configure(bg=bg_color)
                update_colors(child)
            elif isinstance(child, tk.Label):
                child.configure(bg=bg_color, fg=fg_color)
            elif isinstance(child, tk.Button):
                child.configure(bg=btn_bg, fg=btn_fg)
            else:
                try:
                    child.configure(bg=bg_color, fg=fg_color)
                except:
                    pass

    update_colors(root)


def main():
    root = tk.Tk()
    root.title("Aplikasi Nutrisi MMAG")

    # 🔹 Ambil tema aktif dari setting_state
    tema = get_tema_aktif() or "Terang"

    # 🔹 Terapkan tema awal
    terapkan_tema_awal(root, tema)

    # Tampilkan menu utama
    tampilkan_halaman_utama(root)

    root.mainloop()


if __name__ == "__main__":
    main()

🔹 Integrasi dengan SubMenuTemaTampilan

Karena SubMenuTemaTampilan tadi sudah punya terapkan_tema(tema) dan update_colors, maka:

  • Saat user klik Simpan, perubahan langsung kelihatan.
  • Saat app dibuka kembali, app_main.py langsung baca get_tema_aktif() dan panggil terapkan_tema_awal.

📌 Dengan begini, user akan melihat tema terakhir yang dipilih langsung diterapkan saat aplikasi dibuka kembali.


Mau saya rapikan juga supaya fungsi terapkan_tema_awal dan update_colors digabungkan ke dalam satu modul theme_utils.py, biar tidak ada duplikasi antara app_main.py dan sub_menu_tema_tampilan.py?


Bagaimana cara menerapkan tema tampilan ini pada fitur menu atau sub menu lainnya yang ada di dalam applikasi ini.

Mantap, ini pertanyaan penting 👍

Supaya tema tampilan konsisten ke semua menu / sub menu, prinsipnya begini:


🔑 Prinsip Penerapan Tema

  1. Tema aktif disimpan di setting_state.py (misalnya tema_aktif = "Gelap").
  2. Buat satu fungsi global misalnya terapkan_tema(root_frame) di theme_utils.py.
    • Fungsi ini akan mewarnai ulang semua widget (Label, Button, Frame, dsb).
  3. Setiap kali kamu membuka menu / submenu baru (misalnya show_menu_pengaturan, show_sub_menu_bahasa_aplikasi, dll), langsung panggil terapkan_tema(root_frame) setelah membangun layout.

🔹 Contoh Implementasi

1. File Setting/theme_utils.py

import tkinter as tk
from Setting.setting_state import get_tema_aktif

def terapkan_tema(widget):
    """Terapkan tema ke widget dan turunannya"""
    tema = get_tema_aktif() or "Terang"

    if tema == "Gelap":
        bg_color = "#2e2e2e"
        fg_color = "white"
        btn_bg = "#444"
        btn_fg = "white"
    else:  # Terang
        bg_color = "white"
        fg_color = "black"
        btn_bg = "#f0f0f0"
        btn_fg = "black"

    # Atur warna widget sesuai tipenya
    if isinstance(widget, tk.Frame):
        widget.configure(bg=bg_color)
    elif isinstance(widget, tk.Label):
        widget.configure(bg=bg_color, fg=fg_color)
    elif isinstance(widget, tk.Button):
        widget.configure(bg=btn_bg, fg=btn_fg)

    # Rekursif ke anak-anaknya
    for child in widget.winfo_children():
        terapkan_tema(child)

2. Pemakaian di menu_pengaturan.py

from Setting.theme_utils import terapkan_tema

def show_menu_pengaturan(root_frame):
    from menu_utama import tampilkan_halaman_utama  

    for widget in root_frame.winfo_children():
        widget.destroy()

    label_title = tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 16, "bold"))
    label_title.pack(pady=10)

    submenus = [
        ("Ubah Profil Pengguna", lambda: show_sub_menu_profil_pengguna(root_frame)),
        ("Setelan Preferensi Nutrisi MMAG", lambda: show_sub_menu_preferensi_nutrisi_mmag(root_frame)),
        ("Bahasa Aplikasi", lambda: open_sub_menu_bahasa_aplikasi(root_frame, lambda: show_menu_pengaturan(root_frame))),
        ("Tema Tampilan", lambda: SubMenuTemaTampilan(root_frame)),
    ]

    for text, command in submenus:
        btn = tk.Button(root_frame, text=text, width=35, height=2, command=command)
        btn.pack(pady=5)

    tombol_kembali = tk.Button(
        root_frame,
        text="Kembali ke Menu Utama",
        command=lambda: tampilkan_halaman_utama(root_frame)
    )
    tombol_kembali.pack(pady=30)

    # 🔹 Terapkan tema setelah semua widget dibuat
    terapkan_tema(root_frame)

3. Pemakaian di Sub Menu lain (misalnya sub_menu_bahasa_aplikasi.py)

from Setting.theme_utils import terapkan_tema

def open_sub_menu_bahasa_aplikasi(root_frame, go_back_callback):
    for w in root_frame.winfo_children():
        w.destroy()

    lbl_title = tk.Label(root_frame, text="Pengaturan Bahasa", font=("Arial", 16, "bold"))
    lbl_title.pack(pady=10)

    # tombol kembali
    btn_back = tk.Button(root_frame, text="Kembali", command=go_back_callback)
    btn_back.pack(pady=10)

    # 🔹 Terapkan tema di sini juga
    terapkan_tema(root_frame)

📌 Hasilnya

  • Tema selalu mengikuti setting_state (Gelap / Terang).
  • Saat user berpindah menu, tema langsung diterapkan tanpa perlu bikin logika pewarnaan di tiap file.
  • Kalau user ganti tema lewat SubMenuTemaTampilan, cukup panggil terapkan_tema(root_frame) di akhir fungsi simpan.

Apakah kamu mau saya bikinkan contoh refaktor SubMenuTemaTampilan juga, supaya saat klik Simpan, dia langsung panggil terapkan_tema(root_frame) ke seluruh halaman aktif?


Berikut ini hasil akhir yang diperoleh:

- Kode isi file menu_pengaturan.py:

# menu_pengaturan.py

import tkinter as tk


from Setting.sub_menu_ubah_profil_pengguna import show_sub_menu_profil_pengguna

from Setting.sub_menu_preferensi_nutrisi_mmag import show_sub_menu_preferensi_nutrisi_mmag

from Setting.sub_menu_bahasa_aplikasi import open_sub_menu_bahasa_aplikasi

from Setting.sub_menu_tema_tampilan import SubMenuTemaTampilan

from Setting.utils_text import get_text



def show_menu_pengaturan(root_frame):

    from menu_utama import tampilkan_halaman_utama  # Import lokal agar aman


    # Bersihkan frame sebelum tampilkan isi baru

    for widget in root_frame.winfo_children():

        widget.destroy()


    # Judul

    label_title = tk.Label(

        root_frame,

        text=get_text("Pengaturan Aplikasi"),

        font=("Arial", 16, "bold"),

        bg="light grey"

    )

    label_title.pack(pady=10)


    # Handler buka Tema Tampilan

    def open_tema_tampilan():

        for widget in root_frame.winfo_children():

            widget.destroy()

        frame_tema = SubMenuTemaTampilan(root_frame)

        frame_tema.pack(fill="both", expand=True)


    # Submenu daftar

    submenus = [

        (get_text("Ubah Profil Pengguna"), lambda: show_sub_menu_profil_pengguna(root_frame)),

        (get_text("Setelan Preferensi Nutrisi MMAG"), lambda: show_sub_menu_preferensi_nutrisi_mmag(root_frame)),

        (get_text("Filter Resep Sesuai Preferensi"), lambda: show_sub_menu_filter_resep_mmag(root_frame)),

        (get_text("Bahasa Aplikasi"), lambda: open_sub_menu_bahasa_aplikasi(root_frame, lambda: show_menu_pengaturan(root_frame))),

        (get_text("Tema Tampilan"), open_tema_tampilan),

    ]


    for text, command in submenus:

        btn = tk.Button(

            root_frame,

            text=text,

            font=("Arial", 12),

            width=35,

            height=2,

            bg="#fce4ec",

            fg="dark green",

            command=command

        )

        btn.pack(pady=5)


    # Tombol kembali

    tombol_kembali = tk.Button(

        root_frame,

        text=get_text("Kembali ke Menu Utama"),

        font=("Arial", 12),

        bg="#d9534f",

        fg="white",

        command=lambda: tampilkan_halaman_utama(root_frame)

    )

    tombol_kembali.pack(pady=30)



- Kode isi file sub_menu_tema_tampilan.py:

import tkinter as tk

from tkinter import ttk, messagebox


from Setting.utils_text import get_text

from Setting.setting_state import get_tema_aktif, set_tema_aktif


class SubMenuTemaTampilan(tk.Frame):

    def __init__(self, root_frame):

        super().__init__(root_frame)

        self.root_frame = root_frame


        # Judul Halaman

        lbl_title = tk.Label(

            self,

            text=get_text("Tema Tampilan"),

            font=("Arial", 16, "bold")

        )

        lbl_title.pack(pady=15)


        # Label Pilih Tema

        lbl_pilih = tk.Label(self, text=get_text("Pilih Tema:"))

        lbl_pilih.pack(pady=5)


        # Combobox Tema

        self.var_tema = tk.StringVar(value=get_tema_aktif() or "Terang")

        self.cmb_tema = ttk.Combobox(

            self,

            textvariable=self.var_tema,

            values=["Terang", "Gelap"],

            state="readonly",

            width=15

        )

        self.cmb_tema.pack(pady=5)


        # Tombol Simpan

        btn_simpan = tk.Button(

            self,

            text=get_text("Simpan"),

            command=self.simpan_tema,

            bg="#4CAF50",

            fg="white",

            relief="raised",

            padx=10, pady=5

        )

        btn_simpan.pack(pady=10)


        # Tombol Kembali

        btn_back = tk.Button(

            self,

            text=get_text("Kembali"),

            command=lambda: self.kembali(root_frame),

            bg="#f44336",

            fg="white",

            relief="raised",

            padx=10, pady=5

        )

        btn_back.pack(pady=5)


    def simpan_tema(self):

        """Simpan pilihan tema ke state dan terapkan"""

        tema = self.var_tema.get()

        set_tema_aktif(tema)


        # Terapkan tema ke root

        self.terapkan_tema(tema)


        # Info popup

        messagebox.showinfo(

            get_text("Informasi"),

            get_text("Tema berhasil diubah ke {tema}", tema=tema)

        )


    def terapkan_tema(self, tema):

        """Update warna background/foreground seluruh root_frame"""

        if tema == "Gelap":

            bg_color = "#2e2e2e"

            fg_color = "white"

            btn_bg = "#444"

            btn_fg = "white"

        else:  # default terang

            bg_color = "white"

            fg_color = "black"

            btn_bg = "#f0f0f0"

            btn_fg = "black"


        # Update root_frame dan anak-anaknya

        root = self.root_frame.winfo_toplevel()

        root.configure(bg=bg_color)

        self.update_colors(self.root_frame, bg_color, fg_color, btn_bg, btn_fg)


    def update_colors(self, widget, bg, fg, btn_bg, btn_fg):

        """Recursive: update semua child widgets"""

        for child in widget.winfo_children():

            if isinstance(child, tk.Frame):

                child.configure(bg=bg)

                self.update_colors(child, bg, fg, btn_bg, btn_fg)

            elif isinstance(child, tk.Label):

                child.configure(bg=bg, fg=fg)

            elif isinstance(child, tk.Button):

                child.configure(bg=btn_bg, fg=btn_fg)

            elif isinstance(child, ttk.Combobox):

                # ttk widget agak terbatas warnanya

                style = ttk.Style()

                style.theme_use("default")

                style.configure("TCombobox", fieldbackground=bg, foreground=fg)

            else:

                try:

                    child.configure(bg=bg, fg=fg)

                except:

                    pass


    def kembali(self, root_frame):

        """Kembali ke menu pengaturan"""

        for w in root_frame.winfo_children():

            w.destroy()

        from menu_pengaturan import show_menu_pengaturan

        show_menu_pengaturan(root_frame)


- Kode isi file theme_utils.py:

import tkinter as tk

from Setting.setting_state import get_tema_aktif


def terapkan_tema(widget):

    """Terapkan tema ke widget dan turunannya"""

    tema = get_tema_aktif() or "Terang"


    if tema == "Gelap":

        bg_color = "#2e2e2e"

        fg_color = "white"

        btn_bg = "#444"

        btn_fg = "white"

    else:  # Terang

        bg_color = "white"

        fg_color = "black"

        btn_bg = "#f0f0f0"

        btn_fg = "black"


    # Atur warna widget sesuai tipenya

    if isinstance(widget, tk.Frame):

        widget.configure(bg=bg_color)

    elif isinstance(widget, tk.Label):

        widget.configure(bg=bg_color, fg=fg_color)

    elif isinstance(widget, tk.Button):

        widget.configure(bg=btn_bg, fg=btn_fg)


    # Rekursif ke anak-anaknya

    for child in widget.winfo_children():

        terapkan_tema(child)



- Tambahan Kode pada isi file utils_text.py:

# Setting/utils_text.py

from Setting.setting_state import get_bahasa_aktif


# ==============================

# Kamus Translasi Lengkap

# ==============================

translations = {

    "id": {

        # Umum

        "Informasi": "Informasi",

        "Berhasil": "Berhasil",

        "Gagal": "Gagal",

        "Kembali": "Kembali",

        "Simpan": "Simpan",

        "Reset": "Reset",

        "Ya": "Ya",

        "Tidak": "Tidak",


        # Menu Utama

        "Menu Utama": "Menu Utama",

        "Rekomendasi Resep": "Rekomendasi Resep",

        "Fitur Lengkap Resep MMAG_Human": "Fitur Lengkap Resep MMAG_Human",

        "Pengaturan": "Pengaturan",

        "Keluar": "Keluar",


        # Menu Pengaturan

        "Pengaturan Aplikasi": "Pengaturan Aplikasi",

        "Ubah Profil Pengguna": "Ubah Profil Pengguna",

        "Setelan Preferensi Nutrisi MMAG": "Setelan Preferensi Nutrisi MMAG",

        "Filter Resep Sesuai Preferensi": "Filter Resep Sesuai Preferensi",

        "Bahasa Aplikasi": "Bahasa Aplikasi",

        "Tema Tampilan": "Tema Tampilan",


        # Bahasa

        "Pengaturan Bahasa": "Pengaturan Bahasa",

        "Menu Bahasa Aplikasi": "Menu Bahasa Aplikasi",

        "Pilih Bahasa:": "Pilih Bahasa:",

        "Bahasa berhasil diubah ke {lang}": "Bahasa berhasil diubah ke {lang}",


        # Tema

        "Pilih Tema:": "Pilih Tema:",

        "Tema berhasil diubah ke {tema}": "Tema berhasil diubah ke {tema}",


        # Resep

        "Hitung Nutrisi": "Hitung Nutrisi",

        "Preferensi Nutrisi": "Preferensi Nutrisi",

        "Cari Resep": "Cari Resep",

        "Hasil Rekomendasi": "Hasil Rekomendasi",

        "Kategori Resep": "Kategori Resep",

        "Bahan": "Bahan",

        "Takaran": "Takaran",

        "Tambah Bahan": "Tambah Bahan",

        "Hapus Bahan": "Hapus Bahan",

        "Simpan Resep": "Simpan Resep",

        "Resep Tersimpan": "Resep Tersimpan",

        "Tidak ada resep ditemukan": "Tidak ada resep ditemukan",


        # Nutrisi

        "Kalori": "Kalori",

        "Protein": "Protein",

        "Karbohidrat": "Karbohidrat",

        "Lemak": "Lemak",

        "Vitamin": "Vitamin",

        "Mineral": "Mineral",

        "Total Nutrisi": "Total Nutrisi",

        "Preferensi Nutrisi Tersimpan": "Preferensi Nutrisi Tersimpan",


        # Contoh format dinamis

        "{jumlah} kalori": "{jumlah} kalori",

        "Resep ditemukan: {n}": "Resep ditemukan: {n}",

        "Total protein: {p} gram": "Total protein: {p} gram",

    },

    "en": {

        # General

        "Informasi": "Information",

        "Berhasil": "Success",

        "Gagal": "Failed",

        "Kembali": "Back",

        "Simpan": "Save",

        "Reset": "Reset",

        "Ya": "Yes",

        "Tidak": "No",


        # Main Menu

        "Menu Utama": "Main Menu",

        "Rekomendasi Resep": "Recipe Recommendations",

        "Fitur Lengkap Resep MMAG_Human": "Full Recipe Features MMAG_Human",

        "Pengaturan": "Settings",

        "Keluar": "Exit",


        # Settings Menu

        "Pengaturan Aplikasi": "Application Settings",

        "Ubah Profil Pengguna": "Edit User Profile",

        "Setelan Preferensi Nutrisi MMAG": "MMAG Nutrition Preferences",

        "Filter Resep Sesuai Preferensi": "Filter Recipes by Preference",

        "Bahasa Aplikasi": "Application Language",

        "Tema Tampilan": "Theme",


        # Language

        "Pengaturan Bahasa": "Language Settings",

        "Menu Bahasa Aplikasi": "Application Language Menu",

        "Pilih Bahasa:": "Choose Language:",

        "Bahasa berhasil diubah ke {lang}": "Language successfully changed to {lang}",


        # Theme

        "Pilih Tema:": "Choose Theme:",

        "Tema berhasil diubah ke {tema}": "Theme successfully changed to {tema}",


        # Recipes

        "Hitung Nutrisi": "Calculate Nutrition",

        "Preferensi Nutrisi": "Nutrition Preferences",

        "Cari Resep": "Search Recipe",

        "Hasil Rekomendasi": "Recommendation Results",

        "Kategori Resep": "Recipe Category",

        "Bahan": "Ingredient",

        "Takaran": "Portion",

        "Tambah Bahan": "Add Ingredient",

        "Hapus Bahan": "Remove Ingredient",

        "Simpan Resep": "Save Recipe",

        "Resep Tersimpan": "Recipe Saved",

        "Tidak ada resep ditemukan": "No recipes found",


        # Nutrition

        "Kalori": "Calories",

        "Protein": "Protein",

        "Karbohidrat": "Carbohydrates",

        "Lemak": "Fat",

        "Vitamin": "Vitamins",

        "Mineral": "Minerals",

        "Total Nutrisi": "Total Nutrition",

        "Preferensi Nutrisi Tersimpan": "Nutrition Preferences Saved",


        # Dynamic text

        "{jumlah} kalori": "{jumlah} calories",

        "Resep ditemukan: {n}": "Recipes found: {n}",

        "Total protein: {p} gram": "Total protein: {p} grams",

    },

    "ms": {

        # Umum

        "Informasi": "Maklumat",

        "Berhasil": "Berjaya",

        "Gagal": "Gagal",

        "Kembali": "Kembali",

        "Simpan": "Simpan",

        "Reset": "Set Semula",

        "Ya": "Ya",

        "Tidak": "Tidak",


        # Menu Utama

        "Menu Utama": "Menu Utama",

        "Rekomendasi Resep": "Cadangan Resipi",

        "Fitur Lengkap Resep MMAG_Human": "Ciri Lengkap Resipi MMAG_Human",

        "Pengaturan": "Tetapan",

        "Keluar": "Keluar",


        # Menu Pengaturan

        "Pengaturan Aplikasi": "Tetapan Aplikasi",

        "Ubah Profil Pengguna": "Ubah Profil Pengguna",

        "Setelan Preferensi Nutrisi MMAG": "Tetapan Preferensi Nutrisi MMAG",

        "Filter Resep Sesuai Preferensi": "Tapis Resipi Ikut Preferensi",

        "Bahasa Aplikasi": "Bahasa Aplikasi",

        "Tema Tampilan": "Tema",


        # Bahasa

        "Pengaturan Bahasa": "Tetapan Bahasa",

        "Menu Bahasa Aplikasi": "Menu Bahasa Aplikasi",

        "Pilih Bahasa:": "Pilih Bahasa:",

        "Bahasa berhasil diubah ke {lang}": "Bahasa berjaya ditukar ke {lang}",


        # Tema

        "Pilih Tema:": "Pilih Tema:",

        "Tema berhasil diubah ke {tema}": "Tema berjaya ditukar ke {tema}",


        # Resipi

        "Hitung Nutrisi": "Kira Nutrisi",

        "Preferensi Nutrisi": "Preferensi Nutrisi",

        "Cari Resep": "Cari Resipi",

        "Hasil Rekomendasi": "Hasil Cadangan",

        "Kategori Resep": "Kategori Resipi",

        "Bahan": "Bahan",

        "Takaran": "Sukatan",

        "Tambah Bahan": "Tambah Bahan",

        "Hapus Bahan": "Padam Bahan",

        "Simpan Resep": "Simpan Resipi",

        "Resep Tersimpan": "Resipi Disimpan",

        "Tidak ada resep ditemukan": "Tiada resipi dijumpai",


        # Nutrisi

        "Kalori": "Kalori",

        "Protein": "Protein",

        "Karbohidrat": "Karbohidrat",

        "Lemak": "Lemak",

        "Vitamin": "Vitamin",

        "Mineral": "Mineral",

        "Total Nutrisi": "Jumlah Nutrisi",

        "Preferensi Nutrisi Tersimpan": "Preferensi Nutrisi Disimpan",


        # Dynamic text

        "{jumlah} kalori": "{jumlah} kalori",

        "Resep ditemukan: {n}": "Resipi dijumpai: {n}",

        "Total protein: {p} gram": "Jumlah protein: {p} gram",

    }

}


# ==============================

# Fungsi Set dan Get Bahasa

# ==============================

current_lang = "id"


def set_language(lang_code: str):

    """Set bahasa aplikasi"""

    global current_lang

    if lang_code in translations:

        current_lang = lang_code

    else:

        current_lang = "id"  # fallback


def get_text(key: str, **kwargs) -> str:

    """Ambil teks sesuai bahasa aktif dengan dukungan format dinamis"""

    lang = get_bahasa_aktif() or current_lang

    lang_dict = translations.get(lang, translations["id"])

    text = lang_dict.get(key, key)  # fallback ke key jika tidak ada


    if kwargs:

        try:

            return text.format(**kwargs)

        except Exception:

            return text  # kalau format gagal, kembalikan plain


    return text


-

Kode pada isi file setting_state.py:

# Setting/setting_state.py

preferensi_aktif = None

bahasa_aktif = "Indonesia"


def set_preferensi_aktif(nama: str):

    global preferensi_aktif

    preferensi_aktif = nama


def get_preferensi_aktif():

    return preferensi_aktif


def set_bahasa_aktif(lang: str):

    global bahasa_aktif

    bahasa_aktif = lang


def get_bahasa_aktif():

    return bahasa_aktif

    

# setting_state.py


_tema_aktif = "Terang"  # default


def get_tema_aktif():

    return _tema_aktif


def set_tema_aktif(tema: str):

    global _tema_aktif

    _tema_aktif = tema    

    


Demikian, semoga bermanfaat.


Comments

Popular posts from this blog

Isi Menu "Setting"

Isi Menu "Riwayat Aktivitas" (Bagian 2)