How to translate SPM packages and the main app together
Hello everyone,
My name is Ilya Chikmarev and I’m an iOS developer on the Exness Trade app platform team.
Exness is a leading market maker in the industry and our app is a popular trading terminal for our clients, giving them the convenience to trade easily and quickly directly from their phones. The app has many of the same advantages as our web terminal — including advanced trading signals, drawing tools, and price alerts — and comes in 12 different languages.
Recently, our team has been undertaking a significant transformation to the app. Historically, we used UIKit as our primary framework, but with the introduction of Apple’s SwiftUI, we quickly adopted it and integrated it into our app.
But, our evolution hasn’t stopped there. Currently, we have started the process of transitioning away from the UIApplication lifecycle and implementing modularization through the use of SPM packages. This process has presented some challenges, particularly when it comes to supporting the multiple available languages on the app.
I will elaborate further on these challenges in the next section.
How it used to be
We opted to utilize native tools rather than relying on generators or wrappers such as SwiftGen for string localization. Utilizing SwiftUI or NSLocalizableStrings allows for out-of-the-box localization capabilities. This was our chosen approach. Initially, all strings and storyboards were implemented this way. The localization process consisted of the following steps:
1. Write code and include the English string where necessary.
2. Use the command xcodebuild -exportLocalizations to generate the *.xliff file.
3. Utilize an API to upload the file to a translation management system, such as CrowdIn, where translators would then translate the strings.
4. Download the updated files and integrate them back into the app.
5. To streamline the process, we created our own script using Swift and ArgumentParser for added convenience and to perform all necessary functions.
Problems
After modularizing our app into SPMs, our previous localization approach became ineffective. We needed a way to collect strings from both the main application and all the packages. One considered solution was specifying the Bundle.module while working with strings, which allowed us to specify the main application and avoid any unnecessary code.
However, this approach was not feasible for us as it would not allow us to access strings from the packages.
With the introduction of Swift 5.3, it became possible to specify Localized Resources and include a flag `defaultLocalization: “en” `to use within packages. However, this still did not address our main issue as it would not allow us to compile all strings together into a single package without the risk of duplicates.
Using the old method of executing `xcodebuild -exportLocalizations` would only pull strings from the main application and not from the packages.
We needed to come up with a new solution that would allow us to collect all strings from both the main application and all packages, while avoiding duplicates.
Solution
The decision was made to make a flexible programme that could meet our needs and make life easier for the developers from the “feature” teams.
The principle is very simple:
1. We download current packages and repositories using xcodebuild.
2. Find file workspace-state.json which contains all information about SPM packages.
3. Revise it and rebuild repositories.
4. For each of them we generate a separate Localizable.strings file using `find ./ -name \\*.swift -print0 | xargs -0 genstrings -SwiftUI -o` .
5. Now, if necessary, the command can fill in strings only from its own package
6. We then merge all strings from the packages into one file and swapped it into the main application (however, the classic way of merging files does not work, we use a normal String for this, to avoid problems with quotes and empty characters)
The subsequent step is to generate an *.xliff file and submit it for localization. The application also stores all previously translated strings to ensure that, during the retrieval process, a translated string is immediately available if required.
To sum up
With our solution, we have successfully streamlined the localization process while retaining its flexibility. We have established a foundation for future enhancements, such as implementing automated lanes on GitLab, and allowing developers to effortlessly execute the steps (outlined above) with the simple click of a button.
Most notably, we have effectively addressed the challenge of localizing individual packages and the entire application, a task that is currently not widely addressed on the web.