Platforms
Android
Localize a module through its own strings.xml files. One project can serve the iOS app as well; keys are shared unless tagged otherwise.
What you get
Everything the editor does, through the files you already ship.
AI drafts, a person verifies
Every language is drafted against your glossary and each key's length limit. A draft never ships until someone has verified it.
Every plural form a language has
Each locale gets exactly the CLDR categories it uses, Arabic six and Japanese one, written in the format's own plural shape.
Updates over the air
Publish the verified strings and let the running app fetch them, so a fixed translation reaches users without a store release.
Over-the-air deliveryRuns in CI
Push the source language on merge and pull every language before the release build, on any CI that can run a shell.
CI pipelinesOne catalog, every platform
The iOS app, the Android app and the web app share one project. Tag the keys only one of them uses and pull that subset alone.
Scriptable from the CLI
Push, pull and sync from a terminal, a build phase or a coding agent's shell, with a token in the environment.
The CLI
Set it up
From nothing to every language.
Step 1: Install the CLI and log in
Homebrew or the one-line installer, then a token issued per project under Settings, Integrations. The read scope pulls; push writes source files and values; manage creates and deletes keys.
shellbrew install anylocale/tap/anylocale anylocale login --project my-appStep 2: Push the source file
The push is additive: keys missing from the file stay in place, so a truncated file cannot empty a project. Descriptions in the file become translator notes.
shellcurl -fsS -X POST "https://anylocale.com/api/loc/push?project=my-app&format=android-xml" \ -H "Authorization: Bearer $ANYLOCALE_TOKEN" \ --data-binary @app/src/main/res/values/strings.xmlStep 3: Pull each language into its values folder
One command per locale. Add --platform android to leave out keys tagged for iOS only.
shellanylocale pull --project my-app --locale de --format android-xml \ --platform android --out app/src/main/res/values-de/strings.xmlStep 4: Pull as a Gradle task
An exec task per locale, wired in front of preBuild, so a build on any machine carries the latest verified text. The token comes from the environment; the CI pipelines guide shows the secret handling.
app/build.gradle.ktsval locales = listOf("de", "fr", "ja") val pullStrings = tasks.register("pullStrings") { doLast { locales.forEach { locale -> exec { commandLine( "anylocale", "pull", "--project", "my-app", "--locale", locale, "--format", "android-xml", "--platform", "android", "--out", "src/main/res/values-$locale/strings.xml" ) } } } } tasks.named("preBuild") { dependsOn(pullStrings) }Step 5: Add the library
Releases are served by JitPack from the repository's tags.
sdkcovers Views and plain Kotlin,sdk-composeadds the composables and already pulls insdk. Android 5.0 (API 21) upward.settings.gradle.ktsdependencyResolutionManagement { repositories { google() mavenCentral() maven("https://jitpack.io") } }gradle/libs.versions.toml[versions] anylocale = "v1.0.2" [libraries] anylocale = { group = "com.github.anylocale.anylocale-android", name = "sdk", version.ref = "anylocale" } anylocale-compose = { group = "com.github.anylocale.anylocale-android", name = "sdk-compose", version.ref = "anylocale" }app/build.gradle.ktsdependencies { implementation(libs.anylocale) // Views implementation(libs.anylocale.compose) // Compose, instead of or in addition to the line above }Step 6: Initialise once, before any string is read
Under Delivery, publish a distribution and create a key for the Android SDK. The storage provider keeps the download for offline starts and is keyed by version code, so an app update starts from its own compiled strings.
MyApplication.ktclass MyApplication : Application() { override fun onCreate() { super.onCreate() Anylocale.init { contentDelivery { url = "https://anylocale.com/ota/v1/<distribution-key>" storage = AnylocaleStorageProviderAndroid(this@MyApplication, BuildConfig.VERSION_CODE) } defaultLanguage("en") } } }Step 7: Read strings in Views and in Compose
Wrap every Activity's base context and
getString,getQuantityStringand theandroid:textattributes resolve through the SDK;preloadfetches in the background. In Compose, importstringResourceandpluralStringResourcefromcom.anylocaleinstead of the AndroidX ones.MainActivity.ktclass MainActivity : ComponentActivity() { private val anylocale = Anylocale.instance override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(AnylocaleContextWrapper.wrap(newBase)) } override fun onStart() { super.onStart() anylocale.preload(this) } }Screen.ktimport com.anylocale.pluralStringResource import com.anylocale.stringResource @Composable fun Screen(name: String, count: Int) { Text(stringResource(R.string.greeting, name)) Text(pluralStringResource(R.plurals.items, count, count)) }
Good to know
- Placeholders such as %d and %s round-trip; positional forms like %1$s are kept as written. Use positional ones in any string with more than one, because formatted="false" does not survive a pull.
- --platform is exclusive: a key ships unless it is tagged for a different platform, so only the exceptions need the iOS or Android tag. An empty or unknown value fails the run instead of shipping the other platform's strings.
- A Gradle plugin that does the pull as a task is planned; until then the CLI in a Gradle exec task does the same.
- Menus inflated from XML, preference screens and WebView content do not go through the wrapped context.
Questions
- Do I need the SDK?
- No. Without it, the text ships with each build through sync, and nothing changes in how the app reads strings. The SDK adds one thing: fetching the verified strings between releases, so a fix reaches users without a store update.Over-the-air delivery
- Does the SDK work with Jetpack Compose?
- Yes. sdk-compose adds a stringResource under com.anylocale that reads the downloaded text and recomposes when it changes. Views read through the wrapped context.
- Is there a Gradle plugin?
- Planned. Until then the CLI in a Gradle exec task does the same: pull each locale into its values folder before the build.
- Which Android versions does the SDK support?
- Android 5.0, API 21, and upward. Releases are served by JitPack from the repository's tags, so pinning a version is one line in the version catalog.
Start with the file your app already ships.
After checkout, import one strings file and read the first drafts within minutes.
Nothing to connect and nothing to install. The CLI is optional.