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


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

CONFIG, LIMIT = "config.json", 50.0


class InventoryApp(tk.Tk):

    def __init__(self):
        super().__init__()
        self.geometry("780x560")

        # 1. Чтение конфига
        self.lang = "ru"
        if os.path.exists(CONFIG):
            try:
                self.lang = json.load(open(CONFIG, encoding="utf-8")).get(
                    "language", "ru"
                )
            except Exception:
                pass
        else:
            json.dump({"language": self.lang}, open(CONFIG, "w"))

        # 2. Загрузка локалей и аудит эталонных ключей
        self.locales, warnings = {}, []
        for l in ("ru", "en"):
            p = f"{l}.json"
            self.locales[l] = (
                json.load(open(p, encoding="utf-8")) if os.path.exists(p) else {}
            )

        ref_keys = set().union(*self.locales.values())
        for l, d in self.locales.items():
            for k in ref_keys - set(d.keys()):
                warnings.append(
                    f"[LOCALIZATION] Отсутствует строка: {k} в файле {l}.json"
                )

        # 3. Данные инвентаря
        self.items = [
            {
                "name": "Healing Potion",
                "cat": "cat_potion",
                "weight": 0.5,
                "qty": 3,
            },
            {"name": "Iron Sword", "cat": "cat_weapon", "weight": 5.0, "qty": 1},
        ]

        # 4. Интерфейс
        self.cats = ["cat_weapon", "cat_armor", "cat_potion", "cat_misc"]
        self.build_ui()
        for w in warnings:
            self.log(w)
        self.apply_lang()

    def t(self, key, **kwargs):
        txt = self.locales.get(self.lang, {}).get(key, f"[MISSING: {key}]")
        return txt.format(**kwargs) if kwargs and "{" in txt else txt

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

    def build_ui(self):
        # Меню
        m = tk.Menu(self)
        self.config(menu=m)
        self.m_inv, self.m_char, self.m_lang = (
            tk.Menu(m, tearoff=0),
            tk.Menu(m, tearoff=0),
            tk.Menu(m, tearoff=0),
        )
        for sub, name in (
            (self.m_inv, "inv"),
            (self.m_char, "char"),
            (self.m_lang, "lang"),
        ):
            m.add_cascade(menu=sub)
        self.m_inv.add_command(command=self.quit)
        self.m_char.add_command(
            command=lambda: messagebox.showinfo(
                self.t("menu_char_profile"), self.t("char_profile_info")
            )
        )
        self.m_char.add_command(
            command=lambda: (
                self.items.clear(),
                self.update_table(),
                self.log(self.t("log_weight_normal")),
            )
        )
        self.m_lang.add_command(
            label="Русский (RU)", command=lambda: self.switch_lang("ru")
        )
        self.m_lang.add_command(
            label="English (EN)", command=lambda: self.switch_lang("en")
        )

        # Форма добавления
        f_in = ttk.Frame(self, padding=5)
        f_in.pack(fill="x")
        self.l_name = ttk.Label(f_in)
        self.l_name.grid(row=0, column=0)
        self.e_name = ttk.Entry(f_in, width=15)
        self.e_name.grid(row=0, column=1, padx=4)

        self.l_cat = ttk.Label(f_in)
        self.l_cat.grid(row=0, column=2)
        self.cb_cat = ttk.Combobox(f_in, state="readonly", width=12)
        self.cb_cat.grid(row=0, column=3, padx=4)

        self.l_w = ttk.Label(f_in)
        self.l_w.grid(row=0, column=4)
        self.e_w = ttk.Entry(f_in, width=8)
        self.e_w.grid(row=0, column=5, padx=4)

        self.l_q = ttk.Label(f_in)
        self.l_q.grid(row=0, column=6)
        self.e_q = ttk.Entry(f_in, width=6)
        self.e_q.grid(row=0, column=7, padx=4)

        self.b_add = ttk.Button(f_in, command=self.add_item)
        self.b_add.grid(row=0, column=8, padx=6)

        # Таблица
        f_t = ttk.Frame(self)
        f_t.pack(fill="both", expand=True, padx=5, pady=2)
        self.tree = ttk.Treeview(
            f_t,
            columns=("n", "c", "w", "q", "tot"),
            show="headings",
            selectmode="browse",
        )
        self.tree.pack(side="left", fill="both", expand=True)
        sb = ttk.Scrollbar(f_t, command=self.tree.yview)
        sb.pack(side="right", fill="y")
        self.tree.config(yscrollcommand=sb.set)

        # Панель управления и статус
        f_act = ttk.Frame(self, padding=5)
        f_act.pack(fill="x")
        self.b_use = ttk.Button(f_act, command=self.use_item)
        self.b_use.pack(side="left", padx=2)
        self.b_del = ttk.Button(f_act, command=self.del_item)
        self.b_del.pack(side="left", padx=2)
        self.l_stat = ttk.Label(f_act, font=("TkDefaultFont", 9, "bold"))
        self.l_stat.pack(side="right", padx=5)

        # Лог
        self.f_log = ttk.LabelFrame(self, padding=3)
        self.f_log.pack(fill="x", padx=5, pady=4)
        self.txt_log = tk.Text(
            self.f_log, height=4, state="disabled", wrap="word", bg="#f4f4f4"
        )
        self.txt_log.pack(fill="x")

    def switch_lang(self, lang):
        self.lang = lang
        json.dump({"language": lang}, open(CONFIG, "w"))
        self.apply_lang()

    def apply_lang(self):
        self.title(self.t("app_title"))
        m = self.nametowidget(self.cget("menu"))
        m.entryconfig(1, label=self.t("menu_inventory"))
        m.entryconfig(2, label=self.t("menu_character"))
        m.entryconfig(3, label=self.t("menu_language"))
        self.m_inv.entryconfig(0, label=self.t("menu_exit"))
        self.m_char.entryconfig(0, label=self.t("menu_char_profile"))
        self.m_char.entryconfig(1, label=self.t("menu_char_reset"))

        self.l_name.config(text=self.t("lbl_item_name"))
        self.l_cat.config(text=self.t("lbl_category"))
        self.l_w.config(text=self.t("lbl_weight"))
        self.l_q.config(text=self.t("lbl_qty"))
        self.b_add.config(text=self.t("btn_add"))
        self.b_use.config(text=self.t("btn_use"))
        self.b_del.config(text=self.t("btn_delete"))
        self.f_log.config(text=self.t("log_title"))

        ci = max(0, self.cb_cat.current())
        self.cb_cat["values"] = [self.t(c) for c in self.cats]
        self.cb_cat.current(ci if ci < len(self.cats) else 0)

        for col, key in zip(
            ("n", "c", "w", "q", "tot"),
            ("col_name", "col_category", "col_weight", "col_qty", "col_total"),
        ):
            self.tree.heading(col, text=self.t(key))
        self.update_table()

    def update_table(self):
        self.tree.delete(*self.tree.get_children())
        tot_w = 0.0
        for it in self.items:
            sub = it["weight"] * it["qty"]
            tot_w += sub
            self.tree.insert(
                "",
                "end",
                values=(
                    it["name"],
                    self.t(it["cat"]),
                    f"{it['weight']:.1f}",
                    it["qty"],
                    f"{sub:.1f}",
                ),
            )
        self.l_stat.config(
            text=self.t(
                "stat_total_weight",
                total=f"{tot_w:.1f}",
                limit=f"{LIMIT:.1f}",
            ),
            foreground="red" if tot_w > LIMIT else "black",
        )

    def add_item(self):
        try:
            name, w, q = (
                self.e_name.get().strip(),
                float(self.e_w.get()),
                int(self.e_q.get()),
            )
            if not name or w < 0 or q <= 0:
                raise ValueError
        except ValueError:
            return messagebox.showerror("!", self.t("err_invalid_input"))

        cat = self.cats[self.cb_cat.current()]
        self.items.append({"name": name, "cat": cat, "weight": w, "qty": q})
        self.update_table()
        self.log(self.t("log_item_added", name=name, qty=q))
        if sum(i["weight"] * i["qty"] for i in self.items) > LIMIT:
            self.log(self.t("log_overweight_warning"))
        for e in (self.e_name, self.e_w, self.e_q):
            e.delete(0, "end")

    def use_item(self):
        sel = self.tree.selection()
        if not sel:
            return
        idx = self.tree.index(sel[0])
        it = self.items[idx]
        it["qty"] -= 1
        self.log(self.t("log_item_used", name=it["name"]))
        if it["qty"] <= 0:
            self.items.pop(idx)
        self.update_table()

    def del_item(self):
        sel = self.tree.selection()
        if not sel:
            return
        idx = self.tree.index(sel[0])
        it = self.items.pop(idx)
        self.log(self.t("log_item_deleted", name=it["name"]))
        self.update_table()


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