Search code examples
androidandroid-layoutandroid-viewandroid-resourceslayout-inflater

How is the Resource class correctly overwritten to implement a dynamic translation system?


Hello to all Android Developers, I need to clarify a doubt in relation to the management of dynamic resources in Android applications.

I need my application to use the translations returned by my backend depending on the language configured on the phone.

I wanted to implement it in an elegant way working on a custom LayoutInflater that applies a ViewTransformer depending on the type of graphic component.

Each ViewTransformer will only collect the identifier (for example @id/landing_welcome_text) and make the next call:

 val value = attrs.getAttributeValue(index)
 if (value != null && value.startsWith("@")) {
      val text = view.context.resources.getString(attrs.getAttributeResourceValue(index, 0))
     setTextForView(view, text)
 }

A ContextWrapper has been implemented that returns my custom LayoutInflater and a Resource implementation

override fun getSystemService(name: String): Any {
   return if (Context.LAYOUT_INFLATER_SERVICE == name)
      CustomLayoutInflater(
          LayoutInflater.from(baseContext),
                this,
                viewTransformerManager
            )
      else
            super.getSystemService(name)
}

override fun getResources(): Resources = customResources

The problem is that overwriting the behavior of the Resources class is considered a deprecated strategy.

As the documentation says:

This constructor is deprecated. Resources should not be constructed by apps. See Context.createConfigurationContext(Configuration).

class CustomResourcesWrapper constructor(
    res: Resources,
    private val languageStringRepo: ILanguageStringRepo
): Resources(res.assets, res.displayMetrics, res.configuration) {


    @Throws(Resources.NotFoundException::class)
    override fun getString(id: Int): String {
        val value = getStringFromRepository(id)
        return value ?: super.getString(id)
    }

}

Does anyone know how I can get the same functionality without overwriting the Resources class?

Thank you very much for your help :)


Solution

  • I was looking into the same thing some time ago, in the end our team decided to go with Lokalise SDK.

    From what I found out, overriding resources is the only way to do it. And even then it still doesn't cover all the cases, like mentioned in Lokalise documentation:

    Some views are not supported when inflating from XML (Action bar, Menu items, Android preferences, may be others), but you can still get the latest translations via getString(), getText(), getQuantityString() and other system methods, and set the content of these views programmatically.

    I saw a similar implementation in this library https://github.com/hamidness/restring although it wasn't nearly as complete as Lokalise. You can see how Lokalise is implemented if you include their library and switch to Project view in Android Studio, expand External Libraries and find com.lokalise.android, then you can see the decompiled .class files:

    Decompiled sources

    As for the constructor being deprecated - they deprecated it for the purpose of recreating the Resources when you need them for a different Configuration. But Context.createConfigurationContext doesn't let you override the source of the strings provided by resources, so I don't see any alternative.