Android 앱 다국어 지원 완벽 가이드 — strings.xml, AppCompatDelegate, Android 13 앱별 언어 설정과 RTL 대응

Android 앱을 처음 개발할 때는 대부분 한국어를 기준으로 화면을 구성합니다.

버튼 이름, 안내 메시지, 오류 문구 등을 XML이나 Kotlin 코드에 직접 작성하는 방식입니다.

하지만 앱을 해외 사용자에게 제공하려면 다국어 지원이 필요합니다.

예를 들어 다음과 같은 상황입니다.

  • 한국어와 영어를 지원하는 생물도감 앱

  • 해외 사용자도 사용하는 세계 버스 정보 앱

  • 한국어, 영어, 일본어를 지원하는 달력 앱

  • 국가별로 날짜와 숫자 표시 형식이 다른 정보 앱

  • 앱 내부에서 사용자가 직접 언어를 변경할 수 있는 앱

이때 단순히 화면의 문자열만 번역해서는 충분하지 않습니다.

Android 시스템 언어 설정과 앱 내부 언어 설정이 충돌할 수도 있고, 날짜와 숫자의 표시 방식이 달라질 수도 있습니다.

또한 아랍어처럼 오른쪽에서 왼쪽으로 읽는 언어를 지원한다면 레이아웃 방향까지 고려해야 합니다.

이번 글에서는 Kotlin + XML 기반 Android 앱에서 다국어 지원을 구현하는 방법을 단계별로 알아보겠습니다.

작성 기준: 2026년 10월. 아래 코드는 AndroidX AppCompat을 사용하는 View 기반 앱을 전제로 한 설명용 예제입니다. 라이브러리 버전과 플랫폼 동작은 실제 프로젝트에서 검증해야 합니다.


1. Android 다국어 지원의 기본 원리

Android는 리소스 디렉터리를 이용해 언어별 문자열을 관리할 수 있습니다.

예를 들어 다음과 같은 구조입니다.

app/src/main/res/
    ├── values/
    │   └── strings.xml
    │
    ├── values-en/
    │   └── strings.xml
    │
    └── values-ja/
        └── strings.xml

각 디렉터리의 의미는 다음과 같습니다.

디렉터리용도
values/기본 문자열 리소스
values-en/영어
values-ja/일본어
values-ko/한국어
values-zh-rCN/중국어 간체, 중국 지역
values-zh-rTW/중국어 번체, 대만 지역

Android는 기기의 언어 및 지역 설정을 기준으로 적절한 리소스를 선택합니다.

단, 해당 언어의 리소스가 없으면 기본 리소스를 사용합니다.

따라서 values/strings.xml은 반드시 정상적인 기본 문자열을 포함하도록 구성하는 것이 중요합니다.

2. strings.xml에 문자열 분리하기

먼저 Kotlin 코드나 XML에 직접 작성한 문자열을 리소스로 분리해야 합니다.

예를 들어 다음과 같은 XML 코드가 있다고 가정하겠습니다.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="조류 검색" />

이렇게 문자열을 직접 작성하면 다국어 관리가 어려워집니다.

다음과 같이 수정합니다.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/bird_search" />

그리고 res/values/strings.xml에 문자열을 정의합니다.

<resources>
    <string name="app_name">한국 조류 정보</string>
    <string name="bird_search">조류 검색</string>
    <string name="search">검색</string>
    <string name="favorite">좋아요</string>
    <string name="memo">메모</string>
    <string name="settings">설정</string>
</resources>

이렇게 하면 XML에서는 문자열 리소스의 이름만 참조하면 됩니다.


3. 영어 문자열 리소스 추가하기

영어를 지원하려면 values-en 디렉터리를 생성합니다.

res/
    ├── values/
    │   └── strings.xml
    │
    └── values-en/
        └── strings.xml

영어 리소스는 다음과 같이 작성할 수 있습니다.

<resources>
    <string name="app_name">Korean Birds</string>
    <string name="bird_search">Bird Search</string>
    <string name="search">Search</string>
    <string name="favorite">Favorites</string>
    <string name="memo">Memo</string>
    <string name="settings">Settings</string>
</resources>

기기 언어가 영어라면 Android가 영어 리소스를 선택할 수 있습니다.

