better-i18n Doctor: 자동화된 번역 품질 모니터링
코드베이스에서 누락된 번역, 오판 키, 플레이스홀더 불일치를 스캔합니다. 커밋마다 0~100의 헬스 스코어를 받으십시오.
better-i18n Doctor: 자동화된 번역 품질 모니터링
애플리케이션이 12개 언어로 출시됩니다. 그런데 모든 화면, 모든 오류 메시지, 모든 툴팁이 번역되었는지 어떻게 알 수 있습니까? 영어의 {count} 플레이스홀더가 프랑스어에서 실수로 {nombre}로 작성되지 않았는지 어떻게 알 수 있습니까? 세 번의 스프린트 전에 코드베이스에서 삭제한 키가 아직도 번역 파일에 남아 혼란을 일으키고 있지 않은지 어떻게 알 수 있습니까?
대부분의 팀은 사용자로부터 번역 문제를 알게 됩니다. 도쿄의 고객이 인사말 대신 dashboard.welcome_message라는 원시 키를 보게 됩니다. 독일 사용자가 실제 금액 대신 {amount}가 표시된다고 보고합니다. QA 엔지니어가 JSON 파일을 수동으로 비교하다가 스페인어 번역에 지난달 추가된 47개의 키가 누락되어 있음을 발견합니다.
better-i18n Doctor는 코드베이스와 번역 파일을 스캔하고 모든 카테고리의 번역 문제를 식별하며 0~100의 헬스 스코어를 생성하는 자동화된 i18n 헬스 체크 도구입니다. CLI를 통해 로컬에서 실행하고 CI/CD 파이프라인에 통합하며 시간 경과에 따른 추적을 위해 better-i18n 플랫폼에 결과를 보고합니다.
헬스 스코어 작동 방식
Doctor는 단일 숫자인 0~100의 헬스 스코어를 생성합니다. 이 스코어는 i18n 품질의 다른 차원을 평가하는 네 가지 카테고리의 가중 집계입니다.
네 가지 카테고리
Coverage는 코드에서 사용되는 모든 키가 모든 대상 언어 파일에 존재하는지 측정합니다. 영어에는 있지만 일본어에는 없는 키는 Coverage 격차입니다. Coverage는 번역 문제의 가장 일반적인 원인입니다. 새 기능이 번역 의뢰되지 않은 키와 함께 출시되거나 개발자가 하나의 namespace에 키를 추가하고 다른 namespace에 추가하는 것을 잊어버리는 경우가 있습니다.
Quality는 구조적 정확성에 대해 번역 내용을 검사합니다. 플레이스홀더 불일치가 주요 관심사입니다. 영어 문자열에 {count}와 {name}이 있으면 독일어 번역에도 정확히 같은 플레이스홀더가 있어야 합니다. Quality는 빈 번역(키는 존재하지만 값이 비어 있는 것), UI 레이아웃을 망칠 수 있는 지나치게 긴 번역, 소스 언어와 동일한 문자열(영어에서 복사된 미번역 콘텐츠를 나타낼 수 있음)도 검사합니다.
Structure는 번역 파일의 구성을 평가합니다. 오판 키, 즉 번역 파일에는 있지만 소스 코드에서 참조되지 않는 키를 검사합니다. 오판 키는 무해하지만 유지 관리 부담을 만들어 냅니다. 번역가는 어떤 사용자도 보지 못할 문자열을 업데이트하는 데 시간을 쓰고 개발자는 사용되지 않는 기능에 대한 번역을 검토하는 데 시간을 낭비합니다. Structure는 일관된 키 명명, 중복 키, namespace 구성도 검사합니다.
Code는 AST 수준 분석을 사용하여 국제화해야 할 하드코딩된 문자열이 있는지 소스 코드를 스캔합니다. JSX 컴포넌트의 사용자 대상 문자열, UI 함수에 전달된 템플릿 리터럴, 오류 메시지나 알림에 사용된 문자열 상수를 감지합니다. 이 카테고리는 i18n 기술 부채의 가장 일반적인 원인을 포착합니다. 개발자가 나중에 수정할 생각으로 빠르게 <p>{t('common.loading')}</p> 대신 <p>Loading...</p>을 작성하는 경우입니다. Doctor는 이러한 문자열을 출시 전에 발견합니다.
스코어 계산
각 카테고리는 통과한 검사 수와 전체 검사 수의 비율에 따라 0~100의 서브 스코어를 생성합니다. 전체 헬스 스코어는 가중 평균입니다:
| 카테고리 | 가중치 | 측정 내용 |
|---|---|---|
| Coverage | 40% | 언어 간 누락된 번역 키 |
| Quality | 30% | 플레이스홀더 불일치, 빈 값, 의심스러운 콘텐츠 |
| Structure | 20% | 오판 키, 명명 일관성, 중복 |
| Code | 10% | 소스 코드의 하드코딩된 문자열 |
Coverage가 가장 높은 가중치를 갖는 것은 누락된 번역이 가장 직접적인 사용자 영향을 미치기 때문입니다. 원시 키나 폴백 언어가 사용자에게 표시됩니다. Code 분석의 가중치가 가장 낮은 것은 하드코딩된 문자열이 사용자 경험을 즉시 망가뜨리지 않는 기술 부채이기 때문입니다(시간이 지나면서 해결해야 하지만).
합격/불합격 임계값
전체 스코어가 80 이상이고 오류가 없을 때(경고가 아닌 오류) 스캔은 합격으로 표시됩니다. 오류는 사용자에게 직접 영향을 미치는 문제입니다. 전체 기능의 번역 누락, 런타임 오류를 일으킬 플레이스홀더 불일치, 존재하지 않는 namespace를 참조하는 키 등입니다. 경고는 사용자 경험을 망가뜨리지 않지만 수정해야 할 문제입니다. 오판 키, 일관성 없는 명명, 하드코딩된 문자열 등입니다.
팀의 기준에 맞게 합격 임계값을 설정할 수 있습니다:
bi18n doctor --threshold 90
Doctor 로컬 실행
기본 스캔
프로젝트 루트에서 전체 헬스 체크를 실행합니다:
bi18n doctor
Doctor는 better-i18n.yml 설정을 기반으로 또는 일반적인 디렉토리 구조(locales/, messages/, i18n/, lib/l10n/)를 감지하여 자동으로 번역 파일을 발견합니다. 소스 파일을 스캔하여 키 사용을 확인하고 모든 것을 교차 참조하여 헬스 리포트를 생성합니다.
출력은 전체 스코어, 카테고리 분류, 개별 규칙 결과를 보여주는 구조화된 리포트입니다:
i18n Doctor Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Overall Score: 87/100 ✓ PASSED
Coverage 92/100 ██████████████████░░ 3 missing keys
Quality 85/100 █████████████████░░░ 2 placeholder mismatches
Structure 78/100 ███████████████░░░░░ 12 orphan keys
Code 90/100 ██████████████████░░ 4 hardcoded strings
Errors: 0 Warnings: 21
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
타깃 스캔
즉각적인 요구에 관련이 없거나 너무 느린 카테고리를 건너뜁니다:
# 코드 분석 건너뛰기 (더 빠른 스캔)
bi18n doctor --skip-code
# 헬스/품질 검사 건너뛰기 (Coverage와 Structure만)
bi18n doctor --skip-health
# 동기화 상태 검사 건너뛰기
bi18n doctor --skip-sync
# 상세 출력 — 실패만이 아닌 모든 규칙 결과 표시
bi18n doctor --verbose
개별 체크 명령어
Doctor는 독립적으로 실행할 수 있는 여러 검사를 번들로 제공합니다:
# 모든 언어에서 누락된 번역 키 확인
bi18n check:missing
# 소스 코드에서 참조되지 않는 오판 키 확인
bi18n check:unused
# 모든 검사 실행 (코드 분석 없는 doctor와 동일)
bi18n check
# 하드코딩된 문자열 소스 코드 스캔
bi18n scan
# 동기화 상태 — 로컬 파일과 플랫폼 상태 비교
bi18n sync --dry-run
각 명령어는 특정 관심사에 집중된 출력을 생성합니다. 특정 카테고리의 문제를 수정할 때 유용합니다.
CI/CD 통합
GitHub Actions
Doctor는 모든 풀 리퀘스트에서 CI 검사로 실행되도록 설계되었습니다. --ci 플래그는 GitHub Actions가 이해하는 형식으로 결과를 출력하고 문제가 있는 파일에 인라인 주석을 생성합니다:
# .github/workflows/i18n-doctor.yml
name: i18n Health Check
on:
pull_request:
paths:
- "locales/**"
- "src/**"
- "messages/**"
jobs:
doctor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Run i18n Doctor
run: bunx @better-i18n/cli doctor --ci --threshold 80
env:
BETTER_I18N_API_KEY: ${{ secrets.BETTER_I18N_API_KEY }}
--ci 플래그가 설정되면 Doctor는:
- 스캔이 실패하면(스코어가 임계값 미만이거나 오류가 있을 경우) 코드 1로 종료하여 GitHub Actions 검사를 실패시킵니다
- GitHub Actions 형식으로 주석을 출력하여 문제가 PR diff의 인라인 댓글로 표시되게 합니다
- GitHub Actions 검사 출력에 표시되는 요약을 생성합니다
GitHub Actions 워크플로우 자동 생성
워크플로우 파일을 수동으로 작성하지 않으려면 better-i18n이 대신 생성할 수 있습니다. 플랫폼 대시보드에서 Integrations, GitHub Actions로 이동한 다음 Create Doctor Workflow를 클릭합니다. 이렇게 하면 프로젝트 설정에 맞게 사전 구성된 워크플로우 파일이 포함된 풀 리퀘스트가 리포지토리에 생성됩니다.
자동 생성된 워크플로우에는 다음이 포함됩니다:
- 번역 파일 위치와 일치하는 경로 필터
- 설정된 임계값
- API 키 설정 지침
- 실패 시 선택적 Slack 알림
플랫폼에 보고
Doctor가 --report 플래그와 API 키로 실행되면 전체 리포트를 better-i18n 플랫폼에 제출합니다:
bi18n doctor --report --api-key $BETTER_I18N_API_KEY
리포트에는 다음이 포함됩니다:
- 스코어와 합격/불합격 상태
- 카테고리별 오류 및 경고 수
- 영향받는 키와 파일이 포함된 개별 규칙 결과
- 메타데이터: 커밋 SHA, 브랜치 이름, 스캔된 파일 수, 확인된 키 수, 타임스탬프
플랫폼에 제출된 리포트는 저장되고 프로젝트 대시보드에 표시됩니다. 시간에 따른 스코어 추세를 확인하고 브랜치 간 리포트를 비교하며 어떤 카테고리가 개선되거나 저하되는지 추적할 수 있습니다.
CI 리포트 제출
CI 환경에서는 --ci와 --report를 결합하여 PR을 검증하면서 리포트를 제출합니다:
- name: Run i18n Doctor
run: bunx @better-i18n/cli doctor --ci --report --threshold 85
env:
BETTER_I18N_API_KEY: ${{ secrets.BETTER_I18N_API_KEY }}
이렇게 하면 두 가지 피드백 루프가 생깁니다:
- 즉각적: PR 검사가 통과 또는 실패하고 개발자는 인라인 주석을 확인합니다
- 이력: 리포트가 추세 분석과 팀 가시성을 위해 플랫폼에 저장됩니다
규칙 세부 정보
Doctor는 각 카테고리 내의 규칙 세트를 평가합니다. 가장 영향력 있는 규칙과 감지 내용을 설명합니다.
Coverage 규칙
missing-keys: 소스 코드에서 사용되는 모든 키에 대해 모든 대상 언어 파일에 번역이 있는지 확인합니다. 누락된 키는 가장 일반적인 i18n 문제이자 가장 사용자에게 잘 보입니다. 원시 키 이름이나 폴백 언어가 표시됩니다.
namespace-coverage: 코드에서 참조된 모든 namespace가 모든 대상 언어에 대응하는 번역 파일을 갖고 있는지 확인합니다. 개발자가 t('checkout.confirm_order')를 추가해도 checkout namespace 파일이 한국어에 존재하지 않을 수 있습니다.
Quality 규칙
placeholder-mismatch: 소스와 대상 번역 간의 플레이스홀더를 비교합니다. en: "Hello {name}, you have {count} items"가 있으면 다른 모든 언어 번역에 {name}과 {count}가 포함되어 있는지 확인합니다. 여분이나 누락된 플레이스홀더는 런타임 오류 또는 원시 플레이스홀더 구문 표시를 유발합니다.
empty-translation: 대상 언어에 존재하지만 빈 문자열 값을 가진 키에 플래그를 표시합니다. 빈 번역은 실제 번역 콘텐츠 없이 프로그래밍 방식으로 키를 추가한 결과인 경우가 많습니다.
source-identical: 소스 언어와 문자 단위로 동일한 번역에 플래그를 표시합니다. 일부 문자열(브랜드 이름, URL, 기술 용어)은 언어 간에 합법적으로 동일하지만 소스 동일 문자열이 많으면 보통 미번역 콘텐츠를 나타냅니다.
Structure 규칙
orphan-keys: 소스 코드의 어디에도 참조되지 않는 번역 파일의 키를 식별합니다. 오판 키는 기능이 제거되었지만 번역 파일이 정리되지 않을 때 축적됩니다. 번역가의 노력을 낭비하고 무엇이 활성 사용 중인지에 대한 혼란을 만듭니다.
duplicate-keys: 단일 파일 또는 namespace 내에서 같은 키가 여러 번 정의된 것을 감지합니다. 중복은 예측 불가능한 동작을 일으킵니다. 번역 엔진은 그 중 하나를 사용하지만 어느 것을 사용하는지는 구현 세부 사항에 따라 다릅니다.
naming-consistency: 키 이름이 일관된 패턴을 따르는지 확인합니다. 대부분의 키가 snake_case를 사용하면 camelCase를 사용하는 키에 플래그가 표시됩니다. 일관성 없는 명명은 키를 찾고 유지 관리하기 어렵게 만듭니다.
Code 규칙
hardcoded-jsx: AST 파싱을 사용하여 JSX 요소 내의 문자열 리터럴을 감지합니다. <h1>Welcome</h1>은 플래그가 표시됩니다; <h1>{t('welcome')}</h1>은 표시되지 않습니다. 이 규칙은 JSX를 이해하고 CSS 클래스 이름과 데이터 속성 같은 사용자 대상이 아닌 문자열을 무시합니다.
hardcoded-template: 토스트 알림, 경고 대화상자, 오류 메시지 등 일반적으로 사용자 대상 출력을 생성하는 함수에 전달된 문자열 리터럴을 감지합니다. showToast("Operation successful")은 플래그가 표시됩니다.
hardcoded-constant: errorMessage, label, title, placeholder 같은 사용자 대상 이름을 가진 변수에 할당된 문자열 상수 중 번역 함수로 감싸지지 않은 것을 식별합니다.
플랫폼 대시보드
--report를 통해 제출된 리포트는 better-i18n 플랫폼 대시보드에서 시각화됩니다.
스코어 추세
시계열 차트가 시간에 따른 헬스 스코어를 보여줍니다. 각 점은 날짜별로 표시된 Doctor 리포트를 나타냅니다. 브랜치로 필터링하여 main과 기능 브랜치의 헬스 궤적을 확인할 수 있습니다. 추세선으로 i18n 품질이 개선, 안정 또는 저하되고 있는지 쉽게 확인할 수 있습니다.
카테고리 분류
각 카테고리를 드릴다운하여 어떤 규칙이 통과하고 어떤 것이 실패하는지 확인합니다. 실패하는 각 규칙에 대해 관련된 특정 키와 파일을 확인할 수 있습니다. 키를 클릭하면 번역 에디터에서 열리고 파일을 클릭하면 리포지토리의 맥락에서 확인할 수 있습니다.
크로스 프로젝트 비교
여러 프로젝트를 가진 조직의 경우 대시보드가 모든 프로젝트의 헬스 스코어를 보여줍니다. 어떤 프로젝트가 i18n 관심이 필요한지 파악하고 조직 전체의 품질 기준을 설정하는 데 유용합니다.
알림
헬스 스코어가 임계값 아래로 떨어질 때 알림을 받도록 설정합니다:
- 이메일: 헬스 스코어 변화의 주간 다이제스트
- Slack: 리포트 실패 시 즉각적인 알림
- Webhook: 모니터링 스택을 위한 커스텀 통합
실용적인 예시
예시 1: 릴리스 전 누락된 번역 감지
팀이 30개의 새 번역 키가 있는 새로운 체크아웃 플로우를 추가했습니다. 개발자가 모든 키를 영어 파일에 추가했습니다. 프랑스어와 독일어 번역은 요청되었지만 아직 완료되지 않았습니다. Doctor 없이는 프랑스어와 독일어 사용자에게 원시 키가 표시된 채로 출시됩니다.
CI에서 Doctor를 사용하면:
Coverage 60/100 ████████████░░░░░░░░ 30 missing keys (fr, de)
Error: 30 keys missing in target languages
checkout.confirm_order — missing in: fr, de
checkout.payment_method — missing in: fr, de
checkout.shipping_address — missing in: fr, de
... (27 more)
Result: FAILED (score 60, threshold 80)
PR이 차단됩니다. 개발자가 인라인 주석을 확인하고 번역을 요청하며 번역이 완료된 후에만 PR이 머지됩니다.
예시 2: 플레이스홀더 불일치 감지
번역가가 알림 문자열의 독일어 번역을 업데이트합니다. 영어 소스에는 You have {count} new messages from {sender}가 있습니다. 독일어 번역이 실수로 {count} 대신 {anzahl}을 사용합니다.
Doctor가 이를 감지합니다:
Quality 75/100 ███████████████░░░░░ 1 placeholder mismatch
Error: Placeholder mismatch in notifications.new_messages (de)
Source placeholders: {count}, {sender}
Target placeholders: {anzahl}, {sender}
Missing: {count}
Extra: {anzahl}
예시 3: 기능 제거 후 정리
팀이 6개월 전에 레거시 대시보드를 제거했습니다. 코드는 사라졌지만 번역 파일에는 legacy_dashboard namespace 아래에 85개의 키가 여전히 남아 있습니다. 번역가가 대량 번역 작업을 할 때 아무도 보지 않는 콘텐츠를 업데이트하는 데 시간을 낭비하는 일이 가끔 있습니다.
Doctor가 오판 키를 찾습니다:
Structure 65/100 █████████████░░░░░░░ 85 orphan keys
Warning: 85 keys in namespace "legacy_dashboard" are not referenced in source code
legacy_dashboard.welcome — not referenced
legacy_dashboard.stats_header — not referenced
legacy_dashboard.chart_title — not referenced
... (82 more)
예시 4: 하드코딩된 문자열 찾기
새 개발자가 팀에 합류하여 번역 시스템을 사용하지 않고 기능을 구현합니다. 모든 문자열을 JSX에 직접 하드코딩합니다:
// Doctor 적용 전
<div>
<h2>Account Settings</h2>
<p>Manage your account preferences below.</p>
<button>Save Changes</button>
</div>
Doctor가 모든 하드코딩된 문자열에 플래그를 표시합니다:
Code 40/100 ████████░░░░░░░░░░░░ 3 hardcoded strings
Warning: Hardcoded string in JSX element (src/pages/Settings.tsx:15)
<h2>Account Settings</h2>
Suggestion: <h2>{t('settings.account_title')}</h2>
Warning: Hardcoded string in JSX element (src/pages/Settings.tsx:16)
<p>Manage your account preferences below.</p>
Warning: Hardcoded string in JSX element (src/pages/Settings.tsx:17)
<button>Save Changes</button>
대안과의 비교
수동 JSON 비교: 번역 파일을 수동으로 비교하는 팀은 Coverage 문제를 감지할 수 있지만 그 외의 모든 것을 놓칩니다. 플레이스홀더 불일치, 오판 키, 하드코딩된 문자열 등입니다. 수동 검사는 오류가 발생하기 쉽고 몇 가지 언어를 넘어서는 확장이 되지 않습니다.
ESLint i18n 플러그인: eslint-plugin-i18next 같은 린트 규칙은 JSX의 하드코딩된 문자열을 감지하지만 번역 파일 품질, 언어 간 Coverage, 구조적 문제를 확인하지 않습니다. Doctor는 코드 분석을 네 가지 카테고리 중 하나로 포함하고 i18n 문제의 전체 스펙트럼을 커버합니다.
Phrase QPS(품질 성능 스코어): Phrase는 번역 품질 스코어를 제공하지만 기술적 품질(누락된 키, 플레이스홀더 불일치, 오판 키)보다 언어적 품질(문법, 용어)에 초점을 맞춥니다. Doctor는 기술적 차원에 초점을 맞춥니다. 런타임 오류와 망가진 UI를 유발하는 문제들입니다.
자동화된 검사 없음: 많은 팀이 자동화된 i18n 검사가 전혀 없습니다. 문제는 사용자나 QA 엔지니어에 의해 발견됩니다. Doctor는 어떤 환경에도 도달하기 전에 문제를 감지하는 포괄적인 자동화 Coverage를 제공합니다.
자주 묻는 질문
Doctor 스캔은 얼마나 걸립니까?
10,000개의 키, 8개 언어, 200개의 소스 파일을 가진 프로젝트의 일반적인 스캔은 10초 미만으로 완료됩니다. 코드 분석(AST 파싱)이 가장 느린 카테고리입니다. Coverage와 품질 검사만 필요한 경우 더 빠른 스캔을 위해 --skip-code를 사용하십시오.
better-i18n 플랫폼에 연결하지 않고 Doctor를 실행할 수 있습니까?
예. Doctor는 기본적으로 완전히 로컬에서 실행됩니다. --report 플래그는 선택 사항이며 추세 추적을 위해 플랫폼에 결과를 제출하려는 경우에만 필요합니다.
코드 분석은 어떤 프레임워크를 지원합니까? 코드 분석은 현재 React(JSX/TSX), Vue(SFC 템플릿), Svelte 컴포넌트를 지원합니다. Angular 지원은 계획 중입니다. 프레임워크 감지는 프로젝트 의존성을 기반으로 자동으로 이루어집니다.
커스텀 규칙을 추가할 수 있습니까? 커스텀 규칙은 로드맵에 있습니다. 현재 규칙 심각도(오류 대 경고)를 설정하고 프로젝트에 관련 없는 특정 규칙을 비활성화할 수 있습니다.
Doctor는 모노리포에서 작동합니까? 예. Doctor는 워크스페이스 인식 스캔을 지원합니다. 워크스페이스 경계를 감지하고 각 패키지를 독립적으로 스캔하여 패키지별 리포트와 집계된 전체 스코어를 생성합니다.
GitHub Actions 워크플로우 생성은 어떻게 작동합니까?
better-i18n 대시보드에서 Create Doctor Workflow 액션은 GitHub API를 사용하여 사전 구성된 .github/workflows/i18n-doctor.yml 파일이 포함된 풀 리퀘스트를 리포지토리에 생성합니다. PR을 검토하고 머지하면 워크플로우가 활성화됩니다.
i18n 헬스 모니터링 시작하기
번역 문제는 사용자가 아닌 CI에서 감지되어야 합니다. better-i18n Doctor는 팀에게 i18n 품질의 모든 차원을 스코어링하고 잘못된 번역이 출시되는 것을 차단하는 지속적이고 자동화된 헬스 체크를 제공합니다.
무료 체험 시작하고 5분 내에 첫 번째 Doctor 스캔을 실행하십시오.