27 July 2015

Localization Activity - Best way to support several language

Updated on


        As you, a developer, would know, Android application have to support multiple languages. Yeah! It's very easy for android to handle it for you. It is done through "String Resource." The only thing that you must do is simply preparing texts in different languages. The rest is handled by android system.

        However, it is too smart. String Resource is automatically adjusted to the current device's language. We cannot easily switch language on-the-fly.

        • English Article
        • Thai Article

        So I created a new library specifically to handle android application language switching. It called "Localization Activity".

Demo 

        You can watch a short demo from YouTube or try it from Google Play

Features

        • On-the-fly language switching.
        • Auto setup when activity was created.
        • Current language configuration will be saved to SharedPreference automatically.
        • Easy.

Usage

        Add a dependency to your build.gradle

compile 'com.akexorcist:localizationactivity:1.2.2'

        Use custom application class in your project and add the LocalizationApplicationDelegate class like this.

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;

import com.akexorcist.localizationactivity.core.LocalizationApplicationDelegate;

public class CustomApplication extends Application {
    LocalizationApplicationDelegate localizationDelegate = new LocalizationApplicationDelegate(this);

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(localizationDelegate.attachBaseContext(base));
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        localizationDelegate.onConfigurationChanged(this);
    }

    @Override
    public Context getApplicationContext() {
        return localizationDelegate.getApplicationContext(super.getApplicationContext());
    }
}


        This is an example for your activity class.

import android.os.Bundle;
import android.view.View;

import com.akexorcist.localizationactivity.ui.LocalizationActivity;

public class MainActivity extends LocalizationActivity implements View.OnClickListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple);

        findViewById(R.id.btn_th).setOnClickListener(this);
        findViewById(R.id.btn_en).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if (id == R.id.btn_en) {
            setLanguage("en");
        } else if (id == R.id.btn_th) {
            setLanguage("th");
        }
    }
}

        In the example above, when the button is clicked , language will be switched accordingly either Thai or English. That's It!

        Then, you just add String Resources for English and Thai in the values and values-th.


        Completed! Now your application supports multiple languages without much sweat.

Extend from AppCompatActivity

        LocalizationActivity is extended from AppCompatActivity class. If you already use methods from AppCompatActivity. You do not have to refactor your codes!


Public Methods

        The only big adjustment on your codes is the following 3 public methods.

void setLanguage(String language)
void setLanguage(String language, String country)
String getLanguage()
void setDefaultLanguage(String language)
void setDefaultLanguage(String language, String country)

        setLanguage Set the language that you needs to switch. The given string value will be used to setup Locale class later.

setLanguage("th")                             // Language : Thailand
setLanguage("th", "TH")                       // Language : Thailand, Country : Thai
setLanguage("en")                             // Language : English
setLanguage("en", "GB")                       // Language : English,  Country : Great Britain
setLanguage("en", "US")                       // Language : English,  Country : United States 
setLanguage(Locale.KOREA)                     // Language : Korean,  Country : Korea 
setLanguage(Locale.KOREAN)                    // Language : Korean
setLanguage(Locale.CANADA_FRENCH)             // Language : French,  Country : Canada

        So you must determine the correct language for Locale class

        getLanguage Get current language. (Return to string locale)

        setDefaultLanguage Set default language if there is no language configuration. This interface is called only once in the first activity. It will be called before super.onCreate

@Override
public void onCreate(Bundle savedInstanceState) {
    setDefaultLanguage(Locale.JAPAN.toString());
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ...
}

        and 2 optional override methods.

void onBeforeLocaleChanged()
void onAfterLocaleChanged()

        This override method will be called then activity language was changed. If you need to know when language has change, just override these methods.


Change the language on every activity. Although already created.

        You can switch languages in all activities, regardless of whether those activities were in the backstack.



        Normal activity behavior has troubles with language switching.

        If you switch language in the third activity, the previous two activities wouldn't switch language to be the same as the third activity.



        You will find no such problems with LocalizationActivity. All activities in backstack will get the just switched language, too.



