diff --git a/README.md b/README.md index 4f7ddc04..6c687f2d 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ $rendererOptions = [ 'detailLevel' => 'line', // renderer language: eng, cht, chs, jpn, ... // or an array which has the same keys with a language file + // check the "Custom Language" section in the readme for more advanced usage 'language' => 'eng', // show line numbers in HTML renderers 'lineNumbers' => true, @@ -598,6 +599,26 @@ If you don't need those detailed diff, consider using the `JsonText` renderer. +## Custom Language + +### Override an Existing Language + +If you just want to override some translations of an existing language... + +```php +$rendererOptions = [ + 'language' => [ + // use English as the base language + 'eng', + // your custom overrides + [ + // use "Diff" as the new value of the "differences" key + 'differences' => 'Diff', + ], + // maybe more overrides if you somehow need them... + ], +] +``` ## Acknowledgment diff --git a/example/demo_base.php b/example/demo_base.php index a435d197..a61c1693 100644 --- a/example/demo_base.php +++ b/example/demo_base.php @@ -30,6 +30,7 @@ 'detailLevel' => 'line', // renderer language: eng, cht, chs, jpn, ... // or an array which has the same keys with a language file + // check the "Custom Language" section in the readme for more advanced usage 'language' => 'eng', // show line numbers in HTML renderers 'lineNumbers' => true, diff --git a/src/Renderer/AbstractRenderer.php b/src/Renderer/AbstractRenderer.php index d2906091..d9806a34 100644 --- a/src/Renderer/AbstractRenderer.php +++ b/src/Renderer/AbstractRenderer.php @@ -59,6 +59,7 @@ abstract class AbstractRenderer implements RendererInterface 'detailLevel' => 'line', // renderer language: eng, cht, chs, jpn, ... // or an array which has the same keys with a language file + // check the "Custom Language" section in the readme for more advanced usage 'language' => 'eng', // show line numbers in HTML renderers 'lineNumbers' => true, diff --git a/src/Utility/Arr.php b/src/Utility/Arr.php index da3c734e..0ebcb7ef 100644 --- a/src/Utility/Arr.php +++ b/src/Utility/Arr.php @@ -44,4 +44,22 @@ public static function getPartialByIndex(array $array, int $start = 0, ?int $end // make the length non-negative return \array_slice($array, $start, max(0, $end - $start)); } + + /** + * Determines whether the array is associative. + * + * @param array $arr the array + * + * @return bool `true` if the array is associative, `false` otherwise + */ + public static function isAssociative($arr): bool + { + foreach ($arr as $key => $value) { + if (\is_string($key)) { + return true; + } + } + + return false; + } } diff --git a/src/Utility/Language.php b/src/Utility/Language.php index d9640afe..b429c5fa 100644 --- a/src/Utility/Language.php +++ b/src/Utility/Language.php @@ -19,55 +19,52 @@ final class Language /** * The constructor. * - * @param string|string[] $target the language string or translations dict + * @param array|string|string[] $target the language ID or translations dict */ public function __construct($target = 'eng') { - $this->setLanguageOrTranslations($target); + $this->load($target); } /** - * Set up this class. + * Gets the language. * - * @param string|string[] $target the language string or translations array - * - * @throws \InvalidArgumentException + * @return string the language */ - public function setLanguageOrTranslations($target): self + public function getLanguage(): string { - if (\is_string($target)) { - $this->setUpWithLanguage($target); - - return $this; - } - - if (\is_array($target)) { - $this->setUpWithTranslations($target); - - return $this; - } + return $this->language; + } - throw new \InvalidArgumentException('$target must be the type of string|string[]'); + /** + * Gets the translations. + * + * @return array the translations + */ + public function getTranslations(): array + { + return $this->translations; } /** - * Get the language. + * Loads the target language. * - * @return string the language + * @param array|string|string[] $target the language ID or translations dict */ - public function getLanguage(): string + public function load($target): void { - return $this->language; + $this->translations = $this->resolve($target); + $this->language = \is_string($target) ? $target : '_custom_'; } /** - * Get the translations. + * Translates the text. * - * @return array the translations + * @param string $text the text */ - public function getTranslations(): array + public function translate(string $text): string { - return $this->translations; + return $this->translations[$text] ?? "![{$text}]"; } /** @@ -81,7 +78,7 @@ public function getTranslations(): array * * @return string[] */ - public static function getTranslationsByLanguage(string $language): array + private static function getTranslationsByLanguage(string $language): array { $filePath = __DIR__ . "/../languages/{$language}.json"; $file = new \SplFileObject($filePath, 'r'); @@ -97,39 +94,34 @@ public static function getTranslationsByLanguage(string $language): array } /** - * Translation the text. + * Resolves the target language. * - * @param string $text the text - */ - public function translate(string $text): string - { - return $this->translations[$text] ?? "![{$text}]"; - } - - /** - * Set up this class by language name. + * @param array|string|string[] $target the language ID or translations array * - * @param string $language the language name - */ - private function setUpWithLanguage(string $language): self - { - return $this->setUpWithTranslations( - self::getTranslationsByLanguage($language), - $language, - ); - } - - /** - * Set up this class by translations. + * @throws \InvalidArgumentException * - * @param string[] $translations the translations dict - * @param string $language the language name + * @return string[] the resolved translations */ - private function setUpWithTranslations(array $translations, string $language = '_custom_'): self + private function resolve($target): array { - $this->language = $language; - $this->translations = array_map('strval', $translations); + if (\is_string($target)) { + return self::getTranslationsByLanguage($target); + } + + if (\is_array($target)) { + // $target is an associative array + if (Arr::isAssociative($target)) { + return $target; + } + + // $target is a list of "key-value pairs or language ID" + return array_reduce( + $target, + fn ($carry, $translation) => array_merge($carry, $this->resolve($translation)), + [], + ); + } - return $this; + throw new \InvalidArgumentException('$target is not in valid form'); } } diff --git a/tests/Utility/LanguageTest.php b/tests/Utility/LanguageTest.php index 1c9e7043..472be43a 100644 --- a/tests/Utility/LanguageTest.php +++ b/tests/Utility/LanguageTest.php @@ -24,46 +24,34 @@ final class LanguageTest extends TestCase */ protected function setUp(): void { - $this->languageObj = new Language('eng'); + $this->languageObj = new Language(); } /** - * Test the Language::setLanguageOrTranslations. + * Test the Language::load. * - * @covers \Jfcherng\Diff\Utility\Language::setLanguageOrTranslations + * @covers \Jfcherng\Diff\Utility\Language::load */ - public function testSetLanguageOrTranslations(): void + public function testLoad(): void { - $this->languageObj->setLanguageOrTranslations('eng'); - static::assertArrayHasKey( - 'differences', - $this->languageObj->getTranslations(), - ); + $this->languageObj->load('eng'); + static::assertArrayHasKey('differences', $this->languageObj->getTranslations()); - $this->languageObj->setLanguageOrTranslations(['hahaha' => '哈哈哈']); - static::assertArrayHasKey( - 'hahaha', - $this->languageObj->getTranslations(), - ); + $this->languageObj->load(['hahaha' => '哈哈哈']); + static::assertArrayHasKey('hahaha', $this->languageObj->getTranslations()); - $this->expectException(\InvalidArgumentException::class); - $this->languageObj->setLanguageOrTranslations(5); - } + $this->languageObj->load([ + 'eng', + ['hahaha_1' => '哈哈哈_1', 'hahaha_2' => '哈哈哈_2'], + ['hahaha_1' => '哈哈哈_999'], + ]); + $translations = $this->languageObj->getTranslations(); + static::assertSame('Differences', $translations['differences']); + static::assertSame('哈哈哈_999', $translations['hahaha_1']); + static::assertSame('哈哈哈_2', $translations['hahaha_2']); - /** - * Test the Language::getTranslationsByLanguage. - * - * @covers \Jfcherng\Diff\Utility\Language::getTranslationsByLanguage - */ - public function testGetTranslationsByLanguage(): void - { - static::assertArrayHasKey( - 'differences', - $this->languageObj->getTranslationsByLanguage('eng'), - ); - - $this->expectException(\RuntimeException::class); - $this->languageObj->getTranslationsByLanguage('a_non_existing_language'); + $this->expectException(\InvalidArgumentException::class); + $this->languageObj->load(5); } /** @@ -80,7 +68,7 @@ public function testTranslate(): void static::assertStringMatchesFormat( '![%s]', - $this->languageObj->translate('a_non_existing_translation'), + $this->languageObj->translate('a_non_existing_key'), ); } }