Problems encountered in the practice of project RTL language adaptation and summary

Time:2022-11-23

Image from: https://picography.co/ocean-s…
Author of this article: JDMin

opening

The Arabic script is spoken today by approximately 660 million people in more than 22 countries, making it the third most written language in the world after Latin and Chinese. With the gradual deepening of business overseas expansion, app adaptation to Arabic has been put on the agenda. The most obvious difference between Chinese and English, which we have more contact with, is that Arabic is written and used from right to left. Although iOS itself already has a lot of processing for this RTL (Right-To-Left) language, when we develop, we need to pay attention to using the correct specification to avoid mistakes. At the same time, each business and app has its own special design and other characteristics, which will also bring many new problems to be solved. The following introduces the problems and solutions encountered by recent projects in adapting the RTL language.

Before introducing specific problem scenarios, let’s introduce some main features related to RTL language and engineering adaptation:

  • text. The most obvious difference from LTR (Left-To-Right) languages ​​such as Chinese and English is that RTL languages ​​are written and read from right to left.
  • icon. Icons should be handled flexibly for each specific icon. Considering that the copywriting and usage habits of RTL language are from right to left, many icons with clear directions need to change their downward direction (for example, such as commonly used arrow icons). As for other general icons, they remain unchanged in the UI.
  • number. We have more daily contact with Arabic numerals, or Western Arabic numerals. In contrast, Eastern Arabic numerals. Different Arabic countries use different Arabic numerals. For example, Western Arabic numerals are commonly used in Morocco and Algeria, while countries such as Iran, Afghanistan, and Pakistan use Eastern Arabic numerals. In countries such as Egypt and Saudi Arabia, both forms of Arabic numerals are used. Before development, it is necessary to confirm which Arabic numerals are used in the regions where we need to provide services, and to process and display them correctly.

Project status and characteristics

In fact, the iOS system has already done a lot of processing on the RTL language, and provided many APIs to facilitate the business adaptation of the upper layer. However, before discussing these specific issues, we need to understand the current status and characteristics of the current project, and choose the most suitable solution accordingly. In summary, the current characteristics of the project are as follows:

  1. Larger in size. The project has developed to this day, and the amount of code has been relatively large. For relatively large changes, it is necessary to consider the transformation cost and whether there will be any hidden dangers for future business expansion.
  2. The project has a lot of layout code, especially the layout code of the earlier business, which is handled by manual layout using frame layout, without using AutoLayout.
  3. The app supports users to set the language within the app. When the app starts for the first time, the user’s system language will be selected as the default language, and the user can switch languages ​​within the app.

As for the specific impact of the layout method and the language setting in the application on RTL adaptation, we will introduce it in detail later.

problems encountered

In-app language switching

When we set the language to an RTL language such as Arabic in the system settings, the system will automatically change the layout of the App to an RTL layout. Here we will encounter the first problem. We can set the language in the app. When the layout of the application setting language and the system language are inconsistent (for example, the application is set to Arabic and the system is set to English), we hope to use the in-app language prevail. At this time, the default processing of the system can no longer be used. After iOS9, iOS isUIView opened up a newproperty

@property (nonatomic) UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0));

passsemanticContentAttribute The developer can customize whether a view should be flipped under RTL and RTL layout. We need to set the View in the App according to the language in the appsemanticContentAttribute , to avoid using the system’s default judgment. It is obviously too troublesome to make changes to each View here.
Our approach is to set the language by settingUIView.appearance().semanticContentAttributeAccording to the global processing.

if isRTLLanguage {
    UIView.appearance().semanticContentAttribute = .forceRightToLeft
} else {
    UIView.appearance().semanticContentAttribute = .forceLeftToRight
}

For Views with special adaptation scenarios (not flipped in RTL mode), you can set the relevant UI element instances at the top of the business.semanticContentAttribute

layout

There are generally two types of layout methods that we commonly use now. One is to use AutoLayout, and the other is manual layout of frame layout. We introduce them one by one.

For AutoLayout, including the commonly used three-party package library Masonry, SnapKit, etc., it has a relatively good compatibility with RTL. In RTL and LTR, the actual directions corresponding to Left and Right are the same, and the layout will not change. Therefore, when we set constraints, we need to use Leading and Trailing with general meaning to replace the left and right commonly used in the past. Leading is the front constraint, corresponding to Left in LTR and Right in RTL. Trailing is the tail constraint, corresponding to right in LTR and Left in RTL. Use Leading and Trailing to set constraints, and View will follow its ownsemanticContentAttribute Specifically, LTR or RTL automatically adjusts the layout.

