A Swift SDK for integrating Lingohub's over-the-air localization services in iOS applications. This SDK allows you to update your app's localizations without requiring a new app release.
- 🚀 Over-The-Air (OTA) localization updates
- 🔄 Runtime language switching
- 📱 Support for .stringsand.stringsdictfiles
- đź› Method swizzling for seamless integration
- đź”’ Robust error handling
- 📝 Optional debug logging
- iOS 14.0+
Add the following dependency to your Package.swift file:
dependencies: [
    .package(url: "https://github.com/lingohub/ios-cdn-sdk.git", from: "1.0.0")
]Or add it directly in Xcode via File > Add Packages > "https://github.com/lingohub/ios-cdn-sdk.git"
import LingohubSwift UI: Initialize in your main App:
@main
struct YourApp: App {
    @SwiftUI.Environment(\.scenePhase) private var scenePhase
    init() {
        // Configure the SDK with your API key
        LingohubSDK.shared.configure(
            withApiKey: "YOUR-API-KEY",
        )
        // Enable method swizzling for automatic localization
        LingohubSDK.shared.swizzleMainBundle()
    }
    //...
}For UIKit Initialize the SDK in your AppDelegate or SceneDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Configure the SDK with your API key
    LingohubSDK.shared.configure(
        withApiKey: "YOUR-API-KEY",
    )
    // Enable method swizzling for automatic localization
    LingohubSDK.shared.swizzleMainBundle()
    return true
}For Swift UI add the following code to you main App file:
  var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            if newPhase == .active {
                // Check for updates when app becomes active
                LingohubSDK.shared.update()
            }
        }
  }For UIKit add the following code where appropriate (e.g., when your app becomes active):
func applicationDidBecomeActive(_ application: UIApplication) {
    Task {
        LingohubSDK.shared.update()
    }
}Use NSLocalizedString as usual, LingohubSDK will take care of the rest:
NSLocalizedString("id", comment: "description of string")Initialize the Lingohub SDK with optional parameters:
| Parameter | Example Value | Description | Default | 
|---|---|---|---|
| appVersion | "1.0.0" | The version of your app | Value from Info.plist | 
| environment | .production | Environment to use (.production, .staging, .development, .test) | .production | 
| logLevel | .none | Control debug logging output (.none or .full) | .none | 
@main
struct YourApp: App {
    @SwiftUI.Environment(\.scenePhase) private var scenePhase
    init() {
        // Configure the SDK with your API key
        LingohubSDK.shared.configure(
            withApiKey: "YOUR-API-KEY",
            environment: .production,
            logLevel: .full // Enable detailed logging (not recommmended for production)
        )
        // Enable method swizzling for automatic localization
        LingohubSDK.shared.swizzleMainBundle()
    }
    //...
}Change the app's language at runtime:
// Set to a specific language
LingohubSDK.shared.setLanguage("de")Same optional parameters for UIKit in your AppDelegate or SceneDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Configure the SDK with your API key
    LingohubSDK.shared.configure(
        withApiKey: "YOUR-API-KEY",
        environment: .production,
        logLevel: .full // Enable detailed logging (not recommmended for production)
    )
    // Enable method swizzling for automatic localization
    LingohubSDK.shared.swizzleMainBundle()
    return true
}If you prefer not to use method swizzling:
func getLocalizedString(for key: String, tableName: String? = nil) -> String {
    if let localizedString = LingohubSDK.shared.localizedString(forKey: key, tableName: tableName) {
        return localizedString
    }
    return NSLocalizedString(key, tableName: tableName, comment: "")
}Get notified of localization updates via Observer:
NotificationCenter.default.addObserver(
    forName: .LingohubDidUpdateLocalization,
    object: nil,
    queue: .main
) { [weak self] _ in
    self?.updateUI()
}Get notified via callback
LingohubSDK.shared.update { result in
    switch result {
    case .success(let value):
        if value {
            cacheManager.updateLastFetchTime()
       }
    }
}Available environments:
- Production (.production): The default environment for released apps
- Development (.development): For development and testing
- Staging (.staging): For pre-production testing
- Test (.test): For running automated tests
The SDK provides detailed error information through LingohubSDKError:
LingohubSDK.shared.update { result in
    switch result {
    case .success(let updated):
        // Handle successful update
    case .failure(let error as LingohubSDKError):
        switch error {
        case .invalidApiKey:
            print("API key is missing")
        case .invalidAppVersion:
            print("App version is missing")
        case .invalidSdkVersion:
            print("SDK version is missing")
        case .apiError(let statusCode, let message):
            print("API error: \(statusCode), \(message ?? "No message")")
        case .unknown:
            print("Unknown error occurred")
        }
    }
}Optional caching is possible to reduce network requests, you can implement a simple CacheManager:
import Foundation
class CacheManager {
    private let userDefaults = UserDefaults.standard
    private let lastFetchKey = "last_fetch_time"
    private let oneDayInSeconds: TimeInterval = 24 * 60 * 60
    func shouldFetchStrings() -> Bool {
        let lastFetchTime = userDefaults.double(forKey: lastFetchKey)
        let currentTime = Date().timeIntervalSince1970
        return currentTime - lastFetchTime >= oneDayInSeconds
    }
    func updateLastFetchTime() {
        let currentTime = Date().timeIntervalSince1970
        userDefaults.set(currentTime, forKey: lastFetchKey)
    }
}Then use it when checking for updates:
@main
struct YourApp: App {
    @SwiftUI.Environment(\.scenePhase) private var scenePhase
    private let cacheManager = CacheManager()
    // ... existing init code ...
    var body: some Scene {
        WindowGroup {
            //SwiftUI View
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            if newPhase == .active {
                // Only fetch if needed
                if cacheManager.shouldFetchStrings() {
                    LingohubSDK.shared.update { result in
                        switch result {
                        case .success(let value):
                            if value {
                                cacheManager.updateLastFetchTime()
                            }
                        }
                    }
                }
            }
        }
    }
}For bug reports and feature requests, please open an issue on GitHub.
Apache License Version 2.0, January 2004. More infos in the LICENSE file.