#82: Localizing an App.
00:00:00
◼
►
Hello, and welcome to Developing Perspective.
00:00:03
◼
►
Developing Perspective is a podcast discussing news of note in Iowa's development, Apple,
00:00:07
◼
►
and the like.
00:00:08
◼
►
I'm your host, David Smith.
00:00:09
◼
►
I'm an independent iOS developer based in Herne, Virginia.
00:00:12
◼
►
This is show number 82, and today is Tuesday, September 25th.
00:00:16
◼
►
Developing Perspective is never longer than 15 minutes, so let's get started.
00:00:19
◼
►
All right, so this is going to be the next in the series of episodes that I'm going to
00:00:24
◼
►
be talking about the new app I'm working on.
00:00:26
◼
►
It's a weather app.
00:00:27
◼
►
I think I've never mentioned its name before, and I'm going to do that now.
00:00:33
◼
►
The app is called Check the Weather, so that's hopefully being a little bit playful, because
00:00:37
◼
►
I know what I do many times a day is I'm always like, "Oh, let me check the weather.
00:00:41
◼
►
Let me check the weather."
00:00:42
◼
►
So that's what I named the app.
00:00:43
◼
►
And so this is going to be the next thing in the series of posts I've been doing about
00:00:49
◼
►
how I've been building it, some of the considerations that go into that.
00:00:53
◼
►
And these are very developer-oriented, and hopefully I've gotten a lot of good feedback
00:00:56
◼
►
from people who say it's really great to kind of see the inner workings of how, how you
00:00:59
◼
►
actually go about building something like this.
00:01:02
◼
►
And so I'm going to keep doing that.
00:01:03
◼
►
And today I'm going to be talking about localization.
00:01:06
◼
►
And this is something that I've never done before.
00:01:08
◼
►
And if you're a long time listener of Developing Perspective, you'll know that about, I think
00:01:12
◼
►
about two months ago, a month and a half ago, it was something that I talked about and I
00:01:15
◼
►
did an episode about, talking about how I struggle with that.
00:01:19
◼
►
And it's something that I've sort of always thought, oh man, I really wish I did, or I
00:01:23
◼
►
really think I should, or those types of things.
00:01:25
◼
►
But the reality is I never have.
00:01:27
◼
►
And I've been doing this for four, four and a half years now.
00:01:30
◼
►
I mean, it's a long time I've been making apps.
00:01:32
◼
►
I've never done any localization work at all.
00:01:35
◼
►
And so it was kind of like, well, why isn't that?
00:01:37
◼
►
Why aren't I doing this?
00:01:38
◼
►
It seems like if I look at my sales,
00:01:40
◼
►
at least 25% of my users probably
00:01:45
◼
►
don't speak English as their first language.
00:01:47
◼
►
And that's just basically, you know, of app sales.
00:01:50
◼
►
Certainly the United States is the largest market.
00:01:52
◼
►
In my experience, it's at least 50 to 60% of app sales
00:01:55
◼
►
probably in the United States, at least of my apps.
00:01:58
◼
►
Obviously that's probably different for different types of apps.
00:02:02
◼
►
Then you have the next big ones are Canada, the United Kingdom, Australia, New Zealand,
00:02:06
◼
►
which all of course speak English.
00:02:08
◼
►
There are sort of slight differences in terms of spelling or terms or things like that.
00:02:14
◼
►
And then you start to get into all the rest of the world.
00:02:16
◼
►
And so you start to talk about other big countries like Germany, France, Spain, Italy.
00:02:22
◼
►
And so the list goes on from there.
00:02:24
◼
►
I mean, there are a lot of countries with a lot of phones and a lot of users who may
00:02:30
◼
►
struggle to use your app if you only are providing it in English.
00:02:33
◼
►
And so this is kind of a process of taking your application.
00:02:38
◼
►
I mean, I'm sort of starting from a place of that many of the people who listen to this
00:02:43
◼
►
are English speakers.
00:02:45
◼
►
And obviously that's based on the fact that I only ever do episodes in English.
00:02:48
◼
►
And so even if English is not your second language, in order for this to be useful to
00:02:52
◼
►
you, you probably speak English.
00:02:57
◼
►
But it's taking that sort of that basis of saying,
00:03:00
◼
►
here's an app that's in English,
00:03:03
◼
►
and I'm going to try and make it feel comfortable,
00:03:05
◼
►
to feel natural, and to be appealing to a user
00:03:07
◼
►
in another part of the world.
00:03:11
◼
►
And so this is sort of that process,
00:03:12
◼
►
and this is how it went.
00:03:14
◼
►
And at first, on the outset, I'll say,
00:03:16
◼
►
I was surprised, and pleasantly so,
00:03:17
◼
►
about how easy, or maybe easy is the wrong word,
00:03:18
◼
►
how straightforward it was to do a localization of this app.
00:03:22
◼
►
The app is fairly simple, and a lot of it's graphical and number-based, so it's not too
00:03:28
◼
►
But overall, the tools and the process that I had to go through to actually make this
00:03:31
◼
►
happen was fairly straightforward.
00:03:33
◼
►
And so I was pretty happy with that.
00:03:36
◼
►
And so this is kind of how it worked.
00:03:38
◼
►
So to start with, the way you do localization, primarily in sort of cocoa development, iPhone,
00:03:45
◼
►
iPhone, Mac, I think it's the same,
00:03:50
◼
►
is there's a concept of an NS localized string.
00:03:52
◼
►
And this is basically just a key value store
00:03:56
◼
►
that you associate with your application,
00:04:01
◼
►
where rather than providing text in terms of a static string,
00:04:03
◼
►
so say this is a button called refresh,
00:04:07
◼
►
usually either in a NIMF file or in a source code,
00:04:10
◼
►
you would say, you know, button dot, you know, set title,
00:04:14
◼
►
you know, ampersand, quote, refresh, close quote,
00:04:19
◼
►
and you would give it that literal string.
00:04:21
◼
►
And all you do for localization is rather than
00:04:23
◼
►
hard coding that value directly, you pass in that,
00:04:27
◼
►
you can certainly do it this way,
00:04:28
◼
►
or you can do it in a variety of different ways,
00:04:30
◼
►
but the simplest way is you rather than passing
00:04:31
◼
►
that string indirectly, you pass that token
00:04:35
◼
►
to a function called NSLocalizeString.
00:04:37
◼
►
And what that does is at runtime, rather than just displaying that token directly, it'll
00:04:43
◼
►
run through sort of a key value store or a lookup table that you associate with your
00:04:49
◼
►
application that has a bunch of translations.
00:04:52
◼
►
So if you're in English, it will display whatever the value associated with that token is in
00:04:59
◼
►
And if you're in Japan, it will be Japanese.
00:05:00
◼
►
And if you're in France, it will be French.
00:05:02
◼
►
And it's actually not based on the country you're in.
00:05:04
◼
►
It's based on the language that the device is set to.
00:05:07
◼
►
So that's probably a subtle distinction,
00:05:09
◼
►
but it's important that it's not about where the user is,
00:05:11
◼
►
it's about what the user has told their phone
00:05:14
◼
►
or their iPad to display.
00:05:17
◼
►
And then it displays it, and that's really all you do.
00:05:20
◼
►
I mean, the process I went through is probably sort of,
00:05:22
◼
►
it's not great, but it works pretty well,
00:05:25
◼
►
is I just went through my app, I did a global search,
00:05:28
◼
►
and when I came to time to sort of go through
00:05:30
◼
►
and do the localization, I just did a global search
00:05:33
◼
►
for all my literal strings, and I just looked through them,
00:05:37
◼
►
and all the ones that made sense to actually localize.
00:05:40
◼
►
I just went in, changed the token.
00:05:42
◼
►
I changed that from doing a direct string literal
00:05:45
◼
►
to NSLocalizedString.
00:05:47
◼
►
And I did the sort of lazy approach
00:05:48
◼
►
where you make the token start off as the English string.
00:05:53
◼
►
So a lot of my string, my tokens are just literals.
00:05:56
◼
►
And then you give a comment.
00:05:58
◼
►
And the comment is important.
00:05:59
◼
►
It's the second parameter to the NSLocalizedString function.
00:06:02
◼
►
But the importance about the comment
00:06:04
◼
►
is that's an opportunity that you
00:06:05
◼
►
you have to tell the translator, to tell somebody who's looking at this token, what it means
00:06:11
◼
►
And that's important often for localization because this translator is trying to give
00:06:16
◼
►
you not just like a raw translation, they're trying to give you a relevant translation.
00:06:21
◼
►
So it's not, you know, a lot of words in English especially can have multiple meanings in multiple
00:06:27
◼
►
And so you want to give as much context.
00:06:28
◼
►
So rather than necessarily it being refresh and then you just your comment is a refresh
00:06:33
◼
►
which is completely useless, you can say,
00:06:38
◼
►
"A button pressed when the user is trying to refresh
00:06:40
◼
►
the displayed information."
00:06:44
◼
►
And the translation for that may be different
00:06:46
◼
►
in a different language than if it was a different kind
00:06:48
◼
►
of refresh, than if it was, I don't even know.
00:06:51
◼
►
But it's like you're trying to give as much context
00:06:54
◼
►
to that as you can.
00:06:57
◼
►
And then basically that's it.
00:06:59
◼
►
And then you go into Xcode and you say,
00:07:00
◼
►
"I'm going to generate some strings files."
00:07:01
◼
►
And there's a little tool called Gen Strings that comes with Xcode or the developer tools.
00:07:06
◼
►
I think it may be part of the command line tools for a set, but it's pretty straightforward
00:07:09
◼
►
once you get Gen Strings, and that's G-E-N strings, that's one word.
00:07:13
◼
►
And you just run that over your source code or your nib files or all kinds of things,
00:07:18
◼
►
and it'll output what Xcode calls a strings file.
00:07:22
◼
►
And that's just a dot strings file as the extension.
00:07:26
◼
►
basically a look-up table of comment, token equals value.
00:07:30
◼
►
And that's it.
00:07:33
◼
►
And it's just in a very simple format
00:07:35
◼
►
that's machine parsable and all that.
00:07:38
◼
►
And basically that's it.
00:07:38
◼
►
Once you've got that, you've generated your strings file,
00:07:41
◼
►
you now have the basis of what you need
00:07:43
◼
►
to do your localization.
00:07:44
◼
►
That's what you're going to send to a translator.
00:07:46
◼
►
That's what you're going to work through.
00:07:48
◼
►
One thing I will say,
00:07:49
◼
►
just before I get too far ahead of myself,
00:07:52
◼
►
in an ideal world, you probably make your tokens
00:07:54
◼
►
more meaningful and more generic.
00:07:59
◼
►
I've run into this a little bit myself,
00:08:02
◼
►
where I have these very funny things
00:08:03
◼
►
because my tokens were just direct English words
00:08:05
◼
►
rather than actually being tokens
00:08:08
◼
►
in a more abstract sense.
00:08:10
◼
►
So rather than saying refresh,
00:08:15
◼
►
I could have said refresh button title as my token,
00:08:16
◼
►
which isn't what would be displayed to the user.
00:08:22
◼
►
And in the English localization,
00:08:20
◼
►
need to go and set the value of that.
00:08:25
◼
►
But the thing that's often what you'll end up with is,
00:08:27
◼
►
the lazy approach is just to use your English words as the tokens.
00:08:30
◼
►
But what that means is, of course,
00:08:33
◼
►
if you want to change the copy in your app,
00:08:35
◼
►
you're going to end up with these very strange things
00:08:38
◼
►
where the token isn't abstract.
00:08:39
◼
►
It's a very literal token, it's refresh,
00:08:43
◼
►
but it's actually going to turn into update.
00:08:45
◼
►
So you have this refresh equals update in your strings file,
00:08:46
◼
►
which is a little weird.
00:08:51
◼
►
So that's just in an ideal sense, in a true sense,
00:08:53
◼
►
which would probably be wise to do,
00:08:56
◼
►
is to make your tokens abstract.
00:08:58
◼
►
So then the next thing I did is you have your strings file,
00:09:00
◼
►
you just want to look at it, make sure it's good and clean
00:09:03
◼
►
and parsed, and you're ready to go.
00:09:05
◼
►
There's a great tool that I found for doing that.
00:09:08
◼
►
This is by Oliver Drobnik.
00:09:10
◼
►
You probably know him online as Coconetics,
00:09:12
◼
►
and he has a tool called Linguin,
00:09:14
◼
►
which I have a link to in the show notes.
00:09:12
◼
►
And basically, this is a tool for managing strings files.
00:09:15
◼
►
It doesn't do really much beyond that.
00:09:17
◼
►
But what it's really great for doing is it can look at your
00:09:22
◼
►
Xcode project file and look at the strings files that you
00:09:24
◼
►
have, tell you what tokens you're missing, what tokens
00:09:27
◼
►
you have, and kind of just lays them out in a nice grid
00:09:30
◼
►
format, which I find really helpful.
00:09:32
◼
►
So I highly recommend that if you're going to do any
00:09:33
◼
►
localization work.
00:09:34
◼
►
It's in the Mac App Store.
00:09:35
◼
►
It's pretty inexpensive.
00:09:37
◼
►
It's just a great little tool for kind of managing this.
00:09:40
◼
►
So the next process, once you have your strings files,
00:09:45
◼
►
is you're going to need to take those and get them translated.
00:09:48
◼
►
And there's a variety of ways you could do that.
00:09:50
◼
►
You could ask friends, customers, people you know.
00:09:52
◼
►
If you're a linguist yourself, maybe you can just do them yourself.
00:09:55
◼
►
But basically what you're going to be doing is trying to take all those tokens and translate them.
00:09:58
◼
►
What I ended up doing is I was looking for a variety of sources for services for doing this.
00:10:02
◼
►
And I posted on Twitter and I got a lot of recommendations and feedback.
00:10:06
◼
►
And there seems like there's maybe four main vendors for doing this.
00:10:11
◼
►
There's Applingua, WordCraft, iCanLocalize, and Tetris.
00:10:17
◼
►
And I'm not sure if that's how you say it, the T-E-T-H-R-A-S,
00:10:21
◼
►
which is actually the one I went with.
00:10:25
◼
►
And basically all of these services take in a strings file.
00:10:27
◼
►
They're very iOS oriented.
00:10:30
◼
►
So they take a strings file directly, they parse it,
00:10:32
◼
►
they manage it on their servers,
00:10:32
◼
►
and they help kind of work you through that process.
00:10:34
◼
►
They do different amounts of hand-holding, and what I ended up doing is I went with Tetris
00:10:40
◼
►
because they were a nice managed approach.
00:10:45
◼
►
And by that I mean I'm not interacting with my translators directly.
00:10:49
◼
►
I'm not trying to do quality assurance or project management or any of those types of
00:10:54
◼
►
You know, they're taking care of that for you, and you pay a slight premium for that.
00:10:57
◼
►
I think it's about 10 to 18 cents a word, which seemed pretty middle of the road.
00:11:02
◼
►
You can go a little bit cheaper if it's something like I can localize, but I feel like you're
00:11:06
◼
►
doing a bit more of that.
00:11:08
◼
►
You're deeper in the process, which for me, I'd rather spend a little bit of extra money.
00:11:12
◼
►
It's not a huge amount, and I'd rather spend that little bit of extra money to have someone
00:11:16
◼
►
manage that for me who's done this before, who can interact with translators in a way
00:11:19
◼
►
that makes sense.
00:11:20
◼
►
So I'm going to talk about my experience with them and how that went.
00:11:25
◼
►
And so basically, I created an account with them,
00:11:28
◼
►
pretty straightforward.
00:11:29
◼
►
I uploaded my strings file.
00:11:30
◼
►
I had written an app description that I also submitted.
00:11:33
◼
►
So they translated that, and I'll
00:11:34
◼
►
include that in my iTunes Connect
00:11:36
◼
►
when I submit it so that if you're in that country,
00:11:38
◼
►
you can read a description in your language, which I hope
00:11:41
◼
►
is kind of useful and nice to them.
00:11:43
◼
►
I'm probably also going to actually do the English one
00:11:45
◼
►
below it, just in case you also speak English
00:11:47
◼
►
or in case there's something funny in the translation
00:11:50
◼
►
that hopefully that'll help.
00:11:52
◼
►
But I submitted both of those.
00:11:54
◼
►
I had a pretty low word count, it wasn't too bad.
00:11:56
◼
►
The actual app itself only has 122 words
00:11:59
◼
►
for translation, which is not too bad.
00:12:02
◼
►
And like I said, a lot of that's just,
00:12:04
◼
►
the nature of the app isn't very word-based.
00:12:07
◼
►
And most of those are actually in the settings
00:12:08
◼
►
or tutorial areas of the app.
00:12:09
◼
►
They're not actually part of the functional part,
00:12:12
◼
►
because most of the functional part
00:12:14
◼
►
is either graphics or displaying numbers.
00:12:16
◼
►
It's 26 degrees and 18% chance of rain.
00:12:18
◼
►
And so basically I just went through,
00:12:21
◼
►
submitted them all, picked the languages I want.
00:12:23
◼
►
I just kind of did, I guess you could sort of call the big five or the big six, depending
00:12:28
◼
►
on how you want to look at it.
00:12:29
◼
►
I did German, French, Italian, Japanese, and Chinese, and submitted those.
00:12:36
◼
►
And Spanish, sorry.
00:12:37
◼
►
So those were my six.
00:12:39
◼
►
And just sort of submit them.
00:12:41
◼
►
You sort of go through, you buy it.
00:12:44
◼
►
You're paying by word.
00:12:45
◼
►
It's all upfront pricing, which was great.
00:12:47
◼
►
And you just kind of wait a day or two.
00:12:49
◼
►
I mean, I was very impressed by the turnaround time.
00:12:51
◼
►
It was only, I think the longest translation was my Japanese one and it took probably about
00:12:55
◼
►
48 hours from when I hit submit.
00:12:58
◼
►
A lot of them were coming in quicker than that and it was just great.
00:13:01
◼
►
I mean, the way they do it, which I think a lot of them do, there's this nice little
00:13:05
◼
►
kind of interactivity with the translators that's asynchronous and sort of low stress,
00:13:11
◼
►
but a lot of times the translators will find little questions or sort of questions or hiccups
00:13:15
◼
►
or things that they're not sure about.
00:13:17
◼
►
And so rather than just kind of guessing,
00:13:20
◼
►
they can create what they call discussion questions
00:13:23
◼
►
in their system, and I think a bunch of people
00:13:25
◼
►
do a similar kind of thing, where they'll ask you a question
00:13:27
◼
►
and they'll say, "Hey, so I see I can't find,
00:13:31
◼
►
"so if I'm translating this word,
00:13:32
◼
►
"there's like two or three different ways
00:13:34
◼
►
"I could translate it, which of these sort of meanings
00:13:36
◼
►
"or contexts will, you know, would work best for you
00:13:39
◼
►
"in this context?"
00:13:40
◼
►
And you can insert and go back and forth,
00:13:42
◼
►
or they'll sometimes be like, "Here's two options,
00:13:44
◼
►
"this one's shorter, but this one's,
00:13:46
◼
►
"this longer one's better."
00:13:47
◼
►
So if you have space, use the longer one.
00:13:49
◼
►
If it's, basically, a problem, use a shorter one.
00:13:51
◼
►
And that was basically it.
00:13:52
◼
►
Now I had my translated list of things,
00:13:56
◼
►
some things that made that process go better,
00:13:58
◼
►
really good comments, and submitting
00:14:00
◼
►
screenshots of your app.
00:14:01
◼
►
I think translators really like being able to see where
00:14:04
◼
►
that word is going in context.
00:14:05
◼
►
So two things you can really do to help them out,
00:14:08
◼
►
and having nice clean strings.
00:14:11
◼
►
So now I have my strings.
00:14:12
◼
►
I pop them into Xcode, and that's it.
00:14:15
◼
►
Now my app is localized into seven languages, I guess, six foreign and one English, and
00:14:20
◼
►
it's seemed to work pretty great.
00:14:22
◼
►
I can launch it in those apps and it looks great.
00:14:24
◼
►
I don't really speak those languages, so it's hard for me to know necessarily of the quality
00:14:27
◼
►
of the translation.
00:14:28
◼
►
But going through one of these managed vendors, the goal is that a lot of that quality has
00:14:33
◼
►
been taken care of because they've been QA'd internally by their linguists or by two linguists
00:14:38
◼
►
or by someone who at least knows what's going on.
00:14:39
◼
►
And so that's all.
00:14:41
◼
►
That's all I did, and it's great.
00:14:42
◼
►
and I look forward to seeing what that does for my sales abroad. Hopefully it increases them. That's certainly the goal.
00:14:47
◼
►
Alright, that's it for today's show. As always, if you have questions, comments, concerns, or complaints,
00:14:52
◼
►
I'm on Twitter @_DavidSmith. And otherwise, I hope you have a great week, happy coding, and I will talk to you soon. Bye.