반대로 영어 리소스에 특정 문자열이 없다면 기본 values 리소스를 사용할 수 있습니다.

다만 번역이 누락된 문자열이 많으면 하나의 화면에 한국어와 영어가 섞여 표시될 수 있습니다.

따라서 번역 리소스의 누락 여부를 확인해야 합니다.

4. Kotlin 코드에서 문자열 리소스 사용하기

Kotlin 코드에서도 문자열을 직접 작성하기보다 getString()을 사용하는 것이 좋습니다.

val title = getString(
    R.string.bird_search
)

Fragment에서는 다음과 같이 사용할 수 있습니다.

binding.textTitle.text =
    getString(R.string.bird_search)

ViewModel에서 Android Context를 직접 참조하지 않는 구조라면 문자열 리소스 ID를 UI 계층으로 전달하거나, UI 상태와 표시 문자열을 분리하는 방식을 고려할 수 있습니다.

예를 들어 ViewModel에서는 다음과 같은 상태를 관리합니다.

sealed interface SearchUiState {

    data object Idle : SearchUiState

    data object Loading : SearchUiState

    data class Success(
        val count: Int
    ) : SearchUiState

    data object NetworkError : SearchUiState
}

Fragment에서는 상태에 따라 문자열을 선택합니다.

binding.textStatus.text =
    when (val state = uiState) {

        SearchUiState.Idle ->
            getString(R.string.search)

        SearchUiState.Loading ->
            getString(R.string.loading)

        is SearchUiState.Success ->
            getString(
                R.string.search_result_count,
                state.count
            )

        SearchUiState.NetworkError ->
            getString(R.string.network_error)
    }

이 방식은 ViewModel을 테스트하기 쉽게 유지하면서 다국어 처리를 UI 계층에 집중시킬 수 있습니다.


5. 문자열에 숫자나 값을 포함하기

검색 결과 개수를 표시한다고 가정하겠습니다.

한국어에서는 다음과 같이 표시할 수 있습니다.

검색 결과 15건

영어에서는 다음과 같이 표시할 수 있습니다.

15 results found

이때 문자열을 직접 연결하는 방식은 피하는 것이 좋습니다.

// 권장하지 않는 방식
binding.textResult.text =
    "검색 결과 " + count + "건"

대신 문자열 포맷을 사용합니다.

한국어:

<string name="search_result_count">
    검색 결과 %1$d건
</string>

영어:

<string name="search_result_count">
    %1$d results found
</string>

Kotlin:

binding.textResult.text =
    getString(
        R.string.search_result_count,
        count
    )

%1$d는 첫 번째 정수 인자를 의미합니다.

이 방식을 사용하면 언어별 문장 구조가 달라도 자연스럽게 표현할 수 있습니다.

6. 복수형 문자열 처리하기

영어에서는 단수와 복수에 따라 문장이 달라질 수 있습니다.

예를 들어 다음과 같습니다.

1 bird
2 birds

이때 Android의 plurals 리소스를 사용할 수 있습니다.

res/values-en/strings.xml:

<resources>
    <plurals name="bird_count">
        <item quantity="one">%d bird</item>
        <item quantity="other">%d birds</item>
    </plurals>
</resources>

한국어 기본 리소스:

<resources>
    <plurals name="bird_count">
        <item quantity="other">%d마리</item>
    </plurals>
</resources>

Kotlin:

val text = resources.getQuantityString(
    R.plurals.bird_count,
    count,
    count
)

첫 번째 count는 어떤 복수형 규칙을 선택할지 결정하는 값입니다.

두 번째 count는 문자열의 %d에 들어가는 값입니다.

언어마다 복수형 규칙이 다르므로 영어의 one, other 규칙을 모든 언어에 그대로 적용해서는 안 됩니다.


7. Android 13의 앱별 언어 설정

Android 13(API 33)부터는 시스템 설정에서 앱별 언어를 선택할 수 있는 기능이 제공됩니다.

사용자는 기기의 전체 언어를 변경하지 않고 특정 앱에만 다른 언어를 적용할 수 있습니다.

예를 들어 다음과 같은 설정입니다.

시스템 언어: 한국어

앱 A: 한국어
앱 B: 영어
앱 C: 일본어

다만 앱별 언어 설정을 지원하려면 앱의 언어 리소스와 관련 구성을 올바르게 설정해야 합니다.

AndroidX AppCompat을 사용하면 이전 Android 버전까지 포함해 앱 언어 설정을 관리할 수 있습니다.

8. AppCompatDelegate로 앱 언어 변경하기

AppCompat에서는 다음 API를 사용할 수 있습니다.

AppCompatDelegate.setApplicationLocales(
    LocaleListCompat.forLanguageTags("en")
)

이 코드는 앱 언어를 영어로 설정하는 예제입니다.

한국어로 변경하려면 다음과 같이 사용할 수 있습니다.

AppCompatDelegate.setApplicationLocales(
    LocaleListCompat.forLanguageTags("ko")
)

일본어는 다음과 같습니다.

AppCompatDelegate.setApplicationLocales(
    LocaleListCompat.forLanguageTags("ja")
)

시스템 언어 설정을 따르도록 되돌리려면 빈 언어 목록을 전달합니다.

AppCompatDelegate.setApplicationLocales(
    LocaleListCompat.getEmptyLocaleList()
)

필요한 import는 다음과 같습니다.

import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat

언어 변경 시 Activity가 재생성되는 등 구성 변경이 발생할 수 있으므로 화면 상태 유지도 고려해야 합니다.

9. 언어 선택 화면 구현하기

설정 화면에서 사용자가 언어를 선택할 수 있도록 구성해 보겠습니다.

예를 들어 다음과 같은 Spinner를 사용한다고 가정하겠습니다.

<Spinner
    android:id="@+id/spinnerLanguage"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Kotlin 코드에서는 다음과 같이 구성할 수 있습니다.

val languages = listOf(
    "시스템 기본값",
    "한국어",
    "English",
    "日本語"
)

val languageTags = listOf(
    "",
    "ko",
    "en",
    "ja"
)

val adapter = ArrayAdapter(
    requireContext(),
    android.R.layout.simple_spinner_item,
    languages
)

adapter.setDropDownViewResource(
    android.R.layout.simple_spinner_dropdown_item
)

binding.spinnerLanguage.adapter = adapter

사용자가 언어를 선택했을 때는 다음과 같이 적용할 수 있습니다.

binding.spinnerLanguage.onItemSelectedListener =
    object : AdapterView.OnItemSelectedListener {

        override fun onItemSelected(
            parent: AdapterView<*>?,
            view: View?,
            position: Int,
            id: Long
        ) {

            val languageTag =
                languageTags[position]

            AppCompatDelegate
                .setApplicationLocales(
                    LocaleListCompat
                        .forLanguageTags(languageTag)
                )
        }

        override fun onNothingSelected(
            parent: AdapterView<*>?
        ) = Unit
    }

다만 이 코드는 기본적인 동작을 보여주는 예제입니다.

실제 앱에서는 초기 Spinner 설정 시 onItemSelected()가 호출되어 불필요한 언어 변경이 발생하지 않도록 처리해야 합니다.

또한 현재 적용된 언어를 먼저 확인하고, 사용자가 실제로 다른 언어를 선택했을 때만 변경하는 것이 좋습니다.


10. Android 12 이하에서 언어 설정 저장하기

Android 13 이상과 이전 버전에서는 앱 언어 설정을 저장하는 방식에 차이가 있습니다.

AppCompat에서는 Android 12 이하 기기의 앱 언어 설정을 자동 저장하도록 구성할 수 있습니다.

예를 들어 AndroidManifest.xml의 <application> 내부에 다음 서비스를 선언할 수 있습니다.

<service
    android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
    android:enabled="false"
    android:exported="false">

    <meta-data
        android:name="autoStoreLocales"
        android:value="true" />

</service>

이 설정을 사용하면 지원되는 AppCompat 환경에서 앱별 언어 설정의 영속성을 관리할 수 있습니다.

다만 자동 저장은 동기적인 저장 작업을 수반할 수 있으며, AppCompat 버전과 앱 초기화 방식에 따른 동작을 확인해야 합니다.

이미 별도의 언어 설정 저장 구조를 가지고 있다면 중복 저장으로 인한 충돌을 피해야 합니다.

11. 앱이 처음 실행될 때 언어 적용하기

