@sayren/mcp 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -142,14 +142,14 @@ function buildPath(template, params) {
142
142
  const used = /* @__PURE__ */ new Set();
143
143
  const path = template.replace(/\{([^}]+)\}/g, (_match, name) => {
144
144
  const value = params[name];
145
- if (value === void 0 || value === "") throw new Error(`경로 값이 없어요: ${name}`);
145
+ if (value === void 0 || value === "") throw new Error(`경로 값이 없습니다: ${name}`);
146
146
  const text$1 = String(value);
147
- if (text$1 === "." || text$1 === ".." || !SAFE_SEGMENT.test(text$1)) throw new Error(`쓸 수 없는 경로 값이에요: ${name}`);
147
+ if (text$1 === "." || text$1 === ".." || !SAFE_SEGMENT.test(text$1)) throw new Error(`쓸 수 없는 경로 값입니다: ${name}`);
148
148
  used.add(name);
149
149
  return encodeURIComponent(text$1);
150
150
  });
151
151
  const extra = Object.keys(params).filter((name) => !used.has(name));
152
- if (extra.length) throw new Error(`경로에 없는 값이에요: ${extra.join(", ")}`);
152
+ if (extra.length) throw new Error(`경로에 없는 값입니다: ${extra.join(", ")}`);
153
153
  return path;
154
154
  }
155
155
  /**
@@ -163,7 +163,7 @@ function truncateData(data, maxChars) {
163
163
  while (kept > 0 && JSON.stringify(rebuild(items.slice(0, kept))).length > maxChars) kept = Math.floor(kept / 2);
164
164
  return {
165
165
  data: rebuild(items.slice(0, kept)),
166
- truncated: `목록 ${items.length}개 중 ${kept}개만 실었어요. size·page로 나눠 조회하세요`
166
+ truncated: `목록 ${items.length}개 중 ${kept}개만 실었습니다. size·page로 나눠 조회하십시오`
167
167
  };
168
168
  };
169
169
  if (Array.isArray(data)) return shrink(data, (kept) => kept);
@@ -175,29 +175,29 @@ function truncateData(data, maxChars) {
175
175
  const text$1 = JSON.stringify(data);
176
176
  return {
177
177
  data: `${text$1.slice(0, maxChars)}…`,
178
- truncated: `응답 ${text$1.length}자 중 ${maxChars}자만 실었어요`
178
+ truncated: `응답 ${text$1.length}자 중 ${maxChars}자만 실었습니다`
179
179
  };
180
180
  }
181
181
  /** 같은 멱등키의 승인이 끝났을 때 상태별 안내 — 에이전트가 다음에 할 일 */
182
182
  const DECIDED_GUIDE = {
183
183
  SUCCEEDED: {
184
- message: "이 멱등키의 요청은 셀러가 승인해서 이미 실행됐어요. 다시 실행하지 않았어요",
184
+ message: "이 멱등키의 요청은 셀러가 승인해서 이미 실행됐습니다. 다시 실행하지 않았습니다",
185
185
  nextSteps: ["실행 결과는 `result`다. 같은 요청을 다시 보내지 않는다"]
186
186
  },
187
187
  FAILED: {
188
- message: "이 멱등키의 요청은 승인 뒤 실행했지만 실패했어요",
188
+ message: "이 멱등키의 요청은 승인 뒤 실행했지만 실패했습니다",
189
189
  nextSteps: ["`result.error`로 원인을 사용자에게 알린다", "고쳐서 다시 하려면 새 멱등키로 요청한다(같은 키는 이 결과만 돌려준다)"]
190
190
  },
191
191
  UNKNOWN: {
192
- message: "이 멱등키의 요청은 승인 뒤 실행 결과를 확인하지 못했어요. 이미 실행됐을 수 있어요",
192
+ message: "이 멱등키의 요청은 승인 뒤 실행 결과를 확인하지 못했습니다. 이미 실행됐을 수 있습니다",
193
193
  nextSteps: ["다시 보내지 않는다. 대상 리소스를 조회해 바뀌었는지 확인하고 사용자에게 알린다", "셀러에게 콘솔의 감사 로그에서 실행 여부를 확인해 달라고 전한다"]
194
194
  },
195
195
  REJECTED: {
196
- message: "이 멱등키의 요청은 셀러가 거절했어요. 아무것도 바뀌지 않았어요",
196
+ message: "이 멱등키의 요청은 셀러가 거절했습니다. 아무것도 바뀌지 않았습니다",
197
197
  nextSteps: ["거절 사유(`decisionNote`)를 전한다. 사용자가 원하면 새 멱등키로 다시 요청한다"]
198
198
  },
199
199
  EXPIRED: {
200
- message: "이 멱등키의 요청은 승인 기한(24시간)이 지났어요. 아무것도 바뀌지 않았어요",
200
+ message: "이 멱등키의 요청은 승인 기한(24시간)이 지났습니다. 아무것도 바뀌지 않았습니다",
201
201
  nextSteps: ["사용자가 원하면 새 멱등키로 다시 요청한다"]
202
202
  }
203
203
  };
