Skip to content

Commit dc9c4be

Browse files
committed
[FEATURE] Add optimize button to CKEditor
DeepL Write brings lots of features optimizing text. These will help improving text beside a pre-defined translation/optimization. Add a basic functionality for implementing CKeditor optimization button and add a skeleton for talking with the backend.
1 parent 2aeae81 commit dc9c4be

11 files changed

Lines changed: 409 additions & 5 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WebVision\DeeplWrite\Controller;
6+
7+
use Psr\Http\Message\ResponseFactoryInterface;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use TYPO3\CMS\Core\Utility\GeneralUtility;
11+
use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
12+
use TYPO3\CMS\Fluid\View\StandaloneView;
13+
use WebVision\DeeplWrite\Configuration\ConfigurationInterface;
14+
use WebVision\DeeplWrite\Domain\Enum\RephraseToneDeepL;
15+
use WebVision\DeeplWrite\Domain\Enum\RephraseWritingStyleDeepL;
16+
use WebVision\DeeplWrite\Service\DeeplService;
17+
use WebVision\DeeplWrite\Service\HtmlParser;
18+
19+
/**
20+
* @internal
21+
* This class is meant to be used within the DeepL write extension and therefore
22+
* no public API. Endpoints can change without further information.
23+
*/
24+
final class CkEditorController
25+
{
26+
public function __construct(
27+
private readonly ResponseFactoryInterface $responseFactory,
28+
private readonly ConfigurationInterface $configuration,
29+
private readonly DeeplService $deeplService,
30+
private readonly HtmlParser $htmlParser,
31+
) {
32+
}
33+
34+
public function deeplConfiguredAction(ServerRequestInterface $request): ResponseInterface
35+
{
36+
$configured = true;
37+
if ($this->configuration->getApiKey() === '') {
38+
$configured = false;
39+
}
40+
$response = $this->responseFactory->createResponse()
41+
->withHeader('Content-Type', 'application/json; charset=utf-8');
42+
$response->getBody()->write(
43+
json_encode(['configured' => $configured], JSON_THROW_ON_ERROR),
44+
);
45+
return $response;
46+
}
47+
48+
public function optimizeTextAction(ServerRequestInterface $request): ResponseInterface
49+
{
50+
$data = $request->getParsedBody();
51+
$splittedText = $this->htmlParser->splitHtml($data['text']);
52+
foreach ($splittedText as $node => $text) {
53+
$optimizedText = $this->deeplService->rephraseText(
54+
$data['text'],
55+
null,
56+
RephraseWritingStyleDeepL::tryFrom($data['style']),
57+
RephraseToneDeepL::tryFrom($data['tone'])
58+
);
59+
$splittedText[$node] = $optimizedText;
60+
}
61+
$response = $this->responseFactory->createResponse()
62+
->withHeader('Content-Type', 'application/json; charset=utf-8');
63+
$response->getBody()->write(
64+
json_encode(['result' => $this->htmlParser->buildHtml($splittedText)], JSON_THROW_ON_ERROR),
65+
);
66+
return $response;
67+
}
68+
69+
public function getEditMaskAction(ServerRequestInterface $request): ResponseInterface
70+
{
71+
$renderingContext = GeneralUtility::makeInstance(RenderingContextFactory::class)->create(
72+
templatePathsArray: [
73+
'templateRootPaths' => ['EXT:deepl_write/Resources/Private/Backend/Templates/'],
74+
]
75+
);
76+
$renderingContext->setRequest($request);
77+
$renderingContext->setControllerAction('Edit');
78+
$renderingContext->setControllerName('CkEditor');
79+
$view = GeneralUtility::makeInstance(StandaloneView::class, $renderingContext);
80+
$view->assignMultiple([
81+
'styles' => RephraseWritingStyleDeepL::cases(),
82+
'tones' => RephraseToneDeepL::cases(),
83+
]);
84+
$response = $this->responseFactory->createResponse()
85+
->withHeader('Content-Type', 'text/html; charset=utf-8');
86+
$response->getBody()->write($view->render());
87+
return $response;
88+
}
89+
}

Classes/Hooks/PageRendererHook.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WebVision\DeeplWrite\Hooks;
6+
7+
use TYPO3\CMS\Core\Page\PageRenderer;
8+
9+
final class PageRendererHook
10+
{
11+
/**
12+
* Ensure backend javascript module is required and loaded.
13+
*
14+
* @param array<string, mixed> $params
15+
*/
16+
public function renderPreProcess(array $params, PageRenderer $pageRenderer): void
17+
{
18+
if ($pageRenderer->getApplicationType() === 'BE') {
19+
// For some reason, the labels are not available in JavaScript object `TYPO3.lang`. So we add them manually.
20+
$pageRenderer->addInlineLanguageLabelFile('EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf');
21+
}
22+
}
23+
}

