Add function Language
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-16 16:44:04 +07:00
parent 1156e1ab29
commit a2e87aeb29
11 changed files with 1790 additions and 474 deletions

View File

@@ -39,6 +39,7 @@
const SAMPLE_CARTS = ["Any valid cart", "Cart A", "Cart B"];
const el = (id) => document.getElementById(id);
const t = (key, vars) => window.I18n?.t(key, vars) ?? key;
const missionListEl = el("missionList");
const missionListEmptyEl = el("missionListEmpty");
@@ -136,9 +137,9 @@
function actionMeta(type) {
for (const items of Object.values(ACTION_GROUPS)) {
const hit = items.find((a) => a.type === type);
if (hit) return hit;
if (hit) return { ...hit, label: t(`missions.action.${type}`) || hit.label };
}
return { type, label: type };
return { type, label: t(`missions.action.${type}`) || type };
}
function createAction(type, overrides = {}) {
@@ -336,7 +337,7 @@
if (action.id === actionId) return { action, list, index: i, path, parent };
if (Array.isArray(action.children)) {
const hit = findActionWithParent(actionId, action.children, `${path}.${action.id}`, action);
if (hit) return hit;
if (hit) return { ...hit, label: t(`missions.action.${type}`) || hit.label };
}
}
return null;
@@ -476,13 +477,14 @@
function queueStatusLabel(status) {
const map = {
pending: "Chờ",
executing: "Đang chạy",
completed: "Xong",
failed: "Lỗi",
cancelled: "Đã hủy",
pending: "missions.queue.status.pending",
executing: "missions.queue.status.executing",
completed: "missions.queue.status.done",
failed: "missions.queue.status.error",
cancelled: "missions.queue.status.cancelled",
};
return map[status] || status;
const key = map[status];
return key ? t(key) : status;
}
async function refreshQueue() {
@@ -495,7 +497,7 @@
notifyQueueUpdate();
} catch (e) {
if (String(e.message || "").includes("not authenticated")) return;
if (missionQueueRunnerEl) missionQueueRunnerEl.textContent = `Không tải được queue: ${e.message}`;
if (missionQueueRunnerEl) missionQueueRunnerEl.textContent = `${t("common.error", { msg: e.message })}`;
}
}
@@ -536,8 +538,8 @@
? `${store.runner.message}${action}`
: st === "idle"
? compact
? "Sẵn sàng"
: "Robot sẵn sàng — queue trống hoặc chờ mission mới."
? t("missions.queue.ready")
: t("missions.queue.idleMessage")
: "—";
}
@@ -554,12 +556,12 @@
${paramHtml ? `<div class="missionQueueItemParams">${paramHtml}</div>` : ""}
</div>
<div class="missionQueueWidgetActions">
${entry.status === "pending" ? `<button type="button" class="iconBtn danger" data-queue-remove="${entry.id}" title="Xóa">×</button>` : `<span class="missionQueueStatus ${escapeHtml(entry.status || "pending")}">${queueStatusLabel(entry.status)}</span>`}
${entry.status === "pending" ? `<button type="button" class="iconBtn danger" data-queue-remove="${entry.id}" title="" data-i18n-title="common.delete">×</button>` : `<span class="missionQueueStatus ${escapeHtml(entry.status || "pending")}">${queueStatusLabel(entry.status)}</span>`}
</div>`
: `
<div class="missionQueueOrder">
<button type="button" class="iconBtn" data-queue-up="${entry.id}" title="Lên" ${canReorder && index > 0 ? "" : "disabled"}>↑</button>
<button type="button" class="iconBtn" data-queue-down="${entry.id}" title="Xuống" ${canReorder && index < store.queue.length - 1 ? "" : "disabled"}>↓</button>
<button type="button" class="iconBtn" data-queue-up="${entry.id}" title="${t("missions.queue.moveUp")}" ${canReorder && index > 0 ? "" : "disabled"}>↑</button>
<button type="button" class="iconBtn" data-queue-down="${entry.id}" title="${t("missions.queue.moveDown")}" ${canReorder && index < store.queue.length - 1 ? "" : "disabled"}>↓</button>
</div>
<div>
<div class="missionQueueItemTitle">${escapeHtml(entry.mission_name || "Mission")}</div>
@@ -568,7 +570,7 @@
</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:6px;">
<span class="missionQueueStatus ${escapeHtml(entry.status || "pending")}">${queueStatusLabel(entry.status)}</span>
${entry.status === "pending" ? `<button type="button" class="btn subtle danger" data-queue-remove="${entry.id}">Xóa</button>` : ""}
${entry.status === "pending" ? `<button type="button" class="btn subtle danger" data-queue-remove="${entry.id}">${t("common.delete")}</button>` : ""}
</div>`;
row.querySelector("[data-queue-up]")?.addEventListener("click", () => moveQueueItem(entry.id, -1));
@@ -619,7 +621,7 @@
}
async function clearQueue() {
if (!confirm("Xóa các mission đang chờ trong queue?")) return;
if (!confirm(t("missions.queue.clearConfirm"))) return;
try {
await missionApi("/api/mission_queue", { method: "DELETE" });
await refreshQueue();
@@ -650,7 +652,7 @@
}
async function cancelRunner() {
if (!confirm("Hủy mission đang chạy? (thoát loop và dừng ngay)")) return;
if (!confirm(t("missions.queue.cancelConfirm"))) return;
await missionApi("/api/mission_queue/cancel", { method: "POST", body: "{}" });
await refreshQueue();
}
@@ -756,8 +758,8 @@
</div>
<div class="missionListItemActions">
<button type="button" class="iconBtn missionQueueBtn" data-queue="${mission.id}" title="Thêm vào mission queue" aria-label="Thêm vào queue">▤</button>
<button type="button" class="btn subtle" data-edit="${mission.id}">Sửa</button>
<button type="button" class="btn subtle danger" data-delete="${mission.id}">Xóa</button>
<button type="button" class="btn subtle" data-edit="${mission.id}">${t("common.edit")}</button>
<button type="button" class="btn subtle danger" data-delete="${mission.id}">${t("common.delete")}</button>
</div>`;
row.addEventListener("click", (evt) => {
if (evt.target.closest("button")) return;
@@ -773,7 +775,7 @@
});
row.querySelector("[data-delete]").addEventListener("click", (evt) => {
evt.stopPropagation();
if (!confirm(`Xóa mission «${mission.name}»?`)) return;
if (!confirm(t("missions.deleteConfirm", { name: mission.name }))) return;
store.missions = store.missions.filter((m) => m.id !== mission.id);
persistStore();
renderMissionList();
@@ -782,6 +784,12 @@
});
}
function groupLabel(name) {
const key = `missions.group.${name}`;
const v = t(key);
return v !== key ? v : name;
}
function renderActionPalette() {
if (!missionGroupTabsEl) return;
missionGroupTabsEl.innerHTML = "";
@@ -889,7 +897,7 @@
</div>
<div class="missionActionBtns">
<button type="button" class="iconBtn" data-config="${action.id}" title="Cấu hình">⚙</button>
<button type="button" class="iconBtn danger" data-remove="${action.id}" title="Xóa">×</button>
<button type="button" class="iconBtn danger" data-remove="${action.id}" title="" data-i18n-title="common.delete">×</button>
</div>
</div>`;
@@ -1044,7 +1052,7 @@
}
function closeEditor() {
if (store.dirty && !confirm("Bỏ thay đổi chưa lưu?")) return;
if (store.dirty && !confirm(t("missions.editor.discardConfirm"))) return;
store.editingId = null;
store.draft = null;
setDirty(false);
@@ -1057,7 +1065,7 @@
const draft = getDraft();
if (!draft) return false;
if (!draft.name.trim()) {
alert("Tên mission không được trống.");
alert(t("missions.error.nameRequired"));
return false;
}
draft.updated_at = new Date().toISOString();
@@ -1076,7 +1084,7 @@
const name = newName.trim();
if (!name) return false;
if (store.missions.some((m) => m.name === name && m.id !== draft.id)) {
alert("Tên mission đã tồn tại.");
alert(t("missions.error.nameDuplicate"));
return false;
}
const copy = JSON.parse(JSON.stringify(draft));
@@ -1213,7 +1221,7 @@
addField("Timeout (s)", textInput("timeout_s", p.timeout_s, "number"));
{
const chk = document.createElement("label");
chk.innerHTML = `<input type="checkbox" data-param="expected" ${p.expected ? "checked" : ""} /> Chờ mức ON`;
chk.innerHTML = `<input type="checkbox" data-param="expected" ${p.expected ? "checked" : ""} /> ${t("missions.action.waitOnLevel")}`;
addField("Kỳ vọng", chk);
}
break;
@@ -1286,7 +1294,7 @@
if (!store.groups.includes(group)) store.groups.push(group);
}
if (store.missions.some((m) => m.name === name)) {
alert("Tên mission đã tồn tại.");
alert(t("missions.error.nameDuplicate"));
return;
}
const mission = createMission(name, group, el("missionCreateDesc").value);
@@ -1299,7 +1307,7 @@
el("missionEditorBackBtn")?.addEventListener("click", closeEditor);
el("missionSaveBtn")?.addEventListener("click", () => {
if (saveDraft()) alert("Đã lưu mission.");
if (saveDraft()) alert(t("missions.saveSuccess"));
});
el("missionSaveAsBtn")?.addEventListener("click", openSaveAsDialog);
el("missionSettingsBtn")?.addEventListener("click", openSettingsDialog);
@@ -1312,7 +1320,7 @@
draft.group = el("missionSettingsGroup").value;
draft.description = el("missionSettingsDesc").value.trim();
if (!draft.name) {
alert("Tên không được trống.");
alert(t("missions.error.nameEmpty"));
return;
}
setDirty(true);
@@ -1393,6 +1401,16 @@
function boot() {
init();
}
function onLocaleChange() {
if (!missionEditorViewEl?.hidden) renderMissionEditor();
else {
renderMissionList();
renderQueuePanel();
}
renderActionPalette();
}
window.addEventListener("lm:locale-change", onLocaleChange);
if (window.AuthApp?.isReady()) boot();
else window.addEventListener("lm:auth-ready", boot, { once: true });
window.addEventListener("lm:auth-logout", stopQueuePollForce);