Skip to content

Commit bfb89b6

Browse files
committed
feat: provide a way to override an existing language
Signed-off-by: Jack Cherng <[email protected]>
1 parent 775a043 commit bfb89b6

File tree

6 files changed

+109
-88
lines changed

6 files changed

+109
-88
lines changed

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ $rendererOptions = [
8888
'detailLevel' => 'line',
8989
// renderer language: eng, cht, chs, jpn, ...
9090
// or an array which has the same keys with a language file
91+
// check the "Custom Language" section in the readme for more advanced usage
9192
'language' => 'eng',
9293
// show line numbers in HTML renderers
9394
'lineNumbers' => true,
@@ -598,6 +599,26 @@ If you don't need those detailed diff, consider using the `JsonText` renderer.
598599

599600
</details>
600601

602+
## Custom Language
603+
604+
### Override an Existing Language
605+
606+
If you just want to override some translations of an existing language...
607+
608+
```php
609+
$rendererOptions = [
610+
'language' => [
611+
// use English as the base language
612+
'eng',
613+
// your custom overrides
614+
[
615+
// use "Diff" as the new value of the "differences" key
616+
'differences' => 'Diff',
617+
],
618+
// maybe more overrides if you somehow need them...
619+
],
620+
]
621+
```
601622

602623
## Acknowledgment
603624

example/demo_base.php

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
'detailLevel' => 'line',
3131
// renderer language: eng, cht, chs, jpn, ...
3232
// or an array which has the same keys with a language file
33+
// check the "Custom Language" section in the readme for more advanced usage
3334
'language' => 'eng',
3435
// show line numbers in HTML renderers
3536
'lineNumbers' => true,

src/Renderer/AbstractRenderer.php

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ abstract class AbstractRenderer implements RendererInterface
5959
'detailLevel' => 'line',
6060
// renderer language: eng, cht, chs, jpn, ...
6161
// or an array which has the same keys with a language file
62+
// check the "Custom Language" section in the readme for more advanced usage
6263
'language' => 'eng',
6364
// show line numbers in HTML renderers
6465
'lineNumbers' => true,

src/Utility/Arr.php

+18
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,22 @@ public static function getPartialByIndex(array $array, int $start = 0, ?int $end
4444
// make the length non-negative
4545
return \array_slice($array, $start, max(0, $end - $start));
4646
}
47+
48+
/**
49+
* Determines whether the array is associative.
50+
*
51+
* @param array $arr the array
52+
*
53+
* @return bool `true` if the array is associative, `false` otherwise
54+
*/
55+
public static function isAssociative($arr): bool
56+
{
57+
foreach ($arr as $key => $value) {
58+
if (\is_string($key)) {
59+
return true;
60+
}
61+
}
62+
63+
return false;
64+
}
4765
}

src/Utility/Language.php

+48-56
Original file line numberDiff line numberDiff line change
@@ -19,55 +19,52 @@ final class Language
1919
/**
2020
* The constructor.
2121
*
22-
* @param string|string[] $target the language string or translations dict
22+
* @param array<int,string|string[]>|string|string[] $target the language ID or translations dict
2323
*/
2424
public function __construct($target = 'eng')
2525
{
26-
$this->setLanguageOrTranslations($target);
26+
$this->load($target);
2727
}
2828

2929
/**
30-
* Set up this class.
30+
* Gets the language.
3131
*
32-
* @param string|string[] $target the language string or translations array
33-
*
34-
* @throws \InvalidArgumentException
32+
* @return string the language
3533
*/
36-
public function setLanguageOrTranslations($target): self
34+
public function getLanguage(): string
3735
{
38-
if (\is_string($target)) {
39-
$this->setUpWithLanguage($target);
40-
41-
return $this;
42-
}
43-
44-
if (\is_array($target)) {
45-
$this->setUpWithTranslations($target);
46-
47-
return $this;
48-
}
36+
return $this->language;
37+
}
4938

50-
throw new \InvalidArgumentException('$target must be the type of string|string[]');
39+
/**
40+
* Gets the translations.
41+
*
42+
* @return array the translations
43+
*/
44+
public function getTranslations(): array
45+
{
46+
return $this->translations;
5147
}
5248

5349
/**
54-
* Get the language.
50+
* Loads the target language.
5551
*
56-
* @return string the language
52+
* @param array<int,string|string[]>|string|string[] $target the language ID or translations dict
5753
*/
58-
public function getLanguage(): string
54+
public function load($target): void
5955
{
60-
return $this->language;
56+
$this->translations = $this->resolve($target);
57+
$this->language = \is_string($target) ? $target : '_custom_';
6158
}
6259

6360
/**
64-
* Get the translations.
61+
* Translates the text.
6562
*
66-
* @return array the translations
63+
* @param string $text the text
6764
*/
68-
public function getTranslations(): array
65+
public function translate(string $text): string
6966
{
70-
return $this->translations;
67+
return $this->translations[$text] ?? "![{$text}]";
7168
}
7269

