Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
57 / 57 |
champlates\Dictionary | |
100.00% |
1 / 1 |
|
100.00% |
7 / 7 |
25 | |
100.00% |
57 / 57 |
__construct | |
100.00% |
1 / 1 |
3 | |
100.00% |
9 / 9 |
|||
crawlForTranslations | |
100.00% |
1 / 1 |
7 | |
100.00% |
15 / 15 |
|||
determineLanguages | |
100.00% |
1 / 1 |
3 | |
100.00% |
9 / 9 |
|||
readTranslations | |
100.00% |
1 / 1 |
5 | |
100.00% |
11 / 11 |
|||
isJsonFile | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
get | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
translate | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
<?php | |
/** | |
* Created by PhpStorm. | |
* User: hermann | |
* Date: 7/14/17 | |
* Time: 3:00 PM | |
*/ | |
namespace champlates; | |
use InvalidArgumentException; | |
/** | |
* Class Dictionary | |
* Handles translation of strings. The translations are provided by external | |
* JSON files in the format: | |
* | |
* { | |
* "KEY": "VALUE", | |
* "KEY2": "VALUE2", | |
* ... | |
* } | |
* | |
* The JSON files must end in .json and start with the language identifier | |
* followed by a hyphen. Each file can only contain data for a single language | |
* | |
* @package champlates | |
*/ | |
class Dictionary { | |
/** | |
* Dictionary constructor. | |
* Automatically constructs the translations from the provided translation | |
* file locations. | |
* @param string $translationFiles: The source file/directory for the | |
* translations | |
* @param string $translationIdentifier: Identifies sections in a string to | |
* translate when using the translate() method. Defaults | |
* to '@{KEY}'. If a custom identifier is set, it must | |
* contain 'KEY'. | |
* @throws InvalidArgumentException: If there is a problem with the input | |
*/ | |
function __construct(string $translationFiles, | |
string $translationIdentifier = "@{KEY}") { | |
$crawledTranslations = $this->crawlForTranslations($translationFiles); | |
$languages = $this->determineLanguages($crawledTranslations); | |
$this->translations = $this->readTranslations($languages); | |
if (count($this->translations) === 0) { | |
throw new InvalidArgumentException("No Translations Loaded"); | |
} elseif (strpos($translationIdentifier, "KEY") === false) { | |
throw new InvalidArgumentException("No KEY in identifier"); | |
} | |
$this->translationIdentifier = $translationIdentifier; | |
} | |
/** | |
* Recursively parses a directory/file for .json files and returns them | |
* in an array. | |
* @param $translationFiles: The file or directory to crawl | |
* @return array: The array of JSON files | |
* @throws InvalidArgumentException: If the argument passed was neither | |
* a file path or a directory path | |
*/ | |
public function crawlForTranslations($translationFiles) : array { | |
// Strip trailing slashes | |
$translationFiles = rtrim($translationFiles, "/"); | |
if (is_dir($translationFiles)) { | |
$crawled = []; | |
foreach (scandir($translationFiles) as $child) { | |
if ($child !== "." && $child !== "..") { | |
$childpath = $translationFiles . "/" . $child; | |
$crawled = array_merge( | |
$this->crawlForTranslations($childpath), | |
$crawled); | |
} | |
} | |
return $crawled; | |
} elseif (is_file($translationFiles)) { | |
if ($this->isJsonFile($translationFiles)) { | |
return [$translationFiles]; | |
} else { | |
return []; | |
} | |
} else { | |
throw new InvalidArgumentException("Not a directory or file"); | |
} | |
} | |
/** | |
* Determines which languages are translated by which files | |
* @param $translationFiles: The array of json files previously crawled | |
* by crawlForTranslations() | |
* @return array: An associative array mapping the language identifiers | |
* to corresponding JSON files. | |
*/ | |
public function determineLanguages($translationFiles) : array { | |
$languages = []; | |
foreach ($translationFiles as $translationFile) { | |
// Determine the language | |
$language = explode("/", $translationFile); | |
$language = $language[count($language) - 1]; | |
$language = explode("-", $language)[0]; | |
if (array_key_exists($language, $languages)) { | |
array_push($languages[$language], $translationFile); | |
} else { | |
$languages[$language] = [$translationFile]; | |
} | |
} | |
return $languages; | |
} | |
/** | |
* Reads the content of the JSON files and maps their data to the | |
* individual languages | |
* @param array $languages: The language array previously defined by | |
* determineLanguages() | |
* @return array: An associative array mapping the | |
* languages to the JSON data | |
* @throws InvalidArgumentException: If duplicate keys exist between the | |
* JSON files for a language | |
*/ | |
public function readTranslations(array $languages) : array { | |
$translations = []; | |
foreach ($languages as $language => $translationFiles) { | |
$translations[$language] = []; | |
foreach ($translationFiles as $translationFile) { | |
$content = file_get_contents($translationFile); | |
$json = json_decode($content, true); | |
foreach (array_keys($json) as $key) { | |
if (array_key_exists($key, $translations[$language])) { | |
throw new InvalidArgumentException("Duplicate key"); | |
} | |
} | |
$translations[$language] += $json; | |
} | |
} | |
return $translations; | |
} | |
/** | |
* Makes sure that a file is a JSON file | |
* @param string $file: The file to check | |
* @return bool: true of the file is a JSON file, false otherwise | |
*/ | |
public function isJsonFile(string $file) { | |
return substr($file, -5) === ".json"; | |
} | |
/** | |
* Translates a given key using the given language | |
* @param string $key: The key/token/string to translate | |
* @param string $language: The language | |
* @return string | |
*/ | |
public function get(string $key, string $language) { | |
if (!array_key_exists($language, $this->translations)) { | |
return "MISSINGLANGUAGE"; | |
} elseif (!array_key_exists($key, $this->translations[$language])) { | |
return "MISSINGTRANSLATION"; | |
} else { | |
return $this->translations[$language][$key]; | |
} | |
} | |
/** | |
* Translates a string using the translation data. Keys in the text | |
* are found using a simple replace operation. Which is why the | |
* keyIdentifier can be used to minimize false positives | |
* @param string $text: The text to translate | |
* @param string $language: The language to translate in | |
* @return string: The translated string | |
* @throws InvalidArgumentException: If the language specified does not | |
* exist. | |
*/ | |
public function translate(string $text, string $language) { | |
$translated = $text; | |
if (!array_key_exists($language, $this->translations)) { | |
throw new InvalidArgumentException("Language not installed"); | |
} else { | |
foreach ($this->translations[$language] as $key => $translation) { | |
$search = | |
str_replace("KEY", $key, $this->translationIdentifier); | |
$translated = str_replace($search, $translation, $translated); | |
} | |
return $translated; | |
} | |
} | |
} |