| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
| 3 # | 3 # |
| 4 # Copyright 2006-2008 Zuza Software Foundation | 4 # Copyright 2006-2008 Zuza Software Foundation |
| 5 # | 5 # |
| 6 # This file is part of translate. | 6 # This file is part of translate. |
| 7 # | 7 # |
| 8 # translate is free software; you can redistribute it and/or modify | 8 # translate is free software; you can redistribute it and/or modify |
| 9 # it under the terms of the GNU General Public License as published by | 9 # it under the terms of the GNU General Public License as published by |
| 10 # the Free Software Foundation; either version 2 of the License, or | 10 # the Free Software Foundation; either version 2 of the License, or |
| 11 # (at your option) any later version. | 11 # (at your option) any later version. |
| 12 # | 12 # |
| 13 # translate is distributed in the hope that it will be useful, | 13 # translate is distributed in the hope that it will be useful, |
| 14 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 # GNU General Public License for more details. | 16 # GNU General Public License for more details. |
| 17 # | 17 # |
| 18 # You should have received a copy of the GNU General Public License | 18 # You should have received a copy of the GNU General Public License |
| 19 # along with translate; if not, write to the Free Software | 19 # along with translate; if not, write to the Free Software |
| 20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 21 | 21 |
| 22 """Base classes for storage interfaces. | 22 """Base classes for storage interfaces. |
| 23 | 23 |
| 24 @organization: Zuza Software Foundation | 24 @organization: Zuza Software Foundation |
| 25 @copyright: 2006-2007 Zuza Software Foundation | 25 @copyright: 2006-2007 Zuza Software Foundation |
| 26 @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>} | 26 @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>} |
| 27 """ | 27 """ |
| 28 | 28 |
| 29 try: | 29 try: |
| 30 import cPickle as pickle | 30 import cPickle as pickle |
| 31 except: | 31 except: |
| 32 import pickle | 32 import pickle |
| 33 from exceptions import NotImplementedError | 33 from exceptions import NotImplementedError |
| 34 | 34 |
| 35 def force_override(method, baseclass): | 35 def force_override(method, baseclass): |
| 36 """Forces derived classes to override method.""" | 36 """Forces derived classes to override method.""" |
| 37 | 37 |
| 38 if type(method.im_self) == type(baseclass): | 38 if type(method.im_self) == type(baseclass): |
| 39 # then this is a classmethod and im_self is the actual class | 39 # then this is a classmethod and im_self is the actual class |
| 40 actualclass = method.im_self | 40 actualclass = method.im_self |
| 41 else: | 41 else: |
| 42 actualclass = method.im_class | 42 actualclass = method.im_class |
| 43 if actualclass != baseclass: | 43 if actualclass != baseclass: |
| 44 raise NotImplementedError("%s does not reimplement %s as required by %s" % (actualclass.__name__, method.__name__, baseclass.__name__)) | 44 raise NotImplementedError("%s does not reimplement %s as required by %s" % (actualclass.__name__, method.__name__, baseclass.__name__)) |
| 45 | 45 |
| 46 class ParseError(Exception): | 46 class ParseError(Exception): |
| 47 pass | 47 pass |
| 48 | 48 |
| 49 class TranslationUnit(object): | 49 class TranslationUnit(object): |
| 50 """Base class for translation units. | 50 """Base class for translation units. |
| 51 | 51 |
| 52 Our concept of a I{translation unit} is influenced heavily by XLIFF: | 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.h tm} | 53 U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.h tm} |
| 54 | 54 |
| 55 As such most of the method- and variable names borrows from XLIFF terminolog y. | 55 As such most of the method- and variable names borrows from XLIFF terminolog y. |
| 56 | 56 |
| 57 A translation unit consists of the following: | 57 A translation unit consists of the following: |
| 58 - A I{source} string. This is the original translatable text. | 58 - A I{source} string. This is the original translatable text. |
| 59 - A I{target} string. This is the translation of the I{source}. | 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 | 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 fro m | 61 comments from a translator on the unit, or some comments originating fro m |
| 62 the source code. | 62 the source code. |
| 63 - Zero or more I{locations}. Locations indicate where in the original | 63 - Zero or more I{locations}. Locations indicate where in the original |
| 64 source code this unit came from. | 64 source code this unit came from. |
| 65 - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on | 65 - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on |
| 66 translations and produce error messages. | 66 translations and produce error messages. |
| 67 - Zero or one I{prev_source} string, the old version of the translatable t ext. | |
| 68 - Zero or one I{prev_context} string, the old version of the message conte xt. | |
| 67 | 69 |
| 68 @group Source: *source* | 70 @group Source: *source* |
| 69 @group Target: *target* | 71 @group Target: *target* |
| 72 @group PrevSource: *PrevSource* | |
| 73 @group PrevContext: *PrevContext* | |
| 70 @group Notes: *note* | 74 @group Notes: *note* |
| 71 @group Locations: *location* | 75 @group Locations: *location* |
| 72 @group Errors: *error* | 76 @group Errors: *error* |
| 73 """ | 77 """ |
| 74 | 78 |
| 75 def __init__(self, source): | 79 def __init__(self, source): |
| 76 """Constructs a TranslationUnit containing the given source string.""" | 80 """Constructs a TranslationUnit containing the given source string.""" |
| 77 | 81 |
| 78 self.source = source | 82 self.source = source |
| 79 self.target = None | 83 self.target = None |
| 84 self.prev_source = None | |
| 85 self.prev_context = None | |
| 80 self.notes = "" | 86 self.notes = "" |
| 81 super(TranslationUnit, self).__init__() | 87 super(TranslationUnit, self).__init__() |
| 82 | 88 |
| 83 def __eq__(self, other): | 89 def __eq__(self, other): |
| 84 """Compares two TranslationUnits. | 90 """Compares two TranslationUnits. |
| 85 | 91 |
| 86 @type other: L{TranslationUnit} | 92 @type other: L{TranslationUnit} |
| 87 @param other: Another L{TranslationUnit} | 93 @param other: Another L{TranslationUnit} |
| 88 @rtype: Boolean | 94 @rtype: Boolean |
| 89 @return: Returns True if the supplied TranslationUnit equals this unit. | 95 @return: Returns True if the supplied TranslationUnit equals this unit. |
| 90 | 96 |
| 91 """ | 97 """ |
| 92 | 98 |
| 93 return self.source == other.source and self.target == other.target | 99 return self.source == other.source and self.target == other.target |
| 94 | 100 |
| 95 def settarget(self, target): | 101 def settarget(self, target): |
| 96 """Sets the target string to the given value.""" | 102 """Sets the target string to the given value.""" |
| 97 | 103 |
| 98 self.target = target | 104 self.target = target |
| 99 | 105 |
| 100 def gettargetlen(self): | 106 def gettargetlen(self): |
| 101 """Returns the length of the target string. | 107 """Returns the length of the target string. |
| 102 | 108 |
| 103 @note: Plural forms might be combined. | 109 @note: Plural forms might be combined. |
| 104 @rtype: Integer | 110 @rtype: Integer |
| 105 | 111 |
| 106 """ | 112 """ |
| 107 | 113 |
| 108 length = len(self.target or "") | 114 length = len(self.target or "") |
| 109 strings = getattr(self.target, "strings", []) | 115 strings = getattr(self.target, "strings", []) |
| 110 if strings: | 116 if strings: |
| 111 length += sum([len(pluralform) for pluralform in strings[1:]]) | 117 length += sum([len(pluralform) for pluralform in strings[1:]]) |
| 112 return length | 118 return length |
| 113 | 119 |
| 114 def getid(self): | 120 def getid(self): |
| 115 """A unique identifier for this unit. | 121 """A unique identifier for this unit. |
| 116 | 122 |
| 117 @rtype: string | 123 @rtype: string |
| 118 @return: an identifier for this unit that is unique in the store | 124 @return: an identifier for this unit that is unique in the store |
| 119 | 125 |
| 120 Derived classes should override this in a way that guarantees a unique | 126 Derived classes should override this in a way that guarantees a unique |
| 121 identifier for each unit in the store. | 127 identifier for each unit in the store. |
| 122 """ | 128 """ |
| 123 return self.source | 129 return self.source |
| 124 | 130 |
| 125 def getlocations(self): | 131 def getlocations(self): |
| 126 """A list of source code locations. | 132 """A list of source code locations. |
| 127 | 133 |
| 128 @note: Shouldn't be implemented if the format doesn't support it. | 134 @note: Shouldn't be implemented if the format doesn't support it. |
| 129 @rtype: List | 135 @rtype: List |
| (...skipping 112 matching lines...) Show 10 above Show 10 below | |
| 242 return True | 248 return True |
| 243 | 249 |
| 244 def isfuzzy(self): | 250 def isfuzzy(self): |
| 245 """Indicates whether this unit is fuzzy.""" | 251 """Indicates whether this unit is fuzzy.""" |
| 246 | 252 |
| 247 return False | 253 return False |
| 248 | 254 |
| 249 def markfuzzy(self, value=True): | 255 def markfuzzy(self, value=True): |
| 250 """Marks the unit as fuzzy or not.""" | 256 """Marks the unit as fuzzy or not.""" |
| 251 pass | 257 pass |
| 252 | 258 |
| 253 def isheader(self): | 259 def isheader(self): |
| 254 """Indicates whether this unit is a header.""" | 260 """Indicates whether this unit is a header.""" |
| 255 | 261 |
| 256 return False | 262 return False |
| 257 | 263 |
| 258 def isreview(self): | 264 def isreview(self): |
| 259 """Indicates whether this unit needs review.""" | 265 """Indicates whether this unit needs review.""" |
| 260 return False | 266 return False |
| 261 | 267 |
| 262 | 268 |
| 263 def isblank(self): | 269 def isblank(self): |
| 264 """Used to see if this unit has no source or target string. | 270 """Used to see if this unit has no source or target string. |
| 265 | 271 |
| 266 @note: This is probably used more to find translatable units, | 272 @note: This is probably used more to find translatable units, |
| 267 and we might want to move in that direction rather and get rid of this. | 273 and we might want to move in that direction rather and get rid of this. |
| 268 | 274 |
| 269 """ | 275 """ |
| 270 | 276 |
| 271 return not (self.source or self.target) | 277 return not (self.source or self.target) |
| 272 | 278 |
| 273 def hasplural(self): | 279 def hasplural(self): |
| 274 """Tells whether or not this specific unit has plural strings.""" | 280 """Tells whether or not this specific unit has plural strings.""" |
| 275 | 281 |
| 276 #TODO: Reconsider | 282 #TODO: Reconsider |
| 277 return False | 283 return False |
| 278 | 284 |
| 279 def merge(self, otherunit, overwrite=False, comments=True): | 285 def merge(self, otherunit, overwrite=False, comments=True): |
| 280 """Do basic format agnostic merging.""" | 286 """Do basic format agnostic merging.""" |
| 281 | 287 |
| 282 if self.target == "" or overwrite: | 288 if self.target == "" or overwrite: |
| 283 self.target = otherunit.target | 289 self.target = otherunit.target |
| 284 | 290 |
| 285 def unit_iter(self): | 291 def unit_iter(self): |
| 286 """Iterator that only returns this unit.""" | 292 """Iterator that only returns this unit.""" |
| 287 yield self | 293 yield self |
| 288 | 294 |
| 289 def getunits(self): | 295 def getunits(self): |
| 290 """This unit in a list.""" | 296 """This unit in a list.""" |
| 291 return [self] | 297 return [self] |
| 298 | |
| 299 def setprev_source(self, source): | |
| 300 """Sets the previous source message for this unit""" | |
| 301 self.prev_source = source | |
| 302 | |
| 303 def getprev_source(self): | |
| 304 """Gets the previous source message for this unit""" | |
| 305 return self.prev_source | |
|
dwaynebailey
2008/07/17 12:59:27
Shouldn't the decorators be here for prev_source?
georgeyk
2008/07/18 05:15:49
On 2008/07/17 12:59:27, dwaynebailey wrote:
> Shou
| |
| 306 | |
| 307 def setprev_context(self, context): | |
| 308 """Sets the previous context of this unit""" | |
| 309 self.prev_context = context | |
| 310 | |
| 311 def getprev_context(self): | |
| 312 """Gets the previous context of this unit""" | |
| 313 return self.prev_context | |
| 314 | |
| 315 def set_as_previous(self): | |
| 316 """Sets this unit as a previous unit message""" | |
| 317 if self.isfuzzy(): | |
| 318 return | |
| 319 self.setprev_context(self.getcontext()) | |
| 320 self.setprev_source(self.source) | |
| 321 self.markfuzzy() | |
| 292 | 322 |
| 293 def buildfromunit(cls, unit): | 323 def buildfromunit(cls, unit): |
| 294 """Build a native unit from a foreign unit, preserving as much | 324 """Build a native unit from a foreign unit, preserving as much |
| 295 information as possible.""" | 325 information as possible.""" |
| 296 | 326 |
| 297 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): | 327 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): |
| 298 return unit.copy() | 328 return unit.copy() |
| 299 newunit = cls(unit.source) | 329 newunit = cls(unit.source) |
| 300 newunit.target = unit.target | 330 newunit.target = unit.target |
| 301 newunit.markfuzzy(unit.isfuzzy()) | 331 newunit.markfuzzy(unit.isfuzzy()) |
| 302 locations = unit.getlocations() | 332 locations = unit.getlocations() |
| 303 if locations: | 333 if locations: |
| 304 newunit.addlocations(locations) | 334 newunit.addlocations(locations) |
| 305 notes = unit.getnotes() | 335 notes = unit.getnotes() |
| 306 if notes: | 336 if notes: |
| 307 newunit.addnote(notes) | 337 newunit.addnote(notes) |
| 308 return newunit | 338 return newunit |
| 309 buildfromunit = classmethod(buildfromunit) | 339 buildfromunit = classmethod(buildfromunit) |
| 310 | 340 |
| 311 class TranslationStore(object): | 341 class TranslationStore(object): |
| 312 """Base class for stores for multiple translation units of type UnitClass."" " | 342 """Base class for stores for multiple translation units of type UnitClass."" " |
| 313 | 343 |
| 314 UnitClass = TranslationUnit | 344 UnitClass = TranslationUnit |
| 315 Mimetypes = None | 345 Mimetypes = None |
| 316 Extensions = None | 346 Extensions = None |
| 317 | 347 |
| 318 def __init__(self, unitclass=None): | 348 def __init__(self, unitclass=None): |
| 319 """Constructs a blank TranslationStore.""" | 349 """Constructs a blank TranslationStore.""" |
| 320 | 350 |
| 321 self.units = [] | 351 self.units = [] |
| 322 self.filepath = None | 352 self.filepath = None |
| 323 self.translator = "" | 353 self.translator = "" |
| 324 self.date = "" | 354 self.date = "" |
| 325 self.sourcelanguage = None | 355 self.sourcelanguage = None |
| 326 self.targetlanguage = None | 356 self.targetlanguage = None |
| 327 if unitclass: | 357 if unitclass: |
| 328 self.UnitClass = unitclass | 358 self.UnitClass = unitclass |
| 329 super(TranslationStore, self).__init__() | 359 super(TranslationStore, self).__init__() |
| 330 | 360 |
| 331 def setsourcelanguage(self, sourcelanguage): | 361 def setsourcelanguage(self, sourcelanguage): |
| 332 """Sets the source language for this store""" | 362 """Sets the source language for this store""" |
| 333 self.sourcelanguage = sourcelanguage | 363 self.sourcelanguage = sourcelanguage |
| 334 | 364 |
| 335 def settargetlanguage(self, targetlanguage): | 365 def settargetlanguage(self, targetlanguage): |
| 336 """Sets the target language for this store""" | 366 """Sets the target language for this store""" |
| 337 self.targetlanguage = targetlanguage | 367 self.targetlanguage = targetlanguage |
| 338 | 368 |
| 339 def unit_iter(self): | 369 def unit_iter(self): |
| 340 """Iterator over all the units in this store.""" | 370 """Iterator over all the units in this store.""" |
| 341 for unit in self.units: | 371 for unit in self.units: |
| (...skipping 111 matching lines...) Show 10 above Show 10 below | |
| 453 return newstore | 483 return newstore |
| 454 parsestring = classmethod(parsestring) | 484 parsestring = classmethod(parsestring) |
| 455 | 485 |
| 456 def parse(self, data): | 486 def parse(self, data): |
| 457 """parser to process the given source string""" | 487 """parser to process the given source string""" |
| 458 self.units = pickle.loads(data).units | 488 self.units = pickle.loads(data).units |
| 459 | 489 |
| 460 def savefile(self, storefile): | 490 def savefile(self, storefile): |
| 461 """Writes the string representation to the given file (or filename).""" | 491 """Writes the string representation to the given file (or filename).""" |
| 462 if isinstance(storefile, basestring): | 492 if isinstance(storefile, basestring): |
| 463 storefile = open(storefile, "w") | 493 storefile = open(storefile, "w") |
| 464 self.fileobj = storefile | 494 self.fileobj = storefile |
| 465 self._assignname() | 495 self._assignname() |
| 466 storestring = str(self) | 496 storestring = str(self) |
| 467 storefile.write(storestring) | 497 storefile.write(storestring) |
| 468 storefile.close() | 498 storefile.close() |
| 469 | 499 |
| 470 def save(self): | 500 def save(self): |
| 471 """Save to the file that data was originally read from, if available.""" | 501 """Save to the file that data was originally read from, if available.""" |
| 472 fileobj = getattr(self, "fileobj", None) | 502 fileobj = getattr(self, "fileobj", None) |
| 473 if not fileobj: | 503 if not fileobj: |
| 474 filename = getattr(self, "filename", None) | 504 filename = getattr(self, "filename", None) |
| 475 if filename: | 505 if filename: |
| 476 fileobj = file(filename, "w") | 506 fileobj = file(filename, "w") |
| 477 else: | 507 else: |
| 478 fileobj.close() | 508 fileobj.close() |
| 479 filename = getattr(fileobj, "name", getattr(fileobj, "filename", Non e)) | 509 filename = getattr(fileobj, "name", getattr(fileobj, "filename", Non e)) |
| 480 if not filename: | 510 if not filename: |
| 481 raise ValueError("No file or filename to save to") | 511 raise ValueError("No file or filename to save to") |
| 482 fileobj = fileobj.__class__(filename, "w") | 512 fileobj = fileobj.__class__(filename, "w") |
| 483 self.savefile(fileobj) | 513 self.savefile(fileobj) |
| 484 | 514 |
| 485 def parsefile(cls, storefile): | 515 def parsefile(cls, storefile): |
| 486 """Reads the given file (or opens the given filename) and parses back to an object.""" | 516 """Reads the given file (or opens the given filename) and parses back to an object.""" |
| 487 | 517 |
| 488 if isinstance(storefile, basestring): | 518 if isinstance(storefile, basestring): |
| 489 storefile = open(storefile, "r") | 519 storefile = open(storefile, "r") |
| 490 mode = getattr(storefile, "mode", "r") | 520 mode = getattr(storefile, "mode", "r") |
| 491 #For some reason GzipFile returns 1, so we have to test for that here | 521 #For some reason GzipFile returns 1, so we have to test for that here |
| 492 if mode == 1 or "r" in mode: | 522 if mode == 1 or "r" in mode: |
| 493 storestring = storefile.read() | 523 storestring = storefile.read() |
| 494 storefile.close() | 524 storefile.close() |
| 495 else: | 525 else: |
| 496 storestring = "" | 526 storestring = "" |
| 497 newstore = cls.parsestring(storestring) | 527 newstore = cls.parsestring(storestring) |
| 498 newstore.fileobj = storefile | 528 newstore.fileobj = storefile |
| 499 newstore._assignname() | 529 newstore._assignname() |
| 500 return newstore | 530 return newstore |
| 501 parsefile = classmethod(parsefile) | 531 parsefile = classmethod(parsefile) |
| 502 | 532 |
| OLD | NEW |