Validate Localized Resources
- Oct 20, 2020
- #swift#contiuous-integration
In this post, we’ll look at a way to validate app localizations and integrate it into Fastlane and any CI processes you may have.
Background
Localization is always a good idea, even when working alone on an app with a single language. Separating localized texts from your code makes the code easier to read and the texts easier to maintain. You app will also only be a translation away from supporting more languages.
When you work on bigger apps, chances are that they support multiple languages, each with it’s own set of localized resources. In these cases, you probably use a cloud-based tool like Lokalise to let a team of translators translate your app without access to the raw resources.
If you then add new language keys with temp texts until the translators have had a chance to translate them, you probably don’t want the untranslated texts to accidentally make their way out into production, just because you forgot to download the latest translations.
To prevent this from happening, you should verify that your localized files don’t contain empty strings, and make it a part of your release process.
grep to the rescue
You can use grep
to check for the occurrence of a string within a file. The following checks for empty keys within an English localization file in a certain folder:
grep "= \"\";" <PATH TO LOCALIZED FOLDER>/en.lproj/Localizable.strings
This will output all empty translations, if any, or exit if the file doesn’t contain any empty keys. We can assign this to a build script variable, to control parts of our build process.
Fastlane to the rescue
If you use Fastlane to automate various parts of your development process, you can add a new lane that helps you validate a localized file:
lane :l10n_validate do |options|
locale = options[:locale]
if locale == nil or locale.empty?
UI.user_error!("Missing parameter 'locale'")
end
file = "<PATH TO LOCALIZED FOLDER>/" + locale + ".lproj/Localizable.strings"
if !sh('cd .. && grep "= \"\";" ' + file + ' || true').empty?
UI.user_error!(file + " has empty translations!")
end
end
You can now call this lane from the Terminal:
fastlane l10n_validate locale:de
as well as from any other lanes. If the file contains any empty translations, Fastlane will fail with a red error text.
If your app supports multiple languages, you can add a lane that validates all localized files:
lane :l10n_validate_all do |options|
l10n_validate(locale: "da")
l10n_validate(locale: "de")
l10n_validate(locale: "en")
l10n_validate(locale: "fi")
l10n_validate(locale: "pl")
l10n_validate(locale: "sv")
end
You can now run l10n_validate_all
from the Terminal or from any other lane to abort operations that require all translations.
This makes it possible for you to for instance abort a release distribution to App Store Connect.
Temporary translations
You probably use temporary strings for your main development language, to avoid having empty strings in the app until translations are done. If so, I’d suggest that you establish a certain text pattern that you always use in your temporary strings, e.g. a adding a certain word like “Todo” or “Temp” that otherwise aren’t used in real translations.
This lets you add a second grep
to your validation, that detects any occurrences of that word:
lane :l10n_validate do |options|
locale = options[:locale]
if locale == nil or locale.empty?
UI.user_error!("Missing parameter 'locale'")
end
file = "<PATH TO LOCALIZED FOLDER>/" + locale + ".lproj/Localizable.strings"
if !sh('cd .. && grep "= \"\";" ' + file + ' || true').empty?
UI.user_error!(file + " has empty translations!")
end
if !sh('cd .. && grep <TERM> ' + file + ' || true').empty?
UI.user_error!(file + " has temp translations!")
end
end
You can naturally take this even further to include other localized resources, for instance Localized.stringsdict
files.
Conclusion
Validating your app’s localized resources is a good practice to protect your app from being released with temporary or missing translations. I hope that you find this approach easy and straightforward to implement.