Skip to content

Commit 695d76c

Browse files
committed
Add translator
1 parent 47deff1 commit 695d76c

3 files changed

Lines changed: 204 additions & 0 deletions

File tree

src/Resources/translations/en.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
return [
4+
'os' => 'Operating System',
5+
'os_family' => 'OS Family',
6+
'hostname' => 'Hostname',
7+
'machine' => 'Architecture',
8+
'php_int_size' => 'PHP Integer Size',
9+
'php_architecture' => 'PHP Architecture',
10+
'sapi' => 'SAPI',
11+
'php_version' => 'PHP Version',
12+
'memory_limit' => 'Memory Limit',
13+
'raw_memory_limit' => 'Raw Memory Limit',
14+
'cpu_cores' => 'CPU Cores',
15+
'total_disk' => 'Disk Size',
16+
'free_disk' => 'Free Disk Space',
17+
'timestamp' => 'Timestamp',
18+
'origin_path' => 'Call Location Path',
19+
'origin_line' => 'Call Location Line',
20+
'memory_usage' => 'Memory Usage',
21+
'real_memory_usage' => 'Real Memory Usage',
22+
'peak_memory_usage' => 'Peak Memory Usage',
23+
'real_peak_memory_usage' => 'Real Peak Memory Usage',
24+
'cpu_system_time' => 'CPU System Time',
25+
'cpu_user_time' => 'CPU User Time',
26+
'cpu_total_time' => 'CPU Total Time',
27+
'cpu_time' => 'CPU Time',
28+
'execution_time' => 'Execution Time',
29+
'count' => 'Nb Iterations',
30+
'minimum' => 'Min Value',
31+
'maximum' => 'Max Value',
32+
'median' => 'Median Value',
33+
'sum' => 'Sum',
34+
'range' => 'Range',
35+
'average' => 'Average',
36+
'variance' => 'Variance',
37+
'std_dev' => 'Std Dev',
38+
'coef_var' => 'Coef Var',
39+
'metrics' => 'Metrics',
40+
];

src/Translator.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Stackwatch;
6+
7+
use function file_exists;
8+
use function is_dir;
9+
use function is_readable;
10+
use function trim;
11+
12+
final class Translator
13+
{
14+
public readonly string $locale;
15+
public readonly string $fallbackLocale;
16+
/** @var non-empty-string */
17+
public readonly string $rootDirectory;
18+
/** @var array<string, array<string, string>> */
19+
private array $translations = [];
20+
21+
public function __construct(string $locale = 'en', string $fallback = 'en', string $rootDirectory = '')
22+
{
23+
$this->locale = trim($locale);
24+
$this->fallbackLocale = trim($fallback);
25+
$rootDirectory = trim($rootDirectory);
26+
if ('' === $rootDirectory) {
27+
$rootDirectory = __DIR__.'/Resources/translations';
28+
}
29+
30+
(is_dir($rootDirectory) && is_readable($rootDirectory)) || throw new InvalidArgument('The root directory "'.$rootDirectory.'" does not exist or is not readable.');
31+
$this->rootDirectory = $rootDirectory;
32+
$this->load($this->locale);
33+
$this->load($this->fallbackLocale);
34+
}
35+
36+
private function load(string $locale): void
37+
{
38+
if (!isset($this->translations[$locale])) {
39+
$file = $this->rootDirectory."/{$locale}.php";
40+
41+
/** @var array<string, string> $data */
42+
$data = file_exists($file) ? require $file : [];
43+
44+
$this->translations[$locale] = $data;
45+
}
46+
}
47+
48+
public function translate(string $key): string
49+
{
50+
return $this->translations[$this->locale][$key]
51+
?? $this->translations[$this->fallbackLocale][$key]
52+
?? $key;
53+
}
54+
55+
/**
56+
* @param array<string, string> $input
57+
*
58+
* @return array<string, string>
59+
*/
60+
public function translateArrayKeys(array $input): array
61+
{
62+
$output = [];
63+
foreach ($input as $key => $value) {
64+
$output[$this->translate($key)] = $value;
65+
}
66+
67+
return $output;
68+
}
69+
}

src/TranslatorTest.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Stackwatch;
6+
7+
use GlobIterator;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\Test;
10+
use PHPUnit\Framework\TestCase;
11+
use SplFileInfo;
12+
13+
use function dirname;
14+
use function file_put_contents;
15+
use function is_dir;
16+
use function mkdir;
17+
use function rmdir;
18+
use function unlink;
19+
20+
#[CoversClass(Translator::class)]
21+
final class TranslatorTest extends TestCase
22+
{
23+
private string $fixturesDir;
24+
25+
protected function setUp(): void
26+
{
27+
$this->fixturesDir = dirname(__DIR__).'/fixtures/translations';
28+
29+
if (!is_dir($this->fixturesDir)) {
30+
mkdir($this->fixturesDir, 0777, true);
31+
}
32+
33+
file_put_contents($this->fixturesDir.'/en.php', "<?php return ['name' => 'Name', 'email' => 'Email'];");
34+
file_put_contents($this->fixturesDir.'/fr.php', "<?php return ['name' => 'Nom'];");
35+
}
36+
37+
protected function tearDown(): void
38+
{
39+
/** @var SplFileInfo $fileInfo */
40+
foreach (new GlobIterator($this->fixturesDir.'/*.php') as $fileInfo) {
41+
unlink($fileInfo->getRealPath());
42+
}
43+
44+
rmdir($this->fixturesDir);
45+
rmdir(dirname($this->fixturesDir));
46+
}
47+
48+
#[Test]
49+
private function translator(string $locale, string $fallback = 'en'): Translator
50+
{
51+
return new Translator($locale, $fallback, $this->fixturesDir);
52+
}
53+
54+
#[Test]
55+
public function it_can_translate_from_a_key(): void
56+
{
57+
self::assertSame('Name', $this->translator('en')->translate('name'));
58+
}
59+
60+
#[Test]
61+
public function it_uses_the_fallback_locale_if_present(): void
62+
{
63+
self::assertSame('Email', $this->translator('fr')->translate('email'));
64+
}
65+
66+
#[Test]
67+
public function it_returns_the_original_text_in_absence_of_a_fallback_translation(): void
68+
{
69+
self::assertSame('unknown_key', $this->translator('fr')->translate('unknown_key'));
70+
}
71+
72+
#[Test]
73+
public function it_translate_the_array_keys(): void
74+
{
75+
$input = [
76+
'name' => 'Alice',
77+
'email' => 'alice@example.com',
78+
];
79+
80+
$output = $this->translator('fr')->translateArrayKeys($input);
81+
82+
self::assertSame([
83+
'Nom' => 'Alice',
84+
'Email' => 'alice@example.com',
85+
], $output);
86+
}
87+
88+
#[Test]
89+
public function it_fails_to_instantiate_if_the_directory_does_not_exists(): void
90+
{
91+
$this->expectException(InvalidArgument::class);
92+
93+
new Translator('en', 'en', __DIR__.'/does_not_exist');
94+
}
95+
}

0 commit comments

Comments
 (0)