
我们最近将2GIS的在线版本翻译成阿拉伯语,并且在上一篇文章中,我谈到了实现这一点所必需的理论-dir dir="rtl"
,根据什么规则显示混合焦点文本以及如何控制自己。
现在是开始练习的时候了-只需最小的努力就可以将整个界面从右转到左,这样即使是真正的阿拉伯人也不会感觉到问题。
在本文中,我将告诉您如何快速原型化CSS组装以及在JS中分解的拐杖,简要介绍翻译和本地化的功能,回顾CSS的逻辑属性,并在CSS-in-JS中触摸RTL主题。
翻转样式
当我将dir =“ rtl”属性应用于标签时,只有元素的隐式顺序发生了变化-例如,表格单元格或flex元素的顺序。 在样式中明确指定的值没有任何反应。
采取位于右下角的某些通知的样式:
.tooltip { position: 'absolute'; bottom: 10px; right: 10px; }
dir="rtl"
不会以任何方式影响这些样式-在RTL版本中,工具提示也位于右侧,尽管预期在左侧。
怎么办 您需要将right: 10px
替换为left: 10px
。 其他所有样式也是如此。 绝对定位,边距/填充,文本对齐-对于阿拉伯版本,所有内容都需要朝相反的方向旋转。
快速原型
首先,您可以毫不犹豫地更改所有从左到右的出现,并用速记值做一点魔术:
- 左:0→右:0
- padding-left:4px→padding-right:4px
- 边距:0 16px 0 0→边距:0 0 0 16px
postcss-rtl插件适用于此。 方便-您只需将其放入所有postcss-project插件列表中。 它用镜像规则替换所有定向规则,并将其包装在[dir="rtl"]
。 例如:
.foo { color: red; margin-left: 16px; } [dir] .foo { color: red; } [dir="ltr"] .foo { margin-left: 16px; } [dir="rtl"] .foo { margin-right: 16px; }
之后,您只需要设置dir="rtl"
并且仅会自动应用必要的规则。 一切正常,似乎几乎所有东西都可以投入生产,但是此解决方案仅适用于快速原型:
- 每个规则的特殊性增加 。 这不一定是问题,但是我想避免它;
- 这种操作会引起bug 。 例如, 属性的顺序可能会中断 ;
- css文件的大小明显增加 。
[dir]
添加到每个选择器,每个有向属性都重复。 在我们的案例中,一个项目的规模增加了21%,另一个项目的规模增加了35%:
| 原始大小(gzip) | 双向大小(gzip) | 肿胀 |
---|
2gis.ru | 272.3 KB | 329.7 KB | 21% |
m.2gis.ru | 24.5 KB | 33.2 KB | 35% |
habr.com | 33.1 KB | 41.4 KB | 25% |
有更好的选择吗?
必须分别收集LTR和RTL的样式。 这样就不必触摸选择器,并且CSS的大小几乎不会改变。
为此,我选择了:
- RTLCSS-该库在postcss-rtl的支持下。
- webpack-rtl-plugin是用于通过ExtractTextPlugin构建的样式的交钥匙解决方案。 引擎盖下有相同的RTLCSS。
然后,他开始将RTL和LTR收集到不同的文件styles.rtl.css
和styles.rtl.css
。 组装到不同文件的唯一缺点是,您必须先下载所需文件,才能即时替换dir。
RTLCSS允许您使用指令来控制特定规则的处理,例如:
.foo { right: 0; } .bar { font-size:16px; }
还有什么其他解决方案?
现有的所有解决方案与RTLCSS几乎没有什么不同。
- 来自Twitter的css-flip ;
- 来自Wikimedia的cssjanus ;
- 是的,并且postcss-rtl支持
onlyDirection
参数,使用该参数只能收集一个方向的样式,但是大小仍在增长-例如,对于移动2GIS,它是18%,而不是35%(24.5 kB→29 kB)。
什么时候需要指令?
何时样式不取决于方向
例如,箭头的旋转角度指示风向:

.arrow._nw { transform: rotate(135deg); }
或淡入淡出的电话号码-数字始终从左到右书写,这意味着渐变应该始终在右边:


