Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Localizing string resources added via build.gradle using "resValue"

This is in continuation to an answer which helped me on this post

We can add the string resource as follows from build.gradle:

productFlavors {
    main{
        resValue "string", "app_name", "InTouch Messenger"
    }

    googlePlay{
        resValue "string", "app_name", "InTouch Messenger: GPE Edition"
    }
}

It works like a charm and serves the purpose of having different app names per flavor. (with the original app_name string resource deleted from strings.xml file.

But, how do we add localized strings for this string resource added from build.gradle ?

Is there an additional parameter we can pass specifying the locale? OR Possible to do it using a gradle task?

Note: I cannot do this using strings.xml (not feasible because of several ways in which my project is structured)

like image 948
AndroidMechanic - Viral Patel Avatar asked Mar 19 '16 18:03

AndroidMechanic - Viral Patel


Video Answer


2 Answers

You're operating on different levels here, BuildConfig is code, and as such not localized, that's why we have Lint warnings for hard-coded strings. Localization in Android is done via <string resources, there's no way around that if you want the system to choose the language at runtime depending on user settings. There are many ways to have resources though: values folder, resValue in build.gradle, and generated resources.

You should look into the buildSrc project in Gradle, for example I use it to generate SQL Inserts from src/main/values/stuff.xml. Here's some code to start with.

buildSrc/build.gradle

// To enable developing buildSrc in IDEA import buildSrc/build.gradle as a separate project
// Create a settings.gradle in buildSrc as well to prevent importing as subproject
apply plugin: 'groovy'
repositories { jcenter() }
dependencies {
    compile localGroovy()
    compile gradleApi()
    testCompile 'junit:junit:4.12'
}

buildSrc/src/main/groovy/Plugin.groovy

import org.gradle.api.*
/**
 * Use it as
 * <code>
 *     apply plugin: MyPlugin
 *     myEntities {
 *         categories {
 *             input = file(path to Android res xml with Strings)
 *             output = file(path to asset SQL file)
 *             conversion = "structure|SQL"
 *         }
 *     }
 * </code>
 */
class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        def entities = project.container(MyEntity)
        // this gives the name for the block in build.gradle
        project.extensions.myEntities = entities

        def allTasks = project.task('generateYourStuff')
        def allTasksClean = project.task('cleanGenerateYourStuff')
        project.afterEvaluate {
            entities.all { entity ->
                //println "Creating task for ${entity.name} (${entity.input} --${entity.conversion}--> ${entity.output})"
                def task = project.task(type: GenerateTask, "generateYourStuff${entity.name.capitalize()}") {
                    input = entity.input
                    output = entity.output
                    conversion = entity.conversion
                }
                allTasks.dependsOn task
                // clean task is automagically generated for every task that has output
                allTasksClean.dependsOn "clean${task.name.capitalize()}"
            }
        }
    }
}
class MyEntity {
    def input
    def output
    String conversion

    final String name
    MyEntity(String name) {
        this.name = name
    }
}

buildSrc/src/main/groovy/GenerateTask.groovy

import net.twisterrob.inventory.database.*
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
class GenerateTask extends DefaultTask {
    @InputFile File input
    @OutputFile File output
    @Optional @Input String conversion
    @TaskAction void generate() {
        input.withReader { reader ->
            // you may need to treat output as a folder
            output.parentFile.mkdirs()
            output.withWriter { writer ->
                // custom transformation here read from reader, write to writer
            }
        }
    }
}

This is just the skeleton you can go wild and do anything from here: e.g. retrieve a CSV through the network and spread the contents into generated variant*/res/values-*/gen.xml files.

You can run it manually when you need to or run it at the right point in the build lifecycle (in build.gradle:

android.applicationVariants.all { com.android.build.gradle.api.ApplicationVariant variant ->
    variant.mergeAssets.dependsOn tasks.generateYourStuff
}
like image 169
TWiStErRob Avatar answered Sep 25 '22 11:09

TWiStErRob


If you do not have to operate on those strings, the best option is moving to strings.xml, but that would make you share all res folder between flavors. If you generate these strings based on some property on build.gradle, then I think you're out of luck, unfortunately.

EDIT: clarifying what I mean by operate above and add some options:

By operating on those strings I mean some sort of concatenation with a build parameter, a reading from command line or environment variable during the build process (e.g., getting the commit SHA1 so that it's easier to trace bugs later). If no operation is necessary, strings.xml may be an option. But when you overwrite a res folder for flavor, all of it is overwritten and that could pose a problem if several flavors share the same res except for a limited number of strings.

If each APK has its own locale, then it's just a resValue or buildConfigField in a flavor. You can define variables to for easier reuse of values. Something like

def myVar = "var"

...

flavor1 {
    resValue "string", "my_res_string", "${myVar}"
}

flavor2 {
    resValue "string", "my_res_string", "${myVar}"
}

But if several locales are needed in the same APK and it will be chosen at runtime by Android, then the string must be in the correct values-<locale> folder.

like image 24
Douglas Drumond Kayama Avatar answered Sep 28 '22 11:09

Douglas Drumond Kayama