Overview
The app downloads over-the-air (OTA) translations at runtime instead of being frozen in the APK. Fixing a typo, retranslating a sentence or adding a language becomes a publication in SimpleLocalize, not a Play Store release.
The SDK is a per-key overlay on top of the localization you already have. A string is resolved in this order:
- over-the-air translation in the current language,
- over-the-air translation in the configured fallback language,
res/values*/strings.xmlcompiled into the APK,- the key / resource default.
Anything you have not published keeps rendering the bundled string, key by key on the same screen
publishing three keys overrides exactly those three. Downloaded content is cached in filesDir, so
the app starts with the last known translations even offline, and a failed refresh never drops what
was already downloaded.
Requirements
minSdk 21, JDK 17 for the build. The library has no runtime dependencies- Translations published to Translation Hosting.
- Project token from Settings > Credentials. It is public by design and meant to be shipped in client code
Installation
The library is not published to a Maven repository yet, so consume it from the repository as a Gradle module:
git clone https://github.com/simplelocalize/simplelocalize-ota-sdk-android.git
// settings.gradle.kts
include(":simplelocalize-ota")
project(":simplelocalize-ota").projectDir =
file("../simplelocalize-ota-sdk-android/simplelocalize-ota")
// app/build.gradle.kts
dependencies {
implementation(project(":simplelocalize-ota"))
}
If your root build file does not declare the Android library plugin yet, add
id("com.android.library") version "<your AGP version>" apply false next to the application plugin.
Usage
Start the SDK once in Application.onCreate(). It reads the disk cache synchronously, so the first
frame already shows the last known translations, and refreshes on a background thread.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
SimpleLocalize.start(
this,
SimpleLocalizeConfiguration(
projectToken = "5a5b1f...", // Settings -> Credentials
environment = "_production", // or "_latest"
fallbackLanguage = "en"
)
)
}
}
Existing getString() and Compose
Wrap the activity context and every getString(R.string.…), XML layout and Compose
stringResource() resolves over the air first, with no call site changes:
class BaseActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(SimpleLocalize.wrapContext(newBase))
}
}
The wrapped Resources translate a resource id to its entry name - R.string.home_title becomes
the key home_title and look that key up over the air, falling back to strings.xml. Use
resource entry names as translation keys in SimpleLocalize, a key published as home.title will
never match R.string.home_title.
Keys that have no resource in the APK are read explicitly:
val text = SimpleLocalize.getString("checkout.summary") ?: "Summary"
Views already on screen are not redrawn when new translations arrive and listen for changes:
SimpleLocalize.addOnTranslationsChangedListener { recreate() }
In Compose, hold the revision in state and key the subtree on it:
var revision by remember { mutableIntStateOf(SimpleLocalize.revision) }
DisposableEffect(Unit) {
val listener = SimpleLocalize.OnTranslationsChangedListener { revision = SimpleLocalize.revision }
SimpleLocalize.addOnTranslationsChangedListener(listener)
onDispose { SimpleLocalize.removeOnTranslationsChangedListener(listener) }
}
key(revision) { Text(stringResource(R.string.home_title)) }
Languages and refreshing
The language is resolved from the device locales unless you set language explicitly. Keys are
probed in the order en_GB, en-GB, en; the first one published wins and is remembered across
launches. SimpleLocalize.setLanguage("pl") overrides it at runtime.
Translations refresh on start(), when an activity is resumed (throttled by
minimumRefreshIntervalMillis, 10 minutes by default) and on demand:
SimpleLocalize.refresh()
Configuration
| Option | Default | Meaning |
|---|---|---|
projectToken | – | Settings > Credentials |
environment | _production | _latest, _production or a custom environment |
baseUrl | https://cdn.simplelocalize.io | change it for a custom hosting provider |
namespaces | [] | downloaded namespaces, passed as the namespace argument of getString |
language | null | forced language key instead of the device locale |
fallbackLanguage | null | language used for keys missing in the current one |
customerId | null | customer-specific translations |
minimumRefreshIntervalMillis | 600_000 | throttle for automatic refreshes |
refreshOnForeground | true | refresh when an activity is resumed |
Translation Hosting setup
The SDK reads exactly what you publish, so hosting configuration is the other half of the setup.
-
Publish. Nothing is served until a publication happens, editing translations changes nothing on the CDN. Publish from the Hosting tab, with the CLI or through the API:
simplelocalize publish --apiKey <PROJECT_API_KEY> --environment _latestFull publications always hit
_latestfirst and cascade to_productionfrom there. See publishing translations. -
Pick an environment per build. Point debug builds at
_latestand release builds at_production._productionis served withCache-Control: max-age=3600, so a publication reaches users within about an hour;_latestis cached briefly and is the right target while iterating. See environments. -
Check Settings → Hosting. Both the flat (
{"home_title": "Hi"}) and the nested ({"home": {"title": "Hi"}}) JSON format work, nested payloads are flattened to dot separated keys. Missing translations may be published as empty strings; the SDK treats an empty translation as missing and falls back to the bundled string, so the UI never goes blank. See hosting settings. -
Match the keys to resource names. Keys are case-sensitive and, for the
wrapContext()integration, must equal thestrings.xmlentry names (home_title, nothome.title). Upload yourstrings.xmlwith the Android strings file format and the keys match automatically.
Limitations
- Plurals are not supported.
getQuantityString()and string arrays are never overridden, they always come from the resources compiled into the APK. Publish plural forms as separate keys if you need them over the air. - The SDK is read-only: it never sends anything to SimpleLocalize, and the CDN it reads is public.