Skip to content

Commit 4d7d312

Browse files
Merge pull request #1 from esign/feature/import-command
Add: command to import translations from files to the database
2 parents 9a1469d + 87391ec commit 4d7d312

File tree

7 files changed

+280
-10
lines changed

7 files changed

+280
-10
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
matrix:
1515
os: [ubuntu-latest]
1616
php: [8.3, 8.2, 8.1, 8.0]
17-
laravel: [11.*, 10.*, 9.*, 8.*]
17+
laravel: [11.*, 10.*, 9.*]
1818
stability: [prefer-lowest, prefer-stable]
1919
include:
2020
- laravel: 11.*
@@ -23,8 +23,6 @@ jobs:
2323
testbench: 8.*
2424
- laravel: 9.*
2525
testbench: 7.*
26-
- laravel: 8.*
27-
testbench: ^6.23
2826
exclude:
2927
- laravel: 11.*
3028
php: 8.0

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>

composer.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
"require": {
2121
"php": "^8.0",
2222
"esign/laravel-underscore-translatable": "^1.1",
23-
"illuminate/cache": "^8.0|^9.0|^10.0|^11.0",
24-
"illuminate/console": "^8.0|^9.0|^10.0|^11.0",
25-
"illuminate/database": "^8.0|^9.0|^10.0|^11.0",
26-
"illuminate/support": "^8.0|^9.0|^10.0|^11.0",
27-
"illuminate/translation": "^8.0|^9.0|^10.0|^11.0"
23+
"illuminate/cache": "^9.2|^10.0|^11.0",
24+
"illuminate/console": "^9.2|^10.0|^11.0",
25+
"illuminate/database": "^9.2|^10.0|^11.0",
26+
"illuminate/support": "^9.2|^10.0|^11.0",
27+
"illuminate/translation": "^9.2|^10.0|^11.0"
2828
},
2929
"require-dev": {
3030
"friendsofphp/php-cs-fixer": "^3.5",
31-
"orchestra/testbench": "^6.0|^7.0|^8.0|^9.0",
31+
"orchestra/testbench": "^7.0|^8.0|^9.0",
3232
"phpunit/phpunit": "^9.5|^10.0"
3333
},
3434
"autoload": {
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: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
9+
class ImportFileTranslationToDatabaseCommandTest extends TestCase
10+
{
11+
/** @test */
12+
public function it_can_import_translations()
13+
{
14+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl']);
15+
16+
$this->assertDatabaseHas(Translation::class, [
17+
'group' => '*',
18+
'key' => 'Hello world',
19+
'value_en' => 'Hello world',
20+
'value_nl' => 'Hallo wereld',
21+
]);
22+
}
23+
24+
/** @test */
25+
public function it_can_import_translations_for_specific_locales()
26+
{
27+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);
28+
29+
$this->assertDatabaseHas(Translation::class, [
30+
'group' => '*',
31+
'key' => 'Hello world',
32+
'value_en' => 'Hello world',
33+
'value_nl' => null,
34+
]);
35+
}
36+
37+
/** @test */
38+
public function it_wont_overwrite_existing_translations_when_the_overwrite_flag_was_not_given()
39+
{
40+
Translation::create([
41+
'group' => '*',
42+
'key' => 'Hello world',
43+
'value_en' => 'Goodbye world',
44+
'value_nl' => 'Tot ziens wereld',
45+
]);
46+
47+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl']);
48+
49+
$this->assertDatabaseHas(Translation::class, [
50+
'group' => '*',
51+
'key' => 'Hello world',
52+
'value_en' => 'Goodbye world',
53+
'value_nl' => 'Tot ziens wereld',
54+
]);
55+
}
56+
57+
/** @test */
58+
public function it_can_overwrite_existing_translations()
59+
{
60+
Translation::create([
61+
'group' => '*',
62+
'key' => 'Hello world',
63+
'value_en' => 'Goodbye world',
64+
'value_nl' => 'Tot ziens wereld',
65+
]);
66+
67+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en,nl', '--overwrite' => true]);
68+
69+
$this->assertDatabaseHas(Translation::class, [
70+
'group' => '*',
71+
'key' => 'Hello world',
72+
'value_en' => 'Hello world',
73+
'value_nl' => 'Hallo wereld',
74+
]);
75+
}
76+
77+
/** @test */
78+
public function it_wont_overwrite_existing_translations_for_locales_that_were_not_specified()
79+
{
80+
Translation::create([
81+
'group' => '*',
82+
'key' => 'Hello world',
83+
'value_en' => 'Goodbye world',
84+
'value_nl' => 'Tot ziens wereld',
85+
]);
86+
87+
$this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);
88+
89+
$this->assertDatabaseHas(Translation::class, [
90+
'group' => '*',
91+
'key' => 'Hello world',
92+
'value_en' => 'Hello world',
93+
'value_nl' => 'Tot ziens wereld',
94+
]);
95+
}
96+
97+
/** @test */
98+
public function it_can_report_the_affected_records()
99+
{
100+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);
101+
102+
$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
103+
$command->assertSuccessful();
104+
}
105+
106+
/** @test */
107+
public function it_can_report_the_affected_records_when_a_translation_is_already_present()
108+
{
109+
Translation::create([
110+
'group' => '*',
111+
'key' => 'Hello world',
112+
'value_en' => 'Hello world',
113+
]);
114+
115+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en']);
116+
117+
$command->expectsOutputToContain('Successfully imported translations, affected records: 0.');
118+
$command->assertSuccessful();
119+
}
120+
121+
/** @test */
122+
public function it_can_report_affected_records_when_the_overwrite_flag_is_given()
123+
{
124+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);
125+
126+
$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
127+
$command->assertSuccessful();
128+
}
129+
130+
/** @test */
131+
public function it_can_report_affected_records_when_the_overwrite_flag_is_given_and_a_translation_is_already_present()
132+
{
133+
Translation::create([
134+
'group' => '*',
135+
'key' => 'Hello world',
136+
'value_en' => 'Hello world',
137+
]);
138+
139+
$command = $this->artisan(ImportFileTranslationsToDatabaseCommand::class, ['--locales' => 'en', '--overwrite' => true]);
140+
141+
$command->expectsOutputToContain('Successfully imported translations, affected records: 1.');
142+
$command->assertSuccessful();
143+
}
144+
}

0 commit comments

Comments
 (0)