콘텐츠 대표 이미지 - iOS 국제화와 NSLocalizedString을활용한 다국어 지원 완전 정복 🌍
📱 iOS 개발 · 국제화

iOS 국제화와 NSLocalizedString을
활용한 다국어 지원 완전 정복 🌍

앱 하나로 전 세계를 공략하는 현실적인 로컬라이제이션 가이드
🇰🇷 🇺🇸 🇯🇵 🇨🇳 🇩🇪 🇫🇷 🇧🇷 🇸🇦

야, 솔직히 말해봐. 앱 만들 때 "일단 한국어로만 만들고 나중에 다국어 추가하지 뭐~" 이런 생각 해본 적 있지? 🙋 나도 그랬어. 근데 나중에 다국어 추가하려고 코드 열어보면... 하드코딩된 한국어 문자열이 수백 개야. 그때 느끼는 그 절망감... 😭

오늘은 그런 실수를 처음부터 방지하고, iOS에서 국제화(Internationalization, i18n)와 현지화(Localization, l10n)를 제대로 구현하는 방법을 친구한테 설명하듯 쉽고 재미있게 알려줄게. NSLocalizedString부터 시작해서 Localizable.strings, stringsdict, SwiftUILocalizedStringKey까지 — 전부 다 커버할 거야!

앱스토어에서 글로벌 출시를 꿈꾸는 개발자라면, 이 글 하나로 다국어 지원의 모든 것을 정리할 수 있을 거야. 자, 시작해보자! 🚀


🌐 1. 국제화(i18n)와 현지화(l10n) — 뭐가 다른 거야?

먼저 용어부터 정리하고 가자. 이 두 개념, 헷갈리는 사람 엄청 많아.

국제화 vs 현지화 — 핵심 차이점 🔧 국제화 (i18n) Internationalization 앱이 여러 언어/지역을 지원할 수 있도록 구조를 설계하는 것 ✔ 날짜/숫자 포맷 처리 ✔ 텍스트 방향(RTL) 지원 ✔ 문자열 외부화 구조 ✔ 레이아웃 유연성 확보 🌍 현지화 (l10n) Localization 특정 언어/지역에 맞게 실제 콘텐츠를 번역·적용하는 것 ✔ 텍스트 번역 작업 ✔ 이미지/아이콘 교체 ✔ 통화/단위 변환 ✔ 문화적 맥락 반영 선행 조건
🎯 한 줄 요약
국제화(i18n)는 "다국어를 지원할 수 있는 뼈대를 만드는 것"이고,
현지화(l10n)는 "그 뼈대에 실제 번역 살을 붙이는 것"이야.

국제화 없이 현지화는 불가능해. 순서가 중요해!
왜 i18n이 18이고 l10n이 10이야?

재밌는 거 알려줄게. i18n은 "internationalization"에서 첫 글자 i와 마지막 글자 n 사이에 글자가 18개라서 i18n이야. 마찬가지로 l10n은 "localization"에서 l과 n 사이에 10개 글자. 개발자들의 귀여운 줄임말 센스 😄

Apple의 국제화 철학

Apple은 iOS 초창기부터 국제화를 굉장히 중요하게 여겼어. Foundation 프레임워크 자체가 국제화를 염두에 두고 설계됐거든. NSLocale, DateFormatter, NumberFormatter 같은 클래스들이 다 그 증거야.

Apple이 제공하는 국제화 도구들을 크게 나누면 이렇게 돼:

도구/API 역할
NSLocalizedString 문자열 현지화의 핵심 매크로
Localizable.strings 번역 문자열 저장 파일
stringsdict 복수형(plural) 처리 파일
NSLocale 지역 설정 정보 접근
DateFormatter 날짜/시간 현지화
NumberFormatter 숫자/통화 현지화
Xcode Localization Catalog 번역 작업 관리 도구

🔬 2. NSLocalizedString 완전 해부

NSLocalizedString은 iOS 다국어 지원의 심장이야. 이걸 제대로 이해하면 나머지는 다 따라와. 자, 파헤쳐보자!

기본 사용법

가장 기본적인 형태는 이래:


// 기본 형태

let greeting = NSLocalizedString("hello_greeting", comment: "메인 화면 인사말")



// 실제로 이렇게 많이 써

label.text = NSLocalizedString("welcome_message", comment: "환영 메시지")



// Swift에서는 이런 축약형도 자주 사용

let title = NSLocalizedString("nav_title_home", comment: "홈 화면 네비게이션 타이틀")
NSLocalizedString의 실제 정체

사실 NSLocalizedString은 함수가 아니라 매크로(Macro)야. Objective-C 시절부터 내려온 매크로인데, Swift에서도 그대로 사용 가능해. 실제로 이렇게 정의돼 있어:


// NSLocalizedString의 실제 정의 (Foundation 프레임워크)

#define NSLocalizedString(key, comment) \

[NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]



// 더 완전한 버전

func NSLocalizedString(

_ key: String,

tableName: String? = nil,

bundle: Bundle = Bundle.main,

value: String = "",

comment: String

) -> String
💡 알아두면 좋은 것!
NSLocalizedStringcomment 파라미터는 번역가를 위한 설명이야. 코드에서는 무시되지만, genstrings 도구로 .strings 파일을 생성할 때 주석으로 포함돼. 번역가가 맥락을 이해하는 데 엄청 중요하니까 절대 빈 문자열로 두지 마!
파라미터 완전 분석
파라미터 타입 설명
key String 번역 파일에서 찾을 키값. 고유해야 함
tableName String? 사용할 .strings 파일명 (nil이면 Localizable.strings)
bundle Bundle 번역 파일이 있는 번들 (기본: mainBundle)
value String 키를 못 찾았을 때 반환할 기본값
comment String 번역가를 위한 컨텍스트 설명
다양한 활용 패턴

// 1. 기본 사용

let text1 = NSLocalizedString("button_ok", comment: "확인 버튼")



// 2. 특정 테이블 지정 (Settings.strings 파일 사용)

let text2 = NSLocalizedString("privacy_title",

tableName: "Settings",

comment: "설정 화면 개인정보 제목")



// 3. 특정 번들 지정 (프레임워크/익스텐션에서 유용)

let text3 = NSLocalizedString("error_message",

bundle: Bundle(for: MyClass.self),

comment: "에러 메시지")



// 4. 기본값 지정 (키 없을 때 폴백)

let text4 = NSLocalizedString("new_feature_title",

value: "새로운 기능",

comment: "새 기능 타이틀")



// 5. 변수 삽입 (String format 활용)

let userName = "김철수"

let template = NSLocalizedString("welcome_user",

comment: "사용자 환영 메시지 (%@는 이름)")

let text5 = String(format: template, userName)

// Localizable.strings: "welcome_user" = "%@님, 환영합니다!";
Swift에서의 편의 확장

실무에서는 NSLocalizedString을 매번 쓰기 번거로워서 이런 확장을 많이 써:


