comparison babel/messages/catalog.py @ 69:1d8e81bfedf9

Enhance catalog to also manage the MIME headers.
author cmlenz
date Fri, 08 Jun 2007 15:32:06 +0000
parents d1a7425739d3
children 620fdd25657a
comparison
equal deleted inserted replaced
68:22e30e5a6736 69:1d8e81bfedf9
11 # individuals. For the exact contribution history, see the revision 11 # individuals. For the exact contribution history, see the revision
12 # history and logs, available at http://babel.edgewall.org/log/. 12 # history and logs, available at http://babel.edgewall.org/log/.
13 13
14 """Data structures for message catalogs.""" 14 """Data structures for message catalogs."""
15 15
16 from datetime import datetime
16 import re 17 import re
17 try: 18 try:
18 set 19 set
19 except NameError: 20 except NameError:
20 from sets import Set as set 21 from sets import Set as set
21 22 import time
23
24 from babel import __version__ as VERSION
22 from babel.core import Locale 25 from babel.core import Locale
23 from babel.util import odict 26 from babel.messages.plurals import PLURALS
27 from babel.util import odict, UTC
24 28
25 __all__ = ['Message', 'Catalog'] 29 __all__ = ['Message', 'Catalog']
26 __docformat__ = 'restructuredtext en' 30 __docformat__ = 'restructuredtext en'
27 31
28 PYTHON_FORMAT = re.compile(r'\%(\([\w]+\))?[diouxXeEfFgGcrs]').search 32 PYTHON_FORMAT = re.compile(r'\%(\([\w]+\))?[diouxXeEfFgGcrs]').search
43 """ 47 """
44 self.id = id 48 self.id = id
45 self.string = string 49 self.string = string
46 self.locations = locations 50 self.locations = locations
47 self.flags = set(flags) 51 self.flags = set(flags)
48 if self.python_format: 52 if id and self.python_format:
49 self.flags.add('python-format') 53 self.flags.add('python-format')
50 else: 54 else:
51 self.flags.discard('python-format') 55 self.flags.discard('python-format')
52 56
53 def __repr__(self): 57 def __repr__(self):
54 return '<%s %r>' % (type(self).__name__, self.id) 58 return '<%s %r>' % (type(self).__name__, self.id)
59
60 def fuzzy(self):
61 return 'fuzzy' in self.flags
62 fuzzy = property(fuzzy, doc="""\
63 Whether the translation is fuzzy.
64
65 >>> Message('foo').fuzzy
66 False
67 >>> Message('foo', 'foo', flags=['fuzzy']).fuzzy
68 True
69
70 :type: `bool`
71 """)
55 72
56 def pluralizable(self): 73 def pluralizable(self):
57 return isinstance(self.id, (list, tuple)) 74 return isinstance(self.id, (list, tuple))
58 pluralizable = property(pluralizable, doc="""\ 75 pluralizable = property(pluralizable, doc="""\
59 Whether the message is plurizable. 76 Whether the message is plurizable.
84 101
85 102
86 class Catalog(object): 103 class Catalog(object):
87 """Representation a message catalog.""" 104 """Representation a message catalog."""
88 105
89 def __init__(self, domain=None, locale=None): 106 def __init__(self, locale=None, domain=None, project=None, version=None,
107 creation_date=None, revision_date=None, last_translator=None):
90 """Initialize the catalog object. 108 """Initialize the catalog object.
91 109
92 :param domain: the message domain 110 :param domain: the message domain
93 :param locale: the locale identifier or `Locale` object, or `None` 111 :param locale: the locale identifier or `Locale` object, or `None`
94 if the catalog is not bound to a locale (which basically 112 if the catalog is not bound to a locale (which basically
96 """ 114 """
97 self.domain = domain #: the message domain 115 self.domain = domain #: the message domain
98 if locale: 116 if locale:
99 locale = Locale.parse(locale) 117 locale = Locale.parse(locale)
100 self.locale = locale #: the locale or `None` 118 self.locale = locale #: the locale or `None`
101 self.messages = odict() #: the actual `Message` entries by ID 119 self._messages = odict()
120
121 self.project = project or 'PROJECT' #: the project name
122 self.version = version or 'VERSION' #: the project version
123
124 if creation_date is None:
125 creation_date = time.localtime()
126 elif isinstance(creation_date, datetime):
127 if creation_date.tzinfo is None:
128 creation_date = creation_date.replace(tzinfo=UTC)
129 creation_date = creation_date.timetuple()
130 self.creation_date = creation_date #: creation date of the template
131 if revision_date is None:
132 revision_date = time.localtime()
133 elif isinstance(revision_date, datetime):
134 if revision_date.tzinfo is None:
135 revision_date = revision_date.replace(tzinfo=UTC)
136 revision_date = revision_date.timetuple()
137 self.revision_date = revision_date #: last revision date of the catalog
138 self.last_translator = last_translator #: last translator name + email
139
140 def headers(self):
141 headers = []
142 headers.append(('Project-Id-Version',
143 '%s %s' % (self.project, self.version)))
144 headers.append(('POT-Creation-Date',
145 time.strftime('%Y-%m-%d %H:%M%z', self.creation_date)))
146 if self.locale is None:
147 headers.append(('PO-Revision-Date', 'YEAR-MO-DA HO:MI+ZONE'))
148 headers.append(('Last-Translator', 'FULL NAME <EMAIL@ADDRESS>'))
149 headers.append(('Language-Team', 'LANGUAGE <LL@li.org>'))
150 else:
151 headers.append(('PO-Revision-Date',
152 time.strftime('%Y-%m-%d %H:%M%z', self.revision_date)))
153 headers.append(('Last-Translator', self.last_translator))
154 headers.append(('Language-Team', '%s <LL@li.org>' % self.locale))
155 headers.append(('Plural-Forms', self.plural_forms))
156 headers.append(('MIME-Version', '1.0'))
157 headers.append(('Content-Type', 'text/plain; charset=utf-8'))
158 headers.append(('Content-Transfer-Encoding', '8bit'))
159 headers.append(('Generated-By', 'Babel %s' % VERSION))
160 return headers
161 headers = property(headers, doc="""\
162 The MIME headers of the catalog, used for the special ``msgid ""`` entry.
163
164 The behavior of this property changes slightly depending on whether a locale
165 is set or not, the latter indicating that the catalog is actually a template
166 for actual translations.
167
168 Here's an example of the output for such a catalog template:
169
170 >>> catalog = Catalog(project='Foobar', version='1.0',
171 ... creation_date=datetime(1990, 4, 1, 15, 30))
172 >>> for name, value in catalog.headers:
173 ... print '%s: %s' % (name, value)
174 Project-Id-Version: Foobar 1.0
175 POT-Creation-Date: 1990-04-01 15:30+0000
176 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
177 Last-Translator: FULL NAME <EMAIL@ADDRESS>
178 Language-Team: LANGUAGE <LL@li.org>
179 Plural-Forms: nplurals=INTEGER; plural=EXPRESSION
180 MIME-Version: 1.0
181 Content-Type: text/plain; charset=utf-8
182 Content-Transfer-Encoding: 8bit
183 Generated-By: Babel ...
184
185 And here's an example of the output when the locale is set:
186
187 >>> catalog = Catalog(locale='de_DE', project='Foobar', version='1.0',
188 ... creation_date=datetime(1990, 4, 1, 15, 30),
189 ... revision_date=datetime(1990, 8, 3, 12, 0),
190 ... last_translator='John Doe <jd@example.com>')
191 >>> for name, value in catalog.headers:
192 ... print '%s: %s' % (name, value)
193 Project-Id-Version: Foobar 1.0
194 POT-Creation-Date: 1990-04-01 15:30+0000
195 PO-Revision-Date: 1990-08-03 12:00+0000
196 Last-Translator: John Doe <jd@example.com>
197 Language-Team: de_DE <LL@li.org>
198 Plural-Forms: nplurals=2; plural=(n != 1)
199 MIME-Version: 1.0
200 Content-Type: text/plain; charset=utf-8
201 Content-Transfer-Encoding: 8bit
202 Generated-By: Babel ...
203
204 :type: `list`
205 """)
206
207 def plural_forms(self):
208 num, expr = ('INTEGER', 'EXPRESSION')
209 if self.locale:
210 if str(self.locale) in PLURALS:
211 num, expr = PLURALS[str(self.locale)]
212 elif self.locale.language in PLURALS:
213 num, expr = PLURALS[self.locale.language]
214 return 'nplurals=%s; plural=%s' % (num, expr)
215 plural_forms = property(plural_forms, doc="""\
216 Return the plural forms declaration for the locale.
217
218 >>> Catalog(locale='en_US').plural_forms
219 'nplurals=2; plural=(n != 1)'
220 >>> Catalog(locale='pt_BR').plural_forms
221 'nplurals=2; plural=(n > 1)'
222
223 :type: `str`
224 """)
225
226 def __contains__(self, id):
227 """Return whether the catalog has a message with the specified ID."""
228 return id in self._messages
102 229
103 def __iter__(self): 230 def __iter__(self):
104 """Iterates through all the entries in the catalog, in the order they 231 """Iterates through all the entries in the catalog, in the order they
105 were added, yielding a `Message` object for every entry. 232 were added, yielding a `Message` object for every entry.
106 233
107 :rtype: ``iterator`` 234 :rtype: ``iterator``
108 """ 235 """
109 for id in self.messages: 236 buf = []
110 yield self.messages[id] 237 for name, value in self.headers:
238 buf.append('%s: %s' % (name, value))
239 yield Message('', '\n'.join(buf), flags=set(['fuzzy']))
240 for id in self._messages:
241 yield self._messages[id]
111 242
112 def __repr__(self): 243 def __repr__(self):
113 locale = '' 244 locale = ''
114 if self.locale: 245 if self.locale:
115 locale = ' %s' % self.locale 246 locale = ' %s' % self.locale
116 return '<%s %r%s>' % (type(self).__name__, self.domain, locale) 247 return '<%s %r%s>' % (type(self).__name__, self.domain, locale)
117 248
118 def __delitem__(self, id): 249 def __delitem__(self, id):
119 """Delete the message with the specified ID.""" 250 """Delete the message with the specified ID."""
120 if id in self.messaages: 251 if id in self._messages:
121 del self.messages[id] 252 del self._messages[id]
122 253
123 def __getitem__(self, id): 254 def __getitem__(self, id):
124 """Return the message with the specified ID. 255 """Return the message with the specified ID.
125 256
126 :param id: the message ID 257 :param id: the message ID
127 :return: the message with the specified ID, or `None` if no such message 258 :return: the message with the specified ID, or `None` if no such message
128 is in the catalog 259 is in the catalog
129 :rytpe: `Message` 260 :rtype: `Message`
130 """ 261 """
131 return self.messages.get(id) 262 return self._messages.get(id)
132 263
133 def __setitem__(self, id, message): 264 def __setitem__(self, id, message):
134 """Add or update the message with the specified ID. 265 """Add or update the message with the specified ID.
135 266
136 >>> catalog = Catalog() 267 >>> catalog = Catalog()
151 282
152 :param id: the message ID 283 :param id: the message ID
153 :param message: the `Message` object 284 :param message: the `Message` object
154 """ 285 """
155 assert isinstance(message, Message), 'expected a Message object' 286 assert isinstance(message, Message), 'expected a Message object'
156 current = self.messages.get(id) 287 current = self._messages.get(id)
157 if current: 288 if current:
158 assert current.string == message.string, 'translation mismatch' 289 assert current.string == message.string, 'translation mismatch'
159 current.locations.extend(message.locations) 290 current.locations.extend(message.locations)
160 current.flags |= message.flags 291 current.flags |= message.flags
161 message = current 292 message = current
162 else: 293 else:
163 if isinstance(id, (list, tuple)): 294 if isinstance(id, (list, tuple)):
164 singular, plural = id 295 singular, plural = id
165 id = singular 296 id = singular
166 self.messages[id] = message 297 self._messages[id] = message
167 298
168 def add(self, id, string=None, locations=(), flags=()): 299 def add(self, id, string=None, locations=(), flags=()):
169 """Add or update the message with the specified ID. 300 """Add or update the message with the specified ID.
170 301
171 >>> catalog = Catalog() 302 >>> catalog = Catalog()
Copyright (C) 2012-2017 Edgewall Software