• Home
  • About
  • Journal
  • Portfolio
  • Engineering
instagram twitter youtube Email linkedin

Enjel Hutasoit

iOS engineer. Notes on systems, craft, and decisions.


#NoteToForget 
— Written by someone who’s shipped 4+ localized apps… 
(Still need this post again later 😅)


🧩 The Essentials

Let’s be honest.
We’ve all hardcoded strings in Swift like this:
let message = "This field cannot be left empty”
Text(message)
At a glance, they look harmless — just strings.
But they’re not just any strings — they’re UI text your users will read, and they’re locked to one language.

Tempting? Absolutely.
Practical? Sure — until your PM casually drops:
“Can we support Indonesian 🇮🇩, German 🇩🇪, and Klingon by next sprint?” 🫠


❌ Why It Doesn’t Cut It

⦿ Scattered Strings – Text is all over the place, making updates and translations a mess.
⦿ No Tooling Support – You miss out on Xcode’s built-in localization tools.
⦿ Hard to Scale – Good luck adding languages or managing translations later.


✅ The Better Way

Use NSLocalizedString() and .strings files like a civilized iOS developer. 

It keeps your code clean, makes your app translatable, and lets Apple do what it does best: hide complexity with keywords. 


🛠️ Environment

◼ Xcode: 16.2
◼ Swift: 5.10


🚀 Workflow That Just Works™


1. Add Localizations

1.1 Add Localizations to Your Project
☞ Go to project navigator 〉 Project 〉Info 〉Localization
☞ Click the ➕ 〉Add: Indonesian, German

1.2 Create Localizable.strings Files
☞ Right-click your project root 〉 New File 〉Resource 〉Strings File 〉 Name it: Localizable.strings
☞ Click Localizable.string 〉File Inspector 〉Localization 〉Check all languages: German and Indonesian


2. Add Translations

▪️Localizable (English)
"alert_empty_field" = "This field cannot be left empty”;

▪️Localizable (Indonesian)
"alert_empty_field" = "Bagian ini tidak boleh kosong”;

▪️Localizable (German)
"alert_empty_field" = "Dieses Feld darf nicht leer sein”;

3. Implement Localization in Code

3.1 Simple Version
let message = NSLocalizedString("alert_empty_field", comment: "")
Text(message)

3.2 Cleaner Syntax with helper
extension String {
    var localized: String {
        NSLocalizedString(self, comment: "")
    }
}
Usage:
let message = "alert_empty_field".localized
Text(message)


