Generating strings.xml from JSON at Build in Android Studio

One project that I'm working on right now shares it's assets across several platforms and development teams. The client maintains all of the string data in Excel and generates several simple JSON files from a worksheet. These sheets are then used by other teams for building the web interfaces and by myself for making the iOS and Android apps. Using the raw JSON files on iOS is not a problem because I use a custom cache backed localized string manager and not the Interface Builder localisation tooling or the default NSLocalizedString macro.

In Android, string handling and localisation are highly integrated into the tools making custom solutions more difficult. Additionally, strings in Android projects are defined in a strings.xml file residing in the read-only res folder and thus are not writeable from within the app itself. The only way then to use the standard IDE etc and still use common set of JSON files for localised strings, is to generate a proper strings.xml file from the various JSON files as part of the build process. Since I am developing in Android Studio, this meant fiddling around with embedded build tool, Gradle. Being relatively unfamiliar with both Gradle and Groovy, the language used in the build.gradle file, it took me a bit of fiddling around but I was finally able to generate a proper strings.xml as part of the build.

The localised JSON string file is just a simple map of keys and strings similar to below:

{
"Localize_back_button":"戻る",
"Localize_html_title":"タイトル",
}

The additions to build.gradle file are as follows:

import groovy.json.JsonSlurper

task generateStrings { def inputFile = new File("app/src/main/assets/localized_strings.json") def json = new JsonSlurper().parseText(inputFile.text)

def sw = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(sw)

//add json values to the xml builder
xml.resources() {
    json.each {
        k, v ->
            string(name: "${k}", "${v}")
    }
}

def stringsFile = new File("app/src/main/res/values/strings.xml")
stringsFile.write(sw.toString())

}

gradle.projectsEvaluated { preBuild.dependsOn('generateStrings') }

In the new generateStrings task, the JSON file containing the localised string data is read in and a new xml MarkupBuilder is created to receive the new string data. Next we iterate over the JSON map and add the keys and values as string elements to the xml MarkupBuilder, writing the new strings.xml file when finished.

The trick to get this to be run on build, is to insert the new generateStrings task into the build process. This is done by adding the generateStrings task as a dependency to the preBuild task, ensuring that the strings.xml file is recreated before every build.

gradle.projectsEvaluated {
    preBuild.dependsOn('generateStrings')
}

Possible Improvements