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:
@@ -121,6 +121,8 @@ def _base_ctx(request: Request, user: dict[str, Any], st: Any, *, wh: str) -> di
|
||||
from app.main import build_erp_nav # noqa: WPS433
|
||||
from app.store import is_admin # noqa: WPS433
|
||||
|
||||
reader = _reader(request)
|
||||
ko_names = reader.name_map() if reader else {}
|
||||
return {
|
||||
"user": user,
|
||||
"is_admin": is_admin(user),
|
||||
@@ -128,6 +130,7 @@ def _base_ctx(request: Request, user: dict[str, Any], st: Any, *, wh: str) -> di
|
||||
"nav_items": build_erp_nav(user, active="malaysia"),
|
||||
"warehouses": st.list_warehouses(),
|
||||
"selected_wh": wh,
|
||||
"ko_names": ko_names,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{# 한글/영문 토글 버튼 + i18n 스크립트. 모든 말레이시아 화면 상단에 include. #}
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:8px;">
|
||||
<button type="button" id="mys-lang-toggle" class="erp-btn erp-btn-outline">ENG</button>
|
||||
</div>
|
||||
<script src="/static/malaysia.js?v=20260611"></script>
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
{% block content %}
|
||||
<section class="mys mys-compact">
|
||||
{% include "malaysia/_i18n.html" %}
|
||||
{% set active_tab = 'status' %}
|
||||
{% include "malaysia/_nav.html" %}
|
||||
|
||||
@@ -19,7 +20,7 @@
|
||||
<!-- 좌: 낱개로 분리된 전체 재고 -->
|
||||
<div class="erp-card">
|
||||
<div class="cpg-card-head" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<h2>전산 재고 현황 — 낱개 기준 ({{ rows|length }})</h2>
|
||||
<h2><span class="i18n">전산 재고 현황 — 낱개 기준</span> ({{ rows|length }})</h2>
|
||||
<span class="erp-muted">현재고 = 입고 − 출고 + 조정 + 재고조사반영 (콤보는 낱개로 분해 반영됨)</span>
|
||||
</div>
|
||||
<div class="erp-table-wrap">
|
||||
@@ -37,7 +38,7 @@
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="ccol">{{ r.item_code }}</td>
|
||||
<td class="nm">{{ r.item_name }}</td>
|
||||
<td class="nm" data-en="{{ r.item_name }}" data-ko="{{ ko_names.get(r.item_code, r.item_name) }}">{{ r.item_name }}</td>
|
||||
<td style="text-align:right;font-weight:600;">{{ r.current_qty }}</td>
|
||||
<td>{{ r.last_stocktake_date or '—' }}</td>
|
||||
<td style="text-align:right">{{ r.last_qty if r.last_qty is not none else '—' }}</td>
|
||||
@@ -67,7 +68,7 @@
|
||||
{% for s in set_rows %}
|
||||
<tr>
|
||||
<td class="ccol">{{ s.set_code }}</td>
|
||||
<td class="nm">{{ s.set_name }}</td>
|
||||
<td class="nm" data-en="{{ s.set_name }}" data-ko="{{ ko_names.get(s.set_code, s.set_name) }}">{{ s.set_name }}</td>
|
||||
<td style="text-align:right;font-weight:600;">{{ s.qty }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<section class="mys mys-compact">
|
||||
{% include "malaysia/_i18n.html" %}
|
||||
{% set active_tab = 'move' %}
|
||||
{% include "malaysia/_nav.html" %}
|
||||
|
||||
@@ -53,7 +54,7 @@
|
||||
{% for it in individual_items %}
|
||||
<tr>
|
||||
<td class="ccol">{{ it.item_code }}</td>
|
||||
<td class="nm">{{ it.item_name }}</td>
|
||||
<td class="nm" data-en="{{ it.item_name }}" data-ko="{{ ko_names.get(it.item_code, it.item_name) }}">{{ it.item_name }}</td>
|
||||
<td class="qcol"><input class="erp-input" type="number" name="qty_{{ it.item_code }}" placeholder="—" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -72,7 +73,7 @@
|
||||
{% for it in set_items %}
|
||||
<tr>
|
||||
<td class="ccol">{{ it.item_code }}</td>
|
||||
<td class="nm">{{ it.item_name }}</td>
|
||||
<td class="nm" data-en="{{ it.item_name }}" data-ko="{{ ko_names.get(it.item_code, it.item_name) }}">{{ it.item_name }}</td>
|
||||
<td class="qcol"><input class="erp-input" type="number" min="0" name="qty_{{ it.item_code }}" placeholder="—" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -86,7 +87,7 @@
|
||||
<!-- 이동 이력 -->
|
||||
<div class="erp-card" style="margin-top:16px;">
|
||||
<div class="cpg-card-head" style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
|
||||
<h2>이동 이력{% if filter_date %} · {{ filter_date }}{% endif %}</h2>
|
||||
<h2><span class="i18n">이동 이력</span>{% if filter_date %} · {{ filter_date }}{% endif %}</h2>
|
||||
<span style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">
|
||||
<form method="get" style="display:flex;gap:6px;align-items:center;">
|
||||
<input type="hidden" name="wh" value="{{ selected_wh }}" />
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
{% block content %}
|
||||
<section class="mys mys-compact">
|
||||
{% include "malaysia/_i18n.html" %}
|
||||
<div class="erp-page-actions">
|
||||
<a class="erp-btn erp-btn-primary" href="/malaysia/stocktakes?wh={{ stocktake.warehouse_code }}">◀◀ 조사 목록</a>
|
||||
</div>
|
||||
@@ -23,7 +24,7 @@
|
||||
|
||||
<!-- 좌: 정보 + 동작 -->
|
||||
<div class="erp-card">
|
||||
<div class="cpg-card-head"><h2>재고조사 #{{ stocktake.id }}</h2></div>
|
||||
<div class="cpg-card-head"><h2><span class="i18n">재고조사</span> #{{ stocktake.id }}</h2></div>
|
||||
<div style="margin-top:8px;display:flex;flex-direction:column;gap:6px;">
|
||||
<div><span class="erp-muted">날짜</span><br>{{ stocktake.stocktake_date }}</div>
|
||||
<div><span class="erp-muted">창고</span><br>{{ stocktake.warehouse_code }}</div>
|
||||
@@ -56,7 +57,7 @@
|
||||
{% for it in individual_items %}
|
||||
<tr>
|
||||
<td class="ccol">{{ it.item_code }}</td>
|
||||
<td class="nm">{{ it.item_name }}</td>
|
||||
<td class="nm" data-en="{{ it.item_name }}" data-ko="{{ ko_names.get(it.item_code, it.item_name) }}">{{ it.item_name }}</td>
|
||||
<td class="qcol"><input class="erp-input" type="number" min="0" name="qty_{{ it.item_code }}"
|
||||
value="{{ entered.get(it.item_code, '') }}" placeholder="—" {% if not is_draft %}disabled{% endif %} /></td>
|
||||
</tr>
|
||||
@@ -76,7 +77,7 @@
|
||||
{% for it in set_items %}
|
||||
<tr>
|
||||
<td class="ccol">{{ it.item_code }}</td>
|
||||
<td class="nm">{{ it.item_name }}</td>
|
||||
<td class="nm" data-en="{{ it.item_name }}" data-ko="{{ ko_names.get(it.item_code, it.item_name) }}">{{ it.item_name }}</td>
|
||||
<td class="qcol"><input class="erp-input" type="number" min="0" name="qty_{{ it.item_code }}"
|
||||
value="{{ entered.get(it.item_code, '') }}" placeholder="—" {% if not is_draft %}disabled{% endif %} /></td>
|
||||
</tr>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="mys">
|
||||
<section class="mys mys-compact">
|
||||
{% include "malaysia/_i18n.html" %}
|
||||
{% set active_tab = 'stocktake' %}
|
||||
{% include "malaysia/_nav.html" %}
|
||||
|
||||
@@ -23,7 +24,7 @@
|
||||
|
||||
<!-- 조사 목록 -->
|
||||
<div class="erp-card">
|
||||
<div class="cpg-card-head"><h2>조사 목록 ({{ stocktakes|length }})</h2></div>
|
||||
<div class="cpg-card-head"><h2><span class="i18n">조사 목록</span> ({{ stocktakes|length }})</h2></div>
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table">
|
||||
<thead><tr><th>#</th><th>Date</th><th>Warehouse</th><th>Status</th><th>By</th><th></th></tr></thead>
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user