Handle localized string contains a link in a single TextView
Asked Answered
M

3

4

i have a string in strings.xml file. clicks on some part of this string redirect to a task. that some part where made based on the index of the string.

Now i am trying to translate it to french but i am getting index out of bound exception as its less then the length of English strings.

Could anyone please say, what will be the best way to handle this scenario?

String separation is one thing we can do.

but i want to handle it in one text view itself.

Code for the English String:

    SpannableString spannableString = new SpannableString(getResources().getString(R.string.desc));
    ClickableSpan clickableSpan = new ClickableSpan() {
        @Override
        public void onClick(View textView) {
            Log.v("dosomething", "dosomething");
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            Log.v("task one", "task one");
        }
    };

    spannableString.setSpan(clickableSpan, 87, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    mDesc.setText(spannableString);
    mDesc.setMovementMethod(LinkMovementMethod.getInstance());
Monster answered 4/8, 2017 at 10:19 Comment(1)
You need string separation, but only to find int start for setSpan() method.Darnley
D
4

Then you will have to detect the language that the device is using whether it uses the french strings or the english strings then, put this in each condition defining the length for the given strings file. e.g.

 if (Locale.getDefault().getLanguage().equals("en")) {
     spannableString.setSpan(clickableSpan, 87, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 }
 else if (Locale.getDefault().getLanguage().equals("fr")) {
     spannableString.setSpan(clickableSpan, 50, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 }

just change the start and the end length depending on the string that is in the strings file.

Delta answered 4/8, 2017 at 10:27 Comment(3)
Can we able to do without language specific @DeltaMonster
I think so, you will have to get the resource string size and calculate the start and end length programmatically.Delta
This solution is not really scalable and quite error prone... There is a more generic solution by using annotationsAloysius
A
5

You can use annotations and then set the ClickableSpan on the annotated part. That way, the translations should contain that information.

Aloysius answered 16/2, 2021 at 13:46 Comment(0)
D
4

Then you will have to detect the language that the device is using whether it uses the french strings or the english strings then, put this in each condition defining the length for the given strings file. e.g.

 if (Locale.getDefault().getLanguage().equals("en")) {
     spannableString.setSpan(clickableSpan, 87, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 }
 else if (Locale.getDefault().getLanguage().equals("fr")) {
     spannableString.setSpan(clickableSpan, 50, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 }

just change the start and the end length depending on the string that is in the strings file.

Delta answered 4/8, 2017 at 10:27 Comment(3)
Can we able to do without language specific @DeltaMonster
I think so, you will have to get the resource string size and calculate the start and end length programmatically.Delta
This solution is not really scalable and quite error prone... There is a more generic solution by using annotationsAloysius
B
-1

Define localized strings in resources like this:

<string name="complete_phrase">By clicking on continue, you agree to the terms and conditions and privacy policy.</string>
<string name="tos_part">terms and conditions</string>
<string name="pp_part">privacy policy</string>

tos_part and pp_part contain the text that is clickable in the complete_phrase.

Then build the span and set it to a TextView like this:

val someTextView = ...
val phrase = resources.getString(R.string.complete_phrase)
val spannable = SpannableStringBuilder(phrase)
    .apply {
        val tosPart = resources.getString(R.string.tos_part)
        val tosStart = phrase.indexOf(tosPart)
        setSpan(object : ClickableSpan() {

            override fun onClick(textView: View) {
                view.context.startActivity(Intent(view.context, ThermsAndConditionsActivity::class.java))
            }
        }, tosStart, tosStart + tosPart.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)

        val ppPart = resources.getString(R.string.pp_part)
        val ppStart = phrase.indexOf(ppPart)
                setSpan(object : ClickableSpan() {

            override fun onClick(textView: View) {
                            view.context.startActivity(Intent(view.context, DataPrivacyActivity::class.java))
                        }
        }, ppStart, ppStart + ppPart.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
    }
someTextView.setText(spannable)
someTextView.movementMethod = LinkMovementMethod.getInstance()
Barnstorm answered 11/12, 2018 at 13:10 Comment(3)
This is pretty bad practice and will prime you for a crash as soon as you translate to the first language. There is no guarantee that the translation of a substring == the translation of the same substring in the whole string.Gaige
@KrisGellci That is a valid point. My assumption is that the programmer is in the control of translations. He is supposed to take the tos_part and pp_part from the complete_phrase and not to rely on translators.Barnstorm
I see, I recommend using placeholder "By clicking on continue, you agree to the %$1s and %$2s." You can then feed the strings as inputs and are at least guaranteed for their range to be valid. What I just wrote is still not an ideal approach because it limits sentence structure in other languages.Gaige

© 2022 - 2024 — McMap. All rights reserved.