Android Tutorial Localize your app using AndroidResources

Here's a short tutorial showing how you can add support for multiple languages to your B4A application using my AndroidResources library.

Native Android applications have built in support for multiple languages.
Instead of hardcoding text that will be displayed into your program code, you instead hardcode a reference to a string resource.
You define a default value for the string resource and can add one or more definitions for the same string resource - one definition for each language that you want to support.
If a device running your application is set to use a language that has a string resource defined then that string resource is used, otherwise the default string resource is used.

Some useful links that you might want to read:

Please read those links so that you understand how Android uses differently name values folders to contain string resource values for different languages.

So you need to create one or more XML files that define string resource values, these XML files must be placed in your project's Objects/res/values and optionally Objects/res/values-?? folders.
(The names of your XML files are by tradition strings.xml but you can use any other filenames if you desire.)
Do not forget to set your XML files to read-only otherwise the B4A compiler will delete them when it compiles your application.

Your application's default string resources are defined in Objects/res/values, here's an example XML file that defines English values:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <string name="continue_text">Continue</string>
   <string name="intro_text">Welcome to the localization demo Activity.</string>
   <string name="main_activity_title">Localize your application</string>
</resources>

Let's add support for both French and German languages.

Resource file location: Objects/res/values-de:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <string name="continue_text">Fortsetzen</string>
   <string name="intro_text" formatted="false">Willkommen bei der Lokalisierung Demo Aktivität.</string>
   <string name="main_activity_title">"Lokalisieren Sie Ihre Anwendung"</string>
</resources>

Resource file location: Objects/res/values-fr:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <string name="continue_text">Continuer</string>
   <string name="intro_text">"Bienvenue à l'activité de démonstration de localisation."</string>
   <string name="main_activity_title">Localisez votre demande</string>
</resources>

You can see here that some characters require special treatment in your XML files.
There's various ways to escape individual characters, but here i've used 2 techniques to properly encode the entire string value:

  • You can contain the entire string value within double quotes:
    <string name="intro_text">"Bienvenue à l'activité de démonstration de localisation."</string>
  • Or you can add the formatted="false" attribute to the string tag:
    <string name="intro_text" formatted="false">Willkommen bei der Lokalisierung Demo Aktivität.</string>

You now have three well formed XML files defining string resources for:
  • The device's default language.
  • The French language.
  • The German language.

We'll use the AndroidResources GetApplicationStrings method to get the required values into our B4A code:

GetApplicationStrings (ResourceNames As Map) As Map
Get one or more application resource Strings.
Pass a Map to this method where each Map key is an application String resource name.
The method will return the same Map object where the Map values are the application resource Strings defined by the Map keys.
If an application resource String is not found the Map value will not be changed.

A simple B4A Activity:

B4X:
'Activity module
Sub Process_Globals
End Sub

Sub Globals
   Dim AndroidResources1 As AndroidResources
   Dim Button1 As Button
   Dim Label1 As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Main")
   
   Dim ResourceStrings As Map
   ResourceStrings.Initialize
   ResourceStrings.Put("continue_text", "Error, resource string not found")
   ResourceStrings.Put("intro_text", "Default intro text")
   ResourceStrings.Put("main_activity_title", "Default activity title")
   
   ResourceStrings=AndroidResources1.GetApplicationStrings(ResourceStrings)
   
   Activity.Title=ResourceStrings.Get("main_activity_title")
   Button1.Text=ResourceStrings.Get("continue_text")
   Label1.Text=ResourceStrings.Get("intro_text")
   
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub

Sub Button1_Click
   '   to do, start a new Activity
End Sub


The layout file Main.bal contains just a Panel containing Label1 and Button1 placed under the Panel.
No Text values are defined in the layout file - Label1.Text and Button1.Text are blank.

The ResourceStrings Map object keys are the names of the defined string resources we want to retrieve.
The Map object is passed to the GetApplicationStrings method.
AndroidResources searches for the requested string resource, if the string resource is found then it's value is set as the corresponding Map value.
If a requested string resource is NOT found the corresponding Map value is left unchanged.

