Skip to main content

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

Từ Localizable.strings đến siêu dữ liệu App Store: bản địa hóa app iOS bằng Xcode, SwiftUI, Fastlane và công nghệ dịch AI tự động.

1

Bật tính năng bản địa hóa trong Xcode

Mở phần cài đặt dự án Xcode, chuyển đến Info > Localizations rồi thêm các ngôn ngữ bạn muốn hỗ trợ. Xcode tự động tạo thư mục .lproj cho từng ngôn ngữ.

Xcode Project Settings
// In Xcode:
// 1. Select your project in the navigator
// 2. Go to Info tab > Localizations
// 3. Click + to add languages (e.g., German, Japanese)
// 4. Select which files to localize
//
// Xcode creates .lproj directories automatically:
// en.lproj/Localizable.strings
// de.lproj/Localizable.strings
// ja.lproj/Localizable.strings
Base localization tách giao diện người dùng khỏi các chuỗi. Khi bạn thêm ngôn ngữ, Xcode đề xuất tạo phiên bản đã bản địa hóa của storyboard, XIB và tệp chuỗi.
2

Tạo Localizable.strings

Tệp bản địa hóa iOS tiêu chuẩn sử dụng các cặp khóa-giá trị phân tách bằng dấu bằng và mỗi dòng kết thúc bằng dấu chấm phẩy. Đặt tệp trong thư mục Base.lproj dành cho ngôn ngữ nguồn.

Base.lproj/Localizable.strings
// Base.lproj/Localizable.strings

"welcome_title" = "Welcome to MyApp";
"login_button" = "Sign In";
"settings_label" = "Settings";
"greeting" = "Hello, %@!";    // %@ = string placeholder
"item_count" = "%d items";     // %d = integer placeholder
Thiếu dấu chấm phẩy gây lỗi ngầm — tệp vẫn tải mà không báo lỗi nhưng bản dịch trống. Đồng thời, hãy bảo đảm bạn đã thêm tệp vào giai đoạn dựng Copy Bundle Resources của target, nếu không app bundle sẽ không chứa tệp.
Common .strings Mistakes
// ❌ Common mistakes in .strings files:

// Missing semicolon — file loads but translations are empty
"welcome_title" = "Welcome"

// Unescaped quotes — causes parse error
"message" = "Click "here" to continue";

// ✅ Correct versions:
"welcome_title" = "Welcome";
"message" = "Click \"here\" to continue";
3

Chuyển sang String Catalogs (Xcode 15+)

String Catalogs (.xcstrings) là giải pháp hiện đại của Apple thay thế các tệp .strings. Công cụ này cung cấp trình chỉnh sửa trực quan trong Xcode, tự động trích xuất chuỗi từ các view SwiftUI và tích hợp sẵn khả năng hỗ trợ số nhiều.

Localizable.xcstrings
// Xcode 15+ String Catalog (Localizable.xcstrings)
// Xcode automatically extracts strings from your code
// and manages translations in a visual editor.

// In SwiftUI, strings are automatically localizable:
Text("Welcome to MyApp")
Text("Hello, \(userName)!")

// Mark strings explicitly:
let title = String(localized: "welcome_title")
String Catalogs lưu TẤT CẢ ngôn ngữ trong một tệp JSON .xcstrings duy nhất. Khi nhiều thành viên cùng thêm chuỗi, nhóm thường xuyên gặp xung đột hợp nhất Git. Với dự án lớn, hãy cân nhắc dùng một catalog cho mỗi mô-đun.
4

Dùng chuỗi đã bản địa hóa trong SwiftUI và UIKit

View Text của SwiftUI tự động bản địa hóa chuỗi ký tự. UIKit dùng NSLocalizedString. Trên iOS 16+, API String(localized:comment:) hiện đại cung cấp cú pháp gọn hơn và tích hợp sẵn khả năng hỗ trợ của trình biên dịch.

ContentView.swift
import SwiftUI

struct ContentView: View {
    let userName: String

