TAOCARTS 知识

RTL 阿拉伯语前端布局适配:面向中东反向海淘站点国际化开发要点

2026-06-26 系统功能介绍

中东市场是近几年反向代购、反向海淘业务的核心蓝海市场,阿拉伯语站点开发和常规中英站点开发有极大区别,核心难点就是 RTL(Right-to-Left)从右向左布局适配。前段时间我们完成了 Taocarts 前台阿拉伯语全页面 RTL 改造,覆盖了完整的阿语首页、下单步骤流程、语言币种面板。本篇专门面向前端开发者,聊聊阿语 RTL 布局的完整适配方案,以及中东站点开发容易忽略的细节坑,想要布局中东跨境电商平台的同行可以参考。

很多前端新手会误以为,RTL 布局只需要给 根标签加上 dir=“rtl” 属性就可以完成全局反转。实际落地后会发现大量控件错乱:图标箭头方向颠倒、表单输入光标跑到右侧、步骤进度条反向、弹窗位置错乱、Flex 弹性布局排版完全失效。第三方 ECharts 图表、弹窗组件也不会自动跟随 RTL 反转,这些都是单纯设置 dir 属性解决不了的问题。一套商用的 淘宝 1688 代购系统 面向中东用户,必须做到全站完美双向适配 LTR(左到右)和 RTL(右到左)两套布局,切换语种时布局自动无感切换,这也是简易代购系统源码很难做好的细节。

一、全局样式基础设施:SCSS 混合宏与 CSS 逻辑属性

我们采用 SCSS 混合函数 + CSS 自定义属性构建全局 RTL 样式层。核心思路是:通过判断当前 lang 语种变量,在根元素添加 .layout-rtl 类,自动切换样式属性。

基础 SCSS 映射与混合宏

// styles/variables.scss

$

rtl-enabled

:

true

;

// 定义需要左右互换的 CSS 属性映射

$

rtl-properties-map

:

(

'left'

:

'right'

,

'right'

:

'left'

,

'margin-left'

:

'margin-right'

,

'margin-right'

:

'margin-left'

,

'padding-left'

:

'padding-right'

,

'padding-right'

:

'padding-left'

,

'border-left'

:

'border-right'

,

'border-right'

:

'border-left'

,

'border-left-color'

:

'border-right-color'

,

'border-right-color'

:

'border-left-color'

,

'border-left-width'

:

'border-right-width'

,

'border-right-width'

:

'border-left-width'

,

'border-left-style'

:

'border-right-style'

,

'border-right-style'

:

'border-left-style'

,

)

;

// RTL 适配混合宏:自动转换属性名

@mixin

rtl

(

$property

,

$ltr-value

,

$

rtl-value

:

null

)

{

@if

$rtl-value == null

{

// 如果没有单独指定 RTL 值,则尝试从映射表中寻找属性互换

$

rtl-property

:

map-get

(

$rtl-properties-map

,

$property

)

;

@if

$rtl-property

{

#

{

$property

}

:

$ltr-value

;

.layout-rtl &

{

#

{

$rtl-property

}

:

$ltr-value

;

}

}

@else

{

// 不涉及方向的属性,直接输出

#

{

$property

}

:

$ltr-value

;

}

}

@else

{

// 单独指定 LTR 和 RTL 不同值

#

{

$property

}

:

$ltr-value

;

.layout-rtl &

{

#

{

$property

}

:

$rtl-value

;

}

}

}

// 快速浮动工具类

@mixin

float-left

{

float

:

left

;

.layout-rtl &

{

float

:

right

;

}

}

@mixin

float-right

{

float

:

right

;

.layout-rtl &

{

float

:

left

;

}

}

// 文本对齐

@mixin

text-align-left

{

text-align

:

left

;

.layout-rtl &

{

text-align

:

right

;

}

}

应用到组件样式

// components/ProductCard.scss

.product-card

{

@include

rtl

(

'padding-left'

,

16px

)

;

// LTR 左内边距 16px,RTL 自动变为右内边距 16px

@include

rtl

(

'margin-right'

,

8px

)

;

.product-title

{

@include

text-align-left

;

font-weight

:

600

;

}

.product-tag

{

@include

float-left

;

background

:

#ff6b6b

;

color

:

#fff

;

padding

:

2px 8px

;

}

// 对于 Flex 布局,利用 CSS 逻辑属性(现代浏览器推荐)

.product-actions

{

display

:

flex

;

// 使用逻辑属性,浏览器会根据 dir 自动反转

justify-content

:

flex-start

;

// 但 flex-start 在 RTL 下表现不一致,保险做法是显式控制

.layout-rtl &

{

justify-content

:

flex-end

;

}

}

}

