Skip to main content

Hướng dẫn toàn diện về bản địa hóa ứng dụng Android

Từ strings.xml đến siêu dữ liệu Play Store: bản địa hóa ứng dụng Android bằng Kotlin, Jetpack Compose, Fastlane và công nghệ dịch AI tự động.

1

Thiết lập dự án Android để bản địa hóa

Android dùng quy ước dựa trên thư mục để bản địa hóa. Các chuỗi mặc định nằm trong res/values/strings.xml còn chuỗi đã dịch nằm trong các thư mục riêng cho từng ngôn ngữ như res/values-de/, res/values-ja/ và các thư mục tương tự.

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
Thư mục res/values/ là ngôn ngữ dự phòng. Nếu thiếu một chuỗi trong thư mục riêng cho ngôn ngữ, Android sẽ tải chuỗi mặc định. Tuy nhiên, nếu chuỗi đó không có trong tệp mặc định, ứng dụng sẽ gặp sự cố ở các ngôn ngữ không được hỗ trợ.
2

Tạo strings.xml

Tài nguyên chuỗi Android dùng XML với các phần tử '<string>' bên trong phần tử gốc '<resources>'. Dùng %s cho phần giữ chỗ chuỗi, %d cho số nguyên và %1$s/%2$s cho các đối số theo vị trí để người dịch có thể sắp xếp lại.

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>
Dấu nháy đơn chưa thoát khiến trình phân tích cú pháp XML âm thầm gặp sự cố. Hãy dùng \' hoặc đặt giá trị trong dấu nháy kép. Ngoài ra, nếu thiếu chuỗi trong values/strings.xml mặc định, ứng dụng sẽ dừng đột ngột thay vì chuyển sang phương án dự phòng như 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

Xử lý số nhiều

Android dùng các phần tử '&lt;plurals&gt;' với thuộc tính số lượng: zero, one, two, few, many, other. Mỗi ngôn ngữ đích có thể cần các danh mục khác nhau: tiếng Ả Rập dùng cả 6, tiếng Nga cần few/many còn tiếng Nhật chỉ dùng 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
) -->
Thuộc tính quantity chọn dạng số nhiều theo quy tắc CLDR dành cho ngôn ngữ của thiết bị. Luôn thêm 'other' làm phương án dự phòng vì đây là danh mục duy nhất chắc chắn có trong mọi ngôn ngữ.
4

Dùng trong mã: Kotlin và Jetpack Compose

Android truyền thống dùng getString(R.string.key) và resources.getQuantityString(). Jetpack Compose dùng stringResource(R.string.key) và pluralStringResource(). Cả hai đều phân giải bản dịch phù hợp theo ngôn ngữ thiết bị trong thời gian chạy.

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): tham số count được truyền hai lần. Lần đầu chọn dạng số nhiều còn lần sau là đối số định dạng. Thiếu count thứ hai là lỗi số nhiều phổ biến nhất trong Compose.
5

Mảng chuỗi và chuỗi đã định dạng

Dùng '&lt;string-array&gt;' cho danh sách có thứ tự (ví dụ: tùy chọn thả xuống, bước làm quen). Dùng đối số định dạng theo vị trí (%1$s, %2$d) trong chuỗi đã định dạng để người dịch có thể sắp xếp lại từ mà không phá vỡ cấu trúc câu.

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>
Các đối số theo vị trí như %1$s cho phép người dịch tự do sắp xếp lại tham số. 'Hello %1$s, you have %2$d items' có thể thành '%2$d items for %1$s' trong ngôn ngữ có trật tự từ khác mà không cần thay đổi mã.
6

Bản địa hóa siêu dữ liệu Google Play bằng Fastlane

Dùng lệnh supply của Fastlane để quản lý siêu dữ liệu Play Store gồm tiêu đề, mô tả ngắn, mô tả đầy đủ và nhật ký thay đổi dưới dạng tệp văn bản thuần được sắp xếp theo ngôn ngữ trong kho lưu trữ.

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
Bản địa hóa trang thông tin Play Store giúp tăng hơn 30% lượt tải xuống ở các thị trường không dùng tiếng Anh. Tiêu đề, mô tả ngắn và mô tả đầy đủ đều được lập chỉ mục tìm kiếm nên dịch chúng là hoạt động bản địa hóa mang lại lợi tức cao nhất.
Google Play

Tự động bản địa hóa trang thông tin Play Store

Bỏ qua thao tác sao chép và dán thủ công. Dịch tiêu đề, mô tả và ghi chú phát hành trên Play Store sang hơn 175 ngôn ngữ trong khi vẫn tuân thủ giới hạn ký tự.

Khám phá tính năng tích hợp Google Play
7

Kiểm thử bản địa hóa

Kiểm thử bằng cách chuyển đổi ngôn ngữ trên trình giả lập, xem trước Compose với LocaleList tùy chỉnh và dùng ngôn ngữ giả trong Developer Options. Dùng resConfigs trong Gradle để loại bỏ tài nguyên ngôn ngữ không mong muốn khỏi thư viện bên thứ ba.

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
Kiểm thử bằng tiếng Đức (văn bản dài thêm khoảng 30%) và tiếng Nhật (ngắn đi khoảng 50%) để phát hiện vấn đề bố cục. Bật ngôn ngữ giả (en-XA cho ký tự có dấu, ar-XB cho RTL) trong Developer Options để kiểm thử bố cục ở cường độ cao mà không cần bản dịch thật.

Tự động đảm bảo chất lượng bản dịch

Dùng i18n-validate để phát hiện khóa bị thiếu, phần giữ chỗ bị hỏng và lỗi số nhiều trước khi phát hành. Dùng i18n-pseudo để kiểm thử giao diện bằng bản dịch giả trước khi có bản dịch thật.
8

Tự động dịch

Dùng AI để dịch strings.xml, số nhiều, mảng chuỗi và siêu dữ liệu Fastlane Supply. Tự động dịch cả chuỗi trong ứng dụng lẫn siêu dữ liệu Play Store để bản địa hóa toàn diện.

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 xử lý ký tự thoát XML của Android, giữ nguyên dấu translatable="false", tuân thủ danh mục số nhiều CLDR của từng ngôn ngữ đích và bảo toàn đối số định dạng theo vị trí.
JetBrains

Đã có trình bổ trợ Android Studio

Dịch tài nguyên XML của Android ngay trong IDE bằng trình bổ trợ i18n Agent cho IntelliJ / Android Studio.

Install
+

Nội dung bổ sung: ngôn ngữ dự phòng thông minh với LocaleChain

Hệ điều hành kiểm soát cơ chế tài nguyên dự phòng của Android. Khi thiếu bản dịch pt-BR, Android bỏ qua hoàn toàn pt-PT và hiển thị tiếng Anh. LocaleChain chặn hoạt động tra cứu chuỗi và duyệt qua chuỗi dự phòng có thể cấu hình để người dùng trong khu vực thấy bản dịch gần nhất hiện có.

LocaleChain cho Android là một thư viện Kotlin mã nguồn mở. Xem trên 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"))
)

Lỗi thường gặp

Thiếu chuỗi mặc định gây sự cố

Khác với iOS hiển thị khóa thô, Android gặp ResourceNotFoundException nếu thiếu chuỗi trong res/values/strings.xml mặc định. Luôn đảm bảo mọi khóa đều có trong tệp mặc định.

Tách ngôn ngữ trong App Bundle làm hỏng tính năng chuyển đổi trong ứng dụng

Google Play App Bundles tách APK theo ngôn ngữ nên người dùng chỉ nhận chuỗi dành cho ngôn ngữ thiết bị. Nếu cung cấp tính năng chuyển đổi ngôn ngữ trong ứng dụng, hãy thêm bundle '{ language { enableSplit = false } }' vào build.gradle.kts.

Bố cục RTL bị hỏng

Nguyên nhân là dùng left/right thay cho start/end trong bố cục hoặc thiếu android:supportsRtl="true" trong AndroidManifest.xml. Dùng Refactor > Add RTL Support của Android Studio để tự động chuyển đổi bố cục hiện có.

Tài nguyên thư viện gây nhiễu

Thư viện bên thứ ba đóng gói các tệp values-XX/strings.xml riêng khiến Android cho rằng ứng dụng hỗ trợ những ngôn ngữ chưa được dịch. Dùng resConfigs trong build.gradle.kts để chỉ đưa vào các ngôn ngữ mà bạn thực sự dịch.

Cấu trúc tệp đề xuất

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

Dùng thử i18n Agent ngay

Thả tệp bản dịch của bạn vào đây

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

hoặc nhấp để duyệt

Ngôn ngữ đích

Không cần đăng kýBáo giá tức thì

Ngôn ngữ dự phòng với locale-chain-android

Khi thiếu khóa dịch trong một ngôn ngữ khu vực như pt-BR, Android chuyển thẳng đến thư mục tài nguyên mặc định thay vì kiểm tra ngôn ngữ mẹ pt trước.

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"),
    )
)

Xem Hướng dẫn về ngôn ngữ dự phòng để biết danh sách đầy đủ các framework được hỗ trợ và 75 chuỗi tích hợp sẵn. Learn more →

Câu hỏi thường gặp về bản địa hóa Android