Adding support for localization

The union of the previous recipes of this book provides you with a complete set of tools to develop what could be a successful game except for its narrow audience.

Libgdx comes with an out of the box internationalization and localization (i18N) system, which means that you will be able to localize your application according to the user's needs without the hassle of implementing your own or hardcoding. In addition, this will allow you to have one or more language files that will contain all the strings of your application, identified by a name that will abstract you from dealing with each language separately.

This data-driven approach fits perfectly for nonprogrammers, translators, or community projects where everybody can contribute with new translations just by editing a friendly text file.

Getting ready

Firstly, check whether you have imported the sample projects into your Eclipse workspace as described in Chapter 1, Diving into Libgdx.

Language files are located in the i18n folder within the assets directory of the Android project:

  • strings_en_GB.properties
  • strings_es_ES.properties

You can guess that Spanish (es) and English (en) are the chosen languages, specifying their variation from Great Britain (GB) and Spain (ES), respectively, in capital letters. Other language variations of the same countries are en_UK (United Kingdom) and es_MX (Mexico).

To keep things tidy, we will have a LanguageManager.java class within the com.cookbook.localization package, in charge of abstracting the programmer from dealing with each language.

Finally, the nub of the issue can be found in LocalizationSample.java, where a practical example of usage is provided.

How to do it…

The process to localize your application is divided into three sections.

Creating language files

One of the main targets of this recipe is to keep the addition of new languages as simple as possible, so we will have a plain text file with the .properties extension containing a set of strings together with their identifier. In the following examples, bookTitle and introduction are keys and the right parts of the equalities are values associated with them:

bookTitle =  Libgdx for Cross Platform Game Development Cookbook
introduction = {0} pragmatic recipes to master cross platform 2D game development using the powerful Libgdx framework.

There is an intruder {0} whose value is replaced at runtime, so it works as a parameter. However, this magic is made by java.text.MessageFormat, which considers them as patterns that are processed and placed at the appropriate places. Double a left curly bracket in order to escape it so you can write it within your text strings.

The following patterns can be built by specifying at least the first of the following properties:

  • ArgumentIndex: This sets which argument will replace the pattern.
  • FormatType: This can be used to format the pattern to number, date, time, or choice.
  • FormatStyle: This will give extra precision to the format so a number can be an integer, a currency, or a percent. A date or a time can be formatted to short, medium, long, or full. You can add your custom styles too through DecimalFormat or SimpleDateFormat. A choice will take the style to specify which message must be shown under certain conditions.

Some practical code examples will shed light on its usage:

Introduction = It's {0, time} on {0,date}. The world is swarming with zombies.
Options = Please, select at least {0, choice, 1# option|1<{0, number,integer} options} 

Note

GWT only supports ArgumentIndex, throwing an IllegalArgumentException if you try to apply FormatType and FormatStyle. In addition, it will convert every argument into a string without taking into account its locale. In case you want to keep uniform behavior in all your platforms, you should make use of setSimpleFormat(true) from the I18NBundle class that we will cover later on.

Managing languages

We can have a set of structured files with all the translations data, but we need to make our application able to understand and process them. The next approach is intended for applications that want to support constant language switching. Perform the following steps:

  1. Here is where I18NBundle comes into action, loading a locale-specific language property file:
    I18NBundle.createBundle(Gdx.input.Internal("i18n/strings_en_GB.properties", Locale.UK));
  2. However, having an I18NBundle class field for each language is not elegant neither maintainable. Instead, we can create a LanguageManager class to gather all loaded languages and keep track of the selected one:
    public class LanguageManager {
       private ObjectMap<String, I18NBundle> languages;
       private String currentLanguage;
       ...
  3. Its constructor will have the responsibility of initializing those fields:
    public LanguageManager() {
    languages = new ObjectMap<String, I18NBundle>();
    currentLanguage = null;
    }
  4. We must also provide an interface to load languages into memory either receiving an I18NBundle or creating a new one:
    public void loadLanguage(String name, I18NBundle bundle) {
    if(name!=null && !name.isEmpty() && bundle != null) 
    languages.put(name.toLowerCase(), bundle);
       }
       
    public void loadLanguage(String name, FileHandle fileHandle, Locale locale) {
    if(name!=null && !name.isEmpty() && fileHandle != null && locale != null)
    languages.put(name.toLowerCase(), I18NBundle.createBundle(fileHandle, locale));
    }
  5. A getter and setter for the currentLanguage field must be included too, so the user can consult it or modify it at runtime:
    public String getCurrentLanguage() {
    return currentLanguage;
    }
    public void setCurrentLanguage(String name) {
    if(languages.containsKey(name.toLowerCase()))
    currentLanguage = name;
    }
  6. Finally, the next method will avoid the user to know or explicitly write the current language every time so he will just deal with a general I18NBundle:
    public I18NBundle getCurrentBundle() {
    return languages.get(currentLanguage);
    }

Usage example

Implementing the preceding approach makes your localization code simpler and cleaner than handling each I18NBundle separately. Perform the following steps:

  1. Consequently, let's create the LanguageManager object and populate with some bundles through the loadLanguage(…) method:
    LanguageManager langManager = new LanguageManager();
    
    FileHandle englishFileHandle = Gdx.files.internal("i18n/strings_en_GB");
    FileHandle spanishFileHandle = Gdx.files.internal("i18n/strings_es_ES");
    
    lm.loadLanguage("English", englishFileHandle, Locale.UK);
    lm.loadLanguage("Spanish", spanishFileHandle, new Locale("es", "ES"));
  2. Once it is initialized, we must set a bundle as active:
    lm.setCurrentLanguage("English");
  3. Now it is time to add some code for retrieving strings so they can be shown on the screen, independent of which language is currently set:
    I18NBundle bundle = lm.getCurrentBundle();
    String title = bundle.get("bookTitle");
    String introduction = bundle.format("introduction", 81);
    String body = bundle.get("body");
  4. Whenever you want to translate the screen text at runtime, just call setCurrentLanguage("Spanish") and update the UI text containers as written in the previous code snippet.

There's more…

You can still improve the LanguageManager class by automatically detecting, within its constructor, the system default language and using it as the current one for the application:

currentLanguage = Locale.getDefault().toString();

Note

Bear in mind that some font types might not fit certain locales because of special characters.

See also

  • This recipe revolves around text resources so do not hesitate to take a look at Chapter 6, Font Rendering and Chapter 8, User Interfaces with Scene2D
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.176.88