changeset 686:f2debe9be4e0

more i18n cleanup
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 22 May 2006 00:44:12 +0200
parents 406590899a01
children 80683dac4937
files MoinMoin/i18n/Makefile MoinMoin/i18n/__init__.py MoinMoin/i18n/check_i18n.py MoinMoin/i18n/mail_i18n-maintainers.py MoinMoin/i18n/mail_i18n-maintainers.txt MoinMoin/i18n/mail_i18n-maintainers2.txt MoinMoin/i18n/po2wiki.py MoinMoin/i18n/prepend.py MoinMoin/i18n/recode.py MoinMoin/i18n/tools/check_i18n.py MoinMoin/i18n/tools/mail_i18n-maintainers.py MoinMoin/i18n/tools/mail_i18n-maintainers.txt MoinMoin/i18n/tools/mail_i18n-maintainers2.txt MoinMoin/i18n/tools/po2wiki.py MoinMoin/i18n/tools/prepend.py MoinMoin/i18n/tools/recode.py MoinMoin/i18n/tools/wiki2po.py MoinMoin/i18n/wiki2po.py setup.py
diffstat 18 files changed, 742 insertions(+), 741 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/i18n/Makefile	Mon May 22 00:31:35 2006 +0200
+++ b/MoinMoin/i18n/Makefile	Mon May 22 00:44:12 2006 +0200
@@ -19,13 +19,13 @@
 .nop.po-update:
 	@lang=`echo $@ | sed -e 's/\.po-update$$//'`; \
 	echo "$$lang:"; \
-	./wiki2po.py $${lang}; \
+	tools/wiki2po.py $${lang}; \
 	echo "msgmerge $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \
 	if msgmerge $$lang.po $(DOMAIN).pot -o $$lang.new.po; then \
 	  if cmp $$lang.po $$lang.new.po >/dev/null 2>&1; then \
 	    rm -f $$lang.new.po; \
 	  else \
-	    echo XXX ./po2wiki.py $$lang <$$lang.new.po; \
+	    echo XXX tools/po2wiki.py $$lang <$$lang.new.po; \
 	    rm -f $$lang.new.po; \
 	  fi; \
 	else \
@@ -60,10 +60,10 @@
 
 $(POFILES):
 	@lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
-	./wiki2po.py $${lang}; \
+	tools/wiki2po.py $${lang}; \
 	echo msgmerge $${lang}.po $(DOMAIN).pot -o $${lang}.new.po; \
 	msgmerge $${lang}.po $(DOMAIN).pot -o $${lang}.new.po; \
-	echo XXX ./po2wiki.py $${lang} <$$lang.new.po; \
+	echo XXX tools/po2wiki.py $${lang} <$$lang.new.po; \
 	rm -f $$lang.new.po
 	
 
