콘텐츠로 이동

번역 시스템

이 사이트는 17개 언어로 제공됩니다. 번역팀이 아니라, Python 스크립트 하나, Claude Opus 모델 하나, 그리고 모든 것이 딱 맞아떨어지도록 설계된 파일 구조로 이루어집니다. 전체 작동 방식은 다음과 같습니다.

문서는 src/content/docs/에 있습니다. 영어가 루트이며 — 다른 모든 언어는 이를 정확히 미러링합니다:

src/content/docs/
├── index.mdx ← English (root)
├── getting-started.md
├── features/
│ ├── play-chess.md
│ ├── multiplayer.md
│ └── ...
├── setup/
│ ├── tts-overview.md
│ └── ...
├── under-the-hood/
│ ├── architecture.md
│ └── ...
├── fr/ ← French
│ ├── index.mdx
│ ├── getting-started.md
│ ├── features/
│ │ ├── play-chess.md
│ │ └── ...
│ └── ...
├── ja/ ← Japanese
│ ├── index.mdx
│ ├── getting-started.md
│ └── ...
└── ... (16 language directories total)

모든 번역 파일은 영어 원본의 구조적 미러입니다. 동일한 파일명, 동일한 하위 디렉토리 경로, 동일한 프론트매터 키. 유일한 차이점은 본문이 다른 언어로 되어 있다는 것입니다.

Starlight(문서 프레임워크)는 이 대칭성에 의존합니다. 사용자가 언어를 전환하면, Starlight는 /docs/getting-started//fr/docs/getting-started/로 교체합니다 — 같은 경로, 다른 로케일 접두사. 프랑스어 파일이 정확히 fr/getting-started.md에 존재하지 않으면, 전환 기능이 깨지거나 조용히 영어로 폴백됩니다.

언어는 전 세계 체스 인구를 기준으로 정렬되어 있으며, Lichess, Chess.com, FIDE 등록 데이터를 기반으로 합니다. 영어가 원본 언어로 첫 번째이고, 나머지는 체스 순위를 따릅니다:

순위코드언어스타일
1en영어원본
2es스페인어표준 격식체
3hi힌디어데바나가리 문자
4ru러시아어표준 격식체
5de독일어표준 격식체
6fr프랑스어표준 격식체
7pt포르투갈어유럽식 포르투갈어
11pl폴란드어표준 격식체
12it이탈리아어표준 격식체
13uk우크라이나어표준 격식체
14tr터키어표준 격식체
17ko한국어합니다/습니다 형
18zh중국어(간체)간체자
zh-tw중국어(번체)번체자
23nb노르웨이어 보크몰표준 보크몰
be벨라루스어표준 벨라루스어
34ja일본어です/ます 형

“스타일” 열은 중요합니다. 일본어와 한국어는 모든 문장에 영향을 미치는 격식체 선택이 있습니다. 번역 프롬프트에 이 지침이 포함되어 있어 모델이 자연스럽고 세련된 문장을 생성합니다 — 딱딱한 기계 번역이 아닙니다.

이 순서는 사이트 헤더의 언어 드롭다운도 제어합니다. 가장 많이 사용되는 체스 언어가 먼저 표시되므로, 사용자가 스크롤 없이 자신의 언어를 찾을 가능성이 높아집니다.

모든 문서는 src/content/docs/에 영어 마크다운으로 시작합니다. 프론트매터에는 titledescription이 있습니다:

---
title: "Getting Started"
description: "Install En Parlant~ and play your first game."
---
Download the latest release...

Python 스크립트(scripts/translate-docs.py)가 모든 영어 원본 파일을 읽고, Claude API로 전송한 후, 번역된 마크다운을 작성합니다:

Terminal window
python3 scripts/translate-docs.py \
--anthropic-key $ANTHROPIC_API_KEY \
--model claude-opus-4-6 \
--workers 5

이 스크립트는 28개 원본 파일을 16개 대상 언어로 번역하는 데 약 60–70분이 소요됩니다(총 448개 파일). 속도 제한 내에서 유지하기 위해 5개의 병렬 API 호출을 실행합니다.

새로운 단일 언어의 경우, 약 4분입니다.

Terminal window
pnpm build

Astro가 원본 마크다운을 읽고, Starlight 템플릿을 통해 렌더링하며, dist/에 정적 HTML을 출력합니다. 약 500개 전체 페이지의 빌드에 약 30초가 소요됩니다.

Terminal window
pnpm run deploy

