feature #10939 [Translation] [Xliff] Support for <note> (Jérémy Derussé)

This PR was squashed before being merged into the 2.6-dev branch (closes #10939).

Discussion
----------

[Translation] [Xliff] Support for <note>

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #10550
| License       | MIT
| Doc PR        | n/a

The purpose of this PR, is to avoid that the automated translation update (`app/console translation:update`) remove the `note` tags from XLIFF files.

Not sure if using the metadata bag is a good thing or if I should have add a "note" property in MessageCatalogue. What do you think ?

Commits
-------

d18e758 [Translation] [Xliff] Support for <note>
This commit is contained in:
Fabien Potencier 2014-06-17 07:29:58 +02:00
commit 6587b3989c
11 changed files with 162 additions and 11 deletions

View File

@ -33,6 +33,9 @@ class DiffOperation extends AbstractOperation
if ($this->target->has($id, $domain)) { if ($this->target->has($id, $domain)) {
$this->messages[$domain]['all'][$id] = $message; $this->messages[$domain]['all'][$id] = $message;
$this->result->add(array($id => $message), $domain); $this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
} else { } else {
$this->messages[$domain]['obsolete'][$id] = $message; $this->messages[$domain]['obsolete'][$id] = $message;
} }
@ -43,6 +46,9 @@ class DiffOperation extends AbstractOperation
$this->messages[$domain]['all'][$id] = $message; $this->messages[$domain]['all'][$id] = $message;
$this->messages[$domain]['new'][$id] = $message; $this->messages[$domain]['new'][$id] = $message;
$this->result->add(array($id => $message), $domain); $this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
} }
} }
} }

View File

@ -32,6 +32,9 @@ class MergeOperation extends AbstractOperation
foreach ($this->source->all($domain) as $id => $message) { foreach ($this->source->all($domain) as $id => $message) {
$this->messages[$domain]['all'][$id] = $message; $this->messages[$domain]['all'][$id] = $message;
$this->result->add(array($id => $message), $domain); $this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
} }
foreach ($this->target->all($domain) as $id => $message) { foreach ($this->target->all($domain) as $id => $message) {
@ -39,6 +42,9 @@ class MergeOperation extends AbstractOperation
$this->messages[$domain]['all'][$id] = $message; $this->messages[$domain]['all'][$id] = $message;
$this->messages[$domain]['new'][$id] = $message; $this->messages[$domain]['new'][$id] = $message;
$this->result->add(array($id => $message), $domain); $this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
} }
} }
} }

View File

@ -50,6 +50,26 @@ class XliffFileDumper extends FileDumper
$t = $translation->appendChild($dom->createElement('target')); $t = $translation->appendChild($dom->createElement('target'));
$t->appendChild($dom->createTextNode($target)); $t->appendChild($dom->createTextNode($target));
$metadata = $messages->getMetadata($source, $domain);
if (null !== $metadata && array_key_exists('notes', $metadata) && is_array($metadata['notes'])) {
foreach ($metadata['notes'] as $note) {
if (!isset($note['content'])) {
continue;
}
$n = $translation->appendChild($dom->createElement('note'));
$n->appendChild($dom->createTextNode($note['content']));
if (isset($note['priority'])) {
$n->setAttribute('priority', $note['priority']);
}
if (isset($note['from'])) {
$n->setAttribute('from', $note['from']);
}
}
}
$xliffBody->appendChild($translation); $xliffBody->appendChild($translation);
} }

View File

