diff --git a/MANIFEST.in b/MANIFEST.in index 4563072..986c374 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include *.txt *.ini *.cfg *.rst include LICENSE VERSION -recursive-include daybed *.py *.rx *.feature +recursive-include daybed *.py *.mo *.rx *.feature global-exclude *.pyc diff --git a/Makefile b/Makefile index b032e61..de8e32f 100644 --- a/Makefile +++ b/Makefile @@ -41,3 +41,11 @@ tests-failfast: install-dev serve: install install-dev $(VENV)/bin/pserve conf/development.ini --reload + + +makemessages: install install-dev + $(VENV)/bin/pot-create -o daybed/locale/daybed.pot daybed/ + msgmerge --update daybed/locale/fr/LC_MESSAGES/daybed.po daybed/locale/daybed.pot + +compilemessages: + msgfmt daybed/locale/fr/LC_MESSAGES/daybed.po -o daybed/locale/fr/LC_MESSAGES/daybed.mo diff --git a/conf/development.ini b/conf/development.ini index 68de870..3f14a24 100644 --- a/conf/development.ini +++ b/conf/development.ini @@ -2,6 +2,7 @@ use = egg:daybed pyramid.includes = pyramid_debugtoolbar pyramid.debug_notfound = true +pyramid.default_locale_name = en # Backend configuration daybed.backend = daybed.backends.redis.RedisBackend diff --git a/conf/docker.ini b/conf/docker.ini index 1b9ee8d..8333b81 100644 --- a/conf/docker.ini +++ b/conf/docker.ini @@ -1,5 +1,6 @@ [app:main] use = egg:daybed +pyramid.default_locale_name = en # Backend configuration daybed.backend = ${BACKEND_ENGINE} diff --git a/daybed/__init__.py b/daybed/__init__.py index e2250b9..01150d2 100644 --- a/daybed/__init__.py +++ b/daybed/__init__.py @@ -4,12 +4,6 @@ import logging import pkg_resources - -#: Module version, as defined in PEP-0396. -__version__ = pkg_resources.get_distribution(__package__).version - -logger = logging.getLogger(__name__) - import six from cornice import Service from pyramid import httpexceptions @@ -17,10 +11,24 @@ from pyramid.events import NewRequest from pyramid.renderers import JSONP from pyramid.authentication import BasicAuthAuthenticationPolicy +from pyramid.i18n import TranslationStringFactory from pyramid_hawkauth import HawkAuthenticationPolicy from pyramid_multiauth import MultiAuthenticationPolicy +#: Module version, as defined in PEP-0396. +__version__ = pkg_resources.get_distribution(__package__).version + +# API main version +API_VERSION = 'v%s' % __version__.split('.')[0] + +# Common logger +logger = logging.getLogger(__name__) + +# Common TranslationString +TranslationString = TranslationStringFactory(__name__) + + from daybed.permissions import ( RootFactory, DaybedAuthorizationPolicy, get_credentials, check_credentials ) @@ -29,9 +37,6 @@ from daybed import indexer, events -API_VERSION = 'v%s' % __version__.split('.')[0] - - def settings_expandvars(settings): """Expands all environment variables in a settings dictionary. """ @@ -170,6 +175,24 @@ def attach_objects_to_request(event): config.add_subscriber(attach_objects_to_request, NewRequest) + # i18n + + def set_default_locale(event): + request = event.request + if not request.accept_language: + return + available = ('en', 'fr') + accepted = request.accept_language + default = request.registry.settings.get('pyramid.default_locale_name') + request._LOCALE_ = accepted.best_match(available, default) + request.tr = request.localizer.translate + + config.add_subscriber(set_default_locale, NewRequest) + config.add_translation_dirs( + 'daybed:locale', + 'colander:locale' + ) + # Plugins try: diff --git a/daybed/locale/daybed.pot b/daybed/locale/daybed.pot new file mode 100644 index 0000000..855941a --- /dev/null +++ b/daybed/locale/daybed.pot @@ -0,0 +1,108 @@ +# +# SOME DESCRIPTIVE TITLE +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2014. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2014-11-06 00:13+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: \n" + +#: ./daybed/views/hello.py:12 +msgid "hello" +msgstr "" + +#: ./daybed/schemas/geom.py:126 +msgid "A point" +msgstr "" + +#: ./daybed/schemas/geom.py:141 +msgid "A line made of points" +msgstr "" + +#: ./daybed/schemas/geom.py:163 +msgid "A polygon made of a closed line" +msgstr "" + +#: ./daybed/schemas/geom.py:217 +msgid "A GeoJSON geometry" +msgstr "" + +#: ./daybed/schemas/object.py:34 +msgid "An object" +msgstr "" + +#: ./daybed/schemas/json.py:36 +msgid "A JSON value" +msgstr "" + +#: ./daybed/schemas/list.py:21 +msgid "A list of objects" +msgstr "" + +#: ./daybed/schemas/relations.py:43 +msgid "A choice among records" +msgstr "" + +#: ./daybed/schemas/relations.py:63 +msgid "Some choices among records" +msgstr "" + +#: ./daybed/schemas/base.py:39 +msgid "An integer" +msgstr "" + +#: ./daybed/schemas/base.py:45 +msgid "A set of characters" +msgstr "" + +#: ./daybed/schemas/base.py:51 +msgid "A text" +msgstr "" + +#: ./daybed/schemas/base.py:71 +msgid "A decimal number" +msgstr "" + +#: ./daybed/schemas/base.py:77 +msgid "True or false" +msgstr "" + +#: ./daybed/schemas/base.py:83 +msgid "A choice among values" +msgstr "" + +#: ./daybed/schemas/base.py:101 +msgid "Some choices among values" +msgstr "" + +#: ./daybed/schemas/base.py:119 +msgid "A number with limits" +msgstr "" + +#: ./daybed/schemas/base.py:139 +msgid "A string matching a pattern" +msgstr "" + +#: ./daybed/schemas/base.py:157 +msgid "A valid email" +msgstr "" + +#: ./daybed/schemas/base.py:169 +msgid "A valid URL" +msgstr "" + +#: ./daybed/schemas/base.py:213 +msgid "A date (yyyy-mm-dd)" +msgstr "" + +#: ./daybed/schemas/base.py:224 +msgid "A date with time (yyyy-mm-ddTHH:MM)" +msgstr "" diff --git a/daybed/locale/fr/LC_MESSAGES/daybed.mo b/daybed/locale/fr/LC_MESSAGES/daybed.mo new file mode 100644 index 0000000..aeda0ec Binary files /dev/null and b/daybed/locale/fr/LC_MESSAGES/daybed.mo differ diff --git a/daybed/locale/fr/LC_MESSAGES/daybed.po b/daybed/locale/fr/LC_MESSAGES/daybed.po new file mode 100644 index 0000000..ff5ca4a --- /dev/null +++ b/daybed/locale/fr/LC_MESSAGES/daybed.po @@ -0,0 +1,108 @@ +# +# French translations for PACKAGE package +# This file is distributed under the same license as the PACKAGE package. +# Mathieu Leplatre , 2014. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2014-11-06 00:13+0100\n" +"PO-Revision-Date: 2014-11-06 00:13+0100\n" +"Last-Translator: Mathieu Leplatre \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: daybed/views/hello.py:12 +msgid "hello" +msgstr "bonjour" + +#: daybed/schemas/geom.py:126 +msgid "A point" +msgstr "Un point" + +#: daybed/schemas/geom.py:141 +msgid "A line made of points" +msgstr "Une ligne faite de points" + +#: daybed/schemas/geom.py:163 +msgid "A polygon made of a closed line" +msgstr "Un polygone fait d'une ligne fermée" + +#: daybed/schemas/geom.py:217 +msgid "A GeoJSON geometry" +msgstr "Une géométrie GeoJSON" + +#: daybed/schemas/object.py:34 +msgid "An object" +msgstr "Un objet" + +#: daybed/schemas/json.py:36 +msgid "A JSON value" +msgstr "Une valeur JSON" + +#: daybed/schemas/list.py:21 +msgid "A list of objects" +msgstr "Une liste d'objets" + +#: daybed/schemas/relations.py:43 +msgid "A choice among records" +msgstr "Un choix parmi des enregistrements" + +#: daybed/schemas/relations.py:63 +msgid "Some choices among records" +msgstr "Des choix parmi des enregistrements" + +#: daybed/schemas/base.py:39 +msgid "An integer" +msgstr "Un entier" + +#: daybed/schemas/base.py:45 +msgid "A set of characters" +msgstr "Une chaîne de caractères" + +#: daybed/schemas/base.py:51 +msgid "A text" +msgstr "Un texte" + +#: daybed/schemas/base.py:71 +msgid "A decimal number" +msgstr "Un nombre décimal" + +#: daybed/schemas/base.py:77 +msgid "True or false" +msgstr "Vrai ou Faux" + +#: daybed/schemas/base.py:83 +msgid "A choice among values" +msgstr "Un choix parmi des valeurs" + +#: daybed/schemas/base.py:101 +msgid "Some choices among values" +msgstr "Des choix parmi des valeurs" + +#: daybed/schemas/base.py:119 +msgid "A number with limits" +msgstr "Un nombre borné" + +#: daybed/schemas/base.py:139 +msgid "A string matching a pattern" +msgstr "Une chaîne respectant un masque" + +#: daybed/schemas/base.py:157 +msgid "A valid email" +msgstr "Un email valide" + +#: daybed/schemas/base.py:169 +msgid "A valid URL" +msgstr "Une URL valide" + +#: daybed/schemas/base.py:213 +msgid "A date (yyyy-mm-dd)" +msgstr "Une date (aaaa-mm-jj)" + +#: daybed/schemas/base.py:224 +msgid "A date with time (yyyy-mm-ddTHH:MM)" +msgstr "Une date avec heure (aaaa-mm-jjTHH:MM)" diff --git a/daybed/schemas/base.py b/daybed/schemas/base.py index 1a833c2..3319ab0 100644 --- a/daybed/schemas/base.py +++ b/daybed/schemas/base.py @@ -1,7 +1,7 @@ import re import datetime -from pyramid.i18n import TranslationString as _ +from daybed import TranslationString as _ from colander import ( deferred, SchemaNode, diff --git a/daybed/schemas/geom.py b/daybed/schemas/geom.py index 90baa20..d752f61 100644 --- a/daybed/schemas/geom.py +++ b/daybed/schemas/geom.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import json -from pyramid.i18n import TranslationString as _ +from daybed import TranslationString as _ import six from colander import ( SchemaNode, diff --git a/daybed/schemas/json.py b/daybed/schemas/json.py index 5f6514a..9c87d31 100644 --- a/daybed/schemas/json.py +++ b/daybed/schemas/json.py @@ -2,7 +2,7 @@ import re import json -from pyramid.i18n import TranslationString as _ +from daybed import TranslationString as _ import six from colander import Sequence, null, Invalid, List, Mapping diff --git a/daybed/schemas/list.py b/daybed/schemas/list.py index daaf26c..56664ca 100644 --- a/daybed/schemas/list.py +++ b/daybed/schemas/list.py @@ -1,4 +1,4 @@ -from pyramid.i18n import TranslationString as _ +from daybed import TranslationString as _ from colander import SchemaNode, Sequence, drop from . import TypeFieldNode diff --git a/daybed/schemas/object.py b/daybed/schemas/object.py index 5d83afe..080d475 100644 --- a/daybed/schemas/object.py +++ b/daybed/schemas/object.py @@ -1,6 +1,6 @@ import six -from pyramid.i18n import TranslationString as _ +from daybed import TranslationString as _ from colander import (Sequence, SchemaNode, Length, String, drop, Invalid) from daybed.backends.exceptions import ModelNotFound diff --git a/daybed/schemas/relations.py b/daybed/schemas/relations.py index addbbc7..ce5d3d2 100644 --- a/daybed/schemas/relations.py +++ b/daybed/schemas/relations.py @@ -1,5 +1,5 @@ import six -from pyramid.i18n import TranslationString as _ +from daybed import TranslationString as _ from colander import (String, SchemaNode, Invalid) from daybed.backends.exceptions import ModelNotFound, RecordNotFound diff --git a/daybed/schemas/validators.py b/daybed/schemas/validators.py index 6369097..e4d5444 100644 --- a/daybed/schemas/validators.py +++ b/daybed/schemas/validators.py @@ -151,7 +151,7 @@ def validate_against_schema(request, schema, data): except Invalid as e: # here we transform the errors we got from colander into cornice # errors - for field, error in e.asdict().items(): + for field, error in e.asdict(translate=request.tr).items(): request.errors.add('body', field, error) diff --git a/daybed/tests/test_views.py b/daybed/tests/test_views.py index 2fccb5d..8f6a264 100644 --- a/daybed/tests/test_views.py +++ b/daybed/tests/test_views.py @@ -73,6 +73,14 @@ def test_fields_are_listed(self): type="boolean", label="Gps")]) + def test_fields_hints_are_translated(self): + response = self.app.get('/fields', headers={ + 'Accept-Language': 'fr-FR' + }) + fields = response.json + textfield = [f for f in fields if f.get('name') == 'text'][0] + self.assertEqual(textfield['default_hint'], u'Un texte') + class HelloViewTest(BaseWebTest): @@ -82,6 +90,12 @@ def test_returns_info_about_url_and_version(self): self.assertEqual(response.json['url'], 'http://localhost') self.assertEqual(response.json['daybed'], 'hello') + def test_says_hello_in_french(self): + response = self.app.get('/', headers={ + 'Accept-Language': 'fr-FR' + }) + self.assertEqual(response.json['daybed'], u'bonjour') + def test_hello_uses_the_defined_http_scheme_if_defined(self): original_scheme = (self.app.app.registry.settings .get('daybed.http_scheme')) diff --git a/daybed/views/fields.py b/daybed/views/fields.py index 4ba36bd..2eb7b2d 100644 --- a/daybed/views/fields.py +++ b/daybed/views/fields.py @@ -16,7 +16,7 @@ def list_fields(request): # Iterate registered field types for name in registry.names: typefield = registry.type(name) - field = dict(name=name, default_hint=typefield.hint) + field = dict(name=name, default_hint=request.tr(typefield.hint)) # Describe field parameters using Colander children for parameter in registry.definition(name).children: if parameter.name not in common_params: diff --git a/daybed/views/hello.py b/daybed/views/hello.py index 7268490..23161c1 100644 --- a/daybed/views/hello.py +++ b/daybed/views/hello.py @@ -1,5 +1,6 @@ from cornice import Service from daybed import __version__ as VERSION +from daybed import TranslationString as _ hello = Service(name="hello", path='/', description="Welcome") @@ -8,6 +9,6 @@ @hello.get() def get_hello(request): """Return information regarding the current instance.""" - return dict(daybed='hello', + return dict(daybed=request.tr(_('hello')), version=VERSION, url=request.host_url) diff --git a/dev-requirements.txt b/dev-requirements.txt index 499ae34..daa26a1 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -8,3 +8,5 @@ Mock tox flake8 sphinx_rtd_theme +Babel +lingua diff --git a/setup.py b/setup.py index c744fca..9c963ef 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,8 @@ 'authorization_header_py26#egg=CouchDB-0.10.1dev', 'https://github.com/ametaireau/koremutake/tarball/' 'py3k#egg=koremutake-1.1.0', + 'https://github.com/Pylons/colander/tarball/' + 'b354a5b327f95f6ac24fd70d63b7c8d04590510b#egg=colander-1.1dev' ] ENTRY_POINTS = { 'paste.app_factory': [ diff --git a/tox.ini b/tox.ini index 5ca3a89..f9367e7 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ deps = webtest unittest2 mock + setuptools-git install_command = pip install --process-dependency-links --pre {opts} {packages} [testenv:py34]