// String 확장으로 편하게 사용하기

extension String {

var localized: String {

return NSLocalizedString(self, comment: "")

}



func localized(with arguments: CVarArg...) -> String {

return String(format: self.localized, arguments: arguments)

}

}



// 사용 예시

label.text = "button_ok".localized

label.text = "welcome_user".localized(with: "김철수")



// 또는 enum으로 관리하는 패턴

enum L10n {

enum Button {

static let ok = NSLocalizedString("button_ok", comment: "확인")

static let cancel = NSLocalizedString("button_cancel", comment: "취소")

static let save = NSLocalizedString("button_save", comment: "저장")

}

enum Error {

static let networkFail = NSLocalizedString("error_network",

comment: "네트워크 오류")

}

}



// 사용

button.setTitle(L10n.Button.ok, for: .normal)
⚠️ 주의!
String 확장의 localized는 편리하지만, comment가 빈 문자열이 돼서 번역가에게 컨텍스트를 제공하지 못해. 소규모 프로젝트나 혼자 번역할 때는 괜찮지만, 전문 번역 서비스를 이용할 때는 comment를 제대로 채우는 게 좋아.

📄 3. Localizable.strings 파일 완전 정복

Localizable.strings는 번역 문자열을 저장하는 핵심 파일이야. 이 파일 구조를 제대로 이해해야 다국어 지원이 매끄럽게 돌아가.

파일 생성 방법
1
Xcode에서 File → New → File 선택
2
Strings File 선택 후 파일명을 Localizable로 지정
3
파일 선택 후 우측 File Inspector에서 Localize... 클릭
4
기본 언어(보통 English 또는 Korean) 선택
5
Project Settings → Info → Localizations에서 추가 언어 설정
파일 구조와 문법

/* Localizable.strings (Korean) */

/* 파일 인코딩: UTF-16 (Xcode 기본) */



/* ===== 공통 버튼 ===== */

"button_ok" = "확인";

"button_cancel" = "취소";

"button_save" = "저장";

"button_delete" = "삭제";

"button_back" = "뒤로";



/* ===== 메인 화면 ===== */

"main_title" = "홈";

"main_welcome" = "안녕하세요!";

"main_subtitle" = "오늘도 좋은 하루 되세요";



/* ===== 포맷 문자열 ===== */

/* %@는 사용자 이름으로 대체됩니다 */

"welcome_user" = "%@님, 환영합니다!";



/* %d는 알림 개수로 대체됩니다 */

"notification_count" = "알림 %d개";



/* ===== 에러 메시지 ===== */

"error_network" = "네트워크 연결을 확인해주세요.";

"error_unknown" = "알 수 없는 오류가 발생했습니다.";

"error_auth_failed" = "인증에 실패했습니다. 다시 시도해주세요.";

/* Localizable.strings (English) */



/* ===== 공통 버튼 ===== */

"button_ok" = "OK";

"button_cancel" = "Cancel";

"button_save" = "Save";

"button_delete" = "Delete";

"button_back" = "Back";



/* ===== 메인 화면 ===== */

"main_title" = "Home";

"main_welcome" = "Hello!";

"main_subtitle" = "Have a great day";



/* ===== 포맷 문자열 ===== */

"welcome_user" = "Welcome, %@!";

"notification_count" = "%d notifications";



/* ===== 에러 메시지 ===== */

"error_network" = "Please check your network connection.";

"error_unknown" = "An unknown error occurred.";

"error_auth_failed" = "Authentication failed. Please try again.";
키 네이밍 컨벤션 — 이게 진짜 중요해!

키 이름을 어떻게 짓느냐에 따라 프로젝트 관리가 천국과 지옥으로 갈려. 실무에서 검증된 패턴들을 소개할게:

패턴 예시 특징
화면_요소 login_button_title 화면별 그룹화, 직관적
카테고리.요소 button.ok 계층 구조 표현 (단, 점은 키에 포함됨)
SCREENAME_ELEMENT HOME_TITLE 대문자 스타일, 명확한 구분
snake_case home_screen_title 가장 일반적, 가독성 좋음 ✅
💡 실무 팁: 키 네이밍 규칙 통일하기
팀 프로젝트에서는 반드시 키 네이밍 규칙을 문서화해야 해. 나중에 번역 파일이 수백 줄이 되면 규칙 없이는 관리가 불가능해져. 추천 패턴: [화면명]_[컴포넌트]_[상태/타입]
예: login_button_submit, profile_label_username, error_alert_network_title
genstrings 도구로 자동 추출하기

코드에서 NSLocalizedString을 사용한 모든 키를 자동으로 추출해주는 genstrings 명령어가 있어:


# 터미널에서 실행

# 프로젝트 루트 디렉토리에서

find . -name "*.swift" | xargs genstrings -o en.lproj/



# 특정 테이블 지정

find . -name "*.swift" | xargs genstrings -o en.lproj/ -s "NSLocalizedString"



# Objective-C 파일도 포함

find . \( -name "*.swift" -o -name "*.m" \) | xargs genstrings -o en.lproj/

이 명령어를 실행하면 코드에서 사용된 모든 NSLocalizedString 키가 자동으로 Localizable.strings 파일에 추가돼. 번역 작업 시작 전에 꼭 실행해봐!


🏗️ 4. Xcode 프로젝트 국제화 설정 — 처음부터 제대로!
다국어 프로젝트 폴더 구조 📁 MyApp 📁 ko.lproj 📄 Localizable.strings 📄 InfoPlist.strings 📄 Localizable.stringsdict 📁 en.lproj 📄 Localizable.strings 📄 InfoPlist.strings 📄 Localizable.stringsdict 📁 ja.lproj 📄 Localizable.strings 📁 zh-Hans.lproj 📄 Localizable.strings 📁 Base.lproj ko.lproj 한국어 리소스 폴더 en.lproj 영어 리소스 폴더 ja.lproj / zh-Hans.lproj 일본어 / 중국어 간체 Base.lproj 스토리보드 기본 리소스 주요 언어 코드 ko → 한국어 en → 영어 ja → 일본어 zh-Hans → 중국어 간체 zh-Hant → 중국어 번체 de → 독일어 fr → 프랑스어 es → 스페인어 ar → 아랍어(RTL) pt-BR → 포르투갈어(브라질) ru → 러시아어
Xcode에서 언어 추가하는 방법

// 1. Project Navigator에서 프로젝트 파일 선택

// 2. PROJECT 섹션 선택 (TARGETS 아님!)

// 3. Info 탭 클릭

// 4. Localizations 섹션에서 + 버튼 클릭

// 5. 추가할 언어 선택



// 이렇게 하면 자동으로 해당 언어의 .lproj 폴더가 생성돼

// 예: ko.lproj, ja.lproj, zh-Hans.lproj 등
InfoPlist.strings — 앱 이름도 현지화하자!

앱 이름, 권한 요청 메시지 같은 시스템 레벨 문자열은 InfoPlist.strings 파일에서 관리해:


