Communitygithub.com

웹 i18n (국제화 및 현지화)

웹 앱에 다국어 지원, UI 번역, 하드코딩된 문자열 추출, 복수형/성별/ICU 메시지 처리, 로케일 라우팅, RTL 레이아웃, 현지화된 날짜/숫자/통화, hreflang/SEO 및 번역 카탈로그 검증을 추가할 때 사용합니다. React, Next.js, Vue, Svelte, Astro, Remix 및 vanilla JS에서 프레임워크에 구애받지 않는 지침을 제공하며 next-intl, react-i18next, FormatJS 및 Lingui 예제를 포함합니다.

웹 i18n (국제화 및 현지화)란 무엇인가요?

이 스킬은 웹 애플리케이션의 국제화(i18n) 및 현지화(l10n)를 위한 생산 수준의 포괄적인 가이드입니다. 단순한 번역 작업이 아닌 아키텍처 수준에서 접근하여, 하드코딩된 문자열 제거, ICU MessageFormat을 사용한 복수형 및 성별 처리, Intl API를 통한 날짜/숫자/통화 형식 지정, RTL(오른쪽에서 왼쪽) 레이아웃 지원, 로케일 기반 라우팅 및 SEO(hreflang, 사이트맵) 설정 등 현지화의 모든 측면을 다룹니다. React, Next.js, Vue, Svelte, Astro, Remix 등 주요 프레임워크와 next-intl, react-i18next, FormatJS, Lingui 같은 i18n 라이브러리에 대한 구체적인 설정 지침과 함정을 제공하며, CI에서 번역 카탈로그의 구조적 유효성을 검증하는 스크립트도 포함합니다. 이 스킬은 코드 변경 전 감사(audit), 아키텍처 결정, 문자열 외부화, 로케일 라우팅 및 렌더링, 현지화 민감 값 처리, 확장 및 RTL 설계, CI 번역 QA 등 7단계 워크플로우를 통해 체계적인 국제화를 안내합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Nelsutro/agent-skills/tree/HEAD/web-i18n

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

웹 i18n

국제화는 마지막에 하는 번역 작업이 아니라 아키텍처입니다. 목표는 특별한 경우 없이 모든 로케일에 대해 올바르게 렌더링되는 UI입니다. 즉, 연결된 문장, 하드코딩된 형식, 독일어가 35% 더 길거나 아랍어가 방향을 뒤집을 때 깨지는 레이아웃이 없어야 합니다.

이 스킬을 사용해야 할 때

사용자가 다음을 요청할 때 사용합니다:

  • 언어, 언어 전환기 또는 로케일 라우팅 추가
  • 앱 번역, 문자열 추출 또는 번역 워크플로우 설정
  • 복수형, 성별 또는 "1개 항목 / 2개 항목" 버그 수정
  • 로케일별 날짜, 숫자, 통화, 상대 시간 또는 목록 형식 지정
  • RTL(아랍어, 히브리어, 페르시아어, 우르두어) 지원
  • hreflang, 현지화된 메타데이터 또는 다국어 사이트맵 추가
  • 기존 i18n 설정, 누락된 키 또는 번역 불일치 검토
  • next-intl, react-i18next, FormatJS, Lingui 또는 TMS 중에서 선택

관련 참고 자료는 references/에 있고 스크립트는 scripts/에 있습니다. 작업에 필요한 것만 읽으십시오. 경로는 이 스킬의 디렉토리를 기준으로 합니다(에이전트 런타임은 스킬이 로드될 때 이를 출력합니다).

