PageSourceSearch

https://www.hamienet.com/assets/TranslationManager-B7c0Sg0H.js

js hamienet.com collected 2026-10-02 05:02:26 UTC 17,145 bytes, 280 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { a as api, r as reloadI18nResources } from "./index-w5lXls15.js";
3import { b as Drawer, D as DevDebugInfo } from "./AdminShared-BP_vxG_A.js";
4import { a as ai } from "./ai-B0Mlroaa.js";
5import { r as extractJson } from "./utils-CEVb7nN1.js";
6import { e1 as Languages, cf as Plus, fg as ToggleRight, fh as ToggleLeft, fV as Code, b5 as Trash2, cd as Save, bi as Sparkles } from "./vendor-D0P4XgNj.js";
7import "./vendor-i18n-BC7ZMXWQ.js";
8import "./vendor-ai-gQv1oOjO.js";
9const AddLanguageModal = ({
10  isOpen,
11  onClose,
12  sourceLocale,
13  onApply
14}) => {
15  const [mode, setMode] = reactExports.useState("manual");
16  const [targetLang, setTargetLang] = reactExports.useState("");
17  const [targetCode, setTargetCode] = reactExports.useState("");
18  const [pastedJson, setPastedJson] = reactExports.useState("");
19  const [generating, setGenerating] = reactExports.useState(false);
20  const [error, setError] = reactExports.useState("");
21  const handleGenerate = async () => {
22    if (!targetLang || !targetCode) return;
23    setGenerating(true);
24    setError("");
25    try {
26      const resources = await api.getTranslation("en", "translation");
27      const prompt = `You are a professional translator for a music education platform. 
28            Translate the values of the following JSON object from English to ${targetLang} (${targetCode}). 
29            Maintain the exact key structure. Return ONLY the valid JSON object, no markdown formatting.
30            
31            Source JSON (English):
32            ${JSON.stringify(resources, null, 2)}`;
33      const response = await ai.models.generateContent({
34        model: "gemini-3-flash-preview",
35        // Simple translation task
36        contents: prompt
37      });
38      const text = response.text || "";
39      const clean = extractJson(text);
40      JSON.parse(clean);
41      setPastedJson(clean);
42    } catch (e) {
43      setError(`Translation failed: ${e.message}`);
44    } finally {
45      setGenerating(false);
46    }
47  };
48  const handleApply = async () => {
49    try {
50      let resources;
51      if (mode === "ai") {
52        const clean = extractJson(pastedJson);
53        resources = JSON.parse(clean);
54      } else {
55        resources = await api.getTranslation("en", "translation");
56      }
57      onApply(resources, targetLang, targetCode);
58      onClose();
59    } catch (e) {
60      setError("Invalid JSON or error processing.");
61    }
62  };
63  if (!isOpen) return null;
64  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [
65    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-sm", onClick: onClose }),
66    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-2xl bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95", children: [
67      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-6", children: [
68        /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Add New Language" }),
69        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex bg-zinc-100 dark:bg-zinc-800 rounded-lg p-1", children: [
70          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setMode("manual"), className: `px-3 py-1 rounded-md text-xs font-bold transition-all ${mode === "manual" ? "bg-white dark:bg-zinc-700 shadow" : "text-zinc-500"}`, children: "Manual" }),
71          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setMode("ai"), className: `px-3 py-1 rounded-md text-xs font-bold transition-all ${mode === "ai" ? "bg-white dark:bg-zinc-700 shadow text-indigo-500" : "text-zinc-500"}`, children: "AI Assist" })
72        ] })
73      ] }),
74      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
75        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
76          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
77            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Language Name" }),
78            /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: targetLang, onChange: (e) => setTargetLang(e.target.value), className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white", placeholder: "e.g. Spanish" })
79          ] }),
80          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
81            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "ISO Code" }),
82            /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: targetCode, onChange: (e) => setTargetCode(e.target.value), className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white font-mono", placeholder: "e.g. es" })
83          ] })
84        ] }),
85        mode === "ai" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
86          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg border border-indigo-100 dark:border-indigo-800", children: [
87            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-2", children: [
88              /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-sm text-indigo-900 dark:text-indigo-200", children: "Auto-Translation" }),
89              /* @__PURE__ */ jsxRuntimeExports.jsxs(
90                "button",
91                {
92                  onClick: handleGenerate,
93                  disabled: !targetLang || !targetCode || generating,
94                  className: "text-xs flex items-center gap-1 bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 text-white px-3 py-1.5 rounded font-bold transition-all",
95                  children: [
96                    generating ? /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 12 }),
97                    generating ? "Translating..." : "Generate with Gemini"
98                  ]
99                }
100              )
101            ] }),
102            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-indigo-700 dark:text-indigo-300", children: "Uses Gemini 3 Flash to generate translation keys based on the English source." })
103          ] }),
104          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
105            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "JSON Output" }),
106            /* @__PURE__ */ jsxRuntimeExports.jsx(
107              "textarea",
108              {
109                value: pastedJson,
110                onChange: (e) => {
111                  setPastedJson(e.target.value);
112                  setError("");
113                },
114                className: "w-full h-32 bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-3 text-xs font-mono outline-none focus:border-indigo-500 text-zinc-900 dark:text-white resize-none",
115                placeholder: "{ ... }",
116                readOnly: generating
117              }
118            ),
119            error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-red-500 text-xs mt-1", children: error })
120          ] })
121        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg border border-zinc-200 dark:border-zinc-800 text-sm text-zinc-600 dark:text-zinc-400", children: "This will create a new language entry copying the English resources structure. You can then edit the values manually." })
122      ] }),
123      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 pt-6 border-t border-zinc-200 dark:border-zinc-800 mt-6", children: [
124        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-4 py-2 rounded-lg text-zinc-600 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800 font-bold text-sm", children: "Cancel" }),
125        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleApply, disabled: !targetCode || !targetLang || mode === "ai" && !pastedJson, className: "px-4 py-2 rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-sm disabled:opacity-50", children: "Create Language" })
126      ] })
127    ] })
128  ] });
129};
130const LocaleEditor = ({ locale, onClose, onSave, activeRole }) => {
131  const [json, setJson] = reactExports.useState("");
132  const [error, setError] = reactExports.useState("");
133  reactExports.useEffect(() => {
134    if (locale && locale.resources) setJson(JSON.stringify(locale.resources, null, 2));
135  }, [locale]);
136  const handleSave = () => {
137    try {
138      const parsed = JSON.parse(json);
139      onSave({ ...locale, resources: parsed });
140      onClose();
141    } catch (e) {
142      setError("Invalid JSON syntax");
143    }
144  };
145  return /* @__PURE__ */ jsxRuntimeExports.jsx(Drawer, { isOpen: !!locale, onClose, title: `Edit ${locale.name} (${locale.code})`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [
146    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
147      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400 mb-2", children: "Directly edit the translation JSON below. Keys must match the default language structure." }),
148      error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-red-500 text-xs font-bold bg-red-100 dark:bg-red-900/20 p-2 rounded", children: error })
149    ] }),
150    /* @__PURE__ */ jsxRuntimeExports.jsx(
151      "textarea",
152      {
153        value: json,
154        onChange: (e) => {
155          setJson(e.target.value);
156          setError("");
157        },
158        className: "flex-1 bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-800 rounded-lg p-4 font-mono text-xs outline-none focus:border-indigo-500 text-zinc-900 dark:text-white resize-none",
159        spellCheck: false
160      }
161    ),
162    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-4 flex gap-2", children: [
163      /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "flex-1 px-4 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 font-bold text-zinc-600 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800", children: "Cancel" }),
164      /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleSave, className: "flex-1 px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white font-bold flex items-center justify-center gap-2", children: [
165        /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 16 }),
166        " Save"
167      ] })
168    ] }),
169    /* @__PURE__ */ jsxRuntimeExports.jsx(DevDebugInfo, { table: "Locale", keyName: locale.code, value: locale.resources, isSuperAdmin: activeRole == null ? void 0 : activeRole.isSuperAdmin })
170  ] }) });
171};
172const TranslationManager = ({ activeRole }) => {
173  const [locales, setLocales] = reactExports.useState([]);
174  const [editingLocale, setEditingLocale] = reactExports.useState(null);
175  const [isAddOpen, setIsAddOpen] = reactExports.useState(false);
176  const [localRefresh, setLocalRefresh] = reactExports.useState(0);
177  const refresh = () => setLocalRefresh((p) => p + 1);
178  reactExports.useEffect(() => {
179    api.getLocales().then(setLocales);
180  }, [localRefresh]);
181  const handleAddLanguage = async (resources, name, code) => {
182    await api.createLocale({ id: Date.now(), code, name, resources, isActive: true });
183    reloadI18nResources();
184    refresh();
185  };
186  const handleUpdateLocale = async (updated) => {
187    await api.updateLocale(updated.code, updated);
188    reloadI18nResources();
189    refresh();
190  };
191  const handleDelete = async (code) => {
192    if (confirm(`Delete ${code}?`)) {
193      await api.deleteLocale(code);
194      refresh();
195    }
196  };
197  const toggleActive = async (l) => {
198    await api.updateLocale(l.code, { ...l, isActive: !l.isActive });
199    refresh();
200  };
201  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col space-y-6", children: [
202    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center bg-white dark:bg-zinc-900 p-4 rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-sm", children: [
203      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
204        /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
205          /* @__PURE__ */ jsxRuntimeExports.jsx(Languages, { className: "text-indigo-500" }),
206          " Translation Manager"
207        ] }),
208        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Manage supported languages and translations." })
209      ] }),
210      /* @__PURE__ */ jsxRuntimeExports.jsxs(
211        "button",
212        {
213          onClick: () => setIsAddOpen(true),
214          className: "flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-sm font-bold shadow-md shadow-indigo-500/20",
215          children: [
216            /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 16 }),
217            " Add Language"
218          ]
219        }
220      )
221    ] }),
222    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: locales.map((locale) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-5 shadow-sm hover:border-indigo-300 dark:hover:border-indigo-700 transition-all ${!locale.isActive ? "opacity-60" : ""}`, children: [
223      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-4", children: [
224        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
225          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-10 h-10 rounded-full bg-indigo-50 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold text-sm", children: locale.code.toUpperCase() }),
226          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
227            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white", children: locale.name }),
228            /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500", children: [
229              Object.keys(locale.resources || {}).length,
230              " keys"
231            ] })
232          ] })
233        ] }),
234        locale.code !== "en" && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => toggleActive(locale), className: `text-2xl ${locale.isActive ? "text-emerald-500" : "text-zinc-300 dark:text-zinc-700"}`, children: locale.isActive ? /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleRight, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleLeft, {}) })
235      ] }),
236      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
237        /* @__PURE__ */ jsxRuntimeExports.jsxs(
238          "button",
239          {
240            onClick: () => setEditingLocale(locale),
241            className: "flex-1 py-2 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-lg text-xs font-bold text-zinc-700 dark:text-zinc-300 flex items-center justify-center gap-2",
242            children: [
243              /* @__PURE__ */ jsxRuntimeExports.jsx(Code, { size: 14 }),
244              " Edit JSON"
245            ]
246          }
247        ),
248        locale.code !== "en" && /* @__PURE__ */ jsxRuntimeExports.jsx(
249          "button",
250          {
251            onClick: () => handleDelete(locale.code),
252            className: "p-2 bg-red-50 dark:bg-red-900/10 hover:bg-red-100 dark:hover:bg-red-900/30 text-red-500 rounded-lg",
253            children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 16 })
254          }
255        )
256      ] })
257    ] }, locale.code)) }),
258    editingLocale && /* @__PURE__ */ jsxRuntimeExports.jsx(
259      LocaleEditor,
260      {
261        locale: editingLocale,
262        onClose: () =>
262 setEditingLocale(null),
263        onSave: handleUpdateLocale,
264        activeRole
265      }
266    ),
267    /* @__PURE__ */ jsxRuntimeExports.jsx(
268      AddLanguageModal,
269      {
270        isOpen: isAddOpen,
271        onClose: () => setIsAddOpen(false),
272        sourceLocale: locales.find((l) => l.code === "en"),
273        onApply: handleAddLanguage
274      }
275    )
276  ] });
277};
278export {
279  TranslationManager as default
280};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.