Skip to main content

Android 앱 현지화 완벽 가이드

strings.xml부터 Play Store 메타데이터까지, Kotlin, Jetpack Compose, Fastlane, AI 자동 번역으로 Android 앱을 현지화하는 방법을 알아보세요.

1

현지화를 위한 Android 프로젝트 설정

Android는 폴더 기반 규칙으로 현지화를 처리해요. 기본 문자열은 res/values/strings.xml에 두고, 번역된 문자열은 res/values-de/, res/values-ja/ 같은 로케일별 폴더에 넣어요.

Android Project Structure
// Android project structure for localization:
// res/
// ├── values/              ← Default (fallback) locale
// │   └── strings.xml
// ├── values-de/           ← German
// │   └── strings.xml
// ├── values-ja/           ← Japanese
// │   └── strings.xml
// └── values-es/           ← Spanish
//     └── strings.xml
//
// Folder naming: values-{language} or values-{language}-r{Region}
// Examples: values-pt-rBR, values-zh-rCN, values-zh-rTW
res/values/ 폴더가 폴백 로케일이에요. 로케일별 폴더에 문자열이 없으면 Android가 기본 폴더에서 로드해요. 하지만 기본 폴더에 문자열이 없으면 지원하지 않는 로케일에서 앱이 비정상 종료돼요.
2

strings.xml 생성

Android 문자열 리소스는 '<resources>' 루트 안에 '<string>' 요소를 넣은 XML을 사용해요. 문자열 플레이스홀더에는 %s, 정수에는 %d, 번역가가 순서를 바꿀 수 있는 위치 인수에는 %1$s/%2$s를 사용하세요.

res/values/strings.xml
<!-- res/values/strings.xml -->
<resources>
    <string name="welcome_title">Welcome to MyApp</string>
    <string name="login_button">Sign In</string>
    <string name="settings_label">Settings</string>
    <string name="greeting">Hello, %s!</string>        <!-- %s = string -->
    <string name="item_count">%d items</string>          <!-- %d = integer -->
    <string name="app_name" translatable="false">MyApp</string>
</resources>
이스케이프하지 않은 아포스트로피가 있으면 XML 파서가 눈에 띄는 오류 없이 실패해요. \'를 사용하거나 값을 큰따옴표로 감싸세요. 또한 기본 values/strings.xml에 문자열이 없으면 iOS처럼 안전하게 폴백하지 않고 앱이 즉시 비정상 종료돼요.
Common strings.xml Mistakes
<!-- ❌ Common mistakes in strings.xml: -->

<!-- Unescaped apostrophe — crashes XML parser silently -->
<string name="message">It's a great day</string>

<!-- Missing from default values/strings.xml — app crashes -->
<!-- (only exists in values-de/strings.xml) -->

<!-- ✅ Correct versions: -->
<string name="message">It\'s a great day</string>
<!-- Or wrap in double quotes: -->
<string name="message">"It's a great day"</string>
3

복수형 처리

Android는 zero, one, two, few, many, other quantity 속성이 있는 '&lt;plurals&gt;' 요소를 사용해요. 필요한 범주는 대상 언어마다 달라요. 아랍어는 6가지를 모두 사용하고, 러시아어에는 few/many가 필요하며, 일본어는 other만 사용해요.

res/values/strings.xml
<!-- res/values/strings.xml -->
<resources>
    <plurals name="items_count">
        <item quantity="zero">No items</item>
        <item quantity="one">%d item</item>
        <item quantity="other">%d items</item>
    </plurals>
</resources>

<!-- Usage in Kotlin: -->
<!-- val text = resources.getQuantityString(
    R.plurals.items_count,
    count,    // selects plural form
    count     // format argument
) -->
quantity 속성은 기기 로케일의 CLDR 규칙에 따라 복수형을 선택해요. 폴백으로 'other'를 항상 포함하세요. 모든 언어에 반드시 존재하는 유일한 범주예요.
4

코드에서 사용: Kotlin 및 Jetpack Compose

기존 Android에서는 getString(R.string.key)과 resources.getQuantityString()을 사용해요. Jetpack Compose에서는 stringResource(R.string.key)와 pluralStringResource()를 사용해요. 둘 다 런타임에 기기 로케일을 기준으로 올바른 번역을 가져와요.

WelcomeScreen.kt
// Traditional Android (Activity/Fragment)
val title = getString(R.string.welcome_title)
val greeting = getString(R.string.greeting, userName)
val items = resources.getQuantityString(
    R.plurals.items_count, count, count
)

// Jetpack Compose
@Composable
fun WelcomeScreen(userName: String, itemCount: Int) {
    // ✅ stringResource — Compose-aware, triggers recomposition
    Text(text = stringResource(R.string.welcome_title))

    // ✅ With format arguments
    Text(text = stringResource(R.string.greeting, userName))

    // ✅ Plurals — count passed TWICE
    Text(text = pluralStringResource(
        R.plurals.items_count,
        itemCount,    // selects plural form
        itemCount     // format argument
    ))
}
pluralStringResource(R.plurals.items, count, count)에서는 count 매개변수를 두 번 전달해요. 첫 번째 값은 복수형을 선택하고 두 번째 값은 형식 인수로 사용해요. 두 번째 count 누락은 Compose 복수형 처리에서 가장 흔한 버그예요.
5

문자열 배열 및 형식 지정 문자열

드롭다운 옵션, 온보딩 단계 같은 순서가 있는 목록에는 '&lt;string-array&gt;'를 사용하세요. 번역가가 문장 구조를 깨뜨리지 않고 단어 순서를 바꿀 수 있도록 형식 지정 문자열에는 위치 형식 인수(%1$s, %2$d)를 사용하세요.

res/values/strings.xml
<!-- res/values/strings.xml -->
<resources>
    <!-- String array for dropdown/list -->
    <string-array name="sort_options">
        <item>Most Recent</item>
        <item>Most Popular</item>
        <item>Price: Low to High</item>
        <item>Price: High to Low</item>
    </string-array>

    <!-- Positional format args for reordering -->
    <string name="welcome_message">
        Hello %1$s, you have %2$d new messages
    </string>
    <!-- Translators can reorder: -->
    <!-- %2$d neue Nachrichten für %1$s -->
</resources>
%1$s 같은 위치 인수를 사용하면 번역가가 매개변수 순서를 자유롭게 바꿀 수 있어요. 어순이 다른 언어에서는 코드를 바꾸지 않고 'Hello %1$s, you have %2$d items'를 '%2$d items for %1$s'로 바꿀 수 있어요.
6

Fastlane으로 Google Play 메타데이터 현지화

Fastlane의 supply 명령을 사용하면 제목, 간단한 설명, 자세한 설명, 변경 로그 같은 Play Store 메타데이터를 저장소에서 로케일별 일반 텍스트 파일로 관리할 수 있어요.

Terminal
# Install Fastlane
$ gem install fastlane

# Initialize supply for Play Store metadata
$ fastlane supply init

# Directory structure created:
# fastlane/metadata/android/
# ├── en-US/
# │   ├── title.txt              # App name (50 chars)
# │   ├── short_description.txt  # Short desc (80 chars)
# │   ├── full_description.txt   # Full desc (4000 chars)
# │   └── changelogs/
# │       └── default.txt        # What's New
# ├── de-DE/
# │   └── ...
# └── ja-JP/
#     └── ...

# Push metadata to Play Store:
$ fastlane supply
Play Store 등록 정보를 현지화하면 비영어권 시장에서 다운로드가 30% 이상 증가해요. 제목, 간단한 설명, 자세한 설명은 검색용으로 색인되므로 이를 번역하는 것이 투자 대비 효과가 가장 큰 현지화예요.
Google Play

Play Store 등록 정보 현지화 자동화

수동으로 복사해 붙여 넣을 필요가 없어요. 글자 수 제한을 고려해 Play Store 제목, 설명, 출시 노트를 175개 이상의 로케일로 번역해요.

Google Play 연동 살펴보기
7

현지화 테스트

에뮬레이터의 로케일 전환, 맞춤 LocaleList를 적용한 Compose 미리 보기, 개발자 옵션의 의사 로케일로 테스트하세요. Gradle의 resConfigs를 사용해 타사 라이브러리에서 불필요한 로케일 리소스를 제거하세요.

Testing Localization
// 1. Emulator: Settings > System > Language > Add language

// 2. Compose Preview with locale:
@Preview
@Composable
fun WelcomePreview() {
    val config = Configuration(resources.configuration).apply {
        setLocale(Locale("de"))
    }
    val localContext = LocalContext.current
    val localizedContext = localContext.createConfigurationContext(config)
    CompositionLocalProvider(
        LocalContext provides localizedContext
    ) {
        WelcomeScreen()
    }
}

// 3. Restrict library locales in build.gradle.kts:
android {
    defaultConfig {
        // Only include locales you actually translate
        resourceConfigurations += listOf("en", "de", "ja", "es", "fr")
    }
}

// 4. Enable pseudolocales in Developer Options:
// en-XA (accented) — detects hardcoded strings
// ar-XB (RTL) — tests layout mirroring
독일어(텍스트가 약 30% 길어짐)와 일본어(약 50% 짧아짐)로 테스트해 레이아웃 문제를 찾아내세요. 개발자 옵션에서 의사 로케일(악센트 문자의 en-XA, RTL의 ar-XB)을 활성화하면 실제 번역 없이도 레이아웃을 집중 테스트할 수 있어요.

번역 품질 검사 자동화

i18n-validate를 사용해 누락된 키, 손상된 플레이스홀더, 복수형 문제를 배포 전에 찾아내세요. 실제 번역이 준비되기 전에 i18n-pseudo의 의사 번역으로 UI를 테스트하세요.
8

번역 자동화

AI로 strings.xml, 복수형, 문자열 배열, Fastlane Supply 메타데이터를 번역하세요. 앱 내 문자열과 Play Store 메타데이터 번역을 모두 자동화해 완전히 현지화된 앱을 제공할 수 있어요.