    var body: some View {
        VStack {
            // ✅ SwiftUI auto-localizes string literals
            Text("welcome_title")

            // ⚠️ This does NOT localize (String interpolation)
            // Text("Hello, \(userName)")

            // ✅ Use String(localized:) for dynamic strings
            Text(String(localized: "greeting \(userName)"))

            // ✅ UIKit style (works everywhere)
            let title = NSLocalizedString(
                "settings_label",
                comment: "Settings screen title"
            )

            // ✅ Modern API (iOS 16+)
            let modern = String(
                localized: "welcome_title",
                comment: "Main screen title"
            )
        }
    }
}
Text("Hello \(name)") không báo lỗi khi không thể bản địa hóa nếu name là biến String thuần túy. Phép nội suy chuỗi của SwiftUI tạo LocalizedStringKey nhưng chỉ nội suy đúng một số kiểu nhất định (Int, Double, v.v.). Với biến String, trước tiên hãy dùng String(localized:) để tạo chuỗi đã bản địa hóa.
5

Xử lý số nhiều

iOS dùng tệp .stringsdict cho quy tắc số nhiều và hỗ trợ mọi nhóm số nhiều CLDR: zero, one, two, few, many, other. String Catalogs xử lý số nhiều bằng trình chỉnh sửa trực quan trong Xcode — đơn giản hơn nhiều so với viết XML stringsdict thủ công.

Localizable.stringsdict
<!-- Localizable.stringsdict -->
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>items_count</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@count@</string>
        <key>count</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>zero</key>
            <string>No items</string>
            <key>one</key>
            <string>%d item</string>
            <key>other</key>
            <string>%d items</string>
        </dict>
    </dict>
</dict>
</plist>

// Usage in Swift:
String(format: NSLocalizedString("items_count", comment: ""),
       itemCount)
Các ngôn ngữ như tiếng Ả Rập có 6 dạng số nhiều, tiếng Nga có 3 và tiếng Nhật có 1. Luôn xác định mọi nhóm CLDR mà ngôn ngữ đích cần. Trình chỉnh sửa số nhiều trực quan của String Catalogs giúp việc này dễ dàng hơn.
6

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

Dùng công cụ deliver của Fastlane để lưu siêu dữ liệu App Store — tên app, phụ đề, mô tả, từ khóa và ghi chú phát hành — dưới dạng tệp văn bản thuần túy được sắp xếp theo locale và quản lý phiên bản trong kho lưu trữ.

Terminal
# Install Fastlane
$ gem install fastlane

# Initialize deliver for App Store metadata
$ fastlane deliver init

# Directory structure created:
# fastlane/metadata/
# ├── en-US/
# │   ├── name.txt            # App name (30 chars)
# │   ├── subtitle.txt        # Subtitle (30 chars)
# │   ├── description.txt     # Full description
# │   ├── keywords.txt        # Search keywords (100 chars)
# │   ├── release_notes.txt   # What's New
# │   └── promotional_text.txt
# ├── de-DE/
# │   └── ...
# └── ja/
#     └── ...

# Push metadata to App Store Connect:
$ fastlane deliver
Bản địa hóa chéo: App Store tại Mỹ lập chỉ mục cả từ khóa tiếng Anh và tiếng Tây Ban Nha. Bản địa hóa siêu dữ liệu sang tiếng Tây Ban Nha giúp bạn tiếp cận lượt tìm kiếm của người dùng Mỹ gốc Latinh mà không cần nhắm đến một thị trường riêng.
7

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

Kiểm thử nội dung đã bản địa hóa mà không cần đổi ngôn ngữ thiết bị. Dùng chế độ ghi đè scheme của Xcode để chạy app bằng bất kỳ ngôn ngữ nào, bản xem trước SwiftUI với môi trường locale và XCUITest kèm đối số khởi chạy để kiểm thử tự động.

Testing Localization
// 1. Xcode Scheme Override:
// Edit Scheme > Run > Options > App Language > Choose language

// 2. SwiftUI Preview with locale:
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environment(\.locale, Locale(identifier: "de"))

        ContentView()
            .environment(\.locale, Locale(identifier: "ja"))

        ContentView()
            .environment(\.locale, Locale(identifier: "ar"))
    }
}

// 3. XCUITest with language override:
let app = XCUIApplication()
app.launchArguments += ["-AppleLanguages", "(de)"]
app.launchArguments += ["-AppleLocale", "de_DE"]
app.launch()
Kiểm thử bằng tiếng Đức (chuỗi dài hơn tiếng Anh khoảng 30%) và tiếng Nhật (chuỗi ngắn hơn khoảng 50%) để sớm phát hiện vấn đề về bố cục. Dùng tính năng bản địa hóa giả của Xcode để kiểm thử giới hạn bố cục mà không cần bản dịch thật.

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

