localization
NSLocalizedString
iOS development
best practices
internationalization

Best practice using NSLocalizedString

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Localization is one of those tasks that feels simple at the start but becomes painful if you get the foundation wrong. NSLocalizedString is the primary API for translating user-facing strings in iOS and macOS apps, yet many developers use it without understanding the practices that keep string files maintainable as an app grows. Getting your localization workflow right from day one saves you from costly refactors when you add your second or tenth language.

Basic Syntax and Why Comments Matter

The simplest form of NSLocalizedString takes a key and a comment:

swift
1let greeting = NSLocalizedString(
2    "welcome_message",
3    comment: "Greeting shown on the home screen when the user first opens the app"
4)

The corresponding Localizable.strings file entry looks like this:

 
/* Greeting shown on the home screen when the user first opens the app */
"welcome_message" = "Welcome back!";

The comment parameter is not optional filler. It is the only context your translators will see when they encounter the string. A translator working on the Arabic version of your app has no idea where "welcome_message" appears or what tone to use without that comment. Always describe where the string appears, what it refers to, and any length constraints.

Choosing Good Keys

Avoid using the English text as the key. This approach seems convenient but causes real problems:

swift
1// Bad: English text as key
2let label = NSLocalizedString("Cancel", comment: "")
3
4// Good: Descriptive key
5let label = NSLocalizedString(
6    "alert_cancel_button",
7    comment: "Cancel button on the delete confirmation alert"
8)

When English text is the key, changing the English wording means changing the key, which breaks every existing translation. Descriptive keys decouple the identifier from the content, letting you update the English text without touching other languages. They also prevent collisions when the same English word means different things in different contexts. "Cancel" on an alert and "Cancel" on a subscription page may need different translations in some languages.

Using genstrings to Extract Strings

Apple provides the genstrings command-line tool to scan your source files and generate .strings files automatically:

bash
find . -name "*.swift" | xargs genstrings -o en.lproj/

This produces a Localizable.strings file with all your NSLocalizedString calls. Run it periodically to catch new strings and verify that your comments are present. If a comment is an empty string, genstrings will warn you. Treat these warnings as errors.

Handling Plurals with .stringsdict

Many languages have complex plural rules. English has two forms, but Arabic has six. A plain .strings file cannot handle this. Use a .stringsdict file instead:

xml
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4<plist version="1.0">
5<dict>
6    <key>items_count</key>
7    <dict>
8        <key>NSStringLocalizedFormatKey</key>
9        <string>%#@count@</string>
10        <key>count</key>
11        <dict>
12            <key>NSStringFormatSpecTypeKey</key>
13            <string>NSStringPluralRuleType</string>
14            <key>NSStringFormatValueTypeKey</key>
15            <string>d</string>
16            <key>one</key>
17            <string>%d item</string>
18            <key>other</key>
19            <string>%d items</string>
20        </dict>
21    </dict>
22</dict>
23</plist>

Use it in code the same way:

swift
1let message = String.localizedStringWithFormat(
2    NSLocalizedString("items_count", comment: "Number of items in the cart"),
3    itemCount
4)

Modern Swift: String(localized:)

Starting with Swift 5.5 and iOS 15, Apple introduced a cleaner API:

swift
1let greeting = String(
2    localized: "welcome_message",
3    defaultValue: "Welcome back!",
4    comment: "Greeting shown on the home screen"
5)

This approach embeds the default English value directly in code, making it easier to read. It also works with the new String Catalog (.xcstrings) format introduced in Xcode 15, which provides a visual editor for managing translations directly in Xcode.

Organizing Strings by Feature

As your app grows, a single Localizable.strings file becomes unwieldy. Split strings into table files named by feature:

swift
1// Uses Settings.strings instead of Localizable.strings
2let title = NSLocalizedString(
3    "settings_title",
4    tableName: "Settings",
5    comment: "Navigation bar title for the settings screen"
6)

Create separate files like Settings.strings, Profile.strings, and Checkout.strings. This keeps each file small, reduces merge conflicts, and lets you hand off specific files to translators working on particular features.

Avoiding Interpolation in Keys

Never embed variables in localization keys:

swift
1// Bad: Dynamic key that genstrings cannot extract
2let key = "error_\(errorCode)"
3let message = NSLocalizedString(key, comment: "")
4
5// Good: Map error codes to static keys
6let message: String
7switch errorCode {
8case 404:
9    message = NSLocalizedString(
10        "error_not_found",
11        comment: "Error message when a resource is not found"
12    )
13case 500:
14    message = NSLocalizedString(
15        "error_server",
16        comment: "Error message for a server error"
17    )
18default:
19    message = NSLocalizedString(
20        "error_generic",
21        comment: "Generic error message for unknown errors"
22    )
23}

Dynamic keys defeat genstrings, meaning your tooling cannot verify that all keys have translations. They also make it impossible to get compile-time warnings about missing strings.

Common Pitfalls

  • Empty comment parameters: Passing comment: "" gives translators zero context, leading to incorrect translations that you will only discover after shipping.
  • Using English text as keys: Any English copy change breaks all translations and creates key collision risks for words with multiple meanings.
  • Concatenating localized strings: Building sentences by concatenating fragments like NSLocalizedString("hello") + " " + name breaks in languages with different word order. Use format strings instead.
  • Forgetting .stringsdict for plurals: Hardcoding "1 item" vs "2 items" logic fails in languages like Polish, Russian, or Arabic that have more than two plural forms.
  • Not testing with pseudolocalization: German text is typically 30 percent longer than English. Use Xcode's pseudolanguage or double-length pseudo-locale to catch layout issues before sending strings to translators.

Summary

  • Always provide meaningful comments in NSLocalizedString to give translators the context they need.
  • Use descriptive keys like screen_element_purpose instead of English text to decouple identifiers from content.
  • Use .stringsdict files for any string that involves numbers or plural forms.
  • Prefer String(localized:) on iOS 15 and later for cleaner syntax and String Catalog compatibility.
  • Split strings into per-feature table files to keep localization files manageable.
  • Run genstrings regularly and treat missing-comment warnings as errors.
  • Never construct localization keys dynamically or concatenate translated fragments into sentences.

Course illustration
Course illustration

All Rights Reserved.