feat(malaysia): 전 화면 한글/영문 실시간 토글

- static/malaysia.js: KO_EN 사전 + 토글(localStorage 기억, 새로고침 없이 즉시)
- _i18n.html 공용 include(ENG/한글 버튼 + 스크립트), 전 화면 상단 삽입
- UI 고정 문구는 사전 치환(제목/헤더/버튼/라벨/배지/옵션/안내)
- 상품/콤보명은 data-en(영문, malaysia_items)·data-ko(itemcode_db 한글) 동시 렌더
- _base_ctx 에 ko_names(itemcode_db name_map) 주입

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 18:14:42 +09:00
parent b312884ddc
commit 3139e213a7
7 changed files with 153 additions and 11 deletions
+130
View File
@@ -0,0 +1,130 @@
/* 말레이시아 재고관리 — 화면 한글/영문 실시간 토글.
*
* - UI 고정 문구: 아래 KO_EN 사전으로 치환(요소 텍스트 = 한글 키).
* 대상 셀렉터는 UI 크롬(제목/헤더/버튼/라벨/배지/옵션/안내문)으로 한정 →
* 데이터 셀(상품코드/수량)은 건드리지 않음.
* - 상품명: 서버가 data-ko / data-en 을 함께 렌더 → 언어에 맞게 textContent 교체.
* - 선택값은 localStorage('mys_lang') 에 저장, 모든 화면 공통 적용.
*/
(function () {
"use strict";
var KO_EN = {
// 탭 / 공통
"재고 현황": "Stock Status",
"입출고": "In / Out",
"일일 재고조사": "Daily Stocktake",
"창고": "Warehouse",
"날짜": "Date",
"메모": "Memo",
"상태": "Status",
// 재고현황
"전산 재고 현황 — 낱개 기준": "System Stock — by Single",
"현재고 = 입고 − 출고 + 조정 + 재고조사반영 (콤보는 낱개로 분해 반영됨)":
"Current = IN OUT + ADJUST + Stocktake (combos exploded to singles)",
"전산재고": "System Qty",
"조사수량": "Stocktake Qty",
"차이": "Diff",
"콤보 재고 (세트 단위)": "Combo Stock (by set)",
"재고조사 입력 없음": "No stocktake input",
"표시할 재고가 없습니다.": "No stock to display.",
"콤보 재고 입력이 없습니다.": "No combo stock input.",
// 입출고
"등록 옵션": "Options",
"이동 종류 (낱개)": "Type (single)",
"IN (입고)": "IN (In)",
"OUT (출고)": "OUT (Out)",
"ADJUST (조정 ±)": "ADJUST (±)",
"일괄 적용": "Apply All",
"낱개 상품": "Single Products",
"콤보 상품": "Combo Products",
"이동 이력": "Movement History",
"전체": "All",
"날짜해제": "Clear date",
"이동 이력이 없습니다.": "No movements.",
// 재고조사 목록
"재고조사 생성": "New Stocktake",
"조사 날짜": "Date",
"생성": "Create",
"생성 후 상세에서 낱개/세트 수량을 입력하고 확정하세요.":
"After creating, enter single/set qty in detail and finalize.",
"조사 목록": "Stocktake List",
"열기": "Open",
"삭제": "Delete",
"재고조사가 없습니다.": "No stocktakes.",
"확정": "Final",
"취소": "Cancel",
"작성중": "Draft",
// 재고조사 상세
"재고조사": "Stocktake",
"◀◀ 조사 목록": "◀◀ List",
"확정시각": "Finalized at",
"입력 저장": "Save",
"확정 (전산 반영)": "Finalize (apply)",
"삭제 (슈퍼관리자)": "Delete (super)",
"최종 계산 (낱개 기준)": "Final Calc (by single)",
"total_qty = direct_qty(낱개 직접) + from_set_qty(콤보 분해)":
"total_qty = direct_qty (single) + from_set_qty (combo)",
"입력된 수량이 없습니다.": "No quantities entered.",
// 표 헤더(한글만)
"이동일": "Date",
};
var EN_KO = {};
Object.keys(KO_EN).forEach(function (k) { EN_KO[KO_EN[k]] = k; });
var SEL = [
".mys h1", ".mys h2", ".mys h3",
".mys th",
".mys label > span",
".mys button",
".mys a.erp-btn",
".mys .erp-muted",
".mys option",
".mys .erp-badge",
".mys .i18n",
].join(",");
function applyLang(lang) {
// 1) 상품명(한/영 동시 보유)
document.querySelectorAll(".mys [data-ko][data-en]").forEach(function (el) {
el.textContent = (lang === "en") ? el.getAttribute("data-en")
: el.getAttribute("data-ko");
});
// 2) UI 고정 문구
document.querySelectorAll(SEL).forEach(function (el) {
// 자식 요소가 있는 컨테이너는 건너뜀(.i18n 로 명시한 경우만 허용)
if (el.children.length && !el.classList.contains("i18n")) return;
if (el.hasAttribute("data-ko") && el.hasAttribute("data-en")) return; // 이미 처리됨
if (el.getAttribute("data-o") === null) {
el.setAttribute("data-o", (el.textContent || "").trim());
}
var ko = el.getAttribute("data-o");
if (lang === "en") {
if (KO_EN[ko] !== undefined) el.textContent = KO_EN[ko];
} else {
el.textContent = ko;
}
});
// 3) 토글 버튼 라벨
var btn = document.getElementById("mys-lang-toggle");
if (btn) btn.textContent = (lang === "en") ? "한글" : "ENG";
document.documentElement.setAttribute("data-mys-lang", lang);
}
function current() {
return localStorage.getItem("mys_lang") === "en" ? "en" : "ko";
}
function toggle() {
var next = current() === "en" ? "ko" : "en";
localStorage.setItem("mys_lang", next);
applyLang(next);
}
document.addEventListener("DOMContentLoaded", function () {
var btn = document.getElementById("mys-lang-toggle");
if (btn) btn.addEventListener("click", toggle);
applyLang(current());
});
})();