Skip to content

Commit 9d629e2

Browse files
committed
Merge branch 'hotfix/188-double-translation' into develop
Forward port zendframework#188
2 parents 906a212 + c8ce65c commit 9d629e2

File tree

3 files changed

+144
-18
lines changed

3 files changed

+144
-18
lines changed

CHANGELOG.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,19 @@ All notable changes to this project will be documented in this file, in reverse
3434

3535
### Added
3636

37-
- Nothing.
37+
- [#188](https://github.com/zendframework/zend-form/pull/188) adds a new method to the `FormElementErrors` view helper, `setTranslateMessages(bool $flag)`.
38+
By default, the helper continues to translate error messages (if a translator
39+
is present), as introduced in 2.11.0. However, using this method, you can
40+
disable translation, which may be necessary to prevent double translation
41+
and/or to reduce logs from missed translation lookups. Because the method
42+
implements a fluent interface, you may do so in one line:
43+
44+
```php
45+
echo $this->formElementErrors()->setTranslateMessages(false);
46+
```
47+
48+
Note: you will need to reset the value afterwards if you want translations to occur
49+
in later invocations.
3850

3951
### Changed
4052

src/View/Helper/FormElementErrors.php

+71-17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class FormElementErrors extends AbstractHelper
2828
*/
2929
protected $attributes = [];
3030

31+
/**
32+
* @var bool Whether or not to translate error messages during render.
33+
*/
34+
protected $translateErrorMessages = true;
35+
3136
/**
3237
* Invoke helper as functor
3338
*
@@ -49,6 +54,10 @@ public function __invoke(ElementInterface $element = null, array $attributes = [
4954
/**
5055
* Render validation errors for the provided $element
5156
*
57+
* If {@link $translateErrorMessages} is true, and a translator is
58+
* composed, messages retrieved from the element will be translated; if
59+
* either is not the case, they will not.
60+
*
5261
* @param ElementInterface $element
5362
* @param array $attributes
5463
* @throws Exception\DomainException
@@ -60,39 +69,32 @@ public function render(ElementInterface $element, array $attributes = [])
6069
if (empty($messages)) {
6170
return '';
6271
}
63-
if (! is_array($messages) && ! $messages instanceof Traversable) {
72+
73+
$messages = $messages instanceof Traversable ? iterator_to_array($messages) : $messages;
74+
if (! is_array($messages)) {
6475
throw new Exception\DomainException(sprintf(
6576
'%s expects that $element->getMessages() will return an array or Traversable; received "%s"',
6677
__METHOD__,
6778
(is_object($messages) ? get_class($messages) : gettype($messages))
6879
));
6980
}
7081

82+
// Flatten message array
83+
$messages = $this->flattenMessages($messages);
84+
if (empty($messages)) {
85+
return '';
86+
}
87+
7188
// Prepare attributes for opening tag
7289
$attributes = array_merge($this->attributes, $attributes);
7390
$attributes = $this->createAttributesString($attributes);
7491
if (! empty($attributes)) {
7592
$attributes = ' ' . $attributes;
7693
}
7794

78-
// Flatten message array
79-
$escapeHtml = $this->getEscapeHtmlHelper();
80-
$messagesToPrint = [];
81-
$translator = $this->getTranslator();
82-
$textDomain = $this->getTranslatorTextDomain();
83-
$messageCallback = function ($item) use (&$messagesToPrint, $escapeHtml, $translator, $textDomain) {
84-
$item = $translator ? $translator->translate($item, $textDomain) : $item;
85-
$messagesToPrint[] = $escapeHtml($item);
86-
};
87-
array_walk_recursive($messages, $messageCallback);
88-
89-
if (empty($messagesToPrint)) {
90-
return '';
91-
}
92-
9395
// Generate markup
9496
$markup = sprintf($this->getMessageOpenFormat(), $attributes);
95-
$markup .= implode($this->getMessageSeparatorString(), $messagesToPrint);
97+
$markup .= implode($this->getMessageSeparatorString(), $messages);
9698
$markup .= $this->getMessageCloseString();
9799

98100
return $markup;
@@ -185,4 +187,56 @@ public function getMessageSeparatorString()
185187
{
186188
return $this->messageSeparatorString;
187189
}
190+
191+
/**
192+
* Set the flag detailing whether or not to translate error messages.
193+
*
194+
* @param bool $flag
195+
* @return self
196+
*/
197+
public function setTranslateMessages($flag)
198+
{
199+
$this->translateErrorMessages = (bool) $flag;
200+
return $this;
201+
}
202+
203+
/**
204+
* @param array $messages
205+
* @return array
206+
*/
207+
private function flattenMessages(array $messages)
208+
{
209+
return $this->translateErrorMessages && $this->getTranslator()
210+
? $this->flattenMessagesWithTranslator($messages)
211+
: $this->flattenMessagesWithoutTranslator($messages);
212+
}
213+
214+
/**
215+
* @param array $messages
216+
* @return array
217+
*/
218+
private function flattenMessagesWithoutTranslator(array $messages)
219+
{
220+
$messagesToPrint = [];
221+
array_walk_recursive($messages, function ($item) use (&$messagesToPrint) {
222+
$messagesToPrint[] = $item;
223+
});
224+
return $messagesToPrint;
225+
}
226+
227+
/**
228+
* @param array $messages
229+
* @return array
230+
*/
231+
private function flattenMessagesWithTranslator(array $messages)
232+
{
233+
$translator = $this->getTranslator();
234+
$textDomain = $this->getTranslatorTextDomain();
235+
$messagesToPrint = [];
236+
$messageCallback = function ($item) use (&$messagesToPrint, $translator, $textDomain) {
237+
$messagesToPrint[] = $translator->translate($item, $textDomain);
238+
};
239+
array_walk_recursive($messages, $messageCallback);
240+
return $messagesToPrint;
241+
}
188242
}

