mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 07:20:44 -04:00
234 lines
8.3 KiB
Python
234 lines
8.3 KiB
Python
#!/usr/bin/env python
|
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
|
from __future__ import with_statement
|
|
|
|
__license__ = 'GPL v3'
|
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
import os, cStringIO, tempfile, shutil, atexit, subprocess, glob, re
|
|
from distutils import sysconfig
|
|
|
|
from setup import Command, __appname__
|
|
from setup.pygettext import main as pygettext
|
|
from setup.build_environment import pyqt
|
|
|
|
class POT(Command):
|
|
|
|
description = 'Update the .pot translation template'
|
|
PATH = os.path.join(Command.SRC, __appname__, 'translations')
|
|
|
|
def source_files(self):
|
|
ans = []
|
|
for root, _, files in os.walk(os.path.dirname(self.PATH)):
|
|
for name in files:
|
|
if name.endswith('.py'):
|
|
ans.append(os.path.abspath(os.path.join(root, name)))
|
|
return ans
|
|
|
|
|
|
def run(self, opts):
|
|
files = self.source_files()
|
|
buf = cStringIO.StringIO()
|
|
self.info('Creating translations template...')
|
|
tempdir = tempfile.mkdtemp()
|
|
atexit.register(shutil.rmtree, tempdir)
|
|
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
|
src = buf.getvalue()
|
|
pot = os.path.join(self.PATH, __appname__+'.pot')
|
|
f = open(pot, 'wb')
|
|
f.write(src)
|
|
f.close()
|
|
self.info('Translations template:', os.path.abspath(pot))
|
|
return pot
|
|
|
|
|
|
class Translations(POT):
|
|
description='''Compile the translations'''
|
|
DEST = os.path.join(os.path.dirname(POT.SRC), 'resources', 'localization',
|
|
'locales')
|
|
|
|
def po_files(self):
|
|
return glob.glob(os.path.join(self.PATH, '*.po'))
|
|
|
|
def mo_file(self, po_file):
|
|
locale = os.path.splitext(os.path.basename(po_file))[0]
|
|
return locale, os.path.join(self.DEST, locale, 'LC_MESSAGES', 'messages.mo')
|
|
|
|
|
|
def run(self, opts):
|
|
for f in self.po_files():
|
|
locale, dest = self.mo_file(f)
|
|
base = os.path.dirname(dest)
|
|
if not os.path.exists(base):
|
|
os.makedirs(base)
|
|
if self.newer(dest, f):
|
|
self.info('\tCompiling translations for', locale)
|
|
subprocess.check_call(['msgfmt', '-o', dest, f])
|
|
if locale in ('en_GB', 'nds', 'te', 'yi'):
|
|
continue
|
|
pycountry = self.j(sysconfig.get_python_lib(), 'pycountry',
|
|
'locales', locale, 'LC_MESSAGES')
|
|
if os.path.exists(pycountry):
|
|
iso639 = self.j(pycountry, 'iso639.mo')
|
|
dest = self.j(self.d(dest), self.b(iso639))
|
|
if self.newer(dest, iso639):
|
|
self.info('\tCopying ISO 639 translations')
|
|
shutil.copy2(iso639, dest)
|
|
else:
|
|
self.warn('No ISO 639 translations for locale:', locale,
|
|
'\nDo you have pycountry installed?')
|
|
|
|
base = os.path.join(pyqt.qt_data_dir, 'translations')
|
|
qt_translations = glob.glob(os.path.join(base, 'qt_*.qm'))
|
|
if not qt_translations:
|
|
raise Exception('Could not find qt translations')
|
|
for f in qt_translations:
|
|
locale = self.s(self.b(f))[0][3:]
|
|
dest = self.j(self.DEST, locale, 'LC_MESSAGES', 'qt.qm')
|
|
if self.e(self.d(dest)) and self.newer(dest, f):
|
|
self.info('\tCopying Qt translation for locale:', locale)
|
|
shutil.copy2(f, dest)
|
|
|
|
self.write_stats()
|
|
|
|
@property
|
|
def stats(self):
|
|
return self.j(self.d(self.DEST), 'stats.pickle')
|
|
|
|
def get_stats(self, path):
|
|
return subprocess.Popen(['msgfmt', '--statistics', '-o', '/dev/null',
|
|
path],
|
|
stderr=subprocess.PIPE).stderr.read()
|
|
|
|
def write_stats(self):
|
|
files = self.po_files()
|
|
dest = self.stats
|
|
if not self.newer(dest, files):
|
|
return
|
|
self.info('Calculating translation statistics...')
|
|
raw = self.get_stats(self.j(self.PATH, 'calibre.pot'))
|
|
total = int(raw.split(',')[-1].strip().split()[0])
|
|
stats = {}
|
|
for f in files:
|
|
raw = self.get_stats(f)
|
|
trans = int(raw.split()[0])
|
|
locale = self.mo_file(f)[0]
|
|
stats[locale] = min(1.0, float(trans)/total)
|
|
|
|
|
|
import cPickle
|
|
cPickle.dump(stats, open(dest, 'wb'), -1)
|
|
|
|
def clean(self):
|
|
if os.path.exists(self.stats):
|
|
os.remove(self.stats)
|
|
for f in self.po_files():
|
|
l, d = self.mo_file(f)
|
|
i = self.j(self.d(d), 'iso639.mo')
|
|
j = self.j(self.d(d), 'qt.qm')
|
|
for x in (i, j, d):
|
|
if os.path.exists(x):
|
|
os.remove(x)
|
|
|
|
|
|
class GetTranslations(Translations):
|
|
|
|
description = 'Get updated translations from Launchpad'
|
|
BRANCH = 'lp:~kovid/calibre/translations'
|
|
|
|
@classmethod
|
|
def modified_translations(cls):
|
|
raw = subprocess.Popen(['bzr', 'status'],
|
|
stdout=subprocess.PIPE).stdout.read().strip()
|
|
for line in raw.splitlines():
|
|
line = line.strip()
|
|
if line.startswith(cls.PATH) and line.endswith('.po'):
|
|
yield line
|
|
|
|
def run(self, opts):
|
|
if len(list(self.modified_translations())) == 0:
|
|
subprocess.check_call(['bzr', 'merge', self.BRANCH])
|
|
if len(list(self.modified_translations())) == 0:
|
|
print 'No updated translations available'
|
|
else:
|
|
subprocess.check_call(['bzr', 'commit', '-m',
|
|
'IGN:Updated translations', self.PATH])
|
|
self.check_for_errors()
|
|
|
|
@classmethod
|
|
def check_for_errors(cls):
|
|
errors = os.path.join(tempfile.gettempdir(), 'calibre-translation-errors')
|
|
if os.path.exists(errors):
|
|
shutil.rmtree(errors)
|
|
os.mkdir(errors)
|
|
pofilter = ('pofilter', '-i', cls.PATH, '-o', errors,
|
|
'-t', 'accelerators', '-t', 'escapes', '-t', 'variables',
|
|
#'-t', 'xmltags',
|
|
#'-t', 'brackets',
|
|
#'-t', 'emails',
|
|
#'-t', 'doublequoting',
|
|
#'-t', 'filepaths',
|
|
#'-t', 'numbers',
|
|
'-t', 'options',
|
|
#'-t', 'urls',
|
|
'-t', 'printf')
|
|
subprocess.check_call(pofilter)
|
|
errfiles = glob.glob(errors+os.sep+'*.po')
|
|
subprocess.check_call(['gvim', '-f', '-p', '--']+errfiles)
|
|
for f in errfiles:
|
|
with open(f, 'r+b') as f:
|
|
raw = f.read()
|
|
raw = re.sub(r'# \(pofilter\).*', '', raw)
|
|
f.seek(0)
|
|
f.truncate()
|
|
f.write(raw)
|
|
|
|
subprocess.check_call(['pomerge', '-t', cls.PATH, '-i', errors, '-o',
|
|
cls.PATH])
|
|
if len(list(cls.modified_translations())) > 0:
|
|
subprocess.call(['bzr', 'diff', cls.PATH])
|
|
yes = raw_input('Merge corrections? [y/n]: ').strip()
|
|
if yes in ['', 'y']:
|
|
subprocess.check_call(['bzr', 'commit', '-m',
|
|
'IGN:Translation corrections', cls.PATH])
|
|
|
|
|
|
class ISO639(Command):
|
|
|
|
description = 'Compile translations for ISO 639 codes'
|
|
XML = '/usr/lib/python2.6/site-packages/pycountry/databases/iso639.xml'
|
|
|
|
def run(self, opts):
|
|
src = self.XML
|
|
if not os.path.exists(src):
|
|
raise Exception(src + ' does not exist')
|
|
dest = self.j(self.d(self.SRC), 'resources', 'localization',
|
|
'iso639.pickle')
|
|
if not self.newer(dest, src):
|
|
self.info('Pickled code is up to date')
|
|
return
|
|
self.info('Pickling ISO-639 codes to', dest)
|
|
from lxml import etree
|
|
root = etree.fromstring(open(src, 'rb').read())
|
|
by_2 = {}
|
|
by_3b = {}
|
|
by_3t = {}
|
|
codes2, codes3t, codes3b = set([]), set([]), set([])
|
|
for x in root.xpath('//iso_639_entry'):
|
|
name = x.get('name')
|
|
two = x.get('iso_639_1_code', None)
|
|
if two is not None:
|
|
by_2[two] = name
|
|
codes2.add(two)
|
|
by_3b[x.get('iso_639_2B_code')] = name
|
|
by_3t[x.get('iso_639_2T_code')] = name
|
|
codes3b.add(x.get('iso_639_2B_code'))
|
|
codes3t.add(x.get('iso_639_2T_code'))
|
|
|
|
from cPickle import dump
|
|
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
|
|
'codes3b':codes3b, 'codes3t':codes3t}
|
|
dump(x, open(dest, 'wb'), -1)
|
|
|