[Zope3-checkins] SVN: Zope3/branches/philikon-messages-as-rocks/s
Implement TurningMessageIDsIntoRocks.
Philipp von Weitershausen
philikon at philikon.de
Sun Sep 19 04:33:49 EDT 2004
Log message for revision 27638:
Implement TurningMessageIDsIntoRocks.
Two things of that proposal were modified during the implementation:
- message ids are now simply called Messages (first, because of simplicity,
2nd because of differentiation to the old message ids)
- the % operator for filling the message id mapping was not realized
because it breaks the rule that messages behave like (unicode) strings
in all circumstances.
This is being checked into a branch because Jim and I couldn't figure out
what is wrong with the __reduce__ method implementation (it breaks with a bus
error). Once that is resolved we'll merge.
Changed:
U Zope3/branches/philikon-messages-as-rocks/setup.py
D Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt
_U Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/
U Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py
A Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c
A Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py
A Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/messages.txt
U Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py
-=-
Modified: Zope3/branches/philikon-messages-as-rocks/setup.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/setup.py 2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/setup.py 2004-09-19 08:33:49 UTC (rev 27638)
@@ -256,6 +256,9 @@
"src/zope/proxy/_zope_proxy_proxy.c",
]),
+ Extension("zope.i18nmessageid._zope_i18nmessageid_message",
+ ["src/zope/i18nmessageid/_zope_i18nmessageid_message.c"]),
+
]
# We're using the module docstring as the distutils descriptions.
Deleted: Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt 2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt 2004-09-19 08:33:49 UTC (rev 27638)
@@ -1,49 +0,0 @@
-=================
-Translatable Text
-=================
-
-Rationale
----------
-
-To translate any text, we must be able to discover the source
-domain of the text. A source domain is an identifier that identifies a
-project that produces program source strings. Source strings include
-literals in python programs, text in templates, and some text in XML
-data. The project implies a source language and an application
-context.
-
-We can think of a source domain as a collection of message IDs
-and associated translation strings.
-
-We often need to create strings that will be displayed by separate
-views. The view cannot translate the string without knowing its source
-domain. A string literal carries no domain information, so we use
-message IDs. Message IDs are strings which carry a translation source
-domain. These are created by a message ID factory. The message ID
-factory is created by calling zope.i18n.messageIDFactory with the
-source domain::
-
- from zope import i18n
- _ = i18n.MessageIDFactory("mydomain")
-
- class IContact(Interface):
- "Provides access to basic contact information."
-
- first = TextLine(title=_(u"First name"))
- last = TextLine(title=_(u"Last name"))
- email = TextLine(title=_(u"Electronic mail address"))
- address = Text(title=_(u"Postal address"))
- postal_code = TextLine(title=_(u"Postal code"),
- constraint=re.compile("\d{5,5}(-\d{4,4})?$").match)
-
- def name():
- """Gets the contact name.
-
- The contact name is the first and last name."""
-
-In this example, we create a message ID factory and assign it to
-_. By convention, we use _ as the name of our factory to be compatible
-with translatable string extraction tools such as xgettext. We then
-call _ with each string that needs to be translatable. The resulting
-message IDs can be used by a translation service.
-
Property changes on: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid
___________________________________________________________________
Name: svn:ignore
+ *.so
Modified: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py 2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py 2004-09-19 08:33:49 UTC (rev 27638)
@@ -11,5 +11,9 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"$Id$"
+"""I18n Messages
+
+$Id$
+"""
from messageid import MessageID, MessageIDFactory
+from message import Message, MessageFactory
Added: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c 2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c 2004-09-19 08:33:49 UTC (rev 27638)
@@ -0,0 +1,266 @@
+/*############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+############################################################################*/
+
+/* $Id$ */
+
+#include "Python.h"
+
+/* these macros make gc support easier; they are only available in
+ Python 2.4 and borrowed from there */
+
+#ifndef Py_CLEAR
+#define Py_CLEAR(op) \
+ do { \
+ if (op) { \
+ PyObject *tmp = (op); \
+ (op) = NULL; \
+ Py_DECREF(tmp); \
+ } \
+ } while (0)
+#endif
+
+#ifndef Py_VISIT
+#define Py_VISIT(op) \
+ do { \
+ if (op) { \
+ int vret = visit((op), arg); \
+ if (vret) \
+ return vret; \
+ } \
+ } while (0)
+#endif
+
+/* ----------------------------------------------------- */
+
+typedef struct {
+ PyUnicodeObject base;
+ PyObject *domain;
+ PyObject *default_;
+ PyObject *mapping;
+} Message;
+
+static PyTypeObject MessageType;
+
+static PyObject *
+Message_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"value", "domain", "default", "mapping", NULL};
+ PyObject *value, *domain=NULL, *default_=NULL, *mapping=NULL, *s;
+ Message *self;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist,
+ &value, &domain, &default_, &mapping))
+ return NULL;
+
+ args = Py_BuildValue("(O)", value);
+ if (args == NULL)
+ return NULL;
+
+ s = PyUnicode_Type.tp_new(type, args, NULL);
+ Py_DECREF(args);
+ if (s == NULL)
+ return NULL;
+
+ if (! PyObject_TypeCheck(s, &MessageType))
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "unicode.__new__ didn't return a Message");
+ Py_DECREF(s);
+ return NULL;
+ }
+
+ self = (Message*)s;
+
+ if (PyObject_TypeCheck(value, &MessageType))
+ {
+ self->domain = ((Message *)value)->domain;
+ self->default_ = ((Message *)value)->default_;
+ self->mapping = ((Message *)value)->mapping;
+ }
+ else
+ {
+ self->domain = self->default_ = self->mapping = NULL;
+ }
+
+ if (domain != NULL)
+ self->domain = domain;
+
+ if (default_ != NULL)
+ self->default_ = default_;
+
+ if (mapping != NULL)
+ self->mapping = mapping;
+
+ Py_XINCREF(self->mapping);
+ Py_XINCREF(self->default_);
+ Py_XINCREF(self->domain);
+
+ return (PyObject *)self;
+}
+
+/* Code to access structure members by accessing attributes */
+
+#include "structmember.h"
+
+static PyMemberDef Message_members[] = {
+ { "domain", T_OBJECT, offsetof(Message, domain), RO },
+ { "default", T_OBJECT, offsetof(Message, default_), RO },
+ { "mapping", T_OBJECT, offsetof(Message, mapping), RO },
+ {NULL} /* Sentinel */
+};
+
+static int
+Message_traverse(Message *self, visitproc visit, void *arg)
+{
+ Py_VISIT(self->domain);
+ Py_VISIT(self->default_);
+ Py_VISIT(self->mapping);
+ return 0;
+}
+
+static int
+Message_clear(Message *self)
+{
+ Py_CLEAR(self->domain);
+ Py_CLEAR(self->default_);
+ Py_CLEAR(self->mapping);
+ return 0;
+}
+
+static void
+Message_dealloc(Message *self)
+{
+ Message_clear(self);
+ self->base.ob_type->tp_free((PyObject*)self);
+}
+
+static PyObject *
+Message_reduce(Message *self)
+{
+ PyObject *value, *result;
+ value = PyObject_CallFunctionObjArgs((PyObject *)&PyUnicode_Type, self, NULL);
+ if (value == NULL)
+ return NULL;
+ result = Py_BuildValue("O(OOOO)", self->base.ob_type,
+ value,
+ self->domain || Py_None,
+ self->default_ || Py_None,
+ self->mapping || Py_None);
+ Py_DECREF(value);
+ return result;
+}
+
+static PyMethodDef Message_methods[] = {
+ {"__reduce__", (PyCFunction)Message_reduce, METH_NOARGS,
+ "Reduce messages to a serializable form."},
+ {NULL} /* Sentinel */
+};
+
+
+static char MessageType__doc__[] =
+"Message\n"
+"\n"
+"This is a string used as a message. It has a domain attribute that is\n"
+"its source domain, and a default attribute that is its default text to\n"
+"display when there is no translation. domain may be None meaning there is\n"
+"no translation domain. default may also be None, in which case the\n"
+"message id itself implicitly serves as the default text.\n";
+
+statichere PyTypeObject
+MessageType = {
+ PyObject_HEAD_INIT(NULL)
+ /* ob_size */ 0,
+ /* tp_name */ "zope.i18nmessageid.message."
+ "Message",
+ /* tp_basicsize */ sizeof(Message),
+ /* tp_itemsize */ 0,
+ /* tp_dealloc */ (destructor)&Message_dealloc,
+ /* tp_print */ (printfunc)0,
+ /* tp_getattr */ (getattrfunc)0,
+ /* tp_setattr */ (setattrfunc)0,
+ /* tp_compare */ (cmpfunc)0,
+ /* tp_repr */ (reprfunc)0,
+ /* tp_as_number */ 0,
+ /* tp_as_sequence */ 0,
+ /* tp_as_mapping */ 0,
+ /* tp_hash */ (hashfunc)0,
+ /* tp_call */ (ternaryfunc)0,
+ /* tp_str */ (reprfunc)0,
+ /* tp_getattro */ (getattrofunc)0,
+ /* tp_setattro */ (setattrofunc)0,
+ /* tp_as_buffer */ 0,
+ /* tp_flags */ Py_TPFLAGS_DEFAULT
+ | Py_TPFLAGS_BASETYPE
+ | Py_TPFLAGS_HAVE_GC,
+ /* tp_doc */ MessageType__doc__,
+ /* tp_traverse */ (traverseproc)Message_traverse,
+ /* tp_clear */ (inquiry)Message_clear,
+ /* tp_richcompare */ (richcmpfunc)0,
+ /* tp_weaklistoffset */ (long)0,
+ /* tp_iter */ (getiterfunc)0,
+ /* tp_iternext */ (iternextfunc)0,
+ /* tp_methods */ Message_methods,
+ /* tp_members */ Message_members,
+ /* tp_getset */ 0,
+ /* tp_base */ 0,
+ /* tp_dict */ 0, /* internal use */
+ /* tp_descr_get */ (descrgetfunc)0,
+ /* tp_descr_set */ (descrsetfunc)0,
+ /* tp_dictoffset */ 0,
+ /* tp_init */ (initproc)0,
+ /* tp_alloc */ (allocfunc)0,
+ /* tp_new */ (newfunc)Message_new,
+ /* tp_free */ 0, /* Low-level free-mem routine */
+ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */
+};
+
+/* End of code for Message objects */
+/* -------------------------------------------------------- */
+
+
+/* List of methods defined in the module */
+
+static struct PyMethodDef _zope_i18nmessageid_message_methods[] = {
+ {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
+};
+
+
+static char _zope_i18nmessageid_message_module_documentation[] =
+"I18n Messages"
+;
+
+#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+PyMODINIT_FUNC
+init_zope_i18nmessageid_message(void)
+{
+ PyObject *m;
+ /* Initialize types: */
+ MessageType.tp_base = &PyUnicode_Type;
+ if (PyType_Ready(&MessageType) < 0)
+ return;
+
+ /* Create the module and add the functions */
+ m = Py_InitModule3("_zope_i18nmessageid_message",
+ _zope_i18nmessageid_message_methods,
+ _zope_i18nmessageid_message_module_documentation);
+
+ if (m == NULL)
+ return;
+
+ /* Add types: */
+ if (PyModule_AddObject(m, "Message", (PyObject *)&MessageType) < 0)
+ return;
+}
Property changes on: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c
___________________________________________________________________
Name: svn:keywords
+ Id
Added: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py 2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py 2004-09-19 08:33:49 UTC (rev 27638)
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""I18n Messages
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from _zope_i18nmessageid_message import Message
+
+class MessageFactory(object):
+ """Factory for creating i18n messages."""
+
+ def __init__(self, domain):
+ self._domain = domain
+
+ def __call__(self, ustr, default=None, mapping=None):
+ return Message(ustr, self._domain, default, mapping)
Property changes on: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py
___________________________________________________________________
Name: svn:keywords
+ Id
Copied: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/messages.txt (from rev 27631, Zope3/trunk/src/zope/i18n/message_id.txt)
===================================================================
--- Zope3/trunk/src/zope/i18n/message_id.txt 2004-09-18 05:23:27 UTC (rev 27631)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/messages.txt 2004-09-19 08:33:49 UTC (rev 27638)
@@ -0,0 +1,113 @@
+=============
+I18n Messages
+=============
+
+Rationale
+---------
+
+To translate any text, we must be able to discover the source domain
+of the text. A source domain is an identifier that identifies a
+project that produces program source strings. Source strings occur as
+literals in python programs, text in templates, and some text in XML
+data. The project implies a source language and an application
+context.
+
+We can think of a source domain as a collection of messages and
+associated translation strings.
+
+We often need to create unicode strings that will be displayed by
+separate views. The view cannot translate the string without knowing
+its source domain. A string or unicode literal carries no domain
+information, therefore we use messages. Messages are unicode strings
+which carry a translation source domain and possibly a default
+translation. They are created by a message factory. The message
+factory is created by calling ``MessageFactory`` with the source
+domain.
+
+
+Example
+-------
+
+In this example, we create a message factory and assign it to _. By
+convention, we use _ as the name of our factory to be compatible with
+translatable string extraction tools such as xgettext. We then call _
+with a string that needs to be translatable:
+
+ >>> from zope.i18nmessageid import MessageFactory, Message
+ >>> _ = MessageFactory("futurama")
+ >>> robot = _(u"robot-message", u"${name} is a robot.")
+
+Messages at first seem like they are unicode strings:
+
+ >>> robot
+ u'robot-message'
+ >>> isinstance(robot, unicode)
+ True
+
+The additional domain, default and mapping information is available
+through attributes:
+
+ >>> robot.default
+ u'${name} is a robot.'
+ >>> robot.mapping
+ >>>
+
+The messags's attributes are considered part of the immutable message
+object. They cannot be changed once the message id is created:
+
+ >>> robot.domain = "planetexpress"
+ Traceback (most recent call last):
+ ...
+ TypeError: readonly attribute
+
+ >>> robot.default = u"${name} is not a robot."
+ Traceback (most recent call last):
+ ...
+ TypeError: readonly attribute
+
+ >>> robot.mapping = {u'name': u'Bender'}
+ Traceback (most recent call last):
+ ...
+ TypeError: readonly attribute
+
+If you need to change their information, you'll have to make a new
+message id object:
+
+ >>> new_robot = Message(robot, mapping={u'name': u'Bender'})
+ >>> new_robot
+ u'robot-message'
+ >>> new_robot.domain
+ 'futurama'
+ >>> new_robot.default
+ u'${name} is a robot.'
+ >>> new_robot.mapping
+ {u'name': u'Bender'}
+
+Last but not least, messages are reduceable for pickling:
+
+ >>> callable, args = new_robot.__reduce__()
+ >>> args
+ (u'robot-message', 'futurama', u'${name} is a robot', {u'name': u'Bender'})
+
+
+Message IDs and backward compatability
+--------------------------------------
+
+The change to immutability is not a simple refactoring that can be
+coped with backward compatible APIs--it is a change in semantics.
+Because immutability is one of those "you either have it or you don't"
+things (like pregnancy or death), we will not be able to support both
+in one implementation.
+
+The proposed solution for backward compatability is to support both
+implementations in parallel, deprecating the mutable one. A separate
+factory, 'I18nMessageFactory', will instanciate immutable message ids,
+while the deprecated oldone will continue to work like before.
+
+The roadmap to immutable-only message ids is proposed as follows:
+
+ X3.1: Immutable message ids are introduced. Mutable message ids are
+ deprecated. Security declarations for mutable message ids are
+ provided to make the stripping of security proxies unnecessary.
+
+ X3.2: Mutable message ids are removed.
Modified: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py 2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py 2004-09-19 08:33:49 UTC (rev 27638)
@@ -16,10 +16,13 @@
$Id$
"""
import unittest
-from zope.testing.doctestunit import DocTestSuite
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
def test_suite():
- return DocTestSuite('zope.i18nmessageid.messageid')
+ return unittest.TestSuite((
+ DocTestSuite('zope.i18nmessageid.messageid'),
+ DocFileSuite('messages.txt', package='zope.i18nmessageid'),
+ ))
if __name__ == '__main__':
unittest.main(defaultTest="test_suite")
More information about the Zope3-Checkins
mailing list