#!/usr/bin/env python # -*- mode: python; coding: utf-8 -*- # Copyright © 2001, 2002, 2007 Translation Project. # Copyright © 1996, 1997, 1998, 1999, 2000 Progiciels Bourbeau-Pinard inc. # François Pinard , 1996. """\ Process a received POT or PO file, sending out notifications to the teams, the assigned translators, the team-leaders group, and the maintainer. Usage: po-register [-nqtT] PACKAGE-VERSION.{pot,LL[.po]} -n run dry, don't store files, don't send out mails -q be quiet, don't show mails -t display the list of known team codes and exit -T "list" when processing a POT file, only notify the listed teams; the list of team codes should be space-separated and wrapped in quotes """ import os, sys, shutil import commands, getopt, re, string, datetime sys.path.insert(0, sys.path[0]+'/../lib') import config, data, localweb, po, registry, messages # Find sendmail in /usr/sbin or /usr/lib. os.environ['PATH'] = '/usr/lib:/usr/sbin:' + os.environ['PATH'] # Find calc-postats. os.environ['PATH'] = config.progs_path + '/bin:' + os.environ['PATH'] # Unset possible localisation. try: del os.environ['LANGUAGE'] except KeyError:pass try: del os.environ['LANG'] except KeyError:pass os.umask(00002) def _(text): return messages.MultiString(text) def uni(name): return unicode(name, "utf-8") def recode(hints, name): try: u = unicode(name, "utf-8") except UnicodeError: return name return hints.team.encode(u) def msgmerge(po, pot, out): os.system("msgmerge --previous --no-wrap %s %s -o %s" % (po, pot, out)) def open_output(): if run.quiet: return open('/dev/null', 'w') elif run.dry: return sys.stdout else: #return sys.stdout return os.popen('sendmail -i -t', 'w') def mime_charset(hints): if hints.team.charset: return hints.team.charset return "utf-8" def mime_header(charset): return """MIME-Version: 1.0 Content-Type: text/plain;charset=%s Content-Transfer-Encoding: 8bit """ % charset def underwrite(write): write(_(""" Thank you for all your work, The Translation Project robot, in the name of your translation coordinator. """)) class run: dry = 0 quiet = 0 def main(*arguments): if not arguments: sys.stdout.write(__doc__) sys.exit(0) options, arguments = getopt.getopt(arguments, 'nqtT:') teams = None for option, value in options: if option == '-n': run.dry = 1 elif option == '-q': run.quiet = 1 elif option == '-t': print " ".join([x.name for x in registry.team_list()]) return elif option == '-T': teams = value.split() for name in arguments: if name[0] == '/': raise ("Argument '%s' may not be in absolute notation" % name) hints = registry.hints(name) sys.stderr.write('Processing %s...\n' % name) if hints.pot: process_pot_file(hints, name, teams) else: process_po_file(hints, name) # Processing of a POT file. def process_pot_file(hints, file, teams): if not hints.domain.mailto and not hints.domain.nomailto: raise ("No known maintainer for textual domain '%s'" % hints.domain.name) if run.dry: template_path = '%s/%s-%s.pot' % ( config.temp_path, hints.domain.name, hints.version.name) else: template_path = hints.template_path() template_base = hints.template_base() shutil.copy(file, template_path) os.chmod(template_path, 00664) if not run.dry: # Calculate and store the statistics for the new POT file. response = os.popen("calc-postats "+template_path).readlines() for line in response: print line os.remove(file) # send an update to twitter account #tweet = twitter.Twitter() #tweet.send("[TEMPLATE] %s\n" # % (template_base)) stats = po.stats(po.read(template_path)) if teams: teams = [registry.team(n) for n in teams] else: teams = registry.team_list() for team in teams: hints.team = team archive_base = hints.archive_base() archive_path = hints.archive_path() maintainer_path = hints.maintainer_path() if os.path.isfile(archive_base): sys.stderr.write(" skipping '%s' as local '%s' is waiting\n" % (team.name, archive_base)) continue if os.path.isfile(archive_path): sys.stderr.write(" skipping '%s' as '%s' already exists\n" % (team.name, archive_path)) continue translator = None if os.path.isfile(maintainer_path): previous = commands.getoutput("ls -l %s | sed 's,.*/,,'" % maintainer_path) sys.stderr.write(" merging with '%s'" % previous) merged_file = '%s/%s' % (config.temp_path, archive_base) msgmerge(maintainer_path, template_path, merged_file) name, address = po.last_translator(po.header(po.read(merged_file))) if address: try: translator = registry.translator(team, name, address) except KeyError: pass else: if translator.autosend: if translator.mailto: address = translator.mailto[0] sys.stderr.write(" SENDING '%s' to <%s>\n" % (archive_base, address)) email_file(address, merged_file, translator.autosend) process_po_file(hints, merged_file, fromnewpot = True) elif not team.suppresspot: mailto = hints.team.announce_address() if mailto != "(nothing)": sys.stderr.write(" notifying `%s` <%s> about '%s'\n" % (team.name, mailto, template_base)) notify_team_for_po(hints, stats, "nobody", fromnewpot = True) if not translator: for translator in team.translator_for_domain(hints.domain): if translator.autosend and translator.mailto: address = translator.mailto[0] sys.stderr.write(" SENDING '%s' to <%s>\n" % (template_base, address)) email_file(address, template_path, translator.autosend) # Regenerate the domain page after all statistics were updated: localweb.generate_domain_page(hints.domain.name) if hints.domain.mailto: sys.stderr.write(" notifying maintainer <%s> about '%s'\n" % (hints.domain.mailto[0], template_base)) notify_maintainer_for_pot(hints) sys.stderr.write(" also notifying team leaders\n") notify_team_leaders_for_pot(hints) # Processing of a PO file. def process_po_file(hints, file, fromnewpot = False): if not os.path.isdir('%s/%s' % (config.pos_path, hints.team.name)): sys.stderr.write(" initializing '%s/%s'\n" % (config.pos_path, hints.team.name)) if not run.dry: os.mkdir('%s/%s' % (config.pos_path, hints.team.name), 02775) archive_base = hints.archive_base() archive_path = hints.archive_path() maintainer_path = hints.maintainer_path() template_path = hints.template_path() skip = False stats = po.stats(po.read(file)) translated = stats['translated'] if translated == 0: sys.stderr.write(" skipping '%s' -- it is empty\n" % file) skip = True elif registry.compare_files(archive_path, file): sys.stderr.write(" skipping '%s' -- it is already stored\n" % file) skip = True elif not os.path.isfile(template_path) and not run.dry: sys.stderr.write(" skipping '%s' -- no corresponding POT\n" % file) skip = True elif registry.compare_files(template_path, file) and not run.dry: sys.stderr.write(" skipping '%s' -- identical to POT\n" % file) skip = True if skip: if not run.dry: os.remove(file) return translatorname = po.last_translator(po.header(po.read(file)))[0] if not run.dry: if os.path.isfile(archive_path): os.remove(archive_path) shutil.copy(file, archive_path) os.chmod(archive_path, 00664) if not fromnewpot: # For uploaded files make an entry in the log. now = datetime.datetime.now() log = open('%s/stored-POs.log' % config.cache_path, 'a') log.write("%s %s %s -- %s\n" % (now.date().isoformat(), now.time().strftime('%H:%M'), archive_base, translatorname.encode("utf-8"))) log.close() # Calculate and store the statistics for the new PO file. response = os.popen("calc-postats "+archive_path).readlines() for line in response: print line # Notify the team about the PO file, whether new or updated. mailto = hints.team.announce_address() if mailto != "(nothing)": sys.stderr.write(" notifying `%s` <%s> about '%s'\n" % (hints.team.name, mailto, archive_base)) notify_team_for_po(hints, stats, translatorname, fromnewpot) # Only notify the maintainer (and update the symlink in latest/) if the # submitted PO file is for the most recent POT file of this package. current_pot = data.load_postats().potstats[hints.domain.name][0] if ((registry.hints(current_pot).version == hints.version) and (not fromnewpot) and (translated > 0)): if not run.dry: directory = os.path.dirname(maintainer_path) if not os.path.isdir(directory): os.mkdir(directory, 02775) if (os.path.islink(maintainer_path) or os.path.isfile(maintainer_path)): os.remove(maintainer_path) sys.stderr.write(" pointing symlink to %s\n" % archive_base) os.symlink('../../%s/%s/%s' % (config.pos_dir, hints.team.name, archive_base), maintainer_path) if hints.domain.mailto: maintainer = hints.domain.mailto[0] sys.stderr.write(" notifying maintainer <%s>\n" % maintainer) notify_maintainer_for_po(hints, file, archive_base) else: sys.stderr.write(" maintainer has no email address\n") elif not fromnewpot: sys.stderr.write(" NOT informing maintainer\n") # Regenerate the team page, and when appropriate the domain page too. localweb.generate_team_page(hints.team.name) if not fromnewpot: localweb.generate_domain_page(hints.domain.name) if not run.dry: os.remove(file) # Four main types of notifications. def notify_maintainer_for_pot(hints): file = open_output() def write(msg): return file.write(messages.translate(msg, 'en')) write("""\ From: Translation Project Robot To: %s Subject: New template for '%s' made available """ % (hints.domain.mailto[0], hints.domain.name)) write('\n') write("""\ Hello, gentle maintainer. This is a message from the Translation Project robot. (If you have any questions, send them to .) A new POT file for textual domain '%s' has been made available to the language teams for translation. It is archived as: %s """ % (hints.domain.name, hints.template_url())) write("""\ Whenever you have a new distribution with a new version number ready, containing a newer POT file, please send the URL of that distribution tarball to the address below. The tarball may be just a pretest or a snapshot, it does not even have to compile. It is just used by the translators when they need some extra translation context. """) if hints.domain.url: write("""\ Below is the URL which has been provided to the translators of your package. Please inform the translation coordinator, at the address at the bottom, if this information is not current: """) for url in hints.domain.url: write(' %s\n' % url) if hints.domain.autosend: write("""\ Translated PO files will later be automatically e-mailed to you. """) else: write("""\ We can arrange things so that translated PO files are automatically e-mailed to you when they arrive. Ask at the address below if you want this. """) underwrite(write) def notify_team_leaders_for_pot(hints): file = open_output() def write(msg): return file.write(messages.translate(msg, 'en')) write("""\ From: Translation Project Robot To: team-leaders@translationproject.org Subject: New PO template for '%s' (TP) """ % hints.domain.name) write('\n') write("""\ My nicest hello to all language team leaders. This is a message from the Translation Project robot. A new POT file for programs using the textual domain '%s' has just been made available to the language teams for translation. A copy is available at: %s """ % (hints.domain.name, hints.template_url())) write("""\ Your team will be notified only if it already committed a translation for this domain, or if you have requested that the team be notified anyway. Otherwise it is up to you to decide how a translator might be recruited to take care of it. """) if hints.domain.url: write(""" Here is some URL information that could be provided to translators for this package: """) for url in hints.domain.url: write(' %s\n' % url) underwrite(write) def notify_maintainer_for_po(hints, file_name, archive_base): file = open_output() def write(msg): return file.write(messages.translate(msg, 'en')) write("""\ From: Translation Project Robot To: %s Subject: New %s PO file for '%s' (version %s) """ % (hints.domain.mailto[0], hints.team.language, hints.domain.name, hints.version)) write('\n') write("""\ Hello, gentle maintainer. This is a message from the Translation Project robot. A revised PO file for textual domain '%s' has been submitted by the %s team of translators. The file is available at: %s """ % (hints.domain.name, hints.team.language, hints.maintainer_url())) if hints.domain.autosend: sys.stderr.write(" SENDING '%s' to <%s>\n" % (archive_base, hints.domain.mailto[0])) email_file(hints.domain.mailto[0], file_name, hints.domain.autosend) write("""\ (This file, '%s', has just now been sent to you in a separate email.) """ % archive_base) else: write("""\ (We can arrange things so that in the future such files are automatically e-mailed to you when they arrive. Ask at the address below if you want this.) """) dirname = os.path.dirname(hints.maintainer_url()) + "/" write("""\ All other PO files for your package are available in: %s Please consider including all of these in your next release, whether official or a pretest. """ % dirname) write("""\ Whenever you have a new distribution with a new version number ready, containing a newer POT file, please send the URL of that distribution tarball to the address below. The tarball may be just a pretest or a snapshot, it does not even have to compile. It is just used by the translators when they need some extra translation context. """) write("""\ The following HTML page has been updated: %s/domain/%s.html If any question arises, please contact the translation coordinator. """ % (registry.puburl, hints.domain.name)) underwrite(write) def notify_team_for_po(hints, stats, translatorname, fromnewpot = False): file = open_output() charset = mime_charset(hints) def write(msg): return file.write(messages.translate(msg, hints.team.code, charset)) def write_header(msg): return file.write(messages.translate(msg, hints.team.code, charset, do_Q=True)) translated = stats['translated'] untranslated = stats['fuzzy'] + stats['untranslated'] percent = po.percentage(stats) if hints.team.code in hints.domain.ext: extstat = data.load_extstats().get((hints.domain.name, hints.team.code)) if extstat: translated = extstat['translated'] untranslated = extstat['fuzzy'] + extstat['untranslated'] percent = 100 * translated / (translated + untranslated) assignee = hints.team.translator_for_domain(hints.domain) if fromnewpot and (untranslated == 0): write_header(_("""\ From: Translation Project Robot To: %s Subject: New: %s-%s (%d%%) """) % (hints.team.announce_address(), hints.domain.name, hints.version.name, percent)) elif fromnewpot: write_header(_("""\ From: Translation Project Robot To: %s Subject: New: %s-%s (%d%%, %d untranslated) """) % (hints.team.announce_address(), hints.domain.name, hints.version.name, percent, untranslated)) elif untranslated == 0: write_header(_("""\ From: Translation Project Robot To: %s Subject: %s-%s (%d%%) by %s """) % (hints.team.announce_address(), hints.domain.name, hints.version.name, percent, translatorname)) else: write_header(_("""\ From: Translation Project Robot To: %s Subject: %s-%s (%d%%, %d untranslated) by %s """) % (hints.team.announce_address(), hints.domain.name, hints.version.name, percent, untranslated, translatorname)) write(mime_header(charset)) write('\n') if (translated == 0) or (hints.team.code in hints.domain.ext): kind = "POT" url = hints.template_url() else: kind = "PO" url = hints.archive_url() write(_("""\ Hello, members of the %s team. The TP-robot is happy to announce the presence of a new %s file: %s """) % (_(hints.team.language), kind, url)) # Say how much has been translated. if hints.team.code in hints.domain.ext: pass elif translated == 0: write(_("""\ None of its messages has been translated yet. """)) elif untranslated == 0: write(_("""\ All of its %d messages have been translated. """) % translated) else: write(messages.refill(_("""\ In this file %d messages are already translated, corresponding to %d%% of the original text size in bytes; %d messages still need some work. """) % (translated, percent, untranslated))) # PO file is announced to maintainer only when it's for latest version. current_pot = data.load_postats().potstats[hints.domain.name][0] current_version = registry.hints(current_pot).version if (not fromnewpot) and (current_version == hints.version): write(messages.refill(_("""\ This PO file has been announced to the maintainer of '%s', hoping he or she will include it in a future release. """) % hints.domain.name)) # Check and show who is assigned. if (len(assignee) > 0) and (hints.team.code in hints.domain.ext): write(messages.refill(_("""\ This package is both assigned to a team member AND marked as externally translated. Please report this error to the translation coordinator. """))) elif len(assignee) > 1: write(messages.refill(_("""\ Both %s AND %s are currently assigned for the translation. Please report this error to the translation coordinator. """) % (uni(assignee[0].name[0]), uni(assignee[1].name[0])))) elif len(assignee) == 1: if untranslated > 0: write(messages.refill(_("""\ %s is currently assigned for the translation. Please translate the remaining messages for the benefit of the users of the %s language. """) % (uni(assignee[0].name[0]), _(hints.team.language)))) elif hints.team.code in hints.domain.ext: msg = _("""\ This domain is assigned to a translator who is not a member of the TP, so there is no need for the team to work on this file.""") if extstat: msg = _("""\ %s The current version has %d%% of its messages translated.""") % (msg, percent) write(messages.refill("""\ %s """) % msg) else: write(messages.refill(_("""\ No one in your team is currently assigned to textual domain '%s'. If you decide to translate this package to the %s language, please inform your team leader, who will inform the translation coordinator that you were assigned to '%s'. """) % (hints.domain.name, _(hints.team.language), hints.domain.name))) # Show where to send submission only if there's still work to be done. if (untranslated > 0) and (hints.team.code not in hints.domain.ext): write(messages.refill(_("""\ Once the translation is complete, send the result to , using the Subject line: %s-%s.%s.po """) % (hints.domain.name, hints.version.name, hints.team.name))) # Show either the URLs of updated HTML pages or the URL of the tarball. if (not fromnewpot): write(_("""\ The following HTML pages have been updated: %s/domain/%s.html %s/team/%s.html """) % (registry.puburl, hints.domain.name, registry.puburl, hints.team.name)) elif hints.domain.url: write(_(""" You can find a tarball of the package at: """)) for url in hints.domain.url: write(' %s\n' % url) underwrite(write) def email_file(address, file_name, option = None): file = open_output() def write(msg): return file.write(messages.translate(msg, 'en')) name = re.sub('.*/', '', file_name) write(_("""\ From: Translation Project Robot To: %s Subject: Contents of PO file '%s' """) % (address, name)) if option == "compress": name += ".gz" write(_("""\ MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename=%s Content-Transfer-Encoding: base64 """) % name) if option == "compress": write(commands.getoutput('gzip -c %s | recode ../64' % file_name)) else: write(commands.getoutput('recode ../64 <%s' % file_name)) write('\n') write(_("""\ --=-=-= The Translation Project robot, in the name of your translation coordinator. --=-=-=-- """)) if __name__ == '__main__': apply(main, tuple(sys.argv[1:]))