You must handle your data through the instance state

        When language was switched. Your activity will be recreated. If you have any data object, you should handle your data by save/restore instance state to retain your data or it will be lost. (Still, this is what you must do when your app supports portrait and landscape orientation.)

        Therefore, you have to override onSaveInstance and onRestoreInstance and handle it.

import android.os.Bundle;
import android.view.View;

import com.akexorcist.localizationactivity.ui.LocalizationActivity;

public class MainActivity extends LocalizationActivity implements View.OnClickListener {

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

        // TODO Initial view and widget here

        if (savedInstanceState == null) {
            // TODO Activity first created
        } else {
            // TODO Activity recreated from screen orientation or change language
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // TODO Save instance here
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        // TODO Restore instance here
        super.onRestoreInstanceState(savedInstanceState);
    }
}

Your fragment is affected as well.

        Fragment language configuration depends on its hosted activity. If activity language was changed and recreated. Fragment will do so. You have to handle your data to Instance State on fragment just like the hosted activity.

        About Save/Restore Instance State. Read more on The Real Best Practices to Save/Restore Activity's and Fragment's state.

Activity Blinking Problem

        It's normal that the activity is blinking because it was recreated. I already fixed this problem by inserting a dummy activity to fade in/out transition while the language is switching in the latest version.

Don't like AppCompat v7? Try the delegate way.

         Sometimes your app doesn't use AppCompatActivity from AppCompat v7 library. But you want to support multiple languages. You can create your own activity class and use LocalizationDelegate to make your activity class.

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;

import com.akexorcist.localizationactivity.core.LocalizationActivityDelegate;
import com.akexorcist.localizationactivity.core.OnLocaleChangedListener;

import java.util.Locale;

public abstract class CustomActivity extends Activity implements OnLocaleChangedListener {

    private LocalizationActivityDelegate localizationDelegate = new LocalizationActivityDelegate(this);

    @Override
    public void onCreate(Bundle savedInstanceState) {
        localizationDelegate.addOnLocaleChangedListener(this);
        localizationDelegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onResume() {
        super.onResume();
        localizationDelegate.onResume(this);
    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(localizationDelegate.attachBaseContext(newBase));
    }

    @Override
    public Context getApplicationContext() {
        return localizationDelegate.getApplicationContext(super.getApplicationContext());
    }

    @Override
    public Resources getResources() {
        return localizationDelegate.getResources(super.getResources());
    }

    public final void setLanguage(String language) {
        localizationDelegate.setLanguage(this, language);
    }

    public final void setLanguage(Locale locale) {
        localizationDelegate.setLanguage(this, locale);
    }

    public final void setDefaultLanguage(String language) {
        localizationDelegate.setDefaultLanguage(language);
    }

    public final void setDefaultLanguage(Locale locale) {
        localizationDelegate.setDefaultLanguage(locale);
    }

    public final Locale getCurrentLanguage() {
        return localizationDelegate.getLanguage(this);
    }

    // Just override method locale change event
    @Override
    public void onBeforeLocaleChanged() {
    }

    @Override
    public void onAfterLocaleChanged() {
    }
}

        And don't forget to exclude AppCompat v7 dependency from this library in build.gradle to reduce total method count.

compile ('com.akexorcist:localizationactivity:+') {
    exclude module: 'appcompat-v7'
}

Examples

        If you don't understand how to use it. I have built an example project that contains 3 example codes and this library is open source. Hopefully it will help you understand it. See at Android-LocalizationActivity [GitHub]

        About 6 example codes. It consists of

        • Simple Activity
        • Simple Activity that extends from Custom Localization Activity
        • Simple Activity with Change Language Activity
        • Activity + Fragment
        • Activity + Nested Fragment
        • Activity + View Pager

        All three examples will handleSave/Restore Instance State. It supports the portrait/landscape orientation as well. (Layout is for demonstration only, it won't be beautiful.)

Feel free to give any advice

        It would be great if you can help me improve my English. Feel free to criticize my wrong grammar.