Since you changed the question to one of forethought rather than afterthought.
Not quite so logical
You state, "If you add dir='rtl'
or dir='ltr'
attribute to <body>
element logically you expect that it do the magic." The magic apparently being to convert, "all right
with left
and all left
with right
in rules like text-direction
and margin
."
However, I do not agree that in all cases what you desire is necessarily a logical outcome. There are instances of text-direction
and margin
use that are not necessarily related to the text-direction
of the main site. For margin
, this seems self evident, as many times margin
may be used for some form of positioning of elements wholly unrelated to the text. That text-direction
would not automatically flip may be less obvious, but still valid. Assume one has an English (LTR) site with a block quote of Arabic (RTL) in it. Now one converts the main language to Hebrew (RTL) but still has the quote in Arabic--that should not automatically flip to LTR as it would be incorrect.
Things like floated elements, absolutely positioned elements, etc. (which use right
and left
values) may or may not be positioned as they are because of the text-direction
.
So basically, it boils down to the fact that as one designs a site that is intended to be multilingual in a way that will flip text-direction
, one must think through at each stage what the element should be doing based on a LTR or RTL configuration.
What this means is, in my opinion, CSS does not have a weakness. Any weakness is in design implementation.
Pure Human Forethought in Design
So the good method would be to pick your standard direction (say LTR) and have that be your "base" plan with straight CSS.
Then for elements you want to flip because of a change to RTL, you code additional CSS to account for that by either applying a class to the <body>
to target with or using an attribute selector like body[dir=rtl]
. Then you think through each element you make whether it should be affected by that change, and if it is you add css (with the additional directional selector adding specificity to override):
.someClass {
color: red;
margin: 0 10px 0 20px;
float: right;
}
body[dir=rtl] .someClass {
margin: 0 20px 0 10px;
/* kept float as right */
}
.someOtherClass {
border: 1px 10px 1px 1px;
margin: 0 30px 0 50px;
float: left;
}
body[dir=rtl] .someOtherClass {
border: 1px 1px 1px 10px; /* changed border for text */
margin: 0 50px 0 30px;
float: right;
}
This is where a preprocessor like LESS or SASS (SCSS) (Dave mentioned LESS in his answer) could prove helpful (see my update below), but that is still a solution that requires forethought.
If you don't want code riddled with excess CSS
You could have a separate style sheet for the RTL css that is loaded at the time the site is determined to be that particular direction. The possible disadvantage of this is that it separates out the "switching" code from the original code so maintenance could be more of a challenge (good comment documentation about elements that are affected by RTL conversion in the main code could compensate for this).
(UPDATE) Using LESS to Help?
Here is a thought on making the process more concise through a preprocessor like LESS. This example is using functionality from LESS 1.4 (currently in beta).
Idea 1
- Advantage: keeps all values changed to one selector change.
- Disadvantage: more work to code values in arguments.
Build a mixin to apply desired selector to
.rtl(...) {
//getting the number of arguments
//(weeding out nested commas in parenthesis to do it)
//they are expected to be grouped in pairs of TWO,
//as (property, value, property, value, etc.)
@mainArgs: @arguments;
@numArgs: unit(`"@{mainArgs}".replace(/\([^)]*\)/g,"").split(',').length`);
//keep everything in one selector
body[dir=rtl] & {
//start the loop at 1
.rtlPropLoop(@numArgs);
}
//loop to change all properties
.rtlPropLoop(@total; @index: 1; @prop: extract(@mainArgs, @index); @value: extract(@mainArgs, (@index + 1))) when (@index =< @total) {
//need to define all properties that could be switched
//I've done just four here
.setProp(ML) { //margin left
margin-left: @value;
}
.setProp(MR) { //margin right
margin-right: @value;
}
.setProp(FL) { //float
float: @value;
}
.setProp(TD) { //text direction
text-direction: @value;
}
//... define more possible values to switch
//call setProp
.setProp(@prop);
//continue loop
.rtlPropLoop(@total, (@index + 2));
}
//end loop
.rtlPropLoop(@total, @index) when (@index > @total) {}
}
Then use it as needed in other selectors
.test {
margin: 0 20px 0 10px;
float: right;
.rtl(ML, 20px, MR, 10px, FL, left);
}
.test2 a span {
float: left;
text-direction: rtl;
.rtl(TD, ltr, FL, right);
}
Producing this finished code
.test {
margin: 0 20px 0 10px;
float: right;
}
body[dir=rtl] .test {
margin-left: 20px;
margin-right: 10px;
float: left;
}
.test2 a span {
float: left;
text-direction: rtl;
}
body[dir=rtl] .test2 a span {
text-direction: ltr;
float: right;
}
Idea 2
- Advantage: allows you to use syntax similar to what you desire; could be easily modified to work with LESS 1.3.3.
- Disadvantage: produces excess output css if multiple values are changed in one selector.
Build some helper mixins with syntax like what you expect in CSS
//define global variable for opposite direction
@oppDir: rtl;
//generic helper mixins used inside other helpers
//to auto flip right/left values
.flipSides(left) {
@newSide: right;
}
.flipSides(right) {
@newSide: left;
}
//specific property helper mixins
.padding-near(@top, @right, @bottom, @left) {
padding: @top @right @bottom @left;
body[dir=@{oppDir}] & {
padding: @top @left @bottom @right;
}
}
.margin-near(@top, @right, @bottom, @left) {
margin: @top @right @bottom @left;
body[dir=@{oppDir}] & {
margin: @top @left @bottom @right;
}
}
.float-near(@side) {
float: @side;
.flipSides(@side);
body[dir=@{oppDir}] & {
float: @newSide;
}
}
Use them to define both near and far for each direction at once
.test1 {
.padding-near(10px, 30px, 2px, 40px);
.margin-near(0, 10px, 0, 20px);
.float-near(right);
}
.test2 {
.float-near(left);
}
Producing this finished code (note repetition of .test1
for opposite direction)
.test1 {
padding: 10px 30px 2px 40px;
margin: 0 10px 0 20px;
float: right;
}
body[dir=rtl] .test1 {
padding: 10px 40px 2px 30px;
}
body[dir=rtl] .test1 {
margin: 0 20px 0 10px;
}
body[dir=rtl] .test1 {
float: left;
}
.test2 {
float: left;
}
body[dir=rtl] .test2 {
float: right;
}