Localization (l10n) and internationalization (i18n) intimidate many developers, but Android already provides great tools to aid you in global domination. In this Bay Area Android Dev Group talk, Siena Aguayo teaches you how to localize your app using tools such as layout design flexibility, locale-specific resources, and translation services such as Smartling. Learn how to implement these localization features to target an international audience and substantially increase your market size 🌏👥
Introduction (0:00)
Hi! I’m a software engineer at Indiegogo, and the choice of the word “fearless” in the title here is not an accident: it’s one of our company values. I want to empower you to be more fearless in your implementation of internationalization. In this talk, I’ll go over l10n and i18n, reasons to care about internationalization, how to internationalize your Android app, and some other cool things you can do.
As for me, I mentioned that I work at Indiegogo. We’re a crowdfunding platform. I came to Indiegogo by way of Hackbright Academy, a programming bootcamp for women located here in San Francisco. It’s an awesome institution that empowers more women to become software engineers — woo! I worked on the Indiegogo Android app, and we shipped in four languages: English, Spanish, French, and German. This is where my experience lies. You’ll notice none of those languages are right-to-left languages, and none are non-Latin alphabet languages, so if you were looking for some insight there, this will not be the talk for you.
What are l10n and i18n? (1:42)
Let’s get down to basics. In case you haven’t had the crucial “Aha!” moment with l10n and i18n yet, I’m going to explain it to you. Those numbers actually represent the number of letters in between the first and last letters in the words “localization” and “internationalization.” Cool, right?
I’m going to define “localization” as probably what you think about when you consider putting your apps into other languages. You actually have to translate your content, which means translating your English strings into Spanish, for example. But, what also comes with localization is that you have to format your data differently, depending on the language that you’re targeting. Certain languages format dates, numbers, and currencies differently than they do in English.
Localization also encompasses adjusting your company’s branding according to the target culture. That is all kind of outside the scope of engineering. It’s more focused on the business side of things, so I won’t be guiding you on how to choose which languages you want to translate to.
“Internationalization,” then, is really preparing your app for localized content, for those different number formats and different strings. This encompasses defining alternate resource files, using those number and date formatters, and designing flexible layouts. This probably sounds more like your job as an engineer.
EMPATHY (3:19)
Why should you care about this? Localization and internationalization are not just translation. It also has to do with empathy. I think this is something we should all be talking more about as engineers. We’re all fundamentally people. One of the things I really love about Android is that it’s everywhere. It’s accessible to people of all different kinds all around the world, so really I think this is part of what we should care about as Android engineers.
That being said, if that doesn’t appeal to you because you’re thinking “I’m a robot, I don’t care about people,” here are the technical benefits! Preparing your app for internationalized content really is about separation of concerns. It helps you be more organized, and write more readable code. If you’re thinking, “Well Siena, I really love reading those hardcoded English strings all over my app so I know what’s going on,” Android Studio really does you a favor here. It gives you a little gloss of this ID because it doesn’t want you to miss those hardcoded strings, but it doesn’t want you to use them.
@Override int getTitleId() {
return "Explore";
return R.string.menu_explore;
YAGNI (4:25)
I wanted to mention this concept called YAGNI. We about this a lot at Indiegogo. It stands for “Ya Ain’t Gonna Need It,” and it’s a way to talk about avoiding over-engineering. I feel like some of the pushback that comes from internationalization is something like “Well, it’s a lot of work up front that I don’t want to do until I know I need to do it.” Hopefully, by the end of this post, you’ll believe that there are only a few things you need in order to get you in really good shape for internationalization, and then you can really go to town when you need to.
How Do I Internationalize My Android App? (4:57)
If you’ve followed me this far, you may realize that this could be rephrased as, “How do I prepare my Android app for localized content?” Let’s start off with what content is localizable.
You can localize strings, obviously, but also numbers and currency, dates and time, images with text, and audio and video files. For images with text, you really want to avoid this as much as possible just because it’s a pain to have to manage all those alternate resources with images. If your designer comes to you with a big ol’ image with lots of text on it, you should say, “Hey, that image needs to be a string, and we need to translate that.”
These things can be split into two areas: stuff that you work with in your resource files, and stuff that you deal with in your Java. I’m going to focus on when these bits of data creep into your strings and how you deal with that.
Resource Files (6:04)
Everyone probably knows that you should have a set of default resources, so this is probably your value string’s XML. Then, as you need to, you can define alternate languages you’re going to use. These don’t need to be exact copies of your default resources because it’ll fall back to the default resources, which is kind of cool thing if you’re doing something like US English and UK English. You can just change couple spellings where you need them (i.e. put in some extra U’s), but you don’t have to copy everything over.
Strings (6:37)
Don’t hardcode strings that face the user. This is probably Internationalization 101 material, but I want to underscore it. You want to put them, instead, in your resources file.
Another great tip is you can use nifty position placeholders like %1$s
instead of just doing %s
when you need to put in some dynamic value into your string. You can use ones that have position numbers in them. Instead of I need to %s interpolate %s
you can write I need to %1$s interpolate %2$s
. If grammatically in another language, the thing in position number two needed to come before position number one, this wouldn’t be a problem - Java would know where to stick those dynamic values.
Lastly, providing context for your translators can help them make their job a lot easier.
Android Is On Our Side (7:25)
I want to show you all my favorite thing about Android when I first started. I used to program for iOS, and we didn’t do a lot of up-front work for localization in the beginning. It was really painful when we had to go back and do it all again. Very early on in my programming for Android, I came across a warning on a hardcoded string in a TextView that “Hey, this is an i18n hardcoded string warning. This should be using an @string resource.”
I think this is awesome because Android is really guiding you in the right direction and trying to warn you against doing things that are not a best practice. This is not going to happen if you just call setText
in your Java code though, so you do want to watch out for that.
More Things in Strings (8:16)
Something else we can add in our strings files is a translatable="false"
attribute:
<string name="app_name" translatable="false">
Indiegogo
</string>
You can use this both programmatically and to signal to your translators, “Hey, you don’t need to translate this.” In our case at Indiegogo, we don’t ever translate our company name, so that’s translatable="false"
. This will also bring some helpful lint errors into play if you’ve translated something you weren’t supposed to, or maybe you needed to translate something, but you didn’t.
You can also just kind of just stick in contextual notes for translators. There’s also XLIFF, where you can throw in some IDs for some context. This really helps your translators because you, as someone who works in your business logic every day, know what a certain date represents, but someone who doesn’t work with this every day is just going to be like, “There’s some string value here, what is this?”
<string name="campaign ended label"
note="example: Ended on January 21, 2014">
Ended on %s
</string>
This can have some cool integration points.
Numbers and Currencies (9:34)
This is a fun topic! If you’ve never tried to NumberFormat a number before, I’m here to tell you that it’s really easy and you don’t have to be afraid.
import java.text.NumberFormat;
NumberFormat numberFormat = NumberFormat.getInstance();
textView.setText(numberFormat.format(36965));
This is what it ends up looking like in English and Spanish:
Contributions 36,965 & Contribuciones 36.965
You see we’ve added this nice comma in English, and then in Spanish we’ve used the correct delimiter, which is actually a period. This works great, and again, is simple to implement.
Pluralization is also pretty easy, and if you’ve ever worked with Rails, it works similarly. You can just define your quantities if you need to special case the one or even the two in other languages. If you look in the documentation on plurals, they give a lot of really interesting examples in other languages about what quantities are special cases. That’s just something you don’t normally think about if you only ever think in English.
strings.xml
<plurals name="number_of_fb_friends">
<item quantity="one"%s friend</item>
<item quantity="other"%s friends</item>
</plurals>
res.getQuantityString(
R.plurals.number_of_fb_friends,
count,
numberFormat.format(count)
);
I want to just to give a little PSA about using string interpolation here instead of an integer interpolation, because you probably want to NumberFormat this number, and the NumberFormatter is going to return a string. This getQuantityString
method call uses the second argument here as an int for the quantity selection, and then the second one is actually what gets interpolated. Since NumberFormat
returns the string, we need to use a string placeholder. I just saw a lot of stuff on Stack Overflow with examples like “count count,” and that’s going to mean that you use d or i in your strings. I want you to be aware of that because it’s going to mean that you don’t have any NumberFormatting going on.
Dates and Time (11:27)
The out-of-the-box dateFormatter gives you some date formats that you can just use, and there are only three defaults: short, medium, and long. It works similarly to NumberFormatter. You just instantiate it and call .format
.
// DateFormat.getDateFormat (short)
// DateFormat.getMediumDateFormat
// DateFormat.getLongDateFormat
DateFormat dateFormatter = DateFormat.getDateFormat(context);
Date now = new Date();
dateFormatter.format.(now);
en_US ja_JP
short: 8/23/2015 short: 2015/08/23
medium: Aug 23, 2015 medium: 2015/08/23
long: August 23, 2015 long: 2015年8月23日
Here we have English and Japanese date formats. You can see that short and medium are actually the same in Japanese. There’s no semantic difference in Japanese for those, so just be aware that those might not always mean something that is necessarily shorter.
What if you want a custom date format, for example maybe just a month and a year, instead of a whole month/day/year? This is awesome if you’re working with API 18 and up, as there’s this totally great method you can call: getBestDatetimePattern
, and you just pass it your locale. It’s actually a component string, so it actually doesn’t care about the order it’s in. When you format that date, it just does the right thing.
String formatString = DateFormat.getBestDateTimePattern(
Locale.getDefault(), "MMMMyyyy"
);
SimpleDateFormat dateFormatter = new SimpleDateFormat
(formatString);
Date now = new Date();
String dateString = dateFormatter.format(now);
en_US: August 2015
ja_JP: 2015年8月
You’ll notice in Japanese, the year actually comes first. The API just did that all for us.
However, if you are like us at Indiegogo and you target APIs below 18, this gets a little hairier… ¯\_(ツ)_/¯
We ended up collecting date formats as we needed them, and we kept them in our resources. This isn’t a perfect solution, though. Let’s say someone is using the Indiegogo Android app in a language that we don’t support; Android will try to format that date as if we’re in that locale, so it’s going to look a little weird to them. If you really have to format a lot of dates, you might want to look into a library like Joda-Time because they’ll just do this all for you, but it’s another library you have to add.
Boo to SpannableString (13:44)
If you’ve ever needed to style a bunch of stuff and you tried to work with SpannableString, you’ll know it’s a big pain to work with. It’s a pain because you have to know the index of the start and the end of whatever it is you’re trying to style differently. You can imagine that that might be hard to tell if you’re dealing with a string that’s going to be changing, depending on your locale. This is not a foolproof solution. If you can, I recommend you replace any kind of SpannableString nonsense with HTML. That won’t support all the tags, obviously, but you can kind of get by, and then you can set text from your HTML.
Try to avoid:
public void setSpan (Object what, int start, int end, int flags)
Instead, replace with HTML in your strings.xml
:
textView.setText(Html.fromHtml(
res.getString(R.string.fixed_funding))
);
Build Flexible Layouts (14:35)
If anyone is looking ideas for an awesome conference talk, I think building flexible layouts would be it. It’s a really big topic actually.
I want to give a shout-out to wrap_content
, amirite? If you’ve ever programmed for the web, you know that sometimes it’s really hard to make stuff expand to its content or to its parent, so wrap content
is awesome.
That being said, do be mindful of line lengths. Sometimes the string can be longer in another language, and maybe you weren’t accounting for that. I find for the most part though the Android UI framework is super flexible with this kind of thing, and things will just kind of move around more or less as you expect them to.
At Indiegogo, there is a percent counter at the bottom of the funding meter that reads something like “41% funded” - as an exercise for you, what would happen if, in a different language, you needed to switch this percentage and word around?
You might think, “Okay, well, I have two TextViews. One is bold, and the other one isn’t.” All of sudden, you’ve got this huge problem where you need to switch these two pieces of data around. Instead, just put that in a string with some HTML: all of a sudden, you don’t have a problem.
Other Cool Things (15:43)
There’s a sweet thing in Android Studio called Translations Editor. If you open your strings file, you’ll seen this little notification bar pop up. If you click “Open editor,” you can actually see all of your translations side by side. Then if you needed to quickly do a bunch of work across all of your translations, this can be a nifty way to get there fast. There’s also an “Untranslatable” checkbox, so you can quickly do that across all your files.
There’s also an “Order a translation” button, which links to the Google Play App Translation Service. I brought this up at Droidcon NYC, and someone said that the quality wasn’t that awesome because they use a lot of machine translation, so YMMV. I just think that it’s cool they integrated this link right into Android Studio, so you can start thinking about how to localize your app.
Another cool thing is in the Google Play Developer Console, where you’ve got these statistics that tell you what languages and countries your users are in and how your app compares to others in your category around the world. This is the sort of thing you can bring to your PM and say, “Hey, I think we should localize into these languages.” This might help build a business case for that, if that’s something you need to be concerned about.
I want to give a final shout-out to Smartling, who we use at Indiegogo. They’re a paid translation service, with real humans who translate things. They have an API and a Java SDK. We use Smartling to update our non-English strings with just a push and a pull that we run from the command line, and it’s super easy. It’s just a little Bash script, so push up and pull down. One drawback of this, though, is that it does completely overwrite all of our non-English strings, so if we edit something locally and then do this, we lose all those changes. That does mean we have to edit all of our non-English strings directly in the Smartling interface, but I think that’s worth it for the easy update feature.
Further Reading (17:55)
The official Android documentation on this stuff is actually pretty good and pretty comprehensive and pretty interesting in some cases, so I recommend you check that out. Smartling is also a cool thing.
- Localizing with Resources
- Localization Checklist
- Google Play App Translation Service blog post
- Smartling API
- Smartling Java SDK
Now go out and be fearless!
Receive news and updates from Realm straight to your inbox