이 글은 누구를 위한 것인가
- 앱을 글로벌 시장에 출시하려는 팀
- iOS String Catalog와 Android strings.xml 복수형 처리를 이해하려는 개발자
- 번역 워크플로우 자동화로 팀 부담을 줄이고 싶은 팀
들어가며
현지화는 언어 번역 그 이상이다. 날짜 형식("2026년 4월 23일" vs "April 23, 2026"), 통화 기호, RTL(오른쪽→왼쪽) 레이아웃, 복수형("1 item" vs "2 items")까지 모두 고려해야 한다.
이 글은 bluefoxdev.kr의 모바일 다국어 지원 가이드 를 참고하여 작성했습니다.
1. 현지화 아키텍처와 함정
[현지화 구성 요소]
문자열 (String):
iOS: Localizable.xcstrings (String Catalog, Xcode 15+)
Android: res/values-ko/strings.xml, values-en/strings.xml
복수형:
iOS: StringsDict 또는 String Catalog plural
Android: <plurals> 태그 (zero/one/two/few/many/other)
언어별로 규칙이 다름 (한국어는 단수/복수 구분 없음)
날짜/시간:
절대 하드코딩 금지
iOS: DateFormatter, FormatStyle
Android: DateTimeFormatter, DateFormat
통화/숫자:
iOS: NumberFormatter, .currency 스타일
Android: NumberFormat, Currency
[RTL 지원 (아랍어, 히브리어)]
iOS: semanticContentAttribute = .forceRightToLeft
Android: android:supportsRtl="true" + layoutDirection
레이아웃: Leading/Trailing (Left/Right 대신)
이미지: 화살표, 방향 아이콘 미러링 필요
[흔한 함정]
텍스트 길이: 독일어는 영어보다 30% 길다 → 레이아웃 유연하게
문화적 색상: 빨강 = 위험(서양) vs 행운(중국)
날짜 순서: MM/DD/YY (미국) vs DD/MM/YY (유럽)
전화번호 형식: 국가별 완전히 다름
2. iOS String Catalog와 Android 현지화 구현
// iOS - String Catalog (Xcode 15+)
// Localizable.xcstrings 파일이 자동으로 관리됨
// SwiftUI에서 사용
struct ProductView: View {
let product: Product
let itemCount: Int
var body: some View {
VStack(alignment: .leading) {
// 기본 문자열 (자동으로 String Catalog에서 찾음)
Text("product.detail.title")
// String Interpolation
Text("product.price \(product.price, format: .currency(code: Locale.current.currency?.identifier ?? "KRW"))")
// 복수형 처리
Text("cart.item.count \(itemCount)")
// String Catalog에서: one = "%lld item", other = "%lld items"
}
}
}
// 날짜 포맷 현지화
struct OrderView: View {
let orderDate: Date
var body: some View {
VStack {
// 현지화된 날짜 표시
Text(orderDate, format: .dateTime.year().month().day())
// 한국: 2026년 4월 23일, 미국: April 23, 2026
Text(orderDate, format: .dateTime.hour().minute())
// 24시간/12시간 자동 처리
// 상대적 시간
Text(orderDate, format: .relative(presentation: .named))
// "2시간 전", "2 hours ago"
}
}
}
// 현지화 유틸리티
enum LocalizationManager {
// 통화 포맷
static func formatCurrency(_ amount: Decimal, currencyCode: String = "KRW") -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyCode
formatter.locale = Locale.current
return formatter.string(from: amount as NSDecimalNumber) ?? "\(amount)"
}
// 숫자 포맷 (천단위 구분자)
static func formatNumber(_ number: Int) -> String {
number.formatted(.number)
// 한국: 1,234,567 / 독일: 1.234.567
}
// 동적 언어 전환 (iOS 13+)
static func changeLanguage(to languageCode: String) {
UserDefaults.standard.set([languageCode], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
// 앱 재시작 필요 또는 Bundle 교체 방식 사용
}
// RTL 여부 확인
static var isRTL: Bool {
UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft
}
}
// RTL 대응 레이아웃
struct MessageBubble: View {
let message: String
let isFromMe: Bool
var body: some View {
HStack {
if isFromMe { Spacer() }
Text(message)
.padding(10)
.background(isFromMe ? Color.blue : Color.gray.opacity(0.2))
.foregroundColor(isFromMe ? .white : .primary)
.cornerRadius(12)
// Left/Right 대신 Leading/Trailing 사용 → RTL 자동 반전
.padding(isFromMe ? .leading : .trailing, 50)
if !isFromMe { Spacer() }
}
}
}
// 번역 키 자동 추출 스크립트 (Fastlane)
// lane :extract_strings do
// sh "xcodebuild -exportLocalizations -localizationPath ./Localizations -project App.xcodeproj"
// end
// Android - 다국어 현지화
// res/values/strings.xml (기본, 영어)
// res/values-ko/strings.xml (한국어)
// res/values-ar/strings.xml (아랍어, RTL)
// res/values-de/strings.xml (독일어)
// strings.xml 예시
/*
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="product_price">Price: %1$s</string>
<string name="cart_title">Shopping Cart</string>
<!-- 복수형 -->
<plurals name="cart_item_count">
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
<!-- 한국어 (values-ko/strings.xml) -->
<!-- 복수형 구분 없음 (other만 사용) -->
<!-- <plurals name="cart_item_count">
<item quantity="other">%d개</item>
</plurals> -->
</resources>
*/
// Compose에서 현지화 사용
@Composable
fun ProductScreen(product: Product, cartCount: Int) {
Column {
// 기본 문자열
Text(stringResource(R.string.cart_title))
// 파라미터가 있는 문자열
val price = NumberFormat.getCurrencyInstance(Locale.getDefault())
.format(product.price)
Text(stringResource(R.string.product_price, price))
// 복수형
Text(pluralStringResource(R.plurals.cart_item_count, cartCount, cartCount))
}
}
// 날짜/시간 현지화
fun formatDate(date: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
.withLocale(Locale.getDefault())
return date.format(formatter)
// 한국: 2026년 4월 23일, 영어: April 23, 2026
}
fun formatCurrency(amount: Double, currencyCode: String = "KRW"): String {
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
formatter.currency = Currency.getInstance(currencyCode)
return formatter.format(amount)
}
// RTL 지원 (AndroidManifest.xml)
// <application android:supportsRtl="true">
// Compose RTL 자동 지원
// start/end 패딩 사용 → RTL에서 자동 반전
@Composable
fun MessageBubble(message: String, isFromMe: Boolean) {
Row {
if (isFromMe) Spacer(Modifier.weight(1f))
Box(
Modifier
.background(if (isFromMe) Color.Blue else Color.LightGray, RoundedCornerShape(12.dp))
.padding(start = if (!isFromMe) 0.dp else 50.dp, end = if (isFromMe) 0.dp else 50.dp)
) {
Text(message, modifier = Modifier.padding(10.dp))
}
if (!isFromMe) Spacer(Modifier.weight(1f))
}
}
// 번역 자동화 - Google ML Kit Translation
val translator = Translation.getClient(
TranslatorOptions.Builder()
.setSourceLanguage(TranslateLanguage.ENGLISH)
.setTargetLanguage(TranslateLanguage.KOREAN)
.build()
)
// 번역 모델 다운로드 후 사용 (오프라인 가능)
fun translateText(text: String, onResult: (String) -> Unit) {
translator.downloadModelIfNeeded()
.addOnSuccessListener {
translator.translate(text)
.addOnSuccessListener { translatedText -> onResult(translatedText) }
}
}
#!/bin/bash
# 번역 워크플로우 자동화
# 1. iOS: 번역할 문자열 추출
xcodebuild -exportLocalizations \
-localizationPath ./Localizations \
-project App.xcodeproj \
-exportLanguage en
# 2. XLIFF 파일 번역 API로 전송 (DeepL/Google)
# Python 스크립트로 XLIFF 파싱 후 번역
# 3. 번역 완료된 XLIFF 임포트
xcodebuild -importLocalizations \
-localizationPath ./Localizations/ko.xcloc \
-project App.xcodeproj
# Android: 번역 추출 (Google Play 앱 번역 서비스)
# Android Studio > Tools > Google Play Developer API
# 또는 fastlane supply로 자동화