Introduction
This tutorial will explain you how to change your application language at run time (programatically).
In certain cases your Android Application is supposed to support multiple languages for example English, Arabic, French. This concept is known as Localization.
Developer design app layout in such a way that layout support Right to Left (rtl) support and Left to Right (ltr) support.
By default all Android apps support LTR Layouts.
We will be using Kotlin Programming Language. Which most of other articles are not using 🙂
Steps for Localizing an Android App:
- Create separate strings.xml files for each language supported in your Android App and add required strings in both files.
- Design XML Layout files using marginStart, marginEnd properties along with marginLeft and marginRight.
- Create LocaleManager Class which will be handling all functions related to localization/ language change.
- Create Activity Class from where we will update app language during runtime.
- Create Shared preferences helper class. This class will update/retrieve user language preference in shared preferences.
Adding strings in values/strings (Default English)
<resources> <string name="meter_number">Meter Number</string> <string name="meter_type">Meter Type</string> <string name="howto_read_meter"><u>How do I read my meter?</u></string> <string name="meter_last_reading_date">Last Reading Date</string> <string name="meter_last_reading_count">Last Reading</string> </resources>
Arabic strings.xml
<string name="meter_number">رقم المرفق</string> <string name="meter_type">نوع العداد</string> <string name="howto_read_meter">كيف اقرا العداد الخاص بي ؟</string> <string name="meter_last_reading_date">تاريخ اخر قراءة</string> <string name="meter_last_reading_count">اخر قراءة</string>
Add this flag in manifest under Application Tag.
android:supportsRtl="true"
// MANIFEST.XML <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.developine.translation"> <application android:allowBackup="true" android:name=".Home" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
Lets First Implemet Shared Preferences in Kotlin Way
Create a new File in your project and name PreferenceHelper
// PreferenceHelper.kt
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
object PreferenceHelper {
fun defaultPrefs(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
fun customPrefs(context: Context, name: String): SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
val editor = this.edit()
operation(editor)
editor.apply()
}
/**
* puts a key value pair in shared prefs if doesn't exists, otherwise updates value on given [key]
*/
operator fun SharedPreferences.set(key: String?, value: Any?) {
when (value) {
is String? -> edit({ it.putString(key, value) })
is Int -> edit({ it.putInt(key, value) })
is Boolean -> edit({ it.putBoolean(key, value) })
is Float -> edit({ it.putFloat(key, value) })
is Long -> edit({ it.putLong(key, value) })
else -> throw UnsupportedOperationException("Not yet implemented")
}
}
/**
* finds value on given key.
* [T] is the type of value
* @param defaultValue optional default value - will take null for strings, false for bool and -1 for numeric values if [defaultValue] is not specified
*/
operator inline fun <reified T : Any> SharedPreferences.get(key: String?, defaultValue: T? = null): T? {
return when (T::class) {
String::class -> getString(key, defaultValue as? String) as T?
Int::class -> getInt(key, defaultValue as? Int ?: -1) as T?
Boolean::class -> getBoolean(key, defaultValue as? Boolean ?: false) as T?
Float::class -> getFloat(key, defaultValue as? Float ?: -1f) as T?
Long::class -> getLong(key, defaultValue as? Long ?: -1) as T?
else -> throw UnsupportedOperationException("Not yet implemented")
}
}
}
Locale Manage Utility Class
We will be using Configuration, Resources and Locale Classes.
Configuration will have desired Locale and Resources will have updated Configuration.
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Build
import PreferenceHelper
import PreferenceHelper.get
import PreferenceHelper.set
import java.util.*
object LocaleManagerMew {
val SELECTED_LANGUAGE = "MEW_CURRENT_USER_LANGUAGE"
var mSharedPreference: SharedPreferences? = null
var mEnglishFlag = "en"
var mArabicFlag = "ar"
fun setLocale(context: Context?): Context {
return updateResources(context!!, getCurrentLanguage(context)!!)
}
inline fun setNewLocale(context: Context, language: String) {
persistLanguagePreference(context, language)
updateResources(context, language)
}
inline fun getCurrentLanguage(context: Context?): String? {
var mCurrentLanguage: String?
if (mSharedPreference == null)
mSharedPreference = PreferenceHelper.defaultPrefs(context!!)
mCurrentLanguage = mSharedPreference!![SELECTED_LANGUAGE]
return mCurrentLanguage
}
fun persistLanguagePreference(context: Context, language: String) {
if (mSharedPreference == null)
mSharedPreference = PreferenceHelper.defaultPrefs(context)
mSharedPreference!![SELECTED_LANGUAGE] = language
}
fun updateResources(context: Context, language: String): Context {
var contextFun = context
var locale = Locale(language)
Locale.setDefault(locale)
var resources = context.resources
var configuration = Configuration(resources.configuration)
if (Build.VERSION.SDK_INT >= 17) {
configuration.setLocale(locale)
contextFun = context.createConfigurationContext(configuration)
} else {
configuration.locale = locale
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
}
return contextFun
}
}
Application Class Code
// ApplicationClass.kt
open class ApplicationClass : Application() {
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(LocaleManager.setLocale(base))
MultiDex.install(base)
}
override fun onCreate() {
super.onCreate()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
LocaleManagerMew.setLocale(this)
Log.d(MewConstants.mewLogs, "onConfigurationChanged: " + newConfig.locale.getLanguage())
}
}
Base Activity
abstract class BaseActivity : AppCompatActivity(){
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(LocaleManagerMew.setLocale(base))
}
}
XML Layout File Code
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="24dp"
tools:context=".ui.login.LoginActivity">
<ImageView
android:id="@+id/switchLanguage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/eng"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtLoginLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login"
android:textAllCaps="true"
android:textColor="@android:color/black"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/switchLanguage" />
<ImageView
android:id="@+id/imgLoginLogo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:src="@drawable/login_logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtLoginLabel" />
<TextView
android:id="@+id/txtCustomer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
android:layout_marginTop="30dp"
android:text="@string/customer"
android:textAllCaps="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imgLoginLogo" />
<ImageView
android:id="@+id/imgSwipeLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/txtCustomer"
app:layout_constraintEnd_toStartOf="@id/txtCustomer"
app:layout_constraintTop_toTopOf="@+id/txtCustomer"
app:srcCompat="@drawable/ic_back" />
<EditText
android:id="@+id/etCivilID"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="start"
android:hint="@string/civil_id"
android:inputType="number"
android:maxLength="20"
android:textAllCaps="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtCustomer" />
<android.support.v7.widget.AppCompatSpinner
android:id="@+id/spinner_choose_account_login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_dropdown"
android:gravity="start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etCivilID" />
<EditText
android:id="@+id/etPremiseNumber"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="start"
android:hint="@string/premise_number"
android:inputType="number"
android:maxLength="20"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/spinner_choose_account_login" />
<TextView
android:id="@+id/txtForgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Forgot password?"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etPremiseNumber" />
<Button
android:id="@+id/btnLogin"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:background="@drawable/bg_drawable_orange"
android:padding="6dp"
android:text="@string/login"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnCreateAccount"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btnCreateAccount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:background="@drawable/rectangle_orange_boarder_btn_bg"
android:padding="6dp"
android:text="@string/create_account"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnLogin" />
</android.support.constraint.ConstraintLayout>
Changing Current Language from Activity/Fragment programatically
Here we will first get current language from shared preferences.
If current language saved in shared preferences is arabic we will update language to english.
If current language saved in shared preferences is null, we will assume that user has not updated language dynamically so we will assume current language is english, and we will update current app language to arabic.
Language Change Button Click Listener in our Activity
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.switchLanguage -> {
//LocaleManagerMew.setLocale(this@LoginCustomerFragment.activity?.applicationContext)
var mCurrentLanguage = LocaleManagerMew.getCurrentLanguage(this@LoginCustomerFragment.activity?.applicationContext)
if (mCurrentLanguage == LocaleManagerMew.mArabicFlag) {
LocaleManagerMew.setNewLocale(this@LoginCustomerFragment.context!!, LocaleManagerMew.mEnglishFlag)
} else if (mCurrentLanguage == LocaleManagerMew.mEnglishFlag) {
LocaleManagerMew.setNewLocale(this@LoginCustomerFragment.context!!, LocaleManagerMew.mArabicFlag)
}
activity?.recreate()
}
}
}
If you have not missed any part of the code. hopefully everything will be working fine.
If you face any issue you can write in comments sections.
copyright: http://developine.com/
Hey I made https://simplelocalize.io to manage translations in applications. It might be helpful for Android developers.