Загрузка данных


import json
import os
import tkinter as tk
from tkinter import messagebox, ttk

CONFIG_FILE = "config.json"
DEFAULT_LANG = "ru"
WEIGHT_LIMIT = 50.0


class InventoryApp(tk.Tk):

    def __init__(self):
        super().__init__()
        self.geometry("820x650")
        self.minsize(750, 580)

        # 1. Загрузка конфигурации
        self.current_lang = self.load_config()

        # 2. Загрузка локалей и аудит полноты перевода
        self.locales = {}
        self.reference_keys = set()
        self.localization_warnings = []
        self.load_all_translations()

        # Данные инвентаря: храним список словарей
        # {'name': str, 'cat_key': str, 'weight': float, 'qty': int}
        self.inventory = [
            {
                "name": "Healing Potion",
                "cat_key": "cat_potion",
                "weight": 0.5,
                "qty": 3,
            },
            {
                "name": "Iron Sword",
                "cat_key": "cat_weapon",
                "weight": 5.0,
                "qty": 1,
            },
        ]

        # 3. Построение GUI
        self.create_widgets()

        # 4. Вывод ошибок полноты локализации в лог
        for warn in self.localization_warnings:
            self.log_message(warn)

        # 5. Применение текущего языка
        self.apply_language()

    def load_config(self):
        """Чтение config.json или создание дефолтного."""
        if os.path.exists(CONFIG_FILE):
            try:
                with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                    cfg = json.load(f)
                    return cfg.get("language", DEFAULT_LANG)
            except Exception:
                return DEFAULT_LANG
        else:
            self.save_config(DEFAULT_LANG)
            return DEFAULT_LANG

    def save_config(self, lang_code):
        try:
            with open(CONFIG_FILE, "w", encoding="utf-8") as f:
                json.dump({"language": lang_code}, f, indent=4)
        except Exception as e:
            print(f"Error saving config: {e}")

    def load_all_translations(self):
        """Загрузка всех json и аудит эталонных ключей."""
        langs = ["ru", "en"]
        for l in langs:
            filename = f"{l}.json"
            if os.path.exists(filename):
                try:
                    with open(filename, "r", encoding="utf-8") as f:
                        self.locales[l] = json.load(f)
                except Exception as e:
                    self.locales[l] = {}
                    self.localization_warnings.append(
                        f"[LOCALIZATION] Ошибка чтения {filename}: {e}"
                    )
            else:
                self.locales[l] = {}
                self.localization_warnings.append(
                    f"[LOCALIZATION] Файл не найден: {filename}"
                )

        # Построение эталона (объединение всех ключей)
        for d in self.locales.values():
            self.reference_keys.update(d.keys())

        # Проверка полноты перевода для каждого файла
        for l, d in self.locales.items():
            missing = self.reference_keys - set(d.keys())
            for m in missing:
                self.localization_warnings.append(
                    f"[LOCALIZATION] Отсутствует строка: {m} в файле {l}.json"
                )

    def tr(self, key, **kwargs):
        """Получение перевода с fallback на [MISSING: key]."""
        active_dict = self.locales.get(self.current_lang, {})
        if key not in active_dict:
            return f"[MISSING: {key}]"
        text = active_dict[key]
        if kwargs:
            try:
                return text.format(**kwargs)
            except Exception:
                return text
        return text

    def create_widgets(self):
        # Главное меню
        self.menubar = tk.Menu(self)
        self.config(menu=self.menubar)

        self.menu_inv = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(menu=self.menu_inv)
        self.menu_inv.add_command(command=self.quit)

        self.menu_char = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(menu=self.menu_char)
        self.menu_char.add_command(command=self.show_char_profile)
        self.menu_char.add_command(command=self.reset_inventory)

        self.menu_lang = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(menu=self.menu_lang)
        self.menu_lang.add_command(
            label="Русский (RU)", command=lambda: self.switch_language("ru")
        )
        self.menu_lang.add_command(
            label="English (EN)", command=lambda: self.switch_language("en")
        )

        # Верхняя панель: форма ввода предметов
        self.frame_input = ttk.LabelFrame(self, padding=10)
        self.frame_input.pack(fill="x", padx=10, pady=5)

        self.lbl_name = ttk.Label(self.frame_input)
        self.lbl_name.grid(row=0, column=0, sticky="w", padx=5, pady=2)
        self.entry_name = ttk.Entry(self.frame_input, width=18)
        self.entry_name.grid(row=0, column=1, padx=5, pady=2)

        self.lbl_cat = ttk.Label(self.frame_input)
        self.lbl_cat.grid(row=0, column=2, sticky="w", padx=5, pady=2)
        self.combo_cat = ttk.Combobox(
            self.frame_input, state="readonly", width=14
        )
        self.combo_cat.grid(row=0, column=3, padx=5, pady=2)

        self.lbl_weight = ttk.Label(self.frame_input)
        self.lbl_weight.grid(row=1, column=0, sticky="w", padx=5, pady=2)
        self.entry_weight = ttk.Entry(self.frame_input, width=18)
        self.entry_weight.grid(row=1, column=1, padx=5, pady=2)

        self.lbl_qty = ttk.Label(self.frame_input)
        self.lbl_qty.grid(row=1, column=2, sticky="w", padx=5, pady=2)
        self.entry_qty = ttk.Entry(self.frame_input, width=14)
        self.entry_qty.grid(row=1, column=3, padx=5, pady=2)

        self.btn_add = ttk.Button(self.frame_input, command=self.add_item)
        self.btn_add.grid(
            row=0, column=4, rowspan=2, padx=15, pady=2, sticky="nsew"
        )

        # Таблица предметов (Treeview)
        self.frame_table = ttk.Frame(self, padding=10)
        self.frame_table.pack(fill="both", expand=True, padx=10, pady=5)

        self.tree = ttk.Treeview(
            self.frame_table,
            columns=("name", "cat", "weight", "qty", "total"),
            show="headings",
            selectmode="browse",
        )
        self.tree.pack(side="left", fill="both", expand=True)

        scrollbar = ttk.Scrollbar(
            self.frame_table, orient="vertical", command=self.tree.yview
        )
        self.tree.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side="right", fill="y")

        # Панель действий под таблицей и статистика
        self.frame_actions = ttk.Frame(self, padding=10)
        self.frame_actions.pack(fill="x", padx=10, pady=2)

        self.btn_use = ttk.Button(self.frame_actions, command=self.use_item)
        self.btn_use.pack(side="left", padx=5)

        self.btn_delete = ttk.Button(
            self.frame_actions, command=self.delete_item
        )
        self.btn_delete.pack(side="left", padx=5)

        self.lbl_stats = ttk.Label(
            self.frame_actions, font=("Helvetica", 10, "bold")
        )
        self.lbl_stats.pack(side="right", padx=10)

        # Поле лога
        self.frame_log = ttk.LabelFrame(self, padding=5)
        self.frame_log.pack(fill="x", padx=10, pady=5)

        self.txt_log = tk.Text(
            self.frame_log, height=5, state="disabled", wrap="word", bg="#f5f5f5"
        )
        self.txt_log.pack(fill="x", padx=5, pady=2)

    def apply_language(self):
        """Централизованное обновление всех элементов интерфейса."""
        self.title(self.tr("app_title"))

        # Меню
        self.menubar.entryconfig(1, label=self.tr("menu_inventory"))
        self.menubar.entryconfig(2, label=self.tr("menu_character"))
        self.menubar.entryconfig(3, label=self.tr("menu_language"))

        self.menu_inv.entryconfig(0, label=self.tr("menu_exit"))
        self.menu_char.entryconfig(0, label=self.tr("menu_char_profile"))
        self.menu_char.entryconfig(1, label=self.tr("menu_char_reset"))

        # Лейблы и кнопки
        self.lbl_name.config(text=self.tr("lbl_item_name"))
        self.lbl_cat.config(text=self.tr("lbl_category"))
        self.lbl_weight.config(text=self.tr("lbl_weight"))
        self.lbl_qty.config(text=self.tr("lbl_qty"))
        self.btn_add.config(text=self.tr("btn_add"))
        self.btn_use.config(text=self.tr("btn_use"))
        self.btn_delete.config(text=self.tr("btn_delete"))
        self.frame_log.config(text=self.tr("log_title"))

        # Обновление выпадающего списка категорий с сохранением индекса
        cat_keys = ["cat_weapon", "cat_armor", "cat_potion", "cat_misc"]
        cat_labels = [self.tr(k) for k in cat_keys]
        current_idx = self.combo_cat.current()
        self.combo_cat["values"] = cat_labels
        if current_idx >= 0 and current_idx < len(cat_labels):
            self.combo_cat.current(current_idx)
        else:
            self.combo_cat.current(0)

        # Заголовки колонок таблицы
        self.tree.heading("name", text=self.tr("col_name"))
        self.tree.heading("cat", text=self.tr("col_category"))
        self.tree.heading("weight", text=self.tr("col_weight"))
        self.tree.heading("qty", text=self.tr("col_qty"))
        self.tree.heading("total", text=self.tr("col_total"))

        # Перерисовка строк таблицы и перерасчет веса
        self.refresh_table()

    def switch_language(self, lang):
        if self.current_lang != lang:
            self.current_lang = lang
            self.save_config(lang)
            self.apply_language()

    def log_message(self, message):
        self.txt_log.config(state="normal")
        self.txt_log.insert("end", message + "\n")
        self.txt_log.see("end")
        self.txt_log.config(state="disabled")

    def refresh_table(self):
        # Запоминаем текущий выбор
        selected = self.tree.selection()
        selected_idx = self.tree.index(selected[0]) if selected else None

        for item in self.tree.get_children():
            self.tree.delete(item)

        total_weight = 0.0
        for item in self.inventory:
            cat_display = self.tr(item["cat_key"])
            row_total = item["weight"] * item["qty"]
            total_weight += row_total
            self.tree.insert(
                "",
                "end",
                values=(
                    item["name"],
                    cat_display,
                    f"{item['weight']:.2f}",
                    item["qty"],
                    f"{row_total:.2f}",
                ),
            )

        # Восстановление выбора
        children = self.tree.get_children()
        if (
            selected_idx is not None
            and children
            and selected_idx < len(children)
        ):
            self.tree.selection_set(children[selected_idx])

        # Обновление метки статистики
        self.lbl_stats.config(
            text=self.tr(
                "stat_total_weight",
                total=f"{total_weight:.1f}",
                limit=f"{WEIGHT_LIMIT:.1f}",
            )
        )

        if total_weight > WEIGHT_LIMIT:
            self.lbl_stats.config(foreground="red")
        else:
            self.lbl_stats.config(foreground="black")

    def add_item(self):
        name = self.entry_name.get().strip()
        try:
            weight = float(self.entry_weight.get().strip())
            qty = int(self.entry_qty.get().strip())
            if weight < 0 or qty <= 0 or not name:
                raise ValueError()
        except ValueError:
            messagebox.showerror("Error", self.tr("err_invalid_input"))
            return

        cat_keys = ["cat_weapon", "cat_armor", "cat_potion", "cat_misc"]
        cat_key = cat_keys[self.combo_cat.current()]

        for item in self.inventory:
            if item["name"].lower() == name.lower():
                messagebox.showwarning("Notice", self.tr("err_item_exists"))
                return

        self.inventory.append(
            {"name": name, "cat_key": cat_key, "weight": weight, "qty": qty}
        )
        self.refresh_table()
        self.log_message(self.tr("log_item_added", name=name, qty=qty))

        # Проверка перегруза
        cur_weight = sum(i["weight"] * i["qty"] for i in self.inventory)
        if cur_weight > WEIGHT_LIMIT:
            self.log_message(self.tr("log_overweight_warning"))

        # Очистка полей ввода
        self.entry_name.delete(0, "end")
        self.entry_weight.delete(0, "end")
        self.entry_qty.delete(0, "end")

    def use_item(self):
        sel = self.tree.selection()
        if not sel:
            return
        idx = self.tree.index(sel[0])
        item = self.inventory[idx]
        item["qty"] -= 1
        self.log_message(self.tr("log_item_used", name=item["name"]))
        if item["qty"] <= 0:
            self.inventory.pop(idx)
        self.refresh_table()

    def delete_item(self):
        sel = self.tree.selection()
        if not sel:
            return
        idx = self.tree.index(sel[0])
        item = self.inventory.pop(idx)
        self.log_message(self.tr("log_item_deleted", name=item["name"]))
        self.refresh_table()

    def show_char_profile(self):
        messagebox.showinfo(
            self.tr("menu_char_profile"), self.tr("char_profile_info")
        )

    def reset_inventory(self):
        self.inventory.clear()
        self.refresh_table()
        self.log_message(self.tr("log_weight_normal"))


if __name__ == "__main__":
    app = InventoryApp()
    app.mainloop()