Change your device's default language via Settings > Language & keyboard to see the different string resources display.

There's room for improvement on this example code...
If you Activity contains many Views you might want to find a way to iterate those Views and set their Text values.

Another possible improvement would be to automate the entire process...
Could a Label or Button's Tag property be set in Designer to the name of a string resource and then code created to get all Views with Tags that refer to a string resource and the string resource value automatically set as the View's Text property?

Localization isn't just about strings of course, you can define other language dependent resources such as those defined in Objects/res/drawable.
AndroidResources has GetApplicationDrawable and GetApplicationDrawables methods which you can use to localize images displayed in your application.

Demo code attached, please get the latest version of the AndroidResources library from the AndroidResources thread.

Martin.
 

Attachments

  • Localization.zip
    7.6 KB · Views: 1,456

barx

Well-Known Member
Licensed User
Longtime User
Looks like a useful tutorial. Nice one warwound.

Sent from my HTC Desire Z
 

peacemaker

Expert
Licensed User
Longtime User
Thank you, at last clear :)
 

walterf25

Expert
Licensed User
Longtime User
Setting Locale from within App

Hi Warwound, first of all let me thank you sincerely for this awesome library, i now have my best app translated to spanish, I wish to get a lot more downloads with this new feature, but i have one question though!

Is there a way to set the Locale from within the app, I know that in order for the app to work in different languages you need to go to settings, then Language and Keyboard, and change the Locale to the desired language.

can this be done programatically from within the app? If so, do you mind showing me how to do this, or does anyone here in the forum know how to do it?

Thanks everyone!

Walter
 

warwound

Expert
Licensed User
Longtime User
Hi.

I've spent a little while searching for some java code to change the app's locale setting...

Seems to be a FAQ with various answers, unfortunately the answers all seem to be hacks that may or may not work.
You need to change the locale before the app loads any locale specific resources - those resources won't be automtaically reloaded if you change the app's locale setting.
So all the code examples must be executed on the java onCreate() method.
We can't do that in the B4A Activity_Create - code in Activity_Create is actually executed after the java onCreate() method has executed.

So you'd need a different solution, the only one i've found that might work is here: android - Changing Locale within the app itself - Stack Overflow where sub-classing the Application class is suggested.
So i've compiled this:

B4X:
public class LocaleApplication extends Application {
   private Locale locale = null;

   @Override
   public void onConfigurationChanged(Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
      if (locale != null) {
         newConfig.locale = locale;
         Locale.setDefault(locale);
         getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
      }
   }

   @Override
   public void onCreate() {
      super.onCreate();

      Configuration config = getBaseContext().getResources().getConfiguration();
      String lang = "en";

      //   SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
      //   String lang = settings.getString(getString(R.string.pref_locale), "");
      if (!"".equals(lang) && !config.locale.getLanguage().equals(lang)) {
         locale = new Locale(lang);
         Locale.setDefault(locale);
         config.locale = locale;
         getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
      }
   }
}

For testing i've hardcoded the locale to en, English.
Next you need to update your project's manifest file so the project is compiled using LocaleApplication instead of the default Application, you need to add this text using the Manifest Editor:

B4X:
SetApplicationAttribute(android:name, "uk.co.martinpearman.b4a.androidresources.LocaleApplication")

So download the attached LocaleApplication files and add them to your B4A Additional Libraries folder.
Check the LocaleApplication box in your project and update the manifest file.

Set your device to any language except English and see what happens - does your app display English text?

Try changing the device orientation and going from one Activity to another - does the English text remain or go back to the device's set locale?

Martin.
 
Last edited:

walterf25

Expert
Licensed User
Longtime User
I think it works

Hi Warwound, thanks for providing me with that code, i just tried it, I set my locale to Spanish. In my App I have 5 different activities which i have already added the strings to the strings.xml files in the /res folder so it will display the text in spanish. So I ran the application with the files you posted and the first activity displays the text in spanish but when i go to the next activity it goes back to english, So i think is working, the question is, how do I change the language locale from with in the application, can you add some sort of variable in your code that can be passed to b4A and set the locale that way?

Thanks Warwound, you Rock

Cheers,
Walter
 

warwound

Expert
Licensed User
Longtime User
Hi again.

The easiest way to enable you to choose a default locale would be for you to add the locale text as a String Resource to your app's default language Objects\res\values\strings.xml XML file.

You can add it to the existing XML file, no need for a separate file unless you want to do so.

Here i've added it to the default language demo strings.xml file:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="continue_text">Continue</string>
    <string name="intro_text">Welcome to the localization demo Activity.</string>
    <string name="main_activity_title">Localize your application</string>
    <string name="locale">en</string>
</resources>

Now i've updated the LocaleApplication so it looks for the String Resource named locale, if that String exists and is not empty "" then it'll be used as the app's locale.
If the String does not exist or is empty "" then no attempt will be made to change the app's locale.

So replace the old LocaleApplication jar and xml files with the new ones attached to this post and then add the new locale String Resource to your app's default language strings.xml file and hopefully it will work.

I don't see an easy way to work around the fact that the very first Activity to be created will use the locale set in the device settings.
Maybe you could make the first Activity to be displayed (Main) just an empty Activity that does nothing but load the next Activity.
The empty Main Activity would use the locale set in the device settings and after that the locale set by the library would be used.

There is a manifest edit where you can change the starting Activity in your project.
So you could leave your project code as it is, create a new empty Activity which starts the Main Activity and set that empty Activity as the app's starting Activity.

You can find that manifest edit here: http://www.b4x.com/forum/basic4andr...utorials/13818-manifest-editor.html#post79258

Martin.
 

Attachments

  • LocaleApplication.zip
    1.8 KB · Views: 704

mc73

Well-Known Member
Licensed User
Longtime User
Martin,
I began my app's localization before seeing your lib.
Question: can the end-user process these xmlsor they're read-only?
 

warwound

Expert
Licensed User
Longtime User
Martin,
I began my app's localization before seeing your lib.
Question: can the end-user process these xmlsor they're read-only?

Hi.

I'm not sure what you mean by 'end-user'...

Do you mean the applicaiton user?
The XML files get compiled into your app and there's no way a user of your app could modifiy them.
(Nothing is impossible and in theory a determined hacker could modify them, but within the APK they are compiled into a binary format, not simply packed as text/xml files.)

Martin.
 

mc73

Well-Known Member
Licensed User
Longtime User
This is what I thought, thank you.
I had to create my own mapped files, since I need users can alter custom.dictionary.
 

Claudio Oliveira

Active Member
Licensed User
Longtime User
Hi Martin!

I'm trying to use your AndroidResources library and it works just fine.
However, when I use some portuguese words like 'ç', 'à', 'ã', 'õ', 'ú' and others, the compiler returns the following message:
"res\values-pt-rBR\strings.xml:4: error: Error parsing XML: not well-formed (invalid token)"I used the tutorial app from the first post for testing.
I've created a folder "r-pt-rBR" under the 'res" folder. Copied the french xml file into it, edited to portuguese, saved, marked as "read only", compiled it and got the error.
I have already tried to double quote the string, and tried to include the 'formatted="false"' statement as well, but I've got the same error in both cases.
On the other hand, the french XML compiles perfectly even though it uses a few latin characters like 'à' and 'é'. In fact, that was the reason why I chose the french file to work on...
Did I miss something? Am I doing anything wrong?
Any help would be highly appreciated.
Thanks and regards!
 

Claudio Oliveira

Active Member
Licensed User
Longtime User
Solved

I don't know if this is the most adequate approach, but I've changed the XML encoding from "UTF-8" to "ISO-8859-1" and it worked.
Movin' on... :D

Regards.
 

DavideV

Active Member
Licensed User
Longtime User
Hi all,
maybe this will help others to avoid headcache as mine today on R.java compilation error :
don't start the string name with a number.
this is wrong:
<string name="4_xxxx">YourText</string>
this is good:
<string name="four_xxxx">YourText</string>

Regards
DavideV
 

antonomase

Active Member
Licensed User
Longtime User
Hi,

