How to handle mixed RTL & LTR languages in notific

2020-06-03 06:36发布

问题:

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?

回答1:

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: