How to Localize Your iOS App: SwiftUI Internationalization Guide
Prepare your Xcode app, translate .xcstrings with AI, then upload screenshots so PTC (Private Translation Cloud) reviews the rendered iOS app in every language. This guide walks through the full workflow using SwiftUI and Xcode 15+ String Catalogs, the modern Apple-recommended path. For the cross-platform service overview that covers both iOS and Android, see translate iOS and Android apps with AI.
Part 1: How iOS localization works
iOS localization is the process of adapting your app's text, formatting, and assets to support multiple languages and regions. With String Catalogs introduced in Xcode 15, the workflow is dramatically simpler than the legacy .strings / .stringsdict approach.
Internationalization vs. localization
These are two separate stages, and they need to happen in that order:
- Internationalization (i18n) is the technical groundwork. You structure your code so text, images, and formatting can vary by locale without code changes. Done once, ideally before your first release.
- Localization (l10n) is the ongoing work that follows. Writing translations, adjusting layouts, and supplying locale-specific assets for each new language.
The most common mistake is treating localization as a post-launch task, only to find the codebase is not ready. Going back to fix hardcoded strings, fixed-direction layouts, and non-locale-aware formatters across an existing app takes much longer than building with localization in mind from the start.
How Xcode 15+ uses Localizable.xcstrings as a single source of truth
Apple's localization model in Xcode 15+ centers on a single file per target: Localizable.xcstrings. This file is a JSON-formatted String Catalog that holds your source language plus every translation, including plural variations, device-specific variations, and substitutions.
Xcode automatically extracts localizable strings from your SwiftUI code (any Text("..."), Label("...", systemImage:), Button("..."), and any string interpolation that uses LocalizedStringKey). Running your app's build populates Localizable.xcstrings with every extracted source string.
Apple supports several formats. .strings (legacy key-value), .stringsdict (legacy plurals), and .xcstrings (modern). New projects should start with .xcstrings. Existing .strings-based projects can migrate via File > New > File > String Catalog and Xcode's import option. Apple's official localization documentation covers the full background.
Part 2: Set up your Xcode project for localization
Step 1: Enable Localization. Open your project in Xcode and select the project file in the Navigator. Under the Info tab, scroll to Localizations. English is already listed as the base language. Click + to add each target language (Spanish, French, Arabic, etc.).
Step 2: Create a String Catalog. Right-click on your project and select New File from Template. Search for String Catalog and add it. Keep the default name Localizable.xcstrings. Build the project once with Cmd+B. Xcode scans your code, finds every localizable string, and populates the catalog automatically.
Step 3: Mark strings for localization. In SwiftUI, any literal string passed to Text(_:), Label, Button action labels, navigation titles, etc. is automatically LocalizedStringKey:
import SwiftUI
struct WelcomeView: View {
let userName: String
let unreadCount: Int
var body: some View {
VStack {
Text("Welcome, \(userName)!")
Text("You have \(unreadCount) unread messages")
Button("Get Started") {
// action
}
}
}
}
Two patterns where Xcode cannot extract strings automatically. Both fail silently:
Tip 1: Avoid passing variables to Text. When you pass a variable to a Text view instead of a string literal, SwiftUI treats it as a plain string and skips the catalog lookup entirely:
// NOT localized - SwiftUI treats the variable as a plain String
let title = "welcome_title"
Text(title)
// Localized correctly
Text(LocalizedStringKey(title))
Tip 2: Use String(localized:) outside views. For strings you need to localize in a view model, helper function, or anywhere outside a SwiftUI view, use String(localized:) rather than a plain string:
let errorMessage = String(localized: "error_generic")
Step 4: Add plural and device variants. Strings often need different forms depending on context.
For pluralization, start with the string in your SwiftUI view:
Text("\(bookCount) books on your shelf")
Open the String Catalog, right-click the key, and choose Vary by Plural. Xcode generates the plural categories automatically and pre-fills them with the source string. For English you will see One and Other. Correct the One field to "%lld book on your shelf". Mark both as reviewed.
When you later send the catalog to PTC, the plural structure travels with the file. For Arabic, PTC generates translations for all six plural categories (zero, one, two, few, many, other) because Arabic grammar requires all six. For Spanish, two. Same as English.
For device variations (e.g., "Tap to continue" on iPhone vs. "Click to continue" on Mac), right-click the key and choose Vary by Device. Add the devices you want to customize and enter the appropriate string for each. iOS serves whichever version matches the current device at runtime.
Part 3: Translate your .xcstrings with PTC
For small projects you could open each language column in the catalog and type translations directly. As your app grows, that is unmanageable across hundreds of keys and dozens of languages. PTC handles uploading, translating, and syncing.
Step 1: Export your String Catalog from Xcode. Go to Product > Export Localizations. Xcode packages your String Catalog into an .xcloc file per target language. For PTC, you only need the .xcstrings file inside the .xcloc package. Right-click the exported .xcloc in Finder and select Show Package Contents to find Localizable.xcstrings.
If you see an "Unable to build project for localization string extraction" notification, your project uses iOS-only APIs that Xcode cannot compile against its internal macOS SDK during string extraction. Fix: select the project target under TARGETS, go to Build Settings, search for "Use Compiler to Extract Swift Strings", and set it to No. Then export again.
Step 2: Sign up for PTC. The free trial covers 20,000 words into 2 languages, which is enough to localize most apps. After the trial, PTC runs on Pay-As-You-Go. No subscription, the first 500 words every month free.
Step 3: Set up your project and translate. Drag Localizable.xcstrings into PTC. Leave the output filename as Localizable.xcstrings. Xcode expects that exact name when resolving localized strings. Select your target languages.
PTC automatically generates a description of your app from the uploaded file. Review it and edit if needed. If you have existing translation files, upload them so PTC can match your style. Otherwise translate from scratch. Add glossary terms. PTC adds your app name automatically. Add any brand-specific terminology that should be translated a specific way or not at all. Click Start Translation.
Step 4: Review and download. Once translation is complete, the Translations tab shows each source string alongside its translation. Anyone you add to the project can edit translations directly. If something looks off, report a problem with a specific translation and request a free AI retranslation. PTC learns from feedback and applies it to future strings in the same project.
If any translated string exceeds its length limit, it is highlighted. You have three options. Accept the longer translation if your UI can accommodate it. Request a retranslation that fits the current limit. Adjust the limit in Settings > Translation lengths.
Part 4: Integrate translated .xcstrings into your Xcode project
Three options.
Option 1: Manually download files from PTC. Go to the Resource Files tab and download the ZIP. It contains a single Localizable.xcstrings with your English source strings and all translations. Close Xcode, replace the existing Localizable.xcstrings in your project folder with the one from PTC, then restart Xcode. Your translations appear in the String Catalog with a checkmark next to each fully translated language.
Option 2: Integrate with Git. If your project lives on GitHub, GitLab, or Bitbucket, connect PTC directly. Git integration is a Pro feature. Activate Pay-As-You-Go to access it. In your PTC dashboard, go to Settings > Merge Requests and click Add Git Integration. Provide your repository URL, grant PTC access, and choose your branch and source files. PTC sends a merge request with the translations.
Option 3: Use the API. PTC's API gives you full control over when and how translations are pulled into your build pipeline. With Pay-As-You-Go activated, go to Settings > Manage API tokens, click Add access token, then see the PTC API reference for the endpoints.
An in-app language picker (some apps need this) can override the system locale per-view via the SwiftUI environment:
import SwiftUI
@MainActor
class LocaleManager: ObservableObject {
@Published var currentLocale: Locale = .current
func setLocale(_ identifier: String) {
currentLocale = Locale(identifier: identifier)
UserDefaults.standard.set([identifier], forKey: "AppleLanguages")
}
}
struct LanguageSettingsView: View {
@EnvironmentObject var localeManager: LocaleManager
var body: some View {
List {
Button("English") { localeManager.setLocale("en") }
Button("Espanol") { localeManager.setLocale("es") }
Button("Francais") { localeManager.setLocale("fr") }
}
.environment(\.locale, localeManager.currentLocale)
}
}
Changing AppleLanguages at runtime requires an app restart for some system strings. The SwiftUI environment override takes effect immediately for views inside that scope.
Part 5: Test your localized iOS app
Test with the scheme language setting. The quickest way to test a specific language is via your Xcode scheme. Go to Product > Scheme > Edit Scheme, click the Options tab, change App Language and App Region to the locale you want, and run with Cmd+R. Works well for most languages. You should see the same layout and design as English with all text switched.
Test Arabic and other RTL languages. The scheme language setting can be unreliable in the Simulator for RTL. Use the Simulator's own language settings:
- Run the app with Cmd+R to open the Simulator.
- Press Cmd+Home to go to the home screen.
- Open Settings > General > Language & Region.
- Tap Add Language, select Arabic, and set it as the primary language.
- The Simulator restarts. Open your app from the home screen.
Check that text appears in Arabic and that the layout mirrors correctly, with navigation title and content aligned to the right. For preview testing without committing to a language, Edit Scheme > Options > Application Language > Right-to-Left Pseudolanguage is a faster check. For text-expansion testing, use Double-Length Pseudolanguage to see how layouts handle 30-40% longer strings before you commit to a target language.
iOS localization best practices
- Check your UI for varied text length. German runs ~30% longer than English. French and Spanish ~20%. Use SwiftUI's flexible layout system, let labels grow and wrap naturally, avoid fixed-width constraints on text elements, and test in a few different languages during development.
- Do not skip pluralization variants. Russian has five plural categories, Arabic has six, Japanese has none. String Catalogs handle this when you add the language. Make sure all generated fields are filled in before shipping.
- Localize images and assets. Images with text or culturally specific visuals need localized variants. In
Assets.xcassets, select the image and in the Attributes Inspector click Localize. Choose the languages you want variants for and replace each with the appropriate version. iOS serves the correct image based on the user's locale. - Keep your base language complete. Your base language (usually English) is the fallback for any missing translation. An incomplete base can cause unexpected fallbacks even in otherwise-fully-translated languages. Xcode flags missing or stale base strings during build.
- Localize your App Store listing. A localized app with an English-only listing loses users at discovery. In App Store Connect you can localize app name, subtitle, description, and keywords per territory. Keywords are particularly valuable. Apple indexes keywords from multiple locales per territory, effectively multiplying your keyword character budget beyond the standard 100 characters. Use PTC's Paste to Translate feature for App Store content.
- Use locale-aware formatters.
Date.FormatStyle,Decimal.FormatStyle, andLocale-aware formatters. Never hardcode"$"or"MM/DD/YYYY". - Use
%lldfor integer counts andString(localized: "You have ^[\(count) message](inflect: true)")where Apple's automatic grammatical agreement applies.
Visual translation review of your translated iOS app - ship without manual QA per language
After PTC translates your .xcstrings, you still have to verify the running app in every language. Traditionally a multi-day manual QA pass per release. A translated label may overflow a navigation bar in German. "Send" may translate as a noun in French when the button needed a verb. A hardcoded English string outside Text(_:) will be glaringly untranslated in the running iOS app.
PTC's Visual AI Review replaces that pass. For native iOS apps (the browser extension flow does not apply), use screenshot upload. Capture the running app's critical screens in each target language (sign-in, main tab, settings, edge cases) and upload them to PTC. PTC's vision AI inspects each screen and:
- Fixes issues in the
.xcstringswhen PTC controls them. Re-translates a wrong part of speech, picks a shorter synonym that fits a navigation bar, regenerates a plural form with correct grammatical agreement. - Generates a Cursor / Claude Code prompt when the issue lives in your Swift code. A missing
LocalizedStringKey, a hardcodedStringoutside the localization system, a sentence built by concatenation instead of substitution.
The deliverable: a verified iOS app per release. Not a translated .xcstrings with manual QA still ahead.
Translate your App Store listing, release notes, and push notifications
Your App Store description, what's-new release notes, and push notification text live outside Localizable.xcstrings. PTC's Paste to Translate handles that copy in the same project. Paste the source text in the PTC dashboard, choose target languages, get back translations that use the same glossary and brand voice as your in-app strings. Apple indexes keywords from multiple locales per territory, so localized listings are particularly valuable for discovery.
Translate in-app user content with the PTC API
In-app chat, social posts, and user reviews need translation as content arrives. The PTC REST API translates this content on demand with Bearer-token authentication, using the same glossary and brand voice as your .xcstrings translations.
Fix iOS localization that is not working
The most common cause is the localization file is not included in the app target. Click Localizable.xcstrings in the Navigator, open the File Inspector, and check that your app target is checked under Target Membership. Also confirm the language is listed in your project's Localizations section under the Info tab. If both look correct, clean the build folder with Shift+Cmd+K and rebuild.
iOS localization file formats: .xcstrings, .strings, .stringsdict, .xliff
.xcstrings (String Catalog) is the current default since Xcode 15. A single JSON-based file consolidating all strings, plural rules, and device-specific variants. .strings is the legacy key-value format, still valid in older codebases, paired with .stringsdict for plurals. .xliff and .xcloc are export formats for handing off to translators. Not storage formats.
Localize your app name with InfoPlist.strings
Create a InfoPlist.strings file and localize it for each supported language. In each language version, add CFBundleDisplayName = "Your Translated App Name";. Select the file in the Navigator, open the File Inspector, and click Localize to add language variants. iOS displays the correct app name based on the device language.
Change the iOS app language without restarting
iOS does not provide a native API for this. The standard approach is to set AppleLanguages in UserDefaults and ask the user to restart:
UserDefaults.standard.set(["es"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
The change takes effect the next time the app launches. If you need in-session switching without a restart, you have to manage localization manually by loading the appropriate bundle for the selected language.
Why iOS falls back to English for unsupported languages
iOS uses your base language as a fallback when a translation is not available. This is expected behavior. To minimize fallbacks, make sure your String Catalog shows no missing or stale strings before each release.
How accurate is AI localization for a whole iOS app
PTC uses AI to translate .xcstrings, .strings, and .stringsdict files in minutes, preserving plural rules and placeholders automatically. The majority of translated strings go live without edits. For best results, tell PTC about your app during setup so translations reflect the right tone and terminology.
Which iOS languages to localize into first
Spanish, French, German, Japanese, and Simplified Chinese are common starting points beyond English. If your app already has users in a specific region, prioritize their language first. For the lowest adaptation cost, start with languages geographically or culturally close to your base market.
Localize your own iOS app
Start with PTC's 30-day free trial - 20,000 words on us, no credit card. Upload your Localizable.xcstrings, translate it in minutes, then upload screenshots and let PTC verify the rendered app.
Related:
- Translate iOS and Android apps with AI - cross-platform service overview.
- How to localize your Android app - the equivalent guide for Android Studio.
- PTC API reference - REST endpoints for CI integration.