Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to use DTO to be able to dump replacements #6

Merged
merged 3 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: PHP-CS-Fixer
on:
push:

permissions:
contents: write

jobs:
pint:
runs-on: ubuntu-latest
Expand Down
12 changes: 12 additions & 0 deletions src/DTO/Translation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace Bambamboole\LaravelTranslationDumper\DTO;

class Translation
{
public function __construct(
public readonly string $key,
public readonly string $translation,
public readonly array $replace = [],
) {}
}
7 changes: 6 additions & 1 deletion src/DumpingTranslator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

namespace Bambamboole\LaravelTranslationDumper;

use Bambamboole\LaravelTranslationDumper\DTO\Translation;
use Illuminate\Contracts\Translation\Translator as TranslatorInterface;

class DumpingTranslator implements TranslatorInterface
{
private array $keysWithMissingTranslations = [];

private array $missingTranslations = [];

public function __construct(
private readonly TranslatorInterface $translator,
private readonly TranslationDumperInterface $translationDumper,
private readonly string $dumpPrefix = 'x-',
private readonly array $ignoreKeys = [],
) {}

Expand All @@ -19,6 +23,7 @@ public function get($key, array $replace = [], $locale = null, $fallback = true)
$translation = $this->translator->get($key, $replace, $locale, $fallback);
if ($translation === $key && ! $this->shouldBeIgnored($key)) {
$this->keysWithMissingTranslations[] = $key;
$this->missingTranslations[] = new Translation($key, $this->dumpPrefix.$key, $replace);
}

return $translation;
Expand Down Expand Up @@ -51,7 +56,7 @@ public function __destruct()
return;
}

$this->translationDumper->dump($this->keysWithMissingTranslations);
$this->translationDumper->dump($this->missingTranslations);
}

private function shouldBeIgnored(string $key): bool
Expand Down
6 changes: 3 additions & 3 deletions src/LaravelTranslationDumperServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ public function register(): void
$this->app->singleton(
TranslationDumper::class,
static fn (Application $app) => new TranslationDumper(
new Filesystem(),
new ArrayExporter(),
new Filesystem,
new ArrayExporter,
$app->langPath(),
$app->make(Repository::class)->get('app.locale'),
$app->make(Repository::class)->get('translation.dump_prefix'),
),
);

Expand All @@ -45,6 +44,7 @@ public function register(): void
static fn (Translator $translator, $app) => new DumpingTranslator(
$translator,
$app->make(TranslationDumperInterface::class),
$app->make(Repository::class)->get('translation.dump_prefix'),
$app->make(Repository::class)->get('translation.ignore_keys'),
),
);
Expand Down
87 changes: 43 additions & 44 deletions src/TranslationDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Bambamboole\LaravelTranslationDumper;

use Bambamboole\LaravelTranslationDumper\DTO\Translation;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;

class TranslationDumper implements TranslationDumperInterface
{
Expand All @@ -11,25 +13,30 @@ public function __construct(
private readonly ArrayExporter $exporter,
private readonly string $languageFilePath,
private string $locale,
private readonly string $dumpPrefix = 'x-',
) {}

public function setLocale(string $locale): void
{
$this->locale = $locale;
}

public function dump(array $translationKeys): void
/** @param Translation[] $translations */
public function dump(array $translations): void
{
$dottedStrings = $this->filterForDottedTranslationKeys($translationKeys);
$this->dumpDottedKeys($dottedStrings);
$this->dumpNonDottedKeys(array_unique(array_diff($translationKeys, $dottedStrings)));
collect($translations)
->groupBy(fn (Translation $translation) => TranslationIdentifier::identify($translation->key))
->each(function (Collection $translations, string $type) {
match ($type) {
TranslationType::PHP->value => $this->dumpPhpTranslations($translations->toArray()),
TranslationType::JSON->value => $this->dumpJsonTranslations($translations->toArray()),
};
});
}

private function dumpDottedKeys(array $dottedStrings): void
/** @param Translation[] $translations */
private function dumpPhpTranslations(array $translations): void
{
$result = $this->transformDottedStringsToArray($dottedStrings);

$result = $this->transformDottedStringsToArray($translations);
foreach ($result as $key => $value) {
$path = $this->languageFilePath.'/'.$this->locale;
$file = "{$path}/{$key}.php";
Expand All @@ -41,34 +48,35 @@ private function dumpDottedKeys(array $dottedStrings): void
}
}

private function filterForDottedTranslationKeys(array $unfilteredKeys): array
/** @param Translation[] $translations */
private function dumpJsonTranslations(array $translations): void
{
$keys = array_filter(
$unfilteredKeys,
function (string $key) {
if (str_contains($key, ' ')) {
return false;
}
if (! str_contains($key, '.')) {
return false;
}
if (str_ends_with($key, '.')) {
return false;
}

return true;
});
$keys = array_unique($keys);
if (empty($translations)) {
return;
}
$newTranslations = collect($translations)
->mapWithKeys(function (Translation $translation) {
return [$translation->key => $this->prepareTranslationValue($translation)];
})
->toArray();
$file = "{$this->languageFilePath}/{$this->locale}.json";
$existingKeys = $this->filesystem->exists($file)
? json_decode($this->filesystem->get($file), true)
: [];

return array_values($keys);
$mergedTranslations = array_merge($existingKeys, $newTranslations);
ksort($mergedTranslations, SORT_NATURAL | SORT_FLAG_CASE);
$this->filesystem->ensureDirectoryExists($this->languageFilePath);
$this->filesystem->put($file, json_encode($mergedTranslations, JSON_PRETTY_PRINT).PHP_EOL);
}

private function transformDottedStringsToArray(array $dottedStrings): array
/** @param Translation[] $translations */
private function transformDottedStringsToArray(array $translations): array
{
$result = [];

foreach ($dottedStrings as $dottedString) {
$keys = explode('.', $dottedString);
foreach ($translations as $translation) {
$keys = explode('.', $translation->key);
$current = &$result;

while (count($keys) > 1) {
Expand All @@ -80,7 +88,7 @@ private function transformDottedStringsToArray(array $dottedStrings): array
}

$lastKey = array_shift($keys);
$current[$lastKey] = $this->dumpPrefix.$dottedString;
$current[$lastKey] = $this->prepareTranslationValue($translation);
}

return $result;
Expand Down Expand Up @@ -117,22 +125,13 @@ private function mergeArrays(array $array1, array $array2): array
return $merged;
}

private function dumpNonDottedKeys(array $nonDottedKeys): void
private function prepareTranslationValue(Translation $translation): string
{
if (empty($nonDottedKeys)) {
return;
}
$file = "{$this->languageFilePath}/{$this->locale}.json";
if ($this->filesystem->exists($file)) {
$existingKeys = json_decode($this->filesystem->get($file), true);
} else {
$existingKeys = [];
$value = $translation->translation;
foreach ($translation->replace as $key => $replace) {
$value .= sprintf(' :%s %s', $key, $replace ?? 'n/a');
}

$nonDottedKeys = array_combine($nonDottedKeys, array_map(fn ($key) => $this->dumpPrefix.$key, $nonDottedKeys));
$keys = array_merge($existingKeys, $nonDottedKeys);
ksort($keys, SORT_NATURAL | SORT_FLAG_CASE);
$this->filesystem->ensureDirectoryExists($this->languageFilePath);
$this->filesystem->put($file, json_encode($keys, JSON_PRETTY_PRINT).PHP_EOL);
return $value;
}
}
2 changes: 1 addition & 1 deletion src/TranslationDumperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ interface TranslationDumperInterface
{
public function setLocale(string $locale): void;

public function dump(array $translationKeys): void;
public function dump(array $translations): void;
}
23 changes: 23 additions & 0 deletions src/TranslationIdentifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types=1);

namespace Bambamboole\LaravelTranslationDumper;

use Illuminate\Support\Str;

class TranslationIdentifier
{
public static function identify(string $key): TranslationType
{
if (Str::contains($key, ' ')) {
return TranslationType::JSON;
}
if (! Str::contains($key, '.')) {
return TranslationType::JSON;
}
if (Str::endsWith($key, '.')) {
return TranslationType::JSON;
}

return TranslationType::PHP;
}
}
9 changes: 9 additions & 0 deletions src/TranslationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Bambamboole\LaravelTranslationDumper;

enum TranslationType: string
{
case JSON = 'json';
case PHP = 'php';
}
16 changes: 12 additions & 4 deletions tests/Feature/TranslationDumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Bambamboole\LaravelTranslationDumper\Tests\Feature;

use Bambamboole\LaravelTranslationDumper\ArrayExporter;
use Bambamboole\LaravelTranslationDumper\DTO\Translation;
use Bambamboole\LaravelTranslationDumper\TranslationDumper;
use Illuminate\Filesystem\Filesystem;
use PHPUnit\Framework\TestCase;
Expand All @@ -13,12 +14,19 @@ class TranslationDumperTest extends TestCase

public function testItDumpsKeysAsExpected(): void
{
$fs = new Filesystem();
$fs = new Filesystem;
$testFolderName = uniqid();
$workingPath = str_replace('lang', $testFolderName, self::TEST_LANGUAGE_PATH);

$fs->copyDirectory(self::TEST_LANGUAGE_PATH, $workingPath);
$this->createSubject($workingPath)->dump(['test.dotted.additional', 'test undotted key', 'word', 'Works.']);
$translations = [
new Translation('test.dotted.additional', 'x-test.dotted.additional'),
new Translation('test.dotted.replacement', 'x-test.dotted.replacement', ['name' => 'foo']),
new Translation('test undotted key', 'x-test undotted key'),
new Translation('word', 'x-word'),
new Translation('Works.', 'x-Works.'),
];
$this->createSubject($workingPath)->dump($translations);

$files = $fs->allFiles($workingPath);

Expand All @@ -35,8 +43,8 @@ public function testItDumpsKeysAsExpected(): void
private function createSubject(string $folder): TranslationDumper
{
return new TranslationDumper(
new Filesystem(),
new ArrayExporter(),
new Filesystem,
new ArrayExporter,
$folder,
'en',
);
Expand Down
1 change: 1 addition & 0 deletions tests/Feature/fixtures/expected/en/test.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
'dotted' => [
'additional' => 'x-test.dotted.additional',
'nested' => 'dotted nested key',
'replacement' => 'x-test.dotted.replacement :name foo',
],
];
2 changes: 1 addition & 1 deletion tests/Unit/ArrayExporterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public function testItCanDumpArraysInShortSyntax(): void

EOT;

self::assertEquals($out, (new ArrayExporter())->export($in));
self::assertEquals($out, (new ArrayExporter)->export($in));
}
}
5 changes: 4 additions & 1 deletion tests/Unit/DumpingTranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Bambamboole\LaravelTranslationDumper\Tests\Unit;

