Internationalization in RubyMotion Projects

It’s not all Bachelor Project for me! One of my contract projects at the moment is to write an OS X app using RubyMotion.

One of my goals for this project from the get-go was to make localization really simple. Fortunately, this is really nicely implemented in RubyMotion.

The SugarCube (MIT license) gem from the RubyMotion community has a localization syntax that allows you to declare a string as localized that is just wonderful. Instead of writing the following:

Objective-C
1
NSLocalizedString(@"String", @"Key")

You can just write the following:

Ruby
1
"String"._

It’s that simple. It works wonders for interpolating strings, too. This is how it looks in Objective-C:

Objective-C
1
2
NSInteger value = 1
NSLocalizedString(NSStringWithFormat(@"String with value: %ld", value), @"Key")

…And this is how it is in RubyMotion-Land:

Ruby
1
2
value = 1
"String #{value}"._

Meanwhile, you’re probably wondering where we need to place these localized strings, as well as how RubyMotion knows what language they belong to. This is also quite simple. In your project’s folder, you need a resources folder. This is where your localization files will go. For example, for English, you’ll need a folder called en.lproj, or for Spanish they’ll go in es.lproj. More language codes can be found in the official Apple Docs.

Finally, you need to create a file in the localized folder called Localizable.strings. In it, each line will be formatted like the following example:

“Hello” = “Hola”;

This is nice and all, but here’s where it got a little irritating for me: I didn’t want to go and copy each line for each localizable file. I therefore wrote a script! A rake task, to be precise. What this task does is it looks at the strings in the English localizable file and copies them into those of the other languages, should these not already contain said string:

“Generate Localizable Files”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
desc "Generate localizable strings"
task :generate_localizable do
  # Go through each English .strings file
  Dir.glob('resources/en.lproj/*.strings') do |strings_file|
    # Go through the other localized folders
    # except for the English one
    Dir.glob('resources/*.lproj') do |folder|
      next if folder == 'resources/en.lproj'
      # Get each string from the English localized file
      File.open(strings_file).each do |line|
        string_regex = line[/(".*"\s*=\s*".*";)/, 1]
        next unless string_regex
        string = string_regex[/(".*")\s*=/, 1]
        puts string
        localized_strings_file = File.join(folder, File.basename(strings_file))
        # Create the new localized strings file if it doesn't exist,
        # and add the string if its non-localized version is not preset
        system("touch #{localized_strings_file}")
        unless File.open(localized_strings_file).read =~ /#{Regexp.quote(string)}\s*=\s*".*";/
          File.open(localized_strings_file, 'a+') { |f| f.write("#{string} = #{string};\n") }
        end
      end
    end
  end
end

So, if I have a “Hello” = “Hello”; line in the English localizable file that’s not present in say the Spanish one, the above script will copy that line to the bottom of the Spanish localizable file.

Next up, I’ll look into a way of looking at the source RubyMotion code and see if I can skip writing the English localizable strings altogether, but this’ll do nicely for now!