diff --git a/buildout.cfg b/buildout.cfg
index 3362bd6..895e5db 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -17,8 +17,11 @@ eggs =
Sphinx
unittest2
nose
+ Babel
+ lingua
scripts=
nosetests=tests
+interpreter = python
[test]
recipe = zc.recipe.egg
diff --git a/pyramid_formalchemy/__init__.py b/pyramid_formalchemy/__init__.py
index dfb1eb6..d73af11 100644
--- a/pyramid_formalchemy/__init__.py
+++ b/pyramid_formalchemy/__init__.py
@@ -4,12 +4,15 @@ from pyramid_formalchemy.resources import Models
def includeme(config):
"""include formalchemy's zcml"""
+ config.add_translation_dirs('formalchemy:i18n_resources/', 'pyramid_formalchemy:locale/')
config.add_static_view('fa_admin', 'pyramid_formalchemy:static')
config.add_directive('formalchemy_admin', 'pyramid_formalchemy.formalchemy_admin')
config.add_directive('formalchemy_model', 'pyramid_formalchemy.formalchemy_model')
config.add_directive('formalchemy_model_view', 'pyramid_formalchemy.formalchemy_model_view')
config.registry.pyramid_formalchemy_views = {}
+ config.add_route('set_language', '/set_language', 'pyramid_formalchemy.views.set_language')
+
def formalchemy_model_view(config, route_name,
model=None,
name='',
diff --git a/pyramid_formalchemy/actions.py b/pyramid_formalchemy/actions.py
index 2f96fc8..010b8b9 100644
--- a/pyramid_formalchemy/actions.py
+++ b/pyramid_formalchemy/actions.py
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
from chameleon.zpt.template import PageTemplate
from pyramid.util import DottedNameResolver
+from pyramid_formalchemy.i18n import TranslationString
+from pyramid_formalchemy.i18n import get_localizer
+from pyramid_formalchemy.i18n import _
import functools
def action(name=None):
@@ -29,16 +32,16 @@ class Action(object):
>>> request = Request.blank('/')
>>> class MyAction(Action):
- ... body = u''
+ ... body = u'${content}'
- >>> action = MyAction('myaction', content=repr('Click here'),
+ >>> action = MyAction('myaction', content=_('Click here'),
... attrs={'href': repr('#'), 'onclick': repr('$.click()')})
>>> action.render(request)
u'Click here'
"""
- def __init__(self, id, action='', content="", attrs=None, **rcontext):
+ def __init__(self, id, content="", alt="", attrs=None, **rcontext):
self.id = id
self.attrs = attrs or {}
self.rcontext = rcontext
@@ -46,8 +49,9 @@ class Action(object):
self.attrs['id'] = repr(id)
self.update()
attributes = u';'.join([u'%s %s' % v for v in self.attrs.items()])
- rcontext.update(attrs=self.attrs, attributes=attributes, id=id, content=content, action=action)
+ rcontext.update(attrs=self.attrs, attributes=attributes, id=id)
body = self.body % self.rcontext
+ rcontext.update(content=content, alt=alt)
self.template = PageTemplate(body)
def update(self):
@@ -56,6 +60,17 @@ class Action(object):
def render(self, request):
rcontext = {'action': self, 'request': request}
rcontext.update(self.rcontext)
+ localizer = get_localizer(request)
+ mapping = getattr(request, 'action_mapping', {})
+ if not mapping:
+ for k in ('model_name', 'model_id'):
+ mapping[k] = getattr(request, k, '')
+ request.action_mapping = mapping
+ for k in ('content', 'alt'):
+ v = rcontext[k]
+ if isinstance(v, TranslationString):
+ v = TranslationString(v, domain=v.domain, mapping=request.action_mapping)
+ rcontext[k] = localizer.translate(v)
return self.template.render(**rcontext)
def __repr__(self):
@@ -67,14 +82,30 @@ class Link(Action):
>>> from webob import Request
>>> request = Request.blank('/')
- >>> action = Link('myaction', content='label',
+ >>> action = Link('myaction',
... attrs={'href': 'request.application_url'},
- ... label='Click here')
+ ... content=_('Click here'))
>>> action.render(request)
u'Click here'
"""
- body = u''
+ body = u'${content}'
+
+class ListItem(Action):
+ """
+ An action rendered as a link::
+
+ >>> from webob import Request
+ >>> request = Request.blank('/')
+ >>> action = ListItem('myaction',
+ ... attrs={'href': 'request.application_url'},
+ ... content=_('Click here'))
+ >>> action.render(request)
+ u'
Click here'
+
+ """
+ body = u'${content}'
+
class Input(Action):
"""An action rendered as an input::
@@ -82,7 +113,7 @@ class Input(Action):
>>> from webob import Request
>>> request = Request.blank('/')
>>> action = Input('myaction',
- ... value=repr('Click here'))
+ ... value=_('Click here'))
>>> action.render(request)
u''
@@ -95,21 +126,21 @@ class Input(Action):
if 'type' not in self.attrs:
self.attrs['type'] = repr('submit')
-class UILink(Action):
+class UIButton(Action):
"""An action rendered as an jquery.ui aware link::
>>> from webob import Request
>>> request = Request.blank('/')
- >>> action = UILink('myaction', icon='ui-icon-trash',
- ... label="string:Click here")
+ >>> action = UIButton('myaction', icon='ui-icon-trash',
+ ... content=_("Click here"))
>>> print action.render(request)
- >>> action = UILink('myaction', icon='ui-icon-trash',
- ... label="'Click here'", attrs={'onclick':'$(#link).click();'})
+ >>> action = UIButton('myaction', icon='ui-icon-trash',
+ ... content=_("Click here"), attrs={'onclick':'$(#link).click();'})
>>> print action.render(request)
'''
def update(self):
if 'state' not in self.rcontext:
@@ -135,17 +166,18 @@ class UILink(Action):
class Actions(list):
"""
>>> actions = Actions('pyramid_formalchemy.actions.delete',
- ... Link('link1', content=repr('A link'), attrs={'href':'request.application_url'}))
+ ... Link('link1', content=_('A link'), attrs={'href':'request.application_url'}))
>>> actions
- [, ]
+ [, ]
>>> from webob import Request
>>> request = Request.blank('/')
- >>> print actions.render(request) #doctest: +ELLIPSIS
+ >>> print actions.render(request) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
A link
+
+ A link
"""
@@ -154,44 +186,70 @@ class Actions(list):
list.__init__(self, [res.maybe_resolve(a) for a in args])
def render(self, request, **kwargs):
- return u''.join([a.render(request, **kwargs) for a in self])
+ return u'\n'.join([a.render(request, **kwargs) for a in self])
+class Languages(Actions):
+ """
+ >>> langs = Languages('fr', 'en')
+ >>> langs
+ [, ]
+ >>> from webob import Request
+ >>> request = Request.blank('/')
+ >>> request.route_url = lambda name, _query: 'http://localhost/set_language?_LOCALE_=%(_LOCALE_)s' % _query
+ >>> print langs.render(request) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+ fr
+ en
+
+ """
-new = UILink(
+ def __init__(self, *args, **kwargs):
+ list.__init__(self)
+ klass=kwargs.get('class_', ListItem)
+ for l in args:
+ self.append(Link(id=l, content=_(l), attrs=dict(href="request.route_url('set_language', _query={'_LOCALE_': '%s'})" % l)))
+
+new = UIButton(
id='new',
- label='string:New ${request.model_name}',
+ content=_('New ${model_name}'),
icon='ui-icon-circle-plus',
attrs=dict(href="request.fa_url(request.model_name, 'new')"),
)
-save = UILink(
+save = UIButton(
id='save',
- label='string:Save',
+ content=_('Save'),
icon='ui-icon-check',
attrs=dict(onclick="jQuery(this).parents('form').submit();"),
)
-save_and_add_another = UILink(
+save_and_add_another = UIButton(
id='save_and_add_another',
- label='string:Save and add another',
+ content=_('Save and add another'),
icon='ui-icon-check',
attrs=dict(onclick=("var f = jQuery(this).parents('form');"
"jQuery('#next', f).val(window.location.href);"
"f.submit();")),
)
-edit = UILink(
+edit = UIButton(
id='edit',
- label='string:Edit',
+ content=_('Edit'),
icon='ui-icon-check',
attrs=dict(href="request.fa_url(request.model_name, request.model_id, 'edit')"),
)
-delete = UILink(
+back = UIButton(
+ id='back',
+ content=_('Back'),
+ icon='ui-icon-circle-arrow-w',
+ attrs=dict(href="request.fa_url(request.model_name)"),
+ )
+
+delete = UIButton(
id='delete',
views='edit',
- label='string:Delete',
+ content=_('Delete'),
state='ui-state-error',
icon='ui-icon-trash',
attrs=dict(onclick=("string:var f = jQuery(this).parents('form');"
@@ -199,10 +257,10 @@ delete = UILink(
"f.submit();")),
)
-cancel = UILink(
+cancel = UIButton(
id='cancel',
views='edit',
- label='string:Cancel',
+ content=_('Cancel'),
icon='ui-icon-circle-arrow-w',
attrs=dict(href="request.fa_url(request.model_name)"),
)
@@ -210,6 +268,6 @@ cancel = UILink(
defaults_actions = dict(
listing_buttons=Actions(new),
new_buttons=Actions(save, save_and_add_another, cancel),
- show_buttons=Actions(edit, cancel),
+ show_buttons=Actions(edit, back),
edit_buttons=Actions(save, delete, cancel),
)
diff --git a/pyramid_formalchemy/i18n.py b/pyramid_formalchemy/i18n.py
new file mode 100644
index 0000000..0a45e6a
--- /dev/null
+++ b/pyramid_formalchemy/i18n.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+from pyramid.i18n import TranslationStringFactory
+from pyramid.i18n import TranslationString
+from pyramid.i18n import get_localizer
+
+_ = TranslationStringFactory('pyramid_formalchemy')
+
diff --git a/pyramid_formalchemy/locale/fr/LC_MESSAGES/pyramid_formalchemy.mo b/pyramid_formalchemy/locale/fr/LC_MESSAGES/pyramid_formalchemy.mo
new file mode 100644
index 0000000..e30251f
Binary files /dev/null and b/pyramid_formalchemy/locale/fr/LC_MESSAGES/pyramid_formalchemy.mo differ
diff --git a/pyramid_formalchemy/locale/fr/LC_MESSAGES/pyramid_formalchemy.po b/pyramid_formalchemy/locale/fr/LC_MESSAGES/pyramid_formalchemy.po
new file mode 100644
index 0000000..987aa6d
--- /dev/null
+++ b/pyramid_formalchemy/locale/fr/LC_MESSAGES/pyramid_formalchemy.po
@@ -0,0 +1,56 @@
+# French translations for pyramid_formalchemy.
+# Copyright (C) 2011 ORGANIZATION
+# This file is distributed under the same license as the pyramid_formalchemy
+# project.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pyramid_formalchemy 0.3\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2011-06-18 12:02+0200\n"
+"PO-Revision-Date: 2011-06-18 13:18+0200\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: fr \n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.5\n"
+
+#: pyramid_formalchemy/actions.py:190
+msgid "New ${model_name}"
+msgstr "Nouveau ${model_name}"
+
+#: pyramid_formalchemy/actions.py:198
+msgid "Save"
+msgstr "Sauver"
+
+#: pyramid_formalchemy/actions.py:205
+msgid "Save and add another"
+msgstr "Sauver et ajouter un autre"
+
+#: pyramid_formalchemy/actions.py:214
+msgid "Edit"
+msgstr "Editer"
+
+#: pyramid_formalchemy/actions.py:221
+msgid "Back"
+msgstr "Retour"
+
+#: pyramid_formalchemy/actions.py:229
+msgid "Delete"
+msgstr "Supprimer"
+
+#: pyramid_formalchemy/actions.py:240
+msgid "Cancel"
+msgstr "Annuler"
+
+#: pyramid_formalchemy/views.py:245
+msgid "edit"
+msgstr "Editer"
+
+#: pyramid_formalchemy/views.py:252
+msgid "delete"
+msgstr "Supprimer"
+
diff --git a/pyramid_formalchemy/locale/pyramid_formalchemy.pot b/pyramid_formalchemy/locale/pyramid_formalchemy.pot
new file mode 100644
index 0000000..1278b5c
--- /dev/null
+++ b/pyramid_formalchemy/locale/pyramid_formalchemy.pot
@@ -0,0 +1,56 @@
+# Translations template for pyramid_formalchemy.
+# Copyright (C) 2011 ORGANIZATION
+# This file is distributed under the same license as the pyramid_formalchemy
+# project.
+# FIRST AUTHOR , 2011.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: pyramid_formalchemy 0.3\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2011-06-18 13:16+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.5\n"
+
+#: pyramid_formalchemy/actions.py:190
+msgid "New ${model_name}"
+msgstr ""
+
+#: pyramid_formalchemy/actions.py:198
+msgid "Save"
+msgstr ""
+
+#: pyramid_formalchemy/actions.py:205
+msgid "Save and add another"
+msgstr ""
+
+#: pyramid_formalchemy/actions.py:214
+msgid "Edit"
+msgstr ""
+
+#: pyramid_formalchemy/actions.py:221
+msgid "Back"
+msgstr ""
+
+#: pyramid_formalchemy/actions.py:229
+msgid "Delete"
+msgstr ""
+
+#: pyramid_formalchemy/actions.py:240
+msgid "Cancel"
+msgstr ""
+
+#: pyramid_formalchemy/views.py:245
+msgid "edit"
+msgstr ""
+
+#: pyramid_formalchemy/views.py:252
+msgid "delete"
+msgstr ""
+
diff --git a/pyramid_formalchemy/resources.py b/pyramid_formalchemy/resources.py
index a82a8ce..203b9ac 100644
--- a/pyramid_formalchemy/resources.py
+++ b/pyramid_formalchemy/resources.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from pyramid.exceptions import NotFound
+from pyramid_formalchemy import actions
from sqlalchemy import exceptions as sqlalchemy_exceptions
import logging
@@ -27,6 +28,12 @@ class Base(object):
if self.__model_class__:
request.model_class = self.__model_class__
request.model_name = self.__model_class__.__name__
+ langs = request.registry.settings.get('available_languages', '')
+ if langs:
+ if isinstance(langs, basestring):
+ langs = langs.split()
+ request.language_actions = actions.Languages(*langs)
+
def get_model(self):
request = self.request
diff --git a/pyramid_formalchemy/templates/admin/new.pt b/pyramid_formalchemy/templates/admin/new.pt
index 9d21e99..6f1c30e 100644
--- a/pyramid_formalchemy/templates/admin/new.pt
+++ b/pyramid_formalchemy/templates/admin/new.pt
@@ -12,7 +12,7 @@
diff --git a/pyramid_formalchemy/views.py b/pyramid_formalchemy/views.py
index 48e9e0f..fa09c89 100644
--- a/pyramid_formalchemy/views.py
+++ b/pyramid_formalchemy/views.py
@@ -38,6 +38,11 @@ class Session(object):
def commit(self):
"""commit transaction"""
+def set_language(request):
+ resp = exc.HTTPFound(location=request.referer or request.application_url)
+ resp.set_cookie('_LOCALE_', request.GET.get('_LOCALE_', 'en'))
+ return resp
+
class ModelView(object):
"""A RESTful view bound to a model"""
@@ -132,7 +137,7 @@ class ModelView(object):
main = get_renderer('pyramid_formalchemy:templates/admin/master.pt').implementation(),
model_name=request.model_name,
breadcrumb=self.breadcrumb(**kwargs),
- F_=get_translator().gettext)
+ F_=get_translator())
return kwargs
def render_grid(self, **kwargs):
diff --git a/setup.py b/setup.py
index 8e0c31f..aca65cf 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
-requires = ['pyramid>=1.0', 'WebError', 'FormAlchemy>=1.3.8']
+requires = ['pyramid>=1.0', 'WebError', 'FormAlchemy>=1.3.8', 'Babel']
setup(name='pyramid_formalchemy',
version='0.3',
@@ -23,6 +23,10 @@ setup(name='pyramid_formalchemy',
url='http://docs.formalchemy.org/pyramid_formalchemy/',
keywords='web pyramid pylons formalchemy',
packages=find_packages(exclude=['pyramidapp']),
+ message_extractors = { 'pyramid_formalchemy': [
+ ('*.py', 'lingua_python', None ),
+ ('templates/**.pt', 'lingua_xml', None ),
+ ]},
include_package_data=True,
zip_safe=False,
install_requires=requires,