Since a large number of layout codes in our business are manually laid out using frame layout, it is unrealistic to switch to AutoLayout. Especially for scenes with complex UI elements and layout logic, rewriting the layout is difficult and time-consuming. Therefore, it is necessary to consider an RTL adaptation method that provides a smaller modification cost for this layout method. When we set in LTRleft(view.origin.x) = a, mapped to the RTL coordinate system, is actually settingright(view.left + view.width) = view.superview.width - a. When we set in LTRright = a, mapped to the RTL coordinate system, is actually settingview.superview.width - a + self.width, therefore, we can unify the two coordinate systems, refer to the definition in AutoLayout, and extend the leading and trailing attributes of View.

@implementation UIView (RTL)

- (CGFloat)leading {
    NSAssert(self.superview != nil, @"Using leading must add the current view to superView!");
    if ([self isRTL]) {
        return self.superview.width - self.right;
    }
    return self.left;
}

- (void)setLeading:(CGFloat)leading {
    NSAssert(self.superview != nil, @"Using leading must add the current view to superView!");
    if ([self isRTL]) {
        self.right = self.superview.width - leading;
    } else {
        self.left = leading;
    }
}

- (CGFloat)trailing {
    NSAssert(self.superview != nil, @"To use trailing, the current view must be added to superView!");
    if ([self isRTL]) {
        return self.leading + self.width;
    }
    return self.right;
}

- (void)setTrailing:(CGFloat)trailing {
    NSAssert(self.superview != nil, @"To use trailing, the current view must be added to superView!");
    if ([self isRTL]) {
        self.right = self.superview.width - trailing + self.width;
    } else {
        self.left = trailing - self.width;
    }
}

@end

Before setting leading and trailing, it is required that the View has been added to the superview and the size has been set. This can be satisfied in most scenarios (taking our project as an example, there are no scenarios that cannot meet the conditions for the time being).
After adding these related methods, when RTL is adapted, the original (LTR scene) left setting is changed to use the leading setting. The original right setting is changed to use trailing setting. The concept and usage of AutoLayout are basically the same, and the adaptation cost is greatly reduced.

Image

As mentioned above, not all pictures need to be flipped in RTL mode, only some pictures (generally speaking, often have clear direction meaning and nature pictures) need to be flipped.
For images that need to be flipped, there are several ways to handle them.
After iOS9,UIImage Added related methods,

- (UIImage *)imageFlippedForRightToLeftLayoutDirection API_AVAILABLE(ios(9.0));
@property (nonatomic, readonly) BOOL flipsForRightToLeftLayoutDirection API_AVAILABLE(ios(9.0));

For images that need to be flipped differently under LTR and RTL, you can passimageView.image = targetImage.imageFlippedForRightToLeftLayoutDirection()to set. Or in Image Set, set the Direction of related image resources,
Problems encountered in the practice of project RTL language adaptation and summary
It should be noted that these two methods are used forUIImageView , it will be invalid for other containers. Also note that the display is usingUIImageView ofsemanticContentAttribute Make a flip judgment,semanticContentAttribute If the setting is wrong, the final display image will also be wrong.
In view of the above reasons, it is possible toUIImage Provide a custom flip method,

@implementation UIImage (RTL)
- (UIImage *_Nonnull)checkOverturn {
    if (isRTL) {
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
        CGContextRef bitmap = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(bitmap, self.size.width / 2, self.size.height / 2);
        CGContextScaleCTM(bitmap, -1.0, -1.0);
        CGContextTranslateCTM(bitmap, -self.size.width / 2, -self.size.height / 2);
        CGContextDrawImage(bitmap, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage);
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        return image;
    }
    return self;
}
@end

At the same time, it provides a flipping method for the View container,

@implementation UIView (RTL)
- (void)checkOverturn {
    // Avoid repeated flips
    if (self.overturned) {
        return;
    }
    // Flip based on transform
    self.transform = CGAffineTransformScale(self.transform, -1, 1);
}
@end

The top-level business can be handled in an appropriate way according to the actual scenario.

text

There are three important issues to be dealt with here in the text, one is the alignment of the text (alignment). One is the processing of AttributeString. One is the sequence of characters in the text (characters from left to right or right to left). We introduce them one by one.

alignment

Let’s discuss alignment first. existNSTextmiddle,NSTextAlignmentdefined as,

/* Values for NSTextAlignment */
typedef NS_ENUM(NSInteger, NSTextAlignment) {
    NSTextAlignmentLeft      = 0,    // Visually left aligned
#if TARGET_ABI_USES_IOS_VALUES
    NSTextAlignmentCenter    = 1,    // Visually centered
    NSTextAlignmentRight     = 2,    // Visually right aligned
#else /* !TARGET_ABI_USES_IOS_VALUES */
    NSTextAlignmentRight     = 1,    // Visually right aligned
    NSTextAlignmentCenter    = 2,    // Visually centered
#endif
    NSTextAlignmentJustified = 3,    // Fully-justified. The last line in a paragraph is natural-aligned.
    NSTextAlignmentNatural   = 4     // Indicates the default alignment for script
} 

We use the most commonly used Text containerUILabel as an example. forUILabel , if not settextAlignment , before iOS9 will default toNSTextAlignmentLeft , after iOS9 the default isNSTextAlignmentNatural NSTextAlignmentNatural It will automatically adjust the appropriate alignment for us according to whether the system language is RTL. For scenarios that require in-app language setting, because the in-app language may be inconsistent with the system language, the system’s default processing cannot be used. It needs to be set manually according to whether the current application is set to RTL languageUILabel oftextAlignment . For convenience, it is possible to extend theUILabelofrtlAlignmentmethod. The business layer is set as neededrtlAlignment

typedef NS_ENUM(NSUInteger, NMLLabelRTLAlignment) {
    NMLLabelRTLAlignmentUndefine,
    NMLLabelRTLAlignmentLeft,
    NMLLabelRTLAlignmentRight,
    NMLLabelRTLAlignmentCenter,
};

@implementation UILabel (RTL)

