Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Gettext support #221

Merged
merged 1 commit into from
May 11, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added tests/unit/data/i18n/test.mo
Binary file not shown.
64 changes: 64 additions & 0 deletions tests/unit/data/i18n/test.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: resurtm <[email protected]>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.5\n"

msgctxt "context1"
msgid ""
"Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\n"
"aliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel "
"malesuada.\n"
"Nunc vel sapien nunc, a pretium nulla."
msgstr ""
"Олицетворение однократно. Представленный лексико-семантический анализ "
"является\n"
"психолингвистическим в своей основе, но механизм сочленений полидисперсен. "
"Впечатление\n"
"однократно. Различное расположение выбирает сюжетный механизм сочленений."

msgctxt "context1"
msgid "String number two."
msgstr "Строка номер два."

msgctxt "context2"
msgid ""
"The other\n"
"\n"
"context.\n"
msgstr ""
"Другой\n"
"\n"
"контекст.\n"

msgctxt "context1"
msgid ""
"Missing\n"
"\r\t\"translation."
msgstr ""

msgctxt "context1"
msgid ""
"Nunc vel sapien nunc, a pretium nulla.\n"
"Pellentesque habitant morbi tristique senectus et netus et malesuada fames "
"ac turpis egestas."
msgstr "Короткий перевод."

msgid "contextless"
msgstr ""

msgctxt "context2"
msgid ""
"test1\\ntest2\n"
"\\\n"
"test3"
msgstr ""
"тест1\\nтест2\n"
"\\\n"
"тест3"
14 changes: 14 additions & 0 deletions tests/unit/framework/i18n/GettextMessageSourceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace yiiunit\framework\i18n;

use yii\i18n\GettextMessageSource;
use yiiunit\TestCase;

class GettextMessageSourceTest extends TestCase
{
public function testLoadMessages()
{
$this->markTestSkipped();
}
}
95 changes: 95 additions & 0 deletions tests/unit/framework/i18n/GettextMoFileTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace yiiunit\framework\i18n;

use yii\i18n\GettextMoFile;
use yiiunit\TestCase;

class GettextMoFileTest extends TestCase
{
public function testLoad()
{
$moFile = new GettextMoFile();
$moFilePath = __DIR__ . '/../../data/i18n/test.mo';
$context1 = $moFile->load($moFilePath, 'context1');
$context2 = $moFile->load($moFilePath, 'context2');

// item count
$this->assertCount(3, $context1);
$this->assertCount(2, $context2);

// original messages
$this->assertArrayNotHasKey("Missing\n\r\t\"translation.", $context1);
$this->assertArrayHasKey("Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\naliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel malesuada.\nNunc vel sapien nunc, a pretium nulla.", $context1);
$this->assertArrayHasKey("String number two.", $context1);
$this->assertArrayHasKey("Nunc vel sapien nunc, a pretium nulla.\nPellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", $context1);

$this->assertArrayHasKey("The other\n\ncontext.\n", $context2);
$this->assertArrayHasKey("test1\\ntest2\n\\\ntest3", $context2);

// translated messages
$this->assertFalse(in_array("", $context1));
$this->assertTrue(in_array("Олицетворение однократно. Представленный лексико-семантический анализ является\nпсихолингвистическим в своей основе, но механизм сочленений полидисперсен. Впечатление\nоднократно. Различное расположение выбирает сюжетный механизм сочленений.", $context1));
$this->assertTrue(in_array('Строка номер два.', $context1));
$this->assertTrue(in_array('Короткий перевод.', $context1));

$this->assertTrue(in_array("Другой\n\nконтекст.\n", $context2));
$this->assertTrue(in_array("тест1\\nтест2\n\\\nтест3", $context2));
}

public function testSave()
{
// initial data
$s = chr(4);
$messages = array(
'Hello!' => 'Привет!',
"context1{$s}Hello?" => 'Привет?',
'Hello!?' => '',
"context1{$s}Hello!?!" => '',
"context2{$s}\"Quotes\"" => '"Кавычки"',
"context2{$s}\nNew lines\n" => "\nПереносы строк\n",
"context2{$s}\tTabs\t" => "\tТабы\t",
"context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r",
);

// create temporary directory and dump messages
$poFileDirectory = __DIR__ . '/../../runtime/i18n';
if (!is_dir($poFileDirectory)) {
mkdir($poFileDirectory);
}
if (is_file($poFileDirectory . '/test.mo')) {
unlink($poFileDirectory . '/test.mo');
}

$moFile = new GettextMoFile();
$moFile->save($poFileDirectory . '/test.mo', $messages);

// load messages
$context1 = $moFile->load($poFileDirectory . '/test.mo', 'context1');
$context2 = $moFile->load($poFileDirectory . '/test.mo', 'context2');

// context1
$this->assertCount(2, $context1);

$this->assertArrayHasKey('Hello?', $context1);
$this->assertTrue(in_array('Привет?', $context1));

$this->assertArrayHasKey('Hello!?!', $context1);
$this->assertTrue(in_array('', $context1));

// context2
$this->assertCount(4, $context2);

$this->assertArrayHasKey("\"Quotes\"", $context2);
$this->assertTrue(in_array('"Кавычки"', $context2));

$this->assertArrayHasKey("\nNew lines\n", $context2);
$this->assertTrue(in_array("\nПереносы строк\n", $context2));

$this->assertArrayHasKey("\tTabs\t", $context2);
$this->assertTrue(in_array("\tТабы\t", $context2));

$this->assertArrayHasKey("\rCarriage returns\r", $context2);
$this->assertTrue(in_array("\rВозвраты кареток\r", $context2));
}
}
95 changes: 95 additions & 0 deletions tests/unit/framework/i18n/GettextPoFileTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace yiiunit\framework\i18n;

