#!/usr/bin/env python # -*- coding: utf-8 -*- """ Generate POT ~~~~~~~~~~~~ Generate a pot file with all translations for PyGTK applications based on the organization idea I shared in my article about i18n in PyGTK applications. :copyright: 2006-2007 by Armin Ronacher. :license: GNU GPL, see LICENSE for more details. """ import os import sys from xml.dom import minidom from compiler import parse, ast from datetime import datetime PO_HEADER = """# # %(name)s Language File # msgid "" msgstr "" "Project-Id-Version: %(name)s\\n" "POT-Creation-Date: %(time)s\\n" "Last-Translator: Roman Dobosz \\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=utf-8\\n" "Content-Transfer-Encoding: utf-8\\n" "Generated-By: %(filename)s\\n"\ """ EMPTY_STRING = '' EMPTY_LINE = ['""\n'] LINE_SHIFT = ['\\n"\n"'] class StringCollection(object): """Class for collecting strings.""" def __init__(self, basename): self.db = {} self.order = [] self.offset = len(basename) def feed(self, file, line, string): name = file[self.offset:].lstrip('/') if string not in self.db: self.db[string] = [(name, line)] self.order.append(string) else: self.db[string].append((name, line)) def __iter__(self): for string in self.order: yield string, self.db[string] def quote(s): """Quotes a given string so that it is useable in a .po file.""" result = ['"'] firstmatch = True for char in s: if char == '\n': if firstmatch: result = EMPTY_LINE + result firstmatch = False result += LINE_SHIFT continue if char in '\t"': result.append('\\') result.append(char) result.append('"') return EMPTY_STRING.join(result) def scan_python_file(filename, calls): """Scan a python file for gettext calls.""" def scan(nodelist): for node in nodelist: if isinstance(node, ast.CallFunc): handle = False for pos, n in enumerate(node): if pos == 0: if isinstance(n, ast.Name) and n.name in calls: handle = True elif pos == 1: if handle: if n.__class__ is ast.Const and \ isinstance(n.value, basestring): yield n.lineno, n.value break else: for line in scan([n]): yield line elif hasattr(node, '__iter__'): for n in scan(node): yield n fp = file(filename) try: try: return scan(parse(fp.read())) except: print >> sys.stderr, 'Syntax Error in file %r' % filename finally: fp.close() def scan_glade_file(filename): """Scan a glade file for translatable strings.""" try: doc = minidom.parse(filename) except: print >> sys.stderr, 'Syntax Error in file %r' % filename for element in doc.getElementsByTagName('property'): if element.getAttribute('translatable') == 'yes': data = element.firstChild.nodeValue if data and not data.startswith('gtk-'): yield data def scan_tree(pathname, calls=['_']): """Scans a tree for translatable strings.""" out = StringCollection(pathname) for folder, _, files in os.walk(pathname): for filename in files: filename = os.path.join(folder, filename) if filename.endswith('.py'): result = scan_python_file(filename, calls) if result is not None: for lineno, string in result: out.feed(filename, lineno, string) elif filename.endswith('.glade'): result = scan_glade_file(filename) if result is not None: for string in result: out.feed(filename, None, string) for line in out: yield line def run(): if len(sys.argv) != 3: print 'usage: %s ' % sys.argv[0] sys.exit() print PO_HEADER % { 'time': datetime.now(), 'filename': sys.argv[0], 'name': sys.argv[2], } basepath = sys.argv[1] for string, occurrences in scan_tree(basepath): print for path, lineno in occurrences: print '#. file %r, line %s' % (path, lineno or '?') print 'msgid %s' % quote(string) print 'msgstr ""' if __name__ == '__main__': run()