818 lines
36 KiB
Python
Executable File
818 lines
36 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: iso-8859-15 -*-
|
|
|
|
|
|
# Glasnost
|
|
# By: Odile Bénassy <obenassy@entrouvert.com>
|
|
# Romain Chantereau <rchantereau@entrouvert.com>
|
|
# Nicolas Clapiès <nclapies@easter-eggs.org>
|
|
# Pierre-Antoine Dejace <padejace@entrouvert.be>
|
|
# Thierry Dulieu <tdulieu@easter-eggs.com>
|
|
# Florent Monnier <monnier@codelutin.com>
|
|
# Cédric Musso <cmusso@easter-eggs.org>
|
|
# Frédéric Péters <fpeters@entrouvert.be>
|
|
# Benjamin Poussin <poussin@codelutin.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
# Sébastien Régnier <regnier@codelutin.com>
|
|
# Emmanuel Saracco <esaracco@easter-eggs.com>
|
|
#
|
|
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
|
|
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
|
|
# Emmanuel Saracco & Théridion
|
|
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
|
|
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
|
|
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
|
|
# Régnier, Emmanuel Saracco, Théridion & Vecam
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
__doc__ = """Glasnost Translations Server"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
|
|
import difflib
|
|
import md5
|
|
import sys
|
|
import time
|
|
|
|
glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
|
|
sys.path.insert(0, glasnostPythonDir)
|
|
|
|
import glasnost
|
|
|
|
import glasnost.common.faults as faults
|
|
from glasnost.common.TranslationsCommon import *
|
|
import glasnost.common.xhtmlgenerator as X
|
|
|
|
from glasnost.server.ObjectsServer import register, AdministrableServerMixin, \
|
|
AdminServerWithoutWritersMixin, ObjectServerMixin, Server, \
|
|
AdministrableVirtualServer
|
|
from glasnost.server.tools import *
|
|
|
|
from glasnost.proxy.CacheProxy import invalidateKeyStart
|
|
from glasnost.proxy.tools import getProxy
|
|
|
|
|
|
applicationName = 'TranslationsServer'
|
|
applicationRole = 'translations'
|
|
dispatcher = None
|
|
|
|
|
|
class AdminTranslations(AdminServerWithoutWritersMixin,
|
|
AdminTranslationsCommon):
|
|
def __init__(self):
|
|
AdminTranslationsCommon.__init__(self)
|
|
self.translatorsSets = {}
|
|
|
|
def modify(self, changes, givenSlotNames = None):
|
|
AdminServerWithoutWritersMixin.modify(
|
|
self, changes, givenSlotNames = givenSlotNames)
|
|
if self.translatorsSets is None:
|
|
self.translatorsSets = {}
|
|
register(AdminTranslations)
|
|
|
|
class Localization(LocalizationCommon):
|
|
pass
|
|
register(Localization)
|
|
|
|
|
|
class Translation(ObjectServerMixin, ObjectCommon):
|
|
creationTimes = None
|
|
creationTimes_kind_keyKind_valueName = 'String'
|
|
creationTimes_kind_valueKind_valueName = 'Time'
|
|
creationTimes_kindName = 'Mapping'
|
|
|
|
destinationStrings = None
|
|
destinationStrings_kind_keyKind_valueName = 'String'
|
|
destinationStrings_kind_valueKind_valueName = 'String'
|
|
destinationStrings_kindName = 'Mapping'
|
|
|
|
fuzzyLocalizations = None
|
|
fuzzyLocalizations_kind_itemKind_valueName = 'String'
|
|
fuzzyLocalizations_kindName = 'Sequence'
|
|
|
|
previousVersionSigns = None
|
|
previousVersionSigns_kind_keyKind_valueName = 'Source'
|
|
previousVersionSigns_kind_valueKind_valueName = 'String'
|
|
previousVersionSigns_kindName = 'Mapping'
|
|
|
|
sourceLanguage = None
|
|
sourceLanguage_kindName = 'Choice'
|
|
|
|
sources = None
|
|
sources_kind_itemKind_valueName = 'Source'
|
|
sources_kindName = 'Sequence'
|
|
|
|
sourceString = None
|
|
sourceString_kindName = 'String'
|
|
|
|
sourceStringDigest = None
|
|
sourceStringDigest_kindName = 'String'
|
|
|
|
translatorIds = None
|
|
translatorIds_kind_keyKind_valueName = 'String'
|
|
translatorIds_kind_valueKind_valueName = 'Id'
|
|
translatorIds_kindName = 'Mapping'
|
|
|
|
untranslatableLocalizations = None
|
|
untranslatableLocalizations_kind_itemKind_valueName = 'String'
|
|
untranslatableLocalizations_kindName = 'Sequence'
|
|
|
|
def addTranslator(self, destinationLanguage, userId):
|
|
if not self.translatorIds:
|
|
self.translatorIds = {}
|
|
if self.translatorIds.has_key(destinationLanguage):
|
|
if userId in self.translatorIds[destinationLanguage]:
|
|
pass
|
|
else:
|
|
self.translatorIds[destinationLanguage].append(userId)
|
|
else:
|
|
self.translatorIds[destinationLanguage] = [userId]
|
|
|
|
def checkLocalizationState(self, destinationLanguage, possibleStates):
|
|
if not possibleStates:
|
|
return 1
|
|
if not self.sources:
|
|
state = 'obsolete'
|
|
elif destinationLanguage == self.sourceLanguage:
|
|
state = 'original'
|
|
elif self.untranslatableLocalizations is not None \
|
|
and destinationLanguage in self.untranslatableLocalizations:
|
|
state = 'untranslatable'
|
|
elif not self.hasDestinationString(destinationLanguage):
|
|
state = 'untranslated'
|
|
elif self.fuzzyLocalizations is not None \
|
|
and destinationLanguage in self.fuzzyLocalizations:
|
|
state = 'fuzzy'
|
|
else:
|
|
state = 'translated'
|
|
return state in possibleStates
|
|
|
|
def getDestinationString(self, destinationLanguage):
|
|
if destinationLanguage == self.sourceLanguage:
|
|
return self.sourceString
|
|
elif self.destinationStrings is not None \
|
|
and self.destinationStrings.has_key(destinationLanguage):
|
|
return self.destinationStrings[destinationLanguage]
|
|
else:
|
|
return None
|
|
|
|
def getTranslatorsSet(self, destinationLanguage):
|
|
if not self.translatorIds or \
|
|
not self.translatorIds.has_key(destinationLanguage):
|
|
return []
|
|
return self.translatorIds[destinationLanguage]
|
|
|
|
def getLabel(self):
|
|
label = self.getTitle()
|
|
if label is None:
|
|
return ''
|
|
return label
|
|
|
|
def getTitle(self):
|
|
title = self.sourceString
|
|
if len(title) > 80:
|
|
title = title[:60] + '...'
|
|
return title
|
|
|
|
def hasDestinationString(self, destinationLanguage):
|
|
return destinationLanguage == self.sourceLanguage \
|
|
or self.destinationStrings is not None \
|
|
and self.destinationStrings.has_key(destinationLanguage)
|
|
|
|
def isPreviousTranslation(self, translation, visitedTranslations):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getServer().getVirtualServer(virtualServerId)
|
|
if self.previousVersionSigns is None:
|
|
return 0
|
|
for previousVersionSign in self.previousVersionSigns.values():
|
|
if not virtualServer.translations.has_key(previousVersionSign):
|
|
continue
|
|
previousTranslation = virtualServer.translations[
|
|
previousVersionSign]
|
|
if previousTranslation in visitedTranslations:
|
|
return 0
|
|
if previousTranslation == translation:
|
|
return 1
|
|
visitedTranslations.append(previousTranslation)
|
|
if previousTranslation.isPreviousTranslation(
|
|
translation, visitedTranslations):
|
|
return 1
|
|
return 0
|
|
|
|
def setDestinationString(self, destinationLanguage, isTranslatable,
|
|
destinationString, isFuzzy):
|
|
if isTranslatable:
|
|
if self.untranslatableLocalizations is not None and \
|
|
destinationLanguage in self.untranslatableLocalizations:
|
|
self.untranslatableLocalizations.remove(destinationLanguage)
|
|
if not self.untranslatableLocalizations:
|
|
del self.untranslatableLocalizations
|
|
else:
|
|
if self.untranslatableLocalizations is None:
|
|
self.untranslatableLocalizations = []
|
|
if not destinationLanguage in self.untranslatableLocalizations:
|
|
self.untranslatableLocalizations.append(destinationLanguage)
|
|
destinationString = None
|
|
isFuzzy = 0
|
|
if not destinationString:
|
|
if self.destinationStrings is not None and \
|
|
self.destinationStrings.has_key(destinationLanguage):
|
|
del self.destinationStrings[destinationLanguage]
|
|
if not self.destinationStrings:
|
|
del self.destinationStrings
|
|
if self.fuzzyLocalizations is not None and \
|
|
destinationLanguage in self.fuzzyLocalizations:
|
|
self.fuzzyLocalizations.remove(destinationLanguage)
|
|
if not self.fuzzyLocalizations:
|
|
del self.fuzzyLocalizations
|
|
else:
|
|
if self.destinationStrings is None:
|
|
self.destinationStrings = {}
|
|
if self.creationTimes is None:
|
|
self.creationTimes = {}
|
|
self.destinationStrings[destinationLanguage] = destinationString
|
|
self.creationTimes[destinationLanguage] = time.time()
|
|
if isFuzzy:
|
|
if self.fuzzyLocalizations is None:
|
|
self.fuzzyLocalizations = []
|
|
if not destinationLanguage in self.fuzzyLocalizations:
|
|
self.fuzzyLocalizations.append(destinationLanguage)
|
|
elif self.fuzzyLocalizations is not None and \
|
|
destinationLanguage in self.fuzzyLocalizations:
|
|
self.fuzzyLocalizations.remove(destinationLanguage)
|
|
if not self.fuzzyLocalizations:
|
|
del self.fuzzyLocalizations
|
|
register(Translation)
|
|
|
|
|
|
class TranslationsVirtualServer(AdministrableVirtualServer):
|
|
translations = None
|
|
translationsBySource = None
|
|
|
|
def convertIds(self, sourceDispatcherId, destinationDispatcherId):
|
|
AdministrableVirtualServer.convertIds(
|
|
self, sourceDispatcherId, destinationDispatcherId)
|
|
for sign, translation in self.translations.items():
|
|
translation.convertIds(sourceDispatcherId, destinationDispatcherId)
|
|
for source, translation in self.translationsBySource.items():
|
|
newSource = source.replace(
|
|
sourceDispatcherId, destinationDispatcherId)
|
|
if newSource != source:
|
|
del self.translationsBySource[source]
|
|
self.translationsBySource[newSource] = translation
|
|
|
|
def init(self):
|
|
AdministrableVirtualServer.init(self)
|
|
self.translations = {}
|
|
self.translationsBySource = {}
|
|
|
|
def initFromOldData(self, data):
|
|
AdministrableVirtualServer.initFromOldData(self, data)
|
|
(self.translations,
|
|
self.translationsBySource,
|
|
self.admin,
|
|
) = data
|
|
|
|
def removeIds(self, rolesToKeep):
|
|
import glasnost.common.tools_new as commonTools
|
|
AdministrableVirtualServer.removeIds(self, rolesToKeep)
|
|
for sign, translation in self.translations.items():
|
|
translation.removeIds(rolesToKeep)
|
|
for source, translation in self.translationsBySource.items():
|
|
if not commonTools.extractRole(source) in rolesToKeep:
|
|
del self.translationsBySource[source]
|
|
|
|
|
|
class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
|
|
Server):
|
|
VirtualServer = TranslationsVirtualServer
|
|
|
|
def canDeleteLocalization(self, localizationKey, sourceStringDigest):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
if virtualServer.admin.translatorsSets.has_key(localizationKey):
|
|
translatorsSet = virtualServer.admin.translatorsSets[
|
|
localizationKey]
|
|
else:
|
|
translatorsSet = None
|
|
result = self.isAdmin() \
|
|
or getProxyForServerRole('authentication').setContainsUser(
|
|
translatorsSet)
|
|
return result
|
|
|
|
def canModifyLocalization(self, localizationKey, sourceStringDigest):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
if not virtualServer.admin.translatorsSets.has_key(localizationKey):
|
|
return 0
|
|
translatorsSet = virtualServer.admin.translatorsSets[localizationKey]
|
|
authenticationProxy = getProxyForServerRole('authentication')
|
|
return self.isAdmin() or \
|
|
authenticationProxy.setContainsUser(translatorsSet)
|
|
|
|
def getLanguagesForObjectId(self, objectId):
|
|
#languages = self.getPossibleLanguages()
|
|
languages = translation.languageKeys
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
t = objectId + ' '
|
|
translations = [
|
|
virtualServer.translationsBySource[k]
|
|
for k in virtualServer.translationsBySource.keys()
|
|
if k.startswith(t)]
|
|
l = len(translations)
|
|
result = []
|
|
for lang in languages:
|
|
if len( [x for x in translations \
|
|
if x.sourceLanguage == lang] ) == l:
|
|
result.append(lang)
|
|
continue
|
|
if len([x for x in translations
|
|
if (x.destinationStrings and
|
|
x.destinationStrings.has_key(lang)) or (
|
|
lang in (x.untranslatableLocalizations or []))]) == l:
|
|
result.append(lang)
|
|
continue
|
|
|
|
return result
|
|
|
|
def getLastDigestsAndLabelsXmlRpc(
|
|
self, localizationKey, digestsAndLabelsCount, possibleStates):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
if virtualServer.admin.translatorsSets.has_key(localizationKey):
|
|
translatorsSet = virtualServer.admin.translatorsSets[
|
|
localizationKey]
|
|
else:
|
|
translatorsSet = None
|
|
if not self.isAdmin() \
|
|
and not getProxyForServerRole('authentication'
|
|
).setContainsUser(translatorsSet):
|
|
raise faults.UserAccessDenied()
|
|
digestsAndLabels = []
|
|
for sign, translation in virtualServer.translations.items():
|
|
if not sign.startswith(localizationKey[:2]):
|
|
continue
|
|
if not translation.checkLocalizationState(
|
|
localizationKey[2:], possibleStates):
|
|
continue
|
|
digestsAndLabels.append(
|
|
[translation.sourceStringDigest,
|
|
utf8(translation.getLabel()),
|
|
len(translation.sourceString.split())])
|
|
if len(digestsAndLabels) >= digestsAndLabelsCount:
|
|
break
|
|
return digestsAndLabels
|
|
|
|
def getLocalizationXmlRpc(self, localizationKey, sourceStringDigest):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
sign = localizationKey[:2] + sourceStringDigest
|
|
if not virtualServer.translations.has_key(sign):
|
|
raise faults.MissingItem(sign)
|
|
translation = virtualServer.translations[sign]
|
|
if virtualServer.admin.translatorsSets.has_key(localizationKey):
|
|
translatorsSet = virtualServer.admin.translatorsSets[
|
|
localizationKey]
|
|
else:
|
|
translatorsSet = None
|
|
if not self.isAdmin() \
|
|
and not getProxyForServerRole('authentication'
|
|
).setContainsUser(translatorsSet):
|
|
raise faults.UserAccessDenied()
|
|
destinationLanguage = localizationKey[2:]
|
|
localization = Localization()
|
|
localization.version = translation.version
|
|
localization.destinationLanguage = destinationLanguage
|
|
if translation.sources:
|
|
sourceIds = [ source.split(' ', 1)[0]
|
|
for source in translation.sources]
|
|
localization.sourceIds = []
|
|
for sourceId in sourceIds:
|
|
if not sourceId in localization.sourceIds:
|
|
localization.sourceIds.append(sourceId)
|
|
localization.sourceLanguage = translation.sourceLanguage
|
|
localization.sourceString = translation.sourceString
|
|
localization.sourceStringDigest = translation.sourceStringDigest
|
|
localization.isTranslatable = (
|
|
translation.untranslatableLocalizations is None
|
|
or not destinationLanguage in \
|
|
translation.untranslatableLocalizations)
|
|
|
|
# not translatable
|
|
if not localization.isTranslatable:
|
|
return localization.exportToXmlRpc()
|
|
|
|
# already translated
|
|
if translation.hasDestinationString(destinationLanguage):
|
|
localization.translatorsSet = \
|
|
translation.getTranslatorsSet(destinationLanguage)
|
|
localization.destinationString = \
|
|
translation.getDestinationString(destinationLanguage)
|
|
localization.isFuzzy = (
|
|
translation.fuzzyLocalizations is not None
|
|
and destinationLanguage in translation.fuzzyLocalizations)
|
|
print ' returning good translation'
|
|
return localization.exportToXmlRpc()
|
|
|
|
# not translated
|
|
if translation.sources:
|
|
possibleTranslations = []
|
|
for source in translation.sources:
|
|
for trans in virtualServer.translations.values():
|
|
if source in trans.sources:
|
|
possibleTranslations.append(trans)
|
|
if not trans.previousVersionSigns:
|
|
continue
|
|
for src, sign in trans.previousVersionSigns.items():
|
|
if not source == src:
|
|
continue
|
|
possibleTranslations.append(
|
|
virtualServer.translations[sign])
|
|
|
|
print [x.__dict__ for x in possibleTranslations]
|
|
oldResults = []
|
|
for trans in possibleTranslations:
|
|
if not trans.getDestinationString(destinationLanguage):
|
|
print trans.getDestinationString(destinationLanguage)
|
|
continue
|
|
if not trans.creationTimes or \
|
|
not trans.creationTimes.has_key(destinationLanguage):
|
|
oldResults.append( (0, trans) )
|
|
else:
|
|
oldResults.append(
|
|
(trans.creationTimes[destinationLanguage],
|
|
trans) )
|
|
oldResults.sort()
|
|
if oldResults:
|
|
# it once was translated; we return the last known translation
|
|
# for the same sourcePath
|
|
translation = oldResults[-1][1]
|
|
localization.isFuzzy = 1
|
|
localization.similarString = translation.sourceString
|
|
localization.destinationString = \
|
|
translation.destinationStrings[destinationLanguage]
|
|
print ' returning possibly previous translation'
|
|
return localization.exportToXmlRpc()
|
|
|
|
# we try to return a translation matching what's asked
|
|
sequenceMatcher = difflib.SequenceMatcher(None)
|
|
sequenceMatcher.set_seq1(localization.sourceString)
|
|
bestRatio = 0.8
|
|
for similarSign, similarTranslation in \
|
|
virtualServer.translations.items():
|
|
if not similarSign.startswith(localizationKey[:2]):
|
|
continue
|
|
if similarTranslation == translation:
|
|
continue
|
|
if similarTranslation.destinationStrings is None or \
|
|
not similarTranslation.destinationStrings.has_key(
|
|
destinationLanguage):
|
|
continue
|
|
if similarTranslation.fuzzyLocalizations is not None \
|
|
and destinationLanguage in \
|
|
similarTranslation.fuzzyLocalizations:
|
|
continue
|
|
sequenceMatcher.set_seq2(similarTranslation.sourceString)
|
|
ratio = sequenceMatcher.quick_ratio()
|
|
if ratio <= bestRatio:
|
|
continue
|
|
if not translation.isPreviousTranslation(
|
|
similarTranslation, []):
|
|
for source in similarTranslation.sources:
|
|
id, path = source.split(' ', 1)
|
|
try:
|
|
getProxy(id).getObjectStringFromDigest(
|
|
id, path,
|
|
similarTranslation.sourceStringDigest)
|
|
except faults.Fault, fault:
|
|
pass
|
|
else:
|
|
# The translator has read access to the similar
|
|
# string in the current source.
|
|
break
|
|
else:
|
|
# The translator has no read access to the similar
|
|
# string.
|
|
continue
|
|
bestRatio = ratio
|
|
localization.similarString = \
|
|
similarTranslation.sourceString
|
|
localization.destinationString = \
|
|
similarTranslation.destinationStrings[
|
|
destinationLanguage]
|
|
localization.isFuzzy = 1
|
|
print ' returning really fuzzy translation'
|
|
return localization.exportToXmlRpc()
|
|
|
|
def getPossibleLanguages(self):
|
|
translatorsSets = self.getAdminCore().translatorsSets
|
|
if not translatorsSets:
|
|
return []
|
|
languages = {}
|
|
for k in translatorsSets.keys():
|
|
languages[k[:2]] = None
|
|
languages[k[2:]] = None
|
|
return languages.keys()
|
|
|
|
def getTranslationInfos(
|
|
self, sourceStringDigest, sourceId, sourcePath, sourceLanguage,
|
|
destinationLanguages, ignoreNew = 0):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
if splitId(sourceId)[1] == 'virtualhosts':
|
|
try:
|
|
virtualHost = getProxy(sourceId).getObject(sourceId)
|
|
except faults.UnknownDispatcherInId:
|
|
return ('', '', 'ignored')
|
|
fakeSourceId = virtualHost.defaultDispatcherId + '/foo/bar'
|
|
virtualServerId = self.computeVirtualServerId(fakeSourceId)
|
|
try:
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
except KeyError:
|
|
# The virtual host is not running or doesn't exist anymore.
|
|
return ('', '', 'ignored')
|
|
source = sourceId + ' ' + sourcePath
|
|
sign = sourceLanguage + sourceStringDigest
|
|
if virtualServer.translations.has_key(sign):
|
|
translation = virtualServer.translations[sign]
|
|
if not source in translation.sources and not ignoreNew:
|
|
# Check that the string exists on the server before creating
|
|
# its translation, to avoid the creation of fake translations
|
|
# by any user.
|
|
# If getObjectStringFromDigest() raises an exception, it is
|
|
# propagated to the caller of getTranslation().
|
|
sourceString = getProxy(sourceId).getObjectStringFromDigest(
|
|
sourceId, sourcePath, sourceStringDigest)
|
|
virtualServer.lock.acquire()
|
|
translation.sources.append(source)
|
|
if virtualServer.translationsBySource.has_key(source):
|
|
previousTranslation = virtualServer.translationsBySource[
|
|
source]
|
|
previousTranslation.sources.remove(source)
|
|
if previousTranslation.sourceLanguage == sourceLanguage:
|
|
if translation.previousVersionSigns is None:
|
|
translation.previousVersionSigns = {}
|
|
translation.previousVersionSigns[source] = \
|
|
sourceLanguage \
|
|
+ previousTranslation.sourceStringDigest
|
|
virtualServer.translationsBySource[source] = translation
|
|
virtualServer.lock.release()
|
|
virtualServer.markCoreAsDirty()
|
|
elif not ignoreNew:
|
|
if virtualServerId == 'glasnost://system':
|
|
return ('', '', 'ignored') # or use gettext ?
|
|
|
|
# Check that the string exists on the server before creating its
|
|
# translation, to avoid the creation of fake translations by any
|
|
# user.
|
|
# If getObjectStringFromDigest() raises an exception, it is
|
|
# propagated to the caller of getTranslation().
|
|
sourceString = getProxy(sourceId).getObjectStringFromDigest(
|
|
sourceId, sourcePath, sourceStringDigest)
|
|
translation = Translation()
|
|
translation.sourceLanguage = sourceLanguage
|
|
translation.sourceString = sourceString
|
|
translation.sourceStringDigest = sourceStringDigest
|
|
translation.sources = [source]
|
|
virtualServer.lock.acquire()
|
|
virtualServer.translations[sign] = translation
|
|
if virtualServer.translationsBySource.has_key(source):
|
|
previousTranslation = virtualServer.translationsBySource[
|
|
source]
|
|
previousTranslation.sources.remove(source)
|
|
if previousTranslation.sourceLanguage == sourceLanguage:
|
|
translation.previousVersionSigns = {
|
|
source: sourceLanguage \
|
|
+ previousTranslation.sourceStringDigest,
|
|
}
|
|
virtualServer.translationsBySource[source] = translation
|
|
virtualServer.lock.release()
|
|
virtualServer.markCoreAsDirty()
|
|
else:
|
|
return ('', '', 'ignored')
|
|
|
|
state = 'invariant'
|
|
if destinationLanguages:
|
|
# Look for an up to date translation.
|
|
destinationLanguage = destinationLanguages[0]
|
|
if destinationLanguage == sourceLanguage:
|
|
return translation.sourceString, sourceLanguage, 'original'
|
|
if translation.untranslatableLocalizations is not None and \
|
|
destinationLanguage in translation.untranslatableLocalizations:
|
|
return (translation.sourceString,
|
|
sourceLanguage,
|
|
'untranslatable')
|
|
if translation.fuzzyLocalizations is not None and \
|
|
destinationLanguage in translation.fuzzyLocalizations:
|
|
state = 'fuzzy'
|
|
else:
|
|
destinationString = translation.getDestinationString(
|
|
destinationLanguage)
|
|
if destinationString is not None:
|
|
return destinationString, destinationLanguage, 'translated'
|
|
state = 'untranslated'
|
|
# Look for an obsolete translation.
|
|
currentTranslation = translation
|
|
visitedTranslations = []
|
|
while currentTranslation.previousVersionSigns is not None and \
|
|
currentTranslation.previousVersionSigns.has_key(source):
|
|
previousVersionSign = currentTranslation.previousVersionSigns[
|
|
source]
|
|
if not virtualServer.translations.has_key(previousVersionSign):
|
|
break
|
|
visitedTranslations.append(currentTranslation)
|
|
currentTranslation = virtualServer.translations[
|
|
previousVersionSign]
|
|
if currentTranslation in visitedTranslations:
|
|
# Vicious circle in the translation history.
|
|
break
|
|
# We make the assumption that the reader has the right to read
|
|
# the previous version of the sourceString.
|
|
if currentTranslation.untranslatableLocalizations is not None \
|
|
and destinationLanguage in \
|
|
currentTranslation.untranslatableLocalizations:
|
|
return currentTranslation.sourceString, sourceLanguage, \
|
|
'obsolete'
|
|
if currentTranslation.fuzzyLocalizations is not None \
|
|
and destinationLanguage in \
|
|
currentTranslation.fuzzyLocalizations:
|
|
continue
|
|
destinationString = currentTranslation.getDestinationString(
|
|
destinationLanguage)
|
|
if destinationString is None:
|
|
continue
|
|
return destinationString, destinationLanguage, 'obsolete'
|
|
# Look for a translation in another language.
|
|
for destinationLanguage in destinationLanguages[1:]:
|
|
if destinationLanguage == sourceLanguage:
|
|
return translation.sourceString, sourceLanguage, state
|
|
if translation.untranslatableLocalizations is not None \
|
|
and destinationLanguage in \
|
|
translation.untranslatableLocalizations:
|
|
return translation.sourceString, sourceLanguage, state
|
|
if translation.fuzzyLocalizations is not None and \
|
|
destinationLanguage in translation.fuzzyLocalizations:
|
|
continue
|
|
destinationString = translation.getDestinationString(
|
|
destinationLanguage)
|
|
if destinationString is None:
|
|
continue
|
|
return destinationString, destinationLanguage, state
|
|
return translation.sourceString, sourceLanguage, state
|
|
|
|
def getTranslationInfosXmlRpc(
|
|
self, sourceStringDigest, sourceId, sourcePath, sourceLanguage,
|
|
destinationLanguages, ignoreNew):
|
|
infos = list(self.getTranslationInfos(
|
|
sourceStringDigest, sourceId, sourcePath, sourceLanguage,
|
|
destinationLanguages, ignoreNew))
|
|
infos[0] = utf8(infos[0])
|
|
return infos
|
|
|
|
def getTranslationXmlRpc(
|
|
self, sourceStringDigest, sourceId, sourcePath, sourceLanguage,
|
|
destinationLanguages):
|
|
result = utf8(self.getTranslationInfos(
|
|
sourceStringDigest, sourceId, sourcePath, sourceLanguage,
|
|
destinationLanguages)[0])
|
|
return result
|
|
|
|
def getTranslatorLocalizationKeys(self):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
userToken = context.getVar('userToken')
|
|
if not userToken:
|
|
return []
|
|
localizationKeys = []
|
|
for localizationKey, translatorsSet in \
|
|
virtualServer.admin.translatorsSets.items():
|
|
if getProxyForServerRole('authentication').setContainsUser(
|
|
translatorsSet):
|
|
localizationKeys.append(localizationKey)
|
|
return localizationKeys
|
|
|
|
def hasLocalization( self, localizationKey, sourceStringDigest):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
sign = localizationKey[:2] + sourceStringDigest
|
|
if not virtualServer.translations.has_key(sign):
|
|
return 0
|
|
translation = virtualServer.translations[sign]
|
|
return translation.hasDestinationString(localizationKey[2:])
|
|
|
|
def hasSourceString(self, localizationKey, sourceStringDigest):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
sign = localizationKey[:2] + sourceStringDigest
|
|
return virtualServer.translations.has_key(sign)
|
|
|
|
def modifyLocalizationXmlRpc(self, localizationImport):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
localization = commonTools.importThing(localizationImport)
|
|
sign = localization.sourceLanguage \
|
|
+ localization.sourceStringDigest
|
|
if not virtualServer.translations.has_key(sign):
|
|
raise faults.MissingItem(sign)
|
|
translation = virtualServer.translations[sign]
|
|
if not translation.canBeModified():
|
|
raise faults.ReadOnlyObject()
|
|
if virtualServer.admin.translatorsSets.has_key(
|
|
localization.getKey()):
|
|
translatorsSet = virtualServer.admin.translatorsSets[
|
|
localization.getKey()]
|
|
else:
|
|
translatorsSet = None
|
|
if not self.isAdmin() \
|
|
and not getProxyForServerRole('authentication'
|
|
).setContainsUser(translatorsSet):
|
|
raise faults.UserAccessDenied()
|
|
assert translation.sourceLanguage == localization.sourceLanguage
|
|
virtualServer.lock.acquire()
|
|
# Check once again, now that the thread is locked.
|
|
if not virtualServer.translations.has_key(sign):
|
|
virtualServer.lock.release()
|
|
raise faults.MissingItem(sign)
|
|
translation.version += 1
|
|
translation.setDestinationString(
|
|
localization.destinationLanguage, localization.isTranslatable,
|
|
localization.destinationString, localization.isFuzzy)
|
|
userId = getProxyForServerRole('authentication').getUserId()
|
|
translation.addTranslator(localization.destinationLanguage, userId)
|
|
if not translation.destinationStrings and not translation.sources:
|
|
del virtualServer.translations[sign]
|
|
virtualServer.lock.release()
|
|
virtualServer.markCoreAsDirty()
|
|
|
|
cacheKey = '_' .join([localization.sourceStringDigest,
|
|
localization.sourceLanguage,
|
|
''])
|
|
invalidateKeyStart(cacheKey)
|
|
return translation.version
|
|
|
|
def registerPublicMethods(self):
|
|
Server.registerPublicMethods(self)
|
|
AdministrableServerMixin.registerPublicMethods(self)
|
|
self.registerPublicMethod('canDeleteLocalization')
|
|
self.registerPublicMethod('canModifyLocalization')
|
|
self.registerPublicMethod('getLanguagesForObjectId')
|
|
self.registerPublicMethod('getLastDigestsAndLabels',
|
|
self.getLastDigestsAndLabelsXmlRpc)
|
|
self.registerPublicMethod('getPossibleLanguages',
|
|
self.getPossibleLanguages)
|
|
self.registerPublicMethod('getLocalization',
|
|
self.getLocalizationXmlRpc)
|
|
self.registerPublicMethod('getTranslation',
|
|
self.getTranslationXmlRpc)
|
|
self.registerPublicMethod('getTranslationInfos',
|
|
self.getTranslationInfosXmlRpc)
|
|
self.registerPublicMethod('getTranslatorLocalizationKeys')
|
|
self.registerPublicMethod('hasLocalization')
|
|
self.registerPublicMethod('hasSourceString')
|
|
self.registerPublicMethod('modifyLocalization',
|
|
self.modifyLocalizationXmlRpc)
|
|
|
|
def repairVirtualServer(self, virtualServer, version):
|
|
changed = 0
|
|
if version < 4000:
|
|
changed = virtualServer.admin.repair(4000) or changed
|
|
for sign, translation in virtualServer.translations.items():
|
|
changed = translation.repair(4000) or changed
|
|
if version < 5004:
|
|
changed = virtualServer.admin.repair(5004) or changed
|
|
for sign, translation in virtualServer.translations.items():
|
|
changed = translation.repair(5004) or changed
|
|
if version <= 1012000:
|
|
admin = virtualServer.admin
|
|
if admin.id is None:
|
|
changed = 1
|
|
admin.id = '%s/__admin__' % virtualServer.virtualServerId
|
|
if changed:
|
|
virtualServer.markAllAsDirtyFIXME()
|
|
|
|
|
|
translationsServer = TranslationsServer()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
translationsServer.launch(applicationName, applicationRole)
|
|
|