无侵扰的RTL改造方案

1,209 阅读3分钟

RTL改造

背景及意义

什么是RTL?

RTL为right to left的UI样式,因中东地区语言的阅读习惯为从右向左阅读,所以UI界面上,所有元素均需要靠右侧排列。包括文字、icon、图片等。尤其是表示方向性的icon或图片需要注意镜像处理。

image.png

RTL适用语言

语言缩写英文名
阿拉伯语arArbicالعربية
波斯语faPersianفارسی
希伯来语iwHebrewעברית
乌尔都语(印度、巴基斯坦)urUrduاردو
维吾尔语Uyghur

技术难点(思考过程)

适配产品范围:

  • 当地真实用户使用的产品,目前包括乘客端、司机端、增长工具、fleet、clipper和客服B端部分功能(当地客服使用相关),增长后台部分功能(用户触达相关)、官网
  • 内部员工使用的产品可不适配(使用英文)

适配需求类型:

  • 需求会在前端展现文案、图片、布局即需要适配RTL
  • 前端无任何感知的需求不需要适配RTL

当前现状:

  • 老项目较多,且各有各的技术架构,怎么样兼容到所有的项目呢?
  • 接入到每个项目中,怎么样实现无侵扰呢?

技术方案(思考过程)

老项目较多,所以需要做无侵扰式接入;

  1. 局部调整:有的项目不能全局配置RTL,比如规则配置类的平台,需要针对某个组件、某个输入框做处理; 分模块动态控制RTL,进一步根据标识限定当前组件的RTL展示,所以做一层HOC处理,供给RTL标识; (1)无侵入的局部调整,定位是增量找到所有需要调整的组件,直接注入CSS,工具会处理; (2)用户处理标识,做自定义调整;

  2. 全局调整:比如一些端上H5页面,需要全局RTL; 判定RTL后,供给RTL标识,将结果全局挂载,全局处理样式即可。

技术实现

RTLTools 主要功能:

  1. 向外提供统一的标志位,以供个性化处理,解耦业务逻辑;
  2. 统一处理节点RTL(直接注入cssStr/cssStrList);
     insert  RTL  css
=========================================================================================
                                                               ^                 ^
                                                               |                 |
                                                               | Y               | Y
         Y                                        Y
$0 isRTL--->  $1 (exist && (string[] or string)) ---> $1 includes '{}' ---> add '{style}'
         |                 |
         | N               | N
         v                 v
=========================================================================================
   insert  LTR  css

特点:RTL无侵入式改造并提供标准化的复用逻辑

业务方介入,只需要提供:

  1. 判断函数;
  2. 以及要注入的css, 多个的情况下第二个参数向内部传入css List;
  3. 以及判断函数所需要的参数。

支持的两种css注入形式(以及单个和多个):

两种形式区别:是否有具体的css配置(比如direction),一定要提供足够精准定位的css路径; 均可可支持cssStr(单个)及cssStrList(多个);

注入css的具体传参形式
// 方式1
const CSS_TEMPLATE_01 = `body
      div.ant-drawer.ant-drawer-open.ora_drawer.strategy-drawer
      div.ant-drawer-content-wrapper
      div.ant-drawer-content
      div.ant-drawer-wrapper-body
      div.ant-drawer-body
      div.ora_drawer_body
      .ant-input`;

// 方式2
const CSS_TEMPLATE_02 = `body
    div.ant-drawer.ant-drawer-open.ora_drawer.strategy-drawer
    div.ant-drawer-content-wrapper
    div.ant-drawer-content
    div.ant-drawer-wrapper-body
    div.ant-drawer-body
    div.ora_drawer_body
    .ant-input {
      text-align: right !important;
      direction: rtl !important;
    }`;
    
// 方式 3
const CSS_TEMPLATE = [CSS_TEMPLATE01];

标志位处理

为了支持更多的场景,需要自行传入具体的判断逻辑函数及其所需要的实时变化参数,向外提供统一的标志位处理函数;

处理逻辑:
static isRTL(judgeFn: Function, ...others) {
    if (judgeFn && typeof judgeFn === 'function') {
      return Boolean(judgeFn(...others));
    }
    return false;
  }
  
调用:
export const isRTL = (nodeType, nodeId) => {
  return RTLTool.isRTL(judge, nodeType, nodeId);
};
处理RTL & LTR

为了支持更多的场景,需要自行传入具体的判断逻辑函数及其所需要的实时变化参数,向外提供统一的标志位处理函数; 并且需要传入要注入的css模板;

处理逻辑:
static handleCSSItem(judgeFn: Function, insertCssStr, ...others) {
    if (insertCssStr) {
      // 切换rtl & ltr注入
      const regFlag = /{(\S|\s)*}/g.test(insertCssStr);
      let tempRTLStr = '';
      if (regFlag) {
        tempRTLStr = insertCssStr;
      } else {
        tempRTLStr = `${insertCssStr} {
          text-align: right !important;
          direction: rtl !important;
        }`;
      }

      const tempLTRStr = tempRTLStr.replace(
        /{(\S|\s)*}/g,
        `{
          text-align: left !important;
          direction: ltr !important;
        }`
      );

      switch (Boolean(RTLTool.isRTL(judgeFn, ...others))) {
        case true:
          insertCss(tempRTLStr);
          break;
        default:
          insertCss(tempLTRStr);
      }
    }
  }

  /**
   * 统一处理节点(直接传css)
   */
  static handleCSS(judgeFn: Function, cssInsert: string | string[], ...others) {
    // insertCssStr 考虑 数组与 单个 的情况
    if (cssInsert && Array.isArray(cssInsert)) {
      cssInsert.forEach(item => {
        RTLTool.handleCSSItem(judgeFn, item, ...others);
      });
    } else if (cssInsert && typeof cssInsert === 'string') {
      RTLTool.handleCSSItem(judgeFn, cssInsert, ...others);
    }
  }
  
  调用:
  export const handleCSS = (CSS_TEMPLATE, nodeType, nodeId) => {
  RTLTool.handleCSS(judge, CSS_TEMPLATE, nodeType, nodeId);
};

接入:对于项目中公共组件的改造方案

特点: 无侵入式 方案: 提升HOC 具体: 利用HOC做统一的数据处理, 比如要根据currentNode的node_type和template_id进行标识为判定,先做统一的标志为处理,然后不侵入公共组件的逻辑,直接传入标志位

/**
 * @author nikangdi
 * @description RTL 无侵入式修改富文本公共组件
 */
import React from 'react';
import { isRTL } from 'services';
import { StoreState } from 'reducers';
import { useSelector } from 'react-redux';
import { RichText as Rich, RichTextProps } from './richtext';

export const RichText = React.forwardRef((props: RichTextProps, ref: any) => {
  const { currentNode } = useSelector((s: StoreState) => ({
    currentNode: s.strategy.currentNode,
  }));
  const [RTLFlag, setRTLFlag] = React.useState<boolean>(false);
  React.useEffect(() => {
    setRTLFlag(Boolean(isRTL(currentNode?.node_type, currentNode?.template_id)));
  }, [currentNode]);

  return <Rich {...props} ref={ref} isRTL={RTLFlag} />;
});

效果评估