-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
256 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Stadly\Translation\Loader; | ||
|
||
use Symfony\Component\Translation\Exception\InvalidResourceException; | ||
use Symfony\Component\Translation\Loader\FileLoader; | ||
|
||
final class MoFileLoader extends FileLoader | ||
{ | ||
// phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing | ||
// phpcs:disable SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint | ||
/** | ||
* Parses machine object (MO) format, independent of the machine's endian it | ||
* was created on. Both 32bit and 64bit systems are supported. | ||
* | ||
* @param string $filename Path to resource to load. | ||
* @return array<string|int, string> Strings loaded from resource. | ||
*/ | ||
protected function loadResource($filename): array | ||
{ | ||
$stream = fopen($filename, 'r'); | ||
if ($stream === false) { | ||
// @codeCoverageIgnoreStart | ||
throw new InvalidResourceException('Could not open file.'); | ||
// @codeCoverageIgnoreEnd | ||
} | ||
|
||
$this->readLong($stream); // magicNumber | ||
$this->readLong($stream); // formatRevision | ||
$count = $this->readLong($stream); | ||
$offsetId = $this->readLong($stream); | ||
$offsetTranslated = $this->readLong($stream); | ||
$this->readLong($stream); // sizeHashes | ||
$this->readLong($stream); // offsetHashes | ||
|
||
$messages = []; | ||
|
||
for ($i = 0; $i < $count; ++$i) { | ||
fseek($stream, $offsetId + $i * 8); | ||
$id = $this->readEntry($stream); | ||
|
||
fseek($stream, $offsetTranslated + $i * 8); | ||
$translated = $this->readEntry($stream); | ||
|
||
if ($id !== null && $translated !== null) { | ||
$messages[$id] = $translated; | ||
} | ||
} | ||
|
||
fclose($stream); | ||
|
||
return $messages; | ||
} | ||
// phpcs:enable | ||
|
||
/** | ||
* Reads an unsigned long from stream. | ||
* | ||
* @param resource $stream Stream to read from. | ||
* @return int The read number. | ||
*/ | ||
private function readLong($stream): int | ||
{ | ||
$string = fread($stream, 4); | ||
if ($string === false) { | ||
// @codeCoverageIgnoreStart | ||
throw new InvalidResourceException('Could not read from file.'); | ||
// @codeCoverageIgnoreEnd | ||
} | ||
if (strlen($string) !== 4) { | ||
throw new InvalidResourceException('MO stream content has an invalid format.'); | ||
} | ||
$number = unpack('V', $string); | ||
|
||
return $number[1]; | ||
} | ||
|
||
/** | ||
* @param resource $stream Stream to read from. | ||
* @return string|null The read entry or null on failure. | ||
*/ | ||
private function readEntry($stream): ?string | ||
{ | ||
$length = $this->readLong($stream); | ||
$offset = $this->readLong($stream); | ||
|
||
if ($length < 1) { | ||
return null; | ||
} | ||
|
||
fseek($stream, $offset); | ||
$string = fread($stream, $length); | ||
if ($string === false) { | ||
// @codeCoverageIgnoreStart | ||
throw new InvalidResourceException('Could not read from file.'); | ||
// @codeCoverageIgnoreEnd | ||
} | ||
return implode('|', explode("\000", $string)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Stadly\Translation\Loader; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Translation\Exception\InvalidResourceException; | ||
use Symfony\Component\Translation\Exception\NotFoundResourceException; | ||
|
||
/** | ||
* @coversDefaultClass \Stadly\Translation\Loader\MoFileLoader | ||
* @covers ::<protected> | ||
* @covers ::<private> | ||
*/ | ||
final class MoFileLoaderTest extends TestCase | ||
{ | ||
/** | ||
* @covers ::load | ||
*/ | ||
public function testCanLoadFile(): void | ||
{ | ||
$loader = new MoFileLoader(); | ||
$resource = __DIR__ . '/../resources/messages.mo'; | ||
$catalogue = $loader->load($resource, 'en_US', 'messages'); | ||
|
||
self::assertEquals([ | ||
'foo' | ||
=> 'bar', | ||
'one foo|%count% foos' | ||
=> 'one bar|%count% bars', | ||
'{0} no foos|one foo|%count% foos' | ||
=> '{0} no bars|one bar|%count% bars', | ||
'missing foo plural|missing foo plurals' | ||
=> 'missing bar plural|', | ||
'string containing || foo' | ||
=> 'string containing || bar', | ||
'one string containing || foo|%count% strings containing || foos' | ||
=> 'one string containing || bar|%count% strings containing || bars', | ||
'{0} no strings containing || foos|one string containing || foo|%count% strings containing || foos' | ||
=> '{0} no strings containing || bars|one string containing || bar|%count% strings containing || bars', | ||
'escaped "foo"' | ||
=> 'escaped "bar"', | ||
'one escaped "foo"|%count% escaped "foos"' | ||
=> 'one escaped "bar"|%count% escaped "bars"', | ||
'{0} no escaped "foos"|one one escaped "foo"|%count% escaped "foos"' | ||
=> '{0} no escaped "bars"|one one escaped "bar"|%count% escaped "bars"', | ||
], $catalogue->all('messages')); | ||
} | ||
|
||
/** | ||
* @covers ::load | ||
*/ | ||
public function testCannotLoadEmptyFile(): void | ||
{ | ||
$loader = new MoFileLoader(); | ||
$resource = __DIR__ . '/../resources/empty.mo'; | ||
|
||
$this->expectException(InvalidResourceException::class); | ||
|
||
$loader->load($resource, 'en_US', 'messages'); | ||
} | ||
|
||
/** | ||
* @covers ::load | ||
*/ | ||
public function testCannotLoadInvalidFile(): void | ||
{ | ||
$loader = new MoFileLoader(); | ||
$resource = __DIR__ . '/../resources/invalid.mo'; | ||
|
||
$this->expectException(InvalidResourceException::class); | ||
|
||
$loader->load($resource, 'en_US', 'messages'); | ||
} | ||
|
||
/** | ||
* @covers ::load | ||
*/ | ||
public function testCannotLoadNonExistingFile(): void | ||
{ | ||
$loader = new MoFileLoader(); | ||
$resource = __DIR__ . '/../resources/non-existing.mo'; | ||
|
||
$this->expectException(NotFoundResourceException::class); | ||
|
||
$loader->load($resource, 'en_US', 'messages'); | ||
} | ||
} |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
foobar | ||
test | ||
this is an invalid translation file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
foobar | ||
test | ||
this is an invalid translation file |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
msgid "" | ||
msgstr "" | ||
"Content-Type: text/plain; charset=UTF-8\n" | ||
"Content-Transfer-Encoding: 8bit\n" | ||
|
||
msgid "foo" | ||
msgstr "bar" | ||
|
||
msgid "one foo" | ||
msgid_plural "%count% foos" | ||
msgstr[0] "one bar" | ||
msgstr[1] "%count% bars" | ||
|
||
msgid "{0} no foos|one foo|%count% foos" | ||
msgstr "{0} no bars|one bar|%count% bars" | ||
|
||
msgid "missing foo plural" | ||
msgid_plural "missing foo plurals" | ||
msgstr[0] "missing bar plural" | ||
msgstr[1] "" | ||
|
||
msgid "missing foo singular" | ||
msgid_plural "missing foo singulars" | ||
msgstr[0] "" | ||
msgstr[1] "missing bar singulars" | ||
|
||
msgid "message without translation" | ||
msgstr "" | ||
|
||
msgid "one message without translation" | ||
msgid_plural "%count% messages without translations" | ||
msgstr[0] "" | ||
msgstr[1] "" | ||
|
||
msgid "{0} no messages without translations|one message without translation|%count% messages without translations" | ||
msgstr "" | ||
|
||
msgid "string containing || foo" | ||
msgstr "string containing || bar" | ||
|
||
msgid "one string containing || foo" | ||
msgid_plural "%count% strings containing || foos" | ||
msgstr[0] "one string containing || bar" | ||
msgstr[1] "%count% strings containing || bars" | ||
|
||
msgid "{0} no strings containing || foos|one string containing || foo|%count% strings containing || foos" | ||
msgstr "{0} no strings containing || bars|one string containing || bar|%count% strings containing || bars" | ||
|
||
msgid "escaped \"foo\"" | ||
msgstr "escaped \"bar\"" | ||
|
||
msgid "one escaped \"foo\"" | ||
msgid_plural "%count% escaped \"foos\"" | ||
msgstr[0] "one escaped \"bar\"" | ||
msgstr[1] "%count% escaped \"bars\"" | ||
|
||
msgid "{0} no escaped \"foos\"|one one escaped \"foo\"|%count% escaped \"foos\"" | ||
msgstr "{0} no escaped \"bars\"|one one escaped \"bar\"|%count% escaped \"bars\"" |