何时将图标居中
这是上一段的特殊情况。 如果通过缩进/定位使不对称图标居中,则将其块移动到一侧,如果它反映了偏移量,则图标将“移动”到另一侧:

在以下情况下,最好将图标放在svg中:
当您需要隔离不应响应RTL的整个小部件时
在我们的情况下,这是一张地图。 当在块指令中组装时,我们将包装所有样式: /*rtl:begin:ignore*/ ... /*rtl:end:ignore*/
。
还有更好的选择吗?
逆转规则的解决方案很好,但是问题来了-它是拐杖吗? 样式对方向的依赖性是现代Web的自然任务,并且其相关性每年都在增长。 这应该已经在现代标准和方法中得到反映。 发现了!
逻辑性质
为了使布局适应不同的方向, css中有一个逻辑属性标准。 它不仅涉及从左到右和从右到左的方向,而且还涉及从上到下的方向,但我们不会考虑。
我们已经在flexs和grid-row-start
使用了类似的东西-例如, flex-start
, flex-end
, grid-row-start
, grid-column-end
从左/右解开。
建议使用inline-start
, inline-end
, block-start
和block-end
代替left
, right
, top
和bottom
的概念。 而不是width
和height
而是inline-size
和block-size
。 而不是shortcands abcd- logical adcb
(逻辑shortes逆时针旋转)。 此外,对于配对现有的缩短项,出现新的配对版本-padding padding-block
, margin-inline
, border-color-inline
等。
left: 0 → inset-inline-start: 0
padding-left: 4px → padding-inline-start: 4px
margin: 0 16px 0 0 → margin: logical 0 0 0 16px
padding-top: 8px; padding-bottom: 16px → padding-block: 8px 16px
margin-left: 4px; margin-right: 8px → margin-inline: 4px 8px
text-align: right → text-align: end
并且出现了期待已久的定位速记:
left: 4px; right: 8px → inset-inline: 4px 8px
top: 8px; bottom: 16px → inset-block: 8px 16px
top: 0; right: 2px; bottom: 2px; left: 0 → inset: logical 0 0 2px 2px
在不带标志的Firefox和基于标志的Webkit浏览器中已经可以使用此功能。
优点 -该解决方案是本地的,如果支持所需的浏览器,则完全不需要组装/插件即可使用。 不需要指令-当您指的是物理“左”时,只需left
写而不是inline-start
。
缺点来自专家-没有插件,大多数浏览器中的代码都是无效的,您需要做很多工作来翻译现有的大型项目。
如何连接?
最简单的方法是后逻辑 。 如果没有dir
参数,它将以与postcss-rtl相同的方式,使用给定的dir
参数,仅针对指定方向收集两个方向的样式:
.banner { color: #222222; inset: logical 0 5px 10px; padding-inline: 20px 40px; resize: block; transition: color 200ms; } .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; &:dir(ltr) { padding-left: 20px; padding-right: 40px; } &:dir(rtl) { padding-right: 20px; padding-left: 40px; } resize: vertical; transition: color 200ms; } .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; padding-left: 20px; padding-right: 40px; resize: vertical; transition: color 200ms; }
如何说服团队开始编写offset-inline-start而不是left?
没办法 但是我们决定在我们的项目中简化它-写start: 0
而不是offset-inline-start: 0
,只要每个人都习惯了,我就会开始施加一个有效的条目:)
RTL + CSS-in-JS =️️<3
CSS-in-JS不需要预先组装。 这意味着在运行时可以确定组件的方向,并选择要翻转的组件和不翻转的组件。 如果您需要插入一些根本不支持RTL的小部件,则很有用。
通常,任务是将{ paddingInlineStart: '4px' }
类型的对象(如果无法切换到逻辑属性,则将{ paddingLeft: '4px' }
转换为{ paddingRight: '4px' }
类型的对象:
- 装备bidi-css-js或rtl-css-js 。 它们提供了一个接受样式对象并返回转换为所需方向的函数。
- ???
- 赢利!
反应实例
将每个样式化的组件包装在可以接受样式的HOC中:
export default withStyles(styles)(Button);
他从上下文中获取组件的方向,然后选择最终样式:
function withStyles(styles) { const { ltrStyles, rtlStyles } = bidi(styles); return function WithStyles(WrappedComponent) { ... render() { return <WrappedComponent {...this.props} styles={this.context.dir === 'rtl' ? rtlStyles : ltrStyles} />; }; }; ... }; }
提供者将重点放在上下文上:
<DirectionProvider dir="rtl"> ... <Button /> ...
Airbnb使用类似的方法: https : //github.com/airbnb/react-with-styles-interface-aphrodite#built-in-rtl-support ,如果项目中已经使用了阿芙罗狄蒂 ,则可以使用此现成的解决方案。
对于JSS,它更容易-您只需要启用jss-rtl即可 :
jss.use(rtl());
样式化的组件
const Button = styled.button` background: #222; margin-left: 12px; `;
如果我们使用模板字符串而不使用对象怎么办? 一切都很复杂,但是有一个解决方案-从props
指定的方向计算属性名称:
const marginStart = props => props.theme.dir === "rtl" ? "margin-left" : "margin-right"; const Button = styled.button` background: #222; ${marginStart}: 12px; `;
但是从线条切换到对象似乎更容易, 自3.3.0版以来 ,样式化组件就可以执行此操作 。
翻译和本地化功能
我们找出了技术部分。 他们隔离了不确定方向的内容,镜像的样式,在适当的位置放置了例外,然后将文本翻译成阿拉伯语。 一切似乎准备就绪-切换语言时,整个界面出现在屏幕的另一侧,没有布局,并且一切看上去比任何阿拉伯语站点都好。
我们展示真正的阿拉伯人,并...
事实证明,并非每位讲阿拉伯语的人都知道Twitter是什么。 这几乎适用于所有英语单词。 对于这种情况,存在阿拉伯语音译:“تويتر”。
事实证明,在阿拉伯语中有逗号,并且代码中我们到处都是通过“,”连接的事实,在阿拉伯语中,我们需要通过“,”进行连接。
事实证明,在某些穆斯林国家,官方日历为伊斯兰教。 是月球,通常的翻译公式是必不可少的。
事实证明,在迪拜没有负温度,+ 40预测中的加号没有任何意义。
不只是挑选和反映样式
如果我们在block元素上执行dir="auto"
,并且其内容原来是LTR,则文本将跳到容器的左侧,即使它位于RTL附近。 这可以通过显式设置text-align: right
。 您甚至可以将其应用于阿拉伯语版本的整个页面-该属性的值是继承的。
图标也不会自动镜像。 否则,方向性图标(例如画廊中的箭头)的方向可能会错误。 想象一下,这是唯一通过边界制作的箭头证明自己合理的情况!
一个简单的转换将有助于反映图标:
[dir="rtl"] .my-icon { transform: scaleX(-1); }
是的,如果图标包含字母或数字,将无济于事。 然后,您必须制作两个不同的图标并有条件地粘贴它们:

然而,事实证明并非所有接口元素都需要镜像。 例如,在我们的案例中,我们决定将复选框保留为正常状态:


我不知道如何从整个界面中选择此类元素。 只有说母语的人才能在这里提供帮助,他们会说出他所熟悉的和不熟悉的。
用户输入
即使我们完全控制了应用程序的所有数据,也可以由用户输入。 例如,文件名。 您甚至可以设计并以.png格式发布.js文件-Telegram中存在此类漏洞:
cool_picture * U + 202E * gnp.js→cool_picture sj.png
在这种情况下,值得从字符串中过滤掉不合适的utf字符。
翻转脚本
语法在RTL javascript中有所改变。 看起来像这样的循环:
for (let i = 0; i < arr.length; i++) {
现在您需要这样写:
for (++i; length.arr > i; let 0 = i) {
开个玩笑
您要做的就是避免代码中的“左”和“右”概念。 例如,我们在计算屏幕中心的坐标时遇到了问题-在所有卡悬挂在左侧之前,现在悬挂在右侧,但是应用程序代码对此一无所知。 所有计算和内联样式都必须考虑到基本重点。
快速机智
在某些情况下,很难在系统的某些组件中实现RTL支持。 然后,您需要尝试从外部使该组件适应RTL,但将LTR留在内部。
例如,我们有一个滑块。 它仅支持正序值。 它可以是线性和对数的(在开始时,值的密度小于在结束时的值)。 您需要在保持比例尺行为的同时进行反映。

您可以使用以下transform: scaleX(-1)
来翻转滑块transform: scaleX(-1)
。 然后,您必须用鼠标(单击和拖动)相对于滑块的中心反转工作。 不好的选择。
还有另一个选项-沿另一个方向旋转轴,仅更改滑块中值的传递和接收。 如果这是线性比例,则将传递值[N-1000,N-100,N-10]而不是值[10、100、1000]的集合,然后在处理程序中将其转换回去。 对于对数标度,我们传递[1/1000,1/100,1/10]而不是[10,100,1000]:
function flipSliderValues(values, scale, isRtl) { if (!isRtl) { return values; } if (scale === 'log') {
尽管他本人并不知道,这就是滑块开始支持RTL的方式。
故事书
与某些IE9上的排版相比,您无需运行单独的浏览器即可在RTL下验证排版。 您甚至可以在一个窗口中排版并同时查看LTR和RTL的布局。 为此,例如,您可以在Storybook中制作一个装饰器,该装饰器可一次渲染两个版本的故事:

屏幕快照显示没有隔离text-overflow: ellipsis
行为不符合我们的期望-最好立即修复。
在布局期间立即支持RTL比稍后对整个项目进行绝对测试要容易得多。
未解决的问题
理论知识无助于解决所有问题。 我举一个例子。
键入时在文本方向上出现的提示。 当我们谈论多语言输入时,我们无法预先知道显示该提示的方向:


您需要在设计阶段尝试避免此类问题,并有时放弃对LTR显而易见且不适用于RTL的解决方案。 在这种情况下,在浏览提示时,您可以替换整个文本(例如,Yandex或Google这样做)。
结论
RTL不只是“扭转一切”
有必要考虑到语言的特殊性,您不需要翻转某些东西,而需要改编其他东西。 在逻辑上的某个地方,绝对有必要放弃“右” /“左”。
不懂语言是很难做的
您会认为一切准备就绪,直到向真正的母语人士展示您的项目为止。 从一个不懂任何语言的人的角度发展。 毕竟,即使是对于您来说如此明显的单词,例如“ Twitter”,也可能需要翻译。 事实证明,标点符号在整个星球上并不相同。
最终配方
很难在一个列表中描述两篇文章中讨论的所有内容。 不会进行演练,但是主要要做的是:
- 确保找到以英语为母语的人,并尽早向他们展示原型;
- 将LTR和RTL的样式收集到不同的文件中。 为此,rtlcss和webpack-rtl-plugin是合适的;
- 为不需要上交的所有内容添加例外,并清楚反映未上交的内容;
- 使用
<bdi>
和dir="auto"
隔离所有任意内容; - 显式设置
text-align
到整个页面; - 意思是开始和结束时,请避免在js代码中
left
/ right
。
准备在最小的细节上花费最多的时间。
提前准备并不难
对于那些还不打算使站点适应RTL但想要吸管的人的一些提示:
- 请勿将
direction
属性用于其他目的; - 以防万一,仍然隔离所有任意内容(实际上,即使是在英文界面中,用户也可以用阿拉伯语写东西并破坏所有内容);
- 如果可能,请使用逻辑css属性 ;
- 至少出于好奇,不仅在不同的浏览器中,而且有时在RTL中检查布局。 使用故事书之类的工具更好地不受干扰地在RTL下控制布局;
- 不允许使用硬编码语言构造(例如,用逗号分隔的字符串连接),如果可能,请配置所有内容,包括标点符号。 这不仅对RTL有用,例如,在希腊语中,问号是“;”。
这些规则不应该麻烦。 但是,如果突然想要启动RTL版本,它会比它便宜得多。