"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: "Kong ↔ Azure APIM 전환 검증", subtitleDefault: "동일 기능 API를 양쪽 게이트웨이로 동시에 호출하여 HTTP 상태 코드와 응답 본문의 일치 여부를 판정합니다. 응답 헤더는 표시 전용이며 판정에 반영하지 않습니다.", varsPanelTitle: "변수", varsHelp: '입력값은 브라우저 세션에만 보관되며 설정 파일에 기록되지 않습니다. 설정 파일의 {{VAR}} 자리에 치환됩니다.', configFileLabel: "설정 파일", intervalLabel: "요청 간격", intervalUnit: "ms", runAllButton: "전체 실행", stopButton: "중지", reloadButton: "다시 읽기", 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; } } };