Classes/Service/DeeplService.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function __construct(
3636
*/
3737
public function rephraseText(
3838
string $text,
39-
string $targetLanguage,
39+
?string $targetLanguage = null,
4040
?RephraseWritingStyleDeepL $writingStyle = null,
4141
?RephraseToneDeepL $tone = null
4242
): string {
@@ -47,10 +47,22 @@ public function rephraseText(
4747
1741344565
4848
);
4949
}
50-
if ($writingStyle instanceof RephraseWritingStyleDeepL && RephraseSupportedDeepLLanguage::isWritingStyleSupported($targetLanguage)) {
50+
if (
51+
$writingStyle instanceof RephraseWritingStyleDeepL
52+
&& (
53+
$targetLanguage === null ||
54+
RephraseSupportedDeepLLanguage::isWritingStyleSupported($targetLanguage)
55+
)
56+
) {
5157
$options[RephraseTextOptions::WRITING_STYLE] = $writingStyle->value;
5258
}
53-
if ($tone instanceof RephraseToneDeepL && RephraseSupportedDeepLLanguage::isToneSupportedByLanguage($targetLanguage)) {
59+
if (
60+
$tone instanceof RephraseToneDeepL
61+
&& (
62+
$targetLanguage === null ||
63+
RephraseSupportedDeepLLanguage::isToneSupportedByLanguage($targetLanguage)
64+
)
65+
) {
5466
$options[RephraseTextOptions::TONE] = $tone->value;
5567
}
5668

@@ -72,7 +84,7 @@ public function rephraseText(
7284
*/
7385
private function optimizeText(
7486
string $text,
75-
string $targetLanguage,
87+
?string $targetLanguage = null,
7688
array $options = []
7789
): string {
7890
$rephrased = $this->deeplClient->rephraseText(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use WebVision\DeeplWrite\Controller\CkEditorController;
4+
5+
return [
6+
'deeplwrite_ckeditor_configuration' => [
7+
'path' => '/deepl-write/ckeditor/configuration',
8+
'target' => CkEditorController::class . '::deeplConfiguredAction',
9+
'methods' => ['GET'],
10+
],
11+
'deeplwrite_ckeditor_optimize' => [
12+
'path' => '/deepl-write/ckeditor/optimize',
13+
'target' => CkEditorController::class . '::optimizeTextAction',
14+
'methods' => ['POST'],
15+
],
16+
'deeplwrite_ckeditor_edit' => [
17+
'path' => '/deepl-write/ckeditor/edit',
18+
'target' => CkEditorController::class . '::getEditMaskAction',
19+
'methods' => ['GET'],
20+
],
21+
];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
return [
4+
'dependencies' => ['backend'],
5+
'tags' => [
6+
'backend.form',
7+
],
8+
'imports' => [
9+
'@web-vision/deepl-write/deeplwrite-plugin.js' => 'EXT:deepl_write/Resources/Public/JavaScript/Ckeditor/deeplwrite-plugin.js',
10+
],
11+
];
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
editor:
2+
config:
3+
importModules:
4+
- { module: '@web-vision/deepl-write/deeplwrite-plugin.js', exports: [ 'Deeplwrite' ] }
5+
toolbar:
6+
items:
7+
- bold
8+
- italic
9+
- '|'
10+
- clipboard
11+
- undo
12+
- redo
13+
- '|'
14+
- deeplwrite

Configuration/Services.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ services:
1010
WebVision\DeeplWrite\Hooks\WriteHook:
1111
public: true
1212

13+
WebVision\DeeplWrite\Controller\CkEditorController:
14+
public: true
15+
1316
WebVision\DeeplWrite\Configuration\ConfigurationInterface:
1417
class: WebVision\DeeplWrite\Configuration\Configuration
1518

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<html
2+
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
3+
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
4+
xmlns:deeplWrite="http://typo3.org/ns/WebVision/DeeplWrite/ViewHelpers"
5+
data-namespace-typo3-fluid="true"
6+
>
7+
<div class="col-md-12">
8+
<div class="row">
9+
<p>
10+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.description"/>
11+
</p>
12+
</div>
13+
<div role="tabpanel">
14+
<ul class="nav nav-tabs form-section-tabs t3js-tabs" role="tablist" id="tabs-DPLWRITE-format">
15+
<li role="presentation" class="t3js-tabmenu-item nav-item">
16+
<button
17+
class="nav-link active"
18+
data-bs-toggle="tab"
19+
data-bs-target="#DPLWRITE-format-1"
20+
aria-controls="#DPLWRITE-format-1"
21+
role="tab"
22+
aria-selected="true"
23+
>
24+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.heading.style"/>
25+
</button>
26+
</li>
27+
<li role="presentation" class="t3js-tabmenu-item nav-item">
28+
<button
29+
class="nav-link"
30+
data-bs-toggle="tab"
31+
data-bs-target="#DPLWRITE-format-2"
32+
aria-controls="#DPLWRITE-format-2"
33+
role="tab"
34+
>
35+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.heading.tone"/>
36+
</button>
37+
</li>
38+
</ul>
39+
</div>
40+
<div class="tab-content">
41+
<div role="tabpanel" class="tab-pane active" id="DPLWRITE-format-1">
42+
<h2 class="visually-hidden">
43+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.heading.style"/>
44+
</h2>
45+
<fieldset class="form-section">
46+
<p>
47+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.description.style"/>
48+
</p>
49+
<f:for each="{styles}" as="style" iteration="i">
50+
<input type="radio" value="{style.value}" name="format" class="style btn-check" id="style-{i.index}">
51+
<label class="btn btn-default col-md-3 col-xs-4" for="style-{i.index}">{style.name}</label>
52+
</f:for>
53+
</fieldset>
54+
</div>
55+
<div role="tabpanel" class="tab-pane" id="DPLWRITE-format-2">
56+
<h2 class="visually-hidden">
57+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.heading.tone"/>
58+
</h2>
59+
<fieldset class="form-section">
60+
<p>
61+
<f:translate key="LLL:EXT:deepl_write/Resources/Private/Language/locallang_cke.xlf:cke.modal.description.tone"/>
62+
</p>
63+
<f:for each="{tones}" as="tone" iteration="i">
64+
<input type="radio" value="{tone.value}" name="format" class="tone btn-check" id="tone-{i.index}">
65+
<label class="btn btn-default col-md-3 col-xs-4" for="tone-{i.index}">{tone.name}</label>
66+
</f:for>
67+
</fieldset>
68+
</div>
69+
</div>
70+
<fieldset class="form-section">
71+
<div class="row">
72+
<div class="col-md-6 col-xs-12">
73+
<label for="original" class="form-label t3js-formengine-label">Original</label>
74+
<textarea rows="20" class="form-control t3js-formengine-textarea formengine-textarea" id="original"></textarea>
75+
</div>
76+
<div class="col-md-6 col-xs-12">
77+
<label for="optimized" class="form-label t3js-formengine-label">Optimized</label>
78+
<textarea rows="20" class="form-control t3js-formengine-textarea formengine-textarea" id="optimized"></textarea>
79+
</div>
80+
</div>
81+
</fieldset>
82+
</div>
83+
</html>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
3+
<file
4+
t3:id="1750181628"
5+
source-language="en"
6+
datatype="plaintext"
7+
original="messages"
8+
date="2025-06-17T19:34:32Z"
9+
product-name="deepl_write"
10+
>
11+
<header/>
12+
<body>
13+
<trans-unit id="cke.button.title">
14+
<source>DeepL Write</source>
15+
</trans-unit>
16+
<trans-unit id="cke.modal.title">
17+
<source>Optimize text with DeepL Write</source>
18+
</trans-unit>
19+
<trans-unit id="cke.modal.heading.style">
20+
<source>Style</source>
21+
</trans-unit>
22+
<trans-unit id="cke.modal.heading.tone">
23+
<source>Tone</source>
24+
</trans-unit>
25+
<trans-unit id="cke.modal.description.style">
26+
<source>Select a style to rewrite your text in a way that fits your audience and goals.</source>
27+
</trans-unit>
28+
<trans-unit id="cke.modal.description.tone">
29+
<source>Select the desired tone for your text</source>
30+
</trans-unit>
31+
<trans-unit id="cke.modal.description">
32+
<source>You can optimize your text by using the DeepL Write API. For some languages, you can choose the style or tone. Only one or the other is possible.</source>
33+
</trans-unit>
34+
<trans-unit id="cke.modal.button.optimize">
35+
<source>Optimize text</source>
36+
</trans-unit>
37+
<trans-unit id="cke.modal.button.save">
38+
<source>Apply changes</source>
39+
</trans-unit>
40+
</body>
41+
</file>
42+
</xliff>

0 commit comments

Comments
 (0)