i18n: 서버 측 모든 UI 문구를 locales/server/ko-kr.json 으로 분리
- src/shared/i18n.ts: 공용 i18n 로더 (dotted-key + {{placeholder}} 보간)
- locales/server/ko-kr.json: 사이트 + 라우터 + 데이터팩 출력 사전
- EJS 뷰는 res.locals.t 미들웨어로 일괄 적용
- listEditor.js 등 클라이언트 JS 는 사전을 inline <script> 로 주입받아 tt() 헬퍼 사용
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><%= pack.name %> 편집</title>
|
||||
<title><%= t('editor.browserTitle', { name: pack.name }) %></title>
|
||||
<link rel="stylesheet" href="/static/styles.css" />
|
||||
</head>
|
||||
<body class="siteBody">
|
||||
@@ -12,27 +12,27 @@
|
||||
<main class="pageWrap">
|
||||
<section class="editorHeader">
|
||||
<div>
|
||||
<p class="eyebrow">PACK EDITOR</p>
|
||||
<p class="eyebrow"><%= t('editor.eyebrow') %></p>
|
||||
<h1><%= pack.name %></h1>
|
||||
</div>
|
||||
<a class="ghostLink" href="/op/dashboard">목록으로</a>
|
||||
<a class="ghostLink" href="/op/dashboard"><%= t('common.backToList') %></a>
|
||||
</section>
|
||||
|
||||
<form method="post" class="editorForm" id="editorForm">
|
||||
<div class="gridTwo">
|
||||
<label>
|
||||
<span>음악퀴즈 이름</span>
|
||||
<span><%= t('editor.displayName') %></span>
|
||||
<input name="displayName" value="<%= pack.name %>" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>JSON 파일 이름 (확장자 제외)</span>
|
||||
<span><%= t('editor.fileName') %></span>
|
||||
<input name="fileName" value="<%= packKey %>" required pattern="[a-zA-Z0-9_\-]+" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="gridTwo">
|
||||
<label>
|
||||
<span>마인크래프트 버전</span>
|
||||
<span><%= t('editor.mcVersion') %></span>
|
||||
<select name="mcVersion" required>
|
||||
<% releases.forEach(function (release) { %>
|
||||
<option value="<%= release %>" <%= release === pack.mcVersion ? 'selected' : '' %>><%= release %></option>
|
||||
@@ -40,7 +40,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>모드 플랫폼</span>
|
||||
<span><%= t('editor.platformType') %></span>
|
||||
<select name="platformType" id="platformType">
|
||||
<% ['vanilla','forge','fabric','neoforge'].forEach(function (loader) { %>
|
||||
<option value="<%= loader %>" <%= pack.platform.type === loader ? 'selected' : '' %>><%= loader %></option>
|
||||
@@ -48,62 +48,75 @@
|
||||
</select>
|
||||
</label>
|
||||
<label class="fullSpan" id="platformDownloadField">
|
||||
<span>플랫폼 설치파일 URL</span>
|
||||
<span><%= t('editor.platformDownloadUrl') %></span>
|
||||
<input name="platformDownloadUrl" value="<%= pack.platform.downloadUrl || '' %>" placeholder="/forge-installer.jar 또는 https://example.com/forge-installer.jar" />
|
||||
<small class="muted">도메인 없이 입력하면 manifest.json 도메인의 <code>/file/platforms/<파일명></code>으로 해석됩니다.</small>
|
||||
<small class="muted"><%- t('editor.platformDownloadHint') %></small>
|
||||
</label>
|
||||
<label class="fullSpan" id="platformLoaderField" hidden>
|
||||
<span>Fabric Loader 버전</span>
|
||||
<span><%= t('editor.platformLoaderVersion') %></span>
|
||||
<select name="platformLoaderVersion" id="platformLoaderVersion" data-current="<%= pack.platform.loaderVersion || '' %>">
|
||||
<option value="">불러오는 중...</option>
|
||||
<option value=""><%= t('common.loading') %></option>
|
||||
</select>
|
||||
<small class="muted">선택한 마인크래프트 버전 기준 Fabric Loader 목록입니다. 설치기는 최신 fabric-installer 를 받아 자동으로 CLI 설치합니다.</small>
|
||||
<small class="muted"><%= t('editor.platformLoaderHint') %></small>
|
||||
</label>
|
||||
<label>
|
||||
<span>서버 최소 램 (MB)</span>
|
||||
<span><%= t('editor.serverMinRam') %></span>
|
||||
<input type="number" name="serverMinRam" value="<%= pack.serverMinRam %>" min="512" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>서버 최대 램 (MB)</span>
|
||||
<span><%= t('editor.serverMaxRam') %></span>
|
||||
<input type="number" name="serverMaxRam" value="<%= pack.serverMaxRam %>" min="512" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>클라이언트 최소 램 (MB)</span>
|
||||
<span><%= t('editor.clientMinRam') %></span>
|
||||
<input type="number" name="clientMinRam" value="<%= pack.clientMinRam %>" min="512" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>클라이언트 권장 램 (MB)</span>
|
||||
<span><%= t('editor.clientRecommendedRam') %></span>
|
||||
<input type="number" name="clientRecommendedRam" value="<%= pack.clientRecommendedRam %>" min="512" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>맵 파일 (.zip)</span>
|
||||
<span><%= t('editor.mapPath') %></span>
|
||||
<input name="mapPath" value="<%= pack.mapPath %>" placeholder="my-map.zip" pattern=".+\.zip" />
|
||||
<small class="muted">/file/maps/ 아래 zip 파일 이름.</small>
|
||||
<small class="muted"><%= t('editor.mapPathHint') %></small>
|
||||
</label>
|
||||
<label>
|
||||
<span>서버 파일 (.zip)</span>
|
||||
<span><%= t('editor.serverPath') %></span>
|
||||
<input name="serverPath" value="<%= pack.serverPath %>" placeholder="my-server.zip" pattern=".+\.zip" />
|
||||
<small class="muted">/file/servers/ 아래 zip 파일 이름. 멀티 모드 전용.</small>
|
||||
<small class="muted"><%= t('editor.serverPathHint') %></small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="gridTwo">
|
||||
<label>
|
||||
<span>모드 폴더 이름</span>
|
||||
<span><%= t('editor.modsFolder') %></span>
|
||||
<input name="modsFolder" value="<%= pack.modsFolder %>" placeholder="my-pack" pattern="[a-zA-Z0-9_\-]*" />
|
||||
<small class="muted">/file/mods/<폴더이름>/ 안의 모든 .jar을 자동으로 받습니다. 비워두면 모드를 받지 않습니다.</small>
|
||||
<small class="muted"><%- t('editor.modsFolderHint') %></small>
|
||||
</label>
|
||||
<label>
|
||||
<span>리소스팩 (.zip)</span>
|
||||
<span><%= t('editor.resourcepackPath') %></span>
|
||||
<input name="resourcepackPath" value="<%= pack.resourcepackPath %>" placeholder="my-pack.zip" pattern=".*\.zip|" />
|
||||
<small class="muted">/file/resourcepacks/ 아래 .zip 파일 이름. 비워두면 리소스팩을 받지 않습니다.</small>
|
||||
<small class="muted"><%= t('editor.resourcepackHint') %></small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="primaryButton" type="submit">저장</button>
|
||||
<button class="primaryButton" type="submit"><%= t('common.save') %></button>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
var I18N = {
|
||||
ramOrderInvalid: <%- JSON.stringify(t('editor.ramOrderInvalid')) %>,
|
||||
fabricLoaderRequired: <%- JSON.stringify(t('editor.fabricLoaderRequired')) %>,
|
||||
loaderEmpty: <%- JSON.stringify(t('editor.platformLoaderEmpty')) %>,
|
||||
loaderPickMc: <%- JSON.stringify(t('editor.platformLoaderPickMc')) %>,
|
||||
loaderLoadFailedPrefix: <%- JSON.stringify(t('editor.platformLoaderLoadFailed', { message: '__M__' })) %>,
|
||||
loading: <%- JSON.stringify(t('common.loading')) %>
|
||||
}
|
||||
function formatLoaderLoadFailed(message) {
|
||||
return I18N.loaderLoadFailedPrefix.replace('__M__', message)
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
var platformSelect = document.getElementById('platformType')
|
||||
@@ -136,7 +149,7 @@
|
||||
|
||||
function populateLoaderOptions(versions, preselect) {
|
||||
if (!versions || versions.length === 0) {
|
||||
loaderSelect.innerHTML = '<option value="">호환 로더 없음</option>'
|
||||
loaderSelect.innerHTML = '<option value="">' + I18N.loaderEmpty + '</option>'
|
||||
return
|
||||
}
|
||||
var html = ''
|
||||
@@ -156,7 +169,7 @@
|
||||
function loadFabricLoaders() {
|
||||
var mc = (mcVersionSelect && mcVersionSelect.value) || ''
|
||||
if (!mc) {
|
||||
loaderSelect.innerHTML = '<option value="">마인크래프트 버전을 먼저 선택하세요</option>'
|
||||
loaderSelect.innerHTML = '<option value="">' + I18N.loaderPickMc + '</option>'
|
||||
return
|
||||
}
|
||||
if (loaderCache[mc]) {
|
||||
@@ -164,7 +177,7 @@
|
||||
return
|
||||
}
|
||||
var seq = ++loaderFetchSeq
|
||||
loaderSelect.innerHTML = '<option value="">불러오는 중...</option>'
|
||||
loaderSelect.innerHTML = '<option value="">' + I18N.loading + '</option>'
|
||||
fetch('https://meta.fabricmc.net/v2/versions/loader/' + encodeURIComponent(mc))
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status)
|
||||
@@ -181,7 +194,8 @@
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (seq !== loaderFetchSeq) return
|
||||
loaderSelect.innerHTML = '<option value="">로더 목록 로드 실패: ' + (err && err.message ? err.message : err) + '</option>'
|
||||
var msg = (err && err.message) ? err.message : String(err)
|
||||
loaderSelect.innerHTML = '<option value="">' + formatLoaderLoadFailed(msg) + '</option>'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -197,12 +211,12 @@
|
||||
var clientReco = Number(form.clientRecommendedRam.value)
|
||||
if (clientMin > clientReco) {
|
||||
event.preventDefault()
|
||||
alert('클라이언트 최소 램은 권장 램보다 클 수 없습니다.')
|
||||
alert(I18N.ramOrderInvalid)
|
||||
return
|
||||
}
|
||||
if (platformSelect.value === 'fabric' && !loaderSelect.value) {
|
||||
event.preventDefault()
|
||||
alert('Fabric 로더 버전을 선택해 주세요.')
|
||||
alert(I18N.fabricLoaderRequired)
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user