문제 해결
여기에 나열된 항목은 모두 실제로 누군가가 겪은 오류 사례입니다. 각 항목에는 증상, 발생 원인, 해결 방법이 정리되어 있으며, 대부분 다음 세 가지 중 하나로 귀결됩니다. 해당 문구가 프로젝트에 없거나, SDK가 초기화되지 않았거나, 자리 표시자가 잘못된 컨텍스트에 맞게 작성된 경우입니다.
아무것도 번역되지 않음 — 모든 문구가 원본 언어로 그대로 표시됨
모든 것이 원문 텍스트로 표시된다면 SDK 초기화에 실패했을 가능성이 매우 높습니다. SDK는 설계상 오류가 나도 조용히 넘어가므로(UI가 깨지면 안 되기 때문입니다) 확인할 수 있는 뚜렷한 신호는 브라우저 콘솔뿐입니다. 먼저 콘솔을 확인하세요. 일반적인 원인은 가능성이 높은 순서대로 다음과 같습니다.
- CORS. SDK의 첫 번째 요청은
authorize-project입니다. API에서 앱의 오리진을 허용 목록에 추가하지 않으면 브라우저가 이 요청을 차단하고 초기화가 바로 그 자리에서 실패합니다. 오리진은 정확히 스킴 + 호스트 + 포트라는 점을 기억하세요.localhost:5173과localhost:5174는 서로 다른 오리진이며, 개발 서버는 기본 포트가 사용 중이면 다른 포트로 옮겨 가기도 합니다. 실제로 개발에 사용하는 모든 포트를 허용 목록에 추가하세요. - 잘못된 자격 증명. 프로젝트 ID/API 키가 없거나 잘못 입력된 경우에도 동일한 authorize 호출이 실패합니다. 콘솔에서 실패한 요청과 URL을 확인할 수 있습니다.
- 현재 기본 로케일을 보고 계십니다. 기본 로케일에서는 "번역"이 곧 원본 텍스트이므로 눈에 보이는 변화가 없습니다. 테스트하려면 대상 로케일로 전환하세요.
# The SDK's first call is an authorize-project request.
# If the browser blocks it, NOTHING translates. Check the console for:
Access to fetch at 'https://your-api/api/authorize-project/...'
from origin 'http://localhost:5173' has been blocked by CORS policy
# Fix: allowlist the app's EXACT origin (scheme + host + port) on the API.
CORS_ALLOWED_ORIGINS=http://localhost:5173,https://app.example.com 한 문구만 번역되지 않는 경우 (다른 문구는 번역됨)
코드 속 문구가 곧 조회 키입니다. 즉, 문구가 곧 키입니다. 주변 문구는 번역되는데 특정 문구 하나만 원문 그대로 표시된다면, 해당 키가 프로젝트 카탈로그에 없는 것입니다.
- 등록된 적이 없습니다. 등록은 토큰 탐색을 통해 이루어지며, 쓰기 키로만 가능합니다. 앱이 읽기 전용 키로 실행되는 동안 문구가 만들어졌다면 어디에도 전송되지 않은 것입니다. 개발 환경에서 쓰기 키로 해당 화면을 한 번 실행하거나, 대시보드에서 문구를 추가하세요.
- 텍스트가 변경되었습니다. 단어, 문장 부호, 심지어 공백만 바뀌어도 아직 번역이 없는 새로운 문구가 됩니다. 이전 번역은 이전 키에 그대로 남아 있습니다. 특히 콘텐츠 블록에서 이런 일이 자주 발생합니다.
<Translate>블록은 마크업을 기준으로 토큰화되므로, 블록의 텍스트나 구조를 수정하면 조회 대상이 달라지고, 새로운 형태가 등록되어 번역되기 전까지 블록은 조용히 원본 텍스트로 대체됩니다. - 기계 번역이 아직 따라오지 못했습니다. 새로 등록된 문구는 번역이 반영되기까지 잠시 시간이 걸릴 수 있으며, 프로젝트의 대상 목록에 없는 로캘은 번역이 아예 생성되지 않습니다. 대시보드에서 해당 문구를 확인해 보세요. 등록되었나요? 이 로캘로 번역되었나요?
누락된 문구가 있어도 오류가 발생하지 않는 이유는 무엇인가요?
의도된 동작입니다. 프로덕션에서 번역 시스템은 페이지가 깨지지 않고 읽을 수 있는 원문으로 대체되어야 합니다. 그래서 등록되지 않은 문구는 원문 그대로 표시되고 SDK는 아무 알림도 내지 않습니다. 반면 읽기 전용 키에서는 누락된 문구가 조용히 넘어갑니다. 로그도 남지 않고 등록도 되지 않으며, 사용자가 영어를 보고 나서야 알게 됩니다. 이를 막는 방법이 키 분리입니다. 쓰기 키로 개발해 UI에 렌더링되는 모든 문구를 발견한 다음, 읽기 전용으로 배포하세요.
# Development — write key: new phrases are discovered,
# registered, and machine-translated as you build.
VITE_LANGSYS_API_KEY=write-key
# Production — read-only key: fetches the finished catalog only.
# Unregistered phrases silently render their source text.
VITE_LANGSYS_API_KEY=read-only-key 플레이스홀더가 그대로 표시됩니다 — "Hi %name%" 또는 "Hi {name}"
t()호출에서 플레이스홀더는 중괄호 하나{name}를 사용하며, 보간은t('Hello, {name}!', 'Greetings', { name })처럼 매개변수를 전달할 때만 실행됩니다.- React, Vue, Svelte의
<Translate>/<Phrase>마크업에서는 플레이스홀더를%name%형식으로 작성하세요. 프레임워크 컴파일러는{name}형태를 그대로 두면 SDK가 읽기 전에 표현식으로 처리해 버립니다. 반면%name%는 컴파일 후에도 유지되며, SDK가 캡처 시점에 이를 다시{name}로 정규화합니다. 충돌할 템플릿 구문이 없는 순수 HTML에서는 일반{name}를 그대로 사용해도 됩니다. - 매개변수가 누락되었나요?
params가 제공되지 않으면 템플릿이 그대로 렌더링됩니다. 지금 보이는%name%가 바로 그 원본 문자열입니다.
로케일을 이리저리 바꾸면 처음엔 잘 작동하다가 이후에 깨집니다
SDK를 업데이트하세요. 이 영역에서 실제로 수정된 버그는 다음과 같습니다. 초기/SSR 로케일로 돌아가면 이전 카탈로그가 계속 활성 상태로 남던 문제(langsys-js-typescript 0.2.2에서 수정), 그리고 콘텐츠 블록 Translate/Phrase 사용처가 서버에서 시드된 카탈로그에서 무한정 멈출 수 있던 문제(0.2.3에서 수정)입니다. 이전 로케일의 문자열이 보이거나 블록이 끝내 해결되지 않는다면, 직접 작성한 코드를 디버깅하기 전에 설치된 버전을 기본 SDK의 변경 내역과 비교해 확인하세요.
서버 측: 페이지가 단계적으로 저하되지 않고 500 오류를 반환함
PHP SDK의 클라이언트는 자격 증명 없이 생성하면 예외를 던집니다. 따라서 LANGSYS_API_KEY가 비어 있는 Laravel(또는 일반 PHP) 앱은 번역이 실행되기 전, 컨테이너 해석 단계에서 실패합니다. 자격 증명을 설정하세요. 자격 증명 없이도 앱이 부팅되어야 한다면 데모 앱처럼 대체 클라이언트를 바인딩하세요. 자격 증명이 있으면 런타임 API 오류는 정상적으로 처리됩니다. API에 연결할 수 없거나 API가 요청을 거부하면 원본 텍스트를 렌더링하고 예외를 보고할 뿐, 페이지가 중단되지는 않습니다.
서버 측: 첫 요청은 멈춰 있다가 이후에는 모두 빠르게 처리됨
PHP SDK의 HTTP 타임아웃은 30초이며, 유휴 상태에서 깨어나는 API 인스턴스는 첫 호출에서 이 시간에 근접할 수 있습니다. 이 요청은 느리게 완료되거나(이 경우 카탈로그 캐시가 워밍업되어 이후 요청은 모두 캐시에서 제공됩니다), 타임아웃되어 원본 텍스트가 렌더링됩니다. 한동안 사용하지 않은 뒤 첫 페이지 로드가 느리다면 이 때문입니다. 연동 문제가 아니라 API가 워밍업되는 중입니다.
서버 측: 쓰기 키로 렌더링했는데 대시보드에 문구가 나타나지 않습니다
발견된 문구는 요청 처리 중에 대기열에 추가되었다가 응답이 전송된 후에 전송됩니다. 이는 Laravel의 terminable 미들웨어 방식입니다. 대시보드는 페이지가 로드되는 도중이 아니라 로드된 직후 잠시 뒤에 새로고침하세요. 장시간 실행되는 워커 런타임(Octane, FrankenPHP, RoadRunner)에서는 반드시 SDK에서 제공하는 통합을 사용하세요. 일반 PHP 종료 훅은 워커가 요청을 처리하는 사이에는 실행되지 않습니다.
서버 렌더링: 한 요청의 번역이 다른 요청으로 새어 들어가는 문제
JS 기본 SDK는 모듈 수준 싱글턴이므로 한 번에 하나의 로캘 카탈로그만 보유합니다. 동시 요청에 대해 서로 다른 로캘을 렌더링하는 Node 서버에서는 경합 상태가 발생합니다. 요청마다 카탈로그를 가져와 이를 기반으로 클라이언트를 초기화하세요. Next.js, Nuxt, SvelteKit 가이드에 나온 방식입니다. 또는 프로세스당 하나의 로캘만 실행하세요.
여전히 해결되지 않았나요? 실패한 요청의 콘솔 출력(또는 서버 로그)과 함께 SDK 저장소에 이슈를 등록해 주세요 — typescript, react, vue, svelte, php, laravel 모두 가능합니다. 또는 [email protected]으로 문의해 주세요.