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

warwound

Expert
Licensed User
Longtime User
Nobody is perfect and no code is perfect!

Would it be more logical for you to work directly with the android Resources class which i have wrapped but not uploaded to the forum (yet).
Mentioned it here: http://www.b4x.com/android/forum/threads/androidresources.16847/page-2#post-245589

With the Resources library you can work directly with resource ids - you can handle a resource id being zero when the resource is not found.
So if you are confident that all resources will be found you can write code that doesn't compare resources ids to zero and take action if the resource id is zero.

B4X:
Sub Process_Globals 
End Sub

Sub Globals
    Private label1, label2, label3 As Label
    Private ApplicationResources As Resources 
End Sub

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

    ApplicationResources.Initialize(ApplicationResources.RESOURCE_SOURCE_APPLICATION)
    label1.Text = ApplicationResources.GetString(ApplicationResources.GetIdentifier("red", "string", ApplicationResources.PackageName))
    label3.Text = ApplicationResources.GetString(ApplicationResources.GetIdentifier("green", "string", ApplicationResources.PackageName))
    label2.Text = ApplicationResources.GetString(ApplicationResources.GetIdentifier("blue", "string", ApplicationResources.PackageName))
End Sub

That'll crash the application - probably force close - if a string resource is not found.
But the code is clear and concise and the only hardcoded values are the string resource names such as 'red' and 'green'.

Martin.

[edit]meant to say ask me for a copy of the library if you wnat to try it[/edit]
 

antonomase

Active Member
Licensed User
Longtime User
Of course, I will be happy to use this library for the strings and also for the default drawables ic_something.
 

warwound

Expert
Licensed User
Longtime User
You are after less verbose ways to use localized resources, but you have other options...
This is something i've been working on the past few weeks:

B4X:
Sub Process_Globals
	Dim ApplicationResources As Resources
End Sub

'	'	returns the application string resource identified by TagValue
'	if no string resource is found then the DefaultValue is returned
Sub GetApplicationString(TagValue As String, DefaultValue As String) As String
	Dim ResourceId As Int
	ResourceId=ApplicationResources.GetIdentifier(TagValue, "string", ApplicationResources.PackageName)
	If ResourceId=0 Then
		Return DefaultValue
	Else
		Return ApplicationResources.GetString(ResourceId)
	End If
End Sub

'	returns the application string array resource identified by TagValue
'	if no string array resource is found then DefaultValues is returned
Sub GetApplicationStringArray(TagValue As String, DefaultValues() As String) As String()
	Dim ResourceId As Int
	ResourceId=ApplicationResources.GetIdentifier(TagValue, "array", ApplicationResources.PackageName)
	If ResourceId=0 Then
		Return DefaultValues
	Else
		Return ApplicationResources.GetStringArray(ResourceId)
	End If
End Sub

'	localize all Views in Activity1
'	if a View has no Tag value, or a Tag value exists but no string or string array resource is found for the Tag value then the View's Text property or properties will be unchanged
Sub LocalizeActivity(Activity1 As Activity)
	For Each View1 As View In Activity1.GetAllViewsRecursive
		LocalizeView(View1)
	Next
End Sub

'	localize all Views in Panel1
'	if a View has no Tag value, or a Tag value exists but no string or string array resource is found for the Tag value then the View's Text property or properties will be unchanged
Sub LocalizePanel(Panel1 As Panel)
	For Each View1 As View In Panel1.GetAllViewsRecursive
		LocalizeView(View1)
	Next
End Sub

'	localize a View
'	if the View has no Tag value, or a Tag value exists but no string or string array resource is found for the Tag value then the View's Text property or properties will be unchanged
Sub LocalizeView(View1 As View)
	If View1.Tag<>Null AND View1.Tag Is String Then
		Dim TagValue As String=View1.Tag
		If TagValue<>"" Then
			'	the View's Tag property has been set
			'	check if we have a string resource for the TagValue
			Dim ResourceId As Int=ApplicationResources.GetIdentifier(TagValue, "string", ApplicationResources.PackageName)
			'	a ResourceId value of 0 indicates that no string resource exists for the TagValue
			If ResourceId<>0 Then
				'	a string resource found for the TagValue
				'	check to see if the View is a View with a single Text property and set it's Text property
				Dim ResourceString As String=ApplicationResources.GetString(ResourceId)
				If View1 Is Button Then
					Dim Button1 As Button=View1
					Button1.Text=ResourceString
				Else If View1 Is CheckBox Then
					Dim CheckBox1 As CheckBox=View1
					CheckBox1.Text=ResourceString
				Else If View1 Is Label Then
					Dim Label1 As Label=View1
					Label1.Text=ResourceString
				Else If View1 Is RadioButton Then
					Dim RadioButton1 As RadioButton=View1
					RadioButton1.Text=ResourceString
				Else
					'	we have a TagValue and a string resource but have not yet implemented localization of this type of View
					'	we need to add another Else If condition to localize this type of View
					Log("LocalizeView: View with Tag value of "&TagValue&" is of a type not yet implemented")
					'	if necessary we could also log the type of the View to enable us to trace where the View has been created
				End If
			Else
				'	no string resource found for the TagValue
				'	check if we have a string array resource for the TagValue
				ResourceId=ApplicationResources.GetIdentifier(TagValue, "array", ApplicationResources.PackageName)
				If ResourceId<>0 Then
					'	a string array resource found for the TagValue
					'	check to see if the View is a View with multiple text properties and set it's text properties
					Dim ResourceStrings() As String=ApplicationResources.GetStringArray(ResourceId)
					If View1 Is Spinner Then
						Dim Spinner1 As Spinner=View1
						Spinner1.AddAll(ResourceStrings)
					Else If View1 Is ToggleButton Then
						Dim ToggleButton1 As ToggleButton=View1
						ToggleButton1.TextOff=ResourceStrings(0)
						ToggleButton1.TextOn=ResourceStrings(1)
					Else
						'	we have a TagValue and a string array resource but have not yet implemented localization of this type of View
						'	we need to add another Else If condition to localize this type of View
						Log("LocalizeView: View with Tag value of "&TagValue&" is of a type not yet implemented")
						'	if necessary we could also log the type of the View to enable us to trace where the View has been created
					End If
				Else
					'	no string or string array resource found for the TagValue
					'	check if we have a drawable resource for the TagValue
					ResourceId=ApplicationResources.GetIdentifier(TagValue, "drawable", ApplicationResources.PackageName)
					If ResourceId<>0 Then
						'	is the View an ImageView?
						If View1 Is ImageView Then
							Dim BitmapDrawable1 As BitmapDrawable=ApplicationResources.GetDrawable(ResourceId)
							Dim ImageView1 As ImageView=View1
							ImageView1.Bitmap=BitmapDrawable1.Bitmap
						End If
						'	we have a TagValue and a drawable resource but the View is not an ImageView
						
					Else
						'	no string, string array or drawable resource found for the TagValue
						Log("LocalizeView: the View has a Tag value set '"&TagValue&"' but no localized resource was found")
						'	log the View and set it's background color to red - this should help us identify the View
						Log(View1)
						View1.Color=Colors.Red
					End If
				End If
			End If
		End If
	End If
End Sub

This is a code module that needs it's public ApplicationResources member initialized just once in an application.
The module was written so that a View that is to display localized text has it's Tag property set to a string resource identifier.

A View created in the Designer can have it's Tag property set and so can a View created in code.
For example i may have a Label and set it's Tag property to my_string in the Designer or in code.

Now i can pass an Activity, a Panel or a single View to the module and automate localizing it's text.

Some Views display more than 'just a simple string', a Spinner and a ToggleButton both have more than one settable text property.
So android string arrays was the solution there, an array declared in xml populating Spinner items or the on and off state of the View.

Martin.
 

Attachments

  • ResourceManager.bas
    6.1 KB · Views: 401

antonomase

Active Member
Licensed User
Longtime User
The library works fine with my resources (string or drawable).

Just a point which looks ambigous
B4X:
systemResources.Initialize(systemResources.RESOURCE_SOURCE_SYSTEM)
Log (systemResources.PackageName)

I attempt that the packageName of a resource initialized with 'system' returns 'android' and not the packageName of my application. Probably not a bug, but not logic.


B4X:
' This works
applicationResources.Initialize(applicationResources.RESOURCE_SOURCE_APPLICATION)
label3.Text = applicationResources.GetString(applicationResources.GetIdentifier("red", "string", applicationResources.PackageName))

'This doesn't works
systemResources.Initialize(systemResources.RESOURCE_SOURCE_SYSTEM)
label3.Text = systemResources.GetString(systemResources.GetIdentifier("no", "string", systemResources.PackageName))

Other point strange : the system resources "yes" and "no" do not contain "yes" or "no" but "OK" and "Cancel"
 

antonomase

Active Member
Licensed User
Longtime User
Next step : allowing users to change the language. My app is in available in french and english, but how to manage a spanish user and let him choose between french or english ?

I see your message of last summer and try to use LocaleApplication. But when the application do not start when I add
B4X:
SetApplicationAttribute(android:name, "com.mytest.teststringsxml")
 

