Android App Bundle with in-app locale change

AndroidLocaleAndroid App-Bundle

Android Problem Overview


I've a problem with AAB when I need to change the app locale from within the app itself(i.e. have the language change setting inside the app), the issue is that the AAB gives me only my device languages resources, for example:

my device has English and French languages installed in it, so AAb gives me only the resources for English and French,

but from within the app itself there is a choice to switch the language between English, French, and Indonesian,

in that case, when changing the language to English or French everything is working perfectly, but when changing it to Indonesian, the app simply enters a crash loop as it keep looking for Indonesian language but it can't find.

The problem here is that even if I restarted the app, it enters the crash loop again as the app is still looking for the missing language resources, and here the only solution is to clear cash or reinstall which are the solutions that the normal user won't go through.


Just to mention it, this is how I change the locale through the app:

    // get resources
    Resources res = context.getResources();
    // create the corresponding locale
    Locale locale = new Locale(language); // for example "en"
    // Change locale settings in the app.
    android.content.res.Configuration conf = res.getConfiguration();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        conf.setLocale(locale);
        conf.setLayoutDirection(locale);
    } else {
        conf.locale = locale;
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        context.getApplicationContext().createConfigurationContext(conf);
    }
    res.updateConfiguration(conf, null);

P.S. The app is working perfectly when build it as APK.

Android Solutions


Solution 1 - Android

Edit:

The PlayCore API now supports downloading the strings for another language on-demand: https://developer.android.com/guide/playcore/feature-delivery/on-demand#lang_resources

Alternative solution (discouraged):

You can disable the splitting by language by adding the following configuration in your build.gradle

android {
    bundle {
        language {
            // Specifies that the app bundle should not support
            // configuration APKs for language resources. These
            // resources are instead packaged with each base and
            // dynamic feature APK.
            enableSplit = false
        }
    }
}

This latter solution will increase the size of the app.

Solution 2 - Android

This is not possible with app bundles: Google Play only downloads resources when the device's selected languages change.

You'll have to use APKs if you want to have an in app language picker.

Solution 3 - Android

Details of downloading the language on demand can be found here

https://android-developers.googleblog.com/2019/03/the-latest-android-app-bundle-updates.html

In your app’s build.gradle file:

dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:core:1.10.0'

    // For Kotlin users also add the Kotlin extensions library for Play Core:
    implementation 'com.google.android.play:core-ktx:1.8.1'
    ...
}

Get a list of installed languages

val splitInstallManager = SplitInstallManagerFactory.create(context)
val langs: Set<String> = splitInstallManager.installedLanguages

Requesting additional languages

val installRequestBuilder = SplitInstallRequest.newBuilder()
installRequestBuilder.addLanguage(Locale.forLanguageTag("pl"))
splitInstallManager.startInstall(installRequestBuilder.build())

Check above link for full details

Solution 4 - Android

After many hours I was finally able to use the on-demand language with the new PlayCore API.

Step 1.) As the user changes the language, you need to first check whether the language is already available, if not then download the language

private void changeLocale(final String languageSelected){
  SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(PlayAgainstComputer.this);
    final Set<String> installedLangs = splitInstallManager.getInstalledLanguages();
    if(installedLangs.contains(languageSelected)){ // checking if lang already available
        Toast.makeText(PlayAgainstComputer.this,"Done! The language settings will take effect, once you restart the app!").show();
    }
    else{
        SplitInstallRequest request =
                SplitInstallRequest.newBuilder()
                        .addLanguage(Locale.forLanguageTag(languageSelected))
                        .build();
        splitInstallManager.startInstall(request);
        splitInstallManager.registerListener(new SplitInstallStateUpdatedListener() {
        @Override
        public void onStateUpdate(@NonNull SplitInstallSessionState splitInstallSessionState) {
            if(splitInstallSessionState.status() == SplitInstallSessionStatus.INSTALLED){
                Toast.makeText(PlayAgainstComputer.this,"Download complete! The language settings will take effect, once you restart the app!").show();
            }
        }
    });
 }}

Step2.) The downloaded languages must be installed when the user starts the app. which is done in the attchBaseContext() method

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    SplitCompat.install(this); // It will install all the downloaded langauges into the app
  }

Step 3.) You need to tell the Activity to use the chosen language. Following code should be placed before setContentView(R.layout.layout); of that activity

  String selectedLanguage = getFromPrefernceOrWhereEverYouSavedIt(); // should be 2 letters. like "de", "es"
  Locale locale = new Locale(selectedLanguage); 
  Locale.setDefault(locale);
  Resources resources = getResources();
  Configuration config = new Configuration(resources.getConfiguration());
  config.locale = locale;
  resources.updateConfiguration(config,
        getBaseContext().getResources().getDisplayMetrics());

Done!

Please Note

When a user (who chose a non-default/downloaded language) updates the app, that language needs to be downloaded again into the app, so make sure you handle that in your code.

when I used activity.recreate(); after the download finished (to automatically refresh the app for new language) I faced some problems, that is why I used Toast to ask the user to manually restart the app. but you can try other methods

I also noticed some other inconsistencies (even sometimes faced memory leak because of SplitCompat.install(this);) with this method, so make sure you test and optimize it according to your code.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMuhammed RefaatView Question on Stackoverflow
Solution 1 - AndroidPierreView Answer on Stackoverflow
Solution 2 - AndroidianhanniballakeView Answer on Stackoverflow
Solution 3 - AndroidManoharView Answer on Stackoverflow
Solution 4 - AndroidbeginnerView Answer on Stackoverflow