1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """class that handles all header functions for a header in a po file"""
22
23 import re
24 import time
25
26 from translate import __version__
27 from translate.misc import dictutils
28
29 author_re = re.compile(r".*<\S+@\S+>.*\d{4,4}")
30
31 default_header = {
32 "Project-Id-Version": "PACKAGE VERSION",
33 "PO-Revision-Date": "YEAR-MO-DA HO:MI+ZONE",
34 "Last-Translator": "FULL NAME <EMAIL@ADDRESS>",
35 "Language-Team": "LANGUAGE <LL@li.org>",
36 "Plural-Forms": "nplurals=INTEGER; plural=EXPRESSION;",
37 }
38
39
41 """Parses an input string with the definition of a PO header and returns
42 the interpreted values as a dictionary."""
43 headervalues = dictutils.ordereddict()
44 for line in input.split("\n"):
45 if not line or ":" not in line:
46 continue
47 key, value = line.split(":", 1)
48
49 key = str(key.strip())
50 headervalues[key] = value.strip()
51 return headervalues
52
53
55 """Returns the timezone as a string in the format [+-]0000, eg +0200.
56
57 @rtype: str"""
58 if time.daylight:
59 tzoffset = time.altzone
60 else:
61 tzoffset = time.timezone
62
63 hours, minutes = time.gmtime(abs(tzoffset))[3:5]
64 if tzoffset > 0:
65 hours *= -1
66 tz = str("%+d" % hours).zfill(3) + str(minutes).zfill(2)
67 return tz
68
69
70 -def update(existing, add=False, **kwargs):
101
102
104 """This class implements functionality for manipulation of po file headers.
105 This class is a mix-in class and useless on its own. It must be used from all
106 classes which represent a po file"""
107
108 x_generator = "Translate Toolkit %s" % __version__.sver
109
110 header_order = [
111 "Project-Id-Version",
112 "Report-Msgid-Bugs-To",
113 "POT-Creation-Date",
114 "PO-Revision-Date",
115 "Last-Translator",
116 "Language-Team",
117 "Language",
118 "MIME-Version",
119 "Content-Type",
120 "Content-Transfer-Encoding",
121 "Plural-Forms",
122 "X-Generator",
123 ]
124
131
144 """Create a header dictionary with useful defaults.
145
146 pot_creation_date can be None (current date) or a value (datetime or string)
147 po_revision_date can be None (form), False (=pot_creation_date), True (=now),
148 or a value (datetime or string)
149
150 @return: Dictionary with the header items
151 @rtype: dict
152 """
153 if project_id_version is None:
154 project_id_version = "PACKAGE VERSION"
155 if pot_creation_date is None or pot_creation_date == True:
156 pot_creation_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
157 if isinstance(pot_creation_date, time.struct_time):
158 pot_creation_date = time.strftime("%Y-%m-%d %H:%M", pot_creation_date) + tzstring()
159 if po_revision_date is None:
160 po_revision_date = "YEAR-MO-DA HO:MI+ZONE"
161 elif po_revision_date == False:
162 po_revision_date = pot_creation_date
163 elif po_revision_date == True:
164 po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
165 if isinstance(po_revision_date, time.struct_time):
166 po_revision_date = time.strftime("%Y-%m-%d %H:%M", po_revision_date) + tzstring()
167 if last_translator is None:
168 last_translator = "FULL NAME <EMAIL@ADDRESS>"
169 if language_team is None:
170 language_team = "LANGUAGE <LL@li.org>"
171 if mime_version is None:
172 mime_version = "1.0"
173 if report_msgid_bugs_to is None:
174 report_msgid_bugs_to = ""
175
176 defaultargs = dictutils.ordereddict()
177 defaultargs["Project-Id-Version"] = project_id_version
178 defaultargs["Report-Msgid-Bugs-To"] = report_msgid_bugs_to
179 defaultargs["POT-Creation-Date"] = pot_creation_date
180 defaultargs["PO-Revision-Date"] = po_revision_date
181 defaultargs["Last-Translator"] = last_translator
182 defaultargs["Language-Team"] = language_team
183 defaultargs["MIME-Version"] = mime_version
184 defaultargs["Content-Type"] = "text/plain; charset=%s" % charset
185 defaultargs["Content-Transfer-Encoding"] = encoding
186 if plural_forms:
187 defaultargs["Plural-Forms"] = plural_forms
188 defaultargs["X-Generator"] = self.x_generator
189
190 return update(defaultargs, add=True, **kwargs)
191
193 """Returns the header element, or None. Only the first element is allowed
194 to be a header. Note that this could still return an empty header element,
195 if present."""
196 if len(self.units) == 0:
197 return None
198 candidate = self.units[0]
199 if candidate.isheader():
200 return candidate
201 else:
202 return None
203
211
213 """Updates the fields in the PO style header.
214
215 This will create a header if add == True."""
216 header = self.header()
217 if not header:
218 if add:
219 header = self.makeheader(**kwargs)
220 self._insert_header(header)
221 else:
222 headeritems = update(self.parseheader(), add, **kwargs)
223 keys = headeritems.keys()
224 if not "Content-Type" in keys or "charset=CHARSET" in headeritems["Content-Type"]:
225 headeritems["Content-Type"] = "text/plain; charset=UTF-8"
226 if not "Content-Transfer-Encoding" in keys or "ENCODING" in headeritems["Content-Transfer-Encoding"]:
227 headeritems["Content-Transfer-Encoding"] = "8bit"
228 headerString = ""
229 for key, value in headeritems.items():
230 if value is not None:
231 headerString += "%s: %s\n" % (key, value)
232 header.target = headerString
233 header.markfuzzy(False)
234 return header
235
243
245 """Returns the nplural and plural values from the header."""
246 header = self.parseheader()
247 pluralformvalue = header.get('Plural-Forms', None)
248 if pluralformvalue is None:
249 return None, None
250 nplural = re.findall("nplurals=(.+?);", pluralformvalue)
251 plural = re.findall("plural=(.+?);?$", pluralformvalue)
252 if not nplural or nplural[0] == "INTEGER":
253 nplural = None
254 else:
255 nplural = nplural[0]
256 if not plural or plural[0] == "EXPRESSION":
257 plural = None
258 else:
259 plural = plural[0]
260 return nplural, plural
261
267
291
293 """Set the target language in the header.
294
295 This removes any custom Poedit headers if they exist.
296
297 @param lang: the new target language code
298 @type lang: str
299 """
300 if isinstance(lang, basestring) and len(lang) > 1:
301 self.updateheader(add=True, Language=lang, X_Poedit_Language=None, X_Poedit_Country=None)
302
304 """Return the project based on information in the header.
305
306 The project is determined in the following sequence:
307 1. Use the 'X-Project-Style' entry in the header.
308 2. Use 'Report-Msgid-Bug-To' entry
309 3. Use the 'X-Accelerator' entry
310 4. Use the Project ID
311 5. Analyse the file itself (not yet implemented)
312 """
313 header = self.parseheader()
314 project = header.get('X-Project-Style', None)
315 if project is not None:
316 return project
317 bug_address = header.get('Report-Msgid-Bugs-To', None)
318 if bug_address is not None:
319 if 'bugzilla.gnome.org' in bug_address:
320 return 'gnome'
321 if 'bugs.kde.org' in bug_address:
322 return 'kde'
323 accelerator = header.get('X-Accelerator-Marker', None)
324 if accelerator is not None:
325 if accelerator == "~":
326 return "openoffice"
327 elif accelerator == "&":
328 return "mozilla"
329 project_id = header.get('Project-Id-Version', None)
330 if project_id is not None:
331 if 'gnome' in project_id.lower():
332 return "gnome"
333
334 return None
335
345
347 """Merges another header with this header.
348
349 This header is assumed to be the template.
350
351 @type otherstore: L{base.TranslationStore}
352 """
353
354 newvalues = otherstore.parseheader()
355 retain_list = ("Project-Id-Version", "PO-Revision-Date", "Last-Translator",
356 "Language-Team", "Plural-Forms")
357 retain = dict((key, newvalues[key]) for key in retain_list if newvalues.get(key, None) and newvalues[key] != default_header.get(key, None))
358 self.updateheader(**retain)
359
361 """Add contribution comments if necessary."""
362 header = self.header()
363 if not header:
364 return
365 prelines = []
366 contriblines = []
367 postlines = []
368 contribexists = False
369 incontrib = False
370 outcontrib = False
371 for line in header.getnotes("translator").split('\n'):
372 line = line.strip()
373 if line == u"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
374 incontrib = True
375 continue
376 if author_re.match(line):
377 incontrib = True
378 contriblines.append(line)
379 continue
380 if line == "" and incontrib:
381 incontrib = False
382 outcontrib = True
383 if incontrib:
384 contriblines.append(line)
385 elif not outcontrib:
386 prelines.append(line)
387 else:
388 postlines.append(line)
389
390 year = time.strftime("%Y")
391 contribexists = False
392 for i in range(len(contriblines)):
393 line = contriblines[i]
394 if name in line and (email is None or email in line):
395 contribexists = True
396 if year in line:
397 break
398 else:
399
400 if line[-1] == '.':
401 line = line[:-1]
402 contriblines[i] = "%s, %s." % (line, year)
403
404 if not contribexists:
405
406 if email:
407 contriblines.append("%s <%s>, %s." % (name, email, year))
408 else:
409 contriblines.append("%s, %s." % (name, year))
410
411 header.removenotes()
412 header.addnote("\n".join(prelines))
413 header.addnote("\n".join(contriblines))
414 header.addnote("\n".join(postlines))
415
417 """Create a header for the given filename.
418
419 Check .makeheaderdict() for information on parameters."""
420 headerpo = self.UnitClass("", encoding=self._encoding)
421 headerpo.markfuzzy()
422 headeritems = self.makeheaderdict(**kwargs)
423 headervalue = ""
424 for (key, value) in headeritems.items():
425 headervalue += "%s: %s\n" % (key, value)
426 headerpo.target = headervalue
427 return headerpo
428