Skip to content

Commit

Permalink
Merge pull request #1 from esign/feature/import-command
Browse files Browse the repository at this point in the history
Add: command to import translations from files to the database
  • Loading branch information
jordyvanderhaegen authored Jun 18, 2024
2 parents 9a1469d + 87391ec commit 4d7d312
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 10 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
matrix:
os: [ubuntu-latest]
php: [8.3, 8.2, 8.1, 8.0]
laravel: [11.*, 10.*, 9.*, 8.*]
laravel: [11.*, 10.*, 9.*]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 11.*
Expand All @@ -23,8 +23,6 @@ jobs:
testbench: 8.*
- laravel: 9.*
testbench: 7.*
- laravel: 8.*
testbench: ^6.23
exclude:
- laravel: 11.*
php: 8.0
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,19 @@ However, if you make changes outside of these operations, you need to manually c
php artisan translations:clear-cache
```

### Importing file translations to the database
This package ships with an Artisan command that allows you to import file translations into the database.
This can be useful when you want to migrate your translations from file-based to database-based storage.
You should specify the locales you want to import translations for as a comma-separated list:
```bash
php artisan translations:import-files-to-database --locales=en,nl
```

You can optionally specify the `--overwrite` flag to overwrite any existing translations.
```bash
php artisan translations:import-files-to-database --locales=en,nl --overwrite
```

### FAQ
<details>
<summary>Installation conflict with [mcamara/laravel-localization](https://github.com/mcamara/laravel-localization)</summary>
Expand Down
12 changes: 6 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
"require": {
"php": "^8.0",
"esign/laravel-underscore-translatable": "^1.1",
"illuminate/cache": "^8.0|^9.0|^10.0|^11.0",
"illuminate/console": "^8.0|^9.0|^10.0|^11.0",
"illuminate/database": "^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "^8.0|^9.0|^10.0|^11.0",
"illuminate/translation": "^8.0|^9.0|^10.0|^11.0"
"illuminate/cache": "^9.2|^10.0|^11.0",
"illuminate/console": "^9.2|^10.0|^11.0",
"illuminate/database": "^9.2|^10.0|^11.0",
"illuminate/support": "^9.2|^10.0|^11.0",
"illuminate/translation": "^9.2|^10.0|^11.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.5",
"orchestra/testbench": "^6.0|^7.0|^8.0|^9.0",
"orchestra/testbench": "^7.0|^8.0|^9.0",
"phpunit/phpunit": "^9.5|^10.0"
},
"autoload": {
Expand Down
87 changes: 87 additions & 0 deletions src/Actions/ImportFileTranslationsToDatabaseAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Esign\TranslationLoader\Actions;

use Esign\TranslationLoader\TranslationLoaderServiceProvider;
use Illuminate\Support\Facades\DB;
use Illuminate\Translation\FileLoader;

class ImportFileTranslationsToDatabaseAction
{
protected FileLoader $fileLoader;

public function __construct()
{
$this->fileLoader = new FileLoader(app('files'), app('path.lang'));
}

public function handle(array $locales, bool $overwrite): int
{
return $this->upsertOrInsertTranslations($this->getTranslations($locales), $overwrite);
}

protected function getTranslations(array $locales): array
{
$groupedTranslations = [];
foreach ($locales as $locale) {
$translations = $this->fileLoader->load($locale, '*', '*');
foreach ($translations as $key => $value) {
$groupedTranslations[$key][$locale] = $value;
}
}

return $this->normalizeTranslations($groupedTranslations, $locales);
}

protected function normalizeTranslations(array $translations, array $locales): array
{
foreach ($translations as &$translation) {
foreach ($locales as $locale) {
if (! isset($translation[$locale])) {
$translation[$locale] = null;
}
}
}

return $translations;
}

protected function prepareTranslationsForUpsert(array $translations): array
{
/** @var \Esign\TranslationLoader\Models\Translation */
$configuredModelClass = TranslationLoaderServiceProvider::getConfiguredModel();

$preparedTranslations = [];
foreach ($translations as $key => $values) {
$translation = new $configuredModelClass();
$translation->group = '*';
$translation->key = $key;
$translation->created_at = now()->toDateTimeString();
$translation->updated_at = now()->toDateTimeString();
$translation->setTranslations('value', $values);
$preparedTranslations[] = $translation->getAttributes();
}

return $preparedTranslations;
}

protected function upsertOrInsertTranslations(array $translations, bool $overwrite): int
{
/** @var \Esign\TranslationLoader\Models\Translation */
$configuredModelClass = TranslationLoaderServiceProvider::getConfiguredModel();
$translations = $this->prepareTranslationsForUpsert($translations);
$affectedRecords = 0;

DB::transaction(function () use ($translations, $configuredModelClass, $overwrite, &$affectedRecords) {
foreach (array_chunk($translations, 500, true) as $chunk) {
if ($overwrite) {
$affectedRecords += $configuredModelClass::query()->upsert($chunk, ['key', 'group']);
} else {
$affectedRecords += $configuredModelClass::query()->insertOrIgnore($chunk);
}
}
});

return $affectedRecords;
}
}
24 changes: 24 additions & 0 deletions src/Commands/ImportFileTranslationsToDatabaseCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Esign\TranslationLoader\Commands;

use Esign\TranslationLoader\Actions\ImportFileTranslationsToDatabaseAction;
use Illuminate\Console\Command;

class ImportFileTranslationsToDatabaseCommand extends Command
{
protected $signature = 'translations:import-files-to-database {--locales=} {--overwrite}';
protected $description = 'Imports file translations to the database.';

public function handle(ImportFileTranslationsToDatabaseAction $importFileTranslationsToDatabaseAction): int
{
$affectedRecords = $importFileTranslationsToDatabaseAction->handle(
locales: explode(',', $this->option('locales')),
overwrite: (bool) $this->option('overwrite'),
);

$this->info("Successfully imported translations, affected records: {$affectedRecords}.");

return self::SUCCESS;
}
}
6 changes: 5 additions & 1 deletion src/TranslationLoaderServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Esign\TranslationLoader;

use Esign\TranslationLoader\Commands\ClearTranslationsCacheCommand;
use Esign\TranslationLoader\Commands\ImportFileTranslationsToDatabaseCommand;
use Esign\TranslationLoader\Exceptions\InvalidConfiguration;
use Esign\TranslationLoader\Loaders\AggregateLoader;
use Esign\TranslationLoader\Models\Translation;
Expand All @@ -17,7 +18,10 @@ class TranslationLoaderServiceProvider extends BaseTranslationServiceProvider
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([ClearTranslationsCacheCommand::class]);
$this->commands([
ClearTranslationsCacheCommand::class,
ImportFileTranslationsToDatabaseCommand::class,
]);

$this->publishes([
$this->configPath() => config_path('translation-loader.php'),
Expand Down
144 changes: 144 additions & 0 deletions tests/Feature/Commands/ImportFileTranslationToDatabaseCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Esign\TranslationLoader\Tests\Feature\Commands;

use Esign\TranslationLoader\Commands\ImportFileTranslationsToDatabaseCommand;
use Esign\TranslationLoader\Models\Translation;
use Esign\TranslationLoader\Tests\TestCase;

class ImportFileTranslationToDatabaseCommandTest extends TestCase
{
/** @test */
public function it_can_import_translations()
{
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl']);

$this->assertDatabaseHas(Translation::class, [
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Hello world',
'value_nl' => 'Hallo wereld',
]);
}

/** @test */
public function it_can_import_translations_for_specific_locales()
{
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);

$this->assertDatabaseHas(Translation::class, [
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Hello world',
'value_nl' => null,
]);
}

/** @test */
public function it_wont_overwrite_existing_translations_when_the_overwrite_flag_was_not_given()
{
Translation::create([
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Goodbye world',
'value_nl' => 'Tot ziens wereld',
]);

$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl']);

$this->assertDatabaseHas(Translation::class, [
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Goodbye world',
'value_nl' => 'Tot ziens wereld',
]);
}

/** @test */
public function it_can_overwrite_existing_translations()
{
Translation::create([
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Goodbye world',
'value_nl' => 'Tot ziens wereld',
]);

$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl', '--overwrite' => true]);

$this->assertDatabaseHas(Translation::class, [
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Hello world',
'value_nl' => 'Hallo wereld',
]);
}

/** @test */
public function it_wont_overwrite_existing_translations_for_locales_that_were_not_specified()
{
Translation::create([
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Goodbye world',
'value_nl' => 'Tot ziens wereld',
]);

$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);

$this->assertDatabaseHas(Translation::class, [
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Hello world',
'value_nl' => 'Tot ziens wereld',
]);
}

/** @test */
public function it_can_report_the_affected_records()
{
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);

$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
$command->assertSuccessful();
}

/** @test */
public function it_can_report_the_affected_records_when_a_translation_is_already_present()
{
Translation::create([
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Hello world',
]);

$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);

$command->expectsOutputToContain('Successfully imported translations, affected records: 0.');
$command->assertSuccessful();
}

/** @test */
public function it_can_report_affected_records_when_the_overwrite_flag_is_given()
{
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);

$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
$command->assertSuccessful();
}

/** @test */
public function it_can_report_affected_records_when_the_overwrite_flag_is_given_and_a_translation_is_already_present()
{
Translation::create([
'group' => '*',
'key' => 'Hello world',
'value_en' => 'Hello world',
]);

$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);

$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
$command->assertSuccessful();
}
}

0 comments on commit 4d7d312

Please sign in to comment.