A Swift Package that helps small teams and indie developers add multilingual support to SwiftUI and UIKit apps faster.
It provides a provider-based localization stack with built-in Apple Translation support, UI-ready wrappers, and configurable caching for runtime localization flows and custom translation backends.
- runtime localization for SwiftUI and UIKit apps
- provider-based architecture for custom translation backends
- built-in Apple Translation support
- batch-capable provider support with async request throttling controls
- SwiftUI
LiveLocalizedTextand UIKitLiveLocalizedLabel - loading placeholders, progress callbacks, and completion callbacks for UI wrappers
- in-memory and disk-backed cache stores
- configurable cache policy with namespacing, provider segmentation, and TTL
- pseudo-localization, passthrough, and mock translation providers
- shared localizer configuration and explicit localizer injection
Add this repository in Xcode:
https://github.com/MikeChen1109/LiveLocalizationKit.git
Available products:
LiveLocalizationCoreLiveLocalizationUILiveLocalizationTranslationSupport
Configure Apple Translation-based preview:
import LiveLocalizationCore
import LiveLocalizationTranslationSupport
await LiveLocalization.configure(
provider: AppleTranslationProvider(),
cacheStore: DiskLocalizationCacheStore(),
cachePolicy: LocalizationCachePolicy(
namespace: "preview",
providerIdentifier: "apple-translation"
)
)
let localized = await "Settings".localize()LiveLocalization.configure(...) is async because shared configuration can prewarm injected cache stores such as DiskLocalizationCacheStore before your UI starts reading localized content.
For lightweight development flows, LiveLocalizationCore also includes providers such as PseudoLocalizationProvider, MockLocalizationProvider, and PassthroughLocalizationProvider.
Custom providers implement LocalizationProvider and receive a LocalizationRequest.
import LiveLocalizationCore
struct MyTranslationProvider: LocalizationProvider {
func translate(_ request: LocalizationRequest) async throws -> LocalizationResponse {
let targetLanguage = request.targetLanguageIdentifier ?? "en"
let localizedText = "[\(targetLanguage)] \(request.sourceText)"
return LocalizationResponse(localizedText: localizedText)
}
}Use the shared package flow:
await LiveLocalization.configure(
provider: MyTranslationProvider(),
cacheStore: MemoryLocalizationCacheStore(),
cachePolicy: LocalizationCachePolicy(providerIdentifier: "my-provider")
)
let text = await "Settings".localize()Or create an explicit localizer with a custom cache store:
let localizer = LiveLocalizer(
provider: MyTranslationProvider(),
cacheStore: DiskLocalizationCacheStore(),
cachePolicy: LocalizationCachePolicy(
namespace: "preview",
providerIdentifier: "my-provider",
entryLifetime: 3600
)
)
let text = await "Checkout".localize(using: localizer)Or pass more request context when you need it:
let text = await "Checkout".localize(
sourceLanguageIdentifier: "en",
targetLanguageIdentifier: "ja",
context: "paywall.primary_cta"
)Provider guidance:
- Return
request.sourceTextwhen you want an explicit fallback. - Use
targetLanguageIdentifierandcontextif your backend needs routing or domain-specific prompts. - If your provider can answer immediately, prefer
SyncLocalizationProvider.
See docs/ProviderGuide.md for a fuller guide to custom provider design.
If your backend accepts multiple compatible strings in one request, adopt BatchLocalizationProvider so LiveLocalizer can coalesce nearby requests and issue a single batch call:
struct MyBatchProvider: BatchLocalizationProvider {
func batchGroupIdentifier(for request: LocalizationRequest) -> String {
request.targetLanguageIdentifier ?? "default"
}
func translateBatch(_ requests: [LocalizationRequest]) async throws -> [LocalizationResponse] {
requests.map { request in
LocalizationResponse(localizedText: "[batch] \(request.sourceText)")
}
}
func translate(_ request: LocalizationRequest) async throws -> LocalizationResponse {
let responses = try await translateBatch([request])
return responses[0]
}
}LocalizationExecutionPolicy lets you tune async throughput for shared and explicit localizers:
let localizer = LiveLocalizer(
provider: MyBatchProvider(),
executionPolicy: LocalizationExecutionPolicy(
maxConcurrentAsyncRequests: 4,
batchWindow: .milliseconds(30),
maxBatchSize: 16
)
)Use this when you want to protect a remote backend from burst traffic or increase batch efficiency for providers that support grouped translation.
LiveLocalizationCore supports pluggable cache stores.
MemoryLocalizationCacheStorekeeps results in memory for the current process.DiskLocalizationCacheStorepersists localized text to disk across launches.LocalizationCachePolicysupports namespacing, provider-aware segmentation, and TTL-based expiration.- Shared
LiveLocalization.configure(...)can prewarm an injected cache store before the shared localizer is published.
let localizer = LiveLocalizer(
provider: MyTranslationProvider(),
cacheStore: DiskLocalizationCacheStore(),
cachePolicy: LocalizationCachePolicy(
namespace: "preview",
providerIdentifier: "my-backend",
entryLifetime: 3600
)
)LiveLocalizationCore includes lightweight runtime event hooks for debugging and instrumentation.
You can observe:
- shared configuration start / finish
- cache warmup start / finish
- cache hit / miss / write / invalidation / clear
- provider translation start / success / failure fallback
let logger = ClosureLocalizationLogger { event in
print("Localization event:", event)
}
await LiveLocalization.configure(
provider: MyTranslationProvider(),
cacheStore: DiskLocalizationCacheStore(),
cachePolicy: LocalizationCachePolicy(providerIdentifier: "my-provider"),
logger: logger
)LiveLocalizationUI adds simple view wrappers on top of the core localizer layer.
SwiftUI:
import SwiftUI
import LiveLocalizationUI
LiveLocalizedText("Continue")
.placeholder { phase in
Text(phase.displayedText)
.redacted(reason: .placeholder)
}
.onProgress { phase in
print("phase:", phase)
}
.onCompletion { completion in
print("localized:", completion.localizedText)
}UIKit:
import UIKit
import LiveLocalizationUI
let label = LiveLocalizedLabel()
label.setLocalizedText(
"Continue",
progressHandler: { label, phase in
print(label, phase)
},
completionHandler: { label, completion in
print(label, completion.localizedText)
}
)Notes:
LiveLocalizedTextnow keeps UI customization separate from event callbacks: useplaceholder { ... }for loading presentation andonProgress/onCompletionfor side effects.LiveLocalizedLabelstarts blank while loading and reports progress and completion through handler-based callbacks.
If you want a debug flow that checks whether required language packs are available and guides preparation before showing the UI:
import SwiftUI
import LiveLocalizationTranslationSupport
TranslationPreparationGate {
ContentView()
}If you need custom presentation logic, use TranslationPreparationCoordinator directly.
Notes:
- package products support
iOS 18andmacOS 15 AppleTranslationProviderrequiresiOS 26ormacOS 26- available languages depend on the system
- language packs may need to be downloaded on device first
If language pack download appears stuck in-app, managing the pack first in Apple's built-in Translate app is often more reliable during testing.
See LiveLocalizationDemo.xcodeproj for the bundled demo app. The demo sources live under LiveLocalizationKit/ and cover both SwiftUI and UIKit preview flows.
MIT