@@ -231,7 +231,7 @@ function describeWriteResult(result) {
231
231
  body: {
232
232
  status: "APPROVAL_EXECUTING",
233
233
  applied: "unknown",
234
- message: "셀러가 승인해서 서버가 이 요청을 실행하고 있어요",
234
+ message: "셀러가 승인해서 서버가 이 요청을 실행하고 있습니다",
235
235
  approvalId: pending.approvalId,
236
236
  nextSteps: ["같은 요청을 다시 보내지 않는다. 결과는 `get_approval_request`로 확인한다"]
237
237
  }
@@ -241,7 +241,7 @@ function describeWriteResult(result) {
241
241
  body: {
242
242
  status: "APPROVAL_PENDING",
243
243
  applied: false,
244
- message: "승인 대기 중이에요. 아직 아무것도 바뀌지 않았어요. 셀러가 콘솔에서 승인하면 서버가 이 요청을 그대로 실행해요",
244
+ message: "승인 대기 중입니다. 아직 아무것도 바뀌지 않았습니다. 셀러가 콘솔에서 승인하면 서버가 이 요청을 그대로 실행합니다",
245
245
  approvalId: pending.approvalId,
246
246
  reason: pending.reason,
247
247
  expiresAt: pending.expiresAt,
@@ -256,7 +256,7 @@ function describeWriteResult(result) {
256
256
  body: {
257
257
  status: "DRY_RUN",
258
258
  applied: false,
259
- message: "실행하지 않았어요. 서버의 판정과 영향 미리보기예요",
259
+ message: "실행하지 않았습니다. 서버의 판정과 영향 미리보기입니다",
260
260
  ...result.data
261
261
  }
262
262
  };
@@ -314,7 +314,7 @@ var AdminApi = class {
314
314
  redirect: "error",
315
315
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
316
316
  });
317
- if (!response.ok) throw new Error(`API 문서를 읽지 못했어요 (HTTP ${response.status})`);
317
+ if (!response.ok) throw new Error(`API 문서를 읽지 못했습니다 (HTTP ${response.status})`);
318
318
  const document = await response.json();
319
319
  this.spec = {
320
320
  document,
@@ -326,7 +326,7 @@ var AdminApi = class {
326
326
  async find(operationId) {
327
327
  const { document, operations } = await this.load();
328
328
  const op = operations.find((candidate) => candidate.operationId === operationId);
329
- if (!op) throw new Error(`쓸 수 있는 오퍼레이션이 아니에요: ${operationId}. list_operations의 operationId를 쓰세요`);
329
+ if (!op) throw new Error(`쓸 수 있는 오퍼레이션이 아닙니다: ${operationId}. list_operations의 operationId를 쓰십시오`);
330
330
  return {
331
331
  document,
332
332
  op
@@ -372,7 +372,7 @@ var AdminApi = class {
372
372
  const DEFAULT_API_ORIGIN = "https://api.sayren.app";
373
373
  function loadConfig(env = process.env) {
374
374
  const token = env.SAYREN_TOKEN?.trim();
375
- if (!token) throw new Error("SAYREN_TOKEN이 없어요. 셀러 콘솔 개발자 공간 › API 토큰에서 발급한 토큰을 MCP 설정의 env에 넣어주세요");
375
+ if (!token) throw new Error("SAYREN_TOKEN이 없습니다. 셀러 콘솔 개발자 공간 › API 토큰에서 발급한 토큰을 MCP 설정의 env에 넣어주십시오");
376
376
  const origin = (env.SAYREN_API_ORIGIN ?? DEFAULT_API_ORIGIN).replace(/\/+$/, "");
377
377
  return {
378
378
  apiOrigin: origin,
@@ -390,7 +390,7 @@ async function callApi(url, init = {}) {
390
390
  const body = await response.json().catch(() => null);
391
391
  if (!response.ok) {
392
392
  const code = body?.error?.code ?? String(response.status);
393
- const message = body?.error?.message ?? "요청이 실패했어요";
393
+ const message = body?.error?.message ?? "요청이 실패했습니다";
394
394
  throw new Error(`${code}: ${message}`);
395
395
  }
396
396
  return body?.data ?? body;
@@ -412,7 +412,7 @@ async function fetchStoreContext(config$1) {
412
412
  const payment = paymentSettings ? {
413
413
  connectedProviders: connected.map((p) => p.provider),
414
414
  sandboxOnly: connected.length === 0 || connected.every((p) => p.sandboxMode)
415
- } : { unavailable: "결제 설정은 셀러 콘솔 OWNER만 볼 수 있어 이 토큰으로는 확인하지 못했어요" };
415
+ } : { unavailable: "결제 설정은 셀러 콘솔 OWNER만 볼 수 있어 이 토큰으로는 확인하지 못했습니다" };
416
416
  return {
417
417
  storeId: store.storeId,
418
418
  storeCode: store.storeCode,
@@ -519,12 +519,12 @@ function resolveSpec(name, spec, meta, options) {
519
519
  if (options.link && name.startsWith("@sayren/")) return "workspace:*";
520
520
  if (spec === "catalog:" || spec === "catalog:default") {
521
521
  const version$1 = meta.catalog[name];
522
- if (!version$1) throw new Error(`catalog에 ${name} 버전이 없어요`);
522
+ if (!version$1) throw new Error(`catalog에 ${name} 버전이 없습니다`);
523
523
  return version$1;
524
524
  }
525
525
  if (spec.startsWith("workspace:")) {
526
526
  const version$1 = meta.versions[name];
527
- if (!version$1) throw new Error(`${name}의 배포 버전을 모르겠어요`);
527
+ if (!version$1) throw new Error(`${name}의 배포 버전을 모르겠습니다`);
528
528
  return `^${version$1}`;
529
529
  }
530
530
  return spec;
@@ -598,7 +598,7 @@ function resolveRoot() {
598
598
  for (const candidate of CANDIDATES) try {
599
599
  if (statSync(candidate).isDirectory()) return candidate;
600
600
  } catch {}
601
- throw new Error("스토어프론트 템플릿을 찾지 못했어요");
601
+ throw new Error("스토어프론트 템플릿을 찾지 못했습니다");
602
602
  }
603
603
  const TEMPLATE_ROOT = resolveRoot();
604
604
  function walk(dir, acc) {
@@ -623,7 +623,7 @@ function listTemplateFiles() {
623
623
  */
624
624
  function readTemplateFile(path, options = {}) {
625
625
  const normalized = path.replace(/^\.?\//, "");
626
- if (!listTemplateFiles().includes(normalized)) throw new Error(`템플릿에 없는 파일이에요: ${path}`);
626
+ if (!listTemplateFiles().includes(normalized)) throw new Error(`템플릿에 없는 파일입니다: ${path}`);
627
627
  let file = join(TEMPLATE_ROOT, normalized);
628
628
  if (normalized.endsWith(".gitignore") && !existsSync(file)) file = file.replace(/\.gitignore$/, BUNDLED_GITIGNORE);
629
629
  const raw = readFileSync(file, "utf8");
@@ -922,7 +922,7 @@ function buildReport(files) {
922
922
  },
923
923
  results,
924
924
  findings: reported,
925
- message: findings.length ? `규칙 ${RULES.length}개 중 ${violated}개를 어겼어요(위반 ${findings.length}건). 아래 findings의 fix대로 고친 뒤 다시 검사해주세요` : `규칙 ${RULES.length}개를 모두 지켰어요. 정적 검사가 잡지 못하는 것이 있으니 직접 띄워 결제까지 한 번 해보세요`,
925
+ message: findings.length ? `규칙 ${RULES.length}개 중 ${violated}개를 어겼습니다(위반 ${findings.length}건). 아래 findings의 fix대로 고친 뒤 다시 검사해주십시오` : `규칙 ${RULES.length}개를 모두 지켰습니다. 정적 검사가 잡지 못하는 것이 있으니 직접 띄워 결제까지 한 번 해보십시오`,
926
926
  nextSteps: nextSteps(reported)
927
927
  };
928
928
  }
@@ -995,7 +995,7 @@ async function runCreate(argv) {
995
995
  }
996
996
  const target = resolve(args.dir);
997
997
  if (existsSync(target) && readdirSync(target).length > 0 && !args.force) {
998
- console.error(`폴더가 비어 있지 않아요: ${target} (--force로 덮어쓸 수 있어요)`);
998
+ console.error(`폴더가 비어 있지 않습니다: ${target} (--force로 덮어쓸 수 있습니다)`);
999
999
  return 1;
1000
1000
  }
1001
1001
  const name = args.name ?? basename(target);
@@ -1015,7 +1015,7 @@ async function runCreate(argv) {
1015
1015
  const env = `SAYREN_API_URL=${store.storefrontApiBaseUrl}\nSAYREN_STORE_CODE=${store.storeCode}\n`;
1016
1016
  writeFileSync(join(target, ".env"), env);
1017
1017
  console.log(`.env 작성 — ${store.name} (${store.storeCode})`);
1018
- } else console.log("SAYREN_TOKEN이 없어 .env는 비워 뒀어요. SAYREN_API_URL과 SAYREN_STORE_CODE를 넣어주세요");
1018
+ } else console.log("SAYREN_TOKEN이 없어 .env는 비워 뒀습니다. SAYREN_API_URL과 SAYREN_STORE_CODE를 넣어주십시오");
1019
1019
  const startedAt = Date.now();
1020
1020
  const report$1 = buildReport(await collectSources(target));
1021
1021
  const durationMs = Date.now() - startedAt;
@@ -1026,7 +1026,7 @@ async function runCreate(argv) {
1026
1026
  }
1027
1027
  console.log(`\n다음:\n cd ${args.dir}\n pnpm install\n pnpm dev`);
1028
1028
  console.log(`
1029
- 디자인을 고친 뒤에는 MCP 도구 \`verify_storefront\`로 다시 검사해주세요. 결제·구매자 인증·방문 분석 규칙 ${report$1.summary.rules}개를 보고, 어긴 항목마다 고칠 자리와 수정 방법·예시 코드를 알려 줘요.\n결제까지 해 보려면 셀러 콘솔 설정 › 결제에서 결제 도메인에 이 쇼핑몰 주소를 등록해주세요(테스트 결제는 localhost가 항상 허용돼요).`);
1029
+ 디자인을 고친 뒤에는 MCP 도구 \`verify_storefront\`로 다시 검사해주십시오. 결제·구매자 인증·방문 분석 규칙 ${report$1.summary.rules}개를 보고, 어긴 항목마다 고칠 자리와 수정 방법·예시 코드를 알려 줍니다.\n결제까지 해 보려면 셀러 콘솔 설정 › 결제에서 결제 도메인에 이 쇼핑몰 주소를 등록해주십시오(테스트 결제는 localhost가 항상 허용됩니다).`);
1030
1030
  if (config$1) await new Telemetry(config$1).reportVerify("create", report$1, durationMs);
1031
1031
  return 0;
1032
1032
  }
@@ -1276,7 +1276,7 @@ server.registerTool("verify_storefront", {
1276
1276
  const files = await collectSources(projectDir);
1277
1277
  if (!files.length) return text({
1278
1278
  ok: false,
1279
- message: `소스를 찾지 못했어요: ${projectDir}`,
1279
+ message: `소스를 찾지 못했습니다: ${projectDir}`,
1280
1280
  nextSteps: ["`projectDir`이 프로젝트 루트(package.json이 있는 폴더)의 절대 경로인지 확인한다.", "템플릿을 아직 받지 않았으면 `list_template_files`·`get_template_file`로 먼저 만든다."]
1281
1281
  });
1282
1282
  const report$1 = buildReport(files);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sayren-mcp": "./dist/index.mjs"
@@ -12,8 +12,8 @@
12
12
  "dependencies": {
13
13
  "@modelcontextprotocol/sdk": "^1.22.0",
14
14
  "zod": "^4.6.5",
15
- "@sayren/store-sdk": "^0.14.0",
16
- "@sayren/storefront-sdk": "^0.11.0"
15
+ "@sayren/store-sdk": "^0.14.1",
16
+ "@sayren/storefront-sdk": "^0.11.1"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@biomejs/biome": "^2.5.14",
@@ -7,8 +7,8 @@
7
7
  "zod": "^4.6.5"
8
8
  },
9
9
  "versions": {
10
- "@sayren/store-sdk": "0.14.0",
11
- "@sayren/storefront-sdk": "0.11.0",
10
+ "@sayren/store-sdk": "0.14.1",
11
+ "@sayren/storefront-sdk": "0.11.1",
12
12
  "@sayren/ui": "0.1.0"
13
13
  }
14
14
  }
@@ -18,6 +18,6 @@ export function resolveStoreCode(): string {
18
18
  const fromHost = storeCodeFromHost(getRequestHeader("host"));
19
19
  if (fromHost) return fromHost;
20
20
  throw new Error(
21
- "스토어 코드를 알 수 없어요. SAYREN_STORE_CODE 환경변수를 설정하거나 서브도메인으로 서비스해주세요",
21
+ "스토어 코드를 알 수 없습니다. SAYREN_STORE_CODE 환경변수를 설정하거나 서브도메인으로 서비스해주십시오",
22
22
  );
23
23
  }
@@ -67,14 +67,14 @@ function Root() {
67
67
  function RootError({ error }: ErrorComponentProps) {
68
68
  return (
69
69
  <ErrorView
70
- title="문제가 생겼어요"
70
+ title="문제가 생겼습니다"
71
71
  detail={error instanceof Error ? error.message : undefined}
72
72
  />
73
73
  );
74
74
  }
75
75
 
76
76
  function NotFound() {
77
- return <ErrorView title="찾을 수 없어요" />;
77
+ return <ErrorView title="찾을 수 없습니다" />;
78
78
  }
79
79
 
80
80
  function ErrorView({ title, detail }: { title: string; detail?: string }) {
@@ -53,7 +53,7 @@ const changeConsents = createServerFn({ method: "POST" })
53
53
  .validator(consentInput)
54
54
  .handler(async ({ data }) => {
55
55
  const accessToken = readToken();
56
- if (!accessToken) return { error: "다시 로그인해 주세요" };
56
+ if (!accessToken) return { error: "다시 로그인해 주십시오" };
57
57
  try {
58
58
  await apiFor({ accessToken }).member.updateConsents({
59
59
  ...(data.reconsent ? { terms: true as const, privacy: true as const } : {}),
@@ -73,7 +73,7 @@ const startLink = createServerFn({ method: "POST" })
73
73
  .validator(providerInput)
74
74
  .handler(async ({ data }) => {
75
75
  const accessToken = readToken();
76
- if (!accessToken) return { url: null, error: "다시 로그인해 주세요" };
76
+ if (!accessToken) return { url: null, error: "다시 로그인해 주십시오" };
77
77
  const origin = new URL(getRequestUrl()).origin;
78
78
  try {
79
79
  const started = await authFor().linkIdp(accessToken, data.provider, {
@@ -95,7 +95,7 @@ const unlink = createServerFn({ method: "POST" })
95
95
  .validator(providerInput)
96
96
  .handler(async ({ data }) => {
97
97
  const accessToken = readToken();
98
- if (!accessToken) return { error: "다시 로그인해 주세요" };
98
+ if (!accessToken) return { error: "다시 로그인해 주십시오" };
99
99
  try {
100
100
  await authFor().unlinkIdp(accessToken, data.provider);
101
101
  return { error: null };
@@ -105,18 +105,18 @@ const unlink = createServerFn({ method: "POST" })
105
105
  });
106
106
 
107
107
  const ERROR_MESSAGE: Record<string, string> = {
108
- IDENTITY_IN_USE: "이 계정은 이미 다른 회원에 연결돼 있어요",
108
+ IDENTITY_IN_USE: "이 계정은 이미 다른 회원에 연결돼 있습니다",
109
109
  PROVIDER_ALREADY_LINKED:
110
- "같은 서비스의 다른 계정이 이미 연결돼 있어요. 해제한 뒤 다시 연결해 주세요",
111
- LAST_LOGIN_METHOD: "마지막 로그인 수단은 해제할 수 없어요. 다른 계정을 먼저 연결해 주세요",
112
- access_denied: "연결을 취소했어요",
113
- state_mismatch: "연결 요청이 만료됐어요. 다시 시도해 주세요",
110
+ "같은 서비스의 다른 계정이 이미 연결돼 있습니다. 해제한 뒤 다시 연결해 주십시오",
111
+ LAST_LOGIN_METHOD: "마지막 로그인 수단은 해제할 수 없습니다. 다른 계정을 먼저 연결해 주십시오",
112
+ access_denied: "연결을 취소했습니다",
113
+ state_mismatch: "연결 요청이 만료됐습니다. 다시 시도해 주십시오",
114
114
  };
115
115
 
116
116
  function accountErrorMessage(error: unknown): string {
117
117
  if (error instanceof ApiError)
118
- return ERROR_MESSAGE[error.code] ?? "처리하지 못했어요. 잠시 후 다시 시도해 주세요";
119
- return "처리하지 못했어요. 잠시 후 다시 시도해 주세요";
118
+ return ERROR_MESSAGE[error.code] ?? "처리하지 못했습니다. 잠시 후 다시 시도해 주십시오";
119
+ return "처리하지 못했습니다. 잠시 후 다시 시도해 주십시오";
120
120
  }
121
121
 
122
122
  export const Route = createFileRoute("/account")({
@@ -135,7 +135,7 @@ function Account() {
135
135
  const error =
136
136
  actionError ??
137
137
  (callbackError
138
- ? (ERROR_MESSAGE[callbackError] ?? "연결하지 못했어요. 다시 시도해 주세요")
138
+ ? (ERROR_MESSAGE[callbackError] ?? "연결하지 못했습니다. 다시 시도해 주십시오")
139
139
  : null);
140
140
 
141
141
  const changeConsent = (data: { reconsent?: boolean; marketing?: boolean }) => {
@@ -187,7 +187,7 @@ function Account() {
187
187
  <h2 className="font-bold text-lg">로그인 수단</h2>
188
188
  {linked ? (
189
189
  <p className="rounded-md bg-chip px-3 py-2 text-sm">
190
- {PROVIDER_NAME[linked]} 계정을 연결했어요
190
+ {PROVIDER_NAME[linked]} 계정을 연결했습니다
191
191
  </p>
192
192
  ) : null}
193
193
  <form onSubmit={submit}>
@@ -240,7 +240,7 @@ function Account() {
240
240
  </form>
241
241
  {error ? <p className="text-point text-sm">{error}</p> : null}
242
242
  <p className="text-muted text-xs">
243
- 연결한 계정으로도 같은 회원으로 로그인해요. 마지막 로그인 수단은 해제할 수 없어요.
243
+ 연결한 계정으로도 같은 회원으로 로그인합니다. 마지막 로그인 수단은 해제할 수 없습니다.
244
244
  </p>
245
245
  </section>
246
246
 
@@ -252,7 +252,7 @@ function Account() {
252
252
  <span className="font-medium">이용약관 · 개인정보 수집이용</span>
253
253
  <span className="text-muted text-xs">
254
254
  {consents.reconsentRequired
255
- ? "약관이 개정됐어요. 다시 동의해 주세요"
255
+ ? "약관이 개정됐습니다. 다시 동의해 주십시오"
256
256
  : `동의 완료 · v${consents.terms.agreedVersion ?? "-"}`}
257
257
  </span>
258
258
  {/* 스토어가 약관 주소를 정했으면 열어 볼 수 있게 한다 */}
@@ -73,7 +73,7 @@ function CartPage() {
73
73
  if (!cart.items.length) {
74
74
  return (
75
75
  <div className="space-y-4 py-16 text-center">
76
- <p className="text-muted">장바구니가 비었어요</p>
76
+ <p className="text-muted">장바구니가 비었습니다</p>
77
77
  <Link to="/products" className="inline-block rounded-md bg-ink px-5 py-3 text-white">
78
78
  상품 보러 가기
79
79
  </Link>
@@ -107,7 +107,7 @@ function CartPage() {
107
107
  ) : null}
108
108
  {!item.purchasable ? (
109
109
  <span className="text-point text-xs">
110
- {item.unpurchasableReason === "SOLD_OUT" ? "품절이에요" : "지금은 살 수 없어요"}
110
+ {item.unpurchasableReason === "SOLD_OUT" ? "품절입니다" : "지금은 살 수 없습니다"}
111
111
  </span>
112
112
  ) : null}
113
113
  <div className="flex items-center gap-3 pt-1">
@@ -171,7 +171,7 @@ function CartPage() {
171
171
  {loginRequired ? (
172
172
  <div className="flex w-full flex-col gap-2 md:w-64">
173
173
  <p className="text-center text-muted text-xs">
174
- 이 스토어는 회원만 주문할 수 있어요. 담은 상품은 로그인하면 그대로 이어져요
174
+ 이 스토어는 회원만 주문할 수 있습니다. 담은 상품은 로그인하면 그대로 이어집니다
175
175
  </p>
176
176
  <Link
177
177
  to="/login"
@@ -34,11 +34,11 @@ function CheckoutComplete() {
34
34
  return (
35
35
  <div className="space-y-6 py-10 text-center">
36
36
  <div className="space-y-2">
37
- <h1 className="font-bold text-2xl">주문을 완료했어요</h1>
37
+ <h1 className="font-bold text-2xl">주문을 완료했습니다</h1>
38
38
  {orderId ? (
39
39
  <p className="text-muted text-sm">주문번호 {orderId}</p>
40
40
  ) : (
41
- <p className="text-muted text-sm">주문 내역에서 주문을 확인해주세요</p>
41
+ <p className="text-muted text-sm">주문 내역에서 주문을 확인해주십시오</p>
42
42
  )}
43
43
  </div>
44
44
 
@@ -64,7 +64,10 @@ const quoteDelivery = createServerFn({ method: "POST" })
64
64
  const quote = await api.checkout.quoteDelivery(data.checkoutId, { zipCode: data.zipCode });
65
65
  return { quote, error: null };
66
66
  } catch {
67
- return { quote: null, error: "배송비를 확인하지 못했어요. 우편번호를 다시 확인해주세요." };
67
+ return {
68
+ quote: null,
69
+ error: "배송비를 확인하지 못했습니다. 우편번호를 다시 확인해주십시오.",
70
+ };
68
71
  }
69
72
  });
70
73
 
@@ -125,20 +128,21 @@ const startPayment = createServerFn({ method: "POST" })
125
128
  });
126
129
 
127
130
  function startErrorMessage(error: unknown): string {
128
- if (!(error instanceof ApiError)) return "결제를 시작하지 못했어요. 잠시 후 다시 시도해주세요.";
131
+ if (!(error instanceof ApiError))
132
+ return "결제를 시작하지 못했습니다. 잠시 후 다시 시도해주십시오.";
129
133
  switch (error.code) {
130
134
  case "PAYMENT_OPTION_UNAVAILABLE":
131
- return "지금 쓸 수 없는 결제수단이에요. 다른 결제수단을 골라주세요.";
135
+ return "지금 쓸 수 없는 결제수단입니다. 다른 결제수단을 골라주십시오.";
132
136
  case "PAYMENT_PROVIDER_UNAVAILABLE":
133
- return "결제창을 준비하지 못했어요. 다른 결제수단으로 시도해주세요.";
137
+ return "결제창을 준비하지 못했습니다. 다른 결제수단으로 시도해주십시오.";
134
138
  case "PAYMENT_NOT_CONFIGURED":
135
- return "결제 수단을 준비하고 있어요. 잠시 후 다시 시도해주세요.";
139
+ return "결제 수단을 준비하고 있습니다. 잠시 후 다시 시도해주십시오.";
136
140
  case "CASH_RECEIPT_NOT_AVAILABLE":
137
- return "현금영수증은 계좌이체·가상계좌 결제에서만 신청할 수 있어요.";
141
+ return "현금영수증은 계좌이체·가상계좌 결제에서만 신청할 수 있습니다.";
138
142
  case "RETURN_URL_NOT_ALLOWED":
139
- return "이 쇼핑몰 주소에서는 결제할 수 없어요. 셀러 콘솔 설정 › 결제에서 결제 도메인을 확인해주세요.";
143
+ return "이 쇼핑몰 주소에서는 결제할 수 없습니다. 셀러 콘솔 설정 › 결제에서 결제 도메인을 확인해주십시오.";
140
144
  default:
141
- return "결제를 시작하지 못했어요. 입력한 내용을 확인하고 다시 시도해주세요.";
145
+ return "결제를 시작하지 못했습니다. 입력한 내용을 확인하고 다시 시도해주십시오.";
142
146
  }
143
147
  }
144
148
 
@@ -210,10 +214,10 @@ function Checkout() {
210
214
  }
211
215
  setNotice(
212
216
  result.status === "CANCELED"
213
- ? "결제를 취소했어요. 다른 결제수단으로 다시 시도해주세요."
217
+ ? "결제를 취소했습니다. 다른 결제수단으로 다시 시도해주십시오."
214
218
  : result.status === "FAILED"
215
219
  ? result.message
216
- : "결제 결과를 확인하지 못했어요. 주문 내역을 확인해주세요.",
220
+ : "결제 결과를 확인하지 못했습니다. 주문 내역을 확인해주십시오.",
217
221
  );
218
222
  };
219
223
 
@@ -221,7 +225,7 @@ function Checkout() {
221
225
  event.preventDefault();
222
226
  const option = options.find((item) => paymentOptionKey(item) === selected);
223
227
  if (!option) {
224
- setNotice("결제수단을 골라주세요.");
228
+ setNotice("결제수단을 골라주십시오.");
225
229
  return;
226
230
  }
227
231
  const form = new FormData(event.currentTarget);
@@ -233,7 +237,7 @@ function Checkout() {
233
237
  identityNumber: field("cashReceiptNumber"),
234
238
  });
235
239
  if (!parsed.success) {
236
- setNotice(parsed.error.issues[0]?.message ?? "현금영수증 번호를 확인해주세요.");
240
+ setNotice(parsed.error.issues[0]?.message ?? "현금영수증 번호를 확인해주십시오.");
237
241
  return;
238
242
  }
239
243
  cashReceipt = parsed.data;
@@ -275,7 +279,7 @@ function Checkout() {
275
279
  })
276
280
  .catch(() => {
277
281
  paymentWindow.close();
278
- setNotice("결제를 시작하지 못했어요. 잠시 후 다시 시도해주세요.");
282
+ setNotice("결제를 시작하지 못했습니다. 잠시 후 다시 시도해주십시오.");
279
283
  })
280
284
  .finally(() => setSubmitting(false));
281
285
  };
@@ -293,7 +297,7 @@ function Checkout() {
293
297
  placeholder="04524"
294
298
  required
295
299
  onBlur={handleZipBlur}
296
- hint="입력하면 지역 추가 배송비까지 반영한 배송비를 보여 줘요"
300
+ hint="입력하면 지역 추가 배송비까지 반영한 배송비를 보여 줍니다"
297
301
  />
298
302
  <Field label="주소" name="address1" required />
299
303
  <Field label="상세 주소" name="address2" />
@@ -310,7 +314,7 @@ function Checkout() {
310
314
  name="orderPassword"
311
315
  type="password"
312
316
  required
313
- hint="주문 내역을 찾을 때 쓸 6자 이상 비밀번호예요"
317
+ hint="주문 내역을 찾을 때 쓸 6자 이상 비밀번호입니다"
314
318
  />
315
319
  </section>
316
320
  )}
@@ -318,7 +322,7 @@ function Checkout() {
318
322
  <fieldset className="space-y-2">
319
323
  <legend className="mb-3 font-bold text-lg">결제수단</legend>
320
324
  {options.length === 0 ? (
321
- <p className="text-muted text-sm">지금 결제할 수 있는 결제수단이 없어요.</p>
325
+ <p className="text-muted text-sm">지금 결제할 수 있는 결제수단이 없습니다.</p>
322
326
  ) : (
323
327
  <div className="grid grid-cols-2 gap-2">
324
328
  {options.map((option) => {
@@ -438,7 +442,7 @@ function Checkout() {
438
442
  ))}
439
443
  {checkout.testPayment ? (
440
444
  <p className="rounded-md bg-chip px-3 py-2 text-xs">
441
- 테스트 결제예요. 실제로 돈이 오가지 않아요.
445
+ 테스트 결제입니다. 실제로 돈이 오가지 않습니다.
442
446
  </p>
443
447
  ) : null}
444
448
  {notice ? <p className="rounded-md bg-chip px-3 py-2 text-sm">{notice}</p> : null}
@@ -451,10 +455,10 @@ function Checkout() {
451
455
  function deliveryNotes(delivery: CheckoutDelivery): string[] {
452
456
  const notes: string[] = [];
453
457
  if (delivery.bundleCount >= 2) {
454
- notes.push(`배송 묶음 ${delivery.bundleCount}건 — 묶음마다 배송비가 붙어요.`);
458
+ notes.push(`배송 묶음 ${delivery.bundleCount}건 — 묶음마다 배송비가 붙습니다.`);
455
459
  }
456
- if (delivery.freeByThreshold) notes.push("무료배송 기준 금액을 넘겨 기본 배송비가 빠졌어요.");
457
- if (delivery.zipCode === null) notes.push("배송지를 입력하면 배송비가 확정돼요.");
460
+ if (delivery.freeByThreshold) notes.push("무료배송 기준 금액을 넘겨 기본 배송비가 빠졌습니다.");
461
+ if (delivery.zipCode === null) notes.push("배송지를 입력하면 배송비가 확정됩니다.");
458
462
  return notes;
459
463
  }
460
464
 
@@ -107,17 +107,17 @@ function PaymentReturn() {
107
107
  <div className="mx-auto max-w-md space-y-6 py-16 text-center">
108
108
  {view.kind === "working" || view.kind === "processing" ? (
109
109
  <>
110
- <h1 className="font-bold text-2xl">결제를 확인하고 있어요</h1>
110
+ <h1 className="font-bold text-2xl">결제를 확인하고 있습니다</h1>
111
111
  <p className="text-muted text-sm">
112
112
  {view.kind === "processing"
113
- ? "결제사에서 결과를 확인하는 중이에요. 이 화면을 닫지 말고 잠시만 기다려주세요."
114
- : "잠시만 기다려주세요."}
113
+ ? "결제사에서 결과를 확인하는 중입니다. 이 화면을 닫지 말고 잠시만 기다려주십시오."
114
+ : "잠시만 기다려주십시오."}
115
115
  </p>
116
116
  </>
117
117
  ) : view.kind === "failed" ? (
118
118
  <>
119
119
  <h1 className="font-bold text-2xl">
120
- {view.canceled ? "결제를 취소했어요" : "결제하지 못했어요"}
120
+ {view.canceled ? "결제를 취소했습니다" : "결제하지 못했습니다"}
121
121
  </h1>
122
122
  {view.canceled ? null : <p className="text-muted text-sm">{view.message}</p>}
123
123
  <div className="flex justify-center gap-2">
@@ -128,10 +128,10 @@ function PaymentReturn() {
128
128
  </>
129
129
  ) : (
130
130
  <>
131
- <h1 className="font-bold text-2xl">결제 결과를 확인하지 못했어요</h1>
131
+ <h1 className="font-bold text-2xl">결제 결과를 확인하지 못했습니다</h1>
132
132
  <p className="text-muted text-sm">
133
- 결제를 마쳤다면 잠시 뒤 주문 내역에 나타나요. 다시 결제하기 전에 주문 내역을 먼저
134
- 확인해주세요.
133
+ 결제를 마쳤다면 잠시 뒤 주문 내역에 나타납니다. 다시 결제하기 전에 주문 내역을 먼저
134
+ 확인해주십시오.
135
135
  </p>
136
136
  <Link to="/orders" className="inline-block rounded-md border border-ink px-5 py-3">
137
137
  주문 내역
@@ -48,7 +48,7 @@ function Home() {
48
48
  ) : null}
49
49
 
50
50
  <ProductSection listId="home:recommend" title="추천 상품" products={recommended} />
51
- <ProductSection listId="home:latest" title="새로 들어왔어요" products={latest} />
51
+ <ProductSection listId="home:latest" title="새로 들어온 상품" products={latest} />
52
52
  </div>
53
53
  );
54
54
  }
@@ -74,18 +74,18 @@ const passwordLogin = createServerFn({ method: "POST" })
74
74
 
75
75
  function loginErrorMessage(error: unknown): string {
76
76
  if (error instanceof ApiError) {
77
- if (error.code === "INVALID_CREDENTIALS") return "이메일이나 비밀번호가 맞지 않아요";
78
- if (error.code === "PASSWORD_LOGIN_DISABLED") return "이 스토어는 소셜 로그인만 받아요";
77
+ if (error.code === "INVALID_CREDENTIALS") return "이메일이나 비밀번호가 맞지 않습니다";
78
+ if (error.code === "PASSWORD_LOGIN_DISABLED") return "이 스토어는 소셜 로그인만 받습니다";
79
79
  if (error.code === "ACCOUNT_LOCKED")
80
- return "로그인을 여러 번 실패해 잠시 잠겼어요. 10분 뒤에 다시 시도해 주세요";
81
- if (error.status === 400) return "이메일과 비밀번호를 확인해 주세요";
80
+ return "로그인을 여러 번 실패해 잠시 잠겼습니다. 10분 뒤에 다시 시도해 주십시오";
81
+ if (error.status === 400) return "이메일과 비밀번호를 확인해 주십시오";
82
82
  }
83
- return "로그인하지 못했어요. 잠시 후 다시 시도해 주세요";
83
+ return "로그인하지 못했습니다. 잠시 후 다시 시도해 주십시오";
84
84
  }
85
85
 
86
86
  const CALLBACK_ERRORS: Record<string, string> = {
87
- access_denied: "로그인을 취소했어요",
88
- state_mismatch: "로그인 요청이 만료됐어요. 다시 시도해 주세요",
87
+ access_denied: "로그인을 취소했습니다",
88
+ state_mismatch: "로그인 요청이 만료됐습니다. 다시 시도해 주십시오",
89
89
  };
90
90
 
91
91
  export const Route = createFileRoute("/login")({
@@ -105,7 +105,7 @@ function Login() {
105
105
  const error =
106
106
  actionError ??
107
107
  (callbackError
108
- ? (CALLBACK_ERRORS[callbackError] ?? "로그인하지 못했어요. 다시 시도해 주세요")
108
+ ? (CALLBACK_ERRORS[callbackError] ?? "로그인하지 못했습니다. 다시 시도해 주십시오")
109
109
  : null);
110
110
 
111
111
  const submitSocial = (event: React.FormEvent<HTMLFormElement>) => {
@@ -195,7 +195,7 @@ function Login() {
195
195
  ) : null}
196
196
  {error ? <p className="text-point text-sm">{error}</p> : null}
197
197
  {social.length ? (
198
- <p className="text-center text-muted text-xs">처음 소셜 로그인하면 가입이 함께 돼요</p>
198
+ <p className="text-center text-muted text-xs">처음 소셜 로그인하면 가입이 함께 됩니다</p>
199
199
  ) : null}
200
200
  </div>
201
201
  );
@@ -62,18 +62,18 @@ const createClaim = createServerFn({ method: "POST" })
62
62
  });
63
63
 
64
64
  function claimErrorMessage(error: unknown): string {
65
- if (!(error instanceof ApiError)) return "접수하지 못했어요. 잠시 후 다시 시도해주세요.";
65
+ if (!(error instanceof ApiError)) return "접수하지 못했습니다. 잠시 후 다시 시도해주십시오.";
66
66
  switch (error.code) {
67
67
  case "INVALID_QUANTITY":
68
- return "남은 수량보다 많이 신청했어요. 수량을 확인해주세요.";
68
+ return "남은 수량보다 많이 신청했습니다. 수량을 확인해주십시오.";
69
69
  case "CLAIM_IN_PROGRESS":
70
- return "이미 진행 중인 신청이 있어요.";
70
+ return "이미 진행 중인 신청이 있습니다.";
71
71
  case "INVALID_STATUS":
72
- return "지금은 신청할 수 없는 상태예요. 주문 상태를 확인해주세요.";
72
+ return "지금은 신청할 수 없는 상태입니다. 주문 상태를 확인해주십시오.";
73
73
  case "PERIOD_EXPIRED":
74
- return "반품·교환 가능 기간이 지났어요.";
74
+ return "반품·교환 가능 기간이 지났습니다.";
75
75
  default:
76
- return error.message || "접수하지 못했어요. 잠시 후 다시 시도해주세요.";
76
+ return error.message || "접수하지 못했습니다. 잠시 후 다시 시도해주십시오.";
77
77
  }
78
78
  }
79
79
 
@@ -147,7 +147,7 @@ function OrderDetail() {
147
147
 
148
148
  {result ? (
149
149
  <div className="space-y-1 rounded-md bg-chip p-4 text-sm">
150
- <p className="font-semibold">신청이 접수됐어요 (수량 {result.quantity}개)</p>
150
+ <p className="font-semibold">신청이 접수됐습니다 (수량 {result.quantity}개)</p>
151
151
  {/* 상품 금액 환불은 수량 비율이고, 반품 배송비는 이미 빠져 있다. 배송비 환불은 따로 온다 */}
152
152
  {result.expectedRefundAmount === null ? null : (
153
153
  <p className="text-muted">
@@ -271,9 +271,9 @@ function ClaimForm({
271
271
  const quantity = Number(quantityText);
272
272
  const quantityError =
273
273
  quantityText.trim() === "" || !Number.isInteger(quantity)
274
- ? "수량을 입력해주세요."
274
+ ? "수량을 입력해주십시오."
275
275
  : quantity < 1 || quantity > item.activeQuantity
276
- ? `수량은 1개 이상 ${item.activeQuantity}개 이하로 입력해주세요.`
276
+ ? `수량은 1개 이상 ${item.activeQuantity}개 이하로 입력해주십시오.`
277
277
  : null;
278
278
  return (
279
279
  <form
@@ -20,7 +20,7 @@ function Orders() {
20
20
  const orders = Route.useLoaderData();
21
21
 
22
22
  if (!orders.contents.length) {
23
- return <p className="py-16 text-center text-muted">아직 주문이 없어요</p>;
23
+ return <p className="py-16 text-center text-muted">아직 주문이 없습니다</p>;
24
24
  }
25
25
 
26
26
  return (
@@ -107,7 +107,7 @@ function ProductDetail() {
107
107
  setError(null);
108
108
  addToCart({ data: { productId: product.productId, optionId, quantity } })
109
109
  .then(() => navigate({ to: "/cart" }))
110
- .catch(() => setError("장바구니에 담지 못했어요. 다시 시도해주세요"))
110
+ .catch(() => setError("장바구니에 담지 못했습니다. 다시 시도해주십시오"))
111
111
  .finally(() => setSubmitting(false));
112
112
  }}
113
113
  >
@@ -71,7 +71,7 @@ function Products() {
71
71
  type="search"
72
72
  name="keyword"
73
73
  defaultValue={applied.keyword ?? ""}
74
- placeholder="상품을 검색해요"
74
+ placeholder="상품 검색"
75
75
  className="h-11 flex-1 rounded-md border border-line px-3"
76
76
  />
77
77
  {applied.categoryId ? (
@@ -104,7 +104,7 @@ function Products() {
104
104
  </div>
105
105
 
106
106
  {page.contents.length === 0 ? (
107
- <p className="py-16 text-center text-muted">조건에 맞는 상품이 없어요</p>
107
+ <p className="py-16 text-center text-muted">조건에 맞는 상품이 없습니다</p>
108
108
  ) : (
109
109
  <div className="grid grid-cols-2 gap-4 md:grid-cols-4">
110
110
  {page.contents.map((product) => (