7370
/**
@@ -81,7 +78,7 @@ public function getTranslations(): array
8178
*
8279
* @return string[]
8380
*/
84-
public static function getTranslationsByLanguage(string $language): array
81+
private static function getTranslationsByLanguage(string $language): array
8582
{
8683
$filePath = __DIR__ . "/../languages/{$language}.json";
8784
$file = new \SplFileObject($filePath, 'r');
@@ -97,39 +94,34 @@ public static function getTranslationsByLanguage(string $language): array
9794
}
9895

9996
/**
100-
* Translation the text.
97+
* Resolves the target language.
10198
*
102-
* @param string $text the text
103-
*/
104-
public function translate(string $text): string
105-
{
106-
return $this->translations[$text] ?? "![{$text}]";
107-
}
108-
109-
/**
110-
* Set up this class by language name.
99+
* @param array<int,string|string[]>|string|string[] $target the language ID or translations array
111100
*
112-
* @param string $language the language name
113-
*/
114-
private function setUpWithLanguage(string $language): self
115-
{
116-
return $this->setUpWithTranslations(
117-
self::getTranslationsByLanguage($language),
118-
$language,
119-
);
120-
}
121-
122-
/**
123-
* Set up this class by translations.
101+
* @throws \InvalidArgumentException
124102
*
125-
* @param string[] $translations the translations dict
126-
* @param string $language the language name
103+
* @return string[] the resolved translations
127104
*/
128-
private function setUpWithTranslations(array $translations, string $language = '_custom_'): self
105+
private function resolve($target): array
129106
{
130-
$this->language = $language;
131-
$this->translations = array_map('strval', $translations);
107+
if (\is_string($target)) {
108+
return self::getTranslationsByLanguage($target);
109+
}
110+
111+
if (\is_array($target)) {
112+
// $target is an associative array
113+
if (Arr::isAssociative($target)) {
114+
return $target;
115+
}
116+
117+
// $target is a list of "key-value pairs or language ID"
118+
return array_reduce(
119+
$target,
120+
fn ($carry, $translation) => array_merge($carry, $this->resolve($translation)),
121+
[],
122+
);
123+
}
132124

133-
return $this;
125+
throw new \InvalidArgumentException('$target is not in valid form');
134126
}
135127
}

tests/Utility/LanguageTest.php

+20-32
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,34 @@ final class LanguageTest extends TestCase
2424
*/
2525
protected function setUp(): void
2626
{
27-
$this->languageObj = new Language('eng');
27+
$this->languageObj = new Language();
2828
}
2929

3030
/**
31-
* Test the Language::setLanguageOrTranslations.
31+
* Test the Language::load.
3232
*
33-
* @covers \Jfcherng\Diff\Utility\Language::setLanguageOrTranslations
33+
* @covers \Jfcherng\Diff\Utility\Language::load
3434
*/
35-
public function testSetLanguageOrTranslations(): void
35+
public function testLoad(): void
3636
{
37-
$this->languageObj->setLanguageOrTranslations('eng');
38-
static::assertArrayHasKey(
39-
'differences',
40-
$this->languageObj->getTranslations(),
41-
);
37+
$this->languageObj->load('eng');
38+
static::assertArrayHasKey('differences', $this->languageObj->getTranslations());
4239

43-
$this->languageObj->setLanguageOrTranslations(['hahaha' => '哈哈哈']);
44-
static::assertArrayHasKey(
45-
'hahaha',
46-
$this->languageObj->getTranslations(),
47-
);
40+
$this->languageObj->load(['hahaha' => '哈哈哈']);
41+
static::assertArrayHasKey('hahaha', $this->languageObj->getTranslations());
4842

49-
$this->expectException(\InvalidArgumentException::class);
50-
$this->languageObj->setLanguageOrTranslations(5);
51-
}
43+
$this->languageObj->load([
44+
'eng',
45+
['hahaha_1' => '哈哈哈_1', 'hahaha_2' => '哈哈哈_2'],
46+
['hahaha_1' => '哈哈哈_999'],
47+
]);
48+
$translations = $this->languageObj->getTranslations();
49+
static::assertSame('Differences', $translations['differences']);
50+
static::assertSame('哈哈哈_999', $translations['hahaha_1']);
51+
static::assertSame('哈哈哈_2', $translations['hahaha_2']);
5252

53-
/**
54-
* Test the Language::getTranslationsByLanguage.
55-
*
56-
* @covers \Jfcherng\Diff\Utility\Language::getTranslationsByLanguage
57-
*/
58-
public function testGetTranslationsByLanguage(): void
59-
{
60-
static::assertArrayHasKey(
61-
'differences',
62-
$this->languageObj->getTranslationsByLanguage('eng'),
63-
);
64-
65-
$this->expectException(\RuntimeException::class);
66-
$this->languageObj->getTranslationsByLanguage('a_non_existing_language');
53+
$this->expectException(\InvalidArgumentException::class);
54+
$this->languageObj->load(5);
6755
}
6856

6957
/**
@@ -80,7 +68,7 @@ public function testTranslate(): void
8068

8169
static::assertStringMatchesFormat(
8270
'![%s]',
83-
$this->languageObj->translate('a_non_existing_translation'),
71+
$this->languageObj->translate('a_non_existing_key'),
8472
);
8573
}
8674
}

0 commit comments

Comments
 (0)