필수 원칙

  1. 번역된 조각을 연결하여 문장을 절대 만들지 마십시오. 전체 메시지를 플레이스홀더와 함께 번역하십시오: "{count}개의 새 메시지"처럼 사용하고, t('new') + ' ' + count + ' ' + t('messages')처럼 사용하지 마십시오.
  2. 키로 영어 텍스트를 절대 사용하지 마십시오. 키는 안정적인 식별자(inbox.unreadCount)이므로, 복사본 편집이 조회를 깨뜨리거나 모든 번역을 무효화하지 않습니다.
  3. 모든 사용자 대상 문자열은 외부화되어야 합니다. 여기에는 aria-label, alt, title, placeholder, 유효성 검사 오류, 빈 상태 메시지, 토스트, 이메일, 푸시 알림 및 PDF 출력이 포함됩니다.
  4. 형식을 절대 하드코딩하지 마십시오. toFixed(2) + '$', 수동 MM/DD/YYYY, 영어 월 이름, 24h 가정은 안 됩니다. Intl을 사용하십시오.
  5. 로케일은 언어가 아니라 BCP 47 태그입니다. pt-BR과 pt-PT, zh-Hans와 zh-Hant, es-MX와 es-ES는 서로 다른 제품입니다. 전체 태그를 저장하십시오.
  6. 서버에서 로케일 상태는 요청 범위여야 합니다. SSR 동안 현재 로케일을 모듈 수준의 가변 변수에 절대 넣지 마십시오. 동시 요청이 서로의 언어를 누출시킵니다.
  7. TMS, 커뮤니티 또는 CMS에서 가져온 번역은 신뢰할 수 없는 입력입니다. 소독(sanitizing) 없이 innerHTML/dangerouslySetInnerHTML에 절대 전달하지 마십시오.

워크플로우

1. 코드 변경 전 감사

  • 아무것도 제안하기 전에 스택, 라우터 및 기존 i18n 라이브러리를 파악하십시오.
  • 하드코딩된 문자열을 찾으십시오:
    node scripts/scan-hardcoded.mjs src
    
    출력을 후보로 취급하고 절대적인 진리로 취급하지 마십시오: 테스트 픽스처, 상수, URL 및 개발자 대상 로그는 건너뛰십시오.
  • 현재 제공되는 모든 로케일과 카탈로그가 있는 위치를 목록화하십시오.
  • 가장 위험도가 높은 영역을 먼저 식별하십시오: 인증, 체크아웃, 오류, 이메일 및 법적 문구.

2. 아키텍처 결정 (기록해 두십시오)

코드를 작성하기 전에 다음 질문에 답하십시오:

결정기본값참고
로케일 전략경로 접두사 /es/최고의 SEO와 캐시 가능성. 이유가 있는 경우에만 하위 도메인/ccTLD 사용.
기본 로케일소스 언어재정의 없이 처음 방문자를 추측된 로케일로 자동 리디렉션하지 마십시오.
로케일 감지URL → 쿠키 → Accept-Language → 기본값URL이 우선 순위이므로 공유 링크가 언어를 변경하지 않습니다.
카탈로그 형식로케일별, 네임스페이스별 JSON네임스페이스를 지연 로드하고 모든 로케일을 클라이언트에 절대 제공하지 마십시오.
메시지 구문ICU MessageFormat라이브러리와 TMS 간에 이식 가능; plural/select/ordinal 지원.
키 스타일feature.component.element안정적이고 검색 가능하며 영어 텍스트 없음.
번역 출처TMS 또는 저장소, 둘 다 아님하나를 선택하고 CI에서 한 방향으로 동기화하십시오.
폴백로케일 체인 es-MX → es → en개발 중에 폴백을 기록하여 간격을 포착하십시오.

3. 메시지 외부화 및 구조화

  • 문자열을 의미 있는 키로 바꾸고 복사본을 기본 로케일 카탈로그로 이동하십시오.
  • 전체 문장당 하나의 키를 사용하십시오. 문장에 링크나 굵은 글씨가 포함된 경우 분할하는 대신 리치 텍스트 보간을 사용하십시오.
  • 플레이스홀더는 이름을 사용하고({userName}), 위치 기반({0})을 사용하지 마십시오.
  • 라이브러리가 명시적으로 유형화된 태그를 지원하지 않는 한 카탈로그에 HTML을 넣지 마십시오.
  • 페이지별이 아닌 제품 표면별로 그룹화하십시오: checkout.json, auth.json, errors.json.

복수형, select, ordinal 및 리치 텍스트 규칙은 references/icu.md를 참조하십시오.