warwound

Expert
Licensed User
Longtime User
The library works fine with my resources (string or drawable).

Just a point which looks ambigous
B4X:
systemResources.Initialize(systemResources.RESOURCE_SOURCE_SYSTEM)
Log (systemResources.PackageName)

I attempt that the packageName of a resource initialized with 'system' returns 'android' and not the packageName of my application. Probably not a bug, but not logic.

Yes the PackageName property will always return the package name of the application, it's not related to whether you're trying to access system or application resources.

B4X:
' This works
applicationResources.Initialize(applicationResources.RESOURCE_SOURCE_APPLICATION)
label3.Text = applicationResources.GetString(applicationResources.GetIdentifier("red", "string", applicationResources.PackageName))

'This doesn't works
systemResources.Initialize(systemResources.RESOURCE_SOURCE_SYSTEM)
label3.Text = systemResources.GetString(systemResources.GetIdentifier("no", "string", systemResources.PackageName))
I didn't quite think this through when i created the library and currently haven't used it to access system resources so will experiment and update the library if necessary.
There's various syntax options when getting either application or system resources, look at the (android) documentation for the getIdentifier method.
Resource type and package name parameters are actually optional.
You can use a 'fully qualified' resource name and omit the resource type and package name parameters.

Can you try something like this:

B4X:
systemResources.Initialize(systemResources.RESOURCE_SOURCE_SYSTEM)
label3.Text = systemResources.GetString(systemResources.GetIdentifier("android:string/no", Null, Null))

Other point strange : the system resources "yes" and "no" do not contain "yes" or "no" but "OK" and "Cancel"

Look here for a summary of android's currently available string resources: http://developer.android.com/reference/android/R.string.html.
Again the question is should you rely on built in android resources or explicitly create your own?.
Android's built in resources are in theory exactly what you need in some circumstances but in practice may be absent or may have been modified by the device manufacturer.
General consensus of opinion is to always define your own resources and not assume built in resource exist and have not been modified.

Martin.
 

warwound

Expert
Licensed User
Longtime User
Next step : allowing users to change the language. My app is in available in french and english, but how to manage a spanish user and let him choose between french or english ?

I see your message of last summer and try to use LocaleApplication. But when the application do not start when I add
B4X:
SetApplicationAttribute(android:name, "com.mytest.teststringsxml")

Try the attached library,
The Resources library doesn't include the LocaleApplication, the attached library is the LocaleApplication on it's own.

Be sure to use the correct package name for the LocaleApplication:

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

Martin.
 

Attachments

  • LocaleApplication.zip
    4.4 KB · Views: 365

antonomase

Active Member
Licensed User
Longtime User
I didn't quite think this through when i created the library and currently haven't used it to access system resources so will experiment and update the library if necessary...

I found the syntax with "android". I only say that if you write applicationResources.GetIdentifier("red", "string", applicationResources.PackageName), the first reflex is to write systemResources.GetIdentifier("no", "string", systemResources.PackageName).
And when it doesn't work, you begin to read the documentation.

General consensus of opinion is to always define your own resources and not assume built in resource exist and have not been modified.
Yes, of course. It's just funny to see that the resource 'yes' returns 'OK' but not yes, oui, Ja, da, ...
But Logic and Android are not often friends. :)

I also tested drawable resources : and I also choose the application resources even they exist into the system because system icons appears to be blurry.

 

luke2012

Well-Known Member
Licensed User
Longtime User
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.
My compliments for this useful tutorial.
In my app I have some text that contains html tags. It's enought to surround the string with double quotes within xml file?
 

antonomase

Active Member
Licensed User
Longtime User
My compliments for this useful tutorial.
In my app I have some text that contains html tags. It's enought to surround the string with double quotes within xml file?

Try to use
B4X:
<string name="htmlstring"><![CDATA[This text is <b>bold</b>]]></string>
<string name="htmlstring2"><![CDATA[some text]]></string>
 

Mark Baars

Member
Licensed User
Longtime User
Hi, I am relatively new to B4A and am trying to make my first app multi-lingual, following the instructions posted here.

None of my projects seem to have this "Value"-folder. Under Objects\res all I have is: Drawable, layout and xml. I have created the folders (values and values-nl) manually, and put my strings.xml files there (read only). When I compile and run, all values show "null".

I am probably missing something here, but don't know where to look. Can anyone explain when the layout-folder is supposed to be made and how I can trigger that?

Thanks and regards!
 

DonManfred

Expert
Licensed User
Longtime User
Top