根元素动态绑定

<

!-- Vue 3 示例:App.vue -->

<

template

>

<

div

:

class

=

"['app-container', { 'layout-rtl': currentLocale === 'ar' }]"

:

dir

=

"currentLocale === 'ar' ? 'rtl' : 'ltr'"

>

<

router

-

view

/

>

<

/

div

>

<

/

template

>

<

script setup

>

import

{

computed

}

from

'vue';

import

{

useI18n

}

from

'vue-i18n';

const

{

locale

}

=

useI18n

(

)

;

const

currentLocale

=

computed

(

(

)

=

>

locale.value

)

;

<

/

script

>

对于通用 UI 组件库(如 Element Plus、Ant Design Vue),它们本身提供了 RTL 样式包。我们通过动态加载机制,在切换阿语时引入对应的 rtl.css 文件,避免全局样式污染。

// i18n/index.ts

import

{

createI18n

}

from

'vue-i18n'

;

const

i18n

=

createI18n

(

{

// ...

}

)

;

// 监听语言切换,动态加载 UI 库的 RTL 样式

i18n

.

global

.

on

(

'languageChanged'

,

(

locale

:

string

)

=>

{

if

(

locale

===

'ar'

)

{

// 动态导入 Ant Design Vue 的 RTL 样式(以 Antd 为例)

import

(

'ant-design-vue/dist/antd.rtl.css'

)

;

// 或者 Element Plus 的 RTL 样式

import

(

'element-plus/theme-chalk/display.css'

)

;

// 实际按官方指引

}

else

{

// 移除 RTL 样式或切换回 LTR

const

link

=

document

.

querySelector

(

'link[data-rtl="true"]'

)

;

if

(

link

)

link

.

remove

(

)

;

}

}

)

;

export

default

i18n

;

二、业务自定义模块的深度反转:步骤、卡片、导航

本次改造工作量最大的部分正是业务自定义模块,包括首页大标题文案、三步下单流程图标、功能卡片、右上角语言选择弹窗等。最初我们只反转了文字,流程顺序没有改动——例如原生 LTR 布局的步骤流程是从左到右 1→2→3,但阿拉伯用户阅读习惯是从右往左,步骤序号必须同步反转,展示为 3→2→1。中东测试用户反馈这种“文字右对齐但流程左到右”的体验极其别扭。

逻辑层反转方案

<

!-- views/CheckoutSteps.vue -->

<

template

>

<

div

class

=

"steps-container"

>

<

div v

-

for

=

"(step, index) in displaySteps"

:

key

=

"step.id"

class

=

"step-item"

>

<

div

class

=

"step-number"

>

{

{

index

+

1

}

}

<

/

div

>

<

div

class

=

"step-content"

>

{

{

step

.label

}

}

<

/

div

>

<

div v

-

if

=

"index < displaySteps.length - 1"

class

=

"step-arrow"

>

<

/

div

>

<

/

div

>

<

/

div

>

<

/

template

>

<

script setup lang

=

"ts"

>

import

{

computed

}

from

'vue';

import

{

useI18n

}

from

'vue-i18n';

const

{

locale

}

=

useI18n

(

)

;

/

/

原始步骤数据(LTR 顺序)

const

originalSteps

=

[

{

id

:

1

,

label

:

'提交转运信息' },

{

id

:

2

,

label

:

'仓库验收打包' },

{

id

:

3

,

label

:

'国际干线运输' },

]

;

/

/

根据语种动态反转步骤数组

const

displaySteps

=

computed

(

(

)

=

>

{

if

(

locale.value

=

=

=

'ar') {

/

/

深拷贝后反转,同时保留原始 id 用于业务逻辑

return

[...originalSteps].reverse

(

)

.map

(

(

step

,

idx

)

=

>

(

{

...

step

,

displayOrder

:

idx

+

1

,

}

)

)

;

}

return

originalSteps.map

(

(

step

,

idx

)

=

>

(

{

...

step

,

displayOrder

:

idx

+

1

,

}

)

)

;

}

)

;

<

/

script

>

<

style

scoped lang

=

"scss"

>

.steps

-

container