빌드된 사이트를 Cloudflare Workers로 푸시합니다.

번역 스크립트는 의도적으로 단순합니다 — 약 300줄의 Python입니다. 흐름은 다음과 같습니다:

┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Read English │────▶│ Claude API │────▶│ Write target │
│ source file │ │ (Opus 4.6) │ │ language file │
│ │ │ │ │ │
│ getting- │ │ "Translate into │ │ fr/getting- │
│ started.md │ │ French..." │ │ started.md │
└─────────────────┘ └──────────────────┘ └─────────────────┘
× 5 parallel
× 16 languages
× 28 files

프롬프트는 Claude에게 무엇을 번역하고 무엇을 그대로 둘지 정확히 알려줍니다:

  • 번역 대상: 본문, 제목, 표 레이블, 프론트매터 title과 description
  • 영어 유지: 코드 블록, 인라인 코드, 명령어 예시, 파일 경로, URL, 제품명, 사람 이름, ASCII 다이어그램

이 분리는 매우 중요합니다. pnpm build 같은 명령어는 모든 언어에서 pnpm build로 유지되어야 합니다. “En Parlant~“나 “Stockfish” 같은 제품명은 번역하지 않습니다. 하지만 “Getting Started”는 일본어에서 “はじめに”가 되고, 러시아어에서 “Начало работы”가 됩니다.

Claude Opus 4.6은 더 빠른 모델보다 눈에 띄게 나은 번역을 생성합니다. 차이는 다음과 같은 부분에서 나타납니다:

  • 자연스러운 표현 — Opus는 번역가가 아닌 원어민처럼 씁니다. 영어 어순이 대상 언어에서 어색하게 들릴 때 문장을 재구성합니다.
  • 기술적 정확성 — 체스 용어, TTS 전문 용어, 소프트웨어 개념이 올바른 도메인별 용어로 번역됩니다.
  • 일관성 — 격식체가 전체적으로 일관되게 유지됩니다. 일본어는 문단 중간에 반말과 존댓말을 오가지 않고 모든 곳에서 です/ます 형을 사용합니다.
  • MDX 처리 — Opus는 .mdx 파일에서 JSX 컴포넌트 태그(<Card>, <CardGrid>)와 import 문을 손상시키지 않고 올바르게 보존합니다.

비용 차이는 실재합니다 — Opus로 전체 사이트 번역 시 약 $28 대 Sonnet으로 $5 — 하지만 사용자가 실제로 읽을 448개 파일에 대해서는 품질이 그만한 가치가 있습니다.

이 부분이 제대로 만드는 데 가장 오래 걸렸습니다.

Starlight는 콘텐츠 슬러그의 첫 번째 세그먼트를 확인하여 페이지의 언어를 감지합니다. fr/docs/getting-started를 보면, fr이 첫 번째 세그먼트이므로 프랑스어라는 것을 알 수 있습니다. 하지만 초기 구현에서는 docs/fr/getting-started와 같은 슬러그가 생성되었습니다 — 로케일이 docs/ 아래에 묻힌 것입니다. Starlight는 docs를 첫 번째 세그먼트로 인식하고 모든 것을 영어로 처리하여, 약 500개 대신 7,000개 이상의 중복 페이지를 생성했습니다.

src/content.config.ts의 커스텀 generateId 함수가 파일 경로가 콘텐츠 슬러그로 변환되는 방식을 제어합니다:

generateId({ entry }) {
const slug = entry.replace(/\.[^.]+$/, "");
const firstSeg = slug.split("/")[0];
if (firstSeg && localeKeys.includes(firstSeg)) {
return `${firstSeg}/docs/${slug.slice(firstSeg.length + 1)}`;
}
return `docs/${slug}`;
}

이렇게 하면 로케일 접두사가 docs/ 앞에 위치합니다:

파일 경로슬러그URL
getting-started.mddocs/getting-started/docs/getting-started/
fr/getting-started.mdfr/docs/getting-started/fr/docs/getting-started/
ja/features/puzzles.mdja/docs/features/puzzles/ja/docs/features/puzzles/

영어는 defaultLocale: "root"를 사용하므로 접두사가 없습니다 — /en/docs/가 아닌 /docs/에 직접 위치합니다.

같은 파일의 localeKeys 배열에 영어가 아닌 모든 로케일이 나열되어야 합니다. Astro 설정에는 로케일이 존재하지만 이 배열에 없으면, 번역된 콘텐츠가 영어 콘텐츠로 처리됩니다 — 언어 전환 기능이 깨지고 페이지 수가 폭발합니다.

