Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách thay đổi ngôn ngữ (Locale) chương trình Android tại runtime

Giới thiệu

Android cho phép thay đổi ngôn ngữ ứng dụng tại runtime mà không cần khởi động lại thiết bị. Bài viết hướng dẫn cách set locale chương trình (app-level locale) trên các phiên bản Android khác nhau, bao gồm API 33+ (Android 13) có API mới LocaleManager.

Các bước thực hiện

Bước 1: Tạo dự án mới

Android Studio → File → New Project → Empty View Activity.

Bước 2: Chuẩn bị string resources cho nhiều ngôn ngữ

Tạo thư mục values cho từng ngôn ngữ:

  • res/values/strings.xml (mặc định - tiếng Anh)
  • res/values-vi/strings.xml (tiếng Việt)
  • res/values-es/strings.xml (tiếng Tây Ban Nha)

res/values/strings.xml:

<resources>
    <string name="app_name">Locale Demo</string>
    <string name="greeting">Have a nice day</string>
    <string name="btn_english">English</string>
    <string name="btn_vietnamese">Vietnamese</string>
    <string name="btn_spanish">Spanish</string>
</resources>

res/values-vi/strings.xml:

<resources>
    <string name="greeting">Chúc bạn một ngày tốt lành</string>
    <string name="btn_english">Tiếng Anh</string>
    <string name="btn_vietnamese">Tiếng Việt</string>
    <string name="btn_spanish">Tiếng Tây Ban Nha</string>
</resources>

res/values-es/strings.xml:

<resources>
    <string name="greeting">Que tengas un buen día</string>
    <string name="btn_english">Inglés</string>
    <string name="btn_vietnamese">Vietnamita</string>
    <string name="btn_spanish">Español</string>
</resources>

Bước 3: Thiết kế giao diện (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvGreeting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/greeting"
        android:textSize="24sp"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:orientation="horizontal"
        android:gravity="center">

        <Button
            android:id="@+id/btnEn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:text="@string/btn_english" />

        <Button
            android:id="@+id/btnVi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:text="@string/btn_vietnamese" />

        <Button
            android:id="@+id/btnEs"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btn_spanish" />
    </LinearLayout>
</LinearLayout>

Bước 4: Utility class xử lý Locale (LocaleHelper.java / LocaleHelper.kt)

Java (hỗ trợ API 24+):

package com.example.localedemo;

import android.annotation.SuppressWarnings;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import java.util.Locale;

public class LocaleHelper {

    public static Context setLocale(Context context, String languageCode) {
        Locale locale = new Locale(languageCode);
        Locale.setDefault(locale);

        Resources resources = context.getResources();
        Configuration config = resources.getConfiguration();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            config.setLocale(locale);
            config.setLocales(new LocaleList(locale));
        } else {
            config.locale = locale;
        }

        resources.updateConfiguration(config, resources.getDisplayMetrics());
        return context;
    }

    // Cho Activity: override attachBaseContext
    @SuppressWarnings("deprecation")
    public static void applyLocaleToActivity(Context context, String languageCode) {
        setLocale(context, languageCode);
    }
}

Kotlin (hỗ trợ API 33+ với LocaleManager):

package com.example.localedemo

import android.annotation.SuppressLint
import android.app.LocaleManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.os.LocaleList
import java.util.Locale

object LocaleHelper {

    @SuppressLint("NewApi")
    fun setLocale(context: Context, languageCode: String): Context {
        val locale = Locale(languageCode)
        Locale.setDefault(locale)

        val resources = context.resources
        val config = resources.configuration

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33+
            val localeManager = context.getSystemService(LocaleManager::class.java)
            localeManager.applicationLocales = LocaleList(locale)
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24+
            config.setLocale(locale)
            config.setLocales(LocaleList(locale))
            resources.updateConfiguration(config, resources.displayMetrics)
        } else { // API < 24
            config.locale = locale
            resources.updateConfiguration(config, resources.displayMetrics)
        }
        return context
    }

    // Dùng trong Activity: override attachBaseContext
    fun attachBaseContext(context: Context, languageCode: String): Context {
        return setLocale(context, languageCode)
    }
}

Bước 5: MainActivity áp dụng Locale

Java:

package com.example.localedemo;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private static final String PREF_LANG = "app_language";
    private SharedPreferences prefs;

    @Override
    protected void attachBaseContext(Context newBase) {
        prefs = newBase.getSharedPreferences("settings", MODE_PRIVATE);
        String lang = prefs.getString(PREF_LANG, "en");
        super.attachBaseContext(LocaleHelper.setLocale(newBase, lang));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnEn = findViewById(R.id.btnEn);
        Button btnVi = findViewById(R.id.btnVi);
        Button btnEs = findViewById(R.id.btnEs);

        btnEn.setOnClickListener(v -> changeLanguage("en"));
        btnVi.setOnClickListener(v -> changeLanguage("vi"));
        btnEs.setOnClickListener(v -> changeLanguage("es"));
    }

    private void changeLanguage(String langCode) {
        prefs.edit().putString(PREF_LANG, langCode).apply();
        recreate(); // Khởi động lại Activity để áp dụng locale mới
    }
}

Kotlin:

package com.example.localedemo

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.localedemo.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var prefs: SharedPreferences

    companion object {
        const val PREF_LANG = "app_language"
    }

    override fun attachBaseContext(newBase: Context) {
        prefs = newBase.getSharedPreferences("settings", MODE_PRIVATE)
        val lang = prefs.getString(PREF_LANG, "en") ?: "en"
        super.attachBaseContext(LocaleHelper.setLocale(newBase, lang))
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnEn.setOnClickListener { changeLanguage("en") }
        binding.btnVi.setOnClickListener { changeLanguage("vi") }
        binding.btnEs.setOnClickListener { changeLanguage("es") }
    }

    private fun changeLanguage(langCode: String) {
        prefs.edit().putString(PREF_LANG, langCode).apply()
        recreate()
    }
}

Lưu ý quan trọng

  • Phải override attachBaseContext trong mọi Activity (hoặc BaseActivity) để locale áp dụng nhất quán.
  • Gọi recreate() sau khi đổi ngôn ngữ để Activity reload layout với resource mới.
  • Android 13+ (API 33): Có thể dùng LocaleManager.setApplicationLocales() thay vì recreate, nhưng vẫn khuyến nghị recreate để chắc chắn.
  • Lưu language preference vào SharedPreferences để khôi phục khi app khởi động lại.
  • Test trên thiết bị thật: Settings → Languages → thêm ngôn ngữ → kiểm tra app tự đổi theo system locale (nếu không override).

Android 13+ (API 33): Per-app Language Preferences

Từ Android 13, người dùng có thể set ngôn ngữ riêng cho từng app trong Settings. App có thể khai báo ngôn ngữ hỗ trợ trong res/xml/locales_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="https://schemas.android.com/apk/res/android">
    <locale android:name="en"/>
    <locale android:name="vi"/>
    <locale android:name="es"/>
</locale-config>

Và trong Manifest:

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