.strings
iOS codeGen swiftGen bartyCrouch localization Estimated reading time: 9 minutesLocalization for Apple products always was a pain in the ass. Yep, every year Apple adds new and new options and features for localizations, but we always should handle a lot of issues on our own. Sometimes, when some issue is resolved, a few new ones appear.
The most critical issues are:
- unique entries
- untranslated entries
- unused entries
- usage of strings
- typo in entries due to missed autocompletion
- management of strings (by feature or by type)
- different locales in different sources for strings
- xib/storyboard localization (for
UIKit
) orBase.lproj
- refactoring of strings
- localization of
Info.plist
- sync localization between different platforms (for example iOS vs Android)
This is the list of the main problems, as for me. Off cause, there is might be even more of them, at least there are a few related to plurals and templated stings…
Looking for improvements, I found a way, that can solve the most critical problems from the list. And in this article, I would like to tell u about it.
The problems
If u work with an application that has a lot of features - u know, that there is might be a lot of things that should be translated. In my last project, we had about ~3k phrases that should be managed. At the same time, often we got some updates/change requests related to some of them. If u use plain .strings file - this may be a painful process full of errors - u should find, edit and finally check if everything is working as expected after an update.
Another painful process is to use strings in code - u either need to use a method from Bundle
either Foundation macro NSLocalizedString
(that under the hood use the same method from the bundle). In both cases, it’s easy to make a typo in the key name. It’s also hard to group the strings by key and so manage the separate flow. We may also separate each localization for a specific feature, but this is good only for a low qty of features.
I like to use a general name that doesn’t match to message itself (reason - text later or sooner will be changed, then the name of the key becomes outdated).
In android we have resources generator by ID, on iOS - no.
Thankfully to code generators, these 2 described problems can be solved easily. We can use swiftgen for this. And everything works just fine.
So, we can create a configuration for each .strings
file and run swiftgen. Typos, management now a bit less painful. But still, we should manage all strings in the .strings files, we should check if there is no empty translation, no duplicates…
The good point is that this problem isn’t new and one more automized tool is available - bartyCrouch. This tool can also detect empty translations, duplicates and easily can be integrated within swiftgen.
Perfect tutorial from the author of bartyCrouch available here
We can combine swiftGen and bartyCrouch and solve issues with typos, dublicates, missing translations.
So, if we go to the list of localization problems - we can see, that the first top 5 are solved. Good.
.strings management
In the last project, I have used folders and separate .string
files for each feature + swiftgen
. Now, I see that we can do even better - each feature can be packed in a swift package. This is now possible thanks to the latest update - resources for the swift package.
Check WWDC20/10169 video - Swift packages: Resources and localization and Localizing Package Resources.
The good point here - is that we can now put localization in sp (swift package) and get a full feature as a separate unit!. U can now fully control and keep the logic of each part of u’r app, write separate tests and so reduce coupling and increase the quality of u’r code and cost of future changes.
That’s great, but we still have few points to solve before actual usage.
Localization
The tricky moment that I was facing when start dealing with this approach was related to localization. According to the WWDC video - we can just add a localized file into a package, expose it inside pkg and use it outside. All pretty simple, but when all u’r localizations in separate pkg and u try it on another language - nothing works.
here is a demo project - test it.
Yep - nothing works… The true reason here is that u’r app bundle doesn’t know about u’r localization in packages. We should somehow tell about it. Apple didn’t mention anything about this. I found 2 solutions:
CFBundleLocalizations
By adding CFBundleLocalizations
in Info.plist
with required localizations as an Array of strings. Even if in official doc says that it supports only a few localizations, we can add one there (using the same locale code as .lproj
folders) and everything will works.
An application can notify the system that it supports additional localizations through its information property list (
Info.plist
) file. To specify localizations not included in your bundle’s .lproj directories, add theCFBundleLocalizations
key to this file. The value for the key is an array of strings, each of which contains an ISO language designator as described in “Language and Locale Designations.”
this information from old outdated docs is not available right now.
But, thanks to the advice of @SDGGiesbrecht, such a solution is mostly a workaround. The proper one - CFBundleAllowMixedLocalizations
This may sound easy to use, but I spend a day figuring out this.
CFBundleAllowMixedLocalizations
If we are expecting libraries to use additional localizations beyond those supported by the main application bundle, setting CFBundleAllowMixedLocalizations
to YES
in the application’s Info.plist
is the proper solution.
combining all parts
Finally, we have all parts ready to use. But here is still one unresolved issue - swiftGen and bartyCrouch does not support swift packages, so when we run scripts - it’s run on the whole project…
Also, every time create a package and add configuration files for swiftGen and bartyCrouch, add required localizations folders… DRY - do not repeat yourself!
Ok, let’s solve this one-by-one.
To follow DRY we can use xCode templates. I ended up creating a separate template, that contains all config files and specific files that are required for providing minimal functionality (I used TCA as an architecture for a project).
To know more about how to create an xCode template visit this great article
Now, if we try to create sp from this template, we can observe, that everything works fine, except .bartycrouch.toml
file. The system thinks that all files, that is started from a dot - hidden one. For the xCode template, I didn’t find a way how to copy hidden files within all other files. Workaround for this problem - I decided to put this file in the root of the project, that during every build iterate over all pkg in the project, find the one that requires this file, and copy it into. To detect such a package I decided simply to use comment inside the Packadge.swift
file. We can use a bash script to execute it in RunScripts for the project:
To make pck acceptable for this script - add comment
// bartyCrouch
intoPackage.swift
file at any place
Once we do this - configuration will be copied into all required pkg.
Next problem to solve - as was mentioned, swiftGen and bartyCrouch is not supports different sp, but only oot project. To make it works, we can add similar script in RunPhases to project, but this time, execute swiftgen and bartyCrouch.
For swiftGen my script is next:
To make pck acceptable for this script - add comment
// localizableSP
intoPackage.swift
file at any place.
For bartyCrouch - similar one, but another command used instead swiftgen
:
Make sure that RunScript in the correct order
- copy .toml file
- run bartycrouch
- run swiftgen
Done. Now, when u would like to add localization, the flow will be like next:
To make things, even more, better - create a code snippet for xCode and use it instantly when needed.
Conclusion
With this approach, we can solve the biggest part of issues related to localization. Off cause, we can add sync logic - to allow upload/download translation for localization, but this is a bit another story.
Resources
Share on: