How I nailed App Store screenshot automation 🚀

Maybe you are one of those lucky guys who only has an English version of your app’s screenshots on the App Store. Maybe you don’t even have five screenshots per screen size. In that case: Appreciate it 🏆.

In any case, follow along to see how you can achieve 100% text and data localized App Store screenshots at the tap of ONE BUTTON (seriously).

Prolog

At Tonsser we are currently live in 6 countries around Europe. As most other apps out there we have 5 beautiful screenshots on the App Store and of course, these are represented in all of the 4 different iPhone screen sizes.

Some quick math reveals that we currently have to maintain 120 screenshots, adding another 20 for each new country. On top, our platform relies on being very local, so we cannot use a pale, blond haired Scandinavian boy 👦🏼 as the profile picture in the Spanish App Store and vice versa. Just like having a first name like Raul is rather uncommon in the northern parts of Europe 👦🏾.

It is therefore not enough to just automate the process of taking screenshots in the 6 different locales with the same data set. The actual data also has to be localized.

Slave labor

Manual labor FTW

Since the beginning, when we only supported one country 🇩🇰, all of this work was done by hand by our talented designers. But mistakes often happened during this manual process; copy/paste errors was inevitable and every time we updated the design they had to start all over. The amount of resources needed to maintain an ever increasing amount of screenshots started to justify looking into full automation of this.

Liberating the slaves

One tap automation

For a long time I had personally wanted to experiment with automating this process. Seeing the tears on the cheeks of our designers 😢 every time we had to update a screenshot finally gave me the motivation to do something about it. I also knew that Fastlane had already done most of the heavy lifting with snapshot, which uses Xcode UI Testing to navigate your app in different languages and the take screenshots at the right time. However, this tool did not assist in showing customizable, localized data. Some home brew was needed 🍺.

Solution 👉 Hyper Localization

So two things are needed in order to localize the data:

  • A data template with placeholders for each localized data point
  • Translations to fill out these placeholders in the template

Mock JSON

In our project we currently use Moya as the backing network layer. This library supports the use of mock data out of the box, which makes it super easy to just add localized JSON files for each view that needs screenshot. A template for such a view could look as follows for the Profile:

{
"response": {
//...
"firstname": "{snapshot.main_user.firstname}",
"lastname": "{snapshot.main_user.lastname}",
"facebook_id": "{snapshot.main_user.facebook_id}",
//...
"team": {
"club_name": "{snapshot.main_user.club_name}"
}
}
}

And like this for the league table:

{
"response": [
{
"team": {
"club_name": "{snapshot.league_table.first_club_name}"
},
"played_matches": 13,
"goal_difference": 35,
"points": 36
},
{
"team": {
"club_name": "{snapshot.league_table.second_club_name}"
}
,
"played_matches": 13,
"goal_difference": 47,
"points": 32
},
//...
]
}

Filling in the blanks

The fact that we currently use PhraseApp for translating our app UI strings combined with their excellent API made it an obvious choice to also use this tool to localize the placeholders in the templates above.

To utilize this API the choice of programming language landed on Ruby for multiple reasons:

  • It is completely new to me - ️❤️ learning new things
  • The entire Fastlane tool is written in Ruby, making it easier to do more custom stuff with our deployment tool at a later point 📈
  • Our backend is written in Ruby so should everything fail I would have great support 🛠

After a ton of Google searches and Stack Overflow questions the solution was quite simple.

  1. Load the template
  2. Iterate through translation keys
  3. Make a new JSON file for each locale
  4. Fill in placeholders with the translations of each key in each JSON file

Disclaimer: This is newbie Ruby 😄 Suggestions are welcomed 🙏

def replace_texts(file_name, translation_keys, project_id)
  # Load template
template = File.read("./snapshot/mockdata/#{file_name}.json")
  # Fetch all snapshot related PhraseApp keys
translation_keys.each do |key|
  # Fetch all translations for current key
translations = request(project_id, key)
    # Iterate all translations
translations.each do |translation|

# Retrieve data
locale = translation["locale"]["name"]
value = translation["content"]
key_name = translation["key"]["name"]
      # Figure out where to put the data
target_file_name = "/Snapshot/#{file_name}-#{locale}.json"
      # Replace translation key with the translation
template = template.gsub("{#{key_name}}", "#{value}")
      # Write to file
File.open(target_file_name, "w") do |file|
file.write(base)
file.close
end
    end
end
end

This results in a large number of JSON files that are added to the Xcode project and are simply read by Moya instead of making the normal network request:

private var snapshoatSampleData : NSData {

switch self {
case .User:
let path = pathForCurrentLocale(fileName: "user")
return NSData(contentsOfFile: path)!
  case .LeagueRankingTable:
let path = pathForCurrentLocale(fileName: "league")
return NSData(contentsOfFile: path)!
}
}

Fastlane Snapshot

Using the excellent Snapshot tool in fastlane it is super easy to set up the screenshot process with a tiny bit of configuration:

devices([
"iPhone 6s",
"iPhone 6s Plus",
"iPhone 5s",
"iPhone 4s",
])
languages([
"en-US",
"da",
"sv",
"no",
"es-ES",
"it",
"fr-FR",
])

and a few lines of UI Testing code:

XCUIApplication().tabBars.buttons.elementBoundByIndex(2).tap()
snapshot("1_Profile")
XCUIApplication().tabBars.buttons.elementBoundByIndex(1).tap()
snapshot("4_League")

The Golden Overview

Here’s a simple illustration of each step in the process detailed above:

Push the button

And with that, it is now time to hit that Big Red Button 🚀

Resulting in completely different screenshots for each screen size and locale.

Nailed it 💪

All it now takes to add a new country/locale is to “translate” the related PhraseApp strings and then hit that Big Red Button again. Same goes when the design is tweaked or when translations have been updated: 1 button 👉 120+ screenshots 😎

Great success

Epilog

The attentive reader might have also noticed that this procedure will result in an accumulated amount of more or less duplicate JSON files in the Xcode project, thus unnecessarily increasing the size of the app bundle. However, I also made a small script that purges the contents of these files after taking all the screenshots, allowing the files to still be part of the project without increasing the app size significantly.

Apple also just recently announced a simplified screenshot upload process for iTunes Connect, which makes it possible to upload just one set of screenshots to be used across localizations and scaled to other screen sizes. However, in our case at least, we need the localization for our product. Also, if your UI does not scale linearly across screen sizes, you would probably still want different screenshots for each screen size.


If there’s enough interest for the complete source, I’ll post it, so let me know in the comments below 🙌