--- a/MoinMoin/i18n/check_i18n.py	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,376 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-"""check_i18n - compare texts in the source with the language files
-
-Searches in the MoinMoin sources for calls of _() and tries to extract
-the parameter.  Then it checks the language modules if those parameters
-are in the dictionary.
-
-Usage: check_i18n.py [lang ...]
-
-Without arguments, checks all languages in i18n or the specified
-languages. Look into MoinMoin.i18n.__init__ for availeable language
-names.
-
-The script will run from the moin root directory, where the MoinMoin
-package lives, or from MoinMoin/i18n where this script lives.
-
-TextFinder class based on code by Seo Sanghyeon and the python compiler
-package.
-
-TODO: fix it for the changed i18n stuff of moin 1.6
-
-@copyright: 2003 Florian Festi, Nir Soffer, Thomas Waldmann
-@license: GNU GPL, see COPYING for details.
-"""
-
-output_encoding = 'utf-8'
-
-# These lead to crashes (MemoryError - due to missing codecs?)
-#blacklist_files = ["ja.py", "zh.py", "zh_tw.py"]
-#blacklist_langs = ["ja", "zh", "zh-tw"]
-
-# If you have cjkcodecs installed, use this:
-blacklist_files = []
-blacklist_langs = []
-
-import sys, os, compiler
-from compiler.ast import Name, Const, CallFunc, Getattr
-
-class TextFinder:
-    """ Walk through AST tree and collect text from gettext calls
-        
-    Find all calls to gettext function in the source tree and collect
-    the texts in a dict. Use compiler to create an abstract syntax tree
-    from each source file, then find the nodes for gettext function
-    call, and get the text from the call.
-
-    Localized texts are used usually translated during runtime by
-    gettext functions and apear in the source as
-    _('text...'). TextFinder class finds calls to the '_' function in
-    any namespace, or your prefered gettext function.
-
-    Note that TextFinder will only retrieve text from function calls
-    with a constant argument like _('text'). Calls like _('text' % locals()),
-    _('text 1' + 'text 2') are marked as bad call in the report, and the 
-    text is not retrieved into the dictionary.
-
-    Note also that texts in source can appear several times in the same
-    file or different files, but they will only apear once in the
-    dictionary that this tool creates.
-
-    The dictionary value for each text is a dictionary of filenames each
-    containing a list of (best guess) lines numbers containning the text.
-    """
-    
-    def __init__(self, name='_'):
-        """ Init with the gettext function name or '_'"""
-        self._name = name       # getText function name
-        self._dictionary = {}   # Unique texts in the found texts
-        self._found = 0         # All good calls including duplicates
-        self._bad = 0           # Bad calls: _('%s' % var) or _('a' + 'b')
-    
-    def setFilename(self, filename):
-        """Remember the filename we are parsing"""
-        self._filename = filename 
-        
-    def visitModule(self, node):
-        """ Start the search from the top node of a module
-
-        This is the entry point into the search. When compiler.walk is
-        called it calls this method with the module node.
-
-        This is the place to initialize module specific data.
-        """
-        self._visited = {}  # init node cache - we will visit each node once
-        self._lineno = 'NA' # init line number  
-
-        # Start walking in the module node
-        self.walk(node)
-
-    def walk(self, node):
-        """ Walk through all nodes """
-        if self._visited.has_key(node):
-            # We visited this node already
-            return
-            
-        self._visited[node] = 1            
-        if not self.parseNode(node):           
-            for child in node.getChildNodes():
-                self.walk(child)
-
-    def parseNode(self, node):
-        """ Parse function call nodes and collect text """
-
-        # Get the current line number. Since not all nodes have a line number
-        # we save the last line number - it should be close to the gettext call
-        if node.lineno != None:
-            self._lineno = node.lineno
-
-        if node.__class__ == CallFunc and node.args: 
-            child = node.node
-            klass = child.__class__            
-            if (# Standard call _('text')
-                (klass == Name and child.name == self._name) or
-                # A call to an object attribute: object._('text') 
-                (klass == Getattr and child.attrname == self._name)):
-                if node.args[0].__class__ == Const:
-                    # Good call with a constant _('text')
-                    self.addText(node.args[0].value)
-                else:
-                    self.addBadCall(node)
-                return 1
-        return 0
-            
-    def addText(self, text):
-        """ Add text to dictionary and count found texts.
-        
-        Note that number of texts in dictionary could be different from
-        the number of texts found, because some texts appear several
-        times in the code.
-
-        Each text value is a dictionary of filenames that contain the
-        text and each filename value is the list of line numbers with
-        the text. Missing line numbers are recorded as 'NA'.
-
-        self._lineno is the last line number we checked. It may be the line
-        number of the text, or near it.
-        """
-        
-        self._found = self._found + 1
-
-        # Create key for this text if needed
-        if not self._dictionary.has_key(text):
-            self._dictionary[text] = {}
-
-        # Create key for this filename if needed   
-        textInfo = self._dictionary[text]
-        if not textInfo.has_key(self._filename):
-            textInfo[self._filename] = [self._lineno]
-        else:
-            textInfo[self._filename].append(self._lineno)
-
-    def addBadCall(self, node):
-        """Called when a bad call like _('a' + 'b') is found"""
-        self._bad = self._bad + 1
-        print
-        print "<!> Warning: non-constant _ call:"
-        print " `%s`" % str(node)
-        print " `%s`:%s" % (self._filename, self._lineno)
-        
-    # Accessors
-    
-    def dictionary(self):
-        return self._dictionary
-            
-    def bad(self):
-        return self._bad
-        
-    def found(self):
-        return self._found
-        
-
-def visit(path, visitor):
-    visitor.setFilename(path)
-    tree = compiler.parseFile(path)
-    compiler.walk(tree, visitor)
-
-
-# MoinMoin specific stuff follows
-
-
-class Report:
-    """Language status report"""
-    def __init__(self, lang, sourceDict):
-        self.__lang = lang
-        self.__sourceDict = sourceDict
-        self.__langDict = None
-        self.__missing = {}
-        self.__unused = {}
-        self.__error = None
-        self.__ready = 0
-        self.create()
-
-    def loadLanguage(self):
-        filename = i18n.filename(self.__lang)
-        self.__langDict = pysupport.importName("MoinMoin.i18n." + filename, "text")
-
-    def create(self):
-        """Compare language text dict against source dict"""
-        self.loadLanguage()
-        if not self.__langDict:
-            self.__error = "Language %s not found!" % self.__lang
-            self.__ready = 1
-            return
-
-        # Collect missing texts
-        for text in self.__sourceDict:
-            if not self.__langDict.has_key(text):
-                self.__missing[text] = self.__sourceDict[text]
-
-        # Collect unused texts
-        for text in self.__langDict:
-            if not self.__sourceDict.has_key(text):
-                self.__unused[text] = self.__langDict[text]
-        self.__ready = 1
-        
-    def summary(self):
-        """Return summary dict"""
-        summary = {
-            'name': i18n.languages[self.__lang][i18n.ENAME].encode(output_encoding),
-            'maintainer': i18n.languages[self.__lang][i18n.MAINTAINER],
-            'total' : len(self.__langDict),
-            'missing': len(self.__missing),
-            'unused': len(self.__unused),
-            'error': self.__error
-            }
-        return summary
-        
-    def missing(self):
-        return self.__missing
-
-    def unused(self):
-        return self.__unused
-    
-        
-
-if __name__ == '__main__':
-
-    import time
-    
-    # Check that we run from the root directory where MoinMoin package lives
-    # or from the i18n directory when this script lives
-    if os.path.exists('MoinMoin/__init__.py'):
-        # Running from the root directory
-        MoinMoin_dir = os.curdir   
-    elif os.path.exists(os.path.join(os.pardir, 'i18n')):
-        # Runing from i18n
-        MoinMoin_dir = os.path.join(os.pardir, os.pardir)
-    else:
-        print __doc__
-        sys.exit(1)
-
-    # Insert MoinMoin_dir into sys.path
-    sys.path.insert(0, MoinMoin_dir)
-    from MoinMoin import i18n
-    from MoinMoin.util import pysupport
-    
-    textFinder = TextFinder()
-    found = 0
-    unique = 0   
-    bad = 0
-
-    # Find gettext calls in the source
-    for root, dirs, files in os.walk(os.path.join(MoinMoin_dir, 'MoinMoin')):
-        for name in files:
-            if name.endswith('.py'):
-                if name in blacklist_files: continue
-                path = os.path.join(root, name)
-                #print '%(path)s:' % locals(),
-                visit(path, textFinder)
-                
-                # Report each file's results
-                new_unique = len(textFinder.dictionary()) - unique
-                new_found = textFinder.found() - found
-                #print '%(new_unique)d (of %(new_found)d)' % locals()
-                                
-                # Warn about bad calls - these should be fixed!
-                new_bad = textFinder.bad() - bad
-                #if new_bad:
-                #    print '### Warning: %(new_bad)d bad call(s)' % locals()
-                
-                unique = unique + new_unique
-                bad = bad + new_bad
-                found = found + new_found
-
-    # Print report using wiki markup, so we can publish this on MoinDev
-    # !!! Todo:
-    #     save executive summary for the wiki
-    #     save separate report for each language to be sent to the
-    #     language translator.
-    #     Update the wiki using XML-RPC??
-
-    print "This page is generated by `MoinMoin/i18n/check_i18n.py`."
-    print "To recreate this report run `make check-i18n` and paste here"
-    print
-    print '----'
-    print 
-    print '[[TableOfContents(2)]]'
-    print
-    print
-    print "= Translation Report ="
-    print
-    print "== Summary =="
-    print
-    print 'Created on %s' % time.asctime()
-    print
-
-    print ('\n%(unique)d unique texts in dictionary of %(found)d texts '
-           'in source.') % locals()
-    if bad:
-        print '\n%(bad)d bad calls.' % locals()
-    print
-    
-    # Check languages from the command line or from moin.i18n against
-    # the source
-    if sys.argv[1:]:
-        languages = sys.argv[1:]
-    else:
-        languages = i18n.languages.keys()
-        for lang in blacklist_langs: 
-            # problems, maybe due to encoding?
-            if lang in languages:
-                languages.remove(lang)
-    if 'en' in languages:
-        languages.remove('en') # there is no en lang file
-    languages.sort()
-    
-    # Create report for all languages
-    report = {}
-    for lang in languages:
-        report[lang] = Report(lang, textFinder.dictionary())
-
-    # Print summary for all languages
-    print ("||<:>'''Language'''||<:>'''Texts'''||<:>'''Missing'''"
-           "||<:>'''Unused'''||")
-    for lang in languages:
-        print ("||%(name)s||<)>%(total)s||<)>%(missing)s||<)>%(unused)s||"
-               ) % report[lang].summary() 
-
-    # Print details
-    for lang in languages:
-        dict = report[lang].summary()
-        print
-        print "== %(name)s ==" % dict
-        print
-        print "Maintainer: [[MailTo(%(maintainer)s)]]" % dict
-
-        # Print missing texts, if any
-        if report[lang].missing():
-            print """
-=== Missing texts ===
-
-These items should ''definitely'' get fixed.
-
-Maybe the corresponding english text in the source code was only changed
-slightly, then you want to look for a similar text in the ''unused''
-section below and modify i18n, so that it will match again.
-"""
-            for text in report[lang].missing():
-                print " 1. `%r`" % text
-                
-        # Print unused texts, if any
-        if report[lang].unused():
-            print """
-=== Possibly unused texts ===
-
-Be ''very careful'' and double-check before removing any of these 
-potentially unused items.
-
-This program can't detect references done from wiki pages, from
-UserPreferences options, from Icon titles etc.!
-"""        
-            for text in report[lang].unused():
-                print " 1. `%r`" % text
-
-
--- a/MoinMoin/i18n/mail_i18n-maintainers.py	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-#!/usr/bin/env python
-"""
-    read mail text from standard input and 
-    send an email to all i18n maintainers
-    %(lang)s will be replaced by language
-    
-    @copyright: 2004 Thomas Waldmann
-    @license: GNU GPL, see COPYING for details
-"""
-
-mail_from = 'tw-public@gmx.de'
-mail_subject = 'MoinMoin i18n notification'
-
-mail_smarthost = 'localhost'
-mail_login = None
-charset = 'iso-8859-1'
-
-from meta import languages
-
-def sendmail(mfrom, mto, subject, text):
-    """
-    Send a mail to the address(es) in 'to', with the given subject and
-    mail body 'text'.
-    
-    Return a tuple of success or error indicator and message.
-
-    TODO: code duplicated from MoinMoin/util/mail.py
-    
-    @param mfrom: source email address
-    @param to: target email address
-    @param subject: subject of email
-    @param text: email body text
-    @rtype: tuple
-    @return: (is_ok, msg)
-    """
-    import smtplib, socket, os
-    from email.MIMEText import MIMEText
-    from email.Header import Header
-    from email.Utils import formatdate
-    global charset, mail_smarthost, mail_login
-    
-    # Create a text/plain message
-    msg = MIMEText(text, 'plain', charset)
-    msg['From'] = mfrom
-    msg['To'] = ', '.join(mto)
-    msg['Subject'] = Header(subject, charset)
-    msg['Date'] = formatdate()
-    
-    try:
-        server = smtplib.SMTP(mail_smarthost)
-        try:
-            #server.set_debuglevel(1)
-            if mail_login:
-                user, pwd = mail_login.split()
-                server.login(user, pwd)
-            server.sendmail(mail_from, mto, msg.as_string())
-        finally:
-            try:
-                server.quit()
-            except AttributeError:
-                # in case the connection failed, SMTP has no "sock" attribute
-                pass
-    except smtplib.SMTPException, e:
-        return (0, str(e))
-    except (os.error, socket.error), e:
-        return (0, "Connection to mailserver '%(server)s' failed: %(reason)s" % {
-            'server': mail_smarthost, 
-            'reason': str(e)
-        })
-
-    return (1, "Mail sent OK")
-
-def notify_maintainer(lang, mail_text):
-    mailaddr = languages[lang][4]
-    rc = None
-    if mailaddr and mailaddr.find('***vacant***') < 0:
-        text = mail_text % locals()
-        rc = sendmail(mail_from, [mailaddr], mail_subject, text)
-    return rc
-
-if __name__ == '__main__':
-    langs = languages.keys()
-    langs.remove('en') # nothing to do for english, so remove it
-
-    #langs = ['de', ] # for testing
-
-    import sys
-    mail_text = sys.stdin.read()
-
-    if len(mail_text) > 10: # do not send mails w/o real content
-        for lang in langs:
-            notify_maintainer(lang, mail_text)
-
--- a/MoinMoin/i18n/mail_i18n-maintainers.txt	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-Dear MoinMoin i18n maintainer / translator,
-
-You get this mail because one of the MoinMoin wiki engine's i18n files
-lists you as maintainer of it.
-
-By maintaining "%(lang)s", you do great work and all MoinMoin users say
-"Thank you" for that (or at least think it ;)!
-
-What we need
-============
-
-We need your help again for updating the following (high priority and
-most important stuff is on top of the list):
-
-[X] MoinMaster:MoinI18n/%(lang)s page (source for the .po file)
-
-[X] "%(lang)s" system pages on the MoinMaster wiki
-
-[X] "%(lang)s" help pages on the MoinMaster wiki (optional - would be
-    nice, if you had the time)
-
-We need that until 1.12.2005 - because MoinMoin 1.5 RC1 will be released
-then, so we can include that stuff into the distribution archive to make
-people speaking your language happy. ;)
-
-Please, after translating the current stuff, keep an eye on MoinMaster
-wiki - we are still in the beta phase of 1.5 development and thus, some
-strings in the PO file may still change or get added/deleted.
-
-See http://moinmoin.wikiwikiweb.de/MoinMoinTodo/Release_1.5 for our
-planned release schedule.
-
-Please reply immediately
-========================
-
-You can use the form below.
-
-Or even better: directly edit the wiki page listed under Ressources.
-
-[ ] Great! I have some time, can do it now and finish in time.
-
-[ ] I would like to, but have no time these days. Maintenance should
-    be better done by anyone else volunteering, but you can keep me
-    on the list for next time.
-
-[ ] Sorry, but can't do that any more. Remove me as i18n maintainer.
-
-Please, even if you can't help us this time, a fast response will help
-us finding another volunteer.
-
-
-Ressources
-==========
-
-If you want to do it (or update status page), please read on here:
-http://moinmoin.wikiwikiweb.de/MoinDev/Translation
-
-You will find all you need there. If you have questions, also put them
-there (in English, please).
-
-Thanks,
-
-The MoinMoin development team
-
-
--- a/MoinMoin/i18n/mail_i18n-maintainers2.txt	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-Dear MoinMoin i18n maintainer / translator,
-
-If you are still unfinished with translation - keep in mind that the deadline
-for work on MoinMaster stuff (system texts, system and help pages) is at:
-
-               2005-12-24 16:00 UTC.
-
-
-We plan to release MoinMoin 1.5.0 soon. If your translation arrives before last
-release steps have begun, it might still get in.
-
-If not, we will include it in development of next version, so it will
-be there when we release next version.
-
-Please also don't forget to update status here:
-http://moinmoin.wikiwikiweb.de/MoinDev/Translation
-
-Thanks,
-
-The MoinMoin development team
-
-
--- a/MoinMoin/i18n/po2wiki.py	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-#!/usr/bin/python
-"""
-    remove everthing top of the first msgid line (PIs destroyed by gettext),
-    prepend some processing instructions to a .po file to be able to put it
-    onto moinmaster wiki, letting it get processed by gettext parser
-"""
-def run():
-    import sys, os, xmlrpclib
-    sys.path.insert(0, '../..')
-
-    excluded = ["en",] # languages managed in repository, not in wiki
-
-    lang = sys.argv[1]
-    lang = lang.replace('_', '-') # module names use _ instead of -
-
-    data = sys.stdin.read()
-
-    if lang in excluded:
-        f = open("%s.po" % lang, "w")
-        f.write(data)
-        f.close()
-        sys.exit(0)
-        
-    data = data.decode('utf-8')
-
-    cutpos = data.index(u"msgid")
-    data = data[cutpos:] # remove comments at top
-
-    data = u"""\
-## Please edit system and help pages ONLY in the moinmaster wiki! For more
-## information, please see MoinMaster:MoinPagesEditorGroup.
-##master-page:None
-##master-date:None
-#acl MoinPagesEditorGroup:read,write,delete,revert All:read
-#format gettext
-#language %s
-
-#
-# MoinMoin %s system text translation
-#
-%s""" % (lang, lang, data)
-
-
-    from MoinMoin.support.BasicAuthTransport import BasicAuthTransport
-
-    user = "ThomasWaldmann" # must be a known Wiki account
-    password = os.environ.get("PASS", "")
-    pagename = "MoinI18n/%s" % lang
-    pagedata = data.encode('utf-8')
-
-    authtrans = BasicAuthTransport(user, password)
-    wiki = xmlrpclib.ServerProxy("http://moinmaster.wikiwikiweb.de/?action=xmlrpc2", transport=authtrans)
-
-    rc = wiki.putPage(pagename, pagedata)
-    print "Page: %s rc=%s" % (pagename, rc)
-
-if __name__ == "__main__":
-    run()
-
--- a/MoinMoin/i18n/prepend.py	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/python
-"""
-    prepend some processing instructions to a .po file to be able to put it
-    onto moinmaster wiki, letting it get processed by gettext parser
-
-    for f in *.po; do ./prepend.py $f; done
-"""
-def run():
-    import sys, codecs
-    fname = sys.argv[1]
-
-    lang = fname.replace('.po_', '').replace('.po', '')
-    lang = lang.replace('_', '-') # module names use _ instead of -
-
-    f = codecs.open(fname, 'r', 'utf-8')
-    data = f.read()
-    f.close()
-
-    data = u"""\
-## Please edit system and help pages ONLY in the moinmaster wiki! For more
-## information, please see MoinMaster:MoinPagesEditorGroup.
-##master-page:None
-##master-date:None
-#acl MoinPagesEditorGroup:read,write,delete,revert All:read
-#format gettext
-#language %s
-
-%s""" % (lang, data)
-
-    f = codecs.open(fname, 'w', 'utf-8')
-    f.write(data)
-    f.close()
-
-if __name__ == "__main__":
-    run()
-
--- a/MoinMoin/i18n/recode.py	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - file encoding conversion
-
-    @copyright: 2005 by Thomas Waldmann (MoinMoin:ThomasWaldmann)
-    @license: GNU GPL, see COPYING for details.
-
-    Convert data in encoding src_enc to encoding dst_enc, both specified
-    on command line. Data is read from standard input and written to
-    standard output.
-
-    Usage:
-
-    ./recode.py src_enc dst_enc < src  > dst
-
-    Example:
-
-    # Using non utf-8 editor to edit utf-8 file:
-
-    # Make a working copy using iso-8859-1 encoding
-    ./recode.py utf-8 iso-8859-1 < de.po > de-iso1.po
-
-    # Use non-utf8 editor
-    $EDITOR de-iso1.po
-
-    # Recode back to utf-8
-    ./recode.py iso-8859-1 utf-8 < de-iso1.po > de-utf8.po
-
-    # Review changes and replace original if everything is ok
-    diff de.po de-utf8.po | less
-    mv de-utf8.po de.po
-
-"""
-
-import sys
-
-def error(msg):
-    sys.stderr.write(msg + '\n')
-    
-def run():
-    try:
-        cmd, src_enc, dst_enc = sys.argv
-
-        for line in sys.stdin:
-            line = unicode(line, src_enc).encode(dst_enc)
-            sys.stdout.write(line)
-
-    except UnicodeError, err:
-        error("Can't recode: %s" % str(err))
-    except LookupError, err:
-        error(str(err))
-    except ValueError:
-        error("Wrong number of arguments")
-        error(__doc__)
-
-
-if __name__ == "__main__":
-    run()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/check_i18n.py	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,376 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+"""check_i18n - compare texts in the source with the language files
+
+Searches in the MoinMoin sources for calls of _() and tries to extract
+the parameter.  Then it checks the language modules if those parameters
+are in the dictionary.
+
+Usage: check_i18n.py [lang ...]
+
+Without arguments, checks all languages in i18n or the specified
+languages. Look into MoinMoin.i18n.__init__ for availeable language
+names.
+
+The script will run from the moin root directory, where the MoinMoin
+package lives, or from MoinMoin/i18n where this script lives.
+
+TextFinder class based on code by Seo Sanghyeon and the python compiler
+package.
+
+TODO: fix it for the changed i18n stuff of moin 1.6
+
+@copyright: 2003 Florian Festi, Nir Soffer, Thomas Waldmann
+@license: GNU GPL, see COPYING for details.
+"""
+
+output_encoding = 'utf-8'
+
+# These lead to crashes (MemoryError - due to missing codecs?)
+#blacklist_files = ["ja.py", "zh.py", "zh_tw.py"]
+#blacklist_langs = ["ja", "zh", "zh-tw"]
+
+# If you have cjkcodecs installed, use this:
+blacklist_files = []
+blacklist_langs = []
+
+import sys, os, compiler
+from compiler.ast import Name, Const, CallFunc, Getattr
+
+class TextFinder:
+    """ Walk through AST tree and collect text from gettext calls
+        
+    Find all calls to gettext function in the source tree and collect
+    the texts in a dict. Use compiler to create an abstract syntax tree
+    from each source file, then find the nodes for gettext function
+    call, and get the text from the call.
+
+    Localized texts are used usually translated during runtime by
+    gettext functions and apear in the source as
+    _('text...'). TextFinder class finds calls to the '_' function in
+    any namespace, or your prefered gettext function.
+
+    Note that TextFinder will only retrieve text from function calls
+    with a constant argument like _('text'). Calls like _('text' % locals()),
+    _('text 1' + 'text 2') are marked as bad call in the report, and the 
+    text is not retrieved into the dictionary.
+
+    Note also that texts in source can appear several times in the same
+    file or different files, but they will only apear once in the
+    dictionary that this tool creates.
+
+    The dictionary value for each text is a dictionary of filenames each
+    containing a list of (best guess) lines numbers containning the text.
+    """
+    
+    def __init__(self, name='_'):
+        """ Init with the gettext function name or '_'"""
+        self._name = name       # getText function name
+        self._dictionary = {}   # Unique texts in the found texts
+        self._found = 0         # All good calls including duplicates
+        self._bad = 0           # Bad calls: _('%s' % var) or _('a' + 'b')
+    
+    def setFilename(self, filename):
+        """Remember the filename we are parsing"""
+        self._filename = filename 
+        
+    def visitModule(self, node):
+        """ Start the search from the top node of a module
+
+        This is the entry point into the search. When compiler.walk is
+        called it calls this method with the module node.
+
+        This is the place to initialize module specific data.
+        """
+        self._visited = {}  # init node cache - we will visit each node once
+        self._lineno = 'NA' # init line number  
+
+        # Start walking in the module node
+        self.walk(node)
+
+    def walk(self, node):
+        """ Walk through all nodes """
+        if self._visited.has_key(node):
+            # We visited this node already
+            return
+            
+        self._visited[node] = 1            
+        if not self.parseNode(node):           
+            for child in node.getChildNodes():
+                self.walk(child)
+
+    def parseNode(self, node):
+        """ Parse function call nodes and collect text """
+
+        # Get the current line number. Since not all nodes have a line number
+        # we save the last line number - it should be close to the gettext call
+        if node.lineno != None:
+            self._lineno = node.lineno
+
+        if node.__class__ == CallFunc and node.args: 
+            child = node.node
+            klass = child.__class__            
+            if (# Standard call _('text')
+                (klass == Name and child.name == self._name) or
+                # A call to an object attribute: object._('text') 
+                (klass == Getattr and child.attrname == self._name)):
+                if node.args[0].__class__ == Const:
+                    # Good call with a constant _('text')
+                    self.addText(node.args[0].value)
+                else:
+                    self.addBadCall(node)
+                return 1
+        return 0
+            
+    def addText(self, text):
+        """ Add text to dictionary and count found texts.
+        
+        Note that number of texts in dictionary could be different from
+        the number of texts found, because some texts appear several
+        times in the code.
+
+        Each text value is a dictionary of filenames that contain the
+        text and each filename value is the list of line numbers with
+        the text. Missing line numbers are recorded as 'NA'.
+
+        self._lineno is the last line number we checked. It may be the line
+        number of the text, or near it.
+        """
+        
+        self._found = self._found + 1
+
+        # Create key for this text if needed
+        if not self._dictionary.has_key(text):
+            self._dictionary[text] = {}
+
+        # Create key for this filename if needed   
+        textInfo = self._dictionary[text]
+        if not textInfo.has_key(self._filename):
+            textInfo[self._filename] = [self._lineno]
+        else:
+            textInfo[self._filename].append(self._lineno)
+
+    def addBadCall(self, node):
+        """Called when a bad call like _('a' + 'b') is found"""
+        self._bad = self._bad + 1
+        print
+        print "<!> Warning: non-constant _ call:"
+        print " `%s`" % str(node)
+        print " `%s`:%s" % (self._filename, self._lineno)
+        
+    # Accessors
+    
+    def dictionary(self):
+        return self._dictionary
+            
+    def bad(self):
+        return self._bad
+        
+    def found(self):
+        return self._found
+        
+
+def visit(path, visitor):
+    visitor.setFilename(path)
+    tree = compiler.parseFile(path)
+    compiler.walk(tree, visitor)
+
+
+# MoinMoin specific stuff follows
+
+
+class Report:
+    """Language status report"""
+    def __init__(self, lang, sourceDict):
+        self.__lang = lang
+        self.__sourceDict = sourceDict
+        self.__langDict = None
+        self.__missing = {}
+        self.__unused = {}
+        self.__error = None
+        self.__ready = 0
+        self.create()
+
+    def loadLanguage(self):
+        filename = i18n.filename(self.__lang)
+        self.__langDict = pysupport.importName("MoinMoin.i18n." + filename, "text")
+
+    def create(self):
+        """Compare language text dict against source dict"""
+        self.loadLanguage()
+        if not self.__langDict:
+            self.__error = "Language %s not found!" % self.__lang
+            self.__ready = 1
+            return
+
+        # Collect missing texts
+        for text in self.__sourceDict:
+            if not self.__langDict.has_key(text):
+                self.__missing[text] = self.__sourceDict[text]
+
+        # Collect unused texts
+        for text in self.__langDict:
+            if not self.__sourceDict.has_key(text):
+                self.__unused[text] = self.__langDict[text]
+        self.__ready = 1
+        
+    def summary(self):
+        """Return summary dict"""
+        summary = {
+            'name': i18n.languages[self.__lang][i18n.ENAME].encode(output_encoding),
+            'maintainer': i18n.languages[self.__lang][i18n.MAINTAINER],
+            'total' : len(self.__langDict),
+            'missing': len(self.__missing),
+            'unused': len(self.__unused),
+            'error': self.__error
+            }
+        return summary
+        
+    def missing(self):
+        return self.__missing
+
+    def unused(self):
+        return self.__unused
+    
+        
+
+if __name__ == '__main__':
+
+    import time
+    
+    # Check that we run from the root directory where MoinMoin package lives
+    # or from the i18n directory when this script lives
+    if os.path.exists('MoinMoin/__init__.py'):
+        # Running from the root directory
+        MoinMoin_dir = os.curdir   
+    elif os.path.exists(os.path.join(os.pardir, 'i18n')):
+        # Runing from i18n
+        MoinMoin_dir = os.path.join(os.pardir, os.pardir)
+    else:
+        print __doc__
+        sys.exit(1)
+
+    # Insert MoinMoin_dir into sys.path
+    sys.path.insert(0, MoinMoin_dir)
+    from MoinMoin import i18n
+    from MoinMoin.util import pysupport
+    
+    textFinder = TextFinder()
+    found = 0
+    unique = 0   
+    bad = 0
+
+    # Find gettext calls in the source
+    for root, dirs, files in os.walk(os.path.join(MoinMoin_dir, 'MoinMoin')):
+        for name in files:
+            if name.endswith('.py'):
+                if name in blacklist_files: continue
+                path = os.path.join(root, name)
+                #print '%(path)s:' % locals(),
+                visit(path, textFinder)
+                
+                # Report each file's results
+                new_unique = len(textFinder.dictionary()) - unique
+                new_found = textFinder.found() - found
+                #print '%(new_unique)d (of %(new_found)d)' % locals()
+                                
+                # Warn about bad calls - these should be fixed!
+                new_bad = textFinder.bad() - bad
+                #if new_bad:
+                #    print '### Warning: %(new_bad)d bad call(s)' % locals()
+                
+                unique = unique + new_unique
+                bad = bad + new_bad
+                found = found + new_found
+
+    # Print report using wiki markup, so we can publish this on MoinDev
+    # !!! Todo:
+    #     save executive summary for the wiki
+    #     save separate report for each language to be sent to the
+    #     language translator.
+    #     Update the wiki using XML-RPC??
+
+    print "This page is generated by `MoinMoin/i18n/check_i18n.py`."
+    print "To recreate this report run `make check-i18n` and paste here"
+    print
+    print '----'
+    print 
+    print '[[TableOfContents(2)]]'
+    print
+    print
+    print "= Translation Report ="
+    print
+    print "== Summary =="
+    print
+    print 'Created on %s' % time.asctime()
+    print
+
+    print ('\n%(unique)d unique texts in dictionary of %(found)d texts '
+           'in source.') % locals()
+    if bad:
+        print '\n%(bad)d bad calls.' % locals()
+    print
+    
+    # Check languages from the command line or from moin.i18n against
+    # the source
+    if sys.argv[1:]:
+        languages = sys.argv[1:]
+    else:
+        languages = i18n.languages.keys()
+        for lang in blacklist_langs: 
+            # problems, maybe due to encoding?
+            if lang in languages:
+                languages.remove(lang)
+    if 'en' in languages:
+        languages.remove('en') # there is no en lang file
+    languages.sort()
+    
+    # Create report for all languages
+    report = {}
+    for lang in languages:
+        report[lang] = Report(lang, textFinder.dictionary())
+
+    # Print summary for all languages
+    print ("||<:>'''Language'''||<:>'''Texts'''||<:>'''Missing'''"
+           "||<:>'''Unused'''||")
+    for lang in languages:
+        print ("||%(name)s||<)>%(total)s||<)>%(missing)s||<)>%(unused)s||"
+               ) % report[lang].summary() 
+
+    # Print details
+    for lang in languages:
+        dict = report[lang].summary()
+        print
+        print "== %(name)s ==" % dict
+        print
+        print "Maintainer: [[MailTo(%(maintainer)s)]]" % dict
+
+        # Print missing texts, if any
+        if report[lang].missing():
+            print """
+=== Missing texts ===
+
+These items should ''definitely'' get fixed.
+
+Maybe the corresponding english text in the source code was only changed
+slightly, then you want to look for a similar text in the ''unused''
+section below and modify i18n, so that it will match again.
+"""
+            for text in report[lang].missing():
+                print " 1. `%r`" % text
+                
+        # Print unused texts, if any
+        if report[lang].unused():
+            print """
+=== Possibly unused texts ===
+
+Be ''very careful'' and double-check before removing any of these 
+potentially unused items.
+
+This program can't detect references done from wiki pages, from
+UserPreferences options, from Icon titles etc.!
+"""        
+            for text in report[lang].unused():
+                print " 1. `%r`" % text
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/mail_i18n-maintainers.py	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+"""
+    read mail text from standard input and 
+    send an email to all i18n maintainers
+    %(lang)s will be replaced by language
+    
+    @copyright: 2004 Thomas Waldmann
+    @license: GNU GPL, see COPYING for details
+"""
+
+mail_from = 'tw-public@gmx.de'
+mail_subject = 'MoinMoin i18n notification'
+
+mail_smarthost = 'localhost'
+mail_login = None
+charset = 'iso-8859-1'
+
+from meta import languages
+
+def sendmail(mfrom, mto, subject, text):
+    """
+    Send a mail to the address(es) in 'to', with the given subject and
+    mail body 'text'.
+    
+    Return a tuple of success or error indicator and message.
+
+    TODO: code duplicated from MoinMoin/util/mail.py
+    
+    @param mfrom: source email address
+    @param to: target email address
+    @param subject: subject of email
+    @param text: email body text
+    @rtype: tuple
+    @return: (is_ok, msg)
+    """
+    import smtplib, socket, os
+    from email.MIMEText import MIMEText
+    from email.Header import Header
+    from email.Utils import formatdate
+    global charset, mail_smarthost, mail_login
+    
+    # Create a text/plain message
+    msg = MIMEText(text, 'plain', charset)
+    msg['From'] = mfrom
+    msg['To'] = ', '.join(mto)
+    msg['Subject'] = Header(subject, charset)
+    msg['Date'] = formatdate()
+    
+    try:
+        server = smtplib.SMTP(mail_smarthost)
+        try:
+            #server.set_debuglevel(1)
+            if mail_login:
+                user, pwd = mail_login.split()
+                server.login(user, pwd)
+            server.sendmail(mail_from, mto, msg.as_string())
+        finally:
+            try:
+                server.quit()
+            except AttributeError:
+                # in case the connection failed, SMTP has no "sock" attribute
+                pass
+    except smtplib.SMTPException, e:
+        return (0, str(e))
+    except (os.error, socket.error), e:
+        return (0, "Connection to mailserver '%(server)s' failed: %(reason)s" % {
+            'server': mail_smarthost, 
+            'reason': str(e)
+        })
+
+    return (1, "Mail sent OK")
+
+def notify_maintainer(lang, mail_text):
+    mailaddr = languages[lang][4]
+    rc = None
+    if mailaddr and mailaddr.find('***vacant***') < 0:
+        text = mail_text % locals()
+        rc = sendmail(mail_from, [mailaddr], mail_subject, text)
+    return rc
+
+if __name__ == '__main__':
+    langs = languages.keys()
+    langs.remove('en') # nothing to do for english, so remove it
+
+    #langs = ['de', ] # for testing
+
+    import sys
+    mail_text = sys.stdin.read()
+
+    if len(mail_text) > 10: # do not send mails w/o real content
+        for lang in langs:
+            notify_maintainer(lang, mail_text)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/mail_i18n-maintainers.txt	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,65 @@
+Dear MoinMoin i18n maintainer / translator,
+
+You get this mail because one of the MoinMoin wiki engine's i18n files
+lists you as maintainer of it.
+
+By maintaining "%(lang)s", you do great work and all MoinMoin users say
+"Thank you" for that (or at least think it ;)!
+
+What we need
+============
+
+We need your help again for updating the following (high priority and
+most important stuff is on top of the list):
+
+[X] MoinMaster:MoinI18n/%(lang)s page (source for the .po file)
+
+[X] "%(lang)s" system pages on the MoinMaster wiki
+
+[X] "%(lang)s" help pages on the MoinMaster wiki (optional - would be
+    nice, if you had the time)
+
+We need that until 1.12.2005 - because MoinMoin 1.5 RC1 will be released
+then, so we can include that stuff into the distribution archive to make
+people speaking your language happy. ;)
+
+Please, after translating the current stuff, keep an eye on MoinMaster
+wiki - we are still in the beta phase of 1.5 development and thus, some
+strings in the PO file may still change or get added/deleted.
+
+See http://moinmoin.wikiwikiweb.de/MoinMoinTodo/Release_1.5 for our
+planned release schedule.
+
+Please reply immediately
+========================
+
+You can use the form below.
+
+Or even better: directly edit the wiki page listed under Ressources.
+
+[ ] Great! I have some time, can do it now and finish in time.
+
+[ ] I would like to, but have no time these days. Maintenance should
+    be better done by anyone else volunteering, but you can keep me
+    on the list for next time.
+
+[ ] Sorry, but can't do that any more. Remove me as i18n maintainer.
+
+Please, even if you can't help us this time, a fast response will help
+us finding another volunteer.
+
+
+Ressources
+==========
+
+If you want to do it (or update status page), please read on here:
+http://moinmoin.wikiwikiweb.de/MoinDev/Translation
+
+You will find all you need there. If you have questions, also put them
+there (in English, please).
+
+Thanks,
+
+The MoinMoin development team
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/mail_i18n-maintainers2.txt	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,22 @@
+Dear MoinMoin i18n maintainer / translator,
+
+If you are still unfinished with translation - keep in mind that the deadline
+for work on MoinMaster stuff (system texts, system and help pages) is at:
+
+               2005-12-24 16:00 UTC.
+
+
+We plan to release MoinMoin 1.5.0 soon. If your translation arrives before last
+release steps have begun, it might still get in.
+
+If not, we will include it in development of next version, so it will
+be there when we release next version.
+
+Please also don't forget to update status here:
+http://moinmoin.wikiwikiweb.de/MoinDev/Translation
+
+Thanks,
+
+The MoinMoin development team
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/po2wiki.py	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+"""
+    remove everthing top of the first msgid line (PIs destroyed by gettext),
+    prepend some processing instructions to a .po file to be able to put it
+    onto moinmaster wiki, letting it get processed by gettext parser
+"""
+def run():
+    import sys, os, xmlrpclib
+    sys.path.insert(0, '../../..')
+
+    excluded = ["en",] # languages managed in repository, not in wiki
+
+    lang = sys.argv[1]
+    lang = lang.replace('_', '-') # module names use _ instead of -
+
+    data = sys.stdin.read()
+
+    if lang in excluded:
+        f = open("%s.po" % lang, "w")
+        f.write(data)
+        f.close()
+        sys.exit(0)
+        
+    data = data.decode('utf-8')
+
+    cutpos = data.index(u"msgid")
+    data = data[cutpos:] # remove comments at top
+
+    data = u"""\
+## Please edit system and help pages ONLY in the moinmaster wiki! For more
+## information, please see MoinMaster:MoinPagesEditorGroup.
+##master-page:None
+##master-date:None
+#acl MoinPagesEditorGroup:read,write,delete,revert All:read
+#format gettext
+#language %s
+
+#
+# MoinMoin %s system text translation
+#
+%s""" % (lang, lang, data)
+
+
+    from MoinMoin.support.BasicAuthTransport import BasicAuthTransport
+
+    user = "ThomasWaldmann" # must be a known Wiki account
+    password = os.environ.get("PASS", "")
+    pagename = "MoinI18n/%s" % lang
+    pagedata = data.encode('utf-8')
+
+    authtrans = BasicAuthTransport(user, password)
+    wiki = xmlrpclib.ServerProxy("http://moinmaster.wikiwikiweb.de/?action=xmlrpc2", transport=authtrans)
+
+    rc = wiki.putPage(pagename, pagedata)
+    print "Page: %s rc=%s" % (pagename, rc)
+
+if __name__ == "__main__":
+    run()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/prepend.py	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+"""
+    prepend some processing instructions to a .po file to be able to put it
+    onto moinmaster wiki, letting it get processed by gettext parser
+
+    for f in *.po; do ./prepend.py $f; done
+"""
+def run():
+    import sys, codecs
+    fname = sys.argv[1]
+
+    lang = fname.replace('.po_', '').replace('.po', '')
+    lang = lang.replace('_', '-') # module names use _ instead of -
+
+    f = codecs.open(fname, 'r', 'utf-8')
+    data = f.read()
+    f.close()
+
+    data = u"""\
+## Please edit system and help pages ONLY in the moinmaster wiki! For more
+## information, please see MoinMaster:MoinPagesEditorGroup.
+##master-page:None
+##master-date:None
+#acl MoinPagesEditorGroup:read,write,delete,revert All:read
+#format gettext
+#language %s
+
+%s""" % (lang, data)
+
+    f = codecs.open(fname, 'w', 'utf-8')
+    f.write(data)
+    f.close()
+
+if __name__ == "__main__":
+    run()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/recode.py	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - file encoding conversion
+
+    @copyright: 2005 by Thomas Waldmann (MoinMoin:ThomasWaldmann)
+    @license: GNU GPL, see COPYING for details.
+
+    Convert data in encoding src_enc to encoding dst_enc, both specified
+    on command line. Data is read from standard input and written to
+    standard output.
+
+    Usage:
+
+    ./recode.py src_enc dst_enc < src  > dst
+
+    Example:
+
+    # Using non utf-8 editor to edit utf-8 file:
+
+    # Make a working copy using iso-8859-1 encoding
+    ./recode.py utf-8 iso-8859-1 < de.po > de-iso1.po
+
+    # Use non-utf8 editor
+    $EDITOR de-iso1.po
+
+    # Recode back to utf-8
+    ./recode.py iso-8859-1 utf-8 < de-iso1.po > de-utf8.po
+
+    # Review changes and replace original if everything is ok
+    diff de.po de-utf8.po | less
+    mv de-utf8.po de.po
+
+"""
+
+import sys
+
+def error(msg):
+    sys.stderr.write(msg + '\n')
+    
+def run():
+    try:
+        cmd, src_enc, dst_enc = sys.argv
+
+        for line in sys.stdin:
+            line = unicode(line, src_enc).encode(dst_enc)
+            sys.stdout.write(line)
+
+    except UnicodeError, err:
+        error("Can't recode: %s" % str(err))
+    except LookupError, err:
+        error(str(err))
+    except ValueError:
+        error("Wrong number of arguments")
+        error(__doc__)
+
+
+if __name__ == "__main__":
+    run()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/i18n/tools/wiki2po.py	Mon May 22 00:44:12 2006 +0200
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+"""
+    get latest translation page content from the wiki and write it to *.po
+"""
+def run():
+    import sys, xmlrpclib
+    sys.path.insert(0, '../../..')
+
+    excluded = ["en",] # languages managed in repository, not in wiki
+
+    langfname = sys.argv[1]
+    lang = langfname.replace('_', '-') # module names use _ instead of -
+
+    if not lang in excluded:
+        wiki = xmlrpclib.ServerProxy("http://moinmaster.wikiwikiweb.de/?action=xmlrpc2")
+
+        pagename = "MoinI18n/%s" % lang
+        pagedata = wiki.getPage(pagename).encode('utf-8').replace("\n","\r\n")
+
+        f = open("%s.po" % langfname, "w")
+        f.write(pagedata)
+        f.close()
+
+if __name__ == "__main__":
+    run()
+
--- a/MoinMoin/i18n/wiki2po.py	Mon May 22 00:31:35 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-#!/usr/bin/python
-"""
-    get latest translation page content from the wiki and write it to *.po
-"""
-def run():
-    import sys, xmlrpclib
-    sys.path.insert(0, '../..')
-
-    excluded = ["en",] # languages managed in repository, not in wiki
-
-    langfname = sys.argv[1]
-    lang = langfname.replace('_', '-') # module names use _ instead of -
-
-    if not lang in excluded:
-        wiki = xmlrpclib.ServerProxy("http://moinmaster.wikiwikiweb.de/?action=xmlrpc2")
-
-        pagename = "MoinI18n/%s" % lang
-        pagedata = wiki.getPage(pagename).encode('utf-8').replace("\n","\r\n")
-
-        f = open("%s.po" % langfname, "w")
-        f.write(pagedata)
-        f.close()
-
-if __name__ == "__main__":
-    run()
-
--- a/setup.py	Mon May 22 00:31:35 2006 +0200
+++ b/setup.py	Mon May 22 00:44:12 2006 +0200
@@ -202,6 +202,7 @@
         'MoinMoin.formatter',
         'MoinMoin.i18n',
         'MoinMoin.i18n.mo',
+        'MoinMoin.i18n.tools',
         'MoinMoin.logfile',
         'MoinMoin.macro',
         'MoinMoin.parser',