How to keep your iOS localizable files clean — Swift script edition

A few months back I explored how Xcode could be improved on when it came to cleanly maintaining and keeping your localizable string files clean. To reiterate the problem, localizable string files has a tendency to grow out of proportion and become sloppy fairly quickly especially when engineers have enabled their application for different languages. Keeping these files clean and maintainable becomes difficult. Five major problems that commonly occur with localizable files are unreadability, key duplications, mismatched keys across different string files, missing keys but are being used in code, and extra keys not used in code (I refer to these as dead keys).

My previously proposed solution involved injecting a bash script post compiling to assist in keeping your localizable string files in check with your codebase (Clean iOS Localizable Files). The script sorted the keys, searched for duplicated and mismatched keys, and located any undefined and/or dead keys in your localizable files. The solution was viable and mitigated the common problems, but there were a few issues with the script itself that I was not so satisfied with. 🤨

  • The script execution was extremely slow! Especially when the project grew larger — O(n2) solution. The solution was meant for a starting point to solve an issue without taking into account optimization.
  • Bash is difficult to read — this may not be the case for some people, but as an Swift/Obj-C Engineer, re-reading the bash script required some refreshers. With that in mind, modifications to the script for other iOS Engineers to use on their own projects was more painful than beneficial.
  • Chaining multiple grep commands was not an optimized solution compared to cache hashing and sets.

As a follow-up, I have decided to re-adapt the solution with these elements in mind. I’ve settled on Swift 😬, enabling other iOS Engineers to more easily modify the script to their projects needs. Using Swift also allowed me to adopt a structured approach for a more optimized strategy.

If you’d like to jump ahead and check out the entire script, here is the gist , and if you’d like to check out an example project with the script injected, I’ve included the GitHub project on the bottom of this post otherwise let’s analyze the interesting portion of the script!

Similar to the bash script, we begin by searching for the localized string files recursively from the root directory by create(). Apple’s FileManager makes the file searching easy by enumerating through the objects. Notice we ignore any Pods localized files in our localizableFiles computed var since those are usually packaged in. We’ll optimize the script here by searching for key duplications while we parse through the localizable files in the parse(_ path: String) method and store the keys in a Set. The method will print an error for any key duplications along with the file paths that are found.

(Note: Any print("error:")or print("warning:") format notifies Xcode to display the corresponding message on the toolbar along with other build failures and warnings)

Next, we’ll validate that the keys match across all the localizable string files in the validateMatchKeys method. We’ll use symmetricDifference to weave out any extra keys that do not match the base files. This method will throw an error and aborts the script if any localizable files do not contain the same keys. This gives you a chance to clean the localizable files before the next execution, cleanWrite() , which re-writes your localizable file with the keys sorted and extra whitespace and newlines removed.

The next step is to search through the codebase, store the keys used in those files and verify them against our base keys. With a bit of regex, we can successfully locate the keys and store them and the path of the file in a LocalizationCodeFile struct while also optimizing our script by compact mapping out any files without keys. Once we have our structure, we’ll hit validateMissingKeys() — which will loop through our collection of LocalizationCodeFile , subtracting each set of keys against our base keys. Any extra keys from this result are NOT subsets from our base keys — thus giving us our missing keys. An error is thrown if any missing keys are found.

Our last strategy is to search for any dead keys, keys that are defined in our localizable files but are not being used in our codebase. We can retrieve our dead keys by subtracting our base keys against all the keys collected from our collection of LocalizationCodeFile through a flatMap. Any keys found here are displayed as a warning since this step is more of a suggested tech debt clean up rather than a user affecting issue.

Executing the bash script against an enterprise codebase with over 600 executable files and estimated over 800 keys took a hefty average of 81 seconds. Running this swift script against the same codebase took a speedy average of 3 seconds! 🚀🙌

Keeping a Clean and readable codebase should be every engineers goal. With this Swift script injected into your post build — cleaning and maintaining your localizable files should assist in that goal and feel automated. You can read my previous blog to learn how to inject the script into your Xcode post build compiles (https://buildingvts.com/clean-ios-localizable-files-8b910413b985).

I’ve also included a GitHub project with the whole script and the sample project here: https://github.com/ginowu7/CleanSwiftLocalizableExample. Feel free to leave any comments or suggestions and you can follow me on Twitter @ginowu07 ! Happy coding! 🙏