/* InfoPlist.strings (Korean) */



/* 앱 이름 현지화 */

"CFBundleDisplayName" = "나의 앱";



/* 카메라 권한 요청 메시지 */

"NSCameraUsageDescription" = "프로필 사진 촬영을 위해 카메라 접근이 필요합니다.";



/* 사진 라이브러리 권한 */

"NSPhotoLibraryUsageDescription" = "사진을 업로드하기 위해 사진 라이브러리 접근이 필요합니다.";



/* 위치 권한 */

"NSLocationWhenInUseUsageDescription" = "주변 가게를 찾기 위해 위치 정보가 필요합니다.";



/* 알림 권한 */

"NSUserNotificationUsageDescription" = "중요한 소식을 알려드리기 위해 알림 권한이 필요합니다.";
💡 앱스토어 심사 팁!
권한 요청 메시지(NSCameraUsageDescription 등)를 현지화하지 않으면 앱스토어 심사에서 거절될 수 있어. 특히 일본, 중국 시장을 타겟으로 한다면 해당 언어로 반드시 번역해서 넣어야 해!

🔢 5. stringsdict — 복수형 처리의 마법

이게 진짜 많은 개발자들이 놓치는 부분이야. "1개", "2개", "3개"는 한국어에서 별 문제가 없어. 근데 영어로 가면? "1 item", "2 items" — 단수/복수가 달라지잖아. 러시아어는 더 복잡해서 1, 2~4, 5~20이 다 달라. 이걸 처리하는 게 .stringsdict 파일이야!

stringsdict 파일 구조

.stringsdict는 XML 기반의 Property List 파일이야:


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"

"http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<!-- 키: NSLocalizedString에서 사용할 키 -->

<key>items_count</key>

<dict>

<key>NSStringLocalizedFormatKey</key>

<string>%#@items@</string>

<key>items</key>

<dict>

<key>NSStringFormatSpecTypeKey</key>

<string>NSStringPluralRuleType</string>

<key>NSStringFormatValueTypeKey</key>

<string>d</string>

<!-- 복수형 규칙 -->

<key>zero</key>

<string>항목 없음</string>

<key>one</key>

<string>항목 1개</string>

<key>other</key>

<string>항목 %d개</string>

</dict>

</dict>

</dict>

</plist>
영어 stringsdict 예시

<!-- en.lproj/Localizable.stringsdict -->

<key>items_count</key>

<dict>

<key>NSStringLocalizedFormatKey</key>

<string>%#@items@</string>

<key>items</key>

<dict>

<key>NSStringFormatSpecTypeKey</key>

<string>NSStringPluralRuleType</string>

<key>NSStringFormatValueTypeKey</key>

<string>d</string>

<key>one</key>

<string>%d item</string>    <!-- 1 item -->

<key>other</key>

<string>%d items</string>   <!-- 2+ items -->

</dict>

</dict>
Swift에서 stringsdict 사용하기

// stringsdict는 NSLocalizedString으로 그냥 사용하면 돼!

// iOS가 알아서 복수형 규칙을 적용해줌



let count = 5

let format = NSLocalizedString("items_count", comment: "항목 개수")

let result = String(format: format, count)

// 한국어: "항목 5개"

// 영어: "5 items"



// 더 복잡한 예시: 여러 변수 포함

// stringsdict 키: "messages_from_user"

// NSStringLocalizedFormatKey: "%1$@ sent %2$#@messages@"

let userName = "김철수"

let msgCount = 3

let msgFormat = NSLocalizedString("messages_from_user", comment: "")

let msgResult = String(format: msgFormat, userName, msgCount)

// 영어: "김철수 sent 3 messages"
언어별 복수형 규칙 (CLDR 기준)
언어 복수형 카테고리 규칙
한국어, 일본어, 중국어 other만 복수형 구분 없음
영어, 독일어, 스페인어 one, other 1개 vs 나머지
프랑스어 one, other 0~1 vs 나머지
러시아어, 폴란드어 one, few, many, other 매우 복잡한 규칙
아랍어 zero, one, two, few, many, other 6가지 형태!
⚠️ stringsdict 주의사항
.stringsdict 파일은 .strings 파일보다 우선순위가 높아. 같은 키가 두 파일에 있으면 .stringsdict가 사용돼. 그리고 .stringsdict에서 zero 카테고리를 지원하지 않는 언어(영어 등)에서는 zero를 정의해도 무시될 수 있어.

⚡ 6. SwiftUI에서의 다국어 지원 — 더 스마트하게!

SwiftUI는 국제화를 훨씬 더 우아하게 처리해. LocalizedStringKey라는 타입 덕분에 문자열 리터럴이 자동으로 현지화돼. 이게 얼마나 편한지 보여줄게!

Text 뷰의 자동 현지화

import SwiftUI



struct ContentView: View {

var body: some View {

VStack {

// 자동으로 Localizable.strings에서 "hello_greeting" 키를 찾아!

Text("hello_greeting")



// 이것도 자동 현지화

Text("welcome_message")



// 변수 삽입도 가능

Text("welcome_user \("김철수")")

// Localizable.strings: "welcome_user %@" = "%@님, 환영합니다!";



// 현지화 비활성화하고 싶을 때

Text(verbatim: "이 텍스트는 번역 안 됨")

}

}

}
LocalizedStringKey 직접 사용

// LocalizedStringKey 타입 명시

let key: LocalizedStringKey = "button_ok"



// 커스텀 뷰에서 LocalizedStringKey 받기

struct CustomButton: View {

let title: LocalizedStringKey

let action: () -> Void



var body: some View {

Button(action: action) {

Text(title)

.padding()

.background(Color.blue)

.foregroundColor(.white)

.cornerRadius(8)

}

}

}



// 사용

CustomButton(title: "button_ok") {

print("확인 버튼 탭")

}



// String.LocalizationValue (iOS 16+)

// 더 타입 안전한 방식

let greeting = String(localized: "hello_greeting",

comment: "메인 화면 인사말")
SwiftUI의 String Catalog (Xcode 15+)

Xcode 15부터는 String Catalog라는 새로운 방식이 도입됐어. .xcstrings 파일 하나로 모든 언어의 번역을 관리할 수 있어서 훨씬 편해!


// String Catalog (.xcstrings) 사용 시

// Xcode 15+ 에서 File → New → String Catalog 생성



// 코드에서는 기존과 동일하게 사용

Text("hello_greeting")

NSLocalizedString("hello_greeting", comment: "인사말")



// String Catalog의 장점:

// 1. 모든 언어 번역을 하나의 파일에서 관리

// 2. Xcode UI에서 직접 편집 가능

// 3. 번역 상태 추적 (번역됨/미번역/검토 필요)

// 4. 자동으로 코드에서 키 추출

// 5. 복수형 처리도 UI에서 직접 설정
SwiftUI에서 날짜/숫자 현지화

import SwiftUI



struct LocalizedFormatsView: View {

let date = Date()

let price = 29900.0

let count = 1234567



var body: some View {

VStack(alignment: .leading, spacing: 12) {

// 날짜 자동 현지화

Text(date, style: .date)

// 한국어: "2024년 1월 15일"

// 영어: "January 15, 2024"



// 날짜+시간

Text(date, style: .dateTime)



// 통화 현지화

Text(price, format: .currency(code: "KRW"))

// 한국어: "₩29,900"

// 영어: "₩29,900" (통화 코드 기준)



// 숫자 현지화

Text(count, format: .number)

// 한국어: "1,234,567"

// 독일어: "1.234.567" (점이 천단위 구분자)

}

}

}
💡 SwiftUI Text의 마법!
SwiftUI의 Text는 문자열 리터럴을 받으면 자동으로 LocalizedStringKey로 처리해. 그래서 Text("button_ok")라고 쓰면 자동으로 현지화된 문자열을 보여줘. 반면 Text(someVariable)처럼 변수를 넣으면 현지화가 안 돼. 이 차이를 꼭 기억해!

📅 7. 날짜·숫자·통화 현지화 — 디테일이 완성도를 만든다

텍스트 번역만 하면 끝이라고 생각하면 큰 오산이야. 날짜 표기, 숫자 포맷, 통화 기호 — 이런 것들도 나라마다 다 달라. 이걸 제대로 처리해야 진짜 현지화가 완성돼!

DateFormatter 현지화

import Foundation



// ❌ 잘못된 방법 - 하드코딩

let badFormatter = DateFormatter()

badFormatter.dateFormat = "yyyy년 MM월 dd일"  // 한국어만 됨!



// ✅ 올바른 방법 - 시스템 로케일 사용

let goodFormatter = DateFormatter()

goodFormatter.dateStyle = .long

goodFormatter.timeStyle = .short

// locale은 자동으로 현재 기기 설정 따라감



let date = Date()

let formatted = goodFormatter.string(from: date)

// 한국어 기기: "2024년 1월 15일 오후 3:30"

// 영어 기기: "January 15, 2024 at 3:30 PM"

// 독일어 기기: "15. Januar 2024 um 15:30 Uhr"



// 특정 로케일 강제 지정

let japaneseFormatter = DateFormatter()

japaneseFormatter.locale = Locale(identifier: "ja_JP")

japaneseFormatter.dateStyle = .full

let japaneseDate = japaneseFormatter.string(from: date)

// "2024年1月15日 月曜日"



// 상대적 날짜 표현 (iOS 13+)

let relativeFormatter = RelativeDateTimeFormatter()

relativeFormatter.unitsStyle = .full

let relativeDate = relativeFormatter.localizedString(for: date,

relativeTo: Date())

// "방금 전", "3분 전", "어제" 등 자동 현지화
NumberFormatter 현지화

import Foundation



// 숫자 현지화

let numberFormatter = NumberFormatter()

numberFormatter.numberStyle = .decimal

let number = 1234567.89

let formattedNumber = numberFormatter.string(from: NSNumber(value: number))

// 한국어: "1,234,567.89"

// 독일어: "1.234.567,89" (소수점과 천단위 구분자가 반대!)

// 인도: "12,34,567.89" (인도식 자릿수 구분)



// 통화 현지화

let currencyFormatter = NumberFormatter()

currencyFormatter.numberStyle = .currency

currencyFormatter.currencyCode = "KRW"

let price = 29900.0

let formattedPrice = currencyFormatter.string(from: NSNumber(value: price))

// "₩29,900"



// 퍼센트 현지화

let percentFormatter = NumberFormatter()

percentFormatter.numberStyle = .percent

let ratio = 0.756

let formattedPercent = percentFormatter.string(from: NSNumber(value: ratio))

// 한국어: "75.6%"

// 일부 유럽 언어: "75,6 %" (공백 포함)



// 서수 표현 (iOS 9+)

let ordinalFormatter = NumberFormatter()

ordinalFormatter.numberStyle = .ordinal

let rank = 3

let formattedRank = ordinalFormatter.string(from: NSNumber(value: rank))

// 영어: "3rd"

// 한국어: "3번째"

// 프랑스어: "3e"
Measurement 현지화 (단위 변환)

import Foundation



// 거리 단위 현지화

let distance = Measurement(value: 5.0, unit: UnitLength.kilometers)

let formatter = MeasurementFormatter()

formatter.unitOptions = .naturalScale



// 미국 로케일 (마일 사용)

formatter.locale = Locale(identifier: "en_US")

print(formatter.string(from: distance))

// "3.107 mi" (자동으로 킬로미터 → 마일 변환!)



// 한국 로케일 (킬로미터 사용)

formatter.locale = Locale(identifier: "ko_KR")

print(formatter.string(from: distance))

// "5 km"



// 온도 단위

let temp = Measurement(value: 100.0, unit: UnitTemperature.celsius)

formatter.locale = Locale(identifier: "en_US")

print(formatter.string(from: temp))

// "212°F" (섭씨 → 화씨 자동 변환!)
⚠️ 미국 시장 진출 시 주의!
미국은 야드파운드법을 사용해. 거리는 마일, 온도는 화씨, 무게는 파운드. MeasurementFormatter를 사용하면 이런 변환을 자동으로 처리해줘서 정말 편해. 하드코딩으로 단위를 처리하면 미국 사용자들이 엄청 불편해할 거야!

↔️ 8. RTL(오른쪽→왼쪽) 언어 지원 — 아랍어·히브리어 대응

아랍어, 히브리어, 페르시아어는 오른쪽에서 왼쪽으로 읽어. 이걸 RTL(Right-to-Left)이라고 해. 중동 시장을 노린다면 반드시 알아야 해!

LTR vs RTL 레이아웃 비교 LTR (한국어/영어) ← 뒤로 편집 → 아이템 제목 두 번째 아이템 왼쪽 → 오른쪽 방향 RTL (아랍어) تعديل ← الرئيسية → رجوع عنوان العنصر العنصر الثاني 오른쪽 → 왼쪽 방향 (자동 미러링)
iOS의 자동 RTL 지원

좋은 소식이 있어! iOS는 아랍어/히브리어 로케일에서 자동으로 레이아웃을 미러링해줘. Auto Layout을 제대로 사용하면 별도 코드 없이도 RTL이 동작해!


// ✅ RTL 자동 지원 - leading/trailing 사용

view.leadingAnchor.constraint(equalTo: parent.leadingAnchor, constant: 16)

view.trailingAnchor.constraint(equalTo: parent.trailingAnchor, constant: -16)

// LTR: leading = 왼쪽, trailing = 오른쪽

// RTL: leading = 오른쪽, trailing = 왼쪽 (자동 미러링!)



// ❌ RTL 지원 안 됨 - left/right 사용

view.leftAnchor.constraint(equalTo: parent.leftAnchor, constant: 16)

view.rightAnchor.constraint(equalTo: parent.rightAnchor, constant: -16)

// left/right는 항상 물리적 방향이라 RTL에서 미러링 안 됨



// SwiftUI는 기본적으로 RTL 자동 지원

HStack {

Image(systemName: "person.circle")

Text("사용자 이름")

Spacer()

Image(systemName: "chevron.right")

}

// RTL에서는 자동으로 오른쪽부터 배치됨



// 특정 뷰만 RTL 강제 적용

Text("مرحبا")

.environment(\.layoutDirection, .rightToLeft)



// 현재 레이아웃 방향 확인

if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft {

// RTL 처리

}
RTL에서 주의할 것들

// 1. 이미지 미러링 - 방향성 있는 아이콘

// SF Symbols는 자동으로 RTL 미러링 지원

Image(systemName: "arrow.right")

// RTL에서 자동으로 "arrow.left"처럼 보임



// 커스텀 이미지 미러링

Image("custom_arrow")

.flipsForRightToLeftLayoutDirection(true)



// 2. 텍스트 정렬

// ❌ 하드코딩

label.textAlignment = .left



// ✅ 자연스러운 정렬 (RTL에서 자동으로 오른쪽 정렬)

label.textAlignment = .natural



// 3. 스택뷰 방향

// UIStackView는 자동으로 RTL 처리

// semanticContentAttribute 설정으로 제어 가능

stackView.semanticContentAttribute = .forceLeftToRight  // 강제 LTR

stackView.semanticContentAttribute = .forceRightToLeft  // 강제 RTL

stackView.semanticContentAttribute = .unspecified       // 자동

💼 9. 실무 베스트 프랙티스 — 현업에서 검증된 노하우

이론은 충분히 배웠으니, 이제 실제 프로젝트에서 써먹을 수 있는 실전 팁들을 알려줄게. 재능넷 같은 플랫폼에서 다국어 앱을 개발할 때도 이런 패턴들이 엄청 유용해!

1. 문자열 길이 변화에 대비한 레이아웃

번역하면 텍스트 길이가 달라져. 영어 "OK"가 독일어로 "Bestätigen"이 되는 것처럼. 레이아웃이 이걸 수용해야 해:


// ❌ 고정 너비 버튼 - 번역 텍스트가 잘릴 수 있음

button.frame = CGRect(x: 0, y: 0, width: 80, height: 44)



// ✅ 동적 크기 버튼

button.translatesAutoresizingMaskIntoConstraints = false

button.widthAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true

button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)



// SwiftUI에서는 기본적으로 동적 크기

Button("button_ok") { }

.padding(.horizontal, 16)

.frame(minWidth: 80)



// 텍스트 길이 변화 대비 팁:

// - 독일어: 영어 대비 약 30% 길어짐

// - 핀란드어: 영어 대비 약 60% 길어짐

// - 중국어/일본어: 영어 대비 약 30% 짧아짐

// → 레이아웃에 최소 40% 여유 공간 확보 권장
2. 번역 키 관리 자동화

// SwiftGen 같은 도구로 타입 안전한 번역 키 생성

// SwiftGen이 자동으로 이런 코드를 생성해줌:



// Generated by SwiftGen

internal enum L10n {

internal enum Button {

internal static let ok = L10n.tr("Localizable", "button.ok")

internal static let cancel = L10n.tr("Localizable", "button.cancel")

}

internal enum Error {

internal static let network = L10n.tr("Localizable", "error.network")

}

private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {

let format = BundleToken.bundle.localizedString(forKey: key,

value: nil,

table: table)

return String(format: format, locale: Locale.current, arguments: args)

}

}



// 사용 - 오타 없이 안전하게!

label.text = L10n.Button.ok

label.text = L10n.Error.network
3. 번역 파일 품질 검증

// 번역 누락 감지 스크립트 (Python)

// 이런 스크립트를 CI/CD에 추가하면 좋아



/*

import re

import sys



def parse_strings_file(filepath):

keys = set()

with open(filepath, 'r', encoding='utf-16') as f:

content = f.read()

pattern = r'"([^"]+)"\s*='

keys = set(re.findall(pattern, content))

return keys



base_keys = parse_strings_file('en.lproj/Localizable.strings')

ko_keys = parse_strings_file('ko.lproj/Localizable.strings')



missing = base_keys - ko_keys

if missing:

print(f"번역 누락 키: {missing}")

sys.exit(1)

*/



// Xcode에서도 번역 누락 경고 설정 가능

// Build Settings → Missing Localization → Warning
4. 앱 내 언어 변경 기능 구현

사용자가 앱 내에서 직접 언어를 바꿀 수 있게 하고 싶다면? iOS 13+에서는 시스템 설정에서 앱별 언어 설정이 가능하지만, 앱 내에서 직접 구현하려면 이렇게 해:


// 앱 내 언어 변경 구현

class LanguageManager {

static let shared = LanguageManager()

private let languageKey = "AppLanguage"



var currentLanguage: String {

get { UserDefaults.standard.string(forKey: languageKey) ?? "ko" }

set { UserDefaults.standard.set(newValue, forKey: languageKey) }

}



func setLanguage(_ language: String) {

currentLanguage = language

// Bundle 교체로 언어 변경

Bundle.setLanguage(language)

// 앱 재시작 또는 루트 뷰 재로드 필요

NotificationCenter.default.post(name: .languageChanged, object: nil)

}

}



// Bundle 확장 - 언어 강제 변경

extension Bundle {

static func setLanguage(_ language: String) {

defer {

object_setClass(Bundle.main, PrivateBundle.self)

}

objc_setAssociatedObject(

Bundle.main,

&bundleKey,

language,

.OBJC_ASSOCIATION_RETAIN_NONATOMIC

)

}

}



// 사용

LanguageManager.shared.setLanguage("en")  // 영어로 변경

LanguageManager.shared.setLanguage("ja")  // 일본어로 변경
⚠️ 앱 내 언어 변경 주의사항
앱 내 언어 변경은 구현이 복잡하고 버그가 생기기 쉬워. iOS 13+에서는 시스템 설정 → 앱 이름 → 언어에서 앱별 언어를 설정할 수 있어. 가능하면 이 시스템 기능을 활용하고, 앱 내 언어 변경은 정말 필요한 경우에만 구현하는 걸 추천해.
5. 번역 서비스 연동

번역 작업을 전문 번역가에게 맡기고 싶다면 Xcode의 Localization Export/Import 기능을 활용해:


// Xcode에서 번역 파일 내보내기

// Product → Export Localizations...

// → .xcloc 파일 생성 (번역가에게 전달)



// 번역 완료 후 가져오기

// Product → Import Localizations...

// → 번역된 .xcloc 파일 선택



// .xcloc 파일 구조

// MyApp.xcloc/

//   ├── contents.json

//   ├── Localized Contents/

//   │   └── ko.xliff  ← XLIFF 형식의 번역 파일

//   └── Source Contents/

//       └── en.xliff  ← 원본 영어 파일



// XLIFF는 국제 표준 번역 파일 형식이라

// 대부분의 번역 도구(Transifex, Lokalise 등)에서 지원해

🧪 10. 국제화 테스트와 디버깅 — 출시 전 반드시 확인!

다국어 지원 구현했다고 끝이 아니야. 제대로 동작하는지 테스트해야 해. 이 단계를 건너뛰면 출시 후에 "왜 일본어가 깨져요?" 같은 리뷰를 받게 될 거야 😅

Xcode Scheme으로 언어 테스트

// Xcode에서 특정 언어로 앱 실행하기

// 1. Product → Scheme → Edit Scheme

// 2. Run → Options 탭

// 3. Application Language: 테스트할 언어 선택

// 4. Application Region: 테스트할 지역 선택



// 또는 Launch Arguments로 설정

// -AppleLanguages (ko)

// -AppleLocale ko_KR



// 시뮬레이터에서 언어 변경

// Settings → General → Language & Region



// 특수 테스트 언어들:

// "Double-Length Pseudolanguage" → 텍스트 2배 길이로 레이아웃 테스트

// "Accented Pseudolanguage" → 악센트 문자로 폰트 테스트

// "Bounded String Pseudolanguage" → 문자열 경계 표시

// "Right-to-Left Pseudolanguage" → RTL 레이아웃 테스트
Unit Test로 번역 검증

import XCTest



class LocalizationTests: XCTestCase {



// 모든 언어에서 키가 번역되었는지 확인

func testAllKeysTranslated() {

let languages = ["ko", "en", "ja", "zh-Hans"]

let baseBundle = Bundle.main



// 기준 언어(영어)의 모든 키 가져오기

guard let enPath = baseBundle.path(forResource: "Localizable",

ofType: "strings",

inDirectory: "en.lproj"),

let enDict = NSDictionary(contentsOfFile: enPath) else {

XCTFail("영어 번역 파일을 찾을 수 없음")

return

}



for language in languages {

guard let langPath = baseBundle.path(forResource: "Localizable",

ofType: "strings",

inDirectory: "\(language).lproj"),

let langDict = NSDictionary(contentsOfFile: langPath) else {

XCTFail("\(language) 번역 파일 없음")

continue

}



for key in enDict.allKeys {

XCTAssertNotNil(langDict[key],

"\(language)에서 '\(key)' 키 번역 누락")

}

}

}



// 번역 문자열이 비어있지 않은지 확인

func testNoEmptyTranslations() {

let languages = ["ko", "en", "ja"]

for language in languages {

guard let path = Bundle.main.path(forResource: "Localizable",

ofType: "strings",

inDirectory: "\(language).lproj"),

let dict = NSDictionary(contentsOfFile: path) else { continue }



for (key, value) in dict {

if let strValue = value as? String {

XCTAssertFalse(strValue.isEmpty,

"\(language)의 '\(key)' 번역이 비어있음")

}

}

}

}

}
Pseudolanguage 테스트 — 이거 진짜 유용해!

Xcode의 Pseudolanguage 기능은 실제 번역 없이도 국제화 문제를 발견할 수 있게 해줘:

Pseudolanguage 용도 발견할 수 있는 문제
Double-Length 텍스트 2배 길이 레이아웃 잘림, 오버플로우
Accented 악센트 문자 추가 폰트 렌더링, 특수문자 처리
Bounded String 문자열에 경계 표시 하드코딩된 문자열 발견
Right-to-Left RTL 레이아웃 left/right 하드코딩 문제
💡 Bounded String Pseudolanguage 꿀팁!
이 모드에서 실행하면 현지화된 문자열은 [# 텍스트 #] 형태로 표시되고, 하드코딩된 문자열은 그냥 그대로 보여. 이걸로 현지화 안 된 문자열을 쉽게 찾을 수 있어. 개발 초기에 꼭 한 번씩 돌려봐!

🚀 11. 고급 기법 — 한 단계 더 나아가기
1. 동적 타입과 국제화

iOS의 Dynamic Type(사용자 글자 크기 설정)과 국제화를 함께 고려해야 해. 특히 일부 언어는 기본 폰트가 다를 수 있어:


// Dynamic Type + 국제화 조합

let label = UILabel()

label.font = UIFont.preferredFont(forTextStyle: .body)

label.adjustsFontForContentSizeCategory = true  // Dynamic Type 자동 적용

label.numberOfLines = 0  // 여러 줄 허용 (번역 텍스트가 길어질 수 있음)



// SwiftUI에서

Text("welcome_message")

.font(.body)  // 자동으로 Dynamic Type 적용

.fixedSize(horizontal: false, vertical: true)  // 세로 확장 허용



// 특정 언어에 맞는 폰트 설정

func fontForCurrentLanguage() -> UIFont {

let language = Locale.current.language.languageCode?.identifier ?? "en"

switch language {

case "ja", "zh":

// 일본어/중국어는 시스템 폰트가 적합

return UIFont.systemFont(ofSize: 16)

case "ar":

// 아랍어는 특별한 폰트 고려

return UIFont(name: "GeezaPro", size: 16) ?? UIFont.systemFont(ofSize: 16)

default:

return UIFont.preferredFont(forTextStyle: .body)

}

}
2. 이미지와 미디어 현지화

// Asset Catalog에서 이미지 현지화

// Xcode Asset Catalog → 이미지 선택 → Localize 버튼 클릭

// 각 언어별로 다른 이미지 사용 가능



// 코드에서는 그냥 이름으로 사용 (자동으로 현지화된 이미지 로드)

let image = UIImage(named: "promotional_banner")

// 한국어: ko.lproj/promotional_banner.png

// 영어: en.lproj/promotional_banner.png



// 텍스트가 포함된 이미지는 언어별로 다른 이미지 사용 권장

// 예: 앱 소개 배너, 튜토리얼 이미지 등



// 동적으로 언어 확인해서 이미지 선택

func localizedImageName(_ baseName: String) -> String {

let languageCode = Locale.current.language.languageCode?.identifier ?? "en"

let localizedName = "\(baseName)_\(languageCode)"

// 현지화된 이미지가 있으면 사용, 없으면 기본 이미지

if UIImage(named: localizedName) != nil {

return localizedName

}

return baseName

}
3. 앱스토어 메타데이터 현지화

앱 자체만 현지화하면 안 돼. 앱스토어 등록 정보도 현지화해야 해:


// App Store Connect에서 현지화할 항목들:

// - 앱 이름 (App Name)

// - 부제목 (Subtitle)

// - 설명 (Description)

// - 키워드 (Keywords) ← 검색 최적화에 매우 중요!

// - 홍보 문구 (Promotional Text)

// - 스크린샷 (Screenshots) ← 각 언어별 스크린샷 권장

// - 미리보기 동영상 (App Preview)



// Fastlane의 deliver 도구로 자동화 가능

// fastlane/metadata/

//   ├── ko/

//   │   ├── name.txt

//   │   ├── description.txt

//   │   └── keywords.txt

//   ├── en-US/

//   │   ├── name.txt

//   │   └── description.txt

//   └── ja/

//       └── ...
4. 번역 메모리와 용어집 관리

규모가 큰 프로젝트라면 번역 일관성을 위해 용어집(Glossary)을 관리해야 해. 예를 들어 "저장"을 어떤 화면에서는 "Save", 어떤 화면에서는 "Store"로 번역하면 안 되잖아:


// 용어집 관리 예시 (JSON 형태)

/*

{

"glossary": [

{

"term": "저장",

"translations": {

"en": "Save",

"ja": "保存",

"zh-Hans": "保存",

"de": "Speichern",

"fr": "Enregistrer"

},

"context": "파일이나 데이터를 저장하는 동작",

"do_not_translate": false

},

{

"term": "앱 이름: MyApp",

"translations": {},

"context": "앱 브랜드명, 번역하지 않음",

"do_not_translate": true

}

]

}

*/



// Lokalise, Transifex, Phrase 같은 번역 관리 플랫폼에서

// 용어집 기능을 제공해. 번역가가 일관된 용어를 사용하도록 강제할 수 있어.

🛠️ 12. 실전 예제 — 처음부터 끝까지 다국어 앱 만들기

지금까지 배운 내용을 종합해서 간단한 다국어 앱을 처음부터 만들어보자. 이 예제를 따라하면 실제 프로젝트에 바로 적용할 수 있어!

다국어 앱 아키텍처 플로우 앱 실행 시스템 언어 감지 해당 언어 .lproj 번들 로드 NSLocalizedString 호출 → 번역 반환 현지화된 UI 표시 ✓ Locale.current.language UserDefaults 앱 언어 설정 ko.lproj / en.lproj ja.lproj / zh-Hans.lproj 키 없으면 key 자체 반환 또는 value 파라미터 반환
완성된 다국어 앱 예제 코드

// LocalizationManager.swift

import Foundation



final class LocalizationManager {

static let shared = LocalizationManager()

private init() {}



// 지원 언어 목록

let supportedLanguages: [(code: String, name: String)] = [

("ko", "한국어"),

("en", "English"),

("ja", "日本語"),

("zh-Hans", "简体中文")

]



// 현재 언어 코드

var currentLanguageCode: String {

return Locale.current.language.languageCode?.identifier ?? "en"

}



// 현재 언어 이름

var currentLanguageName: String {

return supportedLanguages.first {

$0.code == currentLanguageCode

}?.name ?? "English"

}



// 번역 함수

func localize(_ key: String,

table: String? = nil,

comment: String = "") -> String {

return NSLocalizedString(key,

tableName: table,

bundle: .main,

value: key,

comment: comment)

}



// 포맷 번역

func localizeFormat(_ key: String,

_ args: CVarArg...) -> String {

let format = localize(key)

return String(format: format, arguments: args)

}

}

// ContentView.swift (SwiftUI)

import SwiftUI



struct ContentView: View {

@State private var userName = ""

@State private var itemCount = 0



var body: some View {

NavigationView {

VStack(spacing: 20) {

// 자동 현지화 텍스트

Text("main_welcome")

.font(.largeTitle)

.fontWeight(.bold)



// 변수 포함 현지화

if !userName.isEmpty {

Text("welcome_user \(userName)")

.font(.title2)

}



// 텍스트 필드

TextField("placeholder_name", text: $userName)

.textFieldStyle(.roundedBorder)

.padding(.horizontal)



// 스테퍼

Stepper(value: $itemCount, in: 0...100) {

// stringsdict 복수형 처리

Text(String(format: NSLocalizedString("items_count",

comment: ""),

itemCount))

}

.padding(.horizontal)



// 날짜 현지화

Text(Date(), style: .date)

.foregroundColor(.secondary)



// 통화 현지화

Text(29900.0, format: .currency(code: "KRW"))

.foregroundColor(.secondary)



// 버튼

Button("button_save") {

// 저장 액션

}

.buttonStyle(.borderedProminent)

}

.navigationTitle("main_title")

.navigationBarTitleDisplayMode(.large)

}

}

}

/* ko.lproj/Localizable.strings */

"main_title" = "홈";

"main_welcome" = "안녕하세요! 👋";

"welcome_user %@" = "%@님, 반갑습니다!";

"placeholder_name" = "이름을 입력하세요";

"button_save" = "저장";



/* en.lproj/Localizable.strings */

"main_title" = "Home";

"main_welcome" = "Hello! 👋";

"welcome_user %@" = "Welcome, %@!";

"placeholder_name" = "Enter your name";

"button_save" = "Save";



/* ja.lproj/Localizable.strings */

"main_title" = "ホーム";

"main_welcome" = "こんにちは! 👋";

"welcome_user %@" = "%@さん、ようこそ!";

"placeholder_name" = "名前を入力してください";

"button_save" = "保存";

⚠️ 13. 개발자들이 자주 하는 실수 TOP 7

수많은 iOS 앱을 분석하면서 발견한 국제화 관련 흔한 실수들이야. 이거 보고 미리 방지하자!

국제화 개발 시 흔한 실수 TOP 7 1 하드코딩된 문자열 label.text = "확인" ← 절대 금지! 2 빈 comment 파라미터 NSLocalizedString("key", comment: "") ← 번역가 혼란 3 left/right 앵커 사용 leftAnchor 대신 leadingAnchor 사용해야 함 4 고정 너비 UI 컴포넌트 번역 텍스트 길이 변화 미고려 5 복수형 미처리 "1 items" 같은 문법 오류 발생 6 날짜/숫자 하드코딩 포맷 dateFormat = "yyyy-MM-dd" ← 지역화 안 됨 7 InfoPlist.strings 누락 권한 요청 메시지 미번역 → 앱스토어 거절 이 7가지만 피해도 국제화 품질이 확 올라가! Pseudolanguage 테스트로 대부분 사전 발견 가능 ✓
실수 1: 문자열 연결로 번역하기

// ❌ 절대 이렇게 하면 안 돼!

let message = "안녕하세요, " + userName + "님!"

// 영어로 번역하면 어순이 달라져서 이 방식으로는 불가능

// 영어: "Hello, " + userName + "!"  ← 어순은 같지만...

// 일본어: userName + "さん、こんにちは!"  ← 어순 다름!



// ✅ 올바른 방법: 포맷 문자열 사용

let format = NSLocalizedString("greeting_with_name",

comment: "%@는 사용자 이름")

let message = String(format: format, userName)



// Localizable.strings:

// "greeting_with_name" = "%@님, 안녕하세요!";  (한국어)

// "greeting_with_name" = "Hello, %@!";         (영어)

// "greeting_with_name" = "%@さん、こんにちは!"; (일본어)
실수 2: 숫자를 문자열로 직접 변환

// ❌ 잘못된 방법

let price = 29900

label.text = "\(price)원"  // 한국어만 됨

label.text = "$\(price)"   // 달러 기호 하드코딩



// ✅ 올바른 방법

let formatter = NumberFormatter()

formatter.numberStyle = .currency

formatter.currencyCode = "KRW"

label.text = formatter.string(from: NSNumber(value: price))



// SwiftUI에서

Text(29900.0, format: .currency(code: "KRW"))

🔧 14. 유용한 도구와 리소스

혼자 다 하려고 하지 마. 좋은 도구들이 많아. 이걸 잘 활용하면 개발 시간을 엄청 줄일 수 있어!

개발 도구
도구 용도 특징
SwiftGen 코드 자동 생성 타입 안전한 번역 키 생성, 오타 방지
iOSLocalizationEditor 번역 파일 편집 여러 언어를 나란히 편집 가능
Poedit 번역 편집기 번역가 친화적 UI
Fastlane 배포 자동화 앱스토어 메타데이터 현지화 자동화
Bartycrouch 번역 파일 동기화 코드에서 키 자동 추출 및 동기화
번역 관리 플랫폼
플랫폼 특징 가격
Lokalise Xcode 통합, AI 번역, 팀 협업 유료 (무료 플랜 있음)
Transifex 대규모 프로젝트, 번역가 마켓플레이스 유료
Phrase 개발자 친화적, CI/CD 통합 유료
Crowdin 오픈소스 프로젝트 무료, 커뮤니티 번역 오픈소스 무료
POEditor 간단하고 저렴한 번역 관리 소규모 무료
💡 재능넷 활용 팁!
번역 작업이 필요하다면 재능넷에서 전문 번역가를 찾아볼 수 있어. iOS 앱 번역 경험이 있는 번역가를 찾으면 기술적 맥락을 이해하고 더 정확한 번역을 제공해줄 거야. 특히 일본어, 아랍어 같은 특수 언어는 전문가의 도움이 정말 중요해!
참고 자료

// Apple 공식 문서

// https://developer.apple.com/documentation/xcode/localization

// https://developer.apple.com/documentation/foundation/nslocalizedstring



// WWDC 세션 (강력 추천!)

// WWDC 2021 - "Localize your SwiftUI app"

// WWDC 2022 - "Build global apps: Localization by example"

// WWDC 2023 - "Discover String Catalogs"



// Unicode CLDR (복수형 규칙 참고)

// https://cldr.unicode.org/index/cldr-spec/plural-rules



// iOS Human Interface Guidelines - Localization

// https://developer.apple.com/design/human-interface-guidelines/localization

🆕 15. String Catalog — Xcode 15의 게임 체인저

Xcode 15에서 도입된 String Catalog는 기존 .strings 파일 방식을 완전히 혁신했어. 이게 왜 게임 체인저인지 설명해줄게!

String Catalog vs 기존 방식 비교
항목 기존 .strings 방식 String Catalog (.xcstrings)
파일 수 언어당 1개 파일 모든 언어 1개 파일
번역 상태 추적 수동 확인 자동 추적 (번역됨/미번역/검토필요)
키 자동 추출 genstrings 수동 실행 Xcode가 자동으로 추출
복수형 처리 별도 .stringsdict 파일 같은 파일에서 UI로 설정
편집 UI 텍스트 편집기 전용 테이블 UI
Git 충돌 자주 발생 JSON 기반으로 충돌 감소
String Catalog 사용법

// 1. File → New → String Catalog 생성

// 파일명: Localizable.xcstrings



// 2. 코드에서는 기존과 동일하게 사용

// NSLocalizedString, Text(), String(localized:) 모두 동일



// 3. Xcode가 자동으로 코드에서 키를 추출해서 카탈로그에 추가

// Build 시 자동으로 동기화됨



// String Catalog의 JSON 구조 (내부적으로 이렇게 저장됨)

/*

{

"sourceLanguage" : "ko",

"strings" : {

"button_ok" : {

"comment" : "확인 버튼",

"localizations" : {

"ko" : {

"stringUnit" : {

"state" : "translated",

"value" : "확인"

}

},

"en" : {

"stringUnit" : {

"state" : "translated",

"value" : "OK"

}

},

"ja" : {

"stringUnit" : {

"state" : "needs_review",

"value" : "OK"

}

}

}

}

},

"version" : "1.0"

}

*/



// String Catalog에서 복수형 처리

// Xcode UI에서 직접 설정 가능

// 코드에서는 동일하게 사용

let count = 5

let text = String(localized: "items_count \(count)",

comment: "항목 개수")
💡 String Catalog 마이그레이션
기존 .strings 파일을 String Catalog로 마이그레이션하려면:
Xcode에서 .strings 파일 우클릭 → Migrate to String Catalog 선택!
자동으로 변환해줘서 엄청 편해. 단, Xcode 15 이상에서만 가능해.

🎯 마무리 — 다국어 지원, 이제 두렵지 않지?
다국어 지원 완성 체크리스트 ✅ NSLocalizedString으로 모든 문자열 처리 Localizable.strings 파일 각 언어별 작성 stringsdict로 복수형 처리 DateFormatter/NumberFormatter 현지화 InfoPlist.strings 권한 메시지 번역 leading/trailing 앵커 사용 (RTL 대비) 동적 레이아웃으로 텍스트 길이 대비 Pseudolanguage 테스트 완료 앱스토어 메타데이터 현지화 Unit Test로 번역 누락 검증 모두 체크했다면 글로벌 출시 준비 완료! 🌍🚀

자, 오늘 정말 많은 내용을 다뤘어! 처음에는 복잡해 보이지만, 핵심은 간단해:

🎯 핵심 요약

1. 처음부터 NSLocalizedString 사용 — 나중에 추가하려면 지옥이야

2. comment 파라미터 성실히 작성 — 번역가를 위한 배려

3. 포맷 문자열로 변수 삽입 — 문자열 연결 절대 금지

4. stringsdict로 복수형 처리 — "1 items" 같은 실수 방지

5. leading/trailing 앵커 사용 — RTL 자동 지원

6. DateFormatter/NumberFormatter 활용 — 날짜·숫자 자동 현지화

7. Pseudolanguage 테스트 — 출시 전 반드시 확인

글로벌 앱 시장은 정말 크고, 다국어 지원 하나로 사용자 수가 몇 배로 늘어날 수 있어. 한국어만 지원하는 앱과 5개 언어를 지원하는 앱의 잠재 사용자 수 차이를 생각해봐. 처음부터 제대로 구조를 잡으면 나중에 언어 추가하는 게 정말 쉬워져.

그리고 번역 작업이 필요하다면 재능넷 같은 플랫폼에서 전문 번역가의 도움을 받는 것도 좋은 방법이야. 기계 번역보다 훨씬 자연스럽고 문화적 맥락을 잘 반영한 번역을 받을 수 있거든.

자, 이제 너의 앱을 전 세계로 날려보자! 🌍✈️

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0