- (void)setRtlAlignment:(RTLAlignment)rtlAlignment {
    [self bk_associateValue:@(rtlAlignment) withKey:@selector(rtlAlignment)];
    
    switch (rtlAlignment) {
        case RTLAlignmentLeading:
            self.textAlignment = (isRTL ? NSTextAlignmentRight : NSTextAlignmentLeft);
            break;
        case RTLAlignmentTrailing:
            self.textAlignment = (isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
            break;
        case RTLAlignmentCenter:
            self.textAlignment = NSTextAlignmentCenter;
        case RTLAlignmentUndefine:
            break;
        default:
            break;
    }
}

- (RTLAlignment)rtlAlignment {
    NSNumber *identifier = [self bk_associatedValueForKey:@selector(rtlAlignment)];
    if (identifier) {
        return identifier.integerValue;
    }
    return RTLAlignmentUndefine;
}

@end

Processing of AttributeString

Since setting textAlignment cannot take effect on AttributeString, AttributeString needs to be processed separately. The processing method is similar to setting textAlignment, just replace it withNSParagraphStyle to deal with.

@implementation NSMutableAttributedString (RTL)

- (void)setRtlAlignment:(RTLAlignment)rtlAlignment {
    switch (rtlAlignment) {
        case RTLAlignmentLeading:
            self.yy_alignment = (isRTL ? NSTextAlignmentRight : NSTextAlignmentLeft);
            break;
        case RTLAlignmentTrailing:
            self.yy_alignment = (isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
            break;
        case RTLAlignmentCenter:
            self.yy_alignment = NSTextAlignmentCenter;
        case RTLAlignmentUndefine:
            break;
        default:
            break;
    }
}

@end

character order

The system will use the first character of Text as the basis for judging the sort order. For example, the text “مرحبا Hello”, because the first character is an Arabic character, it will be processed using RTL rules. Similarly, if the text is “Hello مرحبا”, since the first character is Chinese, the LTR rule will be used. This processing method is not a problem when there is only a single language in Text, but when encountering a scene where RTL language and LTR language are mixed, the situation will become much more complicated and more careful consideration is required.

Take a common example, such as the @ format syntax often used in chat messages, there are probably these scenarios in LTR and RTL,
Problems encountered in the practice of project RTL language adaptation and summary

It can be seen that although the default processing of the system can deal with most situations. However, in some scenarios, the requirements cannot be met, such as the label[3] above.
We hope that the “@” with the back of the user name as a whole, for “م ر ح ب an @ me, the weather is good today?”, we expect to show as “@ me, the weather is good today? م ر ح ب an”, but at the end of the show became “I, the weather is good today it @ م ر ح ب an”. Or if we want to show it as LTR,
Problems encountered in the practice of project RTL language adaptation and summary
But it will eventually show as,
Problems encountered in the practice of project RTL language adaptation and summary

For these scenarios, we need to insert some relevant Unicode corrections. The more commonly used related Unicodes are as follows.
Problems encountered in the practice of project RTL language adaptation and summary

To return to the previous two examples, for “standardize standardize standardize @me, is it a good day today”, iOS also regards @ as part of the “standardize standardize standardize” in Arabic, we need to manually add the LEFT-TO-RIGHT flag \u200E to @, declaring it as LTR display. For the second example, we need to add \u202A declaration as LTR display for several Arabic characters and use \u202C as the closing tag.
Problems encountered in the practice of project RTL language adaptation and summary

Other Notes

In addition to those introduced above, there are some scattered points that need attention.

UICollectionView

UICollectionView In the RTL scene, flipping is also required. The system will not help us do this by default, and we need to handle it ourselves. After iOS11,UICollectionViewLayoutextended areadonly ofproperty

@property(nonatomic, readonly) BOOL flipsHorizontallyInOppositeLayoutDirection; 

flipsHorizontallyInOppositeLayoutDirectionThe default isfalse, when set totruehour,UICollectionView The horizontal coordinate system will be flipped according to the current RTL situation. Since this is areadonlyproperties, we need to inheritUICollectionViewLayout and rewriteflipsHorizontallyInOppositeLayoutDirectiongetter method.

UIEdgeInsets

UIEdgeInsets is defined inleftandright, in the RTL scene, the system will not help us to do flip processing. Although after iOS11, the system has addedNSDirectionalEdgeInsetsDefinition, but for commonly used UI controls (such asUIButton etc.) does not expand the relevant properties, but still needs to be setUIEdgeInsets. So consider adding something likeUIEdgeInsetsMake_RTLFlipThe definition is convenient for the upper layer to use.

UIEdgeInsets UIEdgeInsetsMake_RTLFlip(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
{
    if (!isRTL)
    {
        UIEdgeInsets insets = {top, left, bottom, right};
        return insets;
    }
    UIEdgeInsets insets = {top, right, bottom, left};
    return insets;
}

UINavigationController

The slide back gesture of navigationBar will do RTL processing according to the current system language. For our commonly used LTR scene, slide right to return. In the RTL scene, slide left to return. For the scenario of in-app custom language, setUIView.appearance().semanticContentAttributewon’t change this gesture, you still need to setUINavigationController.view.semanticContentAttribute

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        self.view.semanticContentAttribute = [UIView appearance].semanticContentAttribute;
    }
    return self;
}

Gesture

For directional Gestures (such asUISwipeGestureRecognizeretc.), the system will not change the response direction of the gesture. This can only be logically judged by the upper layer based on whether the current scene is an RTL scene. Generally speaking, such gestures are not used very frequently, so the adaptation processing cost of the business layer is not high.

number

As mentioned at the beginning, numbers are also an important point to consider. Whether to use Western Arabic numerals or Eastern Arabic numerals is different in number rules and display. Because the Western Arabic numeral rules used in this business adaptation are the same as our daily contact, we will not expand here. If Eastern Arabic numerals are used, then the digital logic needs to be handled additionally.

Summarize

At this point, the overall RTL compatibility is basically completed. To sum up, since the current app needs to support setting the language in the app, many problems have become complicated. Moreover, due to the many characteristics of the App itself, it is necessary to choose a solution with relatively controllable modification costs and risks when designing the solution. If you do not need to set the App language independently in the app, or if you just want to develop an App from 0 to 1, you can design a solution that is more suitable for the current business according to your own business characteristics.

References

  • Internationalization and Localization Guide
  • Design for Arabic
  • How to use Unicode controls for bidi text

This article was published by the NetEase Cloud Music Technology Team. Any form of reprinting without authorization is prohibited. We recruit various technical positions all year round. If you are going to change jobs and you happen to like cloud music, then join us at grp.music-fe(at)corp.netease.com!