Monday, August 12, 2013

Multi-Language Android application

I'm going to add localization to our application...

I know Android to its core, but I haven't used localization before, (never needed to).

So I'm so thrilled to start this new journey, and be able to share my insights...

Given the fact that I'm a critic, this should be joyful :)

So here I go:

If I've got the basic correct, then:

  • If you want to target a specific locale, your values folder containing the strings.xml should be 'values-xx-rYY', note that the locale region referred in the specs is with underscore, while the values folder MUST NOT have underscores in them.
  • If you want to target a group of locales, and by group I mean locales which fits 'xx_*', then your values folder should be 'values-xx'.
First you would like to know which are the available languages, I've got it from here:
(I have no idea how credible the data is, but it is a start)

Arabic, Egypt (ar_EG)           
Arabic, Israel (ar_IL)          
Bulgarian, Bulgaria (bg_BG)     
Catalan, Spain (ca_ES)          
Czech, Czech Republic (cs_CZ)   
Danish, Denmark(da_DK)          
German, Austria (de_AT)         
German, Switzerland (de_CH)     
German, Germany (de_DE)         
German, Liechtenstein (de_LI)   
Greek, Greece (el_GR)           
English, Australia (en_AU)      
English, Canada (en_CA)         
English, Britain (en_GB)        
English, Ireland (en_IE)        
English, India (en_IN)          
English, New Zealand (en_NZ)    
English, Singapore(en_SG)       
English, US (en_US)             
English, South Africa (en_ZA)   
Spanish (es_ES)                 
Spanish, US (es_US)             
Finnish, Finland (fi_FI)        
French, Belgium (fr_BE)         
French, Canada (fr_CA)          
French, Switzerland (fr_CH)     
French, France (fr_FR)          
Hebrew, Israel (he_IL)          
Hindi, India (hi_IN)            
Croatian, Croatia (hr_HR)       
Hungarian, Hungary (hu_HU)      
Indonesian, Indonesia (id_ID)   
Italian, Switzerland (it_CH)    
Italian, Italy (it_IT)          
Japanese (ja_JP)                
Korean (ko_KR)                  
Lithuanian, Lithuania (lt_LT)   
Latvian, Latvia (lv_LV)         
Norwegian-Bokmol, Norway(nb_NO) 
Dutch, Belgium (nl_BE)          
Dutch, Netherlands (nl_NL)      
Polish (pl_PL)                  
Portuguese, Brazil (pt_BR)      
Portuguese, Portugal (pt_PT)    
Romanian, Romania (ro_RO)       
Russian (ru_RU)                 
Slovak, Slovakia (sk_SK)        
Slovenian, Slovenia (sl_SI)     
Serbian (sr_RS)                 
Swedish, Sweden (sv_SE)         
Thai, Thailand (th_TH)          
Tagalog, Philippines (tl_PH)    
Turkish, Turkey (tr_TR)         
Ukrainian, Ukraine (uk_UA)      
Vietnamese, Vietnam (vi_VN)     
Chinese, PRC (zh_CN)            
Chinese, Taiwan (zh_rTW)

Next thing you would like to do, is be able to switch the languages dynamically.

Why? Simply because it would take far less time to evaluate each screen while switching languages, and been able to see the twigging each causes.
To do that you will first need to distinct whether the application runs in debug mode, or in production...

What I've done, is added a menu while running in debug, then launched a dialog for choosing the language I want to display.

A code snippet for changing the Locale:

 Resources res = getApplicationContext().getResources();
 DisplayMetrics dm = res.getDisplayMetrics();
 android.content.res.Configuration conf = res.getConfiguration();
 conf.locale = newLocale;
 res.updateConfiguration(conf, dm);

Afterwards I've tried to dismiss the dialog which for some weird reason it did not work... but I've found the solution for it.

The last part is rendering the UI... That was one of the biggest hacks I faced!
I'll start of and say that in order to render the UI without writing 10 TONs of code, you should(I think MUST) have a parenting layer of, Application, BaseActivity, BaseFragment, BaseDialogFragment, and so on...

Since the only way (I could find) to check if the locale had change within an activity, in its onResume you need to compare the Context.getResources().getConfiguration().locale, with a value which you save on each onResume, in your BaseActivity, this value I believe can be static.

Once you've recognize a change in the locale, there are a couple of approaches you can and need to take:
  • You can refresh your activity with its intent. (Re-launch the activity and close the current one)
  • You can refresh your activity manually. (Rener the views one by one)
  • If you are using a ViewPager, make sure you are setting its adapter via Handler.post(...), otherwise you might get a 'Recursive entry to executePendingTransactions' error.
  • Since I'm loading all my fragment dynamically, after an activity has gone to background, e.g. the onSaveInstanceState was called on the fragments, I could not commit the new changes to the FragmentTransaction, I had to use the commitAllowingStateLoss.
And I was done...

All took less then a day, there is no greater satisfaction then to see the languages changes live in front of your eyes!

Good luck...

(I'm going to number these as I think there are going to be more then one... prof is in the comments :))
NOTES:

  1. Since Android 4.2 the API to change the Device's Locale has been disabled for non-os-signature applications, so all the Locale changing apps are DOOMED...

-- UPDATE --

I've released Cyborg not too long ago, and Language Swapping is build in and optimized feature in, You can find it here.

2 comments:

  1. NOTE 1:
    I tried to create a specific region locale and the dynamic changes did not work for me... well that was because I created the Locale using new Locale(String arg0), which I assumed parse the text given and looks up the region......... but it doesn't so use the new Locale(String arg0, String arg1)

    ReplyDelete
  2. In Cyborg I've completely removed the usage of Fragments as I've believed in the past, (but still have tried to use them and annoyingly realized) that views are the correct way to go... Fragments are a bad solution and are only distributing the Activity problems into smaller fragments(That is how they got the name... we'll break the Activity problems into Fragments :)) but not solving them... I'll write a post on it sometime. in any case when you take fragments out of the picture, this process becomes about a zillion times easier!

    ReplyDelete