Android Tutorial Localize your app using AndroidResources

Discussion in 'Tutorials & Examples' started by warwound, Jul 1, 2012.

Similar threads

B4A Library AndroidResources
Games [XUI2D] Example Pack
B4A Code Snippet Theme Colors
B4A Code Snippet Add graphic to EditText
B4A Tutorial Integrating Firebase Services
  1. warwound

    warwound Expert Licensed 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:

    Code:
    <?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:

    Code:
    <?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:

    Code:
    <?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:

    Code:
    '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.
     

    Attached Files:

    Mark Baars, fredo, GGSoft and 9 others like this.
  2. barx

    barx Well-Known Member Licensed User

    Looks like a useful tutorial. Nice one warwound.

    Sent from my HTC Desire Z
     
  3. peacemaker

    peacemaker Well-Known Member Licensed User

    Thank you, at last clear :)
     
  4. walterf25

    walterf25 Well-Known Member Licensed 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
     
  5. warwound

    warwound Expert Licensed 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:

    Code:
    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:

    Code:
    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: Aug 24, 2012
  6. walterf25

    walterf25 Well-Known Member Licensed 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
     
  7. warwound

    warwound Expert Licensed 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:

    Code:
    <?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.basic4ppc.com/forum/basi...utorials/13818-manifest-editor.html#post79258

    Martin.
     

    Attached Files:

  8. mc73

    mc73 Well-Known Member Licensed User

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

    warwound Expert Licensed User

    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.
     
  10. mc73

    mc73 Well-Known Member Licensed User

    This is what I thought, thank you.
    I had to create my own mapped files, since I need users can alter custom.dictionary.
     
  11. tuhatinhvn

    tuhatinhvn Active Member Licensed User

    Dim AndroidResources1 As AndroidResources????
     
  12. warwound

    warwound Expert Licensed User

    ?
     
  13. tuhatinhvn

    tuhatinhvn Active Member Licensed User

    I dont dind this library??

    AndroidResources

    It is red color in my code
     
  14. warwound

    warwound Expert Licensed User

  15. Claudio Oliveira

    Claudio Oliveira Active Member Licensed 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!
     
  16. Claudio Oliveira

    Claudio Oliveira Active Member Licensed 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.
     
    warwound likes this.
  17. DavideV

    DavideV Active Member Licensed 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
     
  18. antonomase

    antonomase Active Member Licensed User

    Hi,

    A very usefull library.


    But can we avoid to declare the keys into the code
    Code:
    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 ?
     
  19. warwound

    warwound Expert Licensed 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:

    Code:
    Dim RESOURCE_NOT_FOUND_VALUE As String="Error, string resource not found"
    And use that variable as the default value:

    Code:
    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.
     
  20. antonomase

    antonomase Active Member Licensed 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
    Code:
    #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 StringAttributes 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...
     
    lemonisdead likes this.
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice