1. 왜 매번 사용자에게 묻지 말고 플랫폼에서 locale을 받아야 할까
로컬라이제이션을 “옛 방식”으로 접근하면 보통 이렇게 합니다: 모달 창으로 “언어를 선택하세요”를 보여주고 결과를 localStorage에 저장합니다. ChatGPT Apps에서는 접근이 다릅니다. 이미 똑똑한 플랫폼이 있고, 언어와 지역에 대한 신호를 아낌없이 제공합니다. 이 신호들을 활용하는 법을 익혀서 사용자를 불필요한 질문으로 괴롭히지 않는 것이 중요합니다.
ChatGPT는 당신의 App으로 가는 각 요청에 다음을 컨텍스트로 추가합니다:
- 사용자의 선호 로케일(언어 + 지역) — 필드 openai/locale / _meta["openai/locale"];
- 사용자의 지리/지역 — 필드 _meta["openai/userLocation"].
위젯(프런트엔드)에서는 window.openai 또는 SDK 훅을 통해 locale을 받고, MCP/백엔드 쪽에서는 MCP 요청의 _meta를 통해 받습니다.
결과적으로 정상적인 시나리오는 다음과 같습니다: 사용자가 “엄마 선물을 50유로 한도 내에서 골라줘”라고 입력합니다. ChatGPT는 이미 그의 locale과 userLocation을 알고 있고, 플랫폼은 이 신호들을 당신의 App에 전달합니다. 그러면 당신은:
- 이해 가능한 언어로 UI를 보여주고,
- 올바른 언어의 카탈로그를 불러오며,
- 필요한 통화와 형식으로 가격을 포맷합니다.
“그런데, 어떤 언어를 쓰시나요?” 같은 별도의 대화 없이요.
2. 신호 №1: openai/locale — 사용자의 언어와 지역
무슨 필드이고 어떻게 생겼나
openai/locale은 BCP‑47 형식의 문자열로, 아마 익숙하실 겁니다: "en", "en-US", "ru", "ru-RU", "uk-UA" 등.
플랫폼은 다음을 보낼 수 있습니다:
- 언어만 ("en", "ru"),
- 언어 + 지역 ("en-US", "en-GB", "fr-CA").
BCP‑47은 브라우저의 Intl API와 대부분의 i18n 라이브러리가 훌륭하게 지원하는 표준입니다. 즉, openai/locale을 거의 그대로 Intl.NumberFormat, 번역 엔진, 그리고 여러분의 tools 내부로 전달해도 됩니다.
위젯에서 locale은 어디에 있나
ChatGPT 내부에서 렌더링되는 커스텀 UI에서는 Apps SDK가 전역 객체 window.openai를 제공하며, 그 안에 locale이 있습니다.
일반적으로는 다음과 같습니다(TypeScript, Next.js 16, 우리의 GiftGenius 위젯):
// src/app/widgets/gift-widget.tsx
declare global {
interface Window {
openai?: { locale?: string };
}
}
function getOpenAiLocale(): string {
if (typeof window === "undefined") return "en";
return window.openai?.locale || "en";
}
실제 앱에서는 ChatGPT 샌드박스와 Storybook 모두에서 동작하는 훅을 만드는 편이 더 간단합니다:
// src/app/hooks/useOpenAiLocale.ts
import { useEffect, useState } from "react";
export function useOpenAiLocale(defaultLocale: string = "en") {
const [locale, setLocale] = useState(defaultLocale);
useEffect(() => {
if (typeof window === "undefined") return;
const next = window.openai?.locale || defaultLocale;
setLocale(next);
}, [defaultLocale]);
return locale;
}
이제 어떤 컴포넌트에서든:
import { useOpenAiLocale } from "../hooks/useOpenAiLocale";
export function GiftHeader() {
const locale = useOpenAiLocale();
return (
<h2>
{/* 나중에 여기에는 t('titles.gift_search') */}
{locale.startsWith("ru") ? "선물 검색" : "Gift search"}
</h2>
);
}
4강에서 모든 문자열을 사전에 정리하겠지만, 지금도 이미 무작위 navigator.language가 아니라 플랫폼의 실제 신호에 UI를 연결했습니다. 이 훅은 특화되어 있습니다. 실제 프로젝트에서는 ChatGPT 글로벌 값에 접근하는 더 일반적인 메커니즘 위에 이 훅을 얹어두면 편리한데 — 이에 대해서는 아래에서 별도로 다룹니다.
MCP/백엔드에서 locale은 어디에 있나
ChatGPT가 MCP 도구를 호출할 때, SDK는 JSON‑rpc 요청에 _meta["openai/locale"]를 전달합니다. TypeScript 서버(우리의 GiftGenius MCP)에서는 보통 도구 핸들러의 두 번째 인자에서 접근할 수 있습니다.
예시:
// src/mcp/server.ts
import { McpServer } from "@openai/mcp-sdk";
const server = new McpServer();
server.registerTool(
"suggest_gifts",
{
title: "선물 추천",
description: "선호도에 따라 선물 목록을 제안합니다",
inputSchema: {
type: "object",
properties: {
recipient: { type: "string" },
budget: { type: "number" }
},
required: ["recipient", "budget"]
}
},
async ({ input }, extra) => {
const locale = extra?._meta?.["openai/locale"] || "en";
// 이후 올바른 카탈로그를 로드할 수 있습니다
const gifts = await loadGiftCatalog(locale);
// ...
return {
content: [
{
type: "text",
text: `Found ${gifts.length} gifts for locale ${locale}`
}
],
structuredContent: { gifts }
};
}
);
이처럼 locale은 스택 전체를 관통해 존재합니다: ChatGPT → Apps SDK → MCP 서버.
Insight
서버의 각 mcp-tool에는 extra라는 파라미터가 있으며, 여기에 mcp 서버가 inputSchema에 담기지 않은 모든 데이터를 넣습니다. 다음은 그런 객체의 예시입니다:
{
sessionId: undefined, // 항상 undefined, 아래의 `openai/subject`를 사용하세요
_meta: {
'openai/userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
'openai/locale': 'en-US', // 사용자의 컴퓨터 locale, 채팅 언어와 일치하지 않을 수 있음
'openai/userLocation': { // 상당히 정확한 사용자 위치
city: 'London',
region: 'London City',
country: 'GB',
timezone: 'Europe/London',
latitude: '5.45466',
longitude: '-0.52380'
},
timezone_offset_minutes: -240, // 시간대 오프셋
'openai/subject': 'v1/sEtRuS92UEOPNdwzEUZORfeOKf7XSk2KZoIUGfAsb68BzZ8h5FAOgrH' // 이것이 sessionId
},
authInfo: undefined,
requestId: 1,
requestInfo: {
headers: {
accept: 'application/json, text/event-stream',
'accept-encoding': 'gzip, deflate, br, zstd',
'access-control-allow-headers': '*',
'access-control-allow-methods': 'GET,POST,PUT,DELETE,OPTIONS',
'access-control-allow-origin': '*',
'content-length': '542',
'content-type': 'application/json',
host: 'test.ngrok.app', // 앱의 원래 도메인
'mcp-protocol-version': '2025-11-25',
traceparent: '00-69399d3a000000004fb8cc13dc3a2203-8748a8698107eb34-00',
tracestate: 'dd=s:-1;p:01514e334c1ccef5;t.dm:-3',
'user-agent': 'openai-mcp/1.0.0',
'x-datadog-parent-id': '6089244476286233754',
'x-datadog-sampling-priority': '-1',
'x-datadog-tags': '_dd.p.tid=69399c3a00000000,_dd.p.dm=-3',
'x-datadog-trace-id': '5744565710382309891',
'x-forwarded-for': '199.210.139.232',
'x-forwarded-host': 'test.ngrok.app',
'x-forwarded-port': '3001',
'x-forwarded-proto': 'https'
}
},
}
아마 일부 헤더는 ngrok가 채웠을 수 있지만, 그래도 흥미로운 데이터가 많습니다.
3. 신호 №2: _meta["openai/userLocation"] — 사용자의 지리 정보
구조와 의미
_meta["openai/userLocation"]은 국가, 지역, 도시, 시간대 그리고 좌표까지 담는 지리 정보 객체입니다. 대략 다음과 같습니다:
{
"city": "London",
"region": "England",
"country": "GB",
"timezone": "Europe/London",
"latitude": 51.5074,
"longitude": -0.1278
}
GiftGenius에서 실제로 자주 사용할 핵심 필드:
- country — 두 글자의 ISO 국가 코드로, 품목 구성과 통화에 매우 중요;
- timezone — 날짜/시간 형식과 리마인더에 유용.
Insight
실험적으로 확인됨 — userLocation 판별은 매우 정확하게 동작합니다. 데이터는 각 MCP-tool 호출마다 extra._meta["openai/userLocation"] 파라미터로 전달됩니다. 여러분의 앱을 개발할 때 이 신호에 충분히 의존해도 됩니다.
MCP 도구에서 userLocation을 사용하는 방법
MCP 서버에서는 _meta["openai/userLocation"]가 _meta["openai/locale"] 옆에 있습니다.
우리 도구 예시를 확장해 봅시다:
server.registerTool(
"suggest_gifts",
{ /* schema는 위와 동일 */ },
async ({ input }, extra) => {
const meta = extra?._meta ?? {};
const locale = (meta["openai/locale"] as string) || "en";
const userLocation = meta["openai/userLocation"] as
| { country?: string; city?: string }
| undefined;
const country = userLocation?.country || "US";
const gifts = await loadGiftCatalog(locale, country);
return {
content: [
{
type: "text",
text: `Found ${gifts.length} gifts for locale=${locale}, country=${country}`
}
],
structuredContent: { gifts }
};
}
);
함수 loadGiftCatalog(locale, country)는 이미 다음을 수행할 수 있습니다:
- 적절한 JSON 파일 선택: gift_catalog.en-US.json, gift_catalog.ru-RU.json,
- 해당 국가로 배송할 수 없는 상품 필터링,
- 기본 통화 선택.
조금 뒤의 커머스 모듈에서는 country를 기반으로 세금 규칙을 선택하고 올바른 SKU로 매핑하겠지만, 아키텍처 관점에서는 여전히 같은 신호 — country —에 의존합니다.
userLocation이 locale을 보완하는 방법
고전적인 예:
locale = "en", userLocation.country = "DE".
로직은 다음과 같을 수 있습니다:
- UI와 프롬프트 — 영어(로케일을 존중);
- 통화와 가격 형식 — 사용자가 독일에 있으므로 유로;
- 선물 목록 — DE로 배송 가능한 상품만.
GiftGenius에서는 작은 헬퍼 함수로 표현할 수 있습니다:
export function deriveCurrency(locale: string, country?: string): string {
if (country === "DE") return "EUR";
if (country === "JP") return "JPY";
if (locale === "zh_CN") return "CNY";
return "USD";
}
그리고 가격 포맷을 위해 백엔드/프런트엔드에서 사용합니다:
const currency = deriveCurrency(locale, country);
const formatted = new Intl.NumberFormat(locale, {
style: "currency",
currency
}).format(price);
백엔드에서는 이미 locale과 country로 카탈로그와 통화를 선택하는 법을 익혔습니다. 이제 중요한 것은 동일한 신호를 위젯의 UI까지 정확히 전달하여, 사용자가 예상한 형식의 텍스트와 가격을 보게 하는 것입니다.
4. GiftGenius 위젯에서 locale과 userLocation을 받는 법
우리는 이미 locale과 userLocation이 MCP 측에서 어떻게 존재하며 카탈로그와 통화에 영향을 주는지 살펴봤습니다. 이제 locale을 GiftGenius 위젯으로 가져와 React UI에서 바로 활용하는 방법을 정리해 봅시다.
중요: 위젯에서는 locale에만 직접 접근할 수 있습니다(window.openai와 SDK 훅을 통해). userLocation은 _meta에 있으며 MCP/백엔드 측에서 사용됩니다 — 위에서 이미 다뤘습니다.
Apps SDK에는 “원시” window.openai 외에도 React 훅 형태의 유틸리티가 있습니다. 문서에서는 useOpenAiGlobal("locale") 같은 훅이 설명되어 있으며, ChatGPT 글로벌 컨텍스트의 값을 React 컴포넌트로 끌어옵니다.
무슨 일이 벌어지는지 이해하기 위해 이 훅을 직접 가볍게 구현해 봅시다.
기본 훅 useOpenAiGlobal
앞에서는 특화된 useOpenAiLocale을 만들었습니다. 실전에서는 ChatGPT 글로벌에 접근하기 위한 범용 훅 하나를 갖고 있으면 더 편리합니다 — 이 위에 useOpenAiLocale 같은 래퍼를 쉽게 얹을 수 있습니다. 예를 들어:
// src/app/hooks/useOpenAiGlobal.ts
import { useEffect, useState } from "react";
type OpenAiGlobals = {
locale?: string;
// 나중에 theme, userAgent 등도 추가할 수 있습니다.
};
export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
key: K,
fallback?: NonNullable<OpenAiGlobals[K]>
): NonNullable<OpenAiGlobals[K]> {
const [value, setValue] = useState<NonNullable<OpenAiGlobals[K]>>(
(fallback ?? "") as NonNullable<OpenAiGlobals[K]>
);
useEffect(() => {
if (typeof window === "undefined") return;
const globals = (window.openai || {}) as OpenAiGlobals;
const next = globals[key] ?? fallback;
if (next !== undefined) {
setValue(next as NonNullable<OpenAiGlobals[K]>);
}
}, [key, fallback]);
return value;
}
이제 useOpenAiGlobal("locale", "en")은 기본값 "en"과 함께 최신 locale 값을 제공합니다.
GiftGenius 위젯에서의 적용
로컬라이즈된 환영 문구와 디버그용 현재 로케일을 보여주는 작은 컴포넌트를 만들어 봅시다:
// src/app/widgets/GiftWelcome.tsx
"use client";
import React from "react";
import { useOpenAiGlobal } from "../hooks/useOpenAiGlobal";
export function GiftWelcome() {
const locale = useOpenAiGlobal("locale", "en");
const greeting =
locale.startsWith("ru") || locale.startsWith("uk")
? "안녕하세요! 멋진 선물 찾기를 도와드릴게요."
: "Hi! I’ll help you find a great gift.";
return (
<div>
<p>{greeting}</p>
<small style={{ opacity: 0.6 }}>Debug locale: {locale}</small>
</div>
);
}
아직 사전이나 i18n 라이브러리는 쓰지 않습니다 — 그건 나중에 다룹니다. 지금 중요한 건, 이미 임의의 추정이 아니라 ChatGPT에서 제공하는 언어를 “정직하게” 가져와 쓴다는 점입니다.
5. 언제 사용자에게 언어를 명시적으로 물어봐야 할까
openai/locale과 userLocation이 그렇게 좋다면, 사용자에게 선호 언어를 전혀 묻지 않아도 될까요? 아쉽지만, 가끔은 물어봐야 합니다.
신호만으로 부족할 때
대표적인 상황은 다음과 같습니다:
- ChatGPT 계정은 영어(locale = "en")지만, 사용자는 러시아어로 입력합니다. 모델은 러시아어로 답하지만, 당신의 UI는 영어로 표시됩니다.
- 사용자는 독일에 있고(userLocation.country = "DE"), locale = "en"이며, 당신은 독일어와 영어 UI를 모두 제공할 준비가 되어 있습니다.
- 언어가 중요한 도메인: 심리치료, 법률 상담, 교육 등. 이 경우 자동 감지의 편의성보다 정확한 이해가 더 중요합니다.
이런 경우에는 시나리오 초반에 짧고 정중한 한 번의 질문이 적절하며, 이후에는 선택을 기억해 두면 됩니다.
부담 없이 언어를 묻는 방법
보통 문구를 최대한 간단하고 시각적으로 구성합니다. 예를 들면:
- “어떤 언어가 더 편하신가요: English 또는 Russian?”
- “사용자님의 언어를 English로 감지했습니다. 다른 언어로 전환하시겠어요?”
ChatGPT App에서는 두 가지 방식이 있습니다:
- 위젯 UI를 통해: 상단에 작은 언어 전환 스위치를 렌더링합니다.
- App 이름으로 채팅에 follow‑up 메시지를 보내서 질문하고, 답변을 처리합니다.
코드: GiftGenius에서의 간단한 언어 선택
다음과 같은 스위처 컴포넌트를 만듭니다. 이 컴포넌트는:
- locale에서 시작 언어를 가져오고,
- 사용자가 ru 또는 en을 선택할 수 있게 하며,
- 선택을 위젯 상태에 저장합니다(당장은 React state만 사용).
// src/app/widgets/LanguageSwitcher.tsx
"use client";
import React, { useState, useEffect } from "react";
import { useOpenAiGlobal } from "../hooks/useOpenAiGlobal";
type SupportedLocale = "en" | "ru";
export function LanguageSwitcher(props: {
onChange?: (locale: SupportedLocale) => void;
}) {
const initialLocale = useOpenAiGlobal("locale", "en");
const [locale, setLocale] = useState<SupportedLocale>("en");
useEffect(() => {
const normalized: SupportedLocale = initialLocale.startsWith("ru")
? "ru"
: "en";
setLocale(normalized);
props.onChange?.(normalized);
}, [initialLocale, props]);
const handleChange = (next: SupportedLocale) => {
setLocale(next);
props.onChange?.(next);
};
return (
<div style={{ marginBottom: 8 }}>
<span style={{ marginRight: 8 }}>
{locale === "ru" ? "언어:" : "Language:"}
</span>
<button
type="button"
onClick={() => handleChange("en")}
style={{ fontWeight: locale === "en" ? "bold" : "normal" }}
>
EN
</button>
<button
type="button"
onClick={() => handleChange("ru")}
style={{ fontWeight: locale === "ru" ? "bold" : "normal", marginLeft: 4 }}
>
RU
</button>
</div>
);
}
그리고 메인 GiftGenius 위젯에서는 더 이상 “원시” ChatGPT 데이터가 아니라 selectedLocale을 기준으로 텍스트/사전을 선택할 수 있습니다.
향후 강의에서는 로컬 state를 더 안정적인 저장소로 교체하겠습니다(예: 선택한 언어를 _meta["openai/subject"]를 통해 MCP / Gateway로 전달). 하지만 패턴 자체는 동일합니다.
6. locale과 userLocation을 백엔드로 전달하고 저장하는 방법
ChatGPT에서 신호가 “위에서” 오지만, 거기서 끝이 아닙니다. 이후 이 데이터를 도구와 서비스로 전달하고, 중간에 잃어버리지 않으며, 모델이 언어를 다시 추측하지 않도록 해야 합니다.
tools 인자에 locale 필드를 명시적으로 추가
가장 확실한 방법은 도구의 inputSchema에 locale(필요하다면 country도)을 별도 필드로 추가하는 것입니다. 그러면 모델은 “이 필드를 채워야 한다”는 명확한 신호를 받습니다.
server.registerTool(
"suggest_gifts",
{
title: "Gift suggestions",
description: "Suggest gifts based on recipient and budget",
inputSchema: {
type: "object",
properties: {
recipient: { type: "string" },
budget: { type: "number" },
locale: {
type: "string",
description: "Current user UI locale, BCP-47 (e.g. en-US, fr-FR)"
},
country: {
type: "string",
description: "ISO country code (e.g. US, DE)"
}
},
required: ["recipient", "budget"]
}
},
async ({ input }, extra) => {
// 모델이 locale/country를 채우지 않았다면, _meta로 보강:
const meta = extra?._meta ?? {};
const locale = input.locale || (meta["openai/locale"] as string) || "en";
const country =
input.country ||
(meta["openai/userLocation"] as any)?.country ||
"US";
// ...
}
);
이렇게 하면 서버 내부의 “마법”이 줄어듭니다. 모델이 사용하려는 인자를 서버가 명확히 볼 수 있기 때문입니다.
세션/사용자 수준에서 locale 저장
MCP Gateway(향후 모듈) 아키텍처에서는 locale, currency, 선호 설정 같은 “클라이언트 상태”를 저장하는 것이 일반적입니다. 지금은 개념만 이해하면 충분합니다. ChatGPT에서 신호를 한 번 읽은 뒤에는, 매번 다시 계산하지 말고 세션 상태의 일부로 활용한다는 아이디어입니다.
예시적인 의사코드:
// gateway.ts
const sessionState = new Map<string, { locale: string; country?: string }>();
function onMcpRequest(request: any) {
const subject = request._meta?.["openai/subject"]; // 익명 사용자 ID
const locale = request._meta?.["openai/locale"] || "en";
const country = request._meta?.["openai/userLocation"]?.country;
if (subject) {
sessionState.set(subject, { locale, country });
}
// 이후 locale/country를 특정 MCP 서버로 전달
}
이 강의 범위에서는 Gateway를 구현할 필요는 없습니다. locale과 userLocation이 이러한 “세션 상태”의 유력 후보라는 점만 이해하면 충분합니다.
Insight
실험 데이터: request._meta?.["openai/locale"]는 현재 설정된 사용자의 로케일을 보여줍니다. 대화 언어는 inputSchema를 통해 tool의 파라미터로 받을 수 있습니다.
컴퓨터의 로케일은 EN으로 설정하고, ChatGPT와는 독일어(DE)로 대화했습니다. 그 결과:
- request._meta?.["openai/locale"]는 EN이었고,
- inputSchema의 tool 파라미터로 받은 locale은 DE였습니다.
7. locale vs 텍스트 기반 자동 언어 감지
개발자들은 가끔 이렇게 생각합니다: “사용자 텍스트로 언어를 자동 감지하면 되지, LLM이 다 하잖아.” 실전에서는 openai/locale에 의존하는 것보다 거의 항상 나쁩니다.
이유는 현실적입니다:
- 사용자가 여러 언어를 섞어 쓸 수 있습니다;
- 미묘한 차이(uk-UA vs ru-RU)는 한두 개 메시지로는 잘 감지되지 않습니다;
- ChatGPT가 이미 이 작업을 수행했고 locale을 보내줍니다.
자동 감지는 openai/locale이 이상한 형식으로 오거나 없을 때(fallback) 유용할 수 있지만(요즘은 드뭅니다), 그 위에 핵심 로직을 세우는 것은 권장되지 않습니다. 간단한 규칙:
- 먼저 openai/locale을 “진실”로 봅니다;
- 그다음 userLocation을 고려합니다(통화, 품목 구성);
- 아주 애매한 경우에만 마지막 메시지의 언어를 추가 신호로 참고합니다.
8. locale과 userLocation의 다양한 조합: 시나리오 표
정리를 위해 GiftGenius가 다양한 시나리오에서 어떻게 동작해야 하는지 살펴봅시다.
| 시나리오 | locale | userLocation.country | UI 언어 | 통화 | 카탈로그 |
|---|---|---|---|---|---|
| 1 | |
|
EN | |
US 상품 |
| 2 | |
|
우크라이나어/러시아어 | |
UA 상품 |
| 3 | |
|
EN | |
DE 상품 |
| 4 | |
|
RU | |
DE 상품 |
| 5 | |
(데이터 없음) | EN | |
Global default |
이 관점은 나중에 커머스를 논의할 때도 유용합니다. 이미 보이듯이 locale과 country를 다르게 주입하는 것만으로도 동작을 쉽게 바꿀 수 있습니다.
9. 로케일 신호의 간단한 플로우 다이어그램
정리를 위해 단순화된 도식을 보겠습니다:
flowchart TD U[사용자<br/>메시지 작성] --> C[ChatGPT] C -->|결정| L[openai/locale<br/>+ userLocation] L -->|전달| W["Widget (Next.js)"] L -->|_meta로 전달| S[MCP Server] W -->|locale| UI[GiftGenius UI<br/>텍스트 + 숫자 형식] S -->|locale + country| DATA[카탈로그, 가격, 필터] style L fill:#e0f7ff,stroke:#00a style W fill:#f7fff0,stroke:#4b4 style S fill:#fdf0ff,stroke:#b4
중요한 점: 이 도식 어디에도 “언어를 선택하세요”라는 모달 창은 없습니다. 그건 사용자의 기대와 신호가 충돌할 때만 부가적으로 필요합니다.
10. 실습: 지금 바로 App에서 할 수 있는 것
이 강의가 이론으로만 끝나지 않도록, GiftGenius를 위한 짧은 실천 체크리스트:
- 위젯: useOpenAiGlobal("locale") 또는 유사 훅을 추가하고, 최소 한 곳에서 텍스트에 RU/EN 분기 처리를 넣어보세요.
- MCP 서버: 기존 도구 중 하나(suggest_gifts)에서 _meta["openai/locale"]와 _meta["openai/userLocation"]를 추출해 로그로 남기고, 카탈로그 선택에 사용하세요.
- deriveCurrency(locale, country) 함수를 간단히 작성해 가격 포맷팅에 한 곳이라도 사용해 보세요.
처음부터 완전한 i18n 엔진과 15개 언어를 만들 필요는 없습니다 — 지금의 목표는 플랫폼 신호를 올바르게 활용하는 것입니다.
11. locale과 userLocation을 다룰 때 흔한 실수
실수 №1: openai/locale을 완전히 무시하고 navigator.language만 의존.
일반 웹앱에 익숙한 사람들이 흔히 저지르는 실수입니다. ChatGPT에서는 사용자가 브라우저를 전혀 열지 않을 수도 있고, 당신 쪽의 navigator.language는 사용자가 아닌 터널 서버나 Vercel의 언어일 수 있습니다. 결과적으로 ChatGPT가 ru-RU를 꾸준히 보내주는데도 UI가 항상 “수수께끼처럼” 영어로만 나옵니다.
실수 №2: 매번 “어떤 언어가 편하신가요?”를 묻기.
각 채팅의 첫 응답이 언어 설문이면, 사용자는 공항에서 짐을 다섯 번 연속으로 확인받는 기분을 느낍니다. 플랫폼은 이미 언어와 지역을 알고 있습니다 — openai/locale을 존중하고, 명백한 충돌(예: locale = "en"인데 요청이 러시아어)에서만 질문하세요.
실수 №3: 선택한 언어를 UI에만 저장하고 MCP 도구로 전달하지 않기.
위젯은 한국어로 표시되는데, 서버는 언어 변경을 모르기 때문에 여전히 영어 카탈로그를 제공할 수 있습니다. 항상 종단 간 흐름을 생각하세요. UI에 스위처가 있다면, 그 결과를 백엔드로 — 도구 인자이든 Gateway 세션이든 — 전달해야 합니다.
실수 №4: openai/locale을 무시하고 메시지 텍스트만으로 “언어를 추측”하려 하기.
텍스트 기반 자동 감지는 사용자가 순수 영어만 쓸 때는 그럭저럭 동작합니다. 하지만 언어가 섞이거나 유사한 표현이 등장하면 결과가 흔들립니다. openai/locale은 플랫폼이 제공하는 이미 준비된, 충분히 신뢰할 수 있는 판단입니다. 이를 주요 진실 소스로 삼고, 텍스트 감지는 부가 신호로만 사용하세요.
실수 №5: 비즈니스 로직 전반에 if (locale === 'ru') { ... } 같은 방식으로 로컬라이제이션을 섞어두기.
이 강의에서는 단순화를 위해 조금 그렇게 하지만, 문자열·형식·카탈로그는 비즈니스 로직과 분리해야 합니다. 그렇지 않으면 몇 달 뒤엔 모든 함수가 if(locale.startsWith("ru"))로 시작하는 코드가 되어, 언어 하나 더 추가하는 게 고통스러워집니다. 44강에서는 바로 이 문제를 다룰 예정이며, 이미 locale의 출처가 있고 이를 활용할 줄 안다는 점을 기억할 것입니다.
GO TO FULL VERSION