From 026126be768894c128bdb922079eba91c62166bf Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Sat, 9 May 2020 22:55:07 -0400 Subject: [PATCH] - First working version of Tell - JsonReader and PhpReader added --- src/I18n.php | 175 ++++++++++++++++++++------------- src/Iterate.php | 54 ++++++---- src/MissingKeyInterface.php | 16 +++ src/PrintMissingKey.php | 12 +++ src/Reader/JsonReader.php | 52 ++++++++++ src/Reader/PhpReader.php | 44 +++++++++ src/Reader/ReaderInterface.php | 12 +++ 7 files changed, 277 insertions(+), 88 deletions(-) create mode 100644 src/MissingKeyInterface.php create mode 100644 src/PrintMissingKey.php create mode 100644 src/Reader/JsonReader.php create mode 100644 src/Reader/PhpReader.php create mode 100644 src/Reader/ReaderInterface.php diff --git a/src/I18n.php b/src/I18n.php index ebf191a..eea1f82 100644 --- a/src/I18n.php +++ b/src/I18n.php @@ -2,105 +2,144 @@ namespace Tell; -class I18n +class I18n implements MissingKeyInterface { const DELIMITER = '.'; - protected bool $updatedData = false; + protected string $locale = "en_US"; - protected array $data = []; + protected string $language = ""; - protected array $cache = []; - - protected array $engine = []; - - protected string $currentLanguage = ""; + protected array $data = []; protected array $fileStack = []; - protected function __construct(bool $enableCache = false) { + protected string $currentLanguage = ""; -# $this->persistent = new Persistent(static::class); - if ( $enableCache ) { -# $data = $this->persistent->load(); -# $this->data = $data['data']; -# $this->fileStack = $data['fileStack']; - } + protected Reader\ReaderInterface $dataReader; + + protected ? Reader\ReaderInterface $cacheReader = null; + + protected ? MissingKeyInterface $missingKey = null; + + public function __construct( + Reader\ReaderInterface $dataReader, + ? Reader\ReaderInterface $cacheReader = null, + ? MissingKeyInterface $missingKey = null + ) + { + $this->dataReader = $dataReader; + $this->cacheReader = $cacheReader; + $this->missingKey = $missingKey; } - public function __destruct() { -# $this->update_data() && $this->persistent->save([ -# 'fileStack' => $this->fileStack, -# 'data' => $this->data -# ]); - } + public function initialize(bool $useCache) : bool + { + $loaded = $useCache && $this->fromReader($this->cacheReader) ?: $this->fromReader($this->dataReader); - public function updateData() { - return $this->updatedData; - } - - public static function get(?string $key = null) { - if ( $key === null ) { - return $this->data; + if ( ! $useCache ) { + $this->cacheReader->save($this->locale, $this->data); } - return isset( $this->cache[$key] ) ? $this->cache[$key] : $this->cache[$key] = Iterate::arrayGet($this->data, ( $this->currentLanguage ? $this->currentLanguage."." : "").$key, static::DELIMITER); + return $loaded; } - public static function set(string $key, $value) { + public function fromKey(string $key, array $variables) : ?string + { + if ( null === ( $string = $this->get($key) ) ) { + $string = $this->missingKey ? $this->missingKey->get($key) : null; + } + + if (is_array($string)) { + $string = json_encode($string); + } + + return $string && $variables ? $this->placeVariables($string, $variables) : $string; + } + + public function get(string $key) + { + # Testing full locale key first + if ( null === ( $string = Iterate::arrayGet($this->data, "{$this->locale}.$key", static::DELIMITER)) ) { + # Fallback on language only + $string = Iterate::arrayGet($this->data, "{$this->language}.$key", static::DELIMITER); + } + + return $string; + } + + public function set(string $key, $value) + { $cache = explode(static::DELIMITER, $key); - while ( !empty($cache) ) { + while ( ! empty($cache) ) { $imp = implode(static::DELIMITER, $cache); array_pop($cache); unset( $this->cache[$imp] ); } - $this->cache[$key] = $value ; + $this->cache[$key] = $value; return Iterate::arraySet($this->data, $key, $value, static::DELIMITER); } - public function addEngine($engine) { - $this->engine[] = $engine; - } + public function locale(?string $set = null) + { + $locale = $set === null ? ( $this->locale ?? null ) : $this->locale = strtolower(explode(".", $this->locale)[0]); - public function load(string $path, bool $filenameAsKey = false) { - if ( !in_array($path, $this->fileStack) ) { - $this->updatedData = true; - $this->fileStack[] = $path; - - foreach(Iterate::files($path) as $item) { - foreach($this->engine as $engine) { - if ( $engine->accept($item )) { - - if ( $array = $engine->translate( $item ) ){ - if ($filenameAsKey ) { - $base = basename($item); - - $key = substr($base,0, strrpos($base, ".")); - $keysplit = explode(".", $key); - $key = array_pop($keysplit); - - $this->mergeData([ $keysplit ? "{".implode(".", array_merge([ $key ], $keysplit))."}" : $key => $array ]); - } - else { - $this->mergeData($array); - } - } - - continue 2; - } - } - } + if ( $set !== null ) { + $this->language = explode('_', $locale)[0]; } + + return $locale; } - public static function currentLanguage($set = null) { - return $set === null ? $this->currentLanguage : $this->currentLanguage = $set; - } - - protected function mergeData($data) { + protected function mergeData($data) : void + { $this->data = array_replace_recursive($this->data, Iterate::splitKeys($data)); } + + protected function fromReader(Reader\ReaderInterface $reader) : bool + { + foreach($reader->pathList as $path) { + foreach(Iterate::files($path) as $item) { + if ( $reader->accept($item->getExtension() )) { + if ( null !== ( $data = $reader->load( $item ) )) { + + if ( $reader->filenameAsKey ) { + # Adding folder to full key + $filekey = ltrim(substr($item->getPathname(), strlen($path)), DIRECTORY_SEPARATOR); + + # Removing extension + $filekey = substr($filekey, 0, strrpos($filekey, '.')); + + # Replacing directory separator with dots + $filekey = str_replace(DIRECTORY_SEPARATOR, '.', $filekey); + + $this->mergeData([ '{' . $filekey . '}' => $data ]); + } + else { + $this->mergeData($data); + } + } + } + } + } + + return false; + } + + protected function placeVariables(string $string, array $parameters) { + if ( preg_match_all('~{$(.*?)}~si', $string, $matches, PREG_SET_ORDER) ) { + $search = []; + + foreach($matches as $item) { + $search[ $item[0] ] = Iterate::arrayGet($parameters, $item[1]); + } + + return str_replace(array_keys($search), array_values($search), $string); + } + + return $string; + } } diff --git a/src/Iterate.php b/src/Iterate.php index 7dc5be9..15a40b4 100644 --- a/src/Iterate.php +++ b/src/Iterate.php @@ -3,9 +3,7 @@ namespace Tell; use RecursiveDirectoryIterator, - RecursiveIteratorIterator, - RecursiveRegexIterator, - RegexIterator; + RecursiveIteratorIterator; class Iterate { @@ -15,7 +13,7 @@ class Iterate * @param string $path - path like 'person.name.first' * @param string $value - value to set */ - public static function arraySet(array &$array, string $path, string $value = '', string $delimiter = '.') { + public static function arraySet(?array &$array, string $path, $value, string $delimiter = '.') { $pathArr = explode($delimiter, $path); // Go to next node @@ -28,6 +26,29 @@ class Iterate } } + public static function arrayKeysSet($key_list, &$array, $value, $append = false) { + $current_key = array_shift($key_list); + + if ($key_list) { + return static::arrayKeysSet($key_list, $array[$current_key], $value, $append); + } + else { + if ( $append && isset($array[$current_key]) ) { + if (is_array($array[$current_key])) { + return $array[$current_key] = array_merge_recursive($value, $array[$current_key]); #: array_replace_recursive($value, $array[$current_key]); + } + else { + return $array[$current_key] = $array[$current_key] . $value; + } + } + else { + return $array[$current_key] = $value; + } + } + + return false; + } + public static function arrayGet(array $array, string $path, string $delimiter = '.') { $pathArr = explode($delimiter, $path); @@ -65,8 +86,7 @@ class Iterate $tmp = []; $keylist = explode($delimiter, $key); $finalKey = array_shift($keylist); - # Arrayobj::iterate_set($keylist, $tmp, $value); - static::arraySet($tmp, $key, $value); + static::arrayKeysSet($keylist, $tmp, $value); $swap[$finalKey] = static::splitKeys($tmp, $delimiter); unset($array[isset($oldKey) ? $oldKey : $key]); @@ -90,28 +110,22 @@ class Iterate return $array; } - public static function files(string $path, string $fileExtension = "") { - $retval = []; - + public static function files(string $path, string $fileExtension = "") : \Generator + { if ( \file_exists($path) ) { $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD); - if ($fileExtension) { - $iterator = new RegexIterator($iterator, '/^.+\.'.$fileExtension.'$/i', RecursiveRegexIterator::GET_MATCH); - } - foreach ($iterator as $file) { - if ($fileExtension) { - $retval[] = $file[0]; - } - else { - if ( $file->isFile() || $file->isDir() ) { - $retval[] = $file->getRealPath(); + if ( $file->isFile() || $file->isDir() ) { + if ($fileExtension && ( $file->getExtension() === $fileExtension )) { + yield $file; + } + else { + yield $file; } } } } - return $retval; } } diff --git a/src/MissingKeyInterface.php b/src/MissingKeyInterface.php new file mode 100644 index 0000000..ea895d2 --- /dev/null +++ b/src/MissingKeyInterface.php @@ -0,0 +1,16 @@ +pathList = $pathList; + $this->filenameAsKey = $filenameAsKey; + $this->saveFlag = $saveFlag; + } + + public function accept(string $extension) : bool + { + return in_array($extension, $this->accept); + } + + public function load(string $filepath) : ?array + { + if ( ! file_exists($filepath) ) { + throw new \Exception("Given file path is not existing or is inaccessible."); + } + + if ( null === ( $content = json_decode(file_get_contents($filepath), true) ) ) { + throw new \Exception("An error occured trying to parse configuration file: given json file [ $filepath ] seems to be invalid"); + } + + return $content; + } + + public function save(string $filepath, array $content) : bool + { + $path = $this->pathList[0]; + + if ( !file_exists($path) ) { + mkdir($path, 0750, true); + } + + return ( file_put_contents($path . DIRECTORY_SEPARATOR . "$filepath." . $this->accept[0], json_encode($content, $this->saveFlag)) !== false ); + } +} diff --git a/src/Reader/PhpReader.php b/src/Reader/PhpReader.php new file mode 100644 index 0000000..5ed5f83 --- /dev/null +++ b/src/Reader/PhpReader.php @@ -0,0 +1,44 @@ +pathList = $pathList; + $this->filenameAsKey = $filenameAsKey; + } + + public function accept(string $extension) : bool + { + return in_array($extension, $this->accept); + } + + public function load(string $filepath) : ?array + { + if ( ! file_exists($filepath) ) { + throw new \Exception("Given file path is not existing or is inaccessible."); + } + + if ( null === ( $content = include($filepath) ?? false ) ) { + throw new \Exception("An error occured trying to parse configuration file: given php file [ $filepath ] seems to be invalid"); + } + + return $content; + } + + public function save(string $filepath, array $content) : bool + { + $path = $this->pathList[0]; + return (bool) file_put_contents($path . DIRECTORY_SEPARATOR . "$filepath." . $this->accept[0], "return " . var_export($content, true) . ";"); + } +} diff --git a/src/Reader/ReaderInterface.php b/src/Reader/ReaderInterface.php new file mode 100644 index 0000000..c3594fe --- /dev/null +++ b/src/Reader/ReaderInterface.php @@ -0,0 +1,12 @@ +