A very usefull library.


But can we avoid to declare the keys into the code
B4X:
  Dim ResourceStrings As Map
  ResourceStrings.Initialize
  ResourceStrings.Put("continue_text", "Error, resource string not found")
  ResourceStrings.Put("intro_text", "Default intro text")
  ResourceStrings.Put("main_activity_title", "Default activity title")

  ResourceStrings=AndroidResources1.GetApplicationStrings(ResourceStrings)

The main interest of the strings.xml file is to separate datas from the code. But if we have to declare the keys both into the code and into the xml file, it is a little complicated.

Can we avoid to put the default values ?
 

warwound

Expert
Licensed User
Longtime User
The 'default value' is really just a way to handle the case where the requested resource is not found.

To get a resource you need a resource id.
If you try to get the resource id of a resource that does not exist then you get a resource id of zero.
This is the default android behaviour - to return a resource id of zero when a resource is not found)
Now if you try to get a resource whose resource id is zero your app will raise an exception.

So the library checks if the resource id is zero and if so it returns the default value instead of raising an exception.
Consider the default value a kind of debugging value.
If you have correctly included all the resources in your project the default value will never be used.
Therefore you could use an empty string for the default value - that'd not leave any resource data in your code.

A more elegant solution might be to declare a global string variable such as:

B4X:
Dim RESOURCE_NOT_FOUND_VALUE As String="Error, string resource not found"

And use that variable as the default value:

B4X:
Dim ResourceStrings As Map
ResourceStrings.Initialize
ResourceStrings.Put("continue_text", RESOURCE_NOT_FOUND_VALUE)
ResourceStrings.Put("intro_text", RESOURCE_NOT_FOUND_VALUE)
ResourceStrings.Put("main_activity_title",RESOURCE_NOT_FOUND_VALUE)
ResourceStrings=AndroidResources1.GetApplicationStrings(ResourceStrings)

Your code now contains just a single string instead of multiple (default value) strings, and this string is not 'displayable text data' merely a debugging value.

Martin.
 

antonomase

Active Member
Licensed User
Longtime User
Thanks for your answer. But we've still have to declare all the keys of the xml file into your code. If you add a string into the strings.xml file, you must think to add "ResourceStrings.Put(" into your code. If you remove a string into the file, you must thing to remove it into your code. When the file contains a lot of entries, it will be hard to maintain

I was thinkink about something more automatic.


Step 1 : read the strings.xml file and extract the keys (name)
Step2 : a loop on the keys to generate all the ResourceStrings.Put(key, RESOURCE_NOT_FOUND_VALUE)
Step 3 : perform the GetApplicationStrings(ResourceStrings)

I try something like that by adding one of the strings.xml into the files
B4X:
#Region  Project Attributes
    #ApplicationLabel: TestStringsXML
    #VersionCode: 1
    #VersionName:
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
    #AdditionalRes: C:\Basic4Android\TestStringsXML\TestStringsXML-res, test.teststringsxml
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
End Sub

Sub Globals
    Private label1, label2, label3 As Label
    Dim resourceStrings As Map
    Dim resources As AndroidResources  
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("mainLayout")
    resourceStrings.Initialize
    InitializeResoursesString

    resourceStrings = resources.GetApplicationStrings(resourceStrings)

    label1.Text = resourceStrings.Get("red")
    label3.Text = resourceStrings.Get("green")
    label2.Text = resourceStrings.Get("blue")
End Sub

Sub InitializeResoursesString
    Dim parser As SaxParser
    Dim stream As InputStream
    stream = File.OpenInput(File.DirAssets, "strings.xml")
    parser.Initialize
    parser.Parse(stream, "Parser")
End Sub

Private Sub Parser_StartElement (Uri As String, name As String, Attributes As Attributes)
    Dim key As String
    If name = "string" Then
        key = Attributes.GetValue2(Uri, "name")
        resourceStrings.Put(key, "default value")
    End If
End Sub

I do not have to manually declare all the resourceStrings.Put(...
But each time I change strings.xml, I have to remove the copy into files and add it again.
Nobody's perfect...
 
Top