Astro는 .astro/data-store.json에 슬러그 매핑을 캐시합니다. 로케일 설정 변경 후에는 이 파일을 삭제한 후 다시 빌드해야 합니다. 그렇지 않으면 빌드는 성공하지만 오래된(잘못된) 라우팅이 적용됩니다.

사이드바는 astro.config.mjs에 정의되어 있습니다. 페이지를 가리키는 항목(slug 속성)은 자동으로 번역된 페이지의 프론트매터 제목을 사용합니다 — 수동 번역이 필요 없습니다:

{ slug: "docs/getting-started" }
// English: "Getting Started" (from English frontmatter)
// French: "Premiers pas" (from French frontmatter)
// Japanese: "はじめに" (from Japanese frontmatter)

하지만 그룹 레이블과 외부 링크는 명시적인 번역이 필요합니다:

{
label: "Features",
translations: {
fr: "Fonctionnalités",
es: "Características",
de: "Funktionen",
ja: "機能",
// ... all 16 languages
},
items: [
{ slug: "docs/features/play-chess" },
{ slug: "docs/features/multiplayer" },
// ...
],
}

7개의 사이드바 요소에 수동 번역이 필요합니다: Welcome, Features, App Menus, Setup Guides, Under the Hood, Credits, Accessibility. 새로운 언어를 추가하면 이 7개 블록 각각에 하나의 항목을 추가해야 합니다.

En Parlant~은 도움말 메뉴에서 이 문서로 연결됩니다. 링크는 로케일을 인식합니다 — 프랑스어로 앱을 사용 중이라면 프랑스어 문서가 열립니다:

const docsLocalePrefix = useMemo(() => {
const lang = i18n.language; // e.g. "fr_FR", "zh_TW"
if (!lang || lang.startsWith("en")) return "";
if (lang === "zh_TW") return "/zh-tw";
return `/${lang.slice(0, 2)}`;
}, [i18n.language]);

앱에서 사용 가능한 모든 언어에 사이트에 대응하는 번역이 있습니다. 매핑은 자동입니다 — fr_FR/fr/docs/로, ja_JP/ja/docs/로 매핑됩니다. 유일한 특수 케이스는 중국어 번체입니다: zh_TW/zh-tw/docs/로 매핑됩니다(하이픈 포함).

어려운 부분은 이미 완료되었습니다:

  1. 라우팅 아키텍처가 해결되었습니다. generateId 함수, localeKeys 배열, defaultLocale: "root" 설정이 함께 작동하여 Starlight가 올바른 URL 구조를 생성합니다. 이것이 가장 큰 난제였습니다 — Starlight와 Astro의 6개 이상의 소스 파일을 추적하여 찾아내고 수정해야 했습니다.

  2. 번역 스크립트가 모든 것을 처리합니다. 전체 사이트 재번역, 단일 언어 추가, 개별 파일 업데이트 — 모두 같은 스크립트에 다른 플래그를 사용합니다. 속도 제한 시 재시도하고, 워커 간 병렬 처리하며, 오류를 명확히 보고합니다.

  3. 새 언어 추가는 4번의 설정 편집과 하나의 명령어입니다. astro.config.mjs, content.config.ts, translate-docs.py에 로케일을 추가하고, 사이드바 번역을 추가한 후 스크립트를 실행합니다. 약 10분의 작업과 4분의 번역 시간입니다.

  4. 콘텐츠 업데이트는 더 간단합니다. 영어 원본을 편집하고, 변경된 파일만 --files--overwrite 플래그로 스크립트를 실행한 후 다시 빌드합니다. 또는 작은 문장 수정의 경우, 번역된 파일을 직접 편집합니다.

  5. 전체 파이프라인이 스킬로 캡처되어 있습니다. /translate_docs를 실행하면 전체 프로세스를 안내합니다 — 어떤 모드를 사용할지, 어떤 플래그를 전달할지, 사전 점검, 번역 후 검증. 기관 지식이 필요하지 않습니다.

448개 파일의 16개 언어 전체 번역 비용은 약 $28이며 약 70분이 소요되었습니다. 새로운 단일 언어 비용은 약 $1.70입니다. 모든 언어에 걸쳐 단일 파일 재번역 비용은 약 $1입니다. 이것이 17개 언어로 문서를 최신 상태로 유지하는 데 드는 지속적인 비용입니다.