@ -53,27 +53,61 @@ class XliffFileLoader implements LoaderInterface
} }
$source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
$target = (string) $translation->target;
// If the xlf file has another encoding specified, try to convert it because // If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values // simple_xml will always return utf-8 encoded values
if ('UTF-8' !== $encoding && !empty($encoding)) { $target = $this->utf8ToCharset((string) $translation->target, $encoding);
if (function_exists('mb_convert_encoding')) {
$target = mb_convert_encoding($target, $encoding, 'UTF-8');
} elseif (function_exists('iconv')) {
$target = iconv('UTF-8', $encoding, $target);
} else {
throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
}
}
$catalogue->set((string) $source, $target, $domain); $catalogue->set((string) $source, $target, $domain);
if (isset($translation->note)) {
$notes = array();
foreach ($translation->note as $xmlNote) {
$noteAttributes = $xmlNote->attributes();
$note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding));
if (isset($noteAttributes['priority'])) {
$note['priority'] = (int) $noteAttributes['priority'];
}
if (isset($noteAttributes['from'])) {
$note['from'] = (string) $noteAttributes['from'];
}
$notes[] = $note;
}
$catalogue->setMetadata((string) $source, array('notes' => $notes), $domain);
}
} }
$catalogue->addResource(new FileResource($resource)); $catalogue->addResource(new FileResource($resource));
return $catalogue; return $catalogue;
} }
/**
* Convert a UTF8 string to the specified encoding
*
* @param string $content String to decode
* @param string $encoding Target encoding
*
* @return string
*/
private function utf8ToCharset($content, $encoding=null)
{
if ('UTF-8' !== $encoding && !empty($encoding)) {
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($content, $encoding, 'UTF-8');
}
if (function_exists('iconv')) {
return iconv('UTF-8', $encoding, $content);
}
throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
}
return $content;
}
/** /**
* Validates and parses the given file into a SimpleXMLElement * Validates and parses the given file into a SimpleXMLElement
* *

View File

@ -53,6 +53,28 @@ class DiffOperationTest extends AbstractOperationTest
); );
} }
public function testGetResultWithMetadata()
{
$leftCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b')));
$leftCatalogue->setMetadata('a', 'foo', 'messages');
$leftCatalogue->setMetadata('b', 'bar', 'messages');
$rightCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'new_b', 'c' => 'new_c')));
$rightCatalogue->setMetadata('b', 'baz', 'messages');
$rightCatalogue->setMetadata('c', 'qux', 'messages');
$diffCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'old_b', 'c' => 'new_c')));
$diffCatalogue->setMetadata('b', 'bar', 'messages');
$diffCatalogue->setMetadata('c', 'qux', 'messages');
$this->assertEquals(
$diffCatalogue,
$this->createOperation(
$leftCatalogue,
$rightCatalogue
)->getResult()
);
}
protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target) protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
{ {
return new DiffOperation($source, $target); return new DiffOperation($source, $target);

View File

@ -53,6 +53,29 @@ class MergeOperationTest extends AbstractOperationTest
); );
} }
public function testGetResultWithMetadata()
{
$leftCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b')));
$leftCatalogue->setMetadata('a', 'foo', 'messages');
$leftCatalogue->setMetadata('b', 'bar', 'messages');
$rightCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'new_b', 'c' => 'new_c')));
$rightCatalogue->setMetadata('b', 'baz', 'messages');
$rightCatalogue->setMetadata('c', 'qux', 'messages');
$mergedCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c')));
$mergedCatalogue->setMetadata('a', 'foo', 'messages');
$mergedCatalogue->setMetadata('b', 'bar', 'messages');
$mergedCatalogue->setMetadata('c', 'qux', 'messages');
$this->assertEquals(
$mergedCatalogue,
$this->createOperation(
$leftCatalogue,
$rightCatalogue
)->getResult()
);
}
protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target) protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
{ {
return new MergeOperation($source, $target); return new MergeOperation($source, $target);

View File

@ -20,6 +20,8 @@ class XliffFileDumperTest extends \PHPUnit_Framework_TestCase
{ {
$catalogue = new MessageCatalogue('en'); $catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar', 'key' => '')); $catalogue->add(array('foo' => 'bar', 'key' => ''));
$catalogue->setMetadata('foo', array('notes' => array(array('priority' => 1, 'from' => 'bar', 'content' => 'baz'))));
$catalogue->setMetadata('key', array('notes' => array(array('content' => 'baz'), array('content' => 'qux'))));
$tempDir = sys_get_temp_dir(); $tempDir = sys_get_temp_dir();
$dumper = new XliffFileDumper(); $dumper = new XliffFileDumper();

View File

@ -54,6 +54,7 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(utf8_decode('föö'), $catalogue->get('bar', 'domain1')); $this->assertEquals(utf8_decode('föö'), $catalogue->get('bar', 'domain1'));
$this->assertEquals(utf8_decode('bär'), $catalogue->get('foo', 'domain1')); $this->assertEquals(utf8_decode('bär'), $catalogue->get('foo', 'domain1'));
$this->assertEquals(array('notes' => array(array('content' => utf8_decode('bäz')))), $catalogue->getMetadata('foo', 'domain1'));
} }
/** /**
@ -111,4 +112,15 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s":', $resource)); $this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s":', $resource));
$loader->load($resource, 'en', 'domain1'); $loader->load($resource, 'en', 'domain1');
} }
public function testLoadNotes()
{
$loader = new XliffFileLoader();
$catalogue = $loader->load(__DIR__.'/../fixtures/withnote.xlf', 'en', 'domain1');
$this->assertEquals(array('notes' => array(array('priority' => 1, 'content' => 'foo'))), $catalogue->getMetadata('foo', 'domain1'));
// message without target
$this->assertNull($catalogue->getMetadata('extra', 'domain1'));
$this->assertEquals(array('notes' => array(array('content' => 'baz'), array('priority' => 2, 'from' => 'bar', 'content' => 'qux'))), $catalogue->getMetadata('key', 'domain1'));
}
} }

View File

@ -5,6 +5,7 @@
<trans-unit id="1" resname="foo"> <trans-unit id="1" resname="foo">
<source>foo</source> <source>foo</source>
<target>bär</target> <target>bär</target>
<note>bäz</note>
</trans-unit> </trans-unit>
<trans-unit id="2" resname="bar"> <trans-unit id="2" resname="bar">
<source>bar</source> <source>bar</source>

View File

@ -5,10 +5,13 @@
<trans-unit id="acbd18db4cc2f85cedef654fccc4a4d8" resname="foo"> <trans-unit id="acbd18db4cc2f85cedef654fccc4a4d8" resname="foo">
<source>foo</source> <source>foo</source>
<target>bar</target> <target>bar</target>
<note priority="1" from="bar">baz</note>
</trans-unit> </trans-unit>
<trans-unit id="3c6e0b8a9c15224a8228b9a98ca1531d" resname="key"> <trans-unit id="3c6e0b8a9c15224a8228b9a98ca1531d" resname="key">
<source>key</source> <source>key</source>
<target></target> <target></target>
<note>baz</note>
<note>qux</note>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>foo</source>
<target>bar</target>
<note priority="1">foo</note>
</trans-unit>
<trans-unit id="2">
<source>extra</source>
<note from="foo">bar</note>
</trans-unit>
<trans-unit id="3">
<source>key</source>
<target></target>
<note>baz</note>
<note priority="2" from="bar">qux</note>
</trans-unit>
</body>
</file>
</xliff>