Fix: Updating MIgrateImportCommand to accept language for import

This commit is contained in:
2026-09-24 21:41:11 +03:00
parent c7035d6782
commit b4e9b8a4a9
9 changed files with 140 additions and 35 deletions
-13
View File
@@ -1,13 +0,0 @@
<?php
namespace Modules\Core\MigrateImport;
use Lunar\Models\Language;
class DefaultLocale
{
public static function code(): string
{
return Language::getDefault()->code;
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Modules\Core\MigrateImport;
use Lunar\Models\Language;
/**
* The language every resolver in an import run writes product-facing text
* under (name, description, option names, ...) — NOT necessarily this
* store's own Lunar\Models\Language::getDefault(). An export file has no
* reliable way to declare its own language, and a store's default admin/
* storefront language is an independent fact from whatever language a
* given export happens to be written in — conflating the two (this
* class's own former name, DefaultLocale, said as much) used to silently
* save every imported product's text under the wrong language, invisible
* unless that language was also the one selected while viewing/editing
* the product afterward.
*
* Set once per import run from the operator's own answer (see
* Command\MigrateImportCommand::askImportLocale(), threaded through
* ImportSpec::$locale) at the top of Importer::import() — every resolver
* downstream (ProductAttributeResolver, ProductOptionResolver,
* CollectionResolver, ImportAttributeResolver) calls code() exactly as
* before, unaware anything changed. Falls back to Language::getDefault()
* only when nothing was ever set (e.g. an import path with no locale
* concept of its own — JudgeMe's reviews-only export never calls set()).
*/
class ImportLocale
{
private static ?string $code = null;
public static function set(string $code): void
{
self::$code = $code;
}
public static function code(): string
{
return self::$code ?? Language::getDefault()->code;
}
/**
* A static property outlives a single request only inside a
* long-running worker process, where a queued job for one import
* must not leak its locale into the next — called at the end of
* every Importer::import() run, success or failure.
*/
public static function reset(): void
{
self::$code = null;
}
}
+11
View File
@@ -4,11 +4,22 @@ namespace Modules\Core\MigrateImport;
class ImportSpec
{
/**
* @param ?string $locale The language the export file's own text
* (product titles, descriptions, option names, ...) is actually
* written in — asked of the operator at import time (see
* Command\MigrateImportCommand), since an export has no reliable
* way to declare its own language and it does not necessarily match
* this store's Lunar\Models\Language::getDefault(). Null for a
* source/type this doesn't apply to (e.g. an API-based import with
* no free-text file to attribute a single language to).
*/
public function __construct(
public readonly string $source,
public readonly string $type,
public readonly ?string $filePath = null,
public readonly ?array $credentials = null,
public readonly ?string $locale = null,
) {
}
}
@@ -7,7 +7,7 @@ use Lunar\FieldTypes\TranslatedText;
use Lunar\Models\Collection;
use Lunar\Models\CollectionGroup;
use Lunar\Models\Product;
use Modules\Core\MigrateImport\DefaultLocale;
use Modules\Core\MigrateImport\ImportLocale;
class CollectionResolver
{
@@ -45,7 +45,7 @@ class CollectionResolver
'collection_group_id' => $group->id,
'attribute_data' => [
'name' => new TranslatedText(collect([
DefaultLocale::code() => new Text($name),
ImportLocale::code() => new Text($name),
])),
],
]);
@@ -8,7 +8,7 @@ use Lunar\Models\Attribute;
use Lunar\Models\AttributeGroup;
use Lunar\Models\Product;
use Lunar\Models\ProductType;
use Modules\Core\MigrateImport\DefaultLocale;
use Modules\Core\MigrateImport\ImportLocale;
class ImportAttributeResolver
{
@@ -40,7 +40,7 @@ class ImportAttributeResolver
[
'attribute_group_id' => $group->id,
'position' => $nextPosition++,
'name' => [DefaultLocale::code() => $definition['label']],
'name' => [ImportLocale::code() => $definition['label']],
'section' => 'main',
'type' => $definition['type'],
'required' => false,
@@ -49,7 +49,7 @@ class ImportAttributeResolver
? ['richtext' => false]
: [],
'system' => false,
'description' => [DefaultLocale::code() => ''],
'description' => [ImportLocale::code() => ''],
],
);
@@ -62,7 +62,7 @@ class ImportAttributeResolver
return AttributeGroup::firstOrCreate(
['attributable_type' => Product::morphName(), 'handle' => 'import'],
[
'name' => [DefaultLocale::code() => 'Additional Details'],
'name' => [ImportLocale::code() => 'Additional Details'],
'position' => 100,
],
);
@@ -6,7 +6,7 @@ use Lunar\FieldTypes\Number;
use Lunar\FieldTypes\Text;
use Lunar\FieldTypes\TranslatedText;
use Lunar\Models\ProductType;
use Modules\Core\MigrateImport\DefaultLocale;
use Modules\Core\MigrateImport\ImportLocale;
class ProductAttributeResolver
{
@@ -43,7 +43,7 @@ class ProductAttributeResolver
}
return new TranslatedText(collect([
DefaultLocale::code() => new Text($value),
ImportLocale::code() => new Text($value),
]));
}
}
@@ -5,7 +5,7 @@ namespace Modules\Core\MigrateImport\Shopify\Resolvers;
use Illuminate\Support\Str;
use Lunar\Models\ProductOption;
use Lunar\Models\ProductOptionValue;
use Modules\Core\MigrateImport\DefaultLocale;
use Modules\Core\MigrateImport\ImportLocale;
class ProductOptionResolver
{
@@ -24,8 +24,8 @@ class ProductOptionResolver
return ProductOption::query()->firstOrCreate(
['handle' => $handle],
[
'name' => [DefaultLocale::code() => $name],
'label' => [DefaultLocale::code() => $name],
'name' => [ImportLocale::code() => $name],
'label' => [ImportLocale::code() => $name],
'shared' => true,
],
);
@@ -46,7 +46,7 @@ class ProductOptionResolver
return $existing ?? ProductOptionValue::create([
'product_option_id' => $option->id,
'name' => [DefaultLocale::code() => $value],
'name' => [ImportLocale::code() => $value],
]);
}
}
@@ -12,6 +12,7 @@ use Lunar\Models\Language;
use Lunar\Models\Product;
use Lunar\Models\ProductVariant;
use Lunar\Models\Url;
use Modules\Core\MigrateImport\ImportLocale;
use Modules\Core\MigrateImport\ImportSpec;
use Modules\Core\MigrateImport\Importer;
use Modules\Core\MigrateImport\Models\ImportMapping;
@@ -48,16 +49,26 @@ class ShopifyExportImporter implements Importer
public function import(ImportSpec $spec): void
{
$groups = $this->csvReader->read($spec->filePath);
$imagesPath = dirname($spec->filePath).'/files';
$collectionGroup = CollectionGroup::firstOrCreate(
['handle' => 'shopify'],
['name' => 'Shopify'],
);
$currency = Currency::getDefault();
if ($spec->locale !== null) {
ImportLocale::set($spec->locale);
}
foreach ($groups as $group) {
$this->importProduct($group, $imagesPath, $collectionGroup, $currency);
try {
$groups = $this->csvReader->read($spec->filePath);
$imagesPath = dirname($spec->filePath).'/files';
$collectionGroup = CollectionGroup::firstOrCreate(
['handle' => 'shopify'],
['name' => 'Shopify'],
);
$currency = Currency::getDefault();
foreach ($groups as $group) {
$this->importProduct($group, $imagesPath, $collectionGroup, $currency);
}
} finally {
// A queue worker process outlives a single import run — this
// must not leak into whatever's imported next.
ImportLocale::reset();
}
}
@@ -100,7 +111,12 @@ class ShopifyExportImporter implements Importer
[
'element_type' => $product->getMorphClass(),
'element_id' => $product->id,
'language_id' => Language::getDefault()->id,
// ImportLocale, not Language::getDefault() — same
// reasoning as ProductAttributeResolver et al.: this
// product's handle/slug came from the export, written in
// whatever language the operator said the file is in,
// not necessarily this store's own default language.
'language_id' => Language::where('code', ImportLocale::code())->value('id'),
],
[
'slug' => $group->handle,