2012-02-27 10:05:53 +01:00
|
|
|
from django.db import models
|
2012-02-27 11:39:00 +01:00
|
|
|
from django.conf import settings
|
2012-02-27 10:46:12 +01:00
|
|
|
from lang.models import Language
|
2012-02-27 15:40:31 +01:00
|
|
|
from glob import glob
|
2012-02-27 11:39:00 +01:00
|
|
|
import os
|
|
|
|
import os.path
|
2012-02-27 18:02:34 +01:00
|
|
|
import logging
|
2012-02-27 13:34:51 +01:00
|
|
|
import git
|
2012-02-27 17:12:08 +01:00
|
|
|
from translate.storage import factory
|
2012-02-27 10:13:42 +01:00
|
|
|
|
2012-02-27 17:50:47 +01:00
|
|
|
from trans.managers import TranslationManager, UnitManager
|
2012-02-27 17:54:14 +01:00
|
|
|
from util import is_plural, split_plural, join_plural
|
2012-02-27 11:08:16 +01:00
|
|
|
|
2012-02-27 18:02:34 +01:00
|
|
|
logger = logging.getLogger('weblate')
|
|
|
|
|
2012-02-27 10:13:42 +01:00
|
|
|
class Project(models.Model):
|
|
|
|
name = models.CharField(max_length = 100)
|
|
|
|
slug = models.SlugField(db_index = True)
|
|
|
|
web = models.URLField()
|
|
|
|
mail = models.EmailField()
|
|
|
|
instructions = models.URLField()
|
2012-02-27 10:39:01 +01:00
|
|
|
|
2012-02-28 10:11:06 +01:00
|
|
|
class Meta:
|
|
|
|
ordering = ['name']
|
|
|
|
|
2012-02-27 11:11:37 +01:00
|
|
|
@models.permalink
|
|
|
|
def get_absolute_url(self):
|
2012-02-28 10:32:07 +01:00
|
|
|
return ('trans.views.show_project', (), {
|
|
|
|
'project': self.slug
|
|
|
|
})
|
2012-02-27 11:39:00 +01:00
|
|
|
|
|
|
|
def get_path(self):
|
|
|
|
return os.path.join(settings.GIT_ROOT, self.slug)
|
|
|
|
|
2012-02-27 11:45:27 +01:00
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
|
|
|
|
2012-02-27 11:39:00 +01:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
# Create filesystem directory for storing data
|
|
|
|
p = self.get_path()
|
|
|
|
if not os.path.exists(p):
|
|
|
|
os.makedirs(p)
|
2012-02-27 13:34:51 +01:00
|
|
|
|
2012-02-27 11:39:00 +01:00
|
|
|
super(Project, self).save(*args, **kwargs)
|
2012-02-27 11:11:37 +01:00
|
|
|
|
2012-02-27 10:39:01 +01:00
|
|
|
class SubProject(models.Model):
|
|
|
|
name = models.CharField(max_length = 100)
|
|
|
|
slug = models.SlugField(db_index = True)
|
|
|
|
project = models.ForeignKey(Project)
|
|
|
|
repo = models.CharField(max_length = 200)
|
|
|
|
branch = models.CharField(max_length = 50)
|
2012-02-27 11:53:18 +01:00
|
|
|
filemask = models.CharField(max_length = 200)
|
2012-02-27 11:55:12 +01:00
|
|
|
style_choices = (('po', 'GNU Gettext'), ('ts', 'Qt TS'))
|
2012-02-27 11:54:27 +01:00
|
|
|
style = models.CharField(max_length = 10, choices = style_choices)
|
2012-02-27 10:46:12 +01:00
|
|
|
|
2012-02-28 10:11:06 +01:00
|
|
|
class Meta:
|
|
|
|
ordering = ['name']
|
|
|
|
|
2012-02-27 11:11:37 +01:00
|
|
|
@models.permalink
|
|
|
|
def get_absolute_url(self):
|
2012-02-28 10:32:07 +01:00
|
|
|
return ('trans.views.show_subproject', (), {
|
|
|
|
'project': self.project.slug,
|
|
|
|
'subproject': self.slug
|
|
|
|
})
|
2012-02-27 11:11:37 +01:00
|
|
|
|
2012-02-27 11:45:27 +01:00
|
|
|
def __unicode__(self):
|
2012-02-27 13:34:51 +01:00
|
|
|
return '%s/%s' % (self.project.__unicode__(), self.name)
|
|
|
|
|
|
|
|
def get_path(self):
|
|
|
|
return os.path.join(self.project.get_path(), self.slug)
|
|
|
|
|
2012-02-27 15:03:26 +01:00
|
|
|
def get_repo(self):
|
|
|
|
'''
|
|
|
|
Gets Git repository object.
|
|
|
|
'''
|
2012-02-27 13:34:51 +01:00
|
|
|
p = self.get_path()
|
|
|
|
try:
|
2012-02-27 15:03:26 +01:00
|
|
|
return git.Repo(p)
|
|
|
|
except:
|
|
|
|
return git.Repo.init(p)
|
|
|
|
|
|
|
|
def configure_repo(self):
|
|
|
|
'''
|
|
|
|
Ensures repository is correctly configured and points to current remote.
|
|
|
|
'''
|
|
|
|
# Create/Open repo
|
|
|
|
repo = self.get_repo()
|
|
|
|
# Get/Create origin remote
|
|
|
|
try:
|
|
|
|
origin = repo.remotes.origin
|
|
|
|
except:
|
2012-02-27 15:40:10 +01:00
|
|
|
repo.git.remote('add', 'origin', self.repo)
|
|
|
|
origin = repo.remotes.origin
|
2012-02-27 15:03:26 +01:00
|
|
|
# Check remote source
|
|
|
|
if origin.url != self.repo:
|
2012-02-27 15:40:10 +01:00
|
|
|
repo.git.remote('set-url', 'origin', self.repo)
|
2012-02-27 15:03:26 +01:00
|
|
|
# Update
|
2012-02-28 10:17:43 +01:00
|
|
|
logger.info('updating repo %s', self.__unicode__())
|
2012-02-27 15:40:10 +01:00
|
|
|
repo.git.remote('update', 'origin')
|
2012-02-27 15:03:26 +01:00
|
|
|
|
|
|
|
def configure_branch(self):
|
|
|
|
'''
|
|
|
|
Ensures local tracking branch exists and is checkouted.
|
|
|
|
'''
|
|
|
|
repo = self.get_repo()
|
|
|
|
try:
|
|
|
|
head = repo.heads[self.branch]
|
|
|
|
except:
|
|
|
|
repo.git.branch('--track', self.branch, 'origin/%s' % self.branch)
|
|
|
|
head = repo.heads[self.branch]
|
|
|
|
repo.git.checkout(self.branch)
|
|
|
|
|
|
|
|
def update_branch(self):
|
|
|
|
'''
|
|
|
|
Updates current branch to match remote (if possible).
|
|
|
|
'''
|
|
|
|
repo = self.get_repo()
|
|
|
|
repo.remotes.origin.pull()
|
|
|
|
try:
|
|
|
|
repo.git.merge('origin/%s' % self.branch)
|
2012-02-27 13:34:51 +01:00
|
|
|
except:
|
2012-02-27 15:03:26 +01:00
|
|
|
repo.git.merge('--abort')
|
2012-02-28 10:17:43 +01:00
|
|
|
logger.warning('failed merge on repo %s', self.__unicode__())
|
2012-02-27 15:03:26 +01:00
|
|
|
|
2012-02-27 15:44:06 +01:00
|
|
|
def get_translation_blobs(self):
|
2012-02-27 15:40:31 +01:00
|
|
|
'''
|
2012-02-27 15:44:06 +01:00
|
|
|
Scans directory for translation blobs and returns them as list.
|
2012-02-27 15:40:31 +01:00
|
|
|
'''
|
|
|
|
repo = self.get_repo()
|
|
|
|
tree = repo.tree()
|
|
|
|
|
|
|
|
# Glob files
|
|
|
|
files = glob(os.path.join(self.get_path(), self.filemask))
|
|
|
|
prefix = os.path.join(self.get_path(), '')
|
|
|
|
files = [f.replace(prefix, '') for f in files]
|
|
|
|
|
|
|
|
# Get blobs for files
|
2012-02-27 16:09:31 +01:00
|
|
|
return [(self.get_lang_code(f), f, tree[f]) for f in files]
|
2012-02-27 15:40:31 +01:00
|
|
|
|
2012-02-27 15:44:06 +01:00
|
|
|
def create_translations(self):
|
|
|
|
'''
|
|
|
|
Loads translations from git.
|
|
|
|
'''
|
|
|
|
blobs = self.get_translation_blobs()
|
2012-02-27 16:09:31 +01:00
|
|
|
for code, path, blob in blobs:
|
2012-02-27 18:02:34 +01:00
|
|
|
logger.info('processing %s', path)
|
2012-02-27 16:09:31 +01:00
|
|
|
Translation.objects.update_from_blob(self, code, path, blob)
|
|
|
|
|
|
|
|
def get_lang_code(self, path):
|
|
|
|
'''
|
|
|
|
Parses language code from path.
|
|
|
|
'''
|
|
|
|
parts = self.filemask.split('*')
|
|
|
|
return path[len(parts[0]):-len(parts[1])]
|
2012-02-27 15:40:31 +01:00
|
|
|
|
2012-02-27 15:03:26 +01:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.configure_repo()
|
|
|
|
self.configure_branch()
|
|
|
|
self.update_branch()
|
2012-02-27 15:44:06 +01:00
|
|
|
self.create_translations()
|
2012-02-27 13:34:51 +01:00
|
|
|
|
|
|
|
super(SubProject, self).save(*args, **kwargs)
|
2012-02-27 11:45:27 +01:00
|
|
|
|
2012-02-27 10:46:12 +01:00
|
|
|
class Translation(models.Model):
|
|
|
|
subproject = models.ForeignKey(SubProject)
|
|
|
|
language = models.ForeignKey(Language)
|
2012-02-28 11:24:53 +01:00
|
|
|
translated = models.FloatField(default = 0, db_index = True)
|
|
|
|
fuzzy = models.FloatField(default = 0, db_index = True)
|
2012-02-27 16:09:31 +01:00
|
|
|
revision = models.CharField(max_length = 40, default = '', blank = True)
|
2012-02-27 10:46:12 +01:00
|
|
|
filename = models.CharField(max_length = 200)
|
2012-02-27 10:58:59 +01:00
|
|
|
|
2012-02-27 16:09:31 +01:00
|
|
|
objects = TranslationManager()
|
|
|
|
|
2012-02-28 10:11:06 +01:00
|
|
|
class Meta:
|
|
|
|
ordering = ['language__name']
|
|
|
|
|
2012-02-27 11:11:37 +01:00
|
|
|
@models.permalink
|
|
|
|
def get_absolute_url(self):
|
2012-02-28 10:32:07 +01:00
|
|
|
return ('trans.views.show_translation', (), {
|
|
|
|
'project': self.subproject.project.slug,
|
|
|
|
'subproject': self.subproject.slug,
|
|
|
|
'lang': self.language.code
|
|
|
|
})
|
2012-02-27 11:11:37 +01:00
|
|
|
|
2012-02-27 11:45:27 +01:00
|
|
|
def __unicode__(self):
|
2012-02-28 09:26:31 +01:00
|
|
|
return '%s %s' % (self.language.name, self.subproject.__unicode__())
|
2012-02-27 11:45:27 +01:00
|
|
|
|
2012-02-27 16:29:32 +01:00
|
|
|
def update_from_blob(self, blob):
|
|
|
|
'''
|
|
|
|
Updates translation data from blob.
|
|
|
|
'''
|
|
|
|
# Check if we're not already up to date
|
|
|
|
if self.revision == blob.hexsha:
|
|
|
|
return
|
|
|
|
|
|
|
|
oldunits = set(self.unit_set.all().values_list('id', flat = True))
|
|
|
|
|
2012-02-27 17:12:08 +01:00
|
|
|
# Load po file
|
|
|
|
store = factory.getobject(os.path.join(self.subproject.get_path(), self.filename))
|
|
|
|
for unit in store.units:
|
2012-02-28 10:27:29 +01:00
|
|
|
if unit.isheader():
|
|
|
|
continue
|
2012-02-27 17:50:47 +01:00
|
|
|
newunit = Unit.objects.update_from_unit(self, unit)
|
|
|
|
try:
|
|
|
|
oldunits.remove(newunit.id)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Delete not used units
|
|
|
|
Unit.objects.filter(translation = self, id__in = oldunits).delete()
|
2012-02-27 17:12:08 +01:00
|
|
|
|
2012-02-28 10:17:57 +01:00
|
|
|
# Update revision and stats
|
|
|
|
total = self.unit_set.count()
|
|
|
|
fuzzy = self.unit_set.filter(fuzzy = True).count()
|
|
|
|
translated = self.unit_set.filter(translated = True).count()
|
2012-02-28 10:27:59 +01:00
|
|
|
self.fuzzy = round(fuzzy * 100.0 / total, 1)
|
|
|
|
self.translated = round(translated * 100.0 / total, 1)
|
2012-02-27 17:12:08 +01:00
|
|
|
self.revision = blob.hexsha
|
2012-02-27 17:50:47 +01:00
|
|
|
self.save()
|
2012-02-27 17:12:08 +01:00
|
|
|
|
2012-02-27 16:29:32 +01:00
|
|
|
|
2012-02-27 10:58:59 +01:00
|
|
|
class Unit(models.Model):
|
|
|
|
translation = models.ForeignKey(Translation)
|
2012-02-28 11:24:53 +01:00
|
|
|
checksum = models.CharField(max_length = 40, default = '', blank = True, db_index = True)
|
2012-02-27 17:50:47 +01:00
|
|
|
location = models.TextField(default = '', blank = True)
|
|
|
|
context = models.TextField(default = '', blank = True)
|
|
|
|
flags = models.TextField(default = '', blank = True)
|
2012-02-27 10:58:59 +01:00
|
|
|
source = models.TextField()
|
2012-02-27 17:50:47 +01:00
|
|
|
target = models.TextField(default = '', blank = True)
|
2012-02-28 11:24:53 +01:00
|
|
|
fuzzy = models.BooleanField(default = False, db_index = True)
|
|
|
|
translated = models.BooleanField(default = False, db_index = True)
|
2012-02-27 17:50:47 +01:00
|
|
|
|
|
|
|
objects = UnitManager()
|
|
|
|
|
2012-02-27 18:05:42 +01:00
|
|
|
def update_from_unit(self, unit, force):
|
2012-02-27 17:50:47 +01:00
|
|
|
location = ', '.join(unit.getlocations())
|
|
|
|
flags = '' # FIXME
|
2012-02-27 17:56:06 +01:00
|
|
|
target = join_plural(unit.target.strings)
|
2012-02-27 17:50:47 +01:00
|
|
|
fuzzy = unit.isfuzzy()
|
2012-02-28 10:13:43 +01:00
|
|
|
translated = unit.istranslated()
|
|
|
|
if not force and location == self.location and flags == self.flags and target == self.target and fuzzy == self.fuzzy and translated == self.translated:
|
2012-02-27 17:50:47 +01:00
|
|
|
return
|
|
|
|
self.location = location
|
|
|
|
self.flags = flags
|
|
|
|
self.target = target
|
|
|
|
self.fuzzy = fuzzy
|
2012-02-28 10:13:43 +01:00
|
|
|
self.translated = translated
|
2012-02-27 18:05:42 +01:00
|
|
|
self.save(force_insert = force)
|
2012-02-27 11:08:16 +01:00
|
|
|
|
|
|
|
def is_plural(self):
|
2012-02-27 17:54:14 +01:00
|
|
|
return is_plural(self.source)
|
2012-02-27 11:08:16 +01:00
|
|
|
|
|
|
|
def get_source_plurals(self):
|
2012-02-27 17:54:14 +01:00
|
|
|
return split_plural(self.source)
|
2012-02-27 11:08:16 +01:00
|
|
|
|
|
|
|
def get_target_plurals(self):
|
2012-02-27 17:54:14 +01:00
|
|
|
ret = split_plural(self.target)
|
2012-02-27 11:08:16 +01:00
|
|
|
plurals = self.translation.language.nplurals
|
|
|
|
if len(ret) == plurals:
|
|
|
|
return ret
|
|
|
|
|
|
|
|
while len(ret) < plurals:
|
|
|
|
ret.append('')
|
|
|
|
|
|
|
|
while len(ret) > plurals:
|
|
|
|
del(ret[-1])
|
|
|
|
|
|
|
|
return ret
|