앱의 언어를 직접 저장하고 복원하는 구조라면 초기화 시점을 주의해야 합니다.

예를 들어 SharedPreferences에 저장된 언어를 읽어 적용하는 방식입니다.

하지만 Android 13 이상에서는 플랫폼의 앱별 언어 설정과 충돌할 수 있습니다.

따라서 가능하면 AppCompat의 앱 언어 API를 중심으로 관리하고, 별도의 저장 구조가 꼭 필요한 경우에만 직접 구현하는 것이 좋습니다.

특히 기존 앱에서 새로운 AppCompat 언어 설정 방식으로 변경하는 경우에는 기존 사용자의 언어 설정을 한 번 이전하는 마이그레이션 정책이 필요할 수 있습니다.


12. 앱이 지원하는 언어 선언하기

Android 13 이상의 앱별 언어 설정 화면에서는 앱이 지원하는 언어 정보가 중요합니다.

지원 언어는 LocaleConfig를 통해 선언할 수 있습니다.

예를 들어 res/xml/locales_config.xml 파일을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<locale-config
    xmlns:android="http://schemas.android.com/apk/res/android">

    <locale android:name="ko" />
    <locale android:name="en" />
    <locale android:name="ja" />

</locale-config>

AndroidManifest.xml의 <application>에 다음과 같이 지정할 수 있습니다.

<application
    android:localeConfig="@xml/locales_config"
    ... >
</application>

또한 Android Gradle Plugin의 지원 버전에서는 앱 언어 설정에 필요한 LocaleConfig를 자동 생성하는 기능을 사용할 수 있습니다.

예를 들어 지원되는 AGP 환경에서는 다음과 같이 구성할 수 있습니다.

android {

    androidResources {
        generateLocaleConfig = true
    }
}

자동 생성 방식을 사용할 때는 수동으로 작성한 LocaleConfig와 중복되지 않도록 해야 합니다.

또한 자동 생성 시 실제로 포함되는 언어 목록을 확인하는 것이 좋습니다.

13. 앱 언어 변경 후 화면이 갱신되지 않는 문제

앱 언어를 변경했는데 일부 화면의 문자열이 이전 언어로 남아 있는 경우가 있습니다.

대표적인 원인은 다음과 같습니다.

  • 문자열을 코드에 직접 작성함

  • 이전 Context에서 문자열을 미리 가져와 저장함

  • Adapter가 이전 문자열을 계속 사용함

  • Singleton에서 문자열을 캐싱함

  • Activity 재생성 후 상태 복원이 제대로 이루어지지 않음

예를 들어 다음과 같은 코드는 주의해야 합니다.

object AppMessages {

    var title: String = ""
}

앱 시작 시 문자열을 한 번만 저장하고 계속 사용하면 언어 변경 이후에도 이전 문자열이 남을 수 있습니다.

가능하면 UI가 표시되는 시점에 현재 Context를 기준으로 문자열 리소스를 가져오는 것이 좋습니다.


14. 날짜 형식의 다국어 처리

날짜는 국가와 언어에 따라 표시 방식이 달라집니다.

예를 들어 같은 날짜도 다음과 같이 표현할 수 있습니다.

언어·지역표시 예시
한국어2026. 10. 30.
영어(미국)Oct 30, 2026
영어(영국)30 Oct 2026
일본어2026年10月30日

날짜를 직접 문자열로 조합하면 다국어 처리가 어려워집니다.

예를 들어 다음과 같은 코드는 권장하지 않습니다.

val text =
    "$year-$month-$day"

사용자에게 보여주는 날짜는 지역 설정에 맞는 Formatter를 사용하는 것이 좋습니다.

val formatter = DateFormat.getDateInstance(
    DateFormat.MEDIUM,
    Locale.getDefault()
)

val text = formatter.format(
    Date()
)

다만 Locale.getDefault()는 일반적으로 시스템 기본 Locale을 기준으로 합니다.

앱 내부에서 별도의 언어를 설정한 경우에는 앱의 현재 리소스 구성에 맞는 Locale을 사용하는 것이 좋습니다.

예를 들어 다음과 같이 가져올 수 있습니다.

val locale =
    resources.configuration.locales[0]