4. 로케일 라우팅 및 렌더링 연결

  • 경로에서 로케일을 파생하고 지원 목록에 대해 유효성을 검사한 후, 알 수 없는 로케일은 자동으로 폴백하지 말고 404를 반환하십시오.
  • 현재 경로와 쿼리 문자열을 유지하는 언어 전환기를 제공하십시오.
  • 서버 렌더링 문서에서 <html lang="..."> 및 <html dir="...">를 설정하십시오.
  • 활성 로케일의 카탈로그와 기본 폴백 카탈로그만 로드하십시오.
  • 동일한 카탈로그를 서버 및 클라이언트 렌더링에서 사용할 수 있게 하십시오. 중복되거나 분기된 번역 로직을 피하십시오.

프레임워크별 설정 및 함정: references/frameworks.md. 라우팅, 감지, hreflang 및 사이트맵: references/routing-seo.md.

5. 로케일 민감 값 처리

수동 형식 지정 없이 Intl(또는 프레임워크 래퍼)을 사용하십시오:

new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)
new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone }).format(date)
new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(-3, 'day')
new Intl.ListFormat(locale, { type: 'conjunction' }).format(items)
new Intl.PluralRules(locale).select(count)
new Intl.Collator(locale).compare(a, b)
  • 타임스탬프는 UTC로 저장하고 사용자의 시간대(서버 시간대 아님)로 렌더링하십시오.
  • 통화는 로케일과 동일하지 않습니다. 프랑스 사용자가 USD로 지불할 수 있습니다.
  • 정렬/검색은 < 또는 로케일 없는 localeCompare() 대신 Intl.Collator를 사용하십시오.
  • 대소문자 변환: toLocaleUpperCase(locale)을 사용하십시오. 터키어 i는 İ가 되기 때문입니다.

6. 확장 및 RTL 설계

  • 텍스트 길이가 +30–40% 증가(독일어, 핀란드어, 러시아어)하고 고정 너비 버튼이나 한 줄 레이블이 없다고 가정하십시오.
  • 컨테이너가 커지도록 하고 줄 바꿈을 허용하며 의사 현지화로 테스트하십시오:
    node scripts/pseudo-localize.mjs locales/en.json --out locales/en-XA.json
    
  • left/right 대신 논리 CSS 속성(margin-inline-start, padding-inline, inset-inline)을 사용하십시오.
  • 방향성 아이콘(화살표, 쉐브론, 진행 표시줄)은 미러링하고 로고, 시계, 미디어 컨트롤 또는 체크 표시는 미러링하지 마십시오.
  • 사용자 생성 인라인 콘텐츠는 <bdi> 또는 dir="auto"로 감싸십시오.

전체 RTL 체크리스트: references/rtl.md.

7. CI에서 번역 QA 설정

최소한 카탈로그가 구조적으로 유효하지 않으면 빌드가 실패하도록 하십시오:

node scripts/check-locales.mjs locales --base en

누락/추가 키, 빈 값, 플레이스홀더 불일치 및 잘못된 ICU 구문을 검사합니다. 가능한 경우 라이브러리의 CLI를 통해 ICU 컴파일을 추가하십시오:

npx formatjs compile-folder --ast locales/en locales/compiled/en

복수형 카테고리, 고정 시간대의 날짜/숫자 출력 및 RTL 렌더링에 대한 테스트를 추가하십시오. references/testing.md를 참조하십시오.

주요 결정 및 함정

라이브러리 선택

필요선택
Next.js App Router, 서버 컴포넌트next-intl
React SPA, 성숙한 생태계, 많은 플러그인react-i18next
ICU 우선, 프레임워크 비종속, CLI 추출FormatJS (react-intl)
컴파일 타임 카탈로그, 작은 런타임, 타입 안전성Lingui
Vuevue-i18n
Sveltesvelte-i18n 또는 paraglide
Astro내장 i18n 라우팅 + 메시지 라이브러리
Vanilla / 최소Intl + 작은 카탈로그 로더

이미 i18n 라이브러리가 있는 앱에 두 번째 i18n 라이브러리를 도입하지 마십시오. 기존 설정을 확장하십시오.

복수형

복수형 카테고리는 언어마다 다릅니다. 영어에는 one/other가 있고, 아랍어에는 zero/one/two/few/many/other가 있으며, 일본어에는 other만 있습니다. count === 1 ? ... : ...를 절대 작성하지 마십시오.

