Astrotomic / laravel-translatable

A Laravel package for multilingual models
https://docs.astrotomic.info/laravel-translatable/
MIT License
1.25k stars 158 forks source link

translateOrNew not saving because of deleteTranslations #410

Closed shelleychristie closed 5 months ago

shelleychristie commented 6 months ago

This is the update method in my controller. The idea is that in my view I have a table, one row for each language. The user fills the table, and the translations will be updated accordingly (if they leave it empty, then no translation for that language.) For the request, I used form array syntax like so in my blade file, following the documentation. <input type="text" name="{{$locale}}[name]" />

        $subcat = ProductSubcategory::findOrFail($id);

        $subcat->product_category_id = $request->product_category_id;
        foreach ($helper->languageCodes() as $code) {
            if ($request->{$code}['name'] == null) {
                // if null, remove translation, but not if it's the fallback_locale
                if ($code != config('translatable.fallback_locale')) {
                    $subcat->deleteTranslations($code);
                }
            } else {
                $subcat->translateOrNew($code)->name = $request->{$code}['name'];
                if ($code == config('translatable.fallback_locale')) {
                    // this is filling in another field on the main table
                    $subcat->product_subcategory_name = $request->{$code}['name'];
                }
            }
            // $subcat->save();
            // if I save here, it works. But I wouldn't want to save on loop.
        }

        $subcat->save();

When I save my subcategory model, the translations don't get updated, but the $subcat->product_subcategory_name is saved. If I do $subcat->save() inside the foreach loop, it works, but I don't think that's the intended method. Any help is appreciated, thank you.

Other notes:

Oleksandr-Moik commented 6 months ago

Try to use $subcat->push() after loop

https://laravel.com/docs/11.x/eloquent-relationships#the-push-method

shelleychristie commented 6 months ago

Thanks for the reply. I've tried using push() in place of save() but it doesn't seem to be making a difference. Interestingly, if I dd($subcat) right before push(), it shows that the translations relation is not updated (new translations aren't there, no updates). Another note, when I comment out the part with deleteTranslations(), push works. Not sure why, does deleteTranslations somehow affect the ability to save?

shelleychristie commented 6 months ago

I think I may have figured out what went wrong. In the package's translatable trait, the deleteTranslations method reloads the translations from DB whenever it is called.

Astrotomic\Translatable;

public function deleteTranslations($locales = null): void
    {
        if ($locales === null) {
            $translations = $this->translations()->get();
        } else {
            $locales = (array) $locales;
            $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get();
        }

        $translations->each->delete();

        // we need to manually "reload" the collection built from the relationship
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
        $this->load('translations');
    }

So even though I already made changes to the translations through $subcat, because the translations are reloaded from DB the model loses all of the changes by the time it reaches save() or push().

To work around this, I have made a custom trait that extends Translatable and added a deleteTranslationsWithoutReloading method that is a copy of the deleteTranslations method but without reloading the translations. I then replaced all the models that used the Translatable trait and replaced it with my CustomTranslatable.

trait CustomTranslatable
{
    use Translatable;

    // vendor's deleteTranslations always reloads the collection from DB, preventing us from updating translations on the same model.
    public function deleteTranslationsWithoutReloading($locales = null): void
    {
        if ($locales === null) {
            $translations = $this->translations()->get();
        } else {
            $locales = (array) $locales;
            $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get();
        }

        $translations->each->delete();

        // we need to manually "reload" the collection built from the relationship
        // otherwise $this->translations()->get() would NOT be the same as $this->translations

        // reload commented. see Astrotomic\Translatable\Translatable;
        // $this->load('translations');
    }
}

I replaced the deleteTranslations() with deleteTranslationWithoutReloading() in the controller and can now use $subcat->save() right after the loop and it works as expected. If there are better ways to do this, I'd appreciate the input. Thanks!