|

How to handle the localisation or multi language support in android with examples?

Sharing is caring!

Hello everyone,

Today in this article, we are going to learn about localisation to support the multiple languages in our android apps. Localisation is the basic need for the app as we want to engage the app for the different regions to make them understand their language. Generally, we handle the localisation in two different approaches.

  1. Update the resource configuration and recreate the activity, Here OS itself will figure out and load the correct locale for their resource. But we found that sometimes if the application is heavy to load the resource then it may see a black window for some moment while switching to another language, which is not good for the customer.
  2. Update the resource configuration and restart the main activity, and dynamically every string is loaded by calling a common function. In this case, the user can not see any black window to change the localisation. Let’s understand this approach in detail with an example.

Let’s take an example of two languages English and Hindi to support our app. So first of all we need to create the two folders in the res/values.

  1. res/values-en
  2. res/values-hi

Now we need to add our sting.xml file in each folder respectively.

<resources>
    <string name="title">Hello, I am the life saver.</string>
    <string name="select_language">Select Language</string>
</resources>
<resources>
    <string name="title">नमस्ते, मैं जीवन रक्षक हूँ।</string>
    <string name="select_language">भाषा चुने</string>
</resources>

Now let’s say we provide ab option to the user to select the language based on the there choice and comfort.

object LanguageList {
    val languageList: ArrayList<LanguageDTO>
        get() {
            val list: MutableList<LanguageDTO> = ArrayList<LanguageDTO>()
            list.add(LanguageDTO("en", "English"))
            list.add(LanguageDTO("hi", "हिंदी"))
            return list.toList() as ArrayList<LanguageDTO>
        }
}

@Parcelize
data class LanguageDTO(val languageCode: String, val languageTitle: String): Parcelable

Let’s create an LocaleManager file to update the resource configuration when user select any language and save into memory to support persistence of the this language.

object LocaleManager {
    fun setNewLocale(c: Context?, language: String) {
        updateResources(c, language)
    }

   
    private fun updateResources(context: Context?, language: String) {
        val locale = Locale(language)
        Locale.setDefault(locale)
        if (context != null) {
            val res = context.resources
            val config = Configuration(res.configuration)
            config.locale = locale
            res.updateConfiguration(config, res.displayMetrics)
        }
    }
}

Now let’s create a SharedPref file to save the selected language code to persist.

object SharedPref {
    private const val PREFS_NAME = "Language_prefs"
    private var sharedPreferences: SharedPreferences? = null

    private fun getInstance(context: Context): SharedPreferences? {
        if (sharedPreferences == null)
            sharedPreferences = context.getSharedPreferences(
                PREFS_NAME, Context.MODE_PRIVATE
            )
        return sharedPreferences
    }

    fun getStringParams(aContext: Context, paramName: String?, defaultValue: String?): String? {
        return getInstance(aContext)?.getString(paramName, defaultValue)
    }

    @SuppressLint("ApplySharedPref")
    fun setStringParams(aContext: Context, paramName: String?, paramValue: String?) {
        val editor = getInstance(aContext)?.edit()
        editor?.putString(paramName, paramValue)
        editor?.commit()
    }
}
object SharedPrefsConstants {
    const val USER_SELECTED_LANGUAGE_CODE = "selectedLanguageCode"
}

Now whenever we load the sting id from the resource, first we need to get the language code from the saved preference update the locale to get the correct string id from the resources. Let’s create the common function to call for those string which required for localise.

object LocalizationUtil {
    fun getLocalisedString(context: Context, strId: Int): String {
        val lan = SharedPref.getStringParams(
            context,
            SharedPrefsConstants.USER_SELECTED_LANGUAGE_CODE,
            null
        )
        if (lan != null) LocaleManager.setNewLocale(context, lan)
        return context.resources.getString(strId)
    }
}

So for here we are done for update the language and save the selected language into the SharedPref. Now we need to create the view to show and select option provide to user to change the language. So let create an fragment xml file.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:padding="16dp">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvTitle"
        android:layout_marginTop="200dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:text="@string/title"/>

    <Button
        android:id="@+id/selectLanguage"
        android:layout_width="match_parent"
        android:layout_marginTop="20dp"
        android:layout_height="wrap_content"
        android:text="@string/select_language"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle"/>

</androidx.constraintlayout.widget.ConstraintLayout>
tvTitle.text = LocalizationUtil.getLocalisedString(requireContext(), R.string.title)
selectLanguage.text =
             LocalizationUtil.getLocalisedString(requireContext(), R.string.select_language)

we can load the restring file by calling this function and when user change the language from the option then we need to start the activity.

it?.let { clickLanguage ->
               SharedPref.setStringParams(
                   requireContext(),
                   SharedPrefsConstants.USER_SELECTED_LANGUAGE_CODE,
                   clickLanguage.languageCode
               )
               requireActivity().finish()
               requireActivity().startActivity(requireActivity().intent)
           }

This is all the for localisation change and update the configuration. Let me add the full code here.

class FirstFragment : Fragment(R.layout.first_fragment) {

