본문으로 건너뛰기

클라이언트 앱 다국어(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 연동 설정