The first ever conference talk I gave was about scripting in Swift in early 2015, pretty soon after Swift was released in mid-2014. Since then, I’ve been using Swift for even more scripting and automation tasks. Most recently, we’ve been working on internationalization and localization for the Workflow app. In this talk we’ll go over the steps that went into this localization process, how Swift makes things much more fun and easier, and how to get up and running with Swift scripting so you can start writing even more Swift!
Introduction (0:00)
AltConf was the first iOS Conference that I ever attended back in 2013, and I’ve been attending ever since. This is my first time speaking here, so thank you for having me here.
So as of I think five weeks ago I’ve been at Workflow. I just joined the team there, before that I was at Venmo. And one of the first things that I started working on as soon as I joined is localization. Today I would like to talk a little bit about what we’ve been doing there, and in particular how to use Swift scripting to help us achieve localization.
Why Localization? (0:45)
First of all, why do we even care about localization? When we look at our users for Workflow, beyond the United States the top three countries are Germany, China, and Japan. If you look even more globally, the English language has over 300 million native speakers. That seems like a huge number, but if you look at Spanish, Spanish has more than 400 million native speakers. And if you think that’s large, well the Chinese, if you include all the dialects, has 1.3 billion. That’s 1,300 million. Four times more people speak Chinese than English. It’s huge. Why am I even speaking in English right now to all of you? I guess because I don’t know Chinese or Spanish, but whatever.
Global Audience = 💰 (1:35)
If we increase the number of users that we can reach, we have more potential users for Workflow or the app that you’re working on. If you have more potential users, you have more actual users that you can get. And if you have more users, hopefully you can make more profit, assuming you’re making some sort of money per user. And if you have profit you have more money to put back into your product, which is great, because that means that you have happy long-term users.
Sometimes I’m using an app, and I really like it, and I think, “Well, this is great.” But the cynical side of me is thinking, “Wait, how long is this app going to live for?” And I get a little bit sad when I think, “Oh, they’re not making any money, so they’re probably not going to exist for over two years.”
I think we owe it to our users to make sure our apps exist for a long time, and I think localization and bringing it to a global audience is one of the many ways we can do that.
NSLocalizedString (2:33)
Let’s get started with localization.
If you look at localization from the very micro level, it’s all based on NSLocalizedString
. So in your code you might have might have an OK
button title that has title OK
, and a Cancel
button title with the title Cancel
. They’re both NSLocalizedString
s and if you run $ genstrings *.swift
it’ll generate a localizable.strings
file that looks something like this:
"OK" = "OK";
"Cancel" = "Cancel";
Some placeholders for what your localizations will be. And this you want in your Base.lproj
, that means your base localization. For most people it’s probably going to be English. And then based on that you can have another lproj
file, maybe for Dutch, and have the translations in Dutch in that localizable.strings
file. Then you can do something similar for another language, like Japanese.
Run (3:50)
Let’s talk about this canonical example of languages and how difficult they are.
In the English language, you can use the word “run” to mean something like “run a workflow,” “run a program,” or “run a command.” But you can also say stuff like “run a marathon.” In all these cases, we use the exact same word, “run.” But in Japanese, you say something like 「wakufuro jikko suru」 for running a Workflow. And 「marason hashiru」 for running a marathon.
You might be able to hear that there’s no overlap, or there might be a syllable of overlap. But in terms of words, there isn’t any overlap. There is no “run” word that we’re using for both sentences.
Disambiguating Language (4:36)
How do we differentiate between these two different words? 「jikko」 and 「hashiru」.
They both mean “run,” but in different senses of the word. Well, we could use the comment in NSLocalizedString
to disambiguate them. We can say NSLocalizedString("run")
. For this one we want it to be running a workflow and make that clear to our localizers.
let runWorkflowTitle = NSLocalizedString("Run", comment: "Run workflow title")
/* Run workflow title */
"Run" = "Run"
If you run genstrings
it’ll put the comment in, the comment right there.
And then similarly for go on a run, we want to say, this is for going on a run, and not running a workflow. And if you run genstrings
it’ll produce something like this:
let goOnARunTitle = NSLocalizedString("Run", comment: "Go on a run title")
/* Go on a run title */
"Run" = "Run"
So, this is great, but we have a slight problem here.
Use Unique Keys (5:22)
We have two keys that are exactly the same. They’re both run
. And that’s a problem because when you have localizable.strings
files, you’re supposed to have different, unique keys for each key. You need keys in your localizable.strings
file.
A lot of people do something like this.
/* Run workflow title */
"run-workflow.button.title" = "Run"
/* Go on a run title */
"go-on-a-run.button.title" = "Run"
Instead of just having run
, you might have something like run-workflow.button.title
is “run” for one case and go-on-a-run.button.title
is also “run,” but in a different case.
I’ve also seen something like this, using all caps snake case, RUN_WORKFLOW_BUTTON_TITLE
is “run,” and GO_ON_A_RUN_BUTTON_TITLE
is “run.” Then you can localize it to Japanese or whatever language you want. And in code it will look something like this.
/* Run workflow title */
"RUN_WORKFLOW_BUTTON_TITLE" = "Run"
/* Go on a run title */
"GO_ON_A_RUN_BUTTON_TITLE" = "Run"
So you have your NSLocalizedString
, and you have your snake case title and your comment to disambiguate. But I’m looking at this and thinking, “This is a little bit redundant.”
NSLocalizedString("RUN_WORKFLOW_BUTTON_TITLE", comment: "Run workflow title"
NSLocalizedString("GO_ON_A_RUN_BUTTON_TITLE", comment: "Go on a run title"
That’s kind of redundant and a little bit annoying as a developer if I have to keep typing the same thing over and over again.
If you thought about it, how can we make this a little bit easier?
Well wouldn’t it be nice if we could just have NSLocalizedString
run with Run workflow title
and this other NSLocalizedString
also run with Go on a run title
?
But we can’t do that because that’ll produce the same key. But it’d be nice if we can maybe automatically have it generate a different key for each one based on the comment that we provided it.
So maybe the first one, given the comment “Run workflow title,” it’ll be run-run_workflow_title in snake case. If we look at it,
NSLocalizedString("Run", comment: "Run workflow title")
NSLocalizedString("Run", comment: "Go on a run title")
/* Run work flow title */
"run-run_workflow_title" = "Run";
/* Go on a run title */
"run-go_on_a_run_title" = "Run";
Then it will automatically generate, if you run genstrings
or some command, run the two unique keys with the same values and disambiguate it.
Then you can localize it to Japanese or German or whatever language you want.
Two Goals (7:54)
The first goal that we have is we want to have effortless NSLocalizedString
-ing. We want to make it as easy as possible for us developers to maintain localized strings without having to go back and forth between localizable.strings
files and having crazy snake case titles and keys.
It gets really annoying. It’s already hard to begin with to localize apps, so why make it even more difficult? And another thing that we thought was cool was this idea of continuous localization. We heard this idea floating around, and I think Airbnb does this, but basically what this means is you want to load your localizable.strings
files from the cloud or your API.
What this allows you to do is instead of waiting on your translators to finish translating all the strings in your app, you can ship the app and then little by little, as the strings are ready, just upload them. And your app will get the translations as they get translated.
WFLocalizedString
(8:53)
So we have these two goals.
Number one, effortless NSLocalizedString
-ing via auto-generated localization keys in the comments.
And the second one is continuous localization via loading strings from the cloud.
The first thing that we have to do is we can’t use NSLocalizedString
because we need to change the implementation of it, because we need to load it from the server. And we’re also using different keys, so we want to have a custom implementation. What we did was we defined a WFLocalizedString
, which is a Workflow localized string.
$ genstrings -o en.lproj -s WFLocalizedString
Then there’s this option on genstrings
that lets you specify a separate name for your custom localized string, which is -s
, and have that specify. Instead of looking for NSLocalizedString
, genstrings will instead look for WFLocalizedString
s and dump out everything into localizable.strings
.
But we had a little problem here because genstrings
assumes that you want to use the same key in your code in the NSLocalizedString
and the localizable.strings
file.
So we couldn’t use that, so what do we do?
Scripting (10:11)
Well if we can’t use genstrings
, maybe we can write our own, and we can script it. And maybe we can do it in Swift.
Yay! Swift.
Some of you might be thinking, “Why would you script it in Swift?”
Well first of all, maybe it’s something that’s familiar to you. If you’ve been running Swift for a while, it might be easier than running a Ruby script or a Bash script. For me, writing Ruby scripts and Bash scripts, even reading them is a little bit hard, so it’s nice to be able to script in a language that I’m already familiar with.
Or maybe it’s something new to you. And maybe you can’t write Swift in your code base yet for whatever reason. Maybe you’re waiting for ABI stability or something else, but you still want to start writing Swift.
Well I think writing Swift scripts is a good, kind of stepping stone into writing more Swift. Then you have fewer language dependencies. If you have a script in Bash, it might be really hard for someone new on your team to just come in and be asked, “Hey, can you modify this script that we have here?” That would be kind of scary. At least I’m terrified of Bash, so I think it’s great that we can have Swift and have something that looks a little bit more familiar for new developers.
And then it’s type-safe, which is great, unlike Python or Ruby or other scripting languages out there. We have type-safe use Swift, and Swift will catch at compile time all the bugs that you might have in your code. And even though it’s type-safe, it still feels lightweight and script friendly.
Imagine running a script in Java, which is also type-safe, but I don’t really consider it a scripting language, per se.
And last but not least, it’s really fun.
Regex Parsing (11:56)
Let’s get started.
So here is the master plan.
We’re going to get all the .swift
, .m
, and .mm
s. So Swift, Objective-C, and Objective-C++ files, if you have them. We have some of those files because we use ComponentKit. And then two, we’re going to parse out all of the WFLocalizedString
s from each file. And then lastly, we’re going to write it all to Localizable.strings
.
I think the most interesting part in all of the three-step plan is the middle one. The first and last are just file I/O, pretty cookie cutter. But the middle one is string parsing. And if you have string parsing that means that you have to deal with regular expressions. So you have some code like this
[self setTitle: WFLocalizedString(@"Create Workflow")
forState: UIControlStateNormal];
[self setTitle: WFLocalizedStringWithDescription(@"Create Workflow", @"Button")
forState: UIControlStateNormal];
and you can use regular expressions to parse out the WFLocalizedString
.
I don’t know about you all, but when I think of regular expressions, I think, “Oh my gosh, that’s terrifying!” 😱 And not only is it regular expressions, it’s NSRegularExpression
s, which is even more difficult and double the screaming face. 😱😱
Luckily after a while, I came up with this nasty looking regular expression.
WFLocalizedString\\(@\"([^\"]*)\"\\)"
And then if you have a description it looks even scarier.
WFLocalizedStringWithDescription\\(@\"([^\"]*)\", @\"([^\"]*)\*\\)"
But I somehow did it. 🏆 The code looks something along the lines of this.
func getMatches(in line: String) throws -> [TextCheckingResult] {
let patern = "WFLocalizedString\\(@\"([^\"]*)\"\\)"
let regex = try RegularExpression(pattern: pattern, options: [])
let matches = regex.matches(in: line, options: [], range: NSRange(location: 0, length: line.utf16.count))
if matches.count > 0 {
return matches
}
let descriptivePattern = "WFLocalizedStringWithDescription\\(@\"([^\"]*)\", @\"([^\"]*)\*\\)"
let descriptiveRegex = try RegularExpression(pattern: descriptivePattern, options: [])
return descriptiveRegex.matches(in: line, options: [], range: NSRange(location: -, length: line.utf16.count))
}
We have a getMatches
function. It has a regex pattern.
And it returns a list of text checking results.
And then we have this get LocalizedStrings function that calls the getMatches function.
struct LocalizedString {
let string: String
let description: String?
}
func getLocalizedStrings(in line: String) throws -> [LocalizedString] {
let matches = try getMatches(in: line)
return matches.map {
let line = line as NSString
let string = line.substring(with: $0.range(at: 1))
var description: String? = nil
if $0.numberOfRanges > 2 {
description = line.substring(with: $0.range(at: 2))
}
return LocalizedString(string: string, description: description)
}
}
And it was great, it all worked.
But how did I get there, even though I was terrified of NSRegularExpression
s?
Luckily Swift has Playgrounds.
Playground Demo (13:52)
And Playgrounds are a great way to do development, I think. I spend, when I start working on a project now, I spend most of my time in the Playground, trying to figure out what it should look like. And I just copy and paste things into my Xcode project after I’m done figuring everything out.
Let me show you a quick demo.
I downloaded Xcode 8 this morning and I updated everything to the latest Swift 3. So if Xcode crashes, it’s not my fault. Maybe it is for downloading the Xcode, but whatever. ¯_(ツ)_/¯
This a simple Playground.
So we have getMatches
here, with our scary looking regular expressions. And we have a separate regular expression here for parsing out the localized string with description.
I think there’s a way to combine these two regular expressions, but I wasn’t good enough at regex to figure that out. I just made it two separate ones and did separate matching.
We have this getLocalizedStrings
function that uses the getMatches function, and then given the text checking results, we map it all to a localized string, which is just a string and an optional description.
Let’s see what happens.
Let’s try breaking this.
Down here I have some sample lines of code that I might see in our app.
We have setTitle
, Create Workflow, and this one is setTitle or Create Workflow and with a comment button.
So if we change this to E you’ll see that it breaks.
In this case down here, it’s no longer finding the string. If we fix it, it’ll come back. So my Playground looked like this for probably a good hour or two trying to figure out how to do regular expressions. And regular expression are kind of annoying because you have to escape a lot of special characters.
If you look at it, there are so many backslashes everywhere. I think that made it really hard, but because I had Playgrounds, it was really fast to iterate on different regular expressions and get things working.
And I’ll upload this somewhere afterwards, if you want to play with it.
Another thing I wanted to show you all was when I was updating from Swift 2.2 to Swift 3, I kind of wanted to see the diff. So the NS
dropped. Now it’s just RegularExpression
. regex.matches
in string is just matches inline which reads way more like English and is way more awesome. And I also made my functions a little Swiftier by changing getLocalizedStrings
line string to get getLocalizedStrings
in line.
So when you call that function, it looks more like getLocalizedStrings
in the screen that I’m providing.
I thought it was really cool. I recommend updating all of your apps to Swift 3 because I think it reads so much more nicely.
Cool. So you’re probably wondering, where’s the scripting part? Playgrounds are great, but let’s talk about scripts.
Subtitle (17:38)
So scripts. They’re actually really, really simple. You can define something like Hello-World.swift
, and the only line of code is
print("Hello AltConf 2016!")
And then you can run from the command line,
xcrun Swift Hello-World.Swift
and it will print out Hello AltConf 2016!
I’ll show you this later.
And then if you want to get a little bit fancier, you can add execute permissions to it.
chmod +x Hello-World.Swift
And then add this interpreter directive at the top of the file to tell it how it should be run.
#!/usr/bin/env xcrun swift
And then all you have to do, your file will look something like that.
#!/usr/bin/env xcrun swift
print("Hello AltConf 2016!")
And then you can just do ./Hello-World.Swift
.
Let me quickly show you.
What, was that like two lines of code to get it working? That was a very, very simple example.
Swift Script (19:16)
What does the localized script look like?
Let’s take a look.
This looks something like this.
It’s a lot longer. I have some string extensions up here. This one’s just for truncating.
There is a crypto extension on string that just gets you the sha1
of a given string.
LocalizedString
looks a little bit fancier than what was in the Playground. It’s Hashable
and Equatable
, so I can unique this later. There are some things for sanitizing the strings.
What this does is it just replaces all spaces with underscores, and it gets rid of all of the non-alphanumeric characters. I also have a helper for generating hash for a key and an optional description, because not all localized strings have a description.
There’s also something called lookUpStringForKey
, and that creates the little run, dash, and then the snake case key.
There is this from the Playground, which I literally just copied and pasted from Playground.
I’ll upload all this too, so if you’re not completely following along right now, don’t worry.
There’s getLocalizedStrings
, which is also directly from Playground. Then some more helper stuff. And then if we go down here, there’s some file I/O stuff.
This is getting a NSDirectoryEnumerator
, so we can use familiar NS
-everything for our script instead of using Ruby or Bash. Even though I had never really used NSDirectoryEnumerator
, I knew exactly where to find the documentation for it.
I used Dash and just read through everything. There’s the localize
function.
And then down here there is a main
function that takes in, it looks for an argument, the first argument, and it uses that in run.
The first argument’s going to be the workspace directory of the project that you are localizing. And then at the top level, we’re just calling main
.
Let’s see what happens when we run this.
First of all, if we run it with nothing, it’ll be like, “Hey, can you provide a project directory?” So sure.
I’ll give it the Workflow app.
If we run it, it says, okay. It’s running. It’s writing to file.
Workflow, Workflow UI, and ActionKit. Workflow consists of a bunch of different frameworks. We’re localizing Workflow of the main app, and Workflow UI which handles all the work for the UI components and Workflow. And then ActionKit which handles all of the different actions in Workflow.
Let’s look at what this actually looks like. It generated code that looks something like this.
The reason why we use sha1
to generate the hashes is because sometimes you have really, really long strings like paragraphs of text. And you don’t want the entire paragraph to become the key. We just truncate it to eight characters.
It looks like this.
Let’s find an example that actually has a comment. If you have a description, it’ll add this to yours so localizers can see a little bit more of the context.
This one’s supposed to be current page. It’s just like blah of blah. And as a localizer, they’re going to have no idea what that means.
We added a little comment that’s like this first blah is current page. And the second one is number of pages. It might be one of three if it’s a three page document.
Maybe we can print the output so we can see it actually happening in real time.
Now it looks more like a crazy script. Like the Matrix. Cool. What now, right?
String Files (23:55)
We generated these localizable.strings
files automatically. What do we do now?
Well, we have to translate everything. Right now all we have is these kind of unreadable keys mapped to English, which we already have in our app.
So translation is a little bit less scriptable. But I think it’s still a lot of fun. Languages have a lot of nuances, and I’ll show you a few of them. But people, luckily, are really great with nuances. Let’s look at an example.
In Japanese you can say something like, 「Kono heasutairu do」. Meaning, “what do you think of this hairstyle?” Maybe I just got a haircut or something.
I’d say, “Hey, family member, what do you think of this hairstyle?” But if you pass this into Google Translate, it’ll translate it into “This hair style, what?” Which kind of makes sense. You can probably infer what that means. But machines are still…Google has so much data, but they’re still really, really bad at translation.
And also, if you ask someone, 「Kono heasutairu do」 to a Japanese person, you might get a response that’s something like, 「Un, chotto」, which means “Yeah, a little.”
And if you ask Google, they also agree that it means “Yeah, a little” if you translate it literally. But if you ask any Japanese person or someone who is fluent in Japanese and Japanese culture, they’ll tell you that it actually means, 「That’s really bad. You should go back to the hair salon and have it looked at」. And they’re just too polite to tell that to you in your face.
Translation is hard. And human languages have a lot of nuances. Who are the best localizers? We already kind of know that we need humans to do it because machines are really bad at it. But who of the humans are best at localizing?
Well, who understands your product the most? At least for Workflow most of our users tend to be super technical because Workflow is a pretty technical app. Workflow lets you combine different actions and automate things. And one of those actions is literally a while
loop.
If you ask a translator to translate while
, they’ll probably translate it into their native language, which isn’t good because we don’t program in other languages.
While is while
, if is if
, variable is a variable
.
You probably guessed it.
Our users are the best localizers, because they really understand how the product works and what a “while” action is, what an “if” action is, what an “Uber” action is, or all those other actions that we have in our app.
Luckily for us, we’ve gotten some volunteers through our support cases, saying, “Hey, I really love your app, but where is the translation in my language? Where is the translation in Dutch or German or Chinese?”
This was in 2015 and we responded, “Hey, we’re not localizing our app yet, but we’ll get back to you when we start localizing.” And then when I joined a few weeks ago I wrote back, “Hey, are you still interested in helping us localize our app for us?”
And he responded, “Yeah.”
I thought, “Woah, that’s really cool.” We waited a year and a half to actually get back to them, but they’re still interested in localizing.
We onboarded them onto this site called Crowdin. It’s a translation platform. I looked at a bunch of different platforms and this one seemed like it was the easiest to use.
As you can see, we already have some strings that are translated. And Mischa from the Netherlands started translating things for us, which is awesome. I’ve also been trying to contribute as well, as best as possible, at least in some Dutch and with some of the Japanese translations.
Honestly, this a very long process. When you have to involve people, unlike writing scripts, things take a long time.
But if we script the tedious parts, like generating the keys and updating the localized strings, we can have time to focus on the more nuanced parts that are more human and more language-oriented.
Hopefully this is just the beginning for us in the journey of localizing, internationalizing our app.
But I’m hoping that we can bring Workflow to a bunch of different users and hopefully you learned some things today that you can take and bring your app to more users.
Wrap-Up (29:04)
Then before I wrap up I have a few tips based on things that I’ve been annoyed by through my localization process. The first tip is try to make all user-facing strings NSLocalizedString
s, even if you don’t have any plans in the near future to localize. I say this because as someone jumping into a new project, it’s really hard for me to tell what strings are supposed to be localized. For example, you might have identifiers that are actually used as keys in a dictionary or something, and if you localize that, everything is going to break. Unfortunately there are a bunch of things that are strings that are actually not really strings, like NSURL
s, IDs, and all that. I think it’s easy to start doing localization as it comes, rather than all at once later.
Another one is avoid using spaces to do layout padding, like
@" OK"
We laugh at this, but I’ve definitely done this before too, like when I’m running a little bit low on time. This is bad for multiple reasons. Maybe “OK” is longer or shorter in another language.
Your translators are going to think, “What is this? Am I supposed to put the spaces in my translation, too?” That’s really bad. We should try not to do this. And then the other one, which I think is a little bit less obvious, is to avoid concatenating strings to make sentences.
Sometimes we’re like, “Oh, we can make our code shorter by pulling out the verb, and having all the nouns here, and kind of concatenating things.” But the problem here is different languages have different sentence structures and grammatical structures. If you do that, it might be great for English, but it might be really, really painful for other languages. And these are all based on me going through the past three weeks, trying to localize or internationalize our app.
So those are some tips.
If you’re interested in learning more about localization or scripting, I have a few documents and sites that I thought were good. Apple’s iOS developer library’s Localizing Your App page, I think I used this one the most when I was working on this project, and I’m still reading it a lot. That tells you a lot about localizable.strings
, genstrings
, a bunch of the words that you heard today.
Also objc.io has a really great post on string localization. It’s issue number nine. Definitely check it out, super useful. I also gave a talk last year at Swift Summit London, which focuses more on the scripting side. If you’re interested in that and you want to learn more about how to do dependency management in Swift scripts, check it out.
And then, last but not least, cool projects and inspiration. While I was working on the localization script, I was looking at different projects to see how do other people solve this problem.
SwiftGen by AliSoftware is really, really cool. I think I would use this if our project were all Swift. But what this does is, it does a bunch of things, but in particular to localizable.strings
. If you have a localizable.strings
file that looks like this, it will generate code that looks like this. I think it’s really cool that it’s using associated values in Swift enums to make your localization type-safe.
It knows that “Greetings” will first have a string that is your name, and I’m number. Maybe you’re saying how old you are or something. This is really cool, really Swifty localization if you have an all Swift project, I recommend looking at that.
And another project that I thought was cool is twine. This one is written in Ruby, I think. But it lets you define all of your localized strings in one file and have different sections for each language. I’ll post the slides afterwards so you can link to it, because the internet is not working. And I’ll upload all the slides, all the code and everything to my repo.
Thank you.
Q & A (33:52)
Q: Hello, thanks for your talk. Have you tried doing less verbose ways of using NSLocalizedString
? I have met a few people that would like to have a shorter way to present this variable. Have you tried that?
Ayaka: We thought about making it shorter, because it’s kind of long to have NSLocalizedString
with description. But we kind of talked about it a little bit, but we decided that we want it to be as descriptive as possible without it being too long. And there’s auto-completes. I’m not too worried about verbosity.
Q: You are generating the localized strings from code? Which means that you can’t reuse them.
Ayaka: Most of our localization strings don’t have a comment. We only use a comment when we think it’s ambiguous and the translator needs more information. For most cases, we’ll have NSLocalizedString
“OK” and we’ll use that everywhere. It’ll have the same key in the localizable.strings
file. It will get reused.
Q: Instead of using a string for user-facing strings, have you thought of wrapping it in a custom struct? Use localized string then you can wrap up all the translation stuff within that?
Ayaka: Definitely. So for us we needed a solution that works for both Objective-C and Swift so that was the limiting factor. This would work for Objective-C++ and Objective-C. I think that Swift project that I linked to, SwiftGen, is a really cool solution for Swift specifically. And what you said, wrapping it in a strut.
Q: I imagine that a bunch of people run into this problem when working with Swift and NSRegularExpression
s, and just wondered if you could talk about how you dealt with using NSRange
with Swift strings.
Ayaka: I just say convert it to a NSString
before I do range stuff. It’s kind of sad. Everything else is free of NS
, except for the range. Maybe they’ll fix it soon.
Q: Just a quick question about if you have similar scripting for plural formats, like the string “sticks”?
Ayaka: We’re still working on the plural side of things. If we think of a good solution, we’d love to share it. Pluralization is a hard thing as well.
Q: More like on the testing side of things. How do you deal with your localized project when you don’t know the language? For example, I just translated my app into Japanese. I don’t know how to cross-check it. How do you deal with that?
Ayaka: For us we have a lot of beta testers that are really vocal, which helps a lot. I think checking translations kind of has to be done by a real person. I think making connections with users is probably the best way to test it.
Q: Hi, that was awesome. I just had kind of more of a generic question about designs and handling different strings where maybe in one language it’s really short, but then in another language it’s really long. Do you have any tips on how you would do that from a design perspective?
Ayaka: Interesting. Because in English and German and other languages that might be really long, but in Chinese and Japanese things tend to be really small. I haven’t thought much about it. Apparently Hector has an answer too.
Hector: If you go, I believe in Xcode if you’re editing the schemes, there is a secret key. You could Google it. It has double localized string or something. [Ed. note: Here’s the Apple docs on this.] Whenever you put that in, I think you do space dash yes or something. What it will do is, regardless if it’s a NSLocalizedString
in your app, when you run it in the simulator, it will actually just double the string. That way you can test how your UI would react to extra long strings.
Audience Member: I know regular expressions. If you write your method so it can take in a dictionary instead of just a string, you can paralyze the dictionary later. You can write the dictionary in JSON even. But it has to be one word, one in Japanese, and one in English, and one in German and it is parallel. Then you can ask your users to check the translation instead of translating the whole thing. But your regular expression can take the JSON dictionary as input, and then the output could be, if the language is German, it will take that part of the dictionary and not the other one. That’s how I would do that.
Q: Hi, so maybe this if for another talk. But how did this enable or help with loading localizations from the cloud?
Ayaka: One, we had to define a separate function so that we can, if we want to, we can load things from the cloud. It’s definitely a separate topic and we actually haven’t done it yet. But we’ve set it up so that we can do it. Because if we just have NSLocalizedString
, we can’t just tell it to load everything from the API. We’re going to have a custom implementation for that. And maybe it’ll be a future talk.
Q: So I’m guessing that it doesn’t just wrap NSLocalizedString
? Or maybe it does currently?
Ayaka: No, it won’t. Right now it looks for the key in the localized loadable strings file. But that’s all it’s doing.
Receive news and updates from Realm straight to your inbox