0

I need to translate my UI using Kotlin (for Android). I used this code (every time users run the app):

    val sharedPref: SharedPreferences = getSharedPreferences(UI_LANGUAGE_CHANGED, PRIVATE_MODE)
    var restart: Boolean = sharedPref.getBoolean(UI_LANGUAGE_CHANGED, true)

    var lang = selectedLanguageVar.split("-")[0]
    if (translations_languages.indexOf(lang) == -1) {
        lang = "en"
    }
    //println("-->sel: " + selectedLanguageVar + " -->lang: " + getString(R.string.language))
    //println("-->index: " + translations_languages.indexOf(lang))
    var locale: Locale = Locale(lang)
    Locale.setDefault(locale)
    var res: Resources = resources
    var config: Configuration = res.configuration
    config.setLocale(locale)
    //config.setLayoutDirection(locale)
    //createConfigurationContext(config)
    resources.updateConfiguration(config, resources.displayMetrics)

    if (restart || type == "restart") {
        val intent = Intent(this, RestartActivity::class.java).also {
            startActivity(it)

            finish()
        }
    } else {
        if (type == "start") {
            val intent = Intent(this, RestartActivity::class.java).also {
                startActivity(it)
            }
        }
    }

but it's translated in the selected language JUST when it is a device language when I installed the app. So, for example, I install the app and on my device there are English and French. If I select one of these two languages, it translate correctly, otherwise it doesn't work.

I tried also createConfigurationContext(config) but it doesn't work. Is there a way to translate always in a selected language (independently on device)?

MrMe
  • 59
  • 7
  • Changing the language from the app in android depends a lot on the android version you're running. For example, `config.setLocale` works on `N`, but on `N_MR1` you need `setLocales` to set a list of locales. Which versions are you targetting? – Fred Jan 13 '20 at 09:25
  • Oh, I didnt' know that. It's "API 23" (android 6.0) to "API 29" (android 10.0) – MrMe Jan 13 '20 at 09:27
  • Ok, let me post a possible solution. – Fred Jan 13 '20 at 09:28

1 Answers1

1

As stated in the comments, changing a language is dependent on the API level you are supporting. This is how we're doing it in my company which supports also since API 23.

We do it by wrapping the context of the single activity we have. In here, I'm changing it to French, but you'd have to decide which locale to use. I think that part is depending on your use case, so I'll leave it out of this answer. Every time you want to change the language you need to recreate the activity. This can easily be done by obtaining an instance of the activity and issuing recreate() on it.

class BaseActivity : AppCompatActivity() {

  override fun attachBaseContext(newBase: Context) {
    super.attachBaseContext(newBase.wrap(Locale.FRENCH))
  }
}

The method that wraps the context is an extension function written like:

fun Context.wrap(desiredLocale: Locale): Context {
  if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M)
    return getUpdatedContextApi23(desiredLocale)

  return if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N) 
      getUpdatedContextApi24(desiredLocale) 
    else 
      getUpdatedContextApi25(desiredLocale)
}

And here are each of the methods that update the locale depending on the version:

@TargetApi(Build.VERSION_CODES.M)
private fun Context.getUpdatedContextApi23(locale: Locale): Context {
  val configuration = resources.configuration
  configuration.locale = locale
  return createConfigurationContext(configuration)
}

private fun Context.getUpdatedContextApi24(locale: Locale): Context {
  val configuration = resources.configuration
  configuration.setLocale(locale)
  return createConfigurationContext(configuration)
}

@TargetApi(Build.VERSION_CODES.N_MR1)
private fun Context.getUpdatedContextApi25(locale: Locale): Context {
  val localeList = LocaleList(locale)
  val configuration = resources.configuration
  configuration.locales = localeList
  return createConfigurationContext(configuration)
}
Fred
  • 16,367
  • 6
  • 50
  • 65
  • What exactly is not working? What are you experiencing and what are you doing? – Fred Jan 13 '20 at 13:56
  • The app doesn't change language – MrMe Jan 14 '20 at 14:11
  • I understand, but are you recreating the activity? Are you sure the context is wrapped? Do you have a link to the source code so I can take a look? – Fred Jan 14 '20 at 14:12
  • I use a new activity RestartActivity which close MainActivity and reopen it. – MrMe Jan 14 '20 at 14:13
  • The activity that uses the translations needs to be the one wrapping the context. The context that is translating things is bound to the activity instance. If the activity finishes, then the context won't be wrapped. – Fred Jan 14 '20 at 15:11
  • Now I get "Unresolved reference" for ".wrap" (in attachBaseContext). How should I fix this reference-issue? – MrMe Jan 14 '20 at 16:08
  • hard to tell without any code. I think the easiest would be to get me a link to the code of the app – Fred Jan 14 '20 at 16:56
  • Oh, sure! Project is open-source and it's on github: https://github.com/Sav22999/common-voice-android/blob/master/app/src/main/java/org/commonvoice/saverio/MainActivity.kt – MrMe Jan 14 '20 at 16:58
  • If the `MainActivity` is suppose to react to language changes like I posted then the method `attachBaseContext` must be overriden and the context needs to be wrapped there. Please take another look at my answer and implement the method in the `MainActivity` and recreate the main activity. – Fred Jan 15 '20 at 09:07
  • I'll try again, because before I didn't use "recreate". By the way, it returns me that error because of the "wrap". Do you have a solution? – MrMe Jan 15 '20 at 10:17
  • `wrap` is an extension in context, so unless you make it public available and import it in the right places it will say it's undefined. – Fred Jan 15 '20 at 10:40
  • Ok, now it works! Android Studio sometimes "go crazy" (I've deleted everything, and pasted again). – MrMe Jan 15 '20 at 10:51
  • It returns an other error: "base" in the last private fun. Any solutions? – MrMe Jan 15 '20 at 10:52
  • Sorry, that was my mistake. I've edited the answer. – Fred Jan 15 '20 at 10:53
  • Ok. An other question. Now it's set to `FRENCH`, but if I want to pass a language loaded from `string.xml` is it possible? With `getString(R.string.language)` it goes in error :/ (I know I have to pass `Locale(lang)`, but `var lang=getString(...)` doesn't work) – MrMe Jan 15 '20 at 11:17
  • I suppose you'd have to implement something like this: https://stackoverflow.com/questions/2522248/how-to-get-locale-from-its-string-representation-in-java – Fred Jan 15 '20 at 13:17
  • I've already tried to use `Locale.getDefault().language` but it returns always `en`. – MrMe Jan 15 '20 at 14:20
  • I've solved using https://stackoverflow.com/questions/43160062/cannot-get-shared-preferences-inside-custom-context-wrapper-injection, so `newBase.getSharedPreferences` instead of `getSharedPreferences`. Thank you very much! – MrMe Jan 15 '20 at 14:47
  • I have a problem. As before, when I use Android Studio and run the app from that one, it works correcly. When I install the app from Play Store, it doens't work correctly, basically just the language is installed in the device it shows in that one. Why? – MrMe Jan 15 '20 at 18:24
  • If you're using bundles and not apks you'll have to specify that the splits should not split in language. https://stackoverflow.com/questions/52731670/android-app-bundle-with-in-app-locale-change this link explains it. – Fred Jan 15 '20 at 20:57
  • Perfect! Thanks again! – MrMe Jan 15 '20 at 22:27
  • If it helps it would be awesome if you could accept the answer so others can benefit from it too – Fred Jan 16 '20 at 07:08