My native language is not English. I wanted the game to support multiple languages cleanly. Localization is easy to postpone in the early stages of development, but it becomes much harder if the codebase is built around hard-coded English strings.
This note summarizes the localization rules and tooling I am using in the project. The main goals are:
NSLocalizedString for User-Facing TextThe first rule is simple:
Every user-facing string should go through localization.
For example:
label.text = NSLocalizedString("New Game", comment: "")This is much better than:
label.text = "New Game"Even if the game initially ships in only one language, wrapping strings early avoids painful cleanup later.
Typical places that need localization:
A good rule is: if the player can see it, localize it.
One of the most important localization rules is:
Do not build sentences by joining smaller pieces.
For example, this is bad:
let text = NSLocalizedString("You found", comment: "") + " " + itemNameIt may look harmless in English, but many languages do not use the same word order. Some languages also change grammar depending on gender, case, or plurality.
Instead, use a full localized format string:
let format = NSLocalizedString("You found %@.", comment: "")
let text = String(format: format, itemName)Another bad example:
titleLabel.text = NSLocalizedString("Level", comment: "") + " \(depth)"Prefer:
let format = NSLocalizedString("Level %@", comment: "")
titleLabel.text = String(format: format, "\(depth)")This keeps the word order flexible for translation.
When formatting localized strings, it is important to avoid assumptions that may cause crashes or restrict translators.
%@ Instead of
%d or %fIn many cases I prefer using %@ instead of numeric
format specifiers like %d or %f.
For example, instead of writing:
let format = NSLocalizedString("Level %d", comment: "")
let text = String(format: format, depth)I usually write:
let format = NSLocalizedString("Level %@", comment: "")
let text = String(format: format, "\(depth)")This avoids potential crashes if the type passed to
String(format:) does not match the expected format
specifier.
Using %@ makes the formatting more tolerant because it
simply inserts the string representation of the value.
While %d and %f are technically correct,
they are easier to misuse during refactoring or when code changes.
For safety and maintainability, %@ is often the safer
choice.
Another important technique is using explicit parameter ordering.
For example:
let format = NSLocalizedString("%1$@ defeated %2$@", comment: "")
let text = String(format: format, heroName, monsterName)This allows translators to reorder parameters if their language requires a different sentence structure.
For example, a language might translate the sentence as:
%2$@ was defeated by %1$@
Without parameter indexing, translators would not be able to change the order safely.
Using:
%1$@
%2$@
%3$@
makes the localization system much more flexible and prevents awkward translations.
This is especially useful in games where sentences often include multiple dynamic elements such as:
Try to localize complete thoughts instead of fragments.
Bad:
NSLocalizedString("You are", comment: "")
NSLocalizedString("hungry", comment: "")Good:
NSLocalizedString("You are hungry.", comment: "")Fragments are difficult to translate correctly because translators cannot see the full context.
Game logic should not rely on visible strings.
Bad:
enum ItemType: String {
case potion = "Potion"
}Better:
enum ItemType: String {
case potion
var localizedName: String {
return NSLocalizedString("Potion", comment: "Item name")
}
}Internal identifiers should remain stable regardless of translation changes.
Manual localization becomes difficult as the project grows. A Python pipeline can automate much of the process.
Typical workflow:
Swift Source Files
↓
Python Extractor
↓
Master String List
↓
Translation
↓
Updated .strings Files
The pipeline can:
.strings files automaticallyAutomation keeps the localization process consistent and repeatable.
Adding localization support early makes the game easier to maintain and expand.
For Rocco Rogue, the key rules are:
%@ formatting for safety%1$@Localization is not just a translation step at the end of development. It influences UI design, code structure, and build tooling.
Building localization support early creates a strong foundation for supporting multiple languages in the future.
⬅️ Previous | 🏠 Index | Next ➡️