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
295
297 """Set the target language in the header.
298
299 This removes any custom Poedit headers if they exist.
300
301 @param lang: the new target language code
302 @type lang: str
303 """
304 if isinstance(lang, basestring) and len(lang) > 1:
305 self.updateheader(add=True, Language=lang, X_Poedit_Language=None, X_Poedit_Country=None)
306
308 """Return the project based on information in the header.
309
310 The project is determined in the following sequence:
311 1. Use the 'X-Project-Style' entry in the header.
312 2. Use 'Report-Msgid-Bug-To' entry
313 3. Use the 'X-Accelerator' entry
314 4. Use the Project ID
315 5. Analyse the file itself (not yet implemented)
316 """
317 header = self.parseheader()
318 project = header.get('X-Project-Style', None)
319 if project is not None:
320 return project
321 bug_address = header.get('Report-Msgid-Bugs-To', None)
322 if bug_address is not None:
323 if 'bugzilla.gnome.org' in bug_address:
324 return 'gnome'
325 if 'bugs.kde.org' in bug_address:
326 return 'kde'
327 accelerator = header.get('X-Accelerator-Marker', None)
328 if accelerator is not None:
329 if accelerator == "~":
330 return "openoffice"
331 elif accelerator == "&":
332 return "mozilla"
333 project_id = header.get('Project-Id-Version', None)
334 if project_id is not None:
335 if 'gnome' in project_id.lower():
336 return "gnome"
337
338 return None
339
349
351 """Merges another header with this header.
352
353 This header is assumed to be the template.
354
355 @type otherstore: L{base.TranslationStore}
356 """
357
358 newvalues = otherstore.parseheader()
359 retain_list = ("Project-Id-Version", "PO-Revision-Date", "Last-Translator",
360 "Language-Team", "Plural-Forms")
361 retain = dict((key, newvalues[key]) for key in retain_list if newvalues.get(key, None) and newvalues[key] != default_header.get(key, None))
362 self.updateheader(**retain)
363
365 """Add contribution comments if necessary."""
366 header = self.header()
367 if not header:
368 return
369 prelines = []
370 contriblines = []
371 postlines = []
372 contribexists = False
373 incontrib = False
374 outcontrib = False
375 for line in header.getnotes("translator").split('\n'):
376 line = line.strip()
377 if line == u"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
378 incontrib = True
379 continue
380 if author_re.match(line):
381 incontrib = True
382 contriblines.append(line)
383 continue
384 if line == "" and incontrib:
385 incontrib = False
386 outcontrib = True
387 if incontrib:
388 contriblines.append(line)
389 elif not outcontrib:
390 prelines.append(line)
391 else:
392 postlines.append(line)
393
394 year = time.strftime("%Y")
395 contribexists = False
396 for i in range(len(contriblines)):
397 line = contriblines[i]
398 if name in line and (email is None or email in line):
399 contribexists = True
400 if year in line:
401 break
402 else:
403
404 if line[-1] == '.':
405 line = line[:-1]
406 contriblines[i] = "%s, %s." % (line, year)
407
408 if not contribexists:
409
410 if email:
411 contriblines.append("%s <%s>, %s." % (name, email, year))
412 else:
413 contriblines.append("%s, %s." % (name, year))
414
415 header.removenotes()
416 header.addnote("\n".join(prelines))
417 header.addnote("\n".join(contriblines))
418 header.addnote("\n".join(postlines))
419
421 """Create a header for the given filename.
422
423 Check .makeheaderdict() for information on parameters."""
424 headerpo = self.UnitClass("", encoding=self._encoding)
425 headerpo.markfuzzy()
426 headeritems = self.makeheaderdict(**kwargs)
427 headervalue = ""
428 for (key, value) in headeritems.items():
429 if value is None:
430 continue
431 headervalue += "%s: %s\n" % (key, value)
432 headerpo.target = headervalue
433 return headerpo
434