use Bambamboole\LaravelTranslationDumper\DTO\Translation;
use Bambamboole\LaravelTranslationDumper\DumpingTranslator;
use Bambamboole\LaravelTranslationDumper\TranslationDumperInterface;
use Illuminate\Contracts\Translation\Translator;
Expand Down Expand Up @@ -100,7 +101,9 @@ public function testItCallsTheTranslationDumperOnDestruct(): void
$this->translationDumper
->expects($this->once())
->method('dump')
->with([self::TEST_KEY]);
->with(self::callback(
fn (array $translations) => $translations[0] instanceof Translation && $translations[0]->key === self::TEST_KEY,
));

$dumpingTranslator = $this->createDumpingTranslator();
$dumpingTranslator->get(self::TEST_KEY);
Expand Down
3 changes: 2 additions & 1 deletion tests/Unit/TranslationDumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Bambamboole\LaravelTranslationDumper\Tests\Unit;

use Bambamboole\LaravelTranslationDumper\ArrayExporter;
use Bambamboole\LaravelTranslationDumper\DTO\Translation;
use Bambamboole\LaravelTranslationDumper\TranslationDumper;
use Illuminate\Filesystem\Filesystem;
use PHPUnit\Framework\MockObject\MockObject;
Expand Down Expand Up @@ -64,7 +65,7 @@ public static function provideTestData(): array
{
return [
[
['foo.bar.baz'],
[new Translation('foo.bar.baz', 'x-foo.bar.baz')],
['foo' => ['bar' => ['baz' => 'x-foo.bar.baz']]],
],
];
Expand Down