본문으로 건너뛰기

클라이언트 앱 다국어(i18n) 가이드

작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Browser Extension, Tauri Desktop App, Flutter Mobile App


1. 다국어 아키텍처 원칙​

핵심 원칙​

원칙설명
키 기반 번역UI에 직접 문자열 삽입 금지, 반드시 t('key') 형태 사용
ICU MessageFormat복수형, 성별, 선택 등 복잡한 패턴 처리의 표준
기본 언어 fallback번역 누락 시 기본 언어(en) 표시, 빈 문자열 금지
컴파일 타임 검증타입 안전한 번역 키 사용 (자동 완성 지원)
지연 로딩사용 중인 언어만 로드, 전체 언어 번들 금지

ICU MessageFormat 예시​

# 복수형
items_count = "{count, plural, =0 {항목 없음} one {# 항목} other {# 항목}}"

# 성별
greeting = "{gender, select, male {그가} female {그녀가} other {그 사람이}} 로그인했습니다"

# 중첩 (복수형 + 성별)
notification = "{gender, select,
male {{count, plural, one {그가 # 개를 추가했습니다} other {그가 # 개를 추가했습니다}}}
female {{count, plural, one {그녀가 # 개를 추가했습니다} other {그녀가 # 개를 추가했습니다}}}
other {{count, plural, one {# 개가 추가되었습니다} other {# 개가 추가되었습니다}}}
}"

2. 번역 파일 관리​

파일 형식​

플랫폼파일 형식위치
Browser ExtensionJSON (messages.json)public/_locales/{locale}/messages.json
Tauri (프론트엔드)JSON (네임스페이스별)src/shared/i18n/locales/{locale}/
FlutterARB (Application Resource Bundle)lib/l10n/app_{locale}.arb

폴더 구조​

# Browser Extension (chrome.i18n 표준)
public/_locales/
+-- en/messages.json
+-- ko/messages.json
+-- ja/messages.json

# 프론트엔드 공통 (네임스페이스 분리)
src/shared/i18n/
+-- locales/
| +-- en/
| | +-- common.json # 공통 (버튼, 라벨)
| | +-- auth.json # 인증 관련
| | +-- dashboard.json # 대시보드
| | +-- errors.json # 에러 메시지
| +-- ko/
| | +-- common.json
| | +-- auth.json
| | +-- dashboard.json
| | +-- errors.json
+-- index.ts # i18n 초기화
+-- types.ts # 타입 정의

# Flutter (ARB)
lib/l10n/
+-- app_en.arb # 기본 언어 (모든 키 포함)
+-- app_ko.arb
+-- app_ja.arb

번역 키 네이밍 컨벤션​

패턴예시설명
{기능}.{컴포넌트}.{동작}auth.login.submit기능별 분류
{기능}.{상태}.{설명}auth.error.invalidEmail상태별 분류
common.{카테고리}.{이름}common.button.save공통 요소
error.{코드}error.networkTimeout에러 메시지

번역 파일 JSON 예시​

{
"auth": {
"login": {
"title": "로그인",
"email": "이메일",
"password": "비밀번호",
"submit": "로그인",
"forgotPassword": "비밀번호 찾기",
"noAccount": "계정이 없으신가요? {signUpLink}에서 가입하세요"
},
"error": {
"invalidEmail": "올바른 이메일 형식이 아닙니다",
"wrongPassword": "비밀번호가 일치하지 않습니다",
"tooManyAttempts": "{minutes}분 후 다시 시도하세요"
}
}
}

Flutter ARB 예시​

{
"@@locale": "ko",
"loginTitle": "로그인",
"@loginTitle": {
"description": "로그인 페이지 제목"
},
"itemCount": "{count, plural, =0{항목 없음} =1{1개 항목} other{{count}개 항목}}",
"@itemCount": {
"description": "아이템 개수 표시",
"placeholders": {
"count": {
"type": "int"
}
}
}
}

3. RTL(Right-to-Left) 지원​

RTL 언어 목록​

언어코드사용 인구
아랍어ar~4억
히브리어he~900만
페르시아어fa~1.1억
우르두어ur~2.3억

RTL 구현 체크리스트​

항목구현 방법적용 대상
HTML dir 속성<html dir="rtl"> 또는 dir="auto"Browser Ext, Tauri
CSS 논리적 속성margin-inline-start 대신 margin-left 사용 금지Browser Ext, Tauri
Flexbox 방향direction: rtl 시 자동 반전Browser Ext, Tauri
아이콘 미러링방향성 있는 아이콘(화살표 등) 좌우 반전전체
텍스트 정렬text-align: start 사용 (left 금지)Browser Ext, Tauri
Flutter DirectionalityDirectionality 위젯 또는 TextDirectionFlutter
Flutter EdgeInsetsDirectionalEdgeInsets 대신 EdgeInsetsDirectionalFlutter

CSS 논리적 속성 매핑​

/* 금지: 물리적 속성 */
margin-left: 16px;
padding-right: 8px;
border-left: 1px solid;
text-align: left;

/* 권장: 논리적 속성 */
margin-inline-start: 16px;
padding-inline-end: 8px;
border-inline-start: 1px solid;
text-align: start;

4. 복수형/성별 처리​

CLDR 복수형 규칙​

카테고리영어한국어아랍어
zero--0
one1-1
two--2
few--3-10
many--11-99
other나머지전부100+

한국어는 복수형 구분이 없어 other만 사용. 하지만 다국어 지원 시 모든 CLDR 카테고리를 기본 언어(en)에서 정의해야 한다.

구현 예시​

// i18next (Browser Ext, Tauri)
// en/common.json
{
"item_count_zero": "No items",
"item_count_one": "{{count}} item",
"item_count_other": "{{count}} items"
}

// 사용
t('item_count', { count: 5 }) // "5 items"
// Flutter (intl 패키지)
// app_en.arb
{
"itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}"
}

// 사용
AppLocalizations.of(context)!.itemCount(5) // "5 items"

5. 날짜/숫자/통화 포맷​

Intl API 활용 (Browser Ext, Tauri)​

// 날짜 포맷
new Intl.DateTimeFormat('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date); // "2026년 4월 6일"

// 숫자 포맷
new Intl.NumberFormat('ko-KR').format(1234567.89); // "1,234,567.89"

// 통화 포맷
new Intl.NumberFormat('ko-KR', {
style: 'currency',
currency: 'KRW'
}).format(50000); // "₩50,000"

// 상대 시간
new Intl.RelativeTimeFormat('ko', { numeric: 'auto' })
.format(-3, 'hour'); // "3시간 전"

Flutter (intl 패키지)​

import 'package:intl/intl.dart';

// 날짜 포맷
DateFormat.yMMMMd('ko').format(DateTime.now()); // "2026년 4월 6일"

// 숫자 포맷
NumberFormat('#,###', 'ko').format(1234567); // "1,234,567"

// 통화 포맷
NumberFormat.currency(locale: 'ko_KR', symbol: '₩', decimalDigits: 0)
.format(50000); // "₩50,000"

포맷 원칙​

원칙설명
서버에서 ISO 8601 수신2026-04-06T12:00:00Z
클라이언트에서 로컬 변환Intl API 또는 intl 패키지 사용
통화 코드 서버 전달금액과 함께 ISO 4217 통화 코드 전달
하드코딩 포맷 금지YYYY-MM-DD 같은 고정 포맷 사용 금지
로케일 연동앱 언어 설정에 따라 포맷 자동 변경

6. 언어 감지 전략​

우선순위 (높은 순)​

순위소스설명
1사용자 명시 설정앱 설정에서 직접 선택한 언어
2서버 사용자 프로필백엔드에 저장된 언어 설정
3브라우저/OS 설정시스템 언어
4기본 언어en (fallback)

플랫폼별 감지 방법​

플랫폼감지 API예시
Browser Extensionchrome.i18n.getUILanguage()"ko"
Browser (일반)navigator.languages[0]"ko-KR"
Tauri (Rust)sys_locale::get_locale()"ko-KR"
Tauri (프론트엔드)navigator.languages[0]"ko-KR"
Flutter (Android)Platform.localeName"ko_KR"
Flutter (iOS)Platform.localeName"ko_KR"
Flutter (권장)PlatformDispatcher.instance.localeLocale('ko', 'KR')

구현 패턴​

// 언어 감지 순서 (TypeScript)
function detectLanguage(): string {
// 1. 사용자 명시 설정
const saved = localStorage.getItem('user_language');
if (saved) return saved;

// 2. 서버 프로필 (캐시된)
const profile = getCachedUserProfile();
if (profile?.language) return profile.language;

// 3. 브라우저/OS
const browserLang = navigator.languages?.[0] || navigator.language;
const supported = ['en', 'ko', 'ja', 'zh'];
const detected = browserLang.split('-')[0];
if (supported.includes(detected)) return detected;

// 4. 기본값
return 'en';
}

7. 플랫폼별 i18n 라이브러리 권장​

Browser Extension​

라이브러리용도비고
chrome.i18n (내장)Manifest 기반 번역Chrome 표준, 제한적 기능
i18next + react-i18next런타임 번역가장 범용적, ICU 지원
@formatjs/intlICU MessageFormatFormatJS 생태계
typesafe-i18n타입 안전 번역자동 타입 생성

권장 조합: i18next + react-i18next + i18next-icu (ICU 플러그인)

Tauri Desktop App​

라이브러리용도비고
i18next + 프레임워크 바인딩프론트엔드 번역React/Vue/Svelte 지원
rust-i18n (Rust 측)Rust 백엔드 메시지에러 메시지, 트레이 메뉴
@formatjs/intlICU MessageFormatFormatJS 생태계
lingui매크로 기반번역 키 자동 추출

권장 조합: i18next (프론트엔드) + rust-i18n (Rust)

Flutter​

라이브러리용도비고
flutter_localizations (내장)위젯 로컬라이제이션Material/Cupertino
intl + flutter gen-l10nARB 기반 번역공식 권장
easy_localization간편한 i18nJSON/YAML 지원
slang타입 안전코드 생성 기반

권장 조합: flutter_localizations + intl + flutter gen-l10n (공식 표준)


8. 번역 워크플로우​

자동 키 추출​

# i18next-parser (TS/JS 프로젝트)
npx i18next-parser 'src/**/*.{ts,tsx}' --output 'src/shared/i18n/locales/$LOCALE/$NAMESPACE.json'

# Flutter gen-l10n
flutter gen-l10n

번역 서비스 연동​

서비스특징API 지원
CrowdinGit 연동, OTA 업데이트O
Lokalise실시간 협업, 스크린샷 연동O
Phrase번역 메모리, 용어집O
Transifex오픈소스 지원O
Weblate셀프 호스팅 가능O

워크플로우 자동화 파이프라인​

1. 개발자가 코드에 t('new.key') 추가
2. CI에서 키 추출 (i18next-parser / gen-l10n)
3. 누락 키 감지 -> 번역 서비스로 자동 Push
4. 번역가가 번역 완료
5. CI에서 번역 파일 Pull -> PR 생성
6. 머지 후 앱에 반영 (또는 OTA 업데이트)

CI 검증 체크리스트​

  • 모든 키가 기본 언어(en)에 존재하는가
  • 번역 파일이 유효한 JSON/ARB 형식인가
  • ICU 메시지 구문이 올바른가
  • 플레이스홀더가 모든 언어에 동일하게 존재하는가
  • 미번역 키 목록 리포트 생성
  • 사용되지 않는 키(orphan) 감지

9. 스토어 심사 관련 i18n 요구사항​

Chrome Web Store​

요구사항구현 방법
스토어 등록 정보 번역Chrome Developer Dashboard에서 각 언어별 입력
_locales/ 필수default_locale 지정 시 _locales/ 존재 필수
name, description 번역__MSG_appName__ 형태로 manifest.json에 참조

Apple App Store​

요구사항구현 방법
앱 이름 로컬라이제이션InfoPlist.strings 파일로 각 언어별 앱 이름
스토어 메타데이터App Store Connect에서 각 언어별 설명/스크린샷
앱 내 언어와 스토어 언어 일치지원 언어 목록 동기화

Google Play Store​

요구사항구현 방법
스토어 등록 정보 번역Play Console에서 각 언어별 입력
resConfigs 설정사용하는 언어만 APK에 포함 (빌드 최적화)
기본 언어 설정defaultConfig { resConfigs "en", "ko" }

10. 구현 체크리스트​

초기 설정​

  • i18n 라이브러리 설치 및 초기화
  • 번역 파일 폴더 구조 생성
  • 기본 언어(en) + 한국어(ko) 최소 설정
  • 타입 안전 번역 키 설정 (자동 완성)
  • 언어 감지 로직 구현 (4단계 우선순위)
  • 언어 전환 UI 구현

품질 보장​

  • ICU MessageFormat 복수형 처리
  • RTL 레이아웃 테스트 (아랍어 등)
  • 긴 문자열 UI 깨짐 테스트 (독일어 등 ~30% 길어짐)
  • 날짜/숫자/통화 로컬 포맷 확인
  • 번역 누락 시 fallback 동작 확인
  • CI에서 번역 파일 검증 자동화

배포​

  • 스토어 메타데이터 번역 준비
  • 스크린샷 각 언어별 준비
  • OTA 번역 업데이트 설정 (선택)
  • 번역 서비스 CI 연동 설정