    private var binding: FirstFragmentBinding? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = FirstFragmentBinding.bind(view)
        initView()
    }

    private fun initView() {
        binding?.apply {
            tvTitle.text = LocalizationUtil.getLocalisedString(requireContext(), R.string.title)
            selectLanguage.text =
                LocalizationUtil.getLocalisedString(requireContext(), R.string.select_language)
            selectLanguage.setOnClickListener {
                val languageCode: String? = SharedPref.getStringParams(
                    requireContext(),
                    SharedPrefsConstants.USER_SELECTED_LANGUAGE_CODE,
                    null
                )
                showBottomSheet(LanguageList.languageList, languageCode)
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        binding = null
    }

    private fun showBottomSheet(list: ArrayList<LanguageDTO>, selectedItemId: String?) {
        val bottomSheetFragment = LanguageChangeBottomSheet {
            it?.let { clickLanguage ->
                SharedPref.setStringParams(
                    requireContext(),
                    SharedPrefsConstants.USER_SELECTED_LANGUAGE_CODE,
                    clickLanguage.languageCode
                )
                requireActivity().finish()
                requireActivity().startActivity(requireActivity().intent)
            }
        }
        val args = Bundle()
        args.putParcelableArrayList(LanguageChangeBottomSheet.LANGUAGE_LIST, list)
        args.putString(LanguageChangeBottomSheet.SELECTED_LANGUAGE_COED, selectedItemId)
        bottomSheetFragment.arguments = args
        bottomSheetFragment.show(
            requireActivity().supportFragmentManager,
            bottomSheetFragment.tag
        )

    }
}
class LanguageChangeBottomSheet(
    private val listener: (LanguageDTO?) -> Unit
) : BottomSheetDialogFragment() {

    private var binding: LanguageChangeSheetBinding? = null
    private var genericList: ArrayList<LanguageDTO> = arrayListOf<LanguageDTO>()
    private var adapter: LanguageAdapter? = null
    private var selectedLanguageCode: String? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        dialog?.setOnShowListener { dialog ->
            val d: BottomSheetDialog = dialog as BottomSheetDialog
            val bottomSheetInternal: View =
                d.findViewById(com.google.android.material.R.id.design_bottom_sheet)!!
            val bottomBehavior = BottomSheetBehavior.from(bottomSheetInternal)
            bottomBehavior.state = BottomSheetBehavior.STATE_EXPANDED
            bottomBehavior.isDraggable = false
        }
        return inflater.inflate(R.layout.language_change_sheet, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        genericList =
            arguments?.getParcelableArrayList<LanguageDTO>(LANGUAGE_LIST) as ArrayList<LanguageDTO>
        selectedLanguageCode = arguments?.getString(SELECTED_LANGUAGE_COED) ?: "en"
        binding = LanguageChangeSheetBinding.bind(view)
        initView()
    }

    private fun initView() {
        binding?.apply {
            // setup recyclerview
            recyclerView.layoutManager =
                LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
            val itemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
            ContextCompat.getDrawable(requireContext(), R.drawable.layer_divider)
                ?.let { itemDecoration.setDrawable(it) }
            recyclerView.addItemDecoration(itemDecoration)
            setRecyclerViewFixedHeight()
            attachAdapter(genericList)

        }
    }

    private fun LanguageChangeSheetBinding.setRecyclerViewFixedHeight() {
        val displayMetrics = DisplayMetrics()
        requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
        val height = displayMetrics.heightPixels * 70 / 100
        recyclerView.layoutParams.height = height
    }

    private fun attachAdapter(list: ArrayList<LanguageDTO>) {
        binding?.apply {
            adapter = LanguageAdapter(list, itemOnClick, selectedLanguageCode)
            recyclerView.adapter = adapter
        }

    }

    @SuppressLint("NotifyDataSetChanged")
    val itemOnClick: (LanguageDTO) -> Unit = { languageDTO ->
        lifecycleScope.launch {
            delay(100)
            adapter?.notifyDataSetChanged()
            delay(100)
            listener.invoke(languageDTO)
            dismiss()
        }

    }

    companion object {
        const val LANGUAGE_LIST = "language_list"
        const val SELECTED_LANGUAGE_COED = "selected_language_code"
    }
}
class LanguageAdapter(
    private var addressList: ArrayList<LanguageDTO>,
    private val itemClickListener: (LanguageDTO) -> Unit,
    private var selectedItemId: String?
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var languageFilterList = ArrayList<LanguageDTO>()

    class LanguageHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    init {
        languageFilterList = addressList
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val listView =
            LayoutInflater.from(parent.context).inflate(R.layout.language_item, parent, false)
        return LanguageHolder(listView)
    }

    override fun getItemCount(): Int {
        return languageFilterList.size
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val language = languageFilterList[position]
        holder.itemView.tv_title.text = language.languageTitle

        if (language.languageCode == selectedItemId) {
            holder.itemView.radioButton.setImageDrawable(
                ContextCompat.getDrawable(
                    holder.itemView.context,
                    R.drawable.ic_language_selected
                )
            )
        } else {
            holder.itemView.radioButton.setImageDrawable(
                ContextCompat.getDrawable(
                    holder.itemView.context,
                    R.drawable.ic_language_unselected
                )
            )
        }


        holder.itemView.setOnClickListener {
            holder.itemView.radioButton.setImageDrawable(
                ContextCompat.getDrawable(
                    holder.itemView.context,
                    R.drawable.ic_language_selected
                )
            )
            selectedItemId = language.languageCode
            itemClickListener(language)
        }
    }
}

 

0 0 votes
Article Rating

Similar Posts

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments