Localize an iOS app and debug localization issues in Xcode

This article is a cleaner Xcode localization guide: choose the right base language, add locales, localize storyboard text and code strings, then force language changes in the simulator so missing translations show up early.

Xcode project settings showing the localization list and add-language flow

This article is really about understanding what Xcode localizes automatically, what it stores in files, and where the workflow still needs manual cleanup.

Newer Xcode releases automate more of this than older ones did, but the older workflow still teaches the moving parts clearly. Once you understand the base language, Base internationalization, storyboard-generated strings, and Localizable.strings, localization bugs become much easier to diagnose.

Core idea Keep one source language stable, generate or maintain translation files alongside it, and test with forced language overrides before shipping.

Start by checking the project's development language, because every later localization file inherits that assumption.

In Xcode, the project-level localization section tells you whether the app is currently treated as Japanese, English, or something else. If an older project has the wrong base language, this article shows a manual fallback: close Xcode, open project.pbxproj, and update the language metadata.

Xcode project settings showing the development language field
The development language is the anchor that the rest of the localization workflow builds on.
developmentRegion = en;

knownRegions = (
    en,
    Base,
);

The article's fix is simply to change those values to match the real source language, for example:

developmentRegion = ja;

knownRegions = (
    ja,
    en,
    Base,
);

After the base language is correct, add target languages from the project localization panel and keep Use Base Internationalization enabled.

This is the normal Xcode path: choose the project, open the Info area, and add another language from the Localizations list. The key setting is Use Base Internationalization, because it lets Xcode treat one storyboard as the structural source while the localized text lives in companion files.

Xcode localization list with the add-language control
Add each supported language from the project-level localization list.
Use Base Internationalization option enabled in Xcode
Base internationalization keeps layout and translation concerns separated.

Storyboard localization works by extracting text into generated strings files that point back to interface object IDs.

Select the storyboard, enable the target languages in the right-side localization area, and Xcode generates files such as Main.strings. Those files map interface object IDs to translated values, which is why the article spends time showing how a label in Interface Builder corresponds to one entry in the strings file.

Storyboard localization toggles in Xcode
Enabling a language on the storyboard creates the translation companion files.
Interface Builder showing the object identity of a localized label
The object ID explains why storyboard strings look mechanical but still map to real UI elements.
/* Class = "UILabel"; text = "Greeting"; ObjectID = "ok2-tj-BN9"; */
"ok2-tj-BN9.text" = "Hello";

The same pattern applies to other controls too. A text field uses .placeholder, a button can use .normalTitle, and a view controller can expose .title.

Strings created in Swift code belong in Localizable.strings, and the article uses NSLocalizedString plus genstrings to manage them.

The workflow is straightforward: create a strings file, localize it for every language, wrap visible text in NSLocalizedString, then extract keys from Swift files when you need help generating the initial entries.

New Strings file creation flow in Xcode
Create a dedicated Localizable.strings file for code-driven text.
Localize button for Localizable.strings in Xcode
Then localize that file itself so every language gets its own value set.
@IBAction func actionPress() {
    let alert = UIAlertController(
        title: NSLocalizedString("alert_title", comment: ""),
        message: nil,
        preferredStyle: .alert
    )

    let actionClose = UIAlertAction(
        title: NSLocalizedString("confirm_button", comment: ""),
        style: .cancel,
        handler: nil
    )

    alert.addAction(actionClose)
    present(alert, animated: true, completion: nil)
}

The article also shows the classic extraction command:

find . -name \*.swift | xargs genstrings -o en.lproj
find . -name \*.swift | xargs genstrings -o ja.lproj

That produces entries you can edit into real translations instead of typing every key by hand from scratch.

The practical debugging loop is to force the app into another language from the run scheme, then ask Xcode to flag strings you forgot to localize.

In Edit Scheme, the Options tab lets you choose an Application Language for simulator launches. That is the fastest way to test another language without constantly changing the whole simulator or device environment. The article then turns on Show non-localized strings so missing values show up in the console.

Edit Scheme options showing the Application Language override
Use the scheme to boot the app directly into the language you want to verify.
Edit Scheme option to show non-localized strings
Then enable Xcode's missing-string check while you test the flow.
Xcode console output showing a missing localized string error
The console error gives you the missing key path so you can patch the right strings file quickly.
localization[3921:116986] [strings] ERROR:
pBD-R7-cpO.text not found in table Main of bundle CFBundle ...

The last section of this article also notes that on a real device you can change the app's language from that app's Settings entry if you need to verify the shipped behavior outside the simulator.

The most useful lesson in this article is that localization is easier when you treat project settings, storyboard strings, and code strings as separate layers instead of one vague translation problem.

Check the development language first, add languages from the project, let storyboards generate their own translation files, keep code strings in Localizable.strings, and force alternate languages during testing. Once those layers are clear, localization stops feeling mysterious and starts looking like normal project maintenance.