1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Base classes for storage interfaces.
23
24 @organization: Zuza Software Foundation
25 @copyright: 2006-2007 Zuza Software Foundation
26 @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>}
27 """
28
29 try:
30 import cPickle as pickle
31 except:
32 import pickle
33 from exceptions import NotImplementedError
34
36 """Forces derived classes to override method."""
37
38 if type(method.im_self) == type(baseclass):
39
40 actualclass = method.im_self
41 else:
42 actualclass = method.im_class
43 if actualclass != baseclass:
44 raise NotImplementedError("%s does not reimplement %s as required by %s" % (actualclass.__name__, method.__name__, baseclass.__name__))
45
48
50 """Base class for translation units.
51
52 Our concept of a I{translation unit} is influenced heavily by XLIFF:
53 U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.htm}
54
55 As such most of the method- and variable names borrows from XLIFF terminology.
56
57 A translation unit consists of the following:
58 - A I{source} string. This is the original translatable text.
59 - A I{target} string. This is the translation of the I{source}.
60 - Zero or more I{notes} on the unit. Notes would typically be some
61 comments from a translator on the unit, or some comments originating from
62 the source code.
63 - Zero or more I{locations}. Locations indicate where in the original
64 source code this unit came from.
65 - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on
66 translations and produce error messages.
67
68 @group Source: *source*
69 @group Target: *target*
70 @group Notes: *note*
71 @group Locations: *location*
72 @group Errors: *error*
73 """
74
76 """Constructs a TranslationUnit containing the given source string."""
77
78 self._store = None
79 self.source = source
80 self.target = None
81 self.notes = ""
82 super(TranslationUnit, self).__init__()
83
85 """Compares two TranslationUnits.
86
87 @type other: L{TranslationUnit}
88 @param other: Another L{TranslationUnit}
89 @rtype: Boolean
90 @return: Returns True if the supplied TranslationUnit equals this unit.
91
92 """
93
94 return self.source == other.source and self.target == other.target
95
97 """Sets the target string to the given value."""
98
99 self.target = target
100
102 """Returns the length of the target string.
103
104 @note: Plural forms might be combined.
105 @rtype: Integer
106
107 """
108
109 length = len(self.target or "")
110 strings = getattr(self.target, "strings", [])
111 if strings:
112 length += sum([len(pluralform) for pluralform in strings[1:]])
113 return length
114
116 """A unique identifier for this unit.
117
118 @rtype: string
119 @return: an identifier for this unit that is unique in the store
120
121 Derived classes should override this in a way that guarantees a unique
122 identifier for each unit in the store.
123 """
124 return self.source
125
127 """A list of source code locations.
128
129 @note: Shouldn't be implemented if the format doesn't support it.
130 @rtype: List
131
132 """
133
134 return []
135
137 """Add one location to the list of locations.
138
139 @note: Shouldn't be implemented if the format doesn't support it.
140
141 """
142 pass
143
145 """Add a location or a list of locations.
146
147 @note: Most classes shouldn't need to implement this,
148 but should rather implement L{addlocation()}.
149 @warning: This method might be removed in future.
150
151 """
152
153 if isinstance(location, list):
154 for item in location:
155 self.addlocation(item)
156 else:
157 self.addlocation(location)
158
159 - def getcontext(self):
160 """Get the message context."""
161 return ""
162
164 """Returns all notes about this unit.
165
166 It will probably be freeform text or something reasonable that can be
167 synthesised by the format.
168 It should not include location comments (see L{getlocations()}).
169
170 """
171 return getattr(self, "notes", "")
172
173 - def addnote(self, text, origin=None):
174 """Adds a note (comment).
175
176 @type text: string
177 @param text: Usually just a sentence or two.
178 @type origin: string
179 @param origin: Specifies who/where the comment comes from.
180 Origin can be one of the following text strings:
181 - 'translator'
182 - 'developer', 'programmer', 'source code' (synonyms)
183
184 """
185 if getattr(self, "notes", None):
186 self.notes += '\n'+text
187 else:
188 self.notes = text
189
191 """Remove all the translator's notes."""
192
193 self.notes = u''
194
195 - def adderror(self, errorname, errortext):
196 """Adds an error message to this unit.
197
198 @type errorname: string
199 @param errorname: A single word to id the error.
200 @type errortext: string
201 @param errortext: The text describing the error.
202
203 """
204
205 pass
206
208 """Get all error messages.
209
210 @rtype: Dictionary
211
212 """
213
214 return {}
215
217 """Marks the unit to indicate whether it needs review.
218
219 @keyword needsreview: Defaults to True.
220 @keyword explanation: Adds an optional explanation as a note.
221
222 """
223
224 pass
225
227 """Indicates whether this unit is translated.
228
229 This should be used rather than deducing it from .target,
230 to ensure that other classes can implement more functionality
231 (as XLIFF does).
232
233 """
234
235 return bool(self.target) and not self.isfuzzy()
236
238 """Indicates whether this unit can be translated.
239
240 This should be used to distinguish real units for translation from
241 header, obsolete, binary or other blank units.
242 """
243 return True
244
246 """Indicates whether this unit is fuzzy."""
247
248 return False
249
251 """Marks the unit as fuzzy or not."""
252 pass
253
255 """Indicates whether this unit is a header."""
256
257 return False
258
260 """Indicates whether this unit needs review."""
261 return False
262
263
265 """Used to see if this unit has no source or target string.
266
267 @note: This is probably used more to find translatable units,
268 and we might want to move in that direction rather and get rid of this.
269
270 """
271
272 return not (self.source or self.target)
273
275 """Tells whether or not this specific unit has plural strings."""
276
277
278 return False
279
281 return getattr(self._store, "sourcelanguage", "en")
282
284 return getattr(self._store, "targetlanguage", None)
285
286 - def merge(self, otherunit, overwrite=False, comments=True):
287 """Do basic format agnostic merging."""
288
289 if self.target == "" or overwrite:
290 self.target = otherunit.target
291
293 """Iterator that only returns this unit."""
294 yield self
295
297 """This unit in a list."""
298 return [self]
299
301 """Build a native unit from a foreign unit, preserving as much
302 information as possible."""
303
304 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy):
305 return unit.copy()
306 newunit = cls(unit.source)
307 newunit.target = unit.target
308 newunit.markfuzzy(unit.isfuzzy())
309 locations = unit.getlocations()
310 if locations:
311 newunit.addlocations(locations)
312 notes = unit.getnotes()
313 if notes:
314 newunit.addnote(notes)
315 return newunit
316 buildfromunit = classmethod(buildfromunit)
317
319 """Base class for stores for multiple translation units of type UnitClass."""
320
321 UnitClass = TranslationUnit
322 Mimetypes = None
323 Extensions = None
324
326 """Constructs a blank TranslationStore."""
327
328 self.units = []
329 self.filepath = None
330 self.translator = ""
331 self.date = ""
332 self.sourcelanguage = None
333 self.targetlanguage = None
334 if unitclass:
335 self.UnitClass = unitclass
336 super(TranslationStore, self).__init__()
337
339 """Sets the source language for this store"""
340 self.sourcelanguage = sourcelanguage
341
343 """Sets the target language for this store"""
344 self.targetlanguage = targetlanguage
345
347 """Iterator over all the units in this store."""
348 for unit in self.units:
349 yield unit
350
352 """Return a list of all units in this store."""
353 return [unit for unit in self.unit_iter()]
354
356 """Appends the given unit to the object's list of units.
357
358 This method should always be used rather than trying to modify the
359 list manually.
360
361 @type unit: L{TranslationUnit}
362 @param unit: The unit that will be added.
363
364 """
365 unit._store = self
366 self.units.append(unit)
367
369 """Adds and returns a new unit with the given source string.
370
371 @rtype: L{TranslationUnit}
372
373 """
374
375 unit = self.UnitClass(source)
376 self.addunit(unit)
377 return unit
378
380 """Finds the unit with the given source string.
381
382 @rtype: L{TranslationUnit} or None
383
384 """
385
386 if len(getattr(self, "sourceindex", [])):
387 if source in self.sourceindex:
388 return self.sourceindex[source]
389 else:
390 for unit in self.units:
391 if unit.source == source:
392 return unit
393 return None
394
396 """Returns the translated string for a given source string.
397
398 @rtype: String or None
399
400 """
401
402 unit = self.findunit(source)
403 if unit and unit.target:
404 return unit.target
405 else:
406 return None
407
409 """Indexes the items in this store. At least .sourceindex should be usefull."""
410
411 self.locationindex = {}
412 self.sourceindex = {}
413 for unit in self.units:
414
415 self.sourceindex[unit.source] = unit
416 if unit.hasplural():
417 for nounform in unit.source.strings[1:]:
418 self.sourceindex[nounform] = unit
419 for location in unit.getlocations():
420 if location in self.locationindex:
421
422 self.locationindex[location] = None
423 else:
424 self.locationindex[location] = unit
425
427 """Converts to a string representation that can be parsed back using L{parsestring()}."""
428
429
430 fileobj = getattr(self, "fileobj", None)
431 self.fileobj = None
432 dump = pickle.dumps(self)
433 self.fileobj = fileobj
434 return dump
435
437 """Returns True if the object doesn't contain any translation units."""
438
439 if len(self.units) == 0:
440 return True
441 for unit in self.units:
442 if not (unit.isblank() or unit.isheader()):
443 return False
444 return True
445
447 """Tries to work out what the name of the filesystem file is and
448 assigns it to .filename."""
449 fileobj = getattr(self, "fileobj", None)
450 if fileobj:
451 filename = getattr(fileobj, "name", getattr(fileobj, "filename", None))
452 if filename:
453 self.filename = filename
454
456 """Converts the string representation back to an object."""
457 newstore = cls()
458 if storestring:
459 newstore.parse(storestring)
460 return newstore
461 parsestring = classmethod(parsestring)
462
464 """parser to process the given source string"""
465 self.units = pickle.loads(data).units
466
468 """Writes the string representation to the given file (or filename)."""
469 if isinstance(storefile, basestring):
470 storefile = open(storefile, "w")
471 self.fileobj = storefile
472 self._assignname()
473 storestring = str(self)
474 storefile.write(storestring)
475 storefile.close()
476
478 """Save to the file that data was originally read from, if available."""
479 fileobj = getattr(self, "fileobj", None)
480 if not fileobj:
481 filename = getattr(self, "filename", None)
482 if filename:
483 fileobj = file(filename, "w")
484 else:
485 fileobj.close()
486 filename = getattr(fileobj, "name", getattr(fileobj, "filename", None))
487 if not filename:
488 raise ValueError("No file or filename to save to")
489 fileobj = fileobj.__class__(filename, "w")
490 self.savefile(fileobj)
491
493 """Reads the given file (or opens the given filename) and parses back to an object."""
494
495 if isinstance(storefile, basestring):
496 storefile = open(storefile, "r")
497 mode = getattr(storefile, "mode", "r")
498
499 if mode == 1 or "r" in mode:
500 storestring = storefile.read()
501 storefile.close()
502 else:
503 storestring = ""
504 newstore = cls.parsestring(storestring)
505 newstore.fileobj = storefile
506 newstore._assignname()
507 return newstore
508 parsefile = classmethod(parsefile)
509