How to handle mixed RTL & LTR languages in notifications?
Asked Answered
K

1

11

Background

Android 4.3 has added a lot of support for RTL (Right-To-Left) languages, such as Hebrew and Arabic.

The problem

Even though there is "textDirection", "layoutDirection" and "gravity", I can't find the equivalents for the notification builder, not even in the compatibility library.

This means that if there are Hebrew and English words together, the order is wrong. For example (and I write in English for simplicity) :

Instead of "X called Y" , you get "Y called X" (suppose "called" is a word in Hebrew), as the string is supposed to be in this format:

<string name="notification">%1$s called %2$s</string>

Note: X and Y can be either RTL or LTR words (and even numbers).

The requirements is that in Hebrew, the word on the right should be X , then the word "called" (but in Hebrew, of course), and then Y on the left. As I've tried to show in the English analogy example, it's the opposite.

What I've tried

a. I've tried to search the documentation, and all I've found is that I will probably need to override the layout, but that's not a good solution. The reasons:

  1. I might not use the correct styling of Android .
  2. It's not future proof for next Android versions, which might use a different styling.
  3. It doesn't support the ticker text.

b. I've also tried to investigate which special characters will force the text direction to be different, and it worked by adding '\u200f' to the beginning and end of the text to show, but it has a few flaws:

  1. it's not as flexible as the other attributes.
  2. I'm not sure I use the official way to handle this problem.
  3. I need to add this for each time I use a notification
  4. It doesn't work at all for tickerText. only for notifications, and even then, not for all cases.

Here's a sample code:

/** prepares a string to be shown in a notification, so that it will be shown even on RTL languages */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static String prepareNotificationText(final Context context, final String text) {
    if (VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN_MR1)
        return text;
    final boolean isRTL = context.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
    if (!isRTL)
        return text;
    return '\u200f' + text + '\u200f';
}

c. I could also switch between the '1' and '2' in the string, but this doesn't handle all cases, plus it's even more confusing to the translators.

The question

Is there any way to make the notification builder handle texts correctly (for both notifications and TickerText) ?

Any way to tweak it without actually making totally new layouts for the notifications (or change strings), which might not be in the same native style of Android ?

What's the official way to handle such a thing?

Kyungkyushu answered 22/9, 2014 at 14:23 Comment(11)
Note that as of Android version 17, you can have separate string resources for right-to-left configurations. Just create a folder /res/values-ldrtl and put all your strings specific to right-to-left configurations there. They will be bound automatically by the system based on the layout directionality and you can probably get rid of the prepareNotificationText logic.Bagpipes
@TedHopp How could it help in case I want to put real translated strings, which the OS already knows they are RTL ? Or maybe you mean I could add another placeholder for the middle word? if so, that could work for some languages, but I can't generalize the way they work as there are a lot of various syntaxes and rules for languages.Kyungkyushu
I wasn't suggesting it solved all problems. But suppose you defined a string resource named wrapped_text. In /res/values/strings.xml it could be defined as the string %s while in /res/values-ldrtl/strings.xml it could be defined as &#200f;%s&#200f;. Then you can conditionally wrap any string text in \u200f with: return context.getString(R.string.wrapped_text, text);. That could be the entire body of prepareNotificationText and it would do exactly what your code currently does.Bagpipes
@TedHopp oh,ok , yet sadly my solution doesn't always work, it appears.Kyungkyushu
Hi there, any updates on this one?Cogen
@TeodorKolev Sadly no. The only workaround I've found is to add text before. If you write a bug report or a request (here: issuetracker.google.com/issues/new ) , I will star it.Kyungkyushu
@androiddeveloper what do you mean by add text before? Adding empty string at the beginning of text?Cogen
@TeodorKolev No. I mean real Hebrew/English text, according to what you want it to look like.Kyungkyushu
@androiddeveloper my problem is that in Hebrew title is in right, but text, icon and time are like in English. I don't see any logicCogen
@TeodorKolev Sorry. This issue was a long time ago. If you can make a tiny sample and report about this to Google, I could try it and star the report.Kyungkyushu
@TeodorKolev Added answer. Hope it helps.Kyungkyushu
K
5

OK, found an answer, based on this, this and this.

For the above case:

%1$s called %2$s

In order to change it correctly to Hebrew, you need to add the special character "\u200f" , as such:

  <string name="notification">%1$s התקשר ל %2$s</string>


    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    if (VERSION.SDK_INT >= VERSION_CODES.O) {
        String id = "my_channel_01";
        CharSequence name = "channelName";// getString(R.string.channel_name);
        String description = "channelDesc";//getString(R.string.channel_description);
        int importance = NotificationManager.IMPORTANCE_LOW;
        NotificationChannel channel = new NotificationChannel(id, name, importance);
        channel.setDescription(description);
        channel.enableLights(true);
        channel.setLightColor(Color.RED);
        channel.enableVibration(true);
        channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
        notificationManager.createNotificationChannel(channel);
    }
    Intent resultIntent = new Intent(this, MainActivity.class);
    PendingIntent resultPendingIntent =
            PendingIntent.getActivity(
                    this,
                    0,
                    resultIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT
            );

    String[] names1 = new String[]{"משה", "Moses"};
    String[] names2 = new String[]{"דוד", "David"};
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            String name1, name2;
            name1 = names1[i];
            name2 = names2[j];
            name1 = "\u200f" + name1 + "\u200f";
            name2 = "\u200f" + name2 + "\u200f";

            final String text = getString(R.string.notification, name1, name2);
            NotificationCompat.Builder mBuilder =
                    new NotificationCompat.Builder(this, "TEST")
                            .setSmallIcon(R.mipmap.ic_launcher)
                            .setContentTitle(text)
                            .setContentIntent(resultPendingIntent)
                            .setChannelId("my_channel_01")
                            .setContentText(text);
            int notificationId = i*2+j;
            notificationManager.notify(notificationId, mBuilder.build());
        }
    }
}

You can also check if the current locale is RTL, before choosing to use this solution. Example:

public static boolean isRTL() {
    return
            Character.getDirectionality(Locale.getDefault().getDisplayName().charAt(0)) ==
                    Character.DIRECTIONALITY_RIGHT_TO_LEFT;
}

The result:

enter image description here

Kyungkyushu answered 12/8, 2017 at 10:26 Comment(1)
@Mahdi-Malv It doesn't? Can you please make a sample on Github, and show it?Kyungkyushu

© 2022 - 2024 — McMap. All rights reserved.