add db based translations
load translations from files and adds option to translate string layers (i.e. keywords)
This commit is contained in:
parent
0a4c507346
commit
f93ece1ab7
22 changed files with 682 additions and 1 deletions
|
@ -187,6 +187,7 @@ class Annotation(models.Model):
|
||||||
if not delay_matches:
|
if not delay_matches:
|
||||||
self.update_matches()
|
self.update_matches()
|
||||||
self.update_documents()
|
self.update_documents()
|
||||||
|
self.update_translations()
|
||||||
|
|
||||||
def update_matches(self):
|
def update_matches(self):
|
||||||
from place.models import Place
|
from place.models import Place
|
||||||
|
@ -265,6 +266,15 @@ class Annotation(models.Model):
|
||||||
for document in Document.objects.filter(id__in=added):
|
for document in Document.objects.filter(id__in=added):
|
||||||
self.documents.add(document)
|
self.documents.add(document)
|
||||||
|
|
||||||
|
def update_translations(self):
|
||||||
|
from translation.models import Translation
|
||||||
|
layer = self.get_layer()
|
||||||
|
if layer.get('translate'):
|
||||||
|
t, created = Translation.objects.get_or_create(lang=lang, key=self.value)
|
||||||
|
if created:
|
||||||
|
t.type = Translation.CONTENT
|
||||||
|
t.save()
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
super(Annotation, self).delete(*args, **kwargs)
|
super(Annotation, self).delete(*args, **kwargs)
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
"canManageHome": {},
|
"canManageHome": {},
|
||||||
"canManagePlacesAndEvents": {"staff": true, "admin": true},
|
"canManagePlacesAndEvents": {"staff": true, "admin": true},
|
||||||
"canManageTitlesAndNames": {"staff": true, "admin": true},
|
"canManageTitlesAndNames": {"staff": true, "admin": true},
|
||||||
|
"canManageTranslations": {"admin": true},
|
||||||
"canManageUsers": {"staff": true, "admin": true},
|
"canManageUsers": {"staff": true, "admin": true},
|
||||||
"canPlayClips": {"guest": 2, "member": 2, "friend": 4, "staff": 4, "admin": 4},
|
"canPlayClips": {"guest": 2, "member": 2, "friend": 4, "staff": 4, "admin": 4},
|
||||||
"canPlayVideo": {"guest": 1, "member": 1, "friend": 4, "staff": 4, "admin": 4},
|
"canPlayVideo": {"guest": 1, "member": 1, "friend": 4, "staff": 4, "admin": 4},
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
"canManageHome": {"staff": true, "admin": true},
|
"canManageHome": {"staff": true, "admin": true},
|
||||||
"canManagePlacesAndEvents": {"member": true, "researcher": true, "staff": true, "admin": true},
|
"canManagePlacesAndEvents": {"member": true, "researcher": true, "staff": true, "admin": true},
|
||||||
"canManageTitlesAndNames": {"member": true, "researcher": true, "staff": true, "admin": true},
|
"canManageTitlesAndNames": {"member": true, "researcher": true, "staff": true, "admin": true},
|
||||||
|
"canManageTranslations": {"admin": true},
|
||||||
"canManageUsers": {"staff": true, "admin": true},
|
"canManageUsers": {"staff": true, "admin": true},
|
||||||
"canPlayClips": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
|
"canPlayClips": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
|
||||||
"canPlayVideo": {"guest": 1, "member": 1, "researcher": 3, "staff": 3, "admin": 3},
|
"canPlayVideo": {"guest": 1, "member": 1, "researcher": 3, "staff": 3, "admin": 3},
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
"canManageHome": {"staff": true, "admin": true},
|
"canManageHome": {"staff": true, "admin": true},
|
||||||
"canManagePlacesAndEvents": {"member": true, "staff": true, "admin": true},
|
"canManagePlacesAndEvents": {"member": true, "staff": true, "admin": true},
|
||||||
"canManageTitlesAndNames": {"member": true, "staff": true, "admin": true},
|
"canManageTitlesAndNames": {"member": true, "staff": true, "admin": true},
|
||||||
|
"canManageTranslations": {"admin": true},
|
||||||
"canManageUsers": {"staff": true, "admin": true},
|
"canManageUsers": {"staff": true, "admin": true},
|
||||||
"canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
"canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||||
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||||
|
|
|
@ -67,6 +67,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
||||||
"canManageHome": {"staff": true, "admin": true},
|
"canManageHome": {"staff": true, "admin": true},
|
||||||
"canManagePlacesAndEvents": {"member": true, "staff": true, "admin": true},
|
"canManagePlacesAndEvents": {"member": true, "staff": true, "admin": true},
|
||||||
"canManageTitlesAndNames": {"member": true, "staff": true, "admin": true},
|
"canManageTitlesAndNames": {"member": true, "staff": true, "admin": true},
|
||||||
|
"canManageTranslations": {"admin": true},
|
||||||
"canManageUsers": {"staff": true, "admin": true},
|
"canManageUsers": {"staff": true, "admin": true},
|
||||||
"canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
"canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||||
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||||
|
|
|
@ -141,6 +141,7 @@ INSTALLED_APPS = (
|
||||||
'news',
|
'news',
|
||||||
'user',
|
'user',
|
||||||
'urlalias',
|
'urlalias',
|
||||||
|
'translation',
|
||||||
'tv',
|
'tv',
|
||||||
'documentcollection',
|
'documentcollection',
|
||||||
'document',
|
'document',
|
||||||
|
|
0
pandora/translation/__init__.py
Normal file
0
pandora/translation/__init__.py
Normal file
3
pandora/translation/admin.py
Normal file
3
pandora/translation/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
5
pandora/translation/apps.py
Normal file
5
pandora/translation/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TranslationConfig(AppConfig):
|
||||||
|
name = 'translation'
|
107
pandora/translation/managers.py
Normal file
107
pandora/translation/managers.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.db.models import Q, Manager
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from oxdjango.managers import get_operator
|
||||||
|
from oxdjango.query import QuerySet
|
||||||
|
|
||||||
|
keymap = {
|
||||||
|
'email': 'user__email',
|
||||||
|
'user': 'username',
|
||||||
|
'group': 'user__groups__name',
|
||||||
|
'groups': 'user__groups__name',
|
||||||
|
}
|
||||||
|
default_key = 'username'
|
||||||
|
|
||||||
|
def parseCondition(condition, user):
|
||||||
|
k = condition.get('key', default_key)
|
||||||
|
k = keymap.get(k, k)
|
||||||
|
v = condition['value']
|
||||||
|
op = condition.get('operator')
|
||||||
|
if not op:
|
||||||
|
op = '='
|
||||||
|
if op.startswith('!'):
|
||||||
|
op = op[1:]
|
||||||
|
exclude = True
|
||||||
|
else:
|
||||||
|
exclude = False
|
||||||
|
|
||||||
|
if k == 'level':
|
||||||
|
levels = ['robot'] + settings.CONFIG['userLevels']
|
||||||
|
if v in levels:
|
||||||
|
v = levels.index(v) - 1
|
||||||
|
else:
|
||||||
|
v = 0
|
||||||
|
key = k + get_operator(op, 'int')
|
||||||
|
else:
|
||||||
|
key = k + get_operator(op, 'istr')
|
||||||
|
key = str(key)
|
||||||
|
q = Q(**{key: v})
|
||||||
|
if exclude:
|
||||||
|
q = ~q
|
||||||
|
return q
|
||||||
|
|
||||||
|
def parseConditions(conditions, operator, user):
|
||||||
|
'''
|
||||||
|
conditions: [
|
||||||
|
],
|
||||||
|
operator: "&"
|
||||||
|
'''
|
||||||
|
conn = []
|
||||||
|
for condition in conditions:
|
||||||
|
if 'conditions' in condition:
|
||||||
|
q = parseConditions(condition['conditions'],
|
||||||
|
condition.get('operator', '&'), user)
|
||||||
|
if q:
|
||||||
|
conn.append(q)
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
conn.append(parseCondition(condition, user))
|
||||||
|
if conn:
|
||||||
|
q = conn[0]
|
||||||
|
for c in conn[1:]:
|
||||||
|
if operator == '|':
|
||||||
|
q = q | c
|
||||||
|
else:
|
||||||
|
q = q & c
|
||||||
|
return q
|
||||||
|
return None
|
||||||
|
|
||||||
|
class TranslationManager(Manager):
|
||||||
|
|
||||||
|
def get_query_set(self):
|
||||||
|
return QuerySet(self.model)
|
||||||
|
|
||||||
|
def find(self, data, user):
|
||||||
|
'''
|
||||||
|
query: {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
value: "war"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
key: "year",
|
||||||
|
value: "1970-1980,
|
||||||
|
operator: "!="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "country",
|
||||||
|
value: "f",
|
||||||
|
operator: "^"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
operator: "&"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
#join query with operator
|
||||||
|
qs = self.get_query_set()
|
||||||
|
|
||||||
|
query = data.get('query', {})
|
||||||
|
conditions = parseConditions(query.get('conditions', []),
|
||||||
|
query.get('operator', '&'),
|
||||||
|
user)
|
||||||
|
if conditions:
|
||||||
|
qs = qs.filter(conditions)
|
||||||
|
qs = qs.distinct()
|
||||||
|
return qs
|
32
pandora/translation/migrations/0001_initial.py
Normal file
32
pandora/translation/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.13 on 2018-08-04 15:58
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Translation',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modified', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
|
('lang', models.CharField(max_length=8, verbose_name='language')),
|
||||||
|
('key', models.CharField(max_length=4096, verbose_name='key')),
|
||||||
|
('value', models.CharField(blank=True, default=None, max_length=4096, null=True, verbose_name='translation')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='translation',
|
||||||
|
unique_together=set([('key', 'lang')]),
|
||||||
|
),
|
||||||
|
]
|
20
pandora/translation/migrations/0002_translation_type.py
Normal file
20
pandora/translation/migrations/0002_translation_type.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.13 on 2018-09-19 14:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('translation', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='translation',
|
||||||
|
name='type',
|
||||||
|
field=models.IntegerField(default=0, verbose_name='type'),
|
||||||
|
),
|
||||||
|
]
|
0
pandora/translation/migrations/__init__.py
Normal file
0
pandora/translation/migrations/__init__.py
Normal file
118
pandora/translation/models.py
Normal file
118
pandora/translation/models.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.db import models
|
||||||
|
import django.utils.translation
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
import ox
|
||||||
|
|
||||||
|
from . import managers
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_key(key, lang):
|
||||||
|
return '%s-%s' % (hashlib.sha1(key.encode()).hexdigest(), lang)
|
||||||
|
|
||||||
|
|
||||||
|
def load_itemkey_translations():
|
||||||
|
from annotation.models import Annotation
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
used_keys = []
|
||||||
|
for layer in settings.CONFIG['layers']:
|
||||||
|
if layer.get('translate'):
|
||||||
|
qs = Annotation.objects.filter(layer=layer['id'])
|
||||||
|
query = qs.query
|
||||||
|
query.group_by = ['value']
|
||||||
|
for value in QuerySet(query=query, model=Annotation).values_list('value', flat=True):
|
||||||
|
for lang in settings.CONFIG['languages']:
|
||||||
|
if lang == settings.CONFIG['language']:
|
||||||
|
continue
|
||||||
|
used_keys.append(value)
|
||||||
|
t, created = Translation.objects.get_or_create(lang=lang, key=value)
|
||||||
|
if created:
|
||||||
|
t.type = Translation.CONTENT
|
||||||
|
t.save()
|
||||||
|
|
||||||
|
Translation.objects.filter(type=Translation.CONTENT).exclude(key__in=used_keys).delete()
|
||||||
|
|
||||||
|
def load_translations():
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from glob import glob
|
||||||
|
locale = {}
|
||||||
|
for file in glob('%s/json/locale.??.json' % settings.STATIC_ROOT):
|
||||||
|
lang = file.split('.')[-2]
|
||||||
|
if lang not in locale:
|
||||||
|
locale[lang] = {}
|
||||||
|
with open(os.path.join(file)) as fd:
|
||||||
|
locale[lang].update(json.load(fd))
|
||||||
|
for lang, locale in locale.items():
|
||||||
|
used_keys = []
|
||||||
|
if lang in settings.CONFIG['languages']:
|
||||||
|
for key, value in locale.items():
|
||||||
|
used_keys.append(key)
|
||||||
|
t, created = Translation.objects.get_or_create(lang=lang, key=key)
|
||||||
|
if created:
|
||||||
|
t.type = Translation.UI
|
||||||
|
t.value = value
|
||||||
|
t.save()
|
||||||
|
Translation.objects.filter(type=Translation.UI, lang=lang).exclude(key__in=used_keys).delete()
|
||||||
|
|
||||||
|
class Translation(models.Model):
|
||||||
|
CONTENT = 1
|
||||||
|
UI = 2
|
||||||
|
|
||||||
|
created = models.DateTimeField(auto_now_add=True, editable=False)
|
||||||
|
modified = models.DateTimeField(default=timezone.now, editable=False)
|
||||||
|
|
||||||
|
type = models.IntegerField('type', default=0)
|
||||||
|
lang = models.CharField('language', max_length=8)
|
||||||
|
key = models.CharField('key', max_length=4096)
|
||||||
|
value = models.CharField('translation', max_length=4096, null=True, blank=True, default=None)
|
||||||
|
|
||||||
|
objects = managers.TranslationManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('key', 'lang')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s->%s [%s]' % (self.key, self.value, self.lang)
|
||||||
|
|
||||||
|
def json(self, keys=None, user=None):
|
||||||
|
data = {
|
||||||
|
'id': ox.toAZ(self.id)
|
||||||
|
}
|
||||||
|
for key in ('key', 'lang', 'value'):
|
||||||
|
data[key] = getattr(self, key)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_translations(cls, key):
|
||||||
|
return list(cls.objects.filter(key=key).order_by('-lang').values_list('lang', flat=True))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_translation(cls, key, lang):
|
||||||
|
cache_key = get_cache_key(key, lang)
|
||||||
|
data = cache.get(cache_key)
|
||||||
|
if not data:
|
||||||
|
trans = None
|
||||||
|
for translation in cls.objects.filter(key=key, lang=lang):
|
||||||
|
trans = translation.get_value()
|
||||||
|
break
|
||||||
|
if trans is None:
|
||||||
|
cls.needs_translation(key)
|
||||||
|
trans = key
|
||||||
|
cache.set(cache_key, trans, 5*60)
|
||||||
|
return trans
|
||||||
|
return data
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
cache.delete(get_cache_key(self.key, self.lang))
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
if self.value:
|
||||||
|
return self.value
|
||||||
|
return self.key
|
20
pandora/translation/tasks.py
Normal file
20
pandora/translation/tasks.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import division, print_function, absolute_import
|
||||||
|
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
|
from celery.task import task, periodic_task
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from app.utils import limit_rate
|
||||||
|
|
||||||
|
@periodic_task(run_every=timedelta(days=1), queue='encoding')
|
||||||
|
def cronjob(**kwargs):
|
||||||
|
if limit_rate('translations.tasks.cronjob', 8 * 60 * 60):
|
||||||
|
load_translations()
|
||||||
|
|
||||||
|
@task(ignore_results=True, queue='encoding')
|
||||||
|
def load_translations():
|
||||||
|
from .models import load_itemkey_translations, load_translations
|
||||||
|
load_translations()
|
||||||
|
load_itemkey_translations()
|
3
pandora/translation/tests.py
Normal file
3
pandora/translation/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
111
pandora/translation/views.py
Normal file
111
pandora/translation/views.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from oxdjango.shortcuts import render_to_json_response, json_response, get_object_or_404_json
|
||||||
|
from oxdjango.api import actions
|
||||||
|
import ox
|
||||||
|
|
||||||
|
from item import utils
|
||||||
|
from changelog.models import add_changelog
|
||||||
|
from .models import Translation
|
||||||
|
|
||||||
|
def locale_json(request, lang):
|
||||||
|
locale = {}
|
||||||
|
for t in Translation.objects.filter(lang=lang):
|
||||||
|
if t.value:
|
||||||
|
locale[t.key] = t.value
|
||||||
|
return render_to_json_response(locale)
|
||||||
|
|
||||||
|
def editTranslation(request, data):
|
||||||
|
'''
|
||||||
|
Edits translation for a given key and language
|
||||||
|
takes {
|
||||||
|
key: string, // name key
|
||||||
|
lang: string // language i.e. en
|
||||||
|
value: string // translated value
|
||||||
|
}
|
||||||
|
returns {
|
||||||
|
id: string, // name id
|
||||||
|
key: string // key
|
||||||
|
... // more properties
|
||||||
|
}
|
||||||
|
see: findTranslations
|
||||||
|
'''
|
||||||
|
response = json_response()
|
||||||
|
if not data['value']:
|
||||||
|
Translation.objects.filter(id=ox.fromAZ(data['id'])).delete()
|
||||||
|
else:
|
||||||
|
trans, created = Translation.objects.get_or_create(id=ox.fromAZ(data['id']))
|
||||||
|
trans.value = data['value']
|
||||||
|
trans.save()
|
||||||
|
response['data'] = trans.json()
|
||||||
|
add_changelog(request, data)
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(editTranslation)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_query(data, user):
|
||||||
|
query = {}
|
||||||
|
query['range'] = [0, 100]
|
||||||
|
query['sort'] = [{'key':'key', 'operator':'+'}]
|
||||||
|
for key in ('keys', 'range', 'sort', 'query'):
|
||||||
|
if key in data:
|
||||||
|
query[key] = data[key]
|
||||||
|
query['qs'] = Translation.objects.find(query, user)
|
||||||
|
return query
|
||||||
|
|
||||||
|
def order_query(qs, sort):
|
||||||
|
order_by = []
|
||||||
|
for e in sort:
|
||||||
|
operator = e['operator']
|
||||||
|
if operator != '-':
|
||||||
|
operator = ''
|
||||||
|
key = {}.get(e['key'], e['key'])
|
||||||
|
order = '%s%s' % (operator, key)
|
||||||
|
order_by.append(order)
|
||||||
|
if order_by:
|
||||||
|
qs = qs.order_by(*order_by, nulls_last=True)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def findTranslations(request, data):
|
||||||
|
'''
|
||||||
|
Finds translations for a given query
|
||||||
|
takes {
|
||||||
|
query: object, // query object, see `find`
|
||||||
|
sort: [object], // list of sort objects, see `find`
|
||||||
|
range: [int, int], // range of results to return
|
||||||
|
keys: [string] // list of properties to return
|
||||||
|
}
|
||||||
|
returns {
|
||||||
|
items: [object] // list of translation objects
|
||||||
|
}
|
||||||
|
see: editTranslation
|
||||||
|
'''
|
||||||
|
response = json_response()
|
||||||
|
|
||||||
|
query = parse_query(data, request.user)
|
||||||
|
qs = order_query(query['qs'], query['sort'])
|
||||||
|
qs = qs.distinct()
|
||||||
|
|
||||||
|
if 'keys' in data:
|
||||||
|
qs = qs.select_related()
|
||||||
|
qs = qs[query['range'][0]:query['range'][1]]
|
||||||
|
response['data']['items'] = [p.json(data['keys'], request.user) for p in qs]
|
||||||
|
elif 'position' in query:
|
||||||
|
ids = [i.get_id() for i in qs]
|
||||||
|
data['conditions'] = data['conditions'] + {
|
||||||
|
'value': data['position'],
|
||||||
|
'key': query['sort'][0]['key'],
|
||||||
|
'operator': '^'
|
||||||
|
}
|
||||||
|
query = parse_query(data, request.user)
|
||||||
|
qs = order_query(query['qs'], query['sort'])
|
||||||
|
if qs.count() > 0:
|
||||||
|
response['data']['position'] = utils.get_positions(ids, [qs[0].public_id])[0]
|
||||||
|
elif 'positions' in data:
|
||||||
|
ids = list(qs.values_list('id', flat=True))
|
||||||
|
response['data']['positions'] = utils.get_positions(ids, data['positions'], decode_id=True)
|
||||||
|
else:
|
||||||
|
response['data']['items'] = qs.count()
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(findTranslations)
|
|
@ -25,6 +25,7 @@ import edit.views
|
||||||
import itemlist.views
|
import itemlist.views
|
||||||
import item.views
|
import item.views
|
||||||
import item.urls
|
import item.urls
|
||||||
|
import translation.views
|
||||||
import urlalias.views
|
import urlalias.views
|
||||||
|
|
||||||
def serve_static_file(path, location, content_type):
|
def serve_static_file(path, location, content_type):
|
||||||
|
@ -34,6 +35,7 @@ urlpatterns = [
|
||||||
# Uncomment the admin/doc line below to enable admin documentation:
|
# Uncomment the admin/doc line below to enable admin documentation:
|
||||||
# urlurl(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
# urlurl(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
url(r'^api/locale.(?P<lang>.*).json$', translation.views.locale_json),
|
||||||
url(r'^api/upload/text/?$', text.views.upload),
|
url(r'^api/upload/text/?$', text.views.upload),
|
||||||
url(r'^api/upload/document/?$', document.views.upload),
|
url(r'^api/upload/document/?$', document.views.upload),
|
||||||
url(r'^api/upload/direct/?$', archive.views.direct_upload),
|
url(r'^api/upload/direct/?$', archive.views.direct_upload),
|
||||||
|
|
|
@ -15,6 +15,10 @@ pandora.ui.filter = function(id) {
|
||||||
align: 'left',
|
align: 'left',
|
||||||
id: 'name',
|
id: 'name',
|
||||||
format: function(value) {
|
format: function(value) {
|
||||||
|
var layer = Ox.getObjectById(pandora.site.layers, filter.id);
|
||||||
|
if (layer && layer.translate) {
|
||||||
|
value = Ox._(value)
|
||||||
|
}
|
||||||
return filter.flag
|
return filter.flag
|
||||||
? $('<div>')
|
? $('<div>')
|
||||||
.append(
|
.append(
|
||||||
|
|
|
@ -232,6 +232,7 @@ pandora.ui.mainMenu = function() {
|
||||||
{ id: 'events', title: Ox._('Manage Events...'), disabled: !pandora.hasCapability('canManagePlacesAndEvents') },
|
{ id: 'events', title: Ox._('Manage Events...'), disabled: !pandora.hasCapability('canManagePlacesAndEvents') },
|
||||||
{},
|
{},
|
||||||
{ id: 'users', title: Ox._('Manage Users...'), disabled: !pandora.hasCapability('canManageUsers') },
|
{ id: 'users', title: Ox._('Manage Users...'), disabled: !pandora.hasCapability('canManageUsers') },
|
||||||
|
{ id: 'translations', title: Ox._('Manage Translations...'), disabled: !pandora.hasCapability('canManageTranslations') },
|
||||||
{ id: 'statistics', title: Ox._('Statistics...'), disabled: !pandora.hasCapability('canManageUsers') },
|
{ id: 'statistics', title: Ox._('Statistics...'), disabled: !pandora.hasCapability('canManageUsers') },
|
||||||
{},
|
{},
|
||||||
{ id: 'changelog', title: Ox._('Changelog...'), disabled: !pandora.hasCapability('canManageUsers') }
|
{ id: 'changelog', title: Ox._('Changelog...'), disabled: !pandora.hasCapability('canManageUsers') }
|
||||||
|
@ -652,6 +653,8 @@ pandora.ui.mainMenu = function() {
|
||||||
pandora.$ui.usersDialog = pandora.ui.usersDialog().open();
|
pandora.$ui.usersDialog = pandora.ui.usersDialog().open();
|
||||||
} else if (data.id == 'statistics') {
|
} else if (data.id == 'statistics') {
|
||||||
pandora.$ui.statisticsDialog = pandora.ui.statisticsDialog().open();
|
pandora.$ui.statisticsDialog = pandora.ui.statisticsDialog().open();
|
||||||
|
} else if (data.id == 'translations') {
|
||||||
|
pandora.$ui.translationsDialog = pandora.ui.translationsDialog().open();
|
||||||
} else if (data.id == 'changelog') {
|
} else if (data.id == 'changelog') {
|
||||||
pandora.$ui.changelogDialog = pandora.ui.changelogDialog().open();
|
pandora.$ui.changelogDialog = pandora.ui.changelogDialog().open();
|
||||||
} else if (data.id == 'clearcache') {
|
} else if (data.id == 'clearcache') {
|
||||||
|
|
234
static/js/translationsDialog.js
Normal file
234
static/js/translationsDialog.js
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
pandora.ui.translationsDialog = function() {
|
||||||
|
|
||||||
|
var height = Math.round((window.innerHeight - 48) * 0.9),
|
||||||
|
width = 576 + Ox.UI.SCROLLBAR_SIZE,
|
||||||
|
|
||||||
|
$languageSelect = Ox.Select({
|
||||||
|
id: 'selectlanguage',
|
||||||
|
items: [{
|
||||||
|
id: '',
|
||||||
|
title: Ox._('All')
|
||||||
|
}].concat(pandora.site.languages.filter(function(lang) {
|
||||||
|
return lang != 'en'
|
||||||
|
}).map(function(lang) {
|
||||||
|
return {
|
||||||
|
id: lang,
|
||||||
|
title: Ox.LOCALE_NAMES[lang]
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
value: pandora.site.language,
|
||||||
|
width: 96
|
||||||
|
|
||||||
|
})
|
||||||
|
.css({float: 'right', margin: '4px'})
|
||||||
|
.bindEvent({
|
||||||
|
change: function(data) {
|
||||||
|
var value = $findInput.options('value')
|
||||||
|
var query = prepareQuery(value, data.value)
|
||||||
|
$list.options({
|
||||||
|
query: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
$findInput = Ox.Input({
|
||||||
|
changeOnKeypress: true,
|
||||||
|
clear: true,
|
||||||
|
placeholder: Ox._('Find'),
|
||||||
|
width: 192
|
||||||
|
})
|
||||||
|
.css({float: 'right', margin: '4px'})
|
||||||
|
.bindEvent({
|
||||||
|
change: function(data) {
|
||||||
|
var lang = $languageSelect.options('value')
|
||||||
|
var query = prepareQuery(data.value, lang)
|
||||||
|
$list.options({
|
||||||
|
query: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
$list = Ox.TableList({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
title: Ox._('ID'),
|
||||||
|
visible: false,
|
||||||
|
width: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'key',
|
||||||
|
operator: '+',
|
||||||
|
removable: false,
|
||||||
|
title: Ox._('Key'),
|
||||||
|
format: function(data) {
|
||||||
|
return Ox.encodeHTMLEntities(data)
|
||||||
|
},
|
||||||
|
visible: true,
|
||||||
|
width: 256
|
||||||
|
},
|
||||||
|
{
|
||||||
|
editable: true,
|
||||||
|
id: 'value',
|
||||||
|
operator: '+',
|
||||||
|
title: Ox._('Value'),
|
||||||
|
format: function(data) {
|
||||||
|
return Ox.encodeHTMLEntities(data)
|
||||||
|
},
|
||||||
|
tooltip: Ox._('Edit Translation'),
|
||||||
|
visible: true,
|
||||||
|
width: 256
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lang',
|
||||||
|
align: 'right',
|
||||||
|
operator: '-',
|
||||||
|
title: Ox._('Language'),
|
||||||
|
format: function(lang) {
|
||||||
|
return Ox.LOCALE_NAMES[lang]
|
||||||
|
},
|
||||||
|
visible: true,
|
||||||
|
width: 64
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columnsVisible: true,
|
||||||
|
items: pandora.api.findTranslations,
|
||||||
|
max: 1,
|
||||||
|
scrollbarVisible: true,
|
||||||
|
sort: [{key: 'key', operator: '+'}],
|
||||||
|
unique: 'id'
|
||||||
|
})
|
||||||
|
.bindEvent({
|
||||||
|
init: function(data) {
|
||||||
|
$status.html(
|
||||||
|
Ox.toTitleCase(Ox.formatCount(data.items, 'translation'))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
open: function(data) {
|
||||||
|
$list.find('.OxItem.OxSelected > .OxCell.OxColumnSortname')
|
||||||
|
.trigger('mousedown')
|
||||||
|
.trigger('mouseup');
|
||||||
|
},
|
||||||
|
select: function(data) {
|
||||||
|
},
|
||||||
|
submit: function(data) {
|
||||||
|
Ox.Request.clearCache('findTranslations');
|
||||||
|
console.log(data)
|
||||||
|
pandora.api.editTranslation({
|
||||||
|
id: data.id,
|
||||||
|
value: data.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
|
||||||
|
that = Ox.Dialog({
|
||||||
|
buttons: [
|
||||||
|
{},
|
||||||
|
Ox.Button({
|
||||||
|
title: Ox._('Done'),
|
||||||
|
width: 48
|
||||||
|
}).bindEvent({
|
||||||
|
click: function() {
|
||||||
|
that.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
closeButton: true,
|
||||||
|
content: Ox.SplitPanel({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
element: Ox.Bar({size: 24})
|
||||||
|
.append($status)
|
||||||
|
.append(
|
||||||
|
$findInput
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
$languageSelect
|
||||||
|
),
|
||||||
|
size: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: $list
|
||||||
|
}
|
||||||
|
],
|
||||||
|
orientation: 'vertical'
|
||||||
|
}),
|
||||||
|
height: height,
|
||||||
|
maximizeButton: true,
|
||||||
|
minHeight: 256,
|
||||||
|
minWidth: 512,
|
||||||
|
padding: 0,
|
||||||
|
title: Ox._('Manage Translations'),
|
||||||
|
width: width
|
||||||
|
})
|
||||||
|
.bindEvent({
|
||||||
|
resizeend: function(data) {
|
||||||
|
var width = (data.width - 64 - Ox.UI.SCROLLBAR_SIZE) / 2;
|
||||||
|
[
|
||||||
|
{id: 'name', width: Math.ceil(width)},
|
||||||
|
{id: 'sortname', width: Math.floor(width)}
|
||||||
|
].forEach(function(column) {
|
||||||
|
$list.resizeColumn(column.id, column.width);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
$status = $('<div>')
|
||||||
|
.css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: '4px',
|
||||||
|
left: '128px',
|
||||||
|
right: '32px',
|
||||||
|
bottom: '4px',
|
||||||
|
paddingTop: '2px',
|
||||||
|
fontSize: '9px',
|
||||||
|
textAlign: 'center'
|
||||||
|
})
|
||||||
|
.appendTo(that.find('.OxButtonsbar'));
|
||||||
|
|
||||||
|
|
||||||
|
function prepareQuery(value, lang) {
|
||||||
|
var query;
|
||||||
|
if (value) {
|
||||||
|
query = {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
key: 'key',
|
||||||
|
operator: '=',
|
||||||
|
value: value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'value',
|
||||||
|
operator: '=',
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
],
|
||||||
|
operator: '|'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query = {
|
||||||
|
conditions: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (lang != '') {
|
||||||
|
query = {
|
||||||
|
conditions: [
|
||||||
|
query,
|
||||||
|
{
|
||||||
|
key: 'lang',
|
||||||
|
operator: '==',
|
||||||
|
value: lang
|
||||||
|
}
|
||||||
|
],
|
||||||
|
operator: '&'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
return that;
|
||||||
|
|
||||||
|
};
|
|
@ -3062,9 +3062,13 @@ pandora.setLocale = function(locale, callback) {
|
||||||
url = [
|
url = [
|
||||||
'/static/json/locale.pandora.' + locale + '.json',
|
'/static/json/locale.pandora.' + locale + '.json',
|
||||||
'/static/json/locale.' + pandora.site.site.id + '.' + locale + '.json',
|
'/static/json/locale.' + pandora.site.site.id + '.' + locale + '.json',
|
||||||
|
'/api/locale.' + locale + '.json'
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
url = '/static/json/locale.' + locale + '.json';
|
url = [
|
||||||
|
'/static/json/locale.' + locale + '.json',
|
||||||
|
'/api/locale.' + locale + '.json'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ox.setLocale(locale, url, callback);
|
Ox.setLocale(locale, url, callback);
|
||||||
|
|
Loading…
Reference in a new issue