Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
211: Testing Localized Apps with Different Locales
I’ve seen this play out a dozen times with junior devs: they spend the first few hours of a localization sprint in a state of total frustration. They’ll write a piece of code to format a price or a date, then they’ll manually open the iOS Simulator, navigate to Settings > General > Language > Add Language, move French to the top, and then jump back to the app to see if the comma is in the right place. Then they do it all again for Japanese. Then German. It's a tedious, soul-crushing loop that kills your flow.
The Friction of System-Wide Changes
The "naive" way to test localization is to treat the simulator like a physical device in your hand. While this feels "realistic," it's fundamentally the wrong approach for development. When you change the system language, you're not just testing your app; you're changing the environment for every single process on that virtual device. It's slow, it's prone to human error, and most importantly, it's impossible to automate. You can't write a XCTest that tells the Simulator's system settings to change language mid-run.
If you're relying on the System Settings menu, you're essentially guessing. You might see that a price looks correct in French, but did you check if the currency symbol is placed before or after the amount? Did you check if the decimal separator changed? By the time you've manually toggled through five locales, you've likely forgotten the specific edge case you were looking for in the first one.
Leveraging Scheme Overrides for Fast Iteration
The better way—and the way I want you to start using immediately—is via Xcode Scheme overrides. You don't need to touch the Simulator settings at all. If you click on your target at the top of the Xcode window and select "Edit Scheme," you can head over to the "Run" section and find the "Options" tab. There, you'll see "App Language" and "App Region."
By changing these dropdowns, you're telling Xcode to launch your app with a specific localization environment, regardless of what the simulator is set to. It's a massive quality-of-life improvement. I usually keep a few different schemes configured for my most "problematic" locales—like Arabic for Right-to-Left layout testing or German for those notoriously long compound words that break my UI buttons. It takes three seconds to switch, and it keeps your simulator's global state clean.
Why Locale.current is a Testing Nightmare
Now, schemes are great for manual "smoke testing," but they don't help you with unit tests. This is where many engineers hit a wall because they've hard-coded Locale.current inside their logic. If your price formatter looks like this: let formatter = NumberFormatter(); formatter.locale = Locale.current, your test is now a hostage to whatever the machine running the test happens to be set to.
The professional approach is to inject the locale. I always suggest treating Locale as a dependency. Instead of letting your formatter reach out into the global environment, pass the locale in through the initializer. This allows you to write a test that explicitly creates a Locale(identifier: "fr_FR"), passes it to your formatter, and asserts that the output is "1 234,56 €" instead of "$1,234.56".
It feels like a bit more boilerplate upfront, but the trade-off is a test suite that is deterministic. Your CI server in a data center in Virginia should produce the exact same test results as your MacBook in a coffee shop. That's the only way to truly sleep soundly when shipping a global product.
📋 Practical Task
Exercise: Implementing a Deterministic Currency Validator
You have a PricePresenter class that is currently using Locale.current, making it impossible to test different currencies reliably. Your task is to refactor this class to support locale injection and write two unit tests to verify the formatting.
Requirements:
- Modify the
PricePresenterclass to accept aLocaleobject in its initializer (defaulting to.currentfor production use). - Implement a method
formatPrice(amount: Double) -> Stringthat uses aNumberFormatterconfigured with the injected locale and the style.currency. - Write a test case that verifies an amount of
1234.56returns"1 234,56 €"(or the equivalent French formatting) when initialized withLocale(identifier: "fr_FR"). - Write a second test case that verifies the same amount returns
"$1,234.56"when initialized withLocale(identifier: "en_US").
// Starting point for your refactor:
class PricePresenter {
func formatPrice(amount: Double) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current // This is the line you need to change!
return formatter.string(from: NSNumber(value: amount)) ?? ""
}
}There are no comments for now.