diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 117d66a..76ce874 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,6 +3,9 @@ name: PHP-CS-Fixer on: push: +permissions: + contents: write + jobs: pint: runs-on: ubuntu-latest diff --git a/src/DTO/Translation.php b/src/DTO/Translation.php new file mode 100644 index 0000000..70edc73 --- /dev/null +++ b/src/DTO/Translation.php @@ -0,0 +1,12 @@ +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; @@ -51,7 +56,7 @@ public function __destruct() return; } - $this->translationDumper->dump($this->keysWithMissingTranslations); + $this->translationDumper->dump($this->missingTranslations); } private function shouldBeIgnored(string $key): bool diff --git a/src/LaravelTranslationDumperServiceProvider.php b/src/LaravelTranslationDumperServiceProvider.php index ac65635..d7e2394 100644 --- a/src/LaravelTranslationDumperServiceProvider.php +++ b/src/LaravelTranslationDumperServiceProvider.php @@ -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'), ), ); @@ -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'), ), ); diff --git a/src/TranslationDumper.php b/src/TranslationDumper.php index 4742d46..7b844d9 100644 --- a/src/TranslationDumper.php +++ b/src/TranslationDumper.php @@ -2,7 +2,9 @@ namespace Bambamboole\LaravelTranslationDumper; +use Bambamboole\LaravelTranslationDumper\DTO\Translation; use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Collection; class TranslationDumper implements TranslationDumperInterface { @@ -11,7 +13,6 @@ 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 @@ -19,17 +20,23 @@ 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"; @@ -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) { @@ -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; @@ -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; } } diff --git a/src/TranslationDumperInterface.php b/src/TranslationDumperInterface.php index 0e07974..5759560 100644 --- a/src/TranslationDumperInterface.php +++ b/src/TranslationDumperInterface.php @@ -6,5 +6,5 @@ interface TranslationDumperInterface { public function setLocale(string $locale): void; - public function dump(array $translationKeys): void; + public function dump(array $translations): void; } diff --git a/src/TranslationIdentifier.php b/src/TranslationIdentifier.php new file mode 100644 index 0000000..4502768 --- /dev/null +++ b/src/TranslationIdentifier.php @@ -0,0 +1,23 @@ +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); @@ -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', ); diff --git a/tests/Feature/fixtures/expected/en/test.php b/tests/Feature/fixtures/expected/en/test.php index bff0639..e84a166 100644 --- a/tests/Feature/fixtures/expected/en/test.php +++ b/tests/Feature/fixtures/expected/en/test.php @@ -4,5 +4,6 @@ 'dotted' => [ 'additional' => 'x-test.dotted.additional', 'nested' => 'dotted nested key', + 'replacement' => 'x-test.dotted.replacement :name foo', ], ]; diff --git a/tests/Unit/ArrayExporterTest.php b/tests/Unit/ArrayExporterTest.php index 4d3f78f..ed36cf8 100644 --- a/tests/Unit/ArrayExporterTest.php +++ b/tests/Unit/ArrayExporterTest.php @@ -27,6 +27,6 @@ public function testItCanDumpArraysInShortSyntax(): void EOT; - self::assertEquals($out, (new ArrayExporter())->export($in)); + self::assertEquals($out, (new ArrayExporter)->export($in)); } } diff --git a/tests/Unit/DumpingTranslatorTest.php b/tests/Unit/DumpingTranslatorTest.php index 7687fb4..bc30713 100644 --- a/tests/Unit/DumpingTranslatorTest.php +++ b/tests/Unit/DumpingTranslatorTest.php @@ -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; @@ -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); diff --git a/tests/Unit/TranslationDumperTest.php b/tests/Unit/TranslationDumperTest.php index 0892f02..6659034 100644 --- a/tests/Unit/TranslationDumperTest.php +++ b/tests/Unit/TranslationDumperTest.php @@ -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; @@ -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']]], ], ];