test/View/Helper/FormElementErrorsTest.php

+60
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,29 @@
1010
namespace ZendTest\Form\View\Helper;
1111

1212
use Zend\Form\Element;
13+
use Zend\Form\Form;
1314
use Zend\Form\View\Helper\FormElementErrors as FormElementErrorsHelper;
15+
use Zend\Validator\AbstractValidator;
1416

1517
class FormElementErrorsTest extends CommonTestCase
1618
{
19+
/**
20+
* @var null|\Zend\Validator\Translator\TranslatorInterface
21+
*/
22+
protected $defaultTranslator;
23+
1724
public function setUp()
1825
{
26+
$this->defaultTranslator = AbstractValidator::getDefaultTranslator();
1927
$this->helper = new FormElementErrorsHelper();
2028
parent::setUp();
2129
}
2230

31+
public function tearDown()
32+
{
33+
AbstractValidator::setDefaultTranslator($this->defaultTranslator);
34+
}
35+
2336
public function getMessageList()
2437
{
2538
return [
@@ -76,6 +89,53 @@ public function testRendersErrorMessagesUsingUnorderedListTranslated()
7689
// @codingStandardsIgnoreEnd
7790
}
7891

92+
public function testRendersErrorMessagesWithoutDoubleTranslation()
93+
{
94+
$form = new Form('test_form');
95+
$form->add([
96+
'name' => 'test_element',
97+
'type' => Element\Color::class,
98+
]);
99+
$form->setData(['test_element' => 'This is invalid!']);
100+
101+
$mockValidatorTranslator = $this->createMock('Zend\Validator\Translator\TranslatorInterface');
102+
$mockValidatorTranslator
103+
->expects(self::once())
104+
->method('translate')
105+
->willReturnCallback(
106+
function ($message) {
107+
self::assertEquals(
108+
'The input does not match against pattern \'%pattern%\'',
109+
$message,
110+
'Unexpected translation key.'
111+
);
112+
113+
return 'TRANSLATED: The input does not match against pattern \'%pattern%\'';
114+
}
115+
);
116+
117+
AbstractValidator::setDefaultTranslator($mockValidatorTranslator, 'default');
118+
119+
self::assertFalse($form->isValid());
120+
121+
$mockFormTranslator = $this->createMock('Zend\I18n\Translator\Translator');
122+
$mockFormTranslator
123+
->expects(self::never())
124+
->method('translate');
125+
126+
$this->helper->setTranslator($mockFormTranslator);
127+
$this->assertTrue($this->helper->hasTranslator());
128+
129+
$this->helper->setTranslatorTextDomain('default');
130+
131+
// Disable translation...
132+
$this->helper->setTranslateMessages(false);
133+
134+
$markup = $this->helper->render($form->get('test_element'));
135+
136+
$this->assertRegexp('#^<ul>\s*<li>TRANSLATED#s', $markup);
137+
}
138+
79139
public function testCanSpecifyAttributesForOpeningTag()
80140
{
81141
$messages = $this->getMessageList();

0 commit comments

Comments
 (0)