Every time you fix a typo in your app's translations, you have to submit a new build, wait for App Review, and ask users to update. For a single string change.
There's a better way.
The problem with traditional localization
Most mobile apps bundle their translations directly into the binary — .strings files on iOS, strings.xml on Android, .arb files on Flutter. That means every translation change — a new language, a fixed copy, a reworded button — requires:
- Update the resource file
- Build and sign a new binary
- Submit for App Store / Play Store review (1–3 days)
- Hope users actually update
If you're serving multiple markets and moving fast, this creates a painful bottleneck.
Over-The-Air (OTA) localization
OTA localization works the same way as OTA JavaScript in React Native or feature flags in Firebase Remote Config: the strings live on a server, not in the binary. The app fetches them at launch, caches them, and uses them immediately.
You ship translations the way you ship config — instantly, without going through the stores.
How TranslationsHub works
TranslationsHub is an OTA localization platform for iOS, Android, and Flutter. You manage all your translation keys from a dashboard, and the SDK delivers updates to your users automatically every day.
The architecture is simple:
- Your app calls a Cloud Function with its API key at launch (once per day)
- The function validates the key, records the MAU check-in, and returns a short-lived signed URL
- The SDK fetches the JSON from that URL and caches it locally
- If the network is unavailable, it falls back to the cached version, then to a bundled asset
No credentials or download tokens ever touch your binary. The API key only identifies your project — it can't be used to access the raw storage bucket.
Integration in under 5 minutes
iOS (Swift Package Manager)
Add https://github.com/translate-hub/ios_sdk in Xcode → File › Add Package Dependencies.
// MyApp.swift
import SwiftUI
import TranslateHubSDK
@main
struct MyApp: App {
init() {
TranslateHub.shared.initialize(
config: TranslateHubConfig(apiKey: "YOUR_API_KEY")
) {
// translations ready — refresh UI here
}
}
var body: some Scene { WindowGroup { ContentView() } }
}
// ContentView.swift
Text("greeting".translate ?? "Hello!")
Text("subtitle".translate ?? "Your app, in every language")
Android (Kotlin + JitPack)
// settings.gradle
repositories { maven { url 'https://jitpack.io' } }
// app/build.gradle
implementation 'com.github.translate-hub:android_sdk:1.0.3'
// MyApplication.kt
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
TranslateHub.shared.initialize(
context = applicationContext,
config = TranslateHubConfig(apiKey = "YOUR_API_KEY")
) { /* translations ready */ }
}
}
// In any Fragment or Composable:
val title = "greeting".translate ?: "Hello!"
Don't forget <uses-permission android:name="android.permission.INTERNET"/> — Flutter and Android don't add it by default.
Flutter (pub.dev)
# pubspec.yaml
dependencies:
translate_hub_handler: ^1.0.0
// main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await TranslateHub.shared.initialize(
const TranslateHubConfig(apiKey: 'YOUR_API_KEY'),
);
runApp(const MyApp());
}
// Anywhere in your widget tree:
Text('greeting'.translate ?? 'Hello!')
The translations JSON
All three SDKs read the same file format, so you manage one source of truth:
{
"languages": [
{ "code": "en", "name": "English", "direction": "ltr" },
{ "code": "he", "name": "Hebrew", "direction": "rtl" }
],
"en": { "greeting": "Hello!", "subtitle": "Your app, in every language" },
"he": { "greeting": "שלום!", "subtitle": "האפליקציה שלך, בכל שפה" }
}
RTL is handled automatically via the direction field — no extra configuration needed.
Offline support
If the network is unavailable or the API call fails, the SDK falls back gracefully:
- Local cache — the last successful fetch (up to 24 hours old)
-
Bundled asset —
translations.jsonshipped inside the binary
This means your app always has strings to show, even on the first launch with no internet.
Switching languages at runtime
// iOS
TranslateHub.shared.pickLanguage(code: "he")
// refresh your SwiftUI views or call setNeedsDisplay
// Android
TranslateHub.shared.pickLanguage("he")
refreshUI()
// Flutter
TranslateHub.shared.pickLanguage('he');
setState(() {});
The selection is persisted across app launches via UserDefaults / SharedPreferences.
Who is this for?
- Apps targeting multiple markets that want to iterate on copy without going through review
- Teams that manage translations externally (translators, marketing) and need to push updates independently of the dev cycle
- Apps that need to add a new language after launch without shipping a new binary
Try it
Dashboard and API keys at translate-io.com
- Flutter:
translate_hub_handleron pub.dev - iOS:
github.com/translate-hub/ios_sdk - Android:
github.com/translate-hub/android_sdk
Have questions or ran into an issue? Drop a comment below or open an issue on GitHub.
Top comments (0)