{

display

:

flex

;

flex

-

direction

:

row

;

justify

-

content

:

space

-

between

;

.

step

-

item

{

flex

:

1

;

@include

text

-

align

-

left

;

/

/

混入自动处理文字对齐

.

step

-

arrow

{

@include rtl

(

'margin-left', 8px);

@include rtl

(

'margin-right', 8px);

.layout

-

rtl

&

{

transform

:

scaleX

(

-

1

)

;

/

/

箭头图标镜像翻转

}

}

}

}

<

/

style

>

功能卡片排列反转:首页的功能推荐卡片,在 RTL 模式下也需要从右往左排列。我们采用 CSS Grid + 逻辑属性,再辅以 JavaScript 控制渲染顺序:

<

template

>

<

div

class

=

"card-grid"

>

<

CardComponent

v

-

for

=

"item in sortedCards"

:

key

=

"item.id"

:

data

=

"item"

/

>

<

/

div

>

<

/

template

>

<

script setup

>

const

cards

=

ref

(

[...]

)

;

/

/

原始卡片数据

const

sortedCards

=

computed

(

(

)

=

>

{

return

locale.value

=

=

=

'ar' ? [...cards.value].reverse() : cards.value;

}

)

;

<

/

script

>

<

style

scoped

>

.card

-

grid

{

display

:

grid

;

grid

-

template

-

columns

:

repeat

(

auto

-

fill

,

minmax

(

200

px

,

1

fr

)

)

;

gap

:

16

px

;

/

/

使用 direction

:

rtl 控制 grid 自动反向排列,但为了更精确,结合上述排序

}

<

/

style

>

三、第三方图表与弹窗组件的独立适配(ECharts / 自定义弹窗)

第三方图表、弹窗类非原生 DOM 组件不会跟随页面 dir 属性自动反转,这是 RTL 适配的第二大痛点。我们采用“双轨制”处理:

ECharts 折线图/柱状图适配

ECharts 本身不直接支持 RTL 一键切换,但可以通过配置项手动控制坐标轴方向和类目轴反转。

// hooks/useEChartsRTL.ts

import

type

{

EChartsOption

}

from

'echarts'

;

export

function

adaptEChartsForRTL

(

option

:

EChartsOption

,

isRTL

:

boolean

)

:

EChartsOption

{

if

(

!

isRTL

)

return

option

;

const

newOption

=

{

...

option

}

;

// 1. X 轴类目数据反转(如果是类目轴)

if

(

newOption

.

xAxis

&&

Array

.

isArray

(

newOption

.

xAxis

)

)

{

newOption

.

xAxis

=

newOption

.

xAxis

.

map

(

(

axis

:

any

)

=>

{

if

(

axis

.

type

===

'category'

&&

axis

.

data

)

{

return

{

...

axis

,

data

:

[

...

axis

.

data

]

.

reverse

(

)

}

;

}

return

axis

;

}

)

;

}

// 2. 系列数据同步反转

if

(

newOption

.

series

&&

Array

.

isArray

(

newOption

.

series

)

)

{

newOption

.

series

=

newOption

.

series

.

map

(

(

series

:

any

)

=>

{

if

(

series

.

data

&&

Array

.

isArray

(

series

.

data

)

)

{

return

{

...

series

,

data

:

[

...

series

.

data

]

.

reverse

(

)

}

;

}

return

series

;

}

)

;

}

// 3. 图例位置适配(默认右上角 -> 左上角)

if

(

newOption

.

legend

)

{

newOption

.

legend

=

{

...

newOption

.

legend

,

right

:

undefined

,

left

:

'5%'

,

}

;

}

// 4. Tooltip 位置微调

if

(

newOption

.

tooltip

)

{

newOption

.

tooltip

.

position

=

(

point

:

number

[

]

)

=>

{

// RTL 下 tooltip 默认向左偏移,需手动修正

return

[

point

[

0

]

-

120

,

point

[

1

]

-

10

]

;

}

;

}

return

newOption

;

}

在组件中使用

<

template

>

<

div ref

=

"chartRef"

class

=

"chart-container"

>

<

/

div

>

<

/

template

>

<

script setup

>

import

*

as

echarts from

'echarts';

import

{

adaptEChartsForRTL

}

from

'@/hooks/useEChartsRTL';

import

{

useI18n

}

from

'vue-i18n';

const

{

locale

}

=

useI18n

(

)

;

const

chartRef

=

ref

(

null

)

;

let

chartInstance

=

null

;

const

renderChart

=

(

)

=

>

