"use strict";
/**
* All user-facing wording lives in this file -- static labels (page.*) and
* every dynamic banner/verdict/error string app.js builds at runtime. Edit
* text here; app.js only calls these entries, it never hard-codes prose.
*
* Plain string -> used as-is.
* Function -> receives the same dynamic values the call site already
* had (numbers, labels, sub-messages) and returns the
* final string / HTML fragment.
*/
var MESSAGES = {
// Static page chrome: title, panel headings, toolbar labels, table headers.
page: {
title: "API 검증",
subtitleDefault: "동일 기능 API를 양쪽 게이트웨이로 동시에 호출하여 HTTP 상태 코드와 응답 본문의 일치 여부를 판정합니다. 응답 헤더는 표시 전용이며 판정에 반영하지 않습니다.",
varsPanelTitle: "변수",
varsHelp: '입력값은 브라우저 세션에만 보관되며 설정 파일에 기록되지 않습니다. 설정 파일의 {{VAR}} 자리에 치환됩니다.',
configFileLabel: "설정 파일",
intervalLabel: "요청 간격",
intervalUnit: "ms",
runAllButton: "전체 실행",
stopButton: "중지",
reloadButton: "다시 읽기",
colNo: "No.",
colApi: "API",
colHost: "Host",
colMethod: "메서드",
colPath: "경로",
colVerdict: "판정"
},
// The always-visible dual/single mode chip next to the subtitle.
mode: {
dualChip: "🔀 이중 비교 (dual)",
singleChip: "🎯 단일 비교 (single)",
singleTarget: function (leftLabel, rightLabel) { return leftLabel + " 실호출 → " + rightLabel + "(expected)과 비교"; },
dualTarget: function (leftLabel, rightLabel) { return leftLabel + " ↔ " + rightLabel; }
},
// The prose sentence under the mode chip, describing what gets compared.
subtitle: {
single: function (leftLabel) {
return leftLabel + " 한 곳만 호출하여 설정에 정의된 기대값(expected)과 비교합니다. " +
"expected 의 status / headers / body 중 지정된 항목만 판정에 반영합니다.";
},
dualWithHeaders: function (n) {
return "동일 기능 API를 양쪽 게이트웨이로 동시에 호출하여 HTTP 상태 코드, 응답 본문, " +
"그리고 지정한 응답 헤더(" + n + "개)의 일치 여부를 판정합니다.";
},
dualNoHeaders: "동일 기능 API를 양쪽 게이트웨이로 동시에 호출하여 HTTP 상태 코드와 응답 본문의 일치 여부를 판정합니다. " +
"응답 헤더는 표시 전용이며 판정에 반영하지 않습니다 (compareHeaders 로 지정하면 반영됩니다)."
},
// Fallback labels when a config doesn't name its targets. Dual mode falls
// back to the target's own key (e.g. "kong"), not a hard-coded product name,
// so renaming targets. never leaves a stale label on screen.
compare: {
defaultTargetLabel: "대상",
expectedLabel: "기대값"
},
request: {
missingTarget: function (targetKey) { return "설정에 targets." + targetKey + " 가 없습니다."; },
unresolvedVars: function (unresolvedList) { return "치환되지 않은 변수가 남아 있습니다 (" + unresolvedList + "). 상단 변수 입력란을 확인하세요."; }
},
clipboard: {
copyFailed: "클립보드 복사에 실패했습니다.",
copiedLabel: "복사됨",
curlTooltip: "curl 명령어를 클립보드로 복사 (PowerShell 형식)"
},
ignorePattern: {
invalidRegex: function (index, pattern, errorMessage) {
return "잘못된 ignorePattern [" + index + '] 정규식: "' + pattern + '" (' + errorMessage + ")";
}
},
// Dual-mode verdict reasons (judge()).
judge: {
headerMismatch: function (names) { return "응답 헤더 불일치 (" + names + ")"; },
statusMismatch: function (a, b) { return "상태 코드 불일치 (" + a + " vs " + b + ")"; },
formatMismatch: function (detail) { return "응답 형식 불일치 (" + detail + ")"; },
binaryVsText: function (binaryLabel, textLabel) { return binaryLabel + " 바이너리 / " + textLabel + " 텍스트"; },
textVsBinary: function (textLabel, binaryLabel) { return textLabel + " 텍스트 / " + binaryLabel + " 바이너리"; },
bodyMismatchBinary: function (leftBytes, rightBytes) { return "응답 본문 불일치 (바이너리 " + leftBytes + "B vs " + rightBytes + "B)"; },
bodyMismatch: "응답 본문 불일치"
},
// Single-mode (expected-value) verdict reasons (judgeSingle()).
single: {
expectedMissing: "기대값(expected)이 정의되지 않았습니다",
expectedFileError: function (fileError) { return "기대값 파일을 읽을 수 없습니다: " + fileError; },
expectedNothingToCompare: "기대값(expected)에 비교할 항목이 없습니다 (status/headers/body/bodyFile 중 최소 1개 필요)",
statusMismatch: function (actual, expected) { return "상태 코드 불일치 (실제 " + actual + " vs 기대 " + expected + ")"; },
headerMismatch: function (name, actualDisplay, expectedValue) {
return '기대 헤더 불일치 (' + name + ': 실제 "' + actualDisplay + '" / 기대 "' + expectedValue + '")';
},
headerAbsent: "(없음)",
binaryNotComparable: "응답이 바이너리이거나 본문 상한을 초과해 기대 본문과 비교할 수 없습니다"
},
summary: {
groupsAndApis: function (groupCount, apiCount) { return groupCount + "개 그룹 · API " + apiCount; }
},
headerSection: {
notJudged: "응답 헤더 (판정 미반영)",
judgedMatch: function (count) { return "응답 헤더 (판정 대상 " + count + "개 · 일치)"; },
judgedMismatch: function (count, bad) { return "응답 헤더 (판정 대상 " + count + "개 · 불일치 " + bad + "개)"; }
},
body: {
truncatedTextNote: function (bytes) {
return "본문이 상한을 넘어(" + bytes + ") 읽기를 중단했습니다. 비교는 수신한 부분의 sha256으로 수행되며 ignorePatterns는 적용되지 않습니다.";
},
noContentType: "(Content-Type 없음)",
truncatedBinaryNote: function (bytes) {
return "본문이 커서(" + bytes + ") 인라인 표시를 생략했습니다. 비교는 sha256으로 정상 수행됩니다.";
},
imageAlt: "응답 이미지",
binaryNoPreview: "바이너리 응답입니다. 미리보기를 지원하지 않는 형식이라 sha256으로 비교합니다."
},
column: {
requestFailedTag: "요청 실패",
errorTitle: "오류",
requestTitle: "요청",
expectedNoStatus: "상태 미지정",
expectedStatusSuffix: " (기대)",
expectedSourceNote: function (source) { return "출처: " + source; },
expectedBothSpecifiedNote: "body 와 bodyFile 이 함께 지정되어 body 를 사용했습니다.",
expectedBodyMissing: "기대 본문이 지정되지 않았습니다 (본문 비교 생략).",
expectedHeadersTitle: "기대 헤더",
expectedHeadersNone: "지정되지 않음",
expectedBodyTitle: "기대 본문",
responseBodyTitle: "응답 본문"
},
stats: {
countUnit: "회",
unusedNote: function (count) { return "" + count + "개 패턴이 양쪽 모두 0회 매칭입니다 — 정규식 오타일 가능성이 높습니다."; },
lopsidedNote: function (count) { return "매칭 횟수가 양쪽에서 다른 패턴이 " + count + "개입니다 — 응답 구조 자체가 다를 수 있습니다."; },
header: "정규화 적용됨 — 아래 패턴이 양쪽 본문에서 각각 고유 토큰으로 치환된 뒤 비교되었습니다.",
tokenCol: "토큰",
patternCol: "패턴",
defaultLeftLabel: "좌",
defaultRightLabel: "우"
},
detail: {
notRunYet: "아직 실행되지 않았습니다.",
ignorePatternsSkippedBinary: "이 API에 ignorePatterns가 지정되어 있지만 바이너리 응답이거나 본문 상한을 초과해 적용되지 않았습니다. 바이트 단위(sha256)로 비교했습니다.",
bodyCappedNote: "응답 본문이 상한을 넘어 일부만 읽고 중단했습니다. 비교는 수신한 부분의 sha256으로 수행되었습니다.",
binaryDiffTitle: "본문 차이 (바이너리 / 상한 초과)",
textDiffTitle: function (leftLabel, rightLabel) { return "본문 차이 (− " + leftLabel + " / + " + rightLabel + ")"; },
diffTooLarge: function (limit, position, leftLabel, leftExcerpt, rightLabel, rightExcerpt) {
return "본문이 너무 커서(" + limit + "행 초과) 라인 diff를 생략했습니다.\n" +
"첫 차이 위치: " + position + "번째 문자\n\n" +
leftLabel + " : ..." + leftExcerpt + "...\n" +
rightLabel + " : ..." + rightExcerpt + "...";
}
},
group: {
meta: function (name, source, count) { return name + " · " + source + " · " + count + "건"; },
runButton: "그룹 실행",
runLabel: function (label) { return "그룹 실행 [" + label + "]"; }
},
row: {
running: "실행 중",
notRun: "미실행",
noName: "(이름 없음)",
ignorePatternsApplied: function (count) { return "ignorePatterns " + count + "개 적용"; },
runButton: "실행",
curlButton: "curl"
},
proxy: {
errorPrefix: function (status, text) { return "프록시 오류 " + status + ": " + text; },
clientTimeout: function (budget) { return "클라이언트 타임아웃 (" + budget + "ms 초과)"; }
},
progress: {
waiting: function (what, interval, current, total) { return what + " — 대기 " + interval + "ms (" + current + "/" + total + ")"; },
running: function (what, current, total, name) { return what + " " + current + " / " + total + " — " + name; },
stopped: "중지됨",
done: "완료",
summaryCounts: function (doneCount, total, ok, fail, errorCount) {
return " (" + doneCount + "/" + total + "건 · OK " + ok + " · Fail " + fail + " · ERROR " + errorCount + ")";
},
stopRequested: "중지 요청됨 — 진행 중인 요청을 취소했습니다"
},
runLabels: {
all: "전체 실행",
single: "개별 실행"
},
// Config validation errors (validateConfig()) -- shown as the load-failed banner.
validate: {
rootNotObject: "설정 최상위가 객체가 아닙니다.",
missingTargets: "설정에 targets 가 없습니다.",
invalidCompareMode: function (mode) { return 'compareMode 는 "dual" 또는 "single" 이어야 합니다 (현재: ' + mode + ")."; },
singleTargetRequired: 'compareMode 가 "single" 이면 singleTarget 이 필요합니다.',
missingSingleTarget: function (key) { return "설정에 targets." + key + " 이 없습니다."; },
missingDualTargets: function (leftKey, rightKey) { return "설정에 targets." + leftKey + " / targets." + rightKey + " 이 모두 필요합니다."; },
invalidCompareTargets: 'compareTargets 는 ["좌측키", "우측키"] 형태의 문자열 2개짜리 배열이어야 합니다.',
compareHeadersNotArray: "compareHeaders 는 문자열 배열이어야 합니다.",
noGroups: "설정에 그룹이 없습니다. apis / groups / include 중 하나는 있어야 합니다.",
groupWhere: function (name, source) { return "그룹 '" + name + "' (" + source + ")"; },
groupMissingName: function (index) { return "groups[" + index + "] 에 name 이 없습니다."; },
duplicateGroupName: function (name, sourceA, sourceB) { return "그룹 이름이 중복됩니다: '" + name + "' (" + sourceA + " / " + sourceB + ")"; },
apisNotArray: function (where) { return where + " 의 apis 가 배열이 아닙니다."; },
apiMissingPath: function (where, index) { return where + " apis[" + index + "] 에 path 가 없습니다."; },
apiInvalidMethod: function (where, index, method) { return where + " apis[" + index + "] 의 method는 GET 또는 POST여야 합니다 (현재: " + method + ")."; },
apiFieldNotArray: function (where, index, field) { return where + " apis[" + index + "] 의 " + field + " 는 문자열 배열이어야 합니다."; },
expectedNotObject: function (where, index) { return where + " apis[" + index + "] 의 expected 는 객체여야 합니다."; },
expectedStatusNotNumber: function (where, index) { return where + " apis[" + index + "] 의 expected.status 는 숫자여야 합니다."; },
expectedHeadersNotObject: function (where, index) { return where + " apis[" + index + "] 의 expected.headers 는 객체여야 합니다."; },
expectedBodyNotString: function (where, index) { return where + " apis[" + index + "] 의 expected.body 는 문자열이어야 합니다 (객체는 지원하지 않습니다)."; }
},
// Boot-time banners (file:// warning, config list failures, clipboard/curl errors).
boot: {
requestFailed: function (status) { return "요청이 실패했습니다 (" + status + ")."; },
configLoadFailedBanner: function (message) { return "설정을 불러오지 못했습니다.
" + message; },
// Java-jar wording: this build is served by ApiDiff.jar, not run.ps1/run.bat.
fileProtocolBanner:
"서버를 통해 접속하세요.
" +
"이 페이지를 파일로 직접 열면 브라우저가 Host 헤더 지정과 자체서명 인증서 요청을 차단하기 때문에 테스트를 수행할 수 없습니다.
" +
"java -jar ApiDiff.jar 로 실행한 뒤 콘솔에 표시되는 주소(예: http://localhost:8080/)로 접속하세요.",
noConfigFiles: "jar 파일과 같은 폴더에 설정 파일(*.json)이 없습니다.",
clipboardFailedBanner: function (message) { return "클립보드 복사에 실패했습니다.
" + message; },
curlBuildFailedBanner: function (message) { return "curl 명령어를 만들 수 없습니다.
" + message; }
}
};