How to Include Only One Locale in the APK

Recently, my team and I faced an issue when we had to ship a specific flavor of our application with only one locale and without a default locale. In other words, we had the task to somehow swap all the English texts we had in the default strings.xml with Spanish strings we had in values-es/strings.xml. While looking for a solution, we found several almost workable variants:

  • First what came to our minds is enabling the resConfigs "es" in the build.gradle file. As we had a special flavor for this kind of APK, we could possibly do it. But! If a user is not in the Spanish locale, then he will have the English one. We didn’t want it to happen, so it wasn’t really our choice.
    productFlavors {
      demo {
        applicationIdSuffix ".demo"
      }
      spain {
        resConfigs "es" // For excluding other locales.
        applicationIdSuffix ".spain"
      }
    }
	
  • Next, we found a possible solution by switching the locale in the runtime. It can be done like this:
    try {
      Resources resources = getApplicationContext().getResources();
      DisplayMetrics displayMetrics = resources.getDisplayMetrics();
      Configuration configuration = resources.getConfiguration();
      Locale locale = new Locale("es", "ES");
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        configuration.setLocale(locale);
      } else {
        //noinspection deprecation
        configuration.locale = locale;
      }
      resources.updateConfiguration(configuration, displayMetrics);
    } catch (Throwable throwable) {
      Log.d(TAG, "Couldn't apply ES language.", throwable);
    }

This method worked almost everytime but not always. So again, we had to seek another solution.

Solution

We came up with a little ‘brute-force’ method but one can be pretty sure of showing the right strings to the user. We wrote a groovy script that moves Spain strings to the default strings.xml and then deletes the values-es/strings.xml file. It looks like this:

/** A possible way of changing the default locale in an APK. */
android.applicationVariants.all { variant ->
  // Proceed if current build is for Spain.
  if ("spainRelease".equals(variant.name)) {
    variant.mergeResources.doLast {
      println "Start changing default texts to Spanish ones."

      final String UTF8 = "UTF-8"

      // Get the default file in which we will put our Spain strings.
      File defaultFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml")
      // Get the Spanish file where we will get our strings.
      File esFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values-es/values-es.xml")

      String defaultText = defaultFile.getText(UTF8)
      String esText = esFile.getText(UTF8)

      def defaultStringsList = []
      def esStringsList = []

      final String START_TAG = "<string "
      final String END_TAG = "</string>"
      final int END_OFFSET = 8

      int startIndex = esText.indexOf(START_TAG)
      int endIndex;
      while (startIndex > 0) {
        endIndex = esText.indexOf(END_TAG, startIndex) + END_OFFSET
        String str = esText[startIndex..endIndex]
        esStringsList.add(str)
        startIndex = esText.indexOf(START_TAG, endIndex)
      }

      startIndex = defaultText.indexOf(START_TAG)
      while (startIndex > 0) {
        endIndex = defaultText.indexOf(END_TAG, startIndex) + END_OFFSET
        String str = defaultText[startIndex..endIndex]
        defaultStringsList.add(str)
        startIndex = defaultText.indexOf(START_TAG, endIndex)
      }

      final String VALUE_TAG = "\">"
      for (String defaultString : defaultStringsList) {
        for (String esString : esStringsList) {
          if (defaultString[0..defaultString.indexOf(VALUE_TAG)].equals(esString[0..esString.indexOf(VALUE_TAG)])) {
            println "Replacing $defaultString to $esString."
            defaultText = defaultText.replace(defaultString, esString)
          }
        }
      }
      defaultFile.write(defaultText, UTF8)

      // Delete the file, so our APK can be smaller.
      if (!esFile.delete()) 
        throw new AssertionError("Spain file was not deleted. Aborting.")

      println "Finish changing default texts to Spanish ones."
    }
  }
}

This script can be easily included in the build.gradle file of the app, so feel free to do that!

Thanks for reading.

The sample repo is here.

comments powered by Disqus