"inbox.unread": "{count, plural, =0 {읽지 않은 메시지 없음} one {#개의 읽지 않은 메시지} other {#개의 읽지 않은 메시지}}"

성별 및 문법적 일치

성별 변수에 select를 사용하고 변수 값은 언어 중립적(female, male, neutral, unknown)으로 유지하고 현지화된 단어를 사용하지 마십시오:

"profile.greeting": "{gender, select, female {환영합니다, {name}} male {환영합니다, {name}} other {환영합니다, {name}}}"

언어가 형용사, 명사 또는 관사에 대한 일치가 필요한 경우 문자열을 해킹하는 대신 문법적 성별/격을 인수로 노출하십시오.

리치 텍스트

"Read our " + link + " terms"를 세 개의 키로 분할하지 마십시오. 태그 보간을 사용하십시오:

"signup.terms": "<link>이용약관</link>에 동의합니다"
t.rich('signup.terms', { link: (chunks) => <a href="/terms">{chunks}</a> })

SEO

  • 로케일당 하나의 인덱싱 가능한 URL, 동일한 URL 뒤에 기계 번역된 콘텐츠를 제공하지 마십시오.
  • 자기 참조 hreflang과 x-default, 절대 URL, 모든 로케일을 포함하십시오.
  • <title>, 메타 설명, OG 태그 및 구조화된 데이터를 현지화하십시오.
  • 현지화된 페이지에서 hreflang과 noindex를 결합하지 마십시오.

자세한 내용은 references/routing-seo.md를 참조하십시오.

캐싱

  • 경로의 로케일은 CDN 캐싱을 간단하게 만듭니다. 헤더로 협상하는 경우 Vary: Accept-Language를 보내고 캐시 조각화를 예상하십시오.
  • 개인화된 언어 기본 설정을 공유 CDN 응답에 캐시하지 마십시오.
  • Content-Language를 렌더링된 로케일과 일관되게 유지하십시오.

검토 체크리스트

i18n이 완료되었다고 선언하기 전에:

  • 컴포넌트, 이메일 또는 오류에 하드코딩된 사용자 대상 문자열이 없음
  • 문장 연결 없음, 모든 메시지는 완전한 번역 가능 단위
  • 키는 의미 있고 안정적이며 키로 영어 텍스트 없음
  • 모든 플레이스홀더가 모든 로케일에 존재하고 ICU 구문이 컴파일됨
  • 복수형은 Intl.PluralRules/ICU를 사용하고 count === 1을 사용하지 않음
  • 성별/일치는 select를 통해 처리되고 "/a" 접미사를 사용하지 않음
  • 날짜, 숫자, 통화, 상대 시간은 명시적인 로케일/시간대와 함께 Intl 사용
  • <html lang> 및 dir이 첫 페인트(SSR)에서 정확하고 효과 내에서 설정되지 않음
  • 로케일 전환기는 경로/쿼리를 유지하고 딥 링크와 함께 작동
  • 알 수 없는 로케일은 자동 폴백 대신 404 반환
  • 레이아웃이 +35% 확장 및 RTL에서 정상 작동
  • hreflang, 표준, 사이트맵 및 현지화된 메타데이터가 정확함
  • 카탈로그 유효성 검사가 CI에서 실행됨
  • 폴백 동작이 의도적이고 관찰 가능함

안티 패턴

  • t('hello') + ', ' + name — 분할된 문장
  • key={englishText} — 키로 번역된 텍스트
  • count === 1 ? 'item' : 'items' — 영어 전용 복수형 로직
  • 로케일 또는 시간대 없는 new Date().toLocaleDateString()
  • 금액에 value.toFixed(2) 사용
  • 문서 수준의 dir 대신 컴포넌트 전체에 흩어진 locale === 'ar' && { direction: 'rtl' }
  • useEffect 내부에서 <html lang> 설정 — 깜빡임을 유발하고 크롤러를 방해함
  • 모든 로케일을 초기 번들에 로드
  • 재정의할 방법 없이 IP로 자동 리디렉션
  • 고유 명사, 제품 이름, 코드 식별자 또는 URL 번역
  • 문자열을 기계 번역하고 검토 없이 제공
  • 하나의 카탈로그 파일이 수백 개의 키 이상으로 확장될 것이라고 가정

관련 스킬