Support RTL language direction

- Use bootstrap-rtl-ondemand css rules to modify bootstrap behavior.
- Add flip class on navbar.
- Switch offcanavas direction.
- Replace right/left icon depending.
- Flip icon image

issue7388
review74291002
This commit is contained in:
Cédric Krier
2018-08-30 16:09:18 +02:00
parent 0f2ecee3c4
commit 639f6e4aae
10 changed files with 110 additions and 23 deletions
+1
View File
@@ -1,3 +1,4 @@
* Support RTL language direction
* Apply factor on domain parser
* Use tab name in CSV Export/Import
* Add bus management
+2 -1
View File
@@ -23,7 +23,8 @@
"c3": "^0.6",
"papaparse": "^4.1",
"fullcalendar": "^3.0",
"mousetrap": "^1.6"
"mousetrap": "^1.6",
"bootstrap-rtl-ondemand": "^3.3.4-ondemand"
},
"devDependencies": {
"qunit": "^1.18"
+4 -3
View File
@@ -23,6 +23,7 @@ this repository contains the full copyright notices and license terms. -->
<script type="text/javascript" src="bower_components/mousetrap/mousetrap.min.js"></script>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="bower_components/bootstrap-rtl-ondemand/dist/css/bootstrap-rtl-ondemand.min.css">
<link rel="stylesheet" href="bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css">
<link rel="stylesheet" href="bower_components/c3/c3.min.css">
<link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.min.css">
@@ -53,9 +54,9 @@ this repository contains the full copyright notices and license terms. -->
</a>
</div>
<div class="collapse navbar-collapse" id="main_navbar">
<form class="navbar-form navbar-left" role="search" id="global-search">
<form class="navbar-form navbar-left flip" role="search" id="global-search">
</form>
<ul class="nav navbar-nav navbar-right">
<ul class="nav navbar-nav navbar-right flip">
<li id="user-preferences"></li>
<li id="user-logout"></li>
</ul>
@@ -67,7 +68,7 @@ this repository contains the full copyright notices and license terms. -->
<div id="main" class="container-fluid" style="padding: 0px;">
<div class="row row-offcanvas row-offcanvas-left active"
style="height: calc(100vh - 50px - 1px - 20px); margin: 0px;">
<div class="col-xs-6 col-sm-3 col-lg-2 sidebar-offcanvas" id="menu" role="navigation"
<div class="col-xs-12 col-sm-3 col-lg-2 sidebar-offcanvas" id="menu" role="navigation"
style="overflow-y: auto; height: 100%;"></div>
<div class="col-xs-12 col-md-9 col-lg-10 main-offcanvas" style="overflow-y: auto; height: 100%;">
<div id="tabs" role="tabpanel">
+1 -1
View File
@@ -30,7 +30,7 @@ body {
}
.sidebar-offcanvas {
position: absolute;
position: absolute !important;
top: 0;
}
+8
View File
@@ -280,6 +280,13 @@ var Sao = {};
Sao.i18n.BC47 = function(lang) {
return lang.replace('_', '-');
};
Sao.i18n.set_direction = function(direction) {
Sao.i18n.rtl = (direction === 'rtl');
jQuery('html').attr('dir', direction);
jQuery('.row-offcanvas')
.removeClass('row-offcanvas-left row-offcanvas-right')
.addClass(Sao.i18n.rtl ? 'row-offcanvas-right' : 'row-offcanvas-left');
};
Sao.i18n.locale = {};
Sao.get_preferences = function() {
@@ -311,6 +318,7 @@ var Sao = {};
}
prm.resolve(preferences);
});
Sao.i18n.set_direction(preferences.language_direction);
Sao.i18n.locale = preferences.locale;
return prm;
});
+49
View File
@@ -12,6 +12,10 @@
display: block;
margin-right: 15px;
}
[dir="rtl"] & > span {
margin-right: 0;
margin-left: 15px;
}
}
#menu, #tabs {
@@ -21,13 +25,22 @@
#menu {
border-right: 2px solid @table-border-color;
}
[dir="rtl"] #menu {
border-right: none;
border-left: 2px solid @table-border-color;
}
@media screen and (max-width: @screen-sm-max) {
#menu {
border-right: none;
border-left: none;
}
.active #menu {
border-right: 2px solid @table-border-color;
}
[dir="rtl"] .active #menu {
border-right: none;
border-left: 2px solid @table-border-color;
}
}
#user-preferences > a {
@@ -118,6 +131,9 @@ input[type='color'] {
img.icon {
height: unit(@line-height-base, em);
}
[dir="rtl"] img.icon {
transform: scaleX(-1);
}
.filter-box {
margin: 5px 5px;
@@ -149,9 +165,11 @@ img.icon {
&.char, &.text, &.many2one, &.one2one, &.date, &.time, &.selection,
&.reference, &.url, &.email, &.callto, &.sip {
text-align: left;
text-align: start;
}
&.timedelta, &.float, &.numeric, &.integer, &.biginteger {
text-align: right;
text-align: end;
}
}
& > tbody > tr > td {
@@ -164,9 +182,17 @@ img.icon {
& td.prefix {
padding-right: @table-cell-padding;
}
[dir="rtl"] & td.prefix {
padding-right: 0;
padding-left: @table-cell-padding;
}
& td.suffix {
padding-left: @table-cell-padding;
}
[dir="rtl"] & td.suffix {
padding-left: 0;
padding-right: @table-cell-padding;
}
& img.column-affix, a.column-affix > img {
width: unit(@line-height-base, em);
height: unit(@line-height-base, em);
@@ -210,6 +236,10 @@ img.icon {
margin-right: 5px;
width: 1em;
}
[dir="rtl"] & > img {
float: right;
margin-left: 5px;
}
}
& label {
font-weight: normal;
@@ -279,6 +309,9 @@ img.icon {
float: left;
margin: 5px;
}
[dir="rtl"] & .form-one2many-string, .form-many2many-string {
float: right;
}
& .form-one2many-toolbar, .form-many2many-toolbar {
display: inline-table;
float: right;
@@ -290,6 +323,9 @@ img.icon {
text-overflow: ellipsis;
}
}
[dir="rtl"] & .form-one2many-toolbar, .form-many2many-toolbar {
float: left;
}
}
& .form-one2many-content, .form-many2many-content {
& .treeview {
@@ -351,10 +387,12 @@ img.icon {
.editabletree-integer, .editabletree-float, .editabletree-timedelta {
& input {
text-align: right;
text-align: end;
}
}
.column-integer, .column-float, .column-timedelta {
text-align: right;
text-align: end;
}
.column-many2many, .column-one2many {
text-align: center;
@@ -371,6 +409,7 @@ input.column-boolean {
.dict-label {
text-align: right;
text-align: end;
}
.calendar {
@@ -385,6 +424,7 @@ input.column-boolean {
.window-form {
& .window-form-toolbar {
text-align: right;
text-align: end;
}
}
@@ -400,6 +440,7 @@ input.column-boolean {
@media screen and (max-width: @screen-xs-max) {
.dict-label {
text-align: left;
text-align: start;
}
}
@@ -426,6 +467,7 @@ input.column-boolean {
& > thead > tr,
& > tbody > tr > td {
text-align: left !important;
text-align: start !important;
/* Force height to empty content */
& > span:empty {
min-height: 20px;
@@ -466,11 +508,18 @@ input.column-boolean {
left: 6px;
padding-right: 10px;
text-align: left;
text-align: start;
overflow: hidden;
text-overflow: ellipsis;
/* Label */
content: attr(data-title);
}
[dir="rtl"] & > td:before {
padding-right: 0;
padding-left: 10px;
left: unset;
right: 6px;
}
& > td.required:before {
font-weight: bold;
}
+2 -2
View File
@@ -161,7 +161,7 @@
}).append(jQuery('<div/>', {
'class': 'container-fluid'
}).append(jQuery('<div/>', {
'class': 'dropdown navbar-header navbar-left'
'class': 'dropdown navbar-header navbar-left flip'
}).append(jQuery('<a/>', {
'href': "#",
'class': "navbar-brand dropdown-toggle",
@@ -185,7 +185,7 @@
}).append('&times;')).click(function() {
this.close();
}.bind(this)))).append(jQuery('<div/>', {
'class': 'btn-toolbar navbar-right',
'class': 'btn-toolbar navbar-right flip',
'role': 'toolbar'
})));
var wrapper = jQuery('<div/>', {
+22 -6
View File
@@ -20,18 +20,34 @@
if (this.attributes.mode == 'week') {
defaultview = this.get_week_view();
}
var header = {
left: 'today prev,next',
center: 'title',
right: 'month,' + this.get_week_view(),
};
if (Sao.i18n.rtl) {
var header_rtl = jQuery.extend({}, header);
header_rtl.left = header.right;
header_rtl.right = header.left;
header = header_rtl;
}
this.el.fullCalendar({
defaultView: defaultview,
header: {
left: 'prev,next',
center: 'title',
right: ' month,' + this.get_week_view()
},
header: header,
timeFormat: 'H:mm',
events: this.get_events.bind(this),
locale: lang,
buttonIcons: false,
isRTL: Sao.i18n.rtl,
themeSystem: 'bootstrap3',
bootstrapGlyphicons: {
'prev': 'chevron-' + (Sao.i18n.rtl? 'right' : 'left'),
'next': 'chevron-' + (Sao.i18n.rtl? 'left' : 'right'),
},
buttonTextOverride: {
'today': Sao.i18n.gettext("Today"),
'month': Sao.i18n.gettext("Month"),
'week': Sao.i18n.gettext("Week"),
},
eventRender: this.event_render.bind(this),
eventResize: this.event_resize.bind(this),
eventDrop: this.event_drop.bind(this),
+14 -6
View File
@@ -157,7 +157,6 @@ function eval_pyson(value){
attributes.states = model.fields[name].description.states;
}
if (attributes.string === undefined) {
// TODO RTL and translation
attributes.string = model.fields[name]
.description.string + ': ';
}
@@ -544,7 +543,13 @@ function eval_pyson(value){
if (attributes.xalign !== undefined) {
// TODO replace by start/end when supported
cell.css('text-align', attributes.xalign >= 0.5? 'right': 'left');
var align;
if (Sao.i18n.rtl) {
align = attributes.xalign >= 0.5? 'left': 'right';
} else {
align = attributes.xalign >= 0.5? 'right': 'left';
}
cell.css('text-align', align);
}
if (xexpand) {
cell.addClass('xexpand');
@@ -2008,13 +2013,13 @@ function eval_pyson(value){
add_buttons([
{
'icon': 'align-left',
'command': 'justifyLeft'
'command': Sao.i18n.rtl? 'justifyRight' : 'justifyLeft',
}, {
'icon': 'align-center',
'command': 'justifyCenter'
}, {
'icon': 'align-right',
'command': 'justifyRight'
'command': Sao.i18n.rtl? 'justifyLeft': 'justifyRight',
}, {
'icon': 'align-justify',
'command': 'justifyFull'
@@ -2087,7 +2092,11 @@ function eval_pyson(value){
}
// Some browsers set start as default align
if (el.attr('align') == 'start') {
el.attr('align', 'left');
if (Sao.i18n.rtl) {
el.attr('align', 'right');
} else {
el.attr('align', 'left');
}
}
});
},
@@ -4127,7 +4136,6 @@ function eval_pyson(value){
this.rows[key] = row = jQuery('<div/>', {
'class': 'row'
});
// TODO RTL
var text = key_schema.string + Sao.i18n.gettext(':');
var label = jQuery('<label/>', {
'text': text
+7 -4
View File
@@ -250,7 +250,6 @@
if (!attributes.sum) {
return;
}
// TODO RTL
var label = attributes.sum + Sao.i18n.gettext(': ');
var sum = jQuery('<label/>', {
'text': label,
@@ -875,7 +874,6 @@
for (var i = 0; i < this.tree.columns.length; i++) {
var column = this.tree.columns[i];
td = jQuery('<td/>', {
// TODO RTL
'data-title': column.attributes.string + Sao.i18n.gettext(': ')
}).append(jQuery('<span/>', { // For responsive min-height
'aria-hidden': true
@@ -898,10 +896,15 @@
td.append(table);
if ((i === 0) && this.children_field) {
this.expander = jQuery('<img/>', {
'tabindex': 0
'tabindex': 0,
'class': 'icon',
});
this.expander.html('&nbsp;');
this.expander.css('margin-left', (depth - 1) + 'em');
var margin = 'margin-left';
if (Sao.i18n.rtl) {
margin = 'margin-right';
}
this.expander.css(margin, (depth - 1) + 'em');
this.expander.on('click keypress',
Sao.common.click_press(this.toggle_row.bind(this)));
row.append(jQuery('<td/>', {