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
attachBaseContexttrong 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"
... >