Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9962a26
convert the function that uses google translate to an adapter based l…
erral Sep 29, 2024
d9af445
implement adapter ordering
erral Sep 29, 2024
6b4ad2b
implement availability of service
erral Sep 29, 2024
ab88212
changelog
erral Sep 29, 2024
70699ec
black
erral Sep 29, 2024
823f4fc
isort
erral Sep 29, 2024
327d9ac
format
erral Sep 29, 2024
2feb159
remove empty
erral Sep 29, 2024
7089813
tests
erral Sep 30, 2024
edfc222
isort
erral Sep 30, 2024
adb4fe4
isort
erral Oct 2, 2024
84266a9
lint
erral Oct 2, 2024
e9e4aa0
lint
erral Oct 2, 2024
e55b2af
Merge branch 'master' into erral-issue-467
erral Nov 28, 2024
94f1de0
convert to utilities
erral Nov 30, 2024
3b183a8
changelog
erral Nov 30, 2024
0930656
remove unneeded·
erral Nov 30, 2024
f4646c4
Merge branch 'master' into erral-issue-467
erral Jan 8, 2025
b5d0d5c
only translate non-empty values
erral Jan 8, 2025
fb21293
add plone.restapi test requirements
erral Jan 8, 2025
1fcec1a
Merge branch 'master' into erral-issue-467
erral Mar 31, 2025
cb5fb6e
Merge branch 'master' into erral-issue-467
erral Dec 7, 2025
29a0845
Adds the volto.blocks behavior to LRF if `plone.volto` is installed.
wesleybl Nov 28, 2025
29bf865
Move the function `add volto blocks_behavior_to_lrf` to the class `Se…
wesleybl Dec 2, 2025
498b884
zpretty
erral Dec 7, 2025
3e80c75
fix dependnecies
erral Dec 7, 2025
24ea463
breaking
erral Dec 12, 2025
2b8dade
utlity
erral Dec 12, 2025
38fa588
lint
erral Dec 12, 2025
9911ee8
mark changes as breaking
erral Dec 12, 2025
35ceca2
remove more google traces
erral Dec 12, 2025
931dc60
lint
erral Dec 12, 2025
5e99984
fixes
erral Dec 12, 2025
6db00c1
remove unneeded
erral Dec 12, 2025
987229e
remove unneeded
erral Dec 12, 2025
00d149f
remove unneeded
erral Dec 12, 2025
cdc4734
test new endpoint
erral Dec 12, 2025
7f5885a
rename
erral Dec 12, 2025
f7f5307
lint
erral Dec 12, 2025
6ef0dac
add missing test-dependency
erral Dec 12, 2025
dd69e06
load plone.rest to have the plone:service registration available
erral Dec 14, 2025
177365e
register the endpoints only when p.a.multilingual is installed
erral Dec 14, 2025
8cfb7ea
setup.py
erral Dec 14, 2025
26c4f37
remove unneeded
erral Dec 14, 2025
352a48e
load plone.restapi for tests
erral Dec 14, 2025
c1649f4
fix: load controlpanel permissions
erral Dec 15, 2025
c54cf5d
remove gtranslate icon and use plone provided translate icon
erral Dec 15, 2025
2e4dbeb
restore tinymce detection
erral Dec 15, 2025
b1c2819
Merge branch 'master' into erral-issue-467
erral Dec 15, 2025
2251b07
zpretty
erral Dec 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/467.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Reimplement usage of Google Translate API as utility, to be able to provide different translation services
[erral]
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"plone.memoize",
"plone.protect",
"plone.registry",
"plone.restapi",
"plone.schemaeditor",
"plone.supermodel",
"plone.uuid",
Expand Down Expand Up @@ -63,6 +64,7 @@
"plone.testing",
"robotsuite",
"Products.CMFPlacefulWorkflow",
"plone.restapi",
],
},
entry_points="""
Expand Down
87 changes: 43 additions & 44 deletions src/plone/app/multilingual/browser/translate.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
from Acquisition import aq_inner
from plone.app.multilingual import _
from plone.app.multilingual.interfaces import IMultiLanguageExtraOptionsSchema
from plone.app.multilingual.interfaces import IExternalTranslationService
from plone.app.multilingual.interfaces import ITranslationManager
from plone.app.uuid.utils import uuidToObject
from plone.base.interfaces import ILanguage
from plone.registry.interfaces import IRegistry
from plone.uuid.interfaces import IUUID
from Products.Five import BrowserView
from zope.component import getUtilitiesFor
from zope.component import getUtility

import json
import urllib


def google_translate(question, key, lang_target, lang_source):
length = len(question)
translated = ""
url = "https://www.googleapis.com/language/translate/v2"
temp_question = question
while length > 400:
temp_question = question[:399]
index = temp_question.rfind(" ")
temp_question = temp_question[:index]
question = question[index:]
length = len(question)
data = {
"key": key,
"target": lang_target,
"source": lang_source,
"q": temp_question,
}
params = urllib.parse.urlencode(data)

retorn = urllib.request.urlopen(url + "?" + params)
translated += json.loads(retorn.read())["data"]["translations"][0][
"translatedText"


def translate_text(original_text, source_language, target_language, service=None):
"""translate the text"""

if service is not None:
# if an specific adapter is requested, use it if available

adapter = getUtility(IExternalTranslationService, name=service)
if not adapter.is_available():
return None

adapters = [adapter]

else:

adapters = [
adapter
for _, adapter in getUtilitiesFor(IExternalTranslationService)
if adapter.is_available()
]

data = {
"key": key,
"target": lang_target,
"source": lang_source,
"q": temp_question,
}
params = urllib.parse.urlencode(data)
sorted_adapters = sorted(adapters, key=lambda x: x.order)

retorn = urllib.request.urlopen(url + "?" + params)
translated += json.loads(retorn.read())["data"]["translations"][0]["translatedText"]
return json.dumps({"data": translated})
for adapter in sorted_adapters:
available_languages = adapter.available_languages()
if (
not available_languages
or (source_language, target_language) in available_languages
):
translation = adapter.translate_content(
Comment thread
mamico marked this conversation as resolved.
Outdated
original_text, source_language, target_language
)

if translation:
return translation

return None


class gtranslation_service_dexterity(BrowserView):
Expand All @@ -69,10 +69,6 @@ def __call__(self):
else:
manager = ITranslationManager(self.context)

registry = getUtility(IRegistry)
settings = registry.forInterface(
IMultiLanguageExtraOptionsSchema, prefix="plone"
)
lang_target = ILanguage(self.context).get_language()
lang_source = self.request.form["lang_source"]
orig_object = manager.get_translation(lang_source)
Expand All @@ -83,9 +79,12 @@ def __call__(self):
question = question.raw
else:
return _("Invalid field")
return google_translate(
question, settings.google_translation_key, lang_target, lang_source
)

translation = translate_text(question, lang_source, lang_target)
if translation is None:
return json.dumps({"data": ""})

return json.dumps({"data": translation})


class TranslationForm(BrowserView):
Expand Down
15 changes: 9 additions & 6 deletions src/plone/app/multilingual/browser/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from Acquisition import aq_parent
from plone.app.i18n.locales.browser.selector import LanguageSelector
from plone.app.multilingual.browser.selector import LanguageSelectorViewlet
from plone.app.multilingual.interfaces import IExternalTranslationService
from plone.app.multilingual.interfaces import ILanguageIndependentFolder
from plone.app.multilingual.interfaces import IMultiLanguageExtraOptionsSchema
from plone.app.multilingual.interfaces import ITranslationLocator
Expand All @@ -14,6 +15,7 @@
from plone.registry.interfaces import IRegistry
from Products.CMFCore.utils import getToolByName
from Products.Five import BrowserView
from zope.component import getAdapters
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.component.hooks import getSite
Expand Down Expand Up @@ -52,12 +54,13 @@ def objToTranslate(self):
return self.context

def gtenabled(self):
registry = getUtility(IRegistry)
settings = registry.forInterface(
IMultiLanguageExtraOptionsSchema, prefix="plone"
)
key = settings.google_translation_key
return key is not None and len(key.strip()) > 0
adapters = [
adapter
for _, adapter in getAdapters((self.context,), IExternalTranslationService)
if adapter.is_available()
]

return len(adapters) > 0

def languages(self):
"""Deprecated"""
Expand Down
12 changes: 12 additions & 0 deletions src/plone/app/multilingual/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<include package=".browser" />
<include package=".dx" />

<include
package=".restapi"
zcml:condition="installed plone.restapi"
/>

<!--<adapter factory=".content.lrf.LRFOrdering"/>-->
<adapter factory=".shared_uuid.lrfUUID" />
<adapter factory=".shared_uuid.lifUUID" />
Expand Down Expand Up @@ -192,4 +197,11 @@
handler=".setuphandlers.step_uninstall_various"
/>


<utility
provides=".interfaces.IExternalTranslationService"
name="google_translate"
component=".google_translate.GoogleCloudTranslationAPI"
/>

</configure>
71 changes: 71 additions & 0 deletions src/plone/app/multilingual/google_translate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from plone.app.multilingual.interfaces import IMultiLanguageExtraOptionsSchema
from plone.registry.interfaces import IRegistry
from zope.component import getUtility

import json
import urllib


class GoogleCloudTranslationAPIFactory:
"""implement the external translation using Google Cloud Translation API"""

order = 999
Comment thread
erral marked this conversation as resolved.
Outdated

def is_available(self):
registry = getUtility(IRegistry)
settings = registry.forInterface(
IMultiLanguageExtraOptionsSchema, prefix="plone"
)
key = settings.google_translation_key
return key is not None and len(key.strip()) > 0

def available_languages(self):
# All languages are supported
return []

def translate_content(self, content, source_language, target_language):
registry = getUtility(IRegistry)
settings = registry.forInterface(
IMultiLanguageExtraOptionsSchema, prefix="plone"
)

question = content
length = len(question)
translated = ""
url = "https://www.googleapis.com/language/translate/v2"
temp_question = question
while length > 400:
temp_question = question[:399]
index = temp_question.rfind(" ")
temp_question = temp_question[:index]
question = question[index:]
length = len(question)
data = {
"key": settings.google_translation_key,
"target": target_language,
"source": source_language,
"q": temp_question,
}
params = urllib.parse.urlencode(data)

retorn = urllib.request.urlopen(url + "?" + params)
translated += json.loads(retorn.read())["data"]["translations"][0][
"translatedText"
]

data = {
"key": settings.google_translation_key,
"target": target_language,
"source": source_language,
"q": temp_question,
}
params = urllib.parse.urlencode(data)

retorn = urllib.request.urlopen(url + "?" + params)
translated += json.loads(retorn.read())["data"]["translations"][0][
"translatedText"
]
return translated


GoogleCloudTranslationAPI = GoogleCloudTranslationAPIFactory()
30 changes: 30 additions & 0 deletions src/plone/app/multilingual/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,33 @@ class IMultiLanguageExtraOptionsSchema(ILanguageSchema):
required=True,
vocabulary=selector_policies,
)


class IExternalTranslationService(Interface):
"""This interface is provided to allow external translation services
to be plugged-in in Plone to use them to translate content

Register a utility to install a new external translation service.

To control the order of the services, user the 'order' attribute. The lower
the sooner this service will be used.

The available_languages method can also be used to register the utility
just to some language pairs.

"""

order = schema.Int(title="Order")

def is_available():
"""return whether this service is available"""

def available_languages():
"""return the list of tuples that represents language pairs this utility is enabled for.
An empty list means that all languages are supported
"""

def translate_content(content, source_language, target_language):
"""translate the given content from the source to the target language.
It should return the translated string
"""
Empty file.
26 changes: 26 additions & 0 deletions src/plone/app/multilingual/restapi/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="plone"
>

<include package="plone.restapi" />


<plone:service
method="GET"
factory=".translation_services.TranslationServices"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="cmf.ModifyPortalContent"
name="@translation-services"
/>

<plone:service
method="POST"
factory=".translate_text.TranslateTextService"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="cmf.ModifyPortalContent"
name="@translate-text"
/>

</configure>
29 changes: 29 additions & 0 deletions src/plone/app/multilingual/restapi/translate_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from plone.app.multilingual.browser.translate import translate_text
from plone.restapi.deserializer import json_body
from plone.restapi.services import Service


class TranslateTextService(Service):
"""this endpoints tries to translate the given text into the given language, using the previously registered ExternalTranslationServices"""

def reply(self):
body = json_body(self.request)
source_language = body.get("source_language")
target_language = body.get("target_language")
original_text = body.get("original_text")
service = body.get("service")

translation = translate_text(
original_text, source_language, target_language, service
)

if translation is None:
self.request.response.setStatus(400)
return dict(
error=dict(
type="Translation service not available",
message="The requested translation service is not available.",
)
)

return {"data": translation}
21 changes: 21 additions & 0 deletions src/plone/app/multilingual/restapi/translation_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from plone.app.multilingual.interfaces import IExternalTranslationService
from plone.restapi.services import Service
from zope.component import getUtilitiesFor


class TranslationServices(Service):
"""an endpoint to return all translation services registered in a portal that provide the IExternalTranslationService interface"""

def reply(self):
result = []

for name, adapter in getUtilitiesFor(IExternalTranslationService):
item = {}
item["order"] = adapter.order
item["is_available"] = adapter.is_available()
item["available_languages"] = adapter.available_languages()
item["name"] = name

result.append(item)

return sorted(result, key=lambda x: x["order"], reverse=True)
Loading