laravel-localized-routes icon indicating copy to clipboard operation
laravel-localized-routes copied to clipboard

Astrotomic/laravel-translatable Support

Open sheinfeld opened this issue 4 years ago • 7 comments

Hey guys,

Apologies for this, but after a few hours I can't manage to route bind models that use this package translatable trait.

My goal here is to make the urls dynamic such as:

/en/article/slug-in-english /pt/artigo/slug-in-portuguese

Using this package alongside => https://github.com/Astrotomic/laravel-translatable

In routes.php I have used:

Route::get(Lang::uri('article').'/{article:slug}', 'ArticleController@show')->name('article.show');

Then, in my language switcher dropdown:

\Route::localizedUrl($lang)

The problem so far, is that the dropdown only displays the slugs in english. It changes the 2 char locale ("en" => "pt") but not the slug itself.

Article Model:

<?php
/*
 * Copyright (c) 2021 Ceuton.
 */

namespace App\Models;

use App\Http\Controllers\Controller;
use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
use Astrotomic\Translatable\Translatable;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Str;
use Throwable;

class Article extends Model implements TranslatableContract
{
    use Translatable;

    const STATUS_UNPUBLISHED = 0;
    const STATUS_PUBLISHED = 1;
    const STATUS_DRAFT = 2;
    const STATUS_SCHEDULED = 3;

    protected $fillable = [
        'featured_img',
        'scheduled_at',
        'status',
        'created_at',
        'updated_at'
    ];

    public array $translatedAttributes = [
        'slug',
        'title',
        'body'
    ];

    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }

    public function keywords(): BelongsToMany
    {
        return $this->belongsToMany(Keyword::class);
    }

    public function medias(): MorphToMany
    {
        return $this->morphToMany(Media::class, 'mediable');
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function getRouteKeyName(): string
    {
        return 'slug';
    }

    public function getRouteKey($locale = null)
    {
        try {
            $slugs = [];

            foreach (config('constants.locales') as $key => $name)
                if($this->hasTranslation($key))
                    $slugs[$key] = $this->translate($key)->slug;

            if($locale)
                return $slugs[$locale];

            return $slugs[app()->getLocale()];

        } catch (Throwable $exception) {
            return $this->slug;
        }
    }

    public function getRoute(): string
    {
        try {
            return route('article.show', [$this->getRouteKey()], true, app()->getLocale());

        } catch (Throwable $exception) {
            return "#";
        }
    }

    public function getTitle($size = null, $locale = null)
    {
        try {
            if(!$locale)
                $locale = app()->getLocale();

            if ($size)
                return Str::limit($this->{'title:' . $locale}, $size);

            return $this->{'title:' . $locale};

        } catch (Throwable $exception) {
            return "---";
        }
    }

    public function getExcerpt($size = null): string
    {
        try {
            if ($size)
                return Str::limit(strip_tags($this->{'body:' . app()->getLocale()}), $size);

            return strip_tags($this->{'body:' . app()->getLocale()});

        } catch (Throwable $exception) {
            return "---";
        }
    }

    public function getBody($size = null): string
    {
        try {
            if ($size)
                return Str::limit($this->{'body:' . app()->getLocale()}, $size);

            return $this->{'body:' . app()->getLocale()};

        } catch (Throwable $exception) {
            return "---";
        }
    }

    public function getAdvertisedBody(): string
    {
        try {
            return (new Controller())->injectAdv($this->getBody());

        } catch (Throwable $exception) {
            return "---";
        }
    }

    public function getFeaturedImg(): string
    {
        try {
            return asset_cdn('storage/uploads/' . $this->medias()->firstOrFail()->name);
        } catch (Throwable $exception) {
            return asset_cdn('assets/img/featured.jpg');
        }
    }

    public function getFeaturedImgCredits()
    {
        try {
            return $this->medias()->firstOrFail()->credits;

        } catch (Throwable $exception) {
            return null;
        }
    }

    public function getMainCategory($attribute = "slug")
    {
        try {
            return $this->categories()->firstOrFail()->$attribute;

        } catch (Throwable $exception) {
            return "other";
        }
    }

    public function getKeywords()
    {
        try {
            $keywords = collect([]);
            $keywords->merge($this->categories()->pluck('name'));
            $keywords->merge($this->tags()->pluck('name'));

            return $keywords->toArray();

        } catch (Throwable $exception) {
            return collect([]);
        }
    }

    public function getStatusElements()
    {
        switch ($this->status) {
            case Article::STATUS_UNPUBLISHED:
                return '<span class="badge badge-danger">Lixo</span>';
            case Article::STATUS_PUBLISHED:
                return '<span class="badge badge-success">Publicado</span>';
            case Article::STATUS_SCHEDULED:
                return '<span class="badge badge-primary">Agendado</span>';
            case Article::STATUS_DRAFT:
                return '<span class="badge badge-warning">Rascunho</span>';
        }
    }

    public function resolveRouteBinding($value, $field = null)
    {
        return $this->translations()->where($field ?? $this->getRouteKeyName().'->'.app()->getLocale(), $value)->firstOrFail();
    }
}