3.3 Modular Approach
The modular version using `Bundle(identifier:)` is generally preferred for multi-module apps, as it allows you to specify which localization bundle to look in rather than relying on the main app bundle.
extension String {
    public func localized(identifier: String -> String {
        let bundle = Bundle(identifier: identifier) ?? .main
        return bundle.localizedString(forKey: self, value: nil, table: nil)
    }
}
Usage:
let message = "alert_empty_field".localized(identifier: “com.enjelhutasoit.App.ModuleName")
Text(message)


🧪 Testing Your Setup

On Simulator

☞ Go to Product 〉Scheme 〉 Edit Scheme
☞ Open Run 〉Options
☞ Set App Language and App Region to test (e.g., Indonesian, German)
☞ Hit Run — your app will launch in that locale.  


On Device

☞ Open Settings 〉 General 〉 Language & Region
☞ Change device language to your target language
☞ Relaunch the app to see live localization


If Changes Don't Appear

▪️Clean your build (Cmd+Shift+K)
▪️Restart Xcode
▪️Double-check you localized the .strings file


📌 Key Takeaways


✅ Localization is easier when organized early
✅ App feels pro and scalable
✅ Fallbacks to English are automatic, no trauma needed
✅ Your future self will love you (again).


🧠 Final Note

Yes, you’ve done this before.
Yes, you forgot the steps.

That’s why this blog post exists: not to teach you something new — just to save your future self from Googling it again.

You’re welcome.
— #NoteToForget
Share
Tweet
Pin
No comments
#NoteToForget 
Because you’ll forget it again, and that’s your superpower 🤭 



The Essentials 

A common mistake many iOS developers (myself included) make is hardcoding sensitive data—like API keys—directly into the codebase.

It's easy, quick, and it works… until it becomes a security issue or a pain to manage across multiple environments.


❌ Here are a few wrong ways developers store API keys:

⦿ Constants.swift
     Anyone with access to the code can see your key. 
     It’s stored in plain text and often ends up in version control.
⦿ Info.plist
     Looks hidden, but can still be extracted by reverse-engineering the compiled app.



✅ The Better Way: Use .xcconfig Files

.xcconfig files allow you to define build-specific configuration values like API keys, which you can keep separate from your source code. This keeps things clean, secure, and easy to manage between Debug and Release builds.

⚙️ Environment


⦿ Xcode 16.2
⦿ Swift 5.9


📌 Workflow: Securely Store and Access API Keys in Xcode


1. Create .xcconfig Files

This is where you'll define API keys for different environments.

Steps:
☞ Open Xcode
☞ Go to File  〉New 〉File
☞ Choose Configuration Settings File (.xcconfig) under the "Other" section
☞ Create two files:
    ○ Debug.xcconfig
    ○ Release.xcconfig


2. Add API Key to .xcconfig

In each .xcconfig file, add:
API_KEY = "your-api-key-here"

These files won’t be compiled directly, which makes them safer to store (especially if excluded from version control).


3. Link .xcconfig to Your Project

Steps:
☞ Go to your Project Settings (click the project name in the sidebar)
☞ Under Info 〉Configurations, set the Debug and Release configurations to use the correct .xcconfig file.
     Example:
     〉Debug = Debug.xcconfig
     〉Release = Release.xcconfig


4. Map Variable to Info.plist

To access the value at runtime, expose it through the app's Info.plist.

Steps:
☞ Open Info.plist
☞ Add a new key/value pair:
<key>API_KEY</key> <string>${API_KEY}</string>

This tells Xcode to inject the value of API_KEY from .xcconfig into the plist at build time.


5. Read API Key in Swift Code

Use the following struct to access the key:

struct API {
    static let baseUrl = "https://api.rawg.io/api/games"
    
    static let apiKey: String = {
        guard let apiKey = Bundle.main.object(forInfoDictionaryKey: "API_KEY") as? String else {
            fatalError("API_KEY not found in Info.plist")
        }
        return apiKey
    }()
}
This keeps the API key out of your source code and makes it dependent on the build environment.

💡 Tip: You can replace fatalError with a fallback value or handle it more gracefully in production apps.


6. Test Your Configuration

Make sure the correct API key loads for each environment.

Steps:
☞ In Xcode, select your build scheme (top left dropdown)
☞ Choose either Debug or Release
☞ Run the app and check that the correct key is printed:
  print("API Key: \(API.apiKey)") 


Key Takeaways

🔐 Security: .xcconfig keeps sensitive data out of your codebase and version control.
🔄 Flexibility: Easily switch API keys based on Debug or Release builds.
🧠 Simplicity: Clean separation of logic and configuration.


Final Note

✅ For more advanced scenarios, you can also store sensitive keys on remote servers or use services like CocoaPods or Swift Package Manager for more secure management.
✅ Don’t forget to add your .xcconfig files to .gitignore if you don’t want to commit real API keys. For teams, you can share dummy keys or encrypted versions depending on your workflow.

That’s it! 
Next time you forget this (and you will 😅), just check back here.
 — #NoteToForget
Share
Tweet
Pin
No comments
Older Posts

About me

About Me

Hi 👋, I'm Enjel Hutasoit — an iOS software engineer.

On this blog, I write about apps, systems, and the decisions behind them.

More about me.

Let's Connect

  • instagram
  • youtube
  • x
  • linkedIn
  • facebook
  • pinterest

VIEWS

Labels

  • Collaborative Content (6)
  • Software Engineering (3)
  • Journal (1)

Popular Posts

  • Pantau Gambut: Menjaga Indonesia dengan Gambut
  • 4 Hal Penting yang Harus Anda Cek Saat Membeli Smartphone
  • Amankah Probiotik untuk Anak? Ini Dia Penjelasannya
  • Intip Susu Terbaik yang Mampu Menghangatkan Badan
  • Millennials Pegipegi ke Surabaya
Powered by Blogger.
Instagram x Youtube LinkedIn GitHub

Created with by ThemeXpose