use yii\i18n\GettextPoFile;
use yiiunit\TestCase;

class GettextPoFileTest extends TestCase
{
public function testLoad()
{
$poFile = new GettextPoFile();
$poFilePath = __DIR__ . '/../../data/i18n/test.po';
$context1 = $poFile->load($poFilePath, 'context1');
$context2 = $poFile->load($poFilePath, 'context2');

// item count
$this->assertCount(4, $context1);
$this->assertCount(2, $context2);

// original messages
$this->assertArrayHasKey("Missing\n\r\t\"translation.", $context1);
$this->assertArrayHasKey("Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\naliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel malesuada.\nNunc vel sapien nunc, a pretium nulla.", $context1);
$this->assertArrayHasKey("String number two.", $context1);
$this->assertArrayHasKey("Nunc vel sapien nunc, a pretium nulla.\nPellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", $context1);

$this->assertArrayHasKey("The other\n\ncontext.\n", $context2);
$this->assertArrayHasKey("test1\\\ntest2\n\\\\\ntest3", $context2);

// translated messages
$this->assertTrue(in_array("", $context1));
$this->assertTrue(in_array("Олицетворение однократно. Представленный лексико-семантический анализ является\nпсихолингвистическим в своей основе, но механизм сочленений полидисперсен. Впечатление\nоднократно. Различное расположение выбирает сюжетный механизм сочленений.", $context1));
$this->assertTrue(in_array('Строка номер два.', $context1));
$this->assertTrue(in_array('Короткий перевод.', $context1));

$this->assertTrue(in_array("Другой\n\nконтекст.\n", $context2));
$this->assertTrue(in_array("тест1\\\nтест2\n\\\\\nтест3", $context2));
}

public function testSave()
{
// initial data
$s = chr(4);
$messages = array(
'Hello!' => 'Привет!',
"context1{$s}Hello?" => 'Привет?',
'Hello!?' => '',
"context1{$s}Hello!?!" => '',
"context2{$s}\"Quotes\"" => '"Кавычки"',
"context2{$s}\nNew lines\n" => "\nПереносы строк\n",
"context2{$s}\tTabs\t" => "\tТабы\t",
"context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r",
);

// create temporary directory and dump messages
$poFileDirectory = __DIR__ . '/../../runtime/i18n';
if (!is_dir($poFileDirectory)) {
mkdir($poFileDirectory);
}
if (is_file($poFileDirectory . '/test.po')) {
unlink($poFileDirectory . '/test.po');
}

$poFile = new GettextPoFile();
$poFile->save($poFileDirectory . '/test.po', $messages);

// load messages
$context1 = $poFile->load($poFileDirectory . '/test.po', 'context1');
$context2 = $poFile->load($poFileDirectory . '/test.po', 'context2');

// context1
$this->assertCount(2, $context1);

$this->assertArrayHasKey('Hello?', $context1);
$this->assertTrue(in_array('Привет?', $context1));

$this->assertArrayHasKey('Hello!?!', $context1);
$this->assertTrue(in_array('', $context1));

// context2
$this->assertCount(4, $context2);

$this->assertArrayHasKey("\"Quotes\"", $context2);
$this->assertTrue(in_array('"Кавычки"', $context2));

$this->assertArrayHasKey("\nNew lines\n", $context2);
$this->assertTrue(in_array("\nПереносы строк\n", $context2));

$this->assertArrayHasKey("\tTabs\t", $context2);
$this->assertTrue(in_array("\tТабы\t", $context2));

$this->assertArrayHasKey("\rCarriage returns\r", $context2);
$this->assertTrue(in_array("\rВозвраты кареток\r", $context2));
}
}
37 changes: 37 additions & 0 deletions yii/i18n/GettextFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace yii\i18n;

use yii\base\Component;

/**
* GettextFile is the base class for representing a Gettext message file.
*
* @author Qiang Xue <[email protected]>
* @since 2.0
*/
abstract class GettextFile extends Component
{
/**
* Loads messages from a file.
* @param string $filePath file path
* @param string $context message context
* @return array message translations. Array keys are source messages and array values are translated messages:
* source message => translated message.
*/
abstract public function load($filePath, $context);

/**
* Saves messages to a file.
* @param string $filePath file path
* @param array $messages message translations. Array keys are source messages and array values are
* translated messages: source message => translated message. Note if the message has a context,
* the message ID must be prefixed with the context with chr(4) as the separator.
*/
abstract public function save($filePath, $messages);
}
59 changes: 59 additions & 0 deletions yii/i18n/GettextMessageSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace yii\i18n;

use Yii;

class GettextMessageSource extends MessageSource
{
const MO_FILE_EXT = '.mo';
const PO_FILE_EXT = '.po';

/**
* @var string
*/
public $basePath = '@app/messages';
/**
* @var string
*/
public $catalog = 'messages';
/**
* @var boolean
*/
public $useMoFile = true;
/**
* @var boolean
*/
public $useBigEndian = false;

protected function loadMessages($category, $language)
{
$messageFile = Yii::getAlias($this->basePath) . '/' . $language . '/' . $this->catalog;
if ($this->useMoFile) {
$messageFile .= static::MO_FILE_EXT;
} else {
$messageFile .= static::PO_FILE_EXT;
}

if (is_file($messageFile)) {
if ($this->useMoFile) {
$gettextFile = new GettextMoFile(array('useBigEndian' => $this->useBigEndian));
} else {
$gettextFile = new GettextPoFile();
}
$messages = $gettextFile->load($messageFile, $category);
if (!is_array($messages)) {
$messages = array();
}
return $messages;
} else {
Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__);
return array();
}
}
}
Loading