ArticleTranslation:

<?php
/*
 * Copyright (c) 2021 Ceuton.
 */

namespace App\Models;

use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ArticleTranslation extends Model
{
    use Sluggable;

    public $timestamps = false;

    protected $fillable = [
        'locale',
        'slug',
        'title',
        'body',
    ];

    public function article(): BelongsTo
    {
        return $this->belongsTo(Article::class, 'article_id', 'id');
    }

    public function sluggable(): array
    {
        return [
            'slug' => [
                'source' => 'title'
            ]
        ];
    }
}

sheinfeld avatar Jun 26 '21 16:06 sheinfeld

At first glance, your code looks fine...

When you call...

\Route::localizedUrl($lang)

...your end result is...

/en/article/slug-in-english
/pt/artigo/slug-in-english

Right? Or is artigo not translated either?

I will try to reproduce this in a test.

ivanvermeyen avatar Jun 27 '21 11:06 ivanvermeyen

Hey @ivanvermeyen,

As far as I can see, the problem was with the method localizedUrl.

I googled the problem and found this method that fixed the problem for me in RouteServiceProvider:

Route::macro('currentLocalizedUrl', function ($locale = null, $parameters = null, $absolute = true) {
            $locale = $locale ?? app()->getLocale();
            $parameters = $parameters ?: Route::current()->parameters();
            $currentLocale = app()->getLocale();
            app()->setLocale($locale);
            foreach ($parameters as $attribute => $value) {
                if ($value instanceof Model) {
                    $parameters[$attribute] = $value->getRouteKey();
                }
            }
            app()->setLocale($currentLocale);

            return route(Route::current()->getName(), $parameters, $absolute, $locale);
        });

Let me know what you think :D

sheinfeld avatar Jun 27 '21 14:06 sheinfeld

That is basically what the included macro should do, but it was expanded to handle unnamed routes and 404 pages, which don't have a Route::current().

Are you running the latest version of this package?

ivanvermeyen avatar Jun 28 '21 08:06 ivanvermeyen

I am! That's why is wierd...

sheinfeld avatar Jun 28 '21 08:06 sheinfeld

I got the issue myself now, will start debugging it :)

ivanvermeyen avatar Jun 28 '21 08:06 ivanvermeyen

Cool @ivanvermeyen! I'll be waiting :D

sheinfeld avatar Jun 28 '21 08:06 sheinfeld

Turns out that I have a different problem. Because I use a route like posts/{id}/{slug?}, the id is using model binding but the slug is not, so it's just a string. I'll have to fix that...

But with normal routes like posts/{slug} it should just work, as long as you type hint it in the controller. The slugs are not cached in some way by the other package?

ivanvermeyen avatar Jun 28 '21 09:06 ivanvermeyen

Hi,

Sorry, seems I lost sight of this issue... Is this issue still relevant? Closing for now, but feel free to reopen!

ivanvermeyen avatar Apr 04 '23 17:04 ivanvermeyen