Skip to content

Commit 5854268

Browse files
committed
ACP2E-912: Translate inline not working properly for Javascript
1 parent cd826aa commit 5854268

File tree

5 files changed

+160
-56
lines changed

5 files changed

+160
-56
lines changed

app/code/Magento/Translation/Model/Inline/Parser.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,13 @@ private function _getTranslateData(string $regexp, string &$text, callable $loca
420420
$trArr = [];
421421
$next = 0;
422422
while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE, $next)) {
423+
423424
$trArr[] = json_encode(
424425
[
425-
'shown' => htmlspecialchars_decode($matches[1][0]),
426-
'translated' => htmlspecialchars_decode($matches[2][0]),
427-
'original' => htmlspecialchars_decode($matches[3][0]),
428-
'location' => htmlspecialchars_decode($locationCallback($matches, $options)),
426+
'shown' => $this->unescape((string)$matches[1][0], $options),
427+
'translated' => $this->unescape((string)$matches[2][0], $options),
428+
'original' => $this->unescape((string)$matches[3][0], $options),
429+
'location' => $this->unescape((string) $locationCallback($matches, $options), $options),
429430
]
430431
);
431432

@@ -439,6 +440,27 @@ private function _getTranslateData(string $regexp, string &$text, callable $loca
439440
return $trArr;
440441
}
441442

443+
/**
444+
* Unescape string based on the context
445+
*
446+
* Unescape special characters and unicode characters to prevent double escaping
447+
*
448+
* @param string $string
449+
* @param array $options
450+
* @return string
451+
*/
452+
private function unescape(string $string, array $options): string
453+
{
454+
if ($string && !ctype_digit($string) && isset($options['tagName']) && $options['tagName'] === 'script') {
455+
$decodedString = json_decode('["' . $string . '"]', true);
456+
if (json_last_error() === JSON_ERROR_NONE) {
457+
$string = implode($decodedString);
458+
}
459+
}
460+
461+
return htmlspecialchars_decode($string);
462+
}
463+
442464
/**
443465
* Prepare tags inline translates
444466
*

app/code/Magento/Translation/Test/Unit/Model/Inline/ParserTest.php

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Magento\Framework\App\Cache\TypeListInterface;
1111
use Magento\Framework\App\State;
12+
use Magento\Framework\Escaper;
1213
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
1314
use Magento\Framework\Translate\InlineInterface;
1415
use Magento\Store\Api\Data\StoreInterface;
@@ -20,6 +21,9 @@
2021
use PHPUnit\Framework\MockObject\MockObject;
2122
use PHPUnit\Framework\TestCase;
2223

24+
/**
25+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
26+
*/
2327
class ParserTest extends TestCase
2428
{
2529
/**
@@ -122,6 +126,7 @@ protected function setUp(): void
122126
'appCache' => $this->appCacheMock,
123127
'translateInline' => $this->translateInlineMock,
124128
'cacheManager' => $this->cacheManagerMock,
129+
'escaper' => $this->getMockEscaper()
125130
]
126131
);
127132
}
@@ -143,26 +148,27 @@ public function testProcessAjaxPost()
143148
$this->model->processAjaxPost([]);
144149
}
145150

146-
public function testProcessResponseBodyStringProcessingAttributesCorrectly()
151+
/**
152+
* @return void
153+
*/
154+
public function testProcessResponseBodyString(): void
155+
{
156+
$html = file_get_contents(__DIR__ . '/_files/input.html');
157+
$expectedOutput = file_get_contents(__DIR__ . '/_files/output.html');
158+
$actualOutput = $this->model->processResponseBodyString($html);
159+
$this->assertEquals($expectedOutput, $actualOutput);
160+
}
161+
162+
/**
163+
* @return Escaper
164+
*/
165+
private function getMockEscaper(): Escaper
147166
{
148-
$testContent = file_get_contents(__DIR__ . '/_files/datatranslate_fixture.html');
149-
$processedAttributes = [
150-
"data-translate=\"[{"shown":"* Required Fields","translated":"* Required Fields","
151-
. ""original":"* Required Fields","location":"Tag attribute (ALT, TITLE, etc.)"}]\"",
152-
"data-translate=\"[{"shown":"Email","translated":"Email","original":"Email","
153-
. ""location":"Tag attribute (ALT, TITLE, etc.)"}]\"",
154-
"data-translate=\"[{"shown":"Password","translated":"Password","original":"Password","
155-
. ""location":"Tag attribute (ALT, TITLE, etc.)"}]\""
156-
];
157-
$this->translateInlineMock->method('getAdditionalHtmlAttribute')->willReturn(null);
158-
159-
$processedContent = $this->model->processResponseBodyString($testContent);
160-
foreach ($processedAttributes as $attribute) {
161-
$this->assertStringContainsString(
162-
$attribute,
163-
$processedContent,
164-
'data-translate attribute not processed correctly'
165-
);
166-
}
167+
$escaper = new Escaper();
168+
$reflection = new \ReflectionClass($escaper);
169+
$reflectionProperty = $reflection->getProperty('escaper');
170+
$reflectionProperty->setAccessible(true);
171+
$reflectionProperty->setValue($escaper, new \Magento\Framework\ZendEscaper());
172+
return $escaper;
167173
}
168174
}

app/code/Magento/Translation/Test/Unit/Model/Inline/_files/datatranslate_fixture.html

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!--
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
-->
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<meta charset="UTF-8">
11+
<title>Title</title>
12+
</head>
13+
<body>
14+
<div class="login-container">
15+
<div class="block block-customer-login">
16+
<div class="block-content" aria-labelledby="block-customer-login-heading">
17+
<form class="form form-login"
18+
action=""
19+
method="post"
20+
id="login-form"
21+
data-mage-init='{"validation":{}}'>
22+
<input name="form_key" type="hidden" value="" />
23+
<fieldset class="fieldset login" data-hasrequired="{{{* Required Fields}}{{* Required Fields}}{{* Required Fields}}{{theme3}}}">
24+
<div class="field note">{{{If you have an account, sign in with your email address.}}{{If you have an account, sign in with your email address.}}{{If you have an account, sign in with your email address.}}{{theme3}}}</div>
25+
<div class="field email required">
26+
<label class="label" for="email"><span>{{{Email}}{{Email}}{{Email}}{{theme3}}}</span></label>
27+
<div class="control">
28+
<input name="login[username]" value="" autocomplete="off" id="email" type="email" class="input-text" title="{{{Email}}{{Email}}{{Email}}{{theme3}}}" data-validate="{required:true, 'validate-email':true}">
29+
</div>
30+
</div>
31+
<div class="field password required">
32+
<label for="pass" class="label"><span>{{{Password}}{{Password}}{{Password}}{{theme3}}}</span></label>
33+
<div class="control">
34+
<input name="login[password]" type="password" autocomplete="off" class="input-text" id="pass" title="{{{Password}}{{Password}}{{Password}}{{theme3}}}" data-validate="{required:true}">
35+
</div>
36+
</div>
37+
<div class="actions-toolbar">
38+
<div class="primary"><button type="submit" class="action login primary" name="send" id="send2"><span>{{{Sign In}}{{Sign In}}{{Sign In}}{{theme3}}}</span></button></div>
39+
<div class="secondary"><a class="action remind" href=""><span>{{{Forgot Your Password?}}{{Forgot Your Password?}}{{Forgot Your Password?}}{{theme3}}}</span></a></div>
40+
</div>
41+
</fieldset>
42+
</form>
43+
</div>
44+
</div>
45+
</div>
46+
<script type="text/x-magento-init">
47+
{
48+
"*": {
49+
"warningMsgList": {"deleteProduct":"{{{M\u00f6chten Sie dieses \"Produkt\" wirklich l\u00f6schen?}}{{M\u00f6chten Sie dieses \"Produkt\" wirklich l\u00f6schen?}}{{Are you sure you want to delete this \"product\"?}}{{theme3}}}"}
50+
}
51+
}
52+
</script>
53+
</body>
54+
</html>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!--
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
-->
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<meta charset="UTF-8">
11+
<title>Title</title>
12+
</head>
13+
<body>
14+
<div class="login-container">
15+
<div class="block block-customer-login">
16+
<div class="block-content" aria-labelledby="block-customer-login-heading">
17+
<form class="form form-login"
18+
action=""
19+
method="post"
20+
id="login-form"
21+
data-mage-init='{"validation":{}}'>
22+
<input name="form_key" type="hidden" value="" />
23+
<fieldset data-translate="[{&quot;shown&quot;:&quot;* Required Fields&quot;,&quot;translated&quot;:&quot;* Required Fields&quot;,&quot;original&quot;:&quot;* Required Fields&quot;,&quot;location&quot;:&quot;Tag attribute (ALT, TITLE, etc.)&quot;}]"class="fieldset login" data-hasrequired="* Required Fields">
24+
<div class="field note"><span data-translate="[&#x7B;&quot;shown&quot;&#x3A;&quot;If&#x20;you&#x20;have&#x20;an&#x20;account,&#x20;sign&#x20;in&#x20;with&#x20;your&#x20;email&#x20;address.&quot;,&quot;translated&quot;&#x3A;&quot;If&#x20;you&#x20;have&#x20;an&#x20;account,&#x20;sign&#x20;in&#x20;with&#x20;your&#x20;email&#x20;address.&quot;,&quot;original&quot;&#x3A;&quot;If&#x20;you&#x20;have&#x20;an&#x20;account,&#x20;sign&#x20;in&#x20;with&#x20;your&#x20;email&#x20;address.&quot;,&quot;location&quot;&#x3A;&quot;Text&quot;,&quot;scope&quot;&#x3A;&quot;theme3&quot;&#x7D;]">If you have an account, sign in with your email address.</span></div>
25+
<div class="field email required">
26+
<label data-translate="[{&quot;shown&quot;:&quot;Email&quot;,&quot;translated&quot;:&quot;Email&quot;,&quot;original&quot;:&quot;Email&quot;,&quot;location&quot;:&quot;Label for an input element.&quot;}]" class="label" for="email"><span>Email</span></label>
27+
<div class="control">
28+
<input data-translate="[{&quot;shown&quot;:&quot;Email&quot;,&quot;translated&quot;:&quot;Email&quot;,&quot;original&quot;:&quot;Email&quot;,&quot;location&quot;:&quot;Tag attribute (ALT, TITLE, etc.)&quot;}]"name="login[username]" value="" autocomplete="off" id="email" type="email" class="input-text" title="Email" data-validate="{required:true, 'validate-email':true}">
29+
</div>
30+
</div>
31+
<div class="field password required">
32+
<label data-translate="[{&quot;shown&quot;:&quot;Password&quot;,&quot;translated&quot;:&quot;Password&quot;,&quot;original&quot;:&quot;Password&quot;,&quot;location&quot;:&quot;Label for an input element.&quot;}]" for="pass" class="label"><span>Password</span></label>
33+
<div class="control">
34+
<input data-translate="[{&quot;shown&quot;:&quot;Password&quot;,&quot;translated&quot;:&quot;Password&quot;,&quot;original&quot;:&quot;Password&quot;,&quot;location&quot;:&quot;Tag attribute (ALT, TITLE, etc.)&quot;}]"name="login[password]" type="password" autocomplete="off" class="input-text" id="pass" title="Password" data-validate="{required:true}">
35+
</div>
36+
</div>
37+
<div class="actions-toolbar">
38+
<div class="primary"><button data-translate="[{&quot;shown&quot;:&quot;Sign In&quot;,&quot;translated&quot;:&quot;Sign In&quot;,&quot;original&quot;:&quot;Sign In&quot;,&quot;location&quot;:&quot;Push button&quot;}]" type="submit" class="action login primary" name="send" id="send2"><span>Sign In</span></button></div>
39+
<div class="secondary"><a data-translate="[{&quot;shown&quot;:&quot;Forgot Your Password?&quot;,&quot;translated&quot;:&quot;Forgot Your Password?&quot;,&quot;original&quot;:&quot;Forgot Your Password?&quot;,&quot;location&quot;:&quot;Link label&quot;}]" class="action remind" href=""><span>Forgot Your Password?</span></a></div>
40+
</div>
41+
</fieldset>
42+
</form>
43+
</div>
44+
</div>
45+
</div>
46+
<script type="text/x-magento-init">
47+
{
48+
"*": {
49+
"warningMsgList": {"deleteProduct":"Are you sure you want to delete this \"product\"?"}
50+
}
51+
}
52+
</script><span class="translate-inline-script" data-translate="[{&quot;shown&quot;:&quot;M\u00f6chten Sie dieses \&quot;Produkt\&quot; wirklich l\u00f6schen?&quot;,&quot;translated&quot;:&quot;M\u00f6chten Sie dieses \&quot;Produkt\&quot; wirklich l\u00f6schen?&quot;,&quot;original&quot;:&quot;Are you sure you want to delete this \&quot;product\&quot;?&quot;,&quot;location&quot;:&quot;String in Javascript&quot;}]">SCRIPT</span>
53+
</body>
54+
</html>

0 commit comments

Comments
 (0)