This post was initially published for AppInventor classic, but currently sources are available also for AI2. However, I have not updated the comments and images because the code is roughly the same.
English is not the only language in the world. If we want our apps to be widely spread we’d rather let them speak to each user in the language he likes to hear. In my case the first time I required multilingual capabilities was for a small app addressed to fans of Barça football club. I thought it was of local interest so I developed it just in Catalan… and was surprised to realize one the firsts downloads came from Hungary! So I hurried to prepare a multilingual version for the app and since I was in it I prepared a template to avoid redoing the work in future apps. This tutorial explains this template and provides the sources for those who consider it can also save time for them.
We can say something similar about multi-device. The more devices we support the wider acceptance for our app, and the main point here is resizing components height and width according to the screen size. The requirement is similar to multilingual and, as it may look logical, has a similar solution.
So I’ve decided to write a unique tutorial covering both functionalities. To be honest, multidevice was not a great concern for me but I added it into the template since once the structure is set for multilingual it takes very little effort to support also multidevice.
The first idea we could consider for our app to speak several languages and appear smartly in any device is developing separate applications, but it’s pretty obvious that maintenance would become a nightmare. Imagine we intend to support two languages and two devices. This means each modification must be implemented in four projects, so we must package and test each combination and finally publish four versions. It’s true that Google’s Multiple APK allows uploading several apps to Google Play under a single product as explained in this post, but the same site discourages us from following this practice and recommends distributing a single APK as the easiest way to manage and maintain any app.
So it seems pretty obvious that the right approach consists in having a unique app and dynamically adapting its texts and size to the current language and device. A trivial way to do that is to inform the right values in the Initialize event using the SetText, SetHeight and SetWidth properties of components. But for large amounts of components, languages and devices this is really tedious, error prone and difficult to maintain.
A first improvement would consist in placing the texts and components in a list and program a loop in the Initialize event to go through this list and make the assignments (see this example from Puravida). This way the adaptations are encapsulated into known places in the code and it’s relatively easy to add languages, modify translations, etc. This is the approach explained in the first part of this tutorial.
However, most of us are used to a smarter approach from other environments where texts are stored in a separate resource file and the app dynamically consults this file in order to show the texts in the right language. Therefore, the second part of this tutorial shows a mechanism to separate text from code, making our program cleaner and easier to maintain since modifications that affect only texts can be implemented by simply modifying the resource file and without need to redeploy the app. In a professional environment this means we can delegate text styling to external people who can correct or translate texts simply modifying this resource file.
What are we going to build
We’ll develop an app with a couple of screens containing several graphical components including a listpicker to choose the language. Then we’ll program the code blocks necessary to show the component texts in the selected language and resize them according to the device. We’ll also have a button that shows a message in the current language:
About this tutorial
The purpose of this tutorial is that you understand the idea and main implementation aspects of the template, so that you’re able to adapt it to your needs. Even though the sources provided should work directly, if you intend to use the template it’s absolutely necessary that you understand how it works for two main reasons:
- You need to know where to put your own texts
- You must be able to solve any errors in case the resource file is not correct (typically a message like “attempt to get item 4 from a list of length 3”). The code has some protection against these errors but they still may occur
Since the code is quite extensive, the tutorial does not cover every detail. For example we are not going to see the graphical interface (which is quite trivial), and we’ll only remark the most relevant aspects of the code. If you have any doubt about the omitted details you can inspect the sources or leave a comment that I’ll be pleased to answer.
Also notice that this tutorial does not explain much about the resizing algorithm. You can get some more details in the How to adapt to screen orientation post.
To sum up, if you’re interested in using this template I’d suggest you to proceed as follows:
- Download the sources
- Play with the sample app to understand how it works
- Follow this tutorial
- Adapt the template according to your needs
- Develop and publish fantastic apps for several languages and devices
You can install the template by downloading these sources and uploading them into AppInventor. The app should work straight forward.
To use the template in further apps you must create a new project as a copy of these sources and adapt the Procedure InitConstants in order to indicate the real components and their texts and size proportion. Next chapter explains how to do it.
How it works
To start off here is the big picture of the code:
Let’s see the main aspects of these functions. The association of attributes to controls is based on four “lists of lists”:
- COMPONENTS (lstItems): Contains one list for each type of multilingual control: label, button, textbox, screen, listpicker, checkbox, password, message (the last one is not exactly a control but we profit the same structure). Each list contains the components of that type for which we intend to change any attribute (text, width or height)
- TEXTS, HEIGHT, WIDTH (lstItemsTexts, lstItemsHeight, lstItemsWidth): Contain the same exact structure as the COMPONENTS list, but in this case the contents of the inner lists is the value of the related attribute (text, height or width) for the component. Notice we needn’t inform all the attributes (e.g. we don’t inform a height or width for the screen)
The algorithm to associate texts consists simply in a loop through the first list to obtain the components and inform the corresponding attributes obtained from the other lists:
The tricky part here is the way to associate the right attribute depending on each kind of control. This is performed with an IFELSE series in the setComponentAttributes function:
Notice this piece of code shows only how to set the Text attribute. The same must be done for Height and Width, but I’m not showing it here for a matter of space. The real pattern would be as follows:
Now let’s see how to fill in these four lists (lstItems, lstItemsHeight, lstItemsWidth and lstItemsText). We’ll define their values in some constants at initialization time. We could think of assigning their values when defining the variables, but AppInventor does not allow assigning components in the variables definition, so we need to do it in the InitConstants function. The first three lists are not dependent on the language and therefore can be initialized directly:
Instead the texts list is somewhat more complicated because we need to inform the texts for all languages. We’ll implement it with a new level of lists with an item for each language. Next image shows the beginning of the block (not complete for a matter of space):
Once these constants are set, the variables are filled each time the user selects a language by means of the InitTextsList function:
And that’s it. We’ve got an app that adapts automatically to any size and to all supported languages. Encapsulating texts into a unique function greatly eases the app evolution. For example:
- We can add a new language by simply including a new list at the second level of the LST_DEFAULT_TEXTS constant
- If we add new components to our app, we just need to add them also in the four constants in the InitConstants function
There are obviously many enhancements to be considered for the template. Here ara a couple of them, but these are just my own thinkings which may not be the most useful or even viable so I’d be grateful to receive any suggestions about how to evolve the template.
- A more elaborate way to select the current language would be obtaining automatically the device language as explained in this snippet fromPuravida.
- The current resizing mechanism does not take into account text size, and this produces some undesirable effects when the device size changes considerably regarding the original one. The solution would be to have an additional list (say LST_TEXTROWS) where we would be able to change dynamically text size according to the current height, i.e. we could set a text size corresponding to 90% of height for those components informed in the list, and the value would indicate us how many rows are required (e.g. if HEIGHT = 20 and LST_TEXTROWS = 2 the text would be adjusted to 9)
- A limitation of the current solution is that this kind of changes requires deploying again our app. Instead, the mechanism implemented in the second part of this tutorial allows to make some of these changes without redeploying the app (we just need to modify a resource file stored in the web.