[gen] Added support for right-to-left (RTL) languages.
This commit is contained in:
parent
b680a5ddcb
commit
1b375d387c
|
@ -110,7 +110,7 @@ class ToolMixin(BaseMixin):
|
|||
if not cfg.languageSelector: return
|
||||
if len(cfg.languages) < 2: return
|
||||
page = self.REQUEST.get('ACTUAL_URL').split('/')[-1]
|
||||
return page not in ('edit', 'query', 'search')
|
||||
return page not in ('edit', 'query', 'search', 'do')
|
||||
|
||||
def getLanguages(self):
|
||||
'''Returns the supported languages. First one is the default.'''
|
||||
|
@ -128,11 +128,21 @@ class ToolMixin(BaseMixin):
|
|||
rq.RESPONSE.setCookie('_ZopeLg', rq['language'], path='/')
|
||||
return self.goto(rq['HTTP_REFERER'])
|
||||
|
||||
def flipLanguageDirection(self, align, dir):
|
||||
'''According to language direction p_dir ('ltr' or 'rtl'), this method
|
||||
turns p_align from 'left' to 'right' (or the inverse) when
|
||||
required.'''
|
||||
if dir == 'ltr': return align
|
||||
if align == 'left': return 'right'
|
||||
if align == 'right': return 'left'
|
||||
return align
|
||||
|
||||
def getGlobalCssJs(self):
|
||||
'''Returns the list of CSS and JS files to include in the main template.
|
||||
The method ensures that appy.css and appy.js come first.'''
|
||||
names = self.getPhysicalRoot().ui.objectIds('File')
|
||||
names.remove('appy.js'); names.insert(0, 'appy.js')
|
||||
names.remove('appyrtl.css'); names.insert(0, 'appyrtl.css')
|
||||
names.remove('appy.css'); names.insert(0, 'appy.css')
|
||||
return names
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from appy.gen.utils import *
|
|||
from appy.gen.layout import Table, defaultPageLayouts
|
||||
from appy.gen.descriptors import WorkflowDescriptor, ClassDescriptor
|
||||
from appy.shared.utils import sequenceTypes
|
||||
from appy.shared.data import rtlLanguages
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class BaseMixin:
|
||||
|
@ -1404,6 +1405,11 @@ class BaseMixin:
|
|||
if '-' in res: res = res[:res.find('-')]
|
||||
return res
|
||||
|
||||
def getLanguageDirection(self, lang):
|
||||
'''Determines if p_lang is a LTR or RTL language.'''
|
||||
if lang in rtlLanguages: return 'rtl'
|
||||
return 'ltr'
|
||||
|
||||
def formatText(self, text, format='html'):
|
||||
'''Produces a representation of p_text into the desired p_format, which
|
||||
is "html" by default.'''
|
||||
|
|
|
@ -15,9 +15,13 @@
|
|||
app tool/getApp;
|
||||
appUrl app/absolute_url;
|
||||
template python: contextObj.getPageTemplate(app.ui, page);
|
||||
lang tool/getUserLanguage;
|
||||
dir python: tool.getLanguageDirection(lang);
|
||||
dleft python: (dir == 'ltr') and 'left' or 'right';
|
||||
dright python: (dir == 'ltr') and 'right' or 'left';
|
||||
x python: resp.setHeader('Content-Type','text/html;;charset=utf-8');
|
||||
x python: resp.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
||||
x python: resp.setHeader('Content-Language', req.get('language', 'en'));
|
||||
x python: resp.setHeader('Content-Language', lang);
|
||||
x python: resp.setHeader('CacheControl', 'no-cache')">
|
||||
<tal:comment replace="nothing">Keys "Expires" and "CacheControl" are used for preventing IE to cache
|
||||
this page. Indeed, this page is retrieved through an asynchronous XMLHttpRequest by the browser, and
|
||||
|
|
|
@ -92,6 +92,7 @@ img { border: 0; vertical-align: middle}
|
|||
.grid th { font-style: italic; font-weight: normal;
|
||||
border-bottom: 2px solid grey; padding: 2px 2px;}
|
||||
.grid td { padding-right: 5px; }
|
||||
.cellGap { padding-right: 0.4em; }
|
||||
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important; }
|
||||
.noStyle td { border:0 !important; padding:0 !important; margin:0 !important; }
|
||||
.translationLabel { background-color: #EAEAEA; border-bottom: 1px dashed grey;
|
||||
|
|
7
gen/ui/appyrtl.css
Normal file
7
gen/ui/appyrtl.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
ul { margin: 0 0.6em 0.2em 0 }
|
||||
ul li { background-image: url("ui/lirtl.gif");
|
||||
background-position: right center;
|
||||
padding-right: 15px; padding-left: 0px}
|
||||
.portlet { border-right: none; border-left: 1px solid #5F7983 }
|
||||
.lang { margin-right: 0px; margin-left: 6px; }
|
||||
.cellGap { padding-left: 0.4em; }
|
BIN
gen/ui/bannerrtl.jpg
Normal file
BIN
gen/ui/bannerrtl.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
|
@ -1,4 +1,4 @@
|
|||
<table metal:define-macro="footer"
|
||||
cellpadding="0" cellspacing="0" width="100%" class="footer">
|
||||
<tr><td align="right">Made with <a href="http://appyframework.org" target="_blank">Appy</a></td></tr>
|
||||
<tr><td tal:attributes="align dright">Made with <a href="http://appyframework.org" target="_blank">Appy</a></td></tr>
|
||||
</table>
|
||||
|
|
BIN
gen/ui/lirtl.gif
Normal file
BIN
gen/ui/lirtl.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 B |
|
@ -1,4 +1,4 @@
|
|||
<div metal:define-macro="appyNavigate" tal:condition="python: totalNumber > batchSize" align="right">
|
||||
<div metal:define-macro="appyNavigate" tal:condition="python: totalNumber > batchSize" tal:attributes="align dright">
|
||||
<tal:comment replace="nothing">
|
||||
Buttons for navigating among a list of elements (next, back, first, last, etc).
|
||||
</tal:comment>
|
||||
|
|
|
@ -72,10 +72,10 @@
|
|||
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
|
||||
<table width="100%" class="history">
|
||||
<tr>
|
||||
<th align="left">Action</th>
|
||||
<th align="left">By</th>
|
||||
<th align="left">Date</th>
|
||||
<th align="left">Comment</th>
|
||||
<th tal:attributes="align dleft">Action</th>
|
||||
<th tal:attributes="align dleft">By</th>
|
||||
<th tal:attributes="align dleft">Date</th>
|
||||
<th tal:attributes="align dleft">Comment</th>
|
||||
</tr>
|
||||
<tal:event repeat="event objs">
|
||||
<tr tal:define="odd repeat/event/odd;
|
||||
|
@ -98,8 +98,8 @@
|
|||
Display the previous values of the fields whose value were modified in this change.</tal:comment>
|
||||
<table class="appyChanges" width="100%">
|
||||
<tr>
|
||||
<th align="left" width="30%" tal:content="python: _('modified_field')"></th>
|
||||
<th align="left" width="70%" tal:content="python: _('previous_value')"></th>
|
||||
<th tal:attributes="align dleft" width="30%" tal:content="python: _('modified_field')"></th>
|
||||
<th tal:attributes="align dleft" width="70%" tal:content="python: _('previous_value')"></th>
|
||||
</tr>
|
||||
<tr tal:repeat="change event/changes/items" valign="top">
|
||||
<tal:change define="appyType python:contextObj.getAppyType(change[0], asDict=True);">
|
||||
|
@ -157,7 +157,7 @@
|
|||
<tal:comment replace="nothing">Input field for storing comment</tal:comment>
|
||||
<textarea id="comment" name="comment" cols="30" rows="3" style="display:none"></textarea>
|
||||
<tal:comment replace="nothing">Buttons for triggering transitions</tal:comment>
|
||||
<td align="right" tal:repeat="transition transitions">
|
||||
<td tal:attributes="align dright" tal:repeat="transition transitions">
|
||||
<tal:comment replace="nothing">Real button</tal:comment>
|
||||
<input type="button" tal:condition="transition/may_trigger"
|
||||
tal:attributes="value transition/title;
|
||||
|
@ -198,8 +198,9 @@
|
|||
<tal:comment replace="nothing">Creator and last modification date</tal:comment>
|
||||
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
|
||||
<tal:accessHistory condition="hasHistory">
|
||||
<img align="left" style="cursor:pointer" onClick="toggleCookie('appyHistory')"
|
||||
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');"
|
||||
<img style="cursor:pointer" onClick="toggleCookie('appyHistory')"
|
||||
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');
|
||||
align dleft"
|
||||
id="appyHistory_img"/>
|
||||
<span>History</span> ||
|
||||
</tal:accessHistory>
|
||||
|
@ -233,7 +234,7 @@
|
|||
<table width="100%">
|
||||
<tr>
|
||||
<td><metal:states use-macro="here/ui/page/macros/states"/></td>
|
||||
<td align="right"><metal:states use-macro="here/ui/page/macros/transitions"/></td>
|
||||
<td tal:attributes="align dright"><metal:states use-macro="here/ui/page/macros/transitions"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
|
|
@ -62,10 +62,11 @@
|
|||
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
|
||||
<tal:comment replace="nothing">Group name</tal:comment>
|
||||
<div class="portletGroup">
|
||||
<img align="left" style="cursor:pointer; margin-right: 3px"
|
||||
<img style="cursor:pointer; margin-right: 3px"
|
||||
tal:attributes="id python: '%s_img' % group['labelId'];
|
||||
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
|
||||
onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>
|
||||
onClick python:'toggleCookie(\'%s\')' % group['labelId'];
|
||||
align dleft"/>
|
||||
<span tal:replace="group/label"/>
|
||||
</div>
|
||||
<tal:comment replace="nothing">Group searches</tal:comment>
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
<tal:result condition="objs">
|
||||
|
||||
<tal:comment replace="nothing">Display here POD templates if required.</tal:comment>
|
||||
<table align="right"
|
||||
tal:define="widgets python: tool.getResultPodFields(className);
|
||||
<table tal:define="widgets python: tool.getResultPodFields(className);
|
||||
layoutType python:'view'"
|
||||
tal:condition="python: objs and widgets">
|
||||
tal:condition="python: objs and widgets"
|
||||
tal:attributes="align dright">
|
||||
<tr>
|
||||
<td tal:define="contextObj python: objs[0]" tal:repeat="widget widgets">
|
||||
<metal:pod use-macro="context/ui/widgets/show/macros/field"/>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<span class="discreet" tal:content="descr"></span><br/>
|
||||
</td>
|
||||
</tal:descr>
|
||||
<td align="right" width="25%">
|
||||
<td tal:attributes="align dright" width="25%">
|
||||
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
|
||||
</td>
|
||||
|
@ -118,7 +118,7 @@
|
|||
tal:content="python: _(obj.portal_type)"></td>
|
||||
|
||||
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
||||
<td align="right">
|
||||
<td tal:attributes="align dright">
|
||||
<table class="noStyle" tal:condition="obj/mayAct">
|
||||
<tr>
|
||||
<tal:comment replace="nothing">Edit the element</tal:comment>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</table>
|
||||
|
||||
<tal:comment replace="nothing">Submit button</tal:comment>
|
||||
<p align="right"><br/>
|
||||
<p tal:attributes="align dright"><br/>
|
||||
<input type="submit" tal:attributes="value python: _('search_button')"/>
|
||||
</p>
|
||||
</form>
|
||||
|
|
|
@ -9,14 +9,19 @@
|
|||
_ python: tool.translate;
|
||||
req python: request;
|
||||
resp req/RESPONSE;
|
||||
lang tool/getUserLanguage;
|
||||
dir python: tool.getLanguageDirection(lang);
|
||||
dleft python: (dir == 'ltr') and 'left' or 'right';
|
||||
dright python: (dir == 'ltr') and 'right' or 'left';
|
||||
x python: resp.setHeader('Content-type', 'text/html;;charset=UTF-8');
|
||||
x python: resp.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:00 GMT+2');
|
||||
x python: resp.setHeader('Content-Language', req.get('language', 'en'))">
|
||||
x python: resp.setHeader('Content-Language', lang)"
|
||||
tal:attributes="dir python: tool.getLanguageDirection(lang)">
|
||||
|
||||
<head>
|
||||
<title tal:content="tool/getAppName"></title>
|
||||
<tal:link repeat="name tool/getGlobalCssJs">
|
||||
<link tal:condition="python: name.endswith('.css')"
|
||||
<link tal:condition="python: name.endswith('.css') and not ((dir == 'ltr') and (name == 'appyrtl.css'))"
|
||||
rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/ui/$name"/>
|
||||
<script tal:condition="python: name.endswith('.js')"
|
||||
type="text/javascript" tal:attributes="src string:$appUrl/ui/$name"></script>
|
||||
|
@ -27,16 +32,16 @@
|
|||
<table class="main" align="center" cellpadding="0">
|
||||
<tal:comment replace="nothing">Top banner</tal:comment>
|
||||
<tr class="top" metal:define-slot="top">
|
||||
<td tal:attributes="style python: 'background-image: url(%s/ui/banner.jpg)' % appUrl">
|
||||
<td tal:define="bannerName python: (dir == 'ltr') and 'banner' or 'bannerrtl'"
|
||||
tal:attributes="style python: 'background-image: url(%s/ui/%s.jpg)' % (appUrl, bannerName)">
|
||||
<table width="100%">
|
||||
<tr valign="top">
|
||||
<tr valign="top" tal:define="pages tool/getMainPages">
|
||||
<tal:comment replace="nothing">Links to main pages</tal:comment>
|
||||
<td>
|
||||
<a tal:repeat="page tool/getMainPages" class="pageLink"
|
||||
<td tal:condition="pages"><a tal:repeat="page pages" class="pageLink"
|
||||
tal:content="page/title" tal:attributes="href page/absolute_url"></a>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
|
||||
<td align="right" tal:condition="tool/showLanguageSelector">
|
||||
<td tal:condition="tool/showLanguageSelector" tal:attributes="align dright">
|
||||
<tal:lgs define="languages tool/getLanguages;
|
||||
defaultLanguage python: languages[0];
|
||||
asLinks python: len(languages) <= 8">
|
||||
|
@ -51,7 +56,7 @@
|
|||
</tr>
|
||||
</table>
|
||||
<select tal:condition="not: asLinks"
|
||||
tal:attributes="onchange string:window.location='${context/absolute_url}/switchLanguage?set_language=' + this.options[this.selectedIndex].value">
|
||||
tal:attributes="onchange string:window.location='$appUrl/config/changeLanguage?language=' + this.options[this.selectedIndex].value">
|
||||
<option tal:repeat="lang languages"
|
||||
tal:content="python: tool.getLanguageName(lang)"
|
||||
tal:attributes="selected python:defaultLanguage == lang; value lang">
|
||||
|
@ -79,7 +84,7 @@
|
|||
<p id="appyConfirmText"></p>
|
||||
<input type="hidden" name="actionType"/>
|
||||
<input type="hidden" name="action"/>
|
||||
<div id="commentArea" align="left"><br/>
|
||||
<div id="commentArea" tal:attributes="align dleft"><br/>
|
||||
<span tal:content="python: _('workflow_comment')" class="discreet"></span>
|
||||
<textarea name="comment" cols="30" rows="3"></textarea>
|
||||
<br/>
|
||||
|
@ -143,7 +148,7 @@
|
|||
<img tal:attributes="src string: $appUrl/ui/logout.gif"/>
|
||||
</a>
|
||||
</td>
|
||||
<td align="right" class="userStripText" tal:define="userInfo tool/getUserLine">
|
||||
<td class="userStripText" tal:define="userInfo tool/getUserLine" tal:attributes="align dright">
|
||||
<span tal:content="python: userInfo[0]"></span>
|
||||
<a tal:condition="python: userInfo[1]"
|
||||
tal:attributes="href python: userInfo[1]">
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
</tal:show>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Icon for removing the row</tal:comment>
|
||||
<td align="right" tal:condition="python: layoutType=='edit'">
|
||||
<td tal:condition="python: layoutType=='edit'"
|
||||
tal:attributes="align dright">
|
||||
<img style="cursor:pointer"
|
||||
tal:attributes="src string:$appUrl/ui/delete.png;
|
||||
title python: 'Delete';
|
||||
|
|
|
@ -208,7 +208,7 @@
|
|||
</tal:other>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Actions</tal:comment>
|
||||
<td align="right">
|
||||
<td tal:attributes="align dright">
|
||||
<tal:show condition="obj/mayAct">
|
||||
<metal:showObjectActions use-macro="app/ui/widgets/ref/macros/objectActions" />
|
||||
</tal:show>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<table tal:attributes="cellpadding layout/cellpadding;
|
||||
cellspacing layout/cellspacing;
|
||||
width layout/width;
|
||||
align layout/align;
|
||||
align python: tool.flipLanguageDirection(layout['align'], dir);
|
||||
class python: tagCss and ('%s %s' % (tagCss, layoutCss)).strip() or layoutCss;
|
||||
style layout/style;
|
||||
id tagId;
|
||||
|
@ -30,14 +30,16 @@
|
|||
<tal:comment replace="nothing">The table header row</tal:comment>
|
||||
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
|
||||
<th tal:repeat="cell layout/headerRow/cells"
|
||||
tal:attributes="align cell/align; width cell/width;">
|
||||
tal:attributes="align python: tool.flipLanguageDirection(cell['align'], dir);
|
||||
width cell/width;">
|
||||
</th>
|
||||
</tr>
|
||||
<tal:comment replace="nothing">The table content</tal:comment>
|
||||
<tr tal:repeat="row layout/rows" tal:attributes="valign row/valign">
|
||||
<td tal:repeat="cell row/cells"
|
||||
tal:attributes="align cell/align; colspan cell/colspan;
|
||||
style python: test(repeat['cell'].end, '', 'padding-right: 0.4em')"><tal:content repeat="elem cell/content"><tal:field condition="python: elem == '?'"><metal:call use-macro="python: getattr(contextMacro, widget['type'].lower()).macros[layoutType]"/></tal:field><tal:other condition="python: elem != '?'"><metal:call use-macro="python: getattr(contextMacro, elem[0]).macros[elem[1]]"/></tal:other><img tal:condition="not: repeat/elem/end" tal:attributes="src string: $appUrl/ui/space.gif"/></tal:content>
|
||||
tal:attributes="align python: tool.flipLanguageDirection(cell['align'], dir);
|
||||
colspan cell/colspan;
|
||||
class python: test(repeat['cell'].end, '', 'cellGap')"><tal:content repeat="elem cell/content"><tal:field condition="python: elem == '?'"><metal:call use-macro="python: getattr(contextMacro, widget['type'].lower()).macros[layoutType]"/></tal:field><tal:other condition="python: elem != '?'"><metal:call use-macro="python: getattr(contextMacro, elem[0]).macros[elem[1]]"/></tal:other><img tal:condition="not: repeat/elem/end" tal:attributes="src string: $appUrl/ui/space.gif"/></tal:content>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -153,7 +155,7 @@
|
|||
<table metal:define-macro="groupContent"
|
||||
tal:define="cellgap widget/cellgap"
|
||||
tal:attributes="width widget/wide;
|
||||
align widget/align;
|
||||
align python: tool.flipLanguageDirection(widget['align'], dir);
|
||||
class groupCss;
|
||||
cellspacing widget/cellspacing;
|
||||
cellpadding widget/cellpadding;
|
||||
|
@ -161,7 +163,8 @@
|
|||
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
|
||||
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
|
||||
<td tal:attributes="colspan python: len(widget['columnsWidths']);
|
||||
class widget/style" align="left">
|
||||
class widget/style;
|
||||
align dleft">
|
||||
<span tal:replace="structure python: contextObj.translate(widget['labelId'])"/>
|
||||
<tal:help condition="widget/hasHelp">
|
||||
<metal:call use-macro="app/ui/widgets/show/macros/help"/>
|
||||
|
@ -176,7 +179,7 @@
|
|||
<tr> <tal:comment replace="nothing">The column headers</tal:comment>
|
||||
<th tal:repeat="colNb python:range(len(widget['columnsWidths']))"
|
||||
tal:attributes="width python:widget['columnsWidths'][colNb];
|
||||
align python:widget['columnsAligns'][colNb]"
|
||||
align python: tool.flipLanguageDirection(widget['columnsAligns'][colNb], dir)"
|
||||
tal:content="structure python: widget['hasHeaders'] and contextObj.translate('%s_col%d' % (widget['labelId'], colNb+1)) or ''">
|
||||
</th>
|
||||
</tr>
|
||||
|
|
|
@ -160,6 +160,9 @@ nativeNames = {
|
|||
'zh' : '中文',
|
||||
'zu' : 'isiZulu'
|
||||
}
|
||||
# List of languages having direction right-to-left (RTL) -----------------------
|
||||
rtlLanguages = ('ar', 'he', 'fa')
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Languages:
|
||||
'''This class gives access to the language codes and names as standardized
|
||||
|
|
Loading…
Reference in a new issue