160 lines
5.6 KiB
PHP
160 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace Modules\Core\Command;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Database\Seeder;
|
|
use Modules\Core\Checkout\Database\Seeders\CheckoutTranslationsSeeder;
|
|
use Modules\Core\Localization\Database\Seeders\StorefrontTranslationsSeeder;
|
|
use Modules\Core\Localization\Database\Seeders\ValidationTranslationsSeeder;
|
|
use Modules\Core\Localization\Models\LanguageLine;
|
|
use ReflectionClass;
|
|
use ReflectionMethod;
|
|
|
|
/**
|
|
* The reverse of the translation seeders: copies lines added in the Filament
|
|
* Language Lines UI (e.g. while building the storefront) into the matching
|
|
* seeder's lines(), so they ship with core and every app gets them.
|
|
*
|
|
* Only adds keys the seeder doesn't have yet — a key that already exists in
|
|
* the seeder is left alone even if its text was edited in the database.
|
|
* New lines are appended at the end of lines() under a marker comment, to be
|
|
* moved into the right section by hand.
|
|
*
|
|
* Writes into the seeder files the app actually loaded, so it only makes
|
|
* sense in local mode, where vendor/boboko/core is a symlink to the
|
|
* ../boboko-core checkout. Against an installed copy it refuses to write;
|
|
* --dry-run works anywhere.
|
|
*/
|
|
class PullTranslationsCommand extends Command
|
|
{
|
|
protected $signature = 'boboko:translations:pull {--dry-run : Show what would be added without writing}';
|
|
|
|
protected $description = 'Add translation lines that exist in the database but not in the core translation seeders';
|
|
|
|
/** @var array<string, class-string<Seeder>> */
|
|
private const SEEDERS = [
|
|
'storefront' => StorefrontTranslationsSeeder::class,
|
|
'checkout' => CheckoutTranslationsSeeder::class,
|
|
'validation' => ValidationTranslationsSeeder::class,
|
|
];
|
|
|
|
public function handle(): int
|
|
{
|
|
$dryRun = (bool) $this->option('dry-run');
|
|
$total = 0;
|
|
|
|
foreach (self::SEEDERS as $group => $seederClass) {
|
|
$file = realpath((new ReflectionClass($seederClass))->getFileName());
|
|
|
|
if (! $dryRun && str_contains($file, '/vendor/')) {
|
|
$this->error("{$file} is an installed copy, not your ../boboko-core checkout.");
|
|
$this->line('Switch to local mode first (bin/core-mode local), or use --dry-run.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$known = (new ReflectionMethod($seederClass, 'lines'))->invoke(new $seederClass);
|
|
|
|
$missing = LanguageLine::query()
|
|
->where('group', $group)
|
|
->whereNotIn('key', array_keys($known))
|
|
->orderBy('key')
|
|
->get();
|
|
|
|
if ($missing->isEmpty()) {
|
|
$this->line("{$group}: nothing missing");
|
|
|
|
continue;
|
|
}
|
|
|
|
$entries = '';
|
|
|
|
foreach ($missing as $line) {
|
|
$en = $line->text['en'] ?? '';
|
|
$el = $line->text['el'] ?? '';
|
|
|
|
if ($en === '' || $el === '') {
|
|
$this->warn(" {$group}.{$line->key} has no ".($en === '' ? 'English' : 'Greek').' text — added empty, fill it in');
|
|
}
|
|
|
|
$entries .= $this->entry($line->key, $en, $el);
|
|
$this->info(" + {$group}.{$line->key}");
|
|
}
|
|
|
|
$total += $missing->count();
|
|
|
|
if (! $dryRun && ! $this->append($file, $entries)) {
|
|
return self::FAILURE;
|
|
}
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->line($dryRun
|
|
? "{$total} line(s) would be added."
|
|
: "{$total} line(s) added — move them into the right section and commit core.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* One lines() entry in the seeders' own style: single line when short,
|
|
* split over several lines when long.
|
|
*/
|
|
private function entry(string $key, string $en, string $el): string
|
|
{
|
|
[$key, $en, $el] = array_map(fn (string $value) => var_export($value, true), [$key, $en, $el]);
|
|
|
|
$single = " {$key} => [{$en}, {$el}],\n";
|
|
|
|
if (mb_strlen($single) <= 120) {
|
|
return $single;
|
|
}
|
|
|
|
return " {$key} => [\n {$en},\n {$el},\n ],\n";
|
|
}
|
|
|
|
/**
|
|
* Inserts the entries right before the closing `];` of lines(), then
|
|
* lints the file and restores the original if the result doesn't parse.
|
|
*/
|
|
private function append(string $file, string $entries): bool
|
|
{
|
|
$original = file_get_contents($file);
|
|
$method = strpos($original, 'function lines(): array');
|
|
$close = $method === false ? false : strpos($original, "\n ];\n }", $method);
|
|
|
|
if ($close === false) {
|
|
$this->error("Couldn't find the end of lines() in {$file} — add these by hand.");
|
|
|
|
return false;
|
|
}
|
|
|
|
$before = rtrim(substr($original, 0, $close));
|
|
|
|
// The last existing entry doesn't always have a trailing comma.
|
|
if (! str_ends_with($before, ',') && ! str_ends_with($before, '[')) {
|
|
$before .= ',';
|
|
}
|
|
|
|
$updated = $before
|
|
."\n\n // ── Pulled from the database (boboko:translations:pull) — move into the right section ──\n"
|
|
.$entries
|
|
.substr($original, $close + 1);
|
|
|
|
file_put_contents($file, $updated);
|
|
|
|
exec(PHP_BINARY.' -l '.escapeshellarg($file).' 2>&1', $output, $exitCode);
|
|
|
|
if ($exitCode !== 0) {
|
|
file_put_contents($file, $original);
|
|
$this->error("Writing {$file} produced invalid PHP — restored the original:");
|
|
$this->line(implode("\n", $output));
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|