2 # -*- encoding: utf-8 -*-
3 # Copyright (c) 2004, 2005, 2006 Danilo Ĺ egan <danilo@gnome.org>.
5 # This file is part of xml2po.
7 # xml2po is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # xml2po is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with xml2po; if not, write to the Free Software Foundation, Inc.,
19 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # xml2po -- translate XML documents
25 # Versioning system (I use this for a long time, so lets explain it to
26 # those Linux-versioning-scheme addicts):
27 # 1.0.* are unstable, development versions
28 # 1.1 will be first stable release (release 1), and 1.1.* bugfix releases
29 # 2.0.* will be unstable-feature-development stage (milestone 1)
30 # 2.1.* unstable development betas (milestone 2)
31 # 2.2 second stable release (release 2), and 2.2.* bugfix releases
40 class NoneTranslations
:
41 def gettext(self
, message
):
44 def lgettext(self
, message
):
47 def ngettext(self
, msgid1
, msgid2
, n
):
50 def lngettext(self
, msgid1
, msgid2
, n
):
53 def ugettext(self
, message
):
56 def ungettext(self
, msgid1
, msgid2
, n
):
62 def __init__(self
, with_translations
= 0):
68 self
.translations
= []
69 self
.do_translations
= with_translations
70 self
.output_msgstr
= 0 # this is msgid mode for outputMessage; 1 is for msgstr mode
72 def translationsFollow(self
):
73 """Indicate that what follows are translations."""
74 self
.output_msgstr
= 1
76 def setFilename(self
, filename
):
77 self
.filename
= filename
79 def outputMessage(self
, text
, lineno
= 0, comment
= None, spacepreserve
= 0, tag
= None):
80 """Adds a string to the list of messages."""
81 if (text
.strip() != ''):
82 t
= escapePoString(normalizeString(text
, not spacepreserve
))
83 if self
.output_msgstr
:
84 self
.translations
.append(t
)
87 if self
.do_translations
or (not t
in self
.messages
):
88 self
.messages
.append(t
)
91 if t
in self
.linenos
.keys():
92 self
.linenos
[t
].append((self
.filename
, tag
, lineno
))
94 self
.linenos
[t
] = [ (self
.filename
, tag
, lineno
) ]
95 if (not self
.do_translations
) and comment
and not t
in self
.comments
:
96 self
.comments
[t
] = comment
98 if t
in self
.linenos
.keys():
99 self
.linenos
[t
].append((self
.filename
, tag
, lineno
))
101 self
.linenos
[t
] = [ (self
.filename
, tag
, lineno
) ]
102 if comment
and not t
in self
.comments
:
103 self
.comments
[t
] = comment
105 def outputHeader(self
, out
):
106 from time
import gmtime
, strftime
107 out
.write("""msgid ""
109 "Project-Id-Version: PACKAGE VERSION\\n"
110 "POT-Creation-Date: %s\\n"
111 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
112 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
113 "Language-Team: LANGUAGE <LL@li.org>\\n"
114 "MIME-Version: 1.0\\n"
115 "Content-Type: text/plain; charset=UTF-8\\n"
116 "Content-Transfer-Encoding: 8bit\\n"
118 """ % (strftime("%Y-%m-%d %H:%M +0000", gmtime())))
120 def outputAll(self
, out
):
121 self
.outputHeader(out
)
123 for k
in self
.messages
:
124 if k
in self
.comments
:
125 out
.write("#. %s\n" % (self
.comments
[k
].replace("\n","\n#. ")))
129 for reference
in self
.linenos
[k
]:
130 references
+= "%s:%d " % (reference
[0], reference
[2])
131 if(reference
[1] not in tags
):
132 tags
.append(reference
[1])
133 tagstr
+= "(" + str(reference
[1]) + "), "
134 out
.write("#.%s\n" % (tagstr
[0 : len(tagstr
) - 2]))
135 out
.write("#: %s\n" % (references
[0 : len(references
) - 1]))
136 if k
in self
.nowrap
and self
.nowrap
[k
]:
137 out
.write("#, no-wrap\n")
138 out
.write("msgid \"%s\"\n" % (k
))
140 if self
.do_translations
:
141 if len(self
.translations
)>0:
142 translation
= self
.translations
.pop(0)
145 out
.write("msgstr \"%s\"\n\n" % (translation
))
148 def normalizeNode(node
):
149 #print >>sys.stderr, "<%s> (%s) [%s]" % (node.name, node.type, node.serialize('utf-8'))
152 elif isSpacePreserveNode(node
):
155 if node
.isBlankNode():
156 if expand_entities
or ( not (node
.prev
and not node
.prev
.isBlankNode()
157 and node
.next
and not node
.next
.isBlankNode()) ):
158 #print >>sys.stderr, "BLANK"
161 node
.setContent(re
.sub('\s+',' ', node
.content
))
163 elif node
.children
and node
.type == 'element':
164 child
= node
.children
169 def normalizeString(text
, ignorewhitespace
= 1):
170 """Normalizes string to be used as key for gettext lookup.
172 Removes all unnecessary whitespace."""
173 if not ignorewhitespace
:
176 # Lets add document DTD so entities are resolved
177 dtd
= doc
.intSubset()
178 tmp
= dtd
.serialize('utf-8')
179 tmp
= tmp
+ '<norm>%s</norm>' % text
181 tmp
= '<norm>%s</norm>' % text
184 ctxt
= libxml2
.createDocParserCtxt(tmp
)
186 ctxt
.replaceEntities(1)
189 newnode
= tree
.getRootElement()
191 print >> sys
.stderr
, """Error while normalizing string as XML:\n"%s"\n""" % (text
)
194 normalizeNode(newnode
)
197 child
= newnode
.children
199 result
+= child
.serialize('utf-8')
202 result
= re
.sub('^ ','', result
)
203 result
= re
.sub(' $','', result
)
207 def stringForEntity(node
):
208 """Replaces entities in the node."""
209 text
= node
.serialize('utf-8')
211 # Lets add document DTD so entities are resolved
212 dtd
= node
.doc
.intSubset()
213 tmp
= dtd
.serialize('utf-8') + '<norm>%s</norm>' % text
216 tmp
= '<norm>%s</norm>' % text
219 ctxt
= libxml2
.createDocParserCtxt(tmp
)
221 ctxt
.replaceEntities(1)
225 newnode
= tree
.children
.next
227 newnode
= tree
.children
230 child
= newnode
.children
232 result
+= child
.serialize('utf-8')
238 def escapePoString(text
):
239 return text
.replace('\\','\\\\').replace('"', "\\\"").replace("\n","\\n").replace("\t","\\t")
241 def unEscapePoString(text
):
242 return text
.replace('\\"', '"').replace('\\\\','\\')
244 def getTranslation(text
, spacepreserve
= 0):
245 """Returns a translation via gettext for specified snippet.
247 text should be a string to look for, spacepreserve set to 1
248 when spaces should be preserved.
250 #print >>sys.stderr,"getTranslation('%s')" % (text.encode('utf-8'))
251 text
= normalizeString(text
, not spacepreserve
)
252 if (text
.strip() == ''):
256 res
= gt
.ugettext(text
.decode('utf-8'))
261 def myAttributeSerialize(node
):
264 child
= node
.children
266 if child
.type=='text':
267 result
+= doc
.encodeEntitiesReentrant(child
.content
)
268 elif child
.type=='entity_ref':
269 if not expand_entities
:
270 result
+= '&' + child
.name
+ ';'
272 result
+= child
.content
.decode('utf-8')
274 result
+= myAttributeSerialize(child
)
277 result
= node
.serialize('utf-8')
280 def startTagForNode(node
):
287 for p
in node
.properties
:
288 if p
.type == 'attribute':
290 nsprop
= p
.ns().name
+ ":" + p
.name
293 params
+= " %s=\"%s\"" % (nsprop
, myAttributeSerialize(p
))
296 def endTagForNode(node
):
303 def isFinalNode(node
):
305 auto
= autoNodeIsFinal(node
)
306 # Check if any of the parents is also autoNodeIsFinal,
307 # and if it is, don't consider this node a final one
309 while parent
and auto
:
310 auto
= not autoNodeIsFinal(parent
)
311 parent
= parent
.parent
313 #node.type =='text' or not node.children or
314 if node
.type == 'element' and node
.name
in ultimate_tags
:
318 child
= node
.children
319 while child
and final_children
:
320 if not child
.isBlankNode() and child
.type != 'comment' and not isFinalNode(child
):
327 def ignoreNode(node
):
329 if node
.type in ('dtd', 'comment'):
334 if isFinalNode(node
):
336 if node
.name
in ignored_tags
or node
.type in ('dtd', 'comment'):
340 def isSpacePreserveNode(node
):
341 pres
= node
.getSpacePreserve()
345 if CurrentXmlMode
and (node
.name
in CurrentXmlMode
.getSpacePreserveTags()):
350 def getCommentForNode(node
):
351 """Walk through previous siblings until a comment is found, or other element.
353 Only whitespace is allowed between comment and current node."""
355 while prev
and prev
.type == 'text' and prev
.content
.strip() == '':
357 if prev
and prev
.type == 'comment':
358 return prev
.content
.strip()
362 def replaceAttributeContentsWithText(node
,text
):
363 node
.setContent(text
)
365 def replaceNodeContentsWithText(node
,text
):
366 """Replaces all subnodes of a node with contents of text treated as XML."""
369 starttag
= startTagForNode(node
)
370 endtag
= endTagForNode(node
)
372 # Lets add document DTD so entities are resolved
373 tmp
= '<?xml version="1.0" encoding="utf-8" ?>'
375 dtd
= doc
.intSubset()
376 tmp
= tmp
+ dtd
.serialize('utf-8')
377 except libxml2
.treeError
:
380 content
= '<%s>%s</%s>' % (starttag
, text
, endtag
)
381 tmp
= tmp
+ content
.encode('utf-8')
385 ctxt
= libxml2
.createDocParserCtxt(tmp
)
386 ctxt
.replaceEntities(0)
393 print >> sys
.stderr
, """Error while parsing translation as XML:\n"%s"\n""" % (text
.encode('utf-8'))
396 newelem
= newnode
.getRootElement()
398 if newelem
and newelem
.children
:
406 copy
= newelem
.copyNodeList()
408 node
.replaceNode(newelem
.copyNodeList())
412 # In practice, this happens with tags such as "<para> </para>" (only whitespace in between)
415 node
.setContent(text
)
417 def autoNodeIsFinal(node
):
418 """Returns 1 if node is text node, contains non-whitespace text nodes or entities."""
419 if hasattr(node
, '__autofinal__'):
420 return node
.__autofinal
__
421 if node
.name
in ignored_tags
:
422 node
.__autofinal
__ = 0
424 if node
.isText() and node
.content
.strip()!='':
425 node
.__autofinal
__ = 1
428 child
= node
.children
430 if child
.type in ['text'] and child
.content
.strip()!='':
435 node
.__autofinal
__ = final
439 def worthOutputting(node
, noauto
= 0):
440 """Returns 1 if node is "worth outputting", otherwise 0.
442 Node is "worth outputting", if none of the parents
443 isFinalNode, and it contains non-blank text and entities.
445 if noauto
and hasattr(node
, '__worth__'):
446 return node
.__worth
__
447 elif not noauto
and hasattr(node
, '__autoworth__'):
448 return node
.__autoworth
__
451 final
= isFinalNode(node
) and node
.name
not in ignored_tags
452 while not final
and parent
:
453 if isFinalNode(parent
):
454 final
= 1 # reset if we've got to one final tag
455 if final
and (parent
.name
not in ignored_tags
) and worthOutputting(parent
):
458 parent
= parent
.parent
464 node
.__worth
__ = worth
467 node
.__autoworth
__ = autoNodeIsFinal(node
)
468 return node
.__autoworth
__
470 def processAttribute(node
, attr
):
471 if not node
or not attr
or not worthOutputting(node
=node
, noauto
=1):
474 outtxt
= attr
.content
476 translation
= getTranslation(outtxt
, 0)
477 replaceAttributeContentsWithText(attr
, translation
.encode('utf-8'))
479 msg
.outputMessage(outtxt
, node
.lineNo(), "", 0,
480 node
.name
+ ":" + attr
.name
)
482 def processElementTag(node
, replacements
, restart
= 0):
483 """Process node with node.type == 'element'."""
484 if node
.type == 'element':
485 # Translate attributes if needed
486 if node
.properties
and len(treated_attributes
):
487 for p
in node
.properties
:
488 if p
.name
in treated_attributes
:
489 processAttribute(node
, p
)
495 myrepl
= replacements
499 child
= node
.children
501 if (isFinalNode(child
)) or (child
.type == 'element' and worthOutputting(child
)):
502 myrepl
.append(processElementTag(child
, myrepl
, 1))
503 outtxt
+= '<placeholder-%d/>' % (len(myrepl
))
505 if child
.type == 'element':
506 (starttag
, content
, endtag
, translation
) = processElementTag(child
, myrepl
, 0)
507 outtxt
+= '<%s>%s</%s>' % (starttag
, content
, endtag
)
509 outtxt
+= doSerialize(child
)
514 translation
= getTranslation(outtxt
, isSpacePreserveNode(node
))
516 translation
= outtxt
.decode('utf-8')
518 starttag
= startTagForNode(node
)
519 endtag
= endTagForNode(node
)
521 worth
= worthOutputting(node
)
523 translation
= outtxt
.decode('utf-8')
524 if worth
and mark_untranslated
: node
.setLang('C')
528 while i
< len(myrepl
):
529 replacement
= '<%s>%s</%s>' % (myrepl
[i
][0], myrepl
[i
][3], myrepl
[i
][2])
531 translation
= translation
.replace('<placeholder-%d/>' % (i
), replacement
)
535 replaceNodeContentsWithText(node
, translation
)
537 msg
.outputMessage(outtxt
, node
.lineNo(), getCommentForNode(node
), isSpacePreserveNode(node
), tag
= node
.name
)
539 return (starttag
, outtxt
, endtag
, translation
)
541 raise Exception("You must pass node with node.type=='element'.")
544 def isExternalGeneralParsedEntity(node
):
545 if (node
and node
.type=='entity_ref'):
547 # it would be nice if debugDumpNode could use StringIO, but it apparently cannot
548 tmp
= file(".xml2po-entitychecking","w+")
549 node
.debugDumpNode(tmp
,0)
553 os
.remove(".xml2po-entitychecking")
555 # We fail silently, and replace all entities if we cannot
556 # write .xml2po-entitychecking
557 # !!! This is not very nice thing to do, but I don't know if
558 # raising an exception is any better
560 if tmpstr
.find('EXTERNAL_GENERAL_PARSED_ENTITY') != -1:
567 def doSerialize(node
):
568 """Serializes a node and its children, emitting PO messages along the way.
570 node is the node to serialize, first indicates whether surrounding
571 tags should be emitted as well.
576 elif not node
.children
:
577 return node
.serialize("utf-8")
578 elif node
.type == 'entity_ref':
579 if isExternalGeneralParsedEntity(node
):
580 return node
.serialize('utf-8')
582 return stringForEntity(node
) #content #content #serialize("utf-8")
583 elif node
.type == 'entity_decl':
584 return node
.serialize('utf-8') #'<%s>%s</%s>' % (startTagForNode(node), node.content, node.name)
585 elif node
.type == 'text':
586 return node
.serialize('utf-8')
587 elif node
.type == 'element':
589 (starttag
, content
, endtag
, translation
) = processElementTag(node
, repl
, 1)
590 return '<%s>%s</%s>' % (starttag
, content
, endtag
)
592 child
= node
.children
595 outtxt
+= doSerialize(child
)
600 def read_finaltags(filelist
):
602 return CurrentXmlMode
.getFinalTags()
604 defaults
= ['para', 'title', 'releaseinfo', 'revnumber',
605 'date', 'itemizedlist', 'orderedlist',
606 'variablelist', 'varlistentry', 'term' ]
609 def read_ignoredtags(filelist
):
611 return CurrentXmlMode
.getIgnoredTags()
613 defaults
= ['itemizedlist', 'orderedlist', 'variablelist',
617 def read_treatedattributes(filelist
):
619 return CurrentXmlMode
.getTreatedAttributes()
624 def tryToUpdate(allargs
, lang
):
625 # Remove "-u" and "--update-translation"
626 print >>sys
.stderr
, "OVDI!"
629 opts
, args
= getopt
.getopt(args
, 'avhm:ket:o:p:u:',
630 ['automatic-tags','version', 'help', 'keep-entities', 'extract-all-entities', 'merge', 'translation=',
631 'output=', 'po-file=', 'update-translation=' ])
632 for opt
, arg
in opts
:
633 if opt
in ('-a', '--automatic-tags'):
635 elif opt
in ('-k', '--keep-entities'):
637 elif opt
in ('-e', '--extract-all-entities'):
639 elif opt
in ('-m', '--mode'):
640 command
+= " -m %s" % arg
641 elif opt
in ('-o', '--output'):
642 sys
.stderr
.write("Error: Option '-o' is not yet supported when updating translations directly.\n")
644 elif opt
in ('-v', '--version'):
647 elif opt
in ('-h', '--help'):
648 sys
.stderr
.write("Error: If you want help, please use `%s --help' without '-u' option.\n" % (allargs
[0]))
650 elif opt
in ('-u', '--update-translation'):
653 sys
.stderr
.write("Error: Option `%s' is not supported with option `-u'.\n" % (opt
))
657 command
+= " " + args
.pop()
661 sys
.stderr
.write("Merging translations for %s: " % (lang
))
662 result
= os
.system("%s | msgmerge -o .tmp.%s.po %s -" % (command
, lang
, file))
666 result
= os
.system("mv .tmp.%s.po %s" % (lang
, file))
668 sys
.stderr
.write("Error: cannot rename file.\n")
671 os
.system("msgfmt -cv -o %s %s" % (NULL_STRING
, file))
674 def load_mode(modename
):
676 #found = imp.find_module(modename, submodes_path)
677 #module = imp.load_module(modename, found[0], found[1], found[2])
679 sys
.path
.append(submodes_path
)
680 module
= __import__(modename
)
681 modeModule
= '%sXmlMode' % modename
682 return getattr(module
, modeModule
)
686 def xml_error_handler(arg
, ctxt
):
689 libxml2
.registerErrorHandler(xml_error_handler
, None)
693 if __name__
!= '__main__': raise NotImplementedError
696 submodes_path
= os
.path
.dirname(os
.path
.realpath(sys
.argv
[0])) + "/xml2po-modes"
697 default_mode
= 'docbook'
706 translationlanguage
= ''
708 mode
= 'pot' # 'pot' or 'merge'
711 mark_untranslated
= 0
712 expand_all_entities
= 0
714 output
= '-' # this means to stdout
716 NULL_STRING
= '/dev/null'
717 if not os
.path
.exists('/dev/null'): NULL_STRING
= 'NUL'
719 import getopt
, fileinput
721 def usage (with_help
= False):
722 print >> sys
.stderr
, "Usage: %s [OPTIONS] [XMLFILE]..." % (sys
.argv
[0])
724 print >> sys
.stderr
, """
725 OPTIONS may be some of:
726 -a --automatic-tags Automatically decides if tags are to be considered
728 -k --keep-entities Don't expand entities
729 -e --expand-all-entities Expand ALL entities (including SYSTEM ones)
730 -m --mode=TYPE Treat tags as type TYPE (default: docbook)
731 -o --output=FILE Print resulting text (XML or POT) to FILE
732 -p --po-file=FILE Specify PO file containing translation, and merge
733 Overwrites temporary file .xml2po.mo.
734 -r --reuse=FILE Specify translated XML file with the same structure
735 -t --translation=FILE Specify MO file containing translation, and merge
736 -u --update-translation=LANG.po Updates a PO file using msgmerge program
738 -l --language=LANG Set language of the translation to LANG
739 --mark-untranslated Set 'xml:lang="C"' on untranslated tags
741 -v --version Output version of the xml2po program
743 -h --help Output this message
746 To create a POTemplate book.pot from input files chapter1.xml and
747 chapter2.xml, run the following:
748 %s -o book.pot chapter1.xml chapter2.xml
750 After translating book.pot into de.po, merge the translations back,
751 using -p option for each XML file:
752 %s -p de.po chapter1.xml > chapter1.de.xml
753 %s -p de.po chapter2.xml > chapter2.de.xml
754 """ % (sys
.argv
[0], sys
.argv
[0], sys
.argv
[0])
757 if len(sys
.argv
) < 2: usage()
760 try: opts
, args
= getopt
.getopt(args
, 'avhkem:t:o:p:u:r:l:',
761 ['automatic-tags','version', 'help', 'keep-entities', 'expand-all-entities', 'mode=', 'translation=',
762 'output=', 'po-file=', 'update-translation=', 'reuse=', 'language=', 'mark-untranslated' ])
763 except getopt
.GetoptError
: usage(True)
765 for opt
, arg
in opts
:
766 if opt
in ('-m', '--mode'):
768 if opt
in ('-a', '--automatic-tags'):
770 elif opt
in ('-k', '--keep-entities'):
772 elif opt
in ('--mark-untranslated',):
773 mark_untranslated
= 1
774 elif opt
in ('-e', '--expand-all-entities'):
775 expand_all_entities
= 1
776 elif opt
in ('-l', '--language'):
777 translationlanguage
= arg
778 elif opt
in ('-t', '--translation'):
781 if translationlanguage
== '': translationlanguage
= os
.path
.split(os
.path
.splitext(mofile
)[0])[1]
782 elif opt
in ('-r', '--reuse'):
784 elif opt
in ('-u', '--update-translation'):
785 tryToUpdate(sys
.argv
, arg
)
786 elif opt
in ('-p', '--po-file'):
787 mofile
= ".xml2po.mo"
789 if translationlanguage
== '': translationlanguage
= os
.path
.split(os
.path
.splitext(pofile
)[0])[1]
790 os
.system("msgfmt -o %s %s >%s" % (mofile
, pofile
, NULL_STRING
)) and sys
.exit(7)
792 elif opt
in ('-o', '--output'):
794 elif opt
in ('-v', '--version'):
797 elif opt
in ('-h', '--help'):
800 # Treat remaining arguments as XML files
802 filenames
.append(args
.pop())
804 if len(filenames
) > 1 and mode
=='merge':
805 print >> sys
.stderr
, "Error: You can merge translations with only one XML file at a time."
809 CurrentXmlMode
= load_mode(default_mode
)()
811 CurrentXmlMode
= None
812 print >> sys
.stderr
, "Warning: cannot load module '%s', using automatic detection (-a)." % (default_mode
)
815 if mode
=='merge' and mofile
=='':
816 print >> sys
.stderr
, "Error: You must specify MO file when merging translations."
821 mfile
= open(mofile
, "rb")
823 gt
= gettext
.GNUTranslations(mfile
)
824 gt
.add_fallback(NoneTranslations())
826 print >> sys
.stderr
, "Can't open MO file '%s'." % (mofile
)
828 ultimate_tags
= read_finaltags(ultimate
)
829 ignored_tags
= read_ignoredtags(ignored
)
830 treated_attributes
= read_treatedattributes(ignored
)
832 # I'm not particularly happy about making any of these global,
833 # but I don't want to bother too much with it right now
837 msg
= MessageOutput()
839 filenames
.append(origxml
)
840 msg
= MessageOutput(1)
842 for filename
in filenames
:
844 if filename
== origxml
:
845 msg
.translationsFollow()
846 ctxt
= libxml2
.createFileParserCtxt(filename
)
848 if expand_all_entities
:
849 ctxt
.replaceEntities(1)
852 if doc
.name
!= filename
:
853 print >> sys
.stderr
, "Error: I tried to open '%s' but got '%s' -- how did that happen?" % (filename
, doc
.name
)
856 print >> sys
.stderr
, "Error: cannot open file '%s'." % (filename
)
859 msg
.setFilename(filename
)
860 if CurrentXmlMode
and origxml
=='':
861 CurrentXmlMode
.preProcessXml(doc
,msg
)
868 out
= file(output
, 'w')
870 print >> sys
.stderr
, "Error: cannot open file %s for writing." % (output
)
875 tcmsg
= CurrentXmlMode
.getStringForTranslators()
876 tccom
= CurrentXmlMode
.getCommentForTranslators()
878 msg
.outputMessage(tcmsg
, 0, tccom
)
883 tcmsg
= CurrentXmlMode
.getStringForTranslators()
885 outtxt
= getTranslation(tcmsg
)
888 CurrentXmlMode
.postProcessXmlTranslation(doc
, translationlanguage
, outtxt
)
889 out
.write(doc
.serialize('utf-8', 1))