Dùng i18n-validate để phát hiện khóa bị thiếu, placeholder bị hỏng và vấn đề về 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 các tệp .strings, .xcstrings và siêu dữ liệu Fastlane. Tự động dịch cả chuỗi trong app lẫn siêu dữ liệu App Store để bản địa hóa toàn diện.

Terminal
# Translate .strings files
> Translate Base.lproj/Localizable.strings
  to Japanese, German, and Spanish

# Translate App Store metadata too
> Translate fastlane/metadata/en-US/
  to de-DE, ja, es-MX

✓ 6 files translated in 3.2s
Trang App Store đã bản địa hóa giúp tăng hơn 30% lượt tải xuống tại các thị trường không dùng tiếng Anh. Hãy dịch siêu dữ liệu cùng với chuỗi trong app — đây là hoạt động bản địa hóa mang lại ROI cao nhất.
+

Phần thêm: Cơ chế dự phòng locale thông minh với LocaleChain

Theo mặc định, iOS chuyển về ngôn ngữ phát triển khi không có locale chính xác của người dùng. Người dùng pt-BR chỉ có bản dịch pt-PT sẽ thấy tiếng Anh thay vì tiếng Bồ Đào Nha. LocaleChain khắc phục vấn đề này bằng các chuỗi dự phòng có thể cấu hình.

LocaleChain là gói Swift nguồn mở. Xem trên GitHub

Package Dependencies
// Swift Package Manager
// File > Add Package Dependencies >
// https://github.com/i18n-agent/ios-localechain.git
MyApp.swift
import LocaleChain

// In your App init or AppDelegate:
LocaleChain.configure()  // Activates all default chains

// 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: ["es-MX": ["es-419", "es"]]
)

Lỗi thường gặp

Lỗi cú pháp tệp .strings

Thiếu dấu chấm phẩy, dấu ngoặc kép chưa được escape hoặc sai bảng mã sẽ gây lỗi ngầm. Tệp vẫn tải nhưng bản dịch trống. Luôn xác thực tệp .strings trước khi commit.

Phép nội suy Text của SwiftUI không bản địa hóa

Text("Hello \(stringVar)") không bản địa hóa như mong đợi. Hãy dùng String(localized:) cho chuỗi tính toán hoặc bảo đảm biến nội suy có đúng kiểu dành cho LocalizedStringKey.StringInterpolation.

Widget/extension hiển thị khóa thô

Extension của app có bundle riêng. Hãy bảo đảm bạn đã thêm tệp .strings hoặc .xcstrings vào giai đoạn Copy Bundle Resources của target extension chứ không chỉ target app chính.

Bản dịch bị thiếu làm lộ khóa trong môi trường thực tế

Khi khóa không có bản dịch cho ngôn ngữ của người dùng, iOS hiển thị chính khóa đó. Hãy dùng chiến lược ngôn ngữ dự phòng và kiểm thử mọi locale được hỗ trợ trước khi phát hành.

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

Project Structure
MyApp/
├── MyApp.xcodeproj
├── MyApp/
│   ├── Base.lproj/
│   │   ├── Localizable.strings       # Source strings
│   │   └── Localizable.stringsdict   # Plural rules
│   ├── en.lproj/
│   │   └── Localizable.strings
│   ├── de.lproj/
│   │   └── Localizable.strings
│   ├── ja.lproj/
│   │   └── Localizable.strings
│   ├── Localizable.xcstrings          # OR String Catalog
│   └── Info.plist
├── MyAppTests/
├── fastlane/
│   ├── Fastfile
│   └── metadata/
│       ├── en-US/
│       │   ├── name.txt
│       │   ├── description.txt
│       │   └── keywords.txt
│       ├── de-DE/
│       └── ja/
└── Package.swift

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ì

Cơ chế dự phòng locale với ios-localechain

Khi locale theo khu vực như de-AT thiếu khóa bản dịch, iOS chuyển thẳng sang ngôn ngữ phát triển thay vì kiểm tra locale cha de trước.

Terminal
// Swift Package Manager
// https://github.com/i18n-agent/ios-localechain
Configuration
import LocaleChain

LocaleChain.configure(overrides: [
    "de": ["en-GB", "en"],
    "pt-BR": ["pt", "en"],
    "zh-Hant-HK": ["zh-Hant", "zh", "en"],
])

Xem Hướng dẫn về cơ chế dự phòng locale để 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 iOS