Skip to content

Commit

Permalink
Translate Adapter for multi-column csv (#890)
Browse files Browse the repository at this point in the history
  • Loading branch information
challet authored and sergeyklay committed Mar 7, 2019
1 parent 477413a commit f8e2be2
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 11 deletions.
126 changes: 126 additions & 0 deletions Library/Phalcon/Translate/Adapter/CsvMulti.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace Phalcon\Translate\Adapter;

use Phalcon\Translate\AdapterInterface;
use Phalcon\Translate\Adapter\Csv;
use Phalcon\Translate\Exception;

class CsvMulti extends Csv implements AdapterInterface, \ArrayAccess
{
/**
* @var array
*/
private $locales = array();

/**
* @var string
*/
private $locale = null;

/**
* @var string
*/
private $indexes = array();

/**
* Load translates from file
*
* @param string file
* @param int length
* @param string delimiter
* @param string enclosure
*/
// @codingStandardsIgnoreStart
// Method name "_load" should not be prefixed with an underscore to indicate visibility
// Still, it needs to extend the parent one
private function _load($file, $length, $delimiter, $enclosure)
{
// @codingStandardsIgnoreEnd
$fileHandler = fopen($file, "rb");

if (gettype($fileHandler) !== "resource") {
throw new Exception("Error opening translation file '" . $file . "'");
}

$line = 0;
$locales = array();
while ($data = fgetcsv($fileHandler, $length, $delimiter, $enclosure)) {
if ($line++ == 0) {
// first csv line
// register the horizontal locales sort order
// the first element (must be empty) is removed
foreach (array_slice($data, 1) as $pos => $locale) {
$this->locales[$pos] = $locale;
}
} else {
// the first row is the translation index (label)
$index = array_shift($data);
// store this index internally
$this->indexes[] = $index;
// the first element is removed as well, so the pos is according to the first line
foreach ($data as $pos => $translation) {
$this->_translate[$this->locales[$pos]][$index] = $translation;
}
}
}

fclose($fileHandler);
}

/**
* Sets locale information, according to one from the header row of the source csv
* Set it to false for enabling the "no translation mode"
* <code>
* // Set locale to Dutch
* $adapter->setLocale('nl_NL');
* </code>
*/
public function setLocale($locale)
{
if ($locale !== false && !array_key_exists($locale, $this->_translate)) {
throw new Exception("The locale '{$locale}' is not available in the data source.");
return false;
} else {
return $this->locale = $locale;
}
}

/**
* Returns the translation related to the given key and the previsouly set locale
*/
public function query($index, $placeholders = null)
{
if (!$this->exists($index)) {
throw new Exception("They key '{$index}' was not found.");
}

if ($this->locale === false) {
// "no translation mode"
$translation = $index;
} else {
$translation = $this->_translate[$this->locale][$index];
}

return $this->replacePlaceholders($translation, $placeholders);
}

/**
* Check whether is defined a translation key in the internal array
*/
public function exists($index)
{
if (is_null($this->locale)) {
throw new Exception('The locale must have been defined.');
}
return in_array($index, $this->getIndexes());
}

/**
* Returns all the translation keys
*/
public function getIndexes()
{
return $this->indexes;
}
}
21 changes: 21 additions & 0 deletions Library/Phalcon/Translate/Adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,24 @@ root {
[1]: http://docs.phalconphp.com/en/latest/api/Phalcon_DI.html
[2]: http://docs.phalconphp.com/en/latest/reference/volt.html
[3]: http://php.net/manual/en/book.intl.php

## MultiCsv

This adapter extends *Phalcon\Translate\Adapter\Csv* by allowing one csv file for several languages.

* CSV example (blank spaces added for readability):
```csv
#ignored; en_US; fr_FR; es_ES
label_street; street; rue; calle
label_car; car; voiture; coche
label_home; home; maison; casa
```
* PHP example :
```php
// the constructor is inherited from Phalcon\Translate\Adapter\Csv
$titles_translater = new Phalcon\Translate\Adapter\MultiCsv([
'content' => "{$config->langDir}/titles.csv"
]);
$titles_translater->setLocale('es_ES');
echo $titles_translater->query('label_home'); // string 'casa'
```
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"phpdocumentor/reflection-docblock": "2.0.4",
"phpunit/phpunit": "^4.8",
"squizlabs/php_codesniffer": "^2.9",
"codeception/codeception": "2.3.6",
"codeception/codeception": "^2.5",
"codeception/mockery-module": "0.2.2",
"codeception/aerospike-module": "^1.0",
"codeception/specify": "^0.4",
Expand Down
4 changes: 4 additions & 0 deletions tests/_data/assets/translation/csv/names.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
;en_US;fr_FR;es_ES
label_street;street;rue;calle
label_car;car;voiture;coche
label_home;home;maison;casa
31 changes: 21 additions & 10 deletions tests/unit/Session/Adapter/DatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,28 @@ function ($data, $expected) {
},
[
'examples' => [
[$_SESSION['customer_id'], 'somekey']
[$this->session->get('customer_id'), 'somekey']
]
]
);


session_start();
$this->session->write($sessionID, session_encode());
$this->tester->seeInDatabase(ModelSession::$table, ['session_id' => $sessionID]);
$this->tester->seeInDatabase(ModelSession::$table, ['data' => 'customer_id|s:7:"somekey";']);
$this->session->remove('customer_id');


$sessionData = $this->session->read($sessionID);
session_start();
session_decode($sessionData);

$this->specify(
"Method read() hasn't worked",
function ($data, $expected) {
expect($data)->equals($expected);
},
[
'examples' => [
[$_SESSION['customer_id'], 'somekey']
[$this->session->get('customer_id'), 'somekey']
]
]
);
Expand All @@ -123,16 +121,29 @@ function ($data, $expected) {
$sessionData = $this->session->read($sessionID);
session_start();
session_decode($sessionData);


$session = $this->session;
$this->specify(
"Method read() hasn't worked",
function ($data, $expected) use ($session) {
expect($data)->equals($expected);
},
[
'examples' => [
[$this->session->get('customer_id'), 'somekey']
]
]
);

$this->specify(
"Method update() hasn't worked",
function ($data, $expected) {
expect($data)->equals($expected);
},
[
'examples' => [
[$_SESSION['customer_id'], 'somekey'],
[$_SESSION['customer_id2'], 'somekey2']
[$this->session->get('customer_id'), 'somekey'],
[$this->session->get('customer_id2'), 'somekey2']
]
]
);
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/Translate/Adapter/CsvMulti/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Phalcon\Test\Unit\Translate\Adapter\CsvMulti;

use Phalcon\Translate\Adapter\CsvMulti;

/**
* Class GetIndexesCest
*/
abstract class Base
{

protected $adapter;

public function _before()
{
$content = dirname(__FILE__) . '/../../../../_data/assets/translation/csv/names.csv';
$params = ['content' => $content];
$this->adapter = new CsvMulti($params);
}

public function _after()
{
$this->adapter = null;
}

}
43 changes: 43 additions & 0 deletions tests/unit/Translate/Adapter/CsvMulti/ExistsCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Phalcon\Test\Unit\Translate\Adapter\CsvMulti;

// TODO : autoload instead of require_once ? see https://stackoverflow.com/questions/35386276/codeception-cest-inheritance
require_once 'Base.php';
use Phalcon\Test\Unit\Translate\Adapter\CsvMulti\Base;

use Phalcon\Translate\Exception;
use UnitTester;

/**
* Class ExistsCest
*/
class ExistsCest extends Base
{
public function translateAdapterExistsNoLocaleSet(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - exists cannot work without a locale having been set');
$I->expectThrowable(
new Exception('The locale must have been defined.'),
function () {
$this->adapter->exists('label_street');
}
);
}

public function translateAdapterExistsItDoesnt(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - exists returns false');
$this->adapter->setLocale('en_US');
$I->assertFalse($this->adapter->exists('label_cat'));
}

public function translateAdapterExistsItDoes(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - exists returns true');
$this->adapter->setLocale('en_US');
$I->assertTrue($this->adapter->exists('label_street'));
$I->assertTrue($this->adapter->exists('label_car'));
$I->assertTrue($this->adapter->exists('label_home'));
}
}
22 changes: 22 additions & 0 deletions tests/unit/Translate/Adapter/CsvMulti/GetIndexCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Phalcon\Test\Unit\Translate\Adapter\CsvMulti;

// TODO : autoload instead of require_once ? see https://stackoverflow.com/questions/35386276/codeception-cest-inheritance
require_once 'Base.php';
use Phalcon\Test\Unit\Translate\Adapter\CsvMulti\Base;

use UnitTester;

/**
* Class GetIndexesCest
*/
class GetIndexesCest extends Base
{
public function getIndexes(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - getIndexes returns the indexes');
$this->adapter->setLocale('en_US');
$I->assertEquals(['label_street', 'label_car', 'label_home'], $this->adapter->getIndexes());
}
}
54 changes: 54 additions & 0 deletions tests/unit/Translate/Adapter/CsvMulti/QueryCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Phalcon\Test\Unit\Translate\Adapter\CsvMulti;

// TODO : autoload instead of require_once ? see https://stackoverflow.com/questions/35386276/codeception-cest-inheritance
require_once 'Base.php';
use Phalcon\Test\Unit\Translate\Adapter\CsvMulti\Base;

use Phalcon\Translate\Exception;
use UnitTester;

/**
* Class QueryCest
*/
class QueryCest extends Base
{

public function queryNoTranslationMode(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - query returns the key when locale is false');
$this->adapter->setLocale(false);
$I->assertEquals('label_street', $this->adapter->query('label_street'));
$I->assertEquals('label_street', $this->adapter->query('label_street', 'placeholder_for_street'));
}

public function queryDoesTranslate(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - query returns the translated string matching the index');
$this->adapter->setLocale('en_US');
$I->assertEquals('street', $this->adapter->query('label_street'));
$I->assertEquals('car', $this->adapter->query('label_car'));
$I->assertEquals('home', $this->adapter->query('label_home'));
$this->adapter->setLocale('fr_FR');
$I->assertEquals('rue', $this->adapter->query('label_street'));
$I->assertEquals('voiture', $this->adapter->query('label_car'));
$I->assertEquals('maison', $this->adapter->query('label_home'));
$this->adapter->setLocale('es_ES');
$I->assertEquals('calle', $this->adapter->query('label_street'));
$I->assertEquals('coche', $this->adapter->query('label_car'));
$I->assertEquals('casa', $this->adapter->query('label_home'));
}

public function queryKeyDoesntExist(UnitTester $I)
{
$I->wantToTest('Translate\Adapter\CsvMulti - query raises an exception when the key doesn\'t match');
$I->expectThrowable(
new Exception("They key 'label_unexists' was not found."),
function () {
$this->adapter->setLocale('en_US');
$this->adapter->query('label_unexists');
}
);
}
}

0 comments on commit f8e2be2

Please sign in to comment.