Skip to content

Commit 2dbb050

Browse files
Add: command to import translations from files to the database
1 parent 9a1469d commit 2dbb050

File tree

5 files changed

+276
-1
lines changed

5 files changed

+276
-1
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,19 @@ However, if you make changes outside of these operations, you need to manually c
186186
php artisan translations:clear-cache
187187
```
188188

189+
### Importing file translations to the database
190+
This package ships with an Artisan command that allows you to import file translations into the database.
191+
This can be useful when you want to migrate your translations from file-based to database-based storage.
192+
You should specify the locales you want to import translations for as a comma-separated list:
193+
```bash
194+
php artisan translations:import-files-to-database --locales=en,nl
195+
```
196+
197+
You can optionally specify the `--overwrite` flag to overwrite any existing translations.
198+
```bash
199+
php artisan translations:import-files-to-database --locales=en,nl --overwrite
200+
```
201+
189202
### FAQ
190203
<details>
191204
<summary>Installation conflict with [mcamara/laravel-localization](https://github.com/mcamara/laravel-localization)</summary>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace Esign\TranslationLoader\Actions;
4+
5+
use Esign\TranslationLoader\TranslationLoaderServiceProvider;
6+
use Illuminate\Support\Facades\DB;
7+
use Illuminate\Translation\FileLoader;
8+
9+
class ImportFileTranslationsToDatabaseAction
10+
{
11+
protected FileLoader $fileLoader;
12+
13+
public function __construct()
14+
{
15+
$this->fileLoader = new FileLoader(app('files'), app('path.lang'));
16+
}
17+
18+
public function handle(array $locales, bool $overwrite): int
19+
{
20+
return $this->upsertOrInsertTranslations($this->getTranslations($locales), $overwrite);
21+
}
22+
23+
protected function getTranslations(array $locales): array
24+
{
25+
$groupedTranslations = [];
26+
foreach ($locales as $locale) {
27+
$translations = $this->fileLoader->load($locale, '*', '*');
28+
foreach ($translations as $key => $value) {
29+
$groupedTranslations[$key][$locale] = $value;
30+
}
31+
}
32+
33+
return $this->normalizeTranslations($groupedTranslations, $locales);
34+
}
35+
36+
protected function normalizeTranslations(array $translations, array $locales): array
37+
{
38+
foreach ($translations as &$translation) {
39+
foreach ($locales as $locale) {
40+
if (! isset($translation[$locale])) {
41+
$translation[$locale] = null;
42+
}
43+
}
44+
}
45+
46+
return $translations;
47+
}
48+
49+
protected function prepareTranslationsForUpsert(array $translations): array
50+
{
51+
/** @var \Esign\TranslationLoader\Models\Translation */
52+
$configuredModelClass = TranslationLoaderServiceProvider::getConfiguredModel();
53+
54+
$preparedTranslations = [];
55+
foreach ($translations as $key => $values) {
56+
$translation = new $configuredModelClass();
57+
$translation->group = '*';
58+
$translation->key = $key;
59+
$translation->created_at = now()->toDateTimeString();
60+
$translation->updated_at = now()->toDateTimeString();
61+
$translation->setTranslations('value', $values);
62+
$preparedTranslations[] = $translation->getAttributes();
63+
}
64+
65+
return $preparedTranslations;
66+
}
67+
68+
protected function upsertOrInsertTranslations(array $translations, bool $overwrite): int
69+
{
70+
/** @var \Esign\TranslationLoader\Models\Translation */
71+
$configuredModelClass = TranslationLoaderServiceProvider::getConfiguredModel();
72+
$translations = $this->prepareTranslationsForUpsert($translations);
73+
$affectedRecords = 0;
74+
75+
DB::transaction(function () use ($translations, $configuredModelClass, $overwrite, &$affectedRecords) {
76+
foreach (array_chunk($translations, 500, true) as $chunk) {
77+
if ($overwrite) {
78+
$affectedRecords += $configuredModelClass::query()->upsert($chunk, ['key', 'group']);
79+
} else {
80+
$affectedRecords += $configuredModelClass::query()->insertOrIgnore($chunk);
81+
}
82+
}
83+
});
84+
85+
return $affectedRecords;
86+
}
87+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Esign\TranslationLoader\Commands;
4+
5+
use Esign\TranslationLoader\Actions\ImportFileTranslationsToDatabaseAction;
6+
use Illuminate\Console\Command;
7+
8+
class ImportFileTranslationsToDatabaseCommand extends Command
9+
{
10+
protected $signature = 'translations:import-files-to-database {--locales=} {--overwrite}';
11+
protected $description = 'Imports file translations to the database.';
12+
13+
public function handle(ImportFileTranslationsToDatabaseAction $importFileTranslationsToDatabaseAction): int
14+
{
15+
$affectedRecords = $importFileTranslationsToDatabaseAction->handle(
16+
locales: explode(',', $this->option('locales')),
17+
overwrite: (bool) $this->option('overwrite'),
18+
);
19+
20+
$this->info("Successfully imported translations, affected records: {$affectedRecords}.");
21+
22+
return self::SUCCESS;
23+
}
24+
}

src/TranslationLoaderServiceProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Esign\TranslationLoader;
44

55
use Esign\TranslationLoader\Commands\ClearTranslationsCacheCommand;
6+
use Esign\TranslationLoader\Commands\ImportFileTranslationsToDatabaseCommand;
67
use Esign\TranslationLoader\Exceptions\InvalidConfiguration;
78
use Esign\TranslationLoader\Loaders\AggregateLoader;
89
use Esign\TranslationLoader\Models\Translation;
@@ -17,7 +18,10 @@ class TranslationLoaderServiceProvider extends BaseTranslationServiceProvider
1718
public function boot()
1819
{
1920
if ($this->app->runningInConsole()) {
20-
$this->commands([ClearTranslationsCacheCommand::class]);
21+
$this->commands([
22+
ClearTranslationsCacheCommand::class,
23+
ImportFileTranslationsToDatabaseCommand::class,
24+
]);
2125

2226
$this->publishes([
2327
$this->configPath() => config_path('translation-loader.php'),
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
3+
namespace Esign\TranslationLoader\Tests\Feature\Commands;
4+
5+
use Esign\TranslationLoader\Commands\ImportFileTranslationsToDatabaseCommand;
6+
use Esign\TranslationLoader\Models\Translation;
7+
use Esign\TranslationLoader\Tests\TestCase;
8+
use Illuminate\Foundation\Testing\RefreshDatabase;
9+
10+
class ImportFileTranslationToDatabaseCommandTest extends TestCase
11+
{
12+
use RefreshDatabase;
13+
14+
/** @test */
15+
public function it_can_import_translations()
16+
{
17+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl']);
18+
19+
$this->assertDatabaseHas(Translation::class, [
20+
'group' => '*',
21+
'key' => 'Hello world',
22+
'value_en' => 'Hello world',
23+
'value_nl' => 'Hallo wereld',
24+
]);
25+
}
26+
27+
/** @test */
28+
public function it_can_import_translations_for_specific_locales()
29+
{
30+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);
31+
32+
$this->assertDatabaseHas(Translation::class, [
33+
'group' => '*',
34+
'key' => 'Hello world',
35+
'value_en' => 'Hello world',
36+
'value_nl' => null,
37+
]);
38+
}
39+
40+
/** @test */
41+
public function it_wont_overwrite_existing_translations_when_the_overwrite_flag_was_not_given()
42+
{
43+
Translation::create([
44+
'group' => '*',
45+
'key' => 'Hello world',
46+
'value_en' => 'Goodbye world',
47+
'value_nl' => 'Tot ziens wereld',
48+
]);
49+
50+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl']);
51+
52+
$this->assertDatabaseHas(Translation::class, [
53+
'group' => '*',
54+
'key' => 'Hello world',
55+
'value_en' => 'Goodbye world',
56+
'value_nl' => 'Tot ziens wereld',
57+
]);
58+
}
59+
60+
/** @test */
61+
public function it_can_overwrite_existing_translations()
62+
{
63+
Translation::create([
64+
'group' => '*',
65+
'key' => 'Hello world',
66+
'value_en' => 'Goodbye world',
67+
'value_nl' => 'Tot ziens wereld',
68+
]);
69+
70+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl', '--overwrite' => true]);
71+
72+
$this->assertDatabaseHas(Translation::class, [
73+
'group' => '*',
74+
'key' => 'Hello world',
75+
'value_en' => 'Hello world',
76+
'value_nl' => 'Hallo wereld',
77+
]);
78+
}
79+
80+
/** @test */
81+
public function it_wont_overwrite_existing_translations_for_locales_that_were_not_specified()
82+
{
83+
Translation::create([
84+
'group' => '*',
85+
'key' => 'Hello world',
86+
'value_en' => 'Goodbye world',
87+
'value_nl' => 'Tot ziens wereld',
88+
]);
89+
90+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);
91+
92+
$this->assertDatabaseHas(Translation::class, [
93+
'group' => '*',
94+
'key' => 'Hello world',
95+
'value_en' => 'Hello world',
96+
'value_nl' => 'Tot ziens wereld',
97+
]);
98+
}
99+
100+
/** @test */
101+
public function it_can_report_the_affected_records()
102+
{
103+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);
104+
105+
$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
106+
$command->assertSuccessful();
107+
}
108+
109+
/** @test */
110+
public function it_can_report_the_affected_records_when_a_translation_is_already_present()
111+
{
112+
Translation::create([
113+
'group' => '*',
114+
'key' => 'Hello world',
115+
'value_en' => 'Hello world',
116+
]);
117+
118+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);
119+
120+
$command->expectsOutputToContain('Successfully imported translations, affected records: 0.');
121+
$command->assertSuccessful();
122+
}
123+
124+
/** @test */
125+
public function it_can_report_affected_records_when_the_overwrite_flag_is_given()
126+
{
127+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);
128+
129+
$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
130+
$command->assertSuccessful();
131+
}
132+
133+
/** @test */
134+
public function it_can_report_affected_records_when_the_overwrite_flag_is_given_and_a_translation_is_already_present()
135+
{
136+
Translation::create([
137+
'group' => '*',
138+
'key' => 'Hello world',
139+
'value_en' => 'Hello world',
140+
]);
141+
142+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);
143+
144+
$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
145+
$command->assertSuccessful();
146+
}
147+
}

0 commit comments

Comments
 (0)