{

const

isRTL

=

locale.value

=

=

=

'ar';

let

option

=

{

xAxis

:

{

type

:

'category', data: ['周一', '周二', '周三'] },

yAxis

:

{

type

:

'value' },

series

:

[

{

data

:

[

120

,

200

,

150

]

,

type

:

'line' }],

}

;

/

/

应用 RTL 适配

option

=

adaptEChartsForRTL

(

option

,

isRTL

)

;

chartInstance.setOption

(

option

)

;

}

;

watch

(

locale

,

(

)

=

>

{

renderChart

(

)

;

/

/

语言切换时重新渲染图表

}

)

;

<

/

script

>

自定义弹窗面板定位

对于右上角的语言/币种选择弹窗,我们在 RTL 模式下仍然固定其右上角位置,不受全局 RTL 影响,保证交互一致性。

.language-panel

{

position

:

absolute

;

top

:

50px

;

right

:

0

;

// LTR 靠右

width

:

240px

;

background

:

#fff

;

border-radius

:

8px

;

box-shadow

:

0 4px 12px

rgba

(

0

,

0

,

0

,

0.15

)

;

.layout-rtl &

{

right

:

auto

;

left

:

0

;

// RTL 环境下依旧靠右上角,即

left

:

0 配合父容器相对定位

}

}

特别提醒:图片资源(商品实拍图、Logo、装饰图案)不需要镜像翻转,仅文字、图标箭头、UI 控件布局反转即可。这一点必须和设计、产品团队明确对齐,避免出现“镜像文字”的乌龙。

四、阿拉伯文字体排版与换行规则

阿拉伯文字体和英文、中文字体的行高、字号基线完全不同。如果沿用中文站点的 font-family 和 line-height,阿语文字会出现高低不齐、行间距拥挤的问题。

专属字体栈配置

// styles/typography.scss

$

font-family-ltr

:

'Inter'

,

-apple-system

,

BlinkMacSystemFont

,

'Segoe UI'

,

Roboto

,

sans-serif

;

$

font-family-ar

:

'Tajawal'

,

'Amiri'

,

'Noto Naskh Arabic'

,

'Segoe UI'

,

Tahoma

,

sans-serif

;

body

{

font-family

:

$font-family-ltr

;

.layout-rtl &

{

font-family

:

$font-family-ar

;

// 阿语专属行高和字间距

line-height

:

1.8

;

// 阿语通常需要更大的行高

letter-spacing

:

0.02em

;

word-spacing

:

0.05em

;

}

}

// 标题适配

h1, h2, h3

{

.layout-rtl &

{

line-height

:

2

;

font-weight

:

700

;

// 阿语字重表现不同,适当调整

}

}

换行规则(word-break / overflow-wrap)

阿拉伯语单词内部不允许强制断行(除非有连字符),否则会破坏语义。必须禁止 word-break: break-all 在阿语环境生效。

.arabic-text

{

.layout-rtl &

{

word-break

:

keep-all

;

// 保持单词完整

overflow-wrap

:

break-word

;

// 仅在必要时整个单词换行

white-space

:

normal

;

}

}

五、交互逻辑与输入习惯适配

在交互层面,RTL 模式下的滚动条、输入框光标都有特殊表现:

滚动条:浏览器自动将垂直滚动条移到左侧(符合 RTL 习惯),但我们需确保自定义滚动条组件同步适配。

输入框光标: 和 在 dir=“rtl” 下光标默认居右。但对于数字、金额输入框,我们通常希望数字从左向右输入,此时可以单独设置 dir=“ltr”。

<

template

>

<

!-- 金额输入框,强制 LTR 保证数字输入习惯 -->

<

input

type

=

"text"

v

-

model

=

"amount"

dir

=

"ltr"

class

=

"currency-input"

/

>

<

/

template

>

数字与币种符号规范:币种符号(如 $337.85、IQD 1,500)的数字顺序保持从左到右,不要反转。行业通用规范是“内容镜像,数字不变”。

语言持久化与智能跳转

// utils/locale.ts

export

function

setUserLocale

(

locale

:

string

)

{

localStorage

.

setItem

(

'user_locale'

,

locale

)

;

document

.

documentElement

.

lang

=

locale

;

document

.

documentElement

.

dir

=

locale

===

'ar'

?

'rtl'

:

'ltr'

;

// 触发 Vue i18n 切换

i18n

.

global

.

locale

.

value

=

locale

;

}

export

function

getInitialLoca