Terminal
# Translate strings.xml files
> Translate res/values/strings.xml
  to Japanese, German, and Spanish

# Translate Play Store metadata too
> Translate fastlane/metadata/android/en-US/
  to de-DE, ja-JP, es-ES

✓ 6 files translated in 3.2s
i18n Agent는 Android XML 이스케이프를 처리하고 translatable="false" 표시를 유지해요. 또한 대상 언어별 CLDR 복수형 범주를 준수하고 위치 형식 인수를 그대로 보존해요.
JetBrains

Android Studio 플러그인 제공

IntelliJ 및 Android Studio용 i18n Agent 플러그인으로 IDE에서 Android XML 리소스를 직접 번역하세요.

Install
+

추가 기능: LocaleChain을 활용한 스마트 로케일 폴백

Android의 리소스 폴백은 OS가 제어해요. pt-BR 번역이 없으면 Android는 pt-PT를 완전히 건너뛰고 영어를 표시해요. LocaleChain은 문자열 조회를 가로채 구성 가능한 폴백 체인을 차례로 확인하므로 지역 사용자가 가장 가까운 번역을 볼 수 있어요.

Android용 LocaleChain은 오픈 소스 Kotlin 라이브러리예요. GitHub에서 보기

build.gradle.kts
// build.gradle.kts (app module)
dependencies {
    implementation("com.i18nagent:locale-chain-android:0.1.0")
}
MyApp.kt / BaseActivity.kt
// 1. Application.onCreate() — configure chains once
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        LocaleChain.configure()
    }
}

// 2. BaseActivity — wrap context per Activity
open class BaseActivity : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LocaleChain.wrap(newBase))
    }
}

// pt-BR user with only pt-PT translations?
// → Shows Portuguese instead of falling back to English

// Custom overrides for your specific locales:
LocaleChain.configure(
    overrides = mapOf("es-MX" to listOf("es-419", "es"))
)

흔한 실수

기본 문자열 누락으로 인한 앱 비정상 종료

원시 키를 표시하는 iOS와 달리 기본 res/values/strings.xml에 문자열이 없으면 Android 앱은 ResourceNotFoundException과 함께 비정상 종료돼요. 모든 키가 기본 파일에 있는지 항상 확인하세요.

App Bundle 언어 분할로 인한 앱 내 전환 오류

Google Play App Bundle은 APK를 언어별로 분할하므로 사용자는 기기 언어의 문자열만 받아요. 앱 내 언어 전환을 제공한다면 build.gradle.kts에 bundle '{ language { enableSplit = false } }'를 추가하세요.

RTL 레이아웃 깨짐

레이아웃에서 start/end 대신 left/right를 사용하거나 AndroidManifest.xml에 android:supportsRtl="true"가 없으면 문제가 발생해요. Android Studio의 Refactor > Add RTL Support를 사용해 기존 레이아웃을 자동 변환하세요.

라이브러리 리소스로 인한 지원 언어 혼입

타사 라이브러리에 자체 values-XX/strings.xml 파일이 포함되면 Android가 앱에서 실제로 지원하지 않는 언어도 지원한다고 판단해요. build.gradle.kts에서 resConfigs를 사용해 포함할 로케일을 실제로 번역하는 언어로만 제한하세요.

권장 파일 구조

Project Structure
MyApp/
├── app/
│   └── src/main/
│       ├── res/
│       │   ├── values/
│       │   │   ├── strings.xml          # Default (source) strings
│       │   │   └── plurals.xml          # Plural rules
│       │   ├── values-de/
│       │   │   └── strings.xml
│       │   ├── values-ja/
│       │   │   └── strings.xml
│       │   └── values-es/
│       │       └── strings.xml
│       ├── java/com/example/myapp/
│       └── AndroidManifest.xml
├── fastlane/
│   └── metadata/android/
│       ├── en-US/
│       │   ├── title.txt
│       │   ├── short_description.txt
│       │   ├── full_description.txt
│       │   └── changelogs/default.txt
│       ├── de-DE/
│       └── ja-JP/
├── build.gradle.kts
└── settings.gradle.kts

지금 i18n Agent 사용해 보기

번역 파일을 여기에 드롭

JSON, YAML, PO, XML, CSV, Markdown, Properties

또는 클릭하여 파일 선택

대상 언어

가입 불필요즉시 견적

locale-chain-android를 활용한 로케일 폴백

pt-BR 같은 지역 로케일에 번역 키가 없으면 Android는 상위 로케일 pt를 먼저 확인하지 않고 기본 리소스 폴더로 바로 폴백해요.

Terminal
implementation("com.i18nagent:locale-chain-android:0.1.0")
Configuration
import com.i18nagent.localechain.LocaleChain

LocaleChain.configure(
    overrides = mapOf(
        "pt-BR" to listOf("pt", "en"),
        "zh-Hant-HK" to listOf("zh-Hant", "zh", "en"),
    )
)

지원 프레임워크와 75개 내장 체인의 전체 목록은 로케일 폴백 가이드에서 확인하세요. Learn more →

Android 현지화에 관해 자주 묻는 질문