Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions news/467.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reimplement usage of translators as pluggable utilities and remove Google Translate service @erral
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"plone.memoize",
"plone.protect",
"plone.registry",
"plone.rest",
"plone.restapi",
"plone.schemaeditor",
"plone.supermodel",
"plone.uuid",
Expand Down Expand Up @@ -58,6 +60,8 @@
"plone.testing",
"plone.volto",
"robotsuite",
"plone.restapi[test]",
"plone.volto",
],
},
entry_points="""
Expand Down
16 changes: 4 additions & 12 deletions src/plone/app/multilingual/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
>

<include package="plone.app.contentmenu" />
<include
package="Products.CMFPlone.controlpanel"
file="permissions.zcml"
/>

<!-- Resource Directories -->
<browser:resourceDirectory
Expand All @@ -15,10 +19,6 @@
name="plone.app.multilingual.stylesheet"
directory="stylesheet"
/>
<browser:resourceDirectory
name="plone.app.multilingual.images"
directory="images"
/>

<!-- Vocabulary all languages -->
<utility
Expand Down Expand Up @@ -226,14 +226,6 @@
<!-- DEXTERITY -->
<configure zcml:condition="installed plone.dexterity">

<!-- GTranslate Service -->
<browser:page
name="gtranslation_service"
for="plone.dexterity.interfaces.IDexterityContent"
class=".translate.gtranslation_service_dexterity"
permission="cmf.ModifyPortalContent"
/>

<!-- Standard add view and form - invoked from ++addtranslation++ traverser -->
<adapter
factory=".add.DefaultMultilingualAddView"
Expand Down
Binary file not shown.
18 changes: 11 additions & 7 deletions src/plone/app/multilingual/browser/javascript/babel_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@
orig_field = orig_field[0];
}

const gtranslate_enabled = document.getElementById("gtranslate_service_available");
const translations_enabled = document.getElementById(
"translation_service_available"
);
const target_el = dest_field.querySelector('textarea,input');
const target_tiny = tinymce.get(target_el.id);

Expand All @@ -96,7 +98,7 @@

// Add the google translation field
if (
gtranslate_enabled.value === "True" && (
translations_enabled.value === "True" && (
// it is either a text widget, a text area or rich widget
dest_field.querySelectorAll('.text-widget, .textarea-widget, .richTextWidget').length ||
// or it is a tinymce richtextfield without wrapping CSS class
Expand All @@ -108,29 +110,31 @@

translator_widget.classList.add("translator-widget");
translator_widget.id = `item_translation_${order}`;
translator_widget.style.display = 'block';

translator_widget.addEventListener("click", async function () {
var field = orig_field.getAttribute("rel");

// we use the current URL to get the context's UID
var url_parts = document.location.pathname.split('++addtranslation++');

var postdata = new URLSearchParams({
var postdata = {
'field': field,
'lang_source': langSource,
// we use the second part of the url_parts, the uid itself
'context_uid': url_parts[1]
});
};

const translate_service_url = url_translate + '/gtranslation_service';
const translate_service_url = url_translate + '/@translation-service';

// Now we call the data
const response = await fetch(translate_service_url, {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset: utf-8",
"Content-type": "application/json; charset: utf-8",
"Accept": "application/json"
},
body: postdata,
body: JSON.stringify(postdata),
});

if (!response.ok) {
Expand Down
12 changes: 10 additions & 2 deletions src/plone/app/multilingual/browser/stylesheet/multilingual.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,19 @@
.translator-widget {
position: relative;
float: right;
background: url("++resource++plone.app.multilingual.images/gtranslate.png") no-repeat;
background-size: contain;
mask-image: url("++plone++bootstrap-icons/translate.svg");
mask-position: center;
mask-repeat: no-repeat;
mask-size: calc(var(--bs-body-font-size) * 2);
background-color: var(--plone-link-color);
min-height: 50px;
min-width: 50px;
display: none;
cursor: pointer;
}

.translator-widget:hover {
background-color: var(--plone-link-color-on-grey);
}

.currentLanguage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
value context/absolute_url;
"
/>
<input id="gtranslate_service_available"
<input id="translation_service_available"
type="hidden"
value=""
tal:attributes="
value pamutils/gtenabled;
value pamutils/translations_enabled;
"
/>

Expand Down
84 changes: 0 additions & 84 deletions src/plone/app/multilingual/browser/translate.py
Original file line number Diff line number Diff line change
@@ -1,91 +1,7 @@
from Acquisition import aq_inner
from plone.app.multilingual import _
from plone.app.multilingual.interfaces import IMultiLanguageExtraOptionsSchema
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 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"
]

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"]
return json.dumps({"data": translated})


class gtranslation_service_dexterity(BrowserView):
def __call__(self):
if self.request.method != "POST" and not (
"field" in self.request.form.keys()
and "lang_source" in self.request.form.keys()
):
return _("Need a field")
else:
context_uid = self.request.form.get("context_uid", None)
if context_uid is None:
# try with context if no translation uid is present
manager = ITranslationManager(self.context)
else:
context = uuidToObject(context_uid)
if context is not None:
manager = ITranslationManager(context)
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)
field = self.request.form["field"].split(".")[-1]
if hasattr(orig_object, field):
question = getattr(orig_object, field, "") or ""
if hasattr(question, "raw"):
question = question.raw
else:
return _("Invalid field")
return google_translate(
question, settings.google_translation_key, lang_target, lang_source
)


class TranslationForm(BrowserView):
Expand Down
16 changes: 9 additions & 7 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 @@ -15,6 +16,7 @@
from Products.CMFCore.utils import getToolByName
from Products.Five import BrowserView
from zope.component import getMultiAdapter
from zope.component import getUtilitiesFor
from zope.component import getUtility
from zope.component.hooks import getSite

Expand Down Expand Up @@ -51,13 +53,13 @@ def getPortal(self):
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
def translations_enabled(self):
utilities = [
name
for name, utility in getUtilitiesFor(IExternalTranslationService)
if utility.is_available()
]
return len(utilities) > 0

def languages(self):
"""Deprecated"""
Expand Down
6 changes: 5 additions & 1 deletion 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 @@ -198,5 +203,4 @@
handler=".subscriber.change_language_settings"
/>


</configure>
40 changes: 30 additions & 10 deletions src/plone/app/multilingual/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ class IMultiLanguageExtraOptionsSchema(ILanguageSchema):
"redirect_babel_view",
"bypass_languageindependent_field_permission_check",
"buttons_babel_view_up_to_nr_translations",
"google_translation_key",
"selector_lookup_translations_policy",
],
)
Expand Down Expand Up @@ -254,15 +253,6 @@ class IMultiLanguageExtraOptionsSchema(ILanguageSchema):
required=False,
)

google_translation_key = schema.TextLine(
title=_("heading_google_translation_key", default="Google Translation API Key"),
description=_(
"description_google_translation_key",
default="Is a paying API in order to use google translation " "service",
),
required=False,
)

selector_lookup_translations_policy = schema.Choice(
title=_(
"heading_selector_lookup_translations_policy",
Expand All @@ -278,3 +268,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
"""
2 changes: 1 addition & 1 deletion src/plone/app/multilingual/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<metadata>
<version>1002</version>
<version>1003</version>
<dependencies>
<dependency>profile-plone.app.dexterity:default</dependency>
</dependencies>
Expand Down
1 change: 0 additions & 1 deletion src/plone/app/multilingual/profiles/default/registry.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
<value key="redirect_babel_view">True</value>
<value key="bypass_languageindependent_field_permission_check">False</value>
<value key="buttons_babel_view_up_to_nr_translations">7</value>
<value key="google_translation_key" />
<value key="selector_lookup_translations_policy">closest</value>
</records>

Expand Down
13 changes: 13 additions & 0 deletions src/plone/app/multilingual/profiles/upgrades/to_1003/registry.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<registry>

<records interface="plone.app.multilingual.interfaces.IMultiLanguageExtraOptionsSchema"
prefix="plone"
purge="false"
>
<value key="google_translation_key"
remove="true"
/>
</records>

</registry>
Empty file.
Loading