이 방식은 Android 7.0(API 24) 이상에서 사용할 수 있습니다.

15. 숫자와 소수점 표시

숫자도 국가별로 표시 방식이 다릅니다.

예를 들어 다음과 같습니다.

한국: 1,234.56
독일: 1.234,56

숫자를 표시할 때는 NumberFormat을 사용하는 것이 좋습니다.

val locale =
    resources.configuration.locales[0]

val formatter =
    NumberFormat.getNumberInstance(
        locale
    )

val text = formatter.format(
    1234.56
)

이렇게 하면 해당 Locale에 맞는 숫자 형식으로 표시할 수 있습니다.

단, API 요청이나 데이터베이스 저장에 사용하는 숫자 형식은 사용자에게 표시하는 형식과 구분해야 합니다.

예를 들어 서버 API가 소수점 구분자로 .을 요구한다면 사용자의 Locale에 따라 숫자를 변환해서 전송해서는 안 됩니다.

데이터 저장·전송 형식과 화면 표시 형식은 분리하는 것이 중요합니다.


16. RTL 레이아웃이란?

RTL은 Right-To-Left의 약자입니다.

아랍어와 히브리어처럼 오른쪽에서 왼쪽으로 읽는 언어에서 사용하는 레이아웃 방향입니다.

일반적인 한국어·영어 화면은 LTR(Left-To-Right)입니다.

LTR
[아이콘] 제목          [버튼]

RTL
[버튼]          제목 [아이콘]

RTL을 지원하려면 단순히 문자열을 번역하는 것뿐 아니라 화면 배치 방향도 고려해야 합니다.

17. left/right 대신 start/end 사용하기

XML 레이아웃에서는 가능한 경우 left, right보다 start, end를 사용하는 것이 좋습니다.

예를 들어 다음과 같은 방식입니다.

android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"

RTL을 고려하면 다음과 같이 작성할 수 있습니다.

android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"

Padding도 마찬가지입니다.

android:paddingStart="16dp"
android:paddingEnd="16dp"

ConstraintLayout을 사용하는 경우에도 다음과 같은 속성을 활용할 수 있습니다.

app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

이렇게 하면 레이아웃 방향에 따라 배치를 조정하기 쉽습니다.

18. RTL 지원 설정하기

AndroidManifest.xml에서 RTL 지원을 선언할 수 있습니다.

<application
    android:supportsRtl="true"
    ... >
</application>

하지만 이 설정만으로 모든 화면이 RTL에 적합하게 변경되는 것은 아닙니다.

다음 요소를 별도로 확인해야 합니다.

  • 방향을 나타내는 화살표 아이콘

  • 뒤로 가기 아이콘

  • RecyclerView 아이템 배치

  • ViewPager2 페이지 방향

  • BottomNavigationView 아이콘과 텍스트

  • 사용자 입력 텍스트의 방향

  • 이미지 내부에 포함된 문자

특히 방향성이 있는 아이콘은 RTL에서 반전해야 할 수 있습니다.

반대로 로고나 사진처럼 반전하면 안 되는 이미지도 있습니다.


19. SQLite 데이터베이스의 다국어 처리

SQLite를 사용하는 정보 앱에서는 UI 문자열과 데이터베이스에 저장된 콘텐츠를 구분해야 합니다.

예를 들어 조류 정보 앱에 다음과 같은 테이블이 있다고 가정하겠습니다.

CREATE TABLE korean_birds (
    id INTEGER PRIMARY KEY,
    name_ko TEXT,
    name_en TEXT,
    scientific_name TEXT
);

한국어와 영어 이름을 함께 저장하는 구조입니다.

사용자 언어에 따라 표시할 컬럼을 선택할 수 있습니다.

val locale =
    resources.configuration.locales[0]

val language =
    locale.language

val displayName =
    when (language) {

        "ko" -> bird.nameKo

        "en" -> bird.nameEn

        else -> bird.nameEn
    }

다만 이 방식은 언어가 많아질수록 컬럼이 증가합니다.

지원 언어가 많다면 번역 테이블을 별도로 구성하는 방법을 고려할 수 있습니다.

20. SQLite 번역 테이블 분리하기

다국어 데이터를 관리하기 위한 예시 구조입니다.

CREATE TABLE birds (
    id INTEGER PRIMARY KEY,
    scientific_name TEXT NOT NULL
);

CREATE TABLE bird_translations (
    bird_id INTEGER NOT NULL,
    language_code TEXT NOT NULL,
    display_name TEXT NOT NULL,

    PRIMARY KEY (
        bird_id,
        language_code
    ),

    FOREIGN KEY (bird_id)
        REFERENCES birds(id)
);

데이터는 다음과 같이 저장할 수 있습니다.

INSERT INTO birds
(id, scientific_name)
VALUES
(1, 'Passer montanus');

INSERT INTO bird_translations
(bird_id, language_code, display_name)
VALUES
(1, 'ko', '참새'),
(1, 'en', 'Eurasian Tree Sparrow'),
(1, 'ja', 'スズメ');

언어별 데이터를 조회할 때는 JOIN을 사용할 수 있습니다.

SELECT
    b.id,
    b.scientific_name,
    t.display_name

FROM birds b

LEFT JOIN bird_translations t
    ON b.id = t.bird_id
    AND t.language_code = ?

WHERE b.id = ?;

이 구조는 언어별 번역을 추가하기 쉽다는 장점이 있습니다.

다만 요청한 언어의 번역이 없는 경우를 대비해 기본 언어로 대체하는 정책이 필요합니다.

21. 번역이 없을 때 기본 언어 사용하기

예를 들어 일본어 번역이 없는 경우 영어 이름을 표시하도록 구성할 수 있습니다.

SELECT
    b.id,
    b.scientific_name,

    COALESCE(
        requested.display_name,
        fallback.display_name,
        b.scientific_name
    ) AS display_name

FROM birds b

LEFT JOIN bird_translations requested
    ON requested.bird_id = b.id
    AND requested.language_code = ?

LEFT JOIN bird_translations fallback
    ON fallback.bird_id = b.id
    AND fallback.language_code = 'en'

WHERE b.id = ?;

이렇게 하면 요청한 언어의 번역이 없더라도 기본 영어 이름이나 학명을 표시할 수 있습니다.

도감형 앱에서는 번역 누락으로 인해 이름이 비어 보이는 문제를 줄일 수 있습니다.


22. Retrofit API 응답과 다국어 처리

서버 API에서 다국어 데이터를 제공하는 경우에는 언어 정보를 함께 전달할 수 있습니다.

예를 들어 다음과 같은 요청입니다.

GET /api/birds?lang=en

또는 HTTP Accept-Language 헤더를 사용할 수 있습니다.

Accept-Language: en

Retrofit에서는 다음과 같이 구현할 수 있습니다.

interface BirdApi {

    @GET("api/birds")
    suspend fun getBirds(
        @Query("lang") language: String
    ): List<BirdDto>
}

앱에서 현재 선택한 언어를 전달합니다.

val locale =
    resources.configuration.locales[0]

val birds = api.getBirds(
    locale.language
)

단, 서버가 지원하는 언어 코드와 앱의 언어 코드를 일치시켜야 합니다.

또한 en-US, en-GB처럼 지역별 차이가 필요한 경우 단순히 locale.language만 전달하면 정보가 부족할 수 있습니다.

서버 API의 언어 처리 정책에 맞춰 언어 태그를 전달하는 것이 좋습니다.

23. 다국어 캐시 관리

서버에서 언어별 데이터를 받아 SQLite에 저장하는 경우 캐시 구조도 고려해야 합니다.

예를 들어 한국어 데이터를 받아 저장한 뒤 사용자가 영어로 변경했다고 가정하겠습니다.

이때 한국어 캐시를 그대로 표시하면 앱 언어와 콘텐츠 언어가 달라질 수 있습니다.

따라서 캐시 키에 언어 코드를 포함하는 것이 좋습니다.

birds_ko
birds_en
birds_ja

또는 데이터베이스 테이블에 language_code 컬럼을 포함할 수 있습니다.

CREATE TABLE bird_cache (
    bird_id INTEGER NOT NULL,
    language_code TEXT NOT NULL,
    display_name TEXT NOT NULL,

    PRIMARY KEY (
        bird_id,
        language_code
    )
);

이렇게 하면 언어별 캐시를 구분해서 관리할 수 있습니다.


24. Google Play 스토어 다국어 등록

앱 내부에서 여러 언어를 지원하더라도 Google Play 스토어 등록 정보가 한국어로만 작성되어 있다면 해외 사용자에게 앱의 기능을 충분히 설명하기 어렵습니다.

따라서 Google Play Console에서도 국가 및 언어별 스토어 등록 정보를 관리하는 것이 좋습니다.

대표적인 항목은 다음과 같습니다.

  • 앱 이름

  • 짧은 설명

  • 자세한 설명

  • 스크린샷

  • 프로모션 이미지

  • 업데이트 설명

예를 들어 영어 스토어 등록 정보는 다음과 같이 작성할 수 있습니다.

App name:
Korean Birds

Short description:
Explore bird species found in Korea.

Full description:
Discover bird species, search by name,
and save your favorite birds.

단, 실제 앱에서 제공하지 않는 기능을 스토어 설명에 포함해서는 안 됩니다.

또한 앱 내부의 언어 지원과 스토어 등록 정보의 언어 지원은 별도로 관리해야 합니다.

25. 다국어 스크린샷 준비하기

Google Play 스토어에서는 언어별 스크린샷을 준비하는 것이 좋습니다.

예를 들어 다음과 같은 방식입니다.

한국어 스토어
    └── 한국어 화면 스크린샷

영어 스토어
    └── 영어 화면 스크린샷

일본어 스토어
    └── 일본어 화면 스크린샷

사용자가 스토어에서 본 화면과 실제 앱의 언어가 일치하면 기능을 이해하기 쉽습니다.

다만 모든 국가에 별도의 스크린샷을 준비해야 하는 것은 아닙니다.

지원 언어와 주요 배포 국가를 기준으로 우선순위를 정하는 것이 현실적입니다.


26. 다국어 앱에서 자주 발생하는 문제

다국어 지원을 구현할 때는 다음과 같은 문제가 발생할 수 있습니다.

1. 번역 문자열이 누락됨

일부 화면에 기본 언어가 표시됩니다.

2. 문자열이 길어져 레이아웃이 깨짐

영어, 독일어 등은 한국어보다 문자열이 길어질 수 있습니다.

3. 언어 변경 후 화면이 갱신되지 않음

이전 Context나 Adapter에 저장된 문자열이 남아 있을 수 있습니다.

4. 날짜 형식이 앱 언어와 다름

시스템 Locale을 사용했기 때문일 수 있습니다.

5. SQLite 콘텐츠가 번역되지 않음

UI 문자열과 DB 콘텐츠의 다국어 처리는 별도입니다.

6. RTL에서 화면 배치가 어색함

left/right 속성이나 방향성 아이콘을 그대로 사용했을 수 있습니다.

7. Product Flavor별 번역 리소스가 섞임

공통 리소스와 Flavor 전용 리소스를 잘못 배치했을 수 있습니다.

27. Product Flavor별 다국어 리소스 관리

하나의 프로젝트에서 여러 앱을 관리한다면 Flavor별 문자열 리소스를 분리할 수 있습니다.

예를 들어 다음과 같은 구조입니다.

app/src/
    ├── main/
    │   └── res/
    │       ├── values/
    │       └── values-en/
    │
    ├── bird/
    │   └── res/
    │       ├── values/
    │       └── values-en/
    │
    └── fish/
        └── res/
            ├── values/
            └── values-en/

공통 문자열은 main에 배치하고, 앱별 이름이나 설명은 Flavor 디렉터리에 배치할 수 있습니다.

예를 들어 Bird Flavor에서는 다음과 같이 작성할 수 있습니다.

<resources>
    <string name="app_name">한국 조류 정보</string>
</resources>

Fish Flavor에서는 다음과 같이 작성할 수 있습니다.

<resources>
    <string name="app_name">한국 어류 정보</string>
</resources>

이렇게 하면 동일한 코드를 사용하면서 앱별 문자열을 구분할 수 있습니다.

단, 리소스 병합 우선순위와 번역 누락 여부를 실제 빌드 Variant별로 확인해야 합니다.


28. 다국어 테스트 방법

다국어 앱에서는 단순히 언어가 변경되는지만 확인해서는 안 됩니다.

다음 항목을 테스트하는 것이 좋습니다.

테스트 항목확인 내용
시스템 언어 변경앱이 적절한 리소스를 선택하는가
앱 내부 언어 변경즉시 또는 재생성 후 정상 반영되는가
앱 재실행선택한 언어가 유지되는가
번역 누락기본 언어로 정상 대체되는가
긴 문자열버튼이나 텍스트가 잘리지 않는가
날짜·숫자Locale에 맞게 표시되는가
RTL화면 방향이 정상적인가
SQLite 데이터언어별 콘텐츠가 정상 표시되는가
Retrofit서버에 올바른 언어 코드가 전달되는가
Product Flavor앱별 번역 리소스가 정상 포함되는가

특히 작은 화면과 큰 글꼴 설정에서 테스트하는 것이 중요합니다.

번역된 문자열이 길어지면 화면이 깨지거나 버튼이 잘리는 문제가 발생할 수 있기 때문입니다.

29. Android Studio에서 번역 누락 확인하기

Android Studio에서는 Translations Editor를 이용해 문자열 리소스를 관리할 수 있습니다.

또한 Android Lint를 통해 번역 누락이나 문자열 리소스 관련 문제를 확인할 수 있습니다.

예를 들어 다음 명령어를 사용할 수 있습니다.

./gradlew lintDebug

Product Flavor가 있다면 다음과 같이 실행할 수 있습니다.

./gradlew lintBirdDebug

다만 Lint 결과가 모든 번역 품질 문제를 발견하는 것은 아닙니다.

문법, 문화적 표현, 화면 내 문맥에 맞는 번역인지는 별도로 검토해야 합니다.

30. 다국어 지원 체크리스트

  • 기본 values/strings.xml 구성

  • Kotlin·XML 하드코딩 문자열 제거

  • 언어별 values-* 리소스 추가

  • 문자열 포맷 인자 적용

  • 복수형 plurals 처리

  • AppCompatDelegate 언어 변경 구현

  • Android 13 앱별 언어 설정 검토

  • LocaleConfig 설정

  • Android 12 이하 언어 설정 저장 방식 확인

  • 언어 변경 후 Activity 상태 복원

  • 날짜·숫자 Locale 처리

  • RTL 레이아웃 지원 검토

  • SQLite 콘텐츠 다국어 처리

  • 서버 API 언어 코드 전달

  • 언어별 캐시 분리

  • Google Play 스토어 현지화

  • 다국어 스크린샷 준비

  • 작은 화면 및 큰 글꼴 테스트

  • Android Lint 번역 누락 검사

  • Product Flavor별 리소스 확인

31. 마무리

Android 앱 다국어 지원은 단순히 문자열을 번역하는 작업이 아닙니다.

앱의 언어 설정, 화면 구성, 데이터베이스 콘텐츠, 서버 API, 날짜와 숫자 형식까지 함께 고려해야 합니다.

특히 Kotlin + XML 기반 Android 앱에서는 다음 순서로 접근하는 것이 좋습니다.

  1. 화면 문자열을 strings.xml로 분리합니다.

  2. 지원 언어별 리소스를 추가합니다.

  3. AppCompatDelegate로 앱 언어 변경을 구현합니다.

  4. Android 13 앱별 언어 설정을 지원합니다.

  5. 날짜와 숫자 형식을 Locale에 맞게 처리합니다.

  6. SQLite와 서버 콘텐츠의 다국어 구조를 설계합니다.

  7. Google Play 스토어 등록 정보도 현지화합니다.

  8. 실제 기기에서 언어 변경과 화면 배치를 테스트합니다.

처음부터 많은 언어를 지원할 필요는 없습니다.

한국어와 영어부터 시작해 번역 리소스와 데이터 구조를 정리한 뒤 필요에 따라 지원 언어를 늘리는 것이 현실적입니다.

Android 다국어 지원의 핵심은 번역 문자열의 개수를 늘리는 것이 아니라, 사용자가 선택한 언어에 맞춰 앱 전체가 일관되게 동작하도록 만드는 것입니다.


참고 자료

태그: Android, Kotlin, XML, 다국어, Localization, Internationalization, strings.xml, AppCompatDelegate, LocaleConfig, Android 13, RTL, SQLite, Retrofit, Google Play, Product Flavor