More than three years ago, I wrote about a localization workflow that works well in practice. It was based on the genstrings tool to scan the source code for usages of NSLocalizedString
and update the .strings
files accordingly. A custom script as part of the build process which would update all .strings
files, keeping existing translations around (vanilla genstrings
would just overwrite those).
The workflow as like this:
NSLocalizedString
(with key, default value, comment, and sometimes custom localization table) for user facing stringsupdate_localization
script as a build step to keep .strings
files up to date for the reference language via default values.strings
to translation tool.strings
files from translation tool into XcodeThis workflow worked well for years. I liked the fact that the truth was in the code and unused entires were automatically cleared from the .strings
file.
Unfortunately, this is not possible anymore in swift because the genstrings
tool still only works for the most basic use of NSLocalizedString
in Swift with only key
and comment
parameters. As soon as additional parameters such as tableName
, value
, or bundle
are used, it breaks down (rdar://22817000).
Our new workflow is based on R.swift. R.swift parses your Xcode project and generates a fully typed, compile time checked struct called R
via which you can access your resources.
Now the workflow is reversed:
.strings
fileR.swift
(typically as a a run script phase during builds)R.string
As a quick example, say we want to localize the title view. First, we’re adding an entry to Localizable.strings
:
/*This is the title of the profile view*/
"view.profile.title" = "Profile";
After running R.swift, the R.generated.swift
file now contains the following
/// This `R` struct is code generated, and contains references to static resources.
struct R: Rswift.Validatable {
...
/// This `R.string` struct is generated, and contains static references to 1 localization tables.
struct string {
/// This `R.string.localizable` struct is generated, and contains static references to 389 localization keys.
struct localizable {
...
/// Base translation: Profile
///
/// Locales: Base, de
static func viewProfileTitle(_: Void) -> String {
return NSLocalizedString("view.profile.title", comment: "")
}
...
}
...
}
...
}
In code, we can access this now via the R
struct:
...
override func viewDidLoad() {
super.viewDidLoad()
self.title = R.string.localizable.viewProfileTitle()
}
Note that the strings key has been converted from view.profile.title
to viewProfileTitle
. Whats great about this, is that thanks to the generated struct, the strings are checked by the compiler and even autocompletion works!
Sometimes, when there’s only one localization table anyway, I like to use the following typealias for shorter access to the strings:
/// Typealias for shorter access to localized strings
private typealias S = R.string.localizable
which finally leads to this code:
...
override func viewDidLoad() {
super.viewDidLoad()
self.title = S.viewProfileTitle()
}
Our localization Workflow on iOS is now back at what I presume to be the “default” approach anyway, however with the additional help of R.swift its now a lot more convenient than before.
Actually, the workflow is now the same again as on Android, where the approach of accessing resources via the R class was available right from the beginning. To be honest, I’ve always been a little bit jealous about its convenience 😇
Oh an by the way, I’m pretty sure it works just a well if you prefer SwiftGen over R.swift.
Markus is a technical mastermind and one of the founders of Innovaptor. He studied Computer Engineering at Vienna University of Technology and contributes a valuable diversification to Innovaptor's qualifications. Markus is eager to find innovative solutions to complex problems in order to provide products that are tailored directly to the customers' needs