Commit a3e908ae authored by Nathan Yergler's avatar Nathan Yergler

Copying i18nmessage to new project.

parent a95f1bb7
zope.testing
zope.deprecation
<extension _zope_i18nmessageid_message>
source _zope_i18nmessageid_message.c
</extension>
##############################################################################
#
# 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$
"""
##############################################################################
# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
#
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid.messageid import MessageID, MessageIDFactory
zope.deprecation.__show__.on()
zope.deprecation.deprecated('MessageID',
'Mutable i18n messages ("message ids") have been '
'deprecated in favour of immutable ones and will '
'be removed in Zope 3.3. Please use '
'zope.i18nmessageid.Message instead.')
zope.deprecation.deprecated('MessageIDFactory',
'Mutable i18n messages ("message ids") have been '
'deprecated in favour of immutable ones and will '
'be removed in Zope 3.3. Please use '
'use zope.i18nmessageid.MessageFactory instead.')
#
##############################################################################
from zope.i18nmessageid.message import Message, MessageFactory
/*############################################################################
#
# 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 ? self->domain : Py_None,
self->default_ ? self->default_ : Py_None,
self->mapping ? 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;
}
##############################################################################
#
# 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"
class Message(unicode):
"""Message (Python implementation)
This is a string used as a message. It has a domain attribute that is
its source domain, and a default attribute that is its default text to
display when there is no translation. domain may be None meaning there is
no translation domain. default may also be None, in which case the
message id itself implicitly serves as the default text.
These are the doc tests from message.txt. Note that we have to create the
message manually since MessageFactory would return the C implementation.
>>> from zope.i18nmessageid.message import pyMessage as Message
>>> robot = Message(u"robot-message", 'futurama', u"${name} is a robot.")
>>> robot
u'robot-message'
>>> isinstance(robot, unicode)
True
>>> robot.default
u'${name} is a robot.'
>>> robot.mapping
Only the python implementation has a _readonly attribute
>>> robot._readonly
True
>>> 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
>>> 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'}
>>> callable, args = new_robot.__reduce__()
>>> callable is Message
True
>>> args
(u'robot-message', 'futurama', u'${name} is a robot.', {u'name': u'Bender'})
>>> fembot = Message(u'fembot')
>>> callable, args = fembot.__reduce__()
>>> callable is Message
True
>>> args
(u'fembot', None, None, None)
Change classes for pickle tests
>>> import zope.i18nmessageid.message
>>> oldMessage = zope.i18nmessageid.message.Message
>>> zope.i18nmessageid.message.Message = Message
At first check if pickling and unpicklung from pyMessage to pyMessage works
>>> from pickle import dumps, loads
>>> pystate = dumps(new_robot)
>>> pickle_bot = loads(pystate)
>>> pickle_bot, pickle_bot.domain, pickle_bot.default, pickle_bot.mapping
(u'robot-message', 'futurama', u'${name} is a robot.', {u'name': u'Bender'})
>>> pickle_bot._readonly
True
>>> from zope.i18nmessageid.message import pyMessage
>>> pickle_bot.__reduce__()[0] is pyMessage
True
>>> del pickle_bot
At second check if cMessage is able to load the state of a pyMessage
>>> from _zope_i18nmessageid_message import Message
>>> zope.i18nmessageid.message.Message = Message
>>> c_bot = loads(pystate)
>>> c_bot, c_bot.domain, c_bot.default, c_bot.mapping
(u'robot-message', 'futurama', u'${name} is a robot.', {u'name': u'Bender'})
>>> c_bot._readonly
Traceback (most recent call last):
AttributeError: 'zope.i18nmessageid.message.Message' object has no attribute '_readonly'
>>> from _zope_i18nmessageid_message import Message as cMessage
>>> c_bot.__reduce__()[0] is cMessage
True
At last check if pyMessage can load a state of cMessage
>>> cstate = dumps(c_bot)
>>> del c_bot
>>> from zope.i18nmessageid.message import pyMessage as Message
>>> zope.i18nmessageid.message.Message = Message
>>> py_bot = loads(cstate)
>>> py_bot, py_bot.domain, py_bot.default, py_bot.mapping
(u'robot-message', 'futurama', u'${name} is a robot.', {u'name': u'Bender'})
>>> py_bot._readonly
True
>>> py_bot.__reduce__()[0] is pyMessage
True
Both pickle states should be equal
>>> pystate == cstate
True
Finally restore classes for other unit tests
>>> zope.i18nmessageid.message.Message = oldMessage
"""
__slots__ = ('domain', 'default', 'mapping', '_readonly')
def __new__(cls, ustr, domain=None, default=None, mapping=None):
self = unicode.__new__(cls, ustr)
if isinstance(ustr, self.__class__):
domain = ustr.domain and ustr.domain[:] or domain
default = ustr.default and ustr.default[:] or default
mapping = ustr.mapping and ustr.mapping.copy() or mapping
ustr = unicode(ustr)
self.domain = domain
if default is None:
# MessageID does: self.default = ustr
self.default = default
else:
self.default = unicode(default)
self.mapping = mapping
self._readonly = True
return self
def __setattr__(self, key, value):
"""Message is immutable
It cannot be changed once the message id is created.
"""
if getattr(self, '_readonly', False):
raise TypeError('readonly attribute')
else:
return unicode.__setattr__(self, key, value)
def __reduce__(self):
return self.__class__, self.__getstate__()
def __getstate__(self):
return unicode(self), self.domain, self.default, self.mapping
# save a copy for the unit tests
pyMessage = Message
try:
from _zope_i18nmessageid_message import Message
except ImportError:
pass
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)
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Message IDs.
$Id$
"""
import zope.deprecation
zope.deprecation.deprecated('MessageID',
'Mutable i18n messages ("message ids") have been '
'deprecated in favour of immutable ones and will '
'be removed in Zope 3.3. Please use '
'zope.i18nmessageid.Message instead.')
zope.deprecation.deprecated('MessageIDFactory',
'Mutable i18n messages ("message ids") have been '
'deprecated in favour of immutable ones and will '
'be removed in Zope 3.3. Please use '
'use zope.i18nmessageid.MessageFactory instead.')
class MessageID(unicode):
"""Message ID.
This is a string used as a message ID. It has a domain attribute that is
its source domain, and a default attribute that is its default text to
display when there is no translation. domain may be None meaning there is
no translation domain. default may also be None, in which case the
message id itself implicitly serves as the default text.
MessageID objects also have a mapping attribute which must be set after
construction of the object. This is used when translating and
substituting variables.
To instanciate MessageIDs, it is recommended to use MessageIDFactory:
>>> fact = MessageIDFactory('test')
Now we can use the factory to make MessageIDs. Note that MessageID
is a subclass of unicode:
>>> id = fact(u'this is a test')
>>> isinstance(id, MessageID)
True
>>> isinstance(id, unicode)
True
Additional parameters, such as the i18n domain and the default
text are available through attributes:
>>> id.domain
'test'
>>> id.default
u'this is a test'
You can also reset the default text:
>>> id.default = u'blah'
>>> id.default
u'blah'
It is quite common to pass an abstract identifier as message id
and then a default text:
>>> id = fact(u'test-id', 'default test')
>>> id
u'test-id'
>>> id.default
u'default test'
>>> id.domain
'test'
"""
__slots__ = ('domain', 'default', 'mapping')
def __new__(cls, ustr, domain=None, default=None):
self = unicode.__new__(cls, ustr)
self.domain = domain
if default is None:
self.default = ustr
else:
self.default = unicode(default)
self.mapping = {}
return self
def __getstate__(self):
return unicode(self), self.domain, self.default, self.mapping
def __setstate__(self, (ustr, domain, default, mapping)):
super(MessageID, self).__init__(ustr)
self.domain = domain
if default is None:
self.default = ustr
else:
self.default = default
self.mapping = mapping
class MessageIDFactory(object):
"""Factory for creating MessageIDs."""
def __init__(self, domain):
self._domain = domain
def __call__(self, ustr, default=None):
return MessageID(ustr, self._domain, default)
=============
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
>>> robot.domain
'futurama'
The message'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__()
>>> callable is Message
True
>>> args
(u'robot-message', 'futurama', u'${name} is a robot.', {u'name': u'Bender'})
>>> fembot = Message(u'fembot')
>>> callable, args = fembot.__reduce__()
>>> callable is Message
True
>>> args
(u'fembot', None, None, None)
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, ``MessageFactory``, instantiates immutable messages, while
the deprecated old one continues to work like before.
The roadmap to immutable-only message ids is proposed as follows:
Zope 3.1: Immutable message ids are introduced. Security
declarations for mutable message ids are provided to make the
stripping of security proxies unnecessary.
Zope 3.2: Mutable message ids are deprecated.
Zope 3.3: Mutable message ids are removed.
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Message ID tests.
$Id$
"""
import unittest
from zope.testing.doctestunit import DocTestSuite, DocFileSuite
def test_suite():
return unittest.TestSuite((
DocTestSuite('zope.i18nmessageid.messageid'),
DocTestSuite('zope.i18nmessageid.message'),
DocFileSuite('messages.txt', package='zope.i18nmessageid'),
))
if __name__ == '__main__':
unittest.main(defaultTest="test_suite")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment