Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getLocalizedURL does not translate slug #718

Open
nlucia opened this issue Apr 15, 2020 · 7 comments
Open

getLocalizedURL does not translate slug #718

nlucia opened this issue Apr 15, 2020 · 7 comments
Assignees
Labels

Comments

@nlucia
Copy link

nlucia commented Apr 15, 2020

When i try to get the localized URL of a model with translated route and slug, it returns the URL with the same slug (non localized), example:
LaravelLocalization::getLocalizedURL('en', 'aparato/caldera', [], true);
returns
http://site.test/en/device_type/caldera
instead of
http://site.test/en/device_type/boiler

I have the translated routes as follows:
in resources/lang/en/routes.php:
"device_type" => "device_type/{deviceType}",
in resources/lang/es/routes.php:
"device_type" => "aparato/{deviceType}",
and my routes are defined like this:

Route::prefix(LaravelLocalization::setLocale())
        ->middleware('localize', 'localeSessionRedirect', 'localizationRedirect')
        ->group( function ()
{
      ...
        Route::get(LaravelLocalization::transRoute('routes.device_type'), 'HomeController@deviceType')->name('device_type');
      ...
}

I use route model binding and implement LocalizedUrlRoutable, and have implemented
resolveRouteBinding and getLocalizedRouteKey in my model so that they return what i think they should, tested in tinker:

>>> $dt->resolveRouteBinding('boiler')->getLocalizedRouteKey('es')
=> "caldera"
>>> $dt->resolveRouteBinding('boiler')->getLocalizedRouteKey('en')
=> "boiler"
>>> $dt->resolveRouteBinding('caldera')->getLocalizedRouteKey('es')
=> "caldera"
>>> $dt->resolveRouteBinding('caldera')->getLocalizedRouteKey('en')
=> "boiler"

so when i try to get the localized url, i get the following:

>>> LaravelLocalization::getLocalizedURL('en', 'aparato/caldera', [], true);
=> "http://site.test/en/device_type/caldera"
>>> LaravelLocalization::getLocalizedURL('en', '/es/aparato/caldera', [], true);
=> "http://site.test/en/device_type/caldera"

when I'd expect to get "http://site.test/en/device_type/boiler"

I'm using laravel v. 7.6.1 and laravel-localization v. 1.5.0.
Using middleware localize, localeSessionRedirect and localizationRedirect in my routes.
Config options:

'supportedLocales' => [ 'es' => .... , 'en' => ... ],
'useAcceptLanguageHeader' => true,
'hideDefaultLocaleInURL' => true,
'localesOrder' => [],
'localesMapping' => [],

Is this the expected behaviour? Any idea why it does not translate the slug?
Thank you.

@iwasherefirst2
Copy link
Collaborator

Did you follow all steps mentioned in the video tutorial https://www.youtube.com/watch?v=B1AUqCdizgc&feature=youtu.be ?

@nlucia
Copy link
Author

nlucia commented Apr 24, 2020

Yes, I think so. In the video it shows only getting an object by any slug, and that works ok. The problem I have is related to URL translation.
I noticed that it works ok if I call 'getLocalizedURL' from a view, but shows the problem described in the issue when called from tinker or from a controller. While debugging i noticed that (when called from a controller) the route parameter is a string ('caldera' to follow my example) instead of the model instance, so when it goes to

if ($value instanceOf Interfaces\LocalizedUrlRoutable) {
that expression returns false and the translated slug can not be retrieved.
When called from a view the parameter is a model instance, so 'getLocalizedRouteKey' is called on the model, returning the correct slug.
I don't know enough of the inner workings of Laravelor or how the route parameters work, maybe it can only work after a response is sent ,but not on the request. I did not see anything related to this in the documentation, so I just expected it to work anywhere.
Thank you for your time.

@rsmondejar
Copy link

If you are not using it yet, try Route Model Binding.
Route Model Binding

I had the same issue and it was resolved when I changed my routes.php and my controller method.

If you use the routes you mentioned, it could work if you change your controller method, for example:
function xxxx(DeviceType $deviceType) {...}

@cdanielzt
Copy link

I have the same issue, did you fix it?

@adrinro
Copy link

adrinro commented Dec 17, 2024

I am experiencing the same issue, and after inspecting the package, I see that it can be resolved using route model binding as mentioned above. However, if you don't use it, the error persists, and I prefer not to use route model binding. Does anyone know how to solve this without using route model binding?

While looking into the code in the file LaravelLocalization.php, I found that the function getURLFromRouteNameTranslated()—which is discussed in this thread—uses another function for slug translations, namely substituteAttributesInRoute(). It seems to work with route model binding, but without it, how would an example look?

<?php
/**
 * Change route attributes for the ones in the $attributes array.
 *
 * @param array  $attributes Array of attributes
 * @param string $route      Route to substitute
 * @param string $locale     Locale code
 *
 * @return string Route with attributes changed
 */
protected function substituteAttributesInRoute($attributes, $route, $locale = null)
{
    foreach ($attributes as $key => $value) {
        if ($value instanceof Interfaces\LocalizedUrlRoutable) {
            $value = $value->getLocalizedRouteKey($locale);
        } elseif ($value instanceof UrlRoutable) {
            $value = $value->getRouteKey();
        }
        $route = str_replace(['{' . $key . '}', '{' . $key . '?}'], $value, $route);
    }

    // Delete empty optional arguments that are not in the $attributes array
    $route = preg_replace('/\/{[^)]+\?}/', '', $route);

    return $route;
}

@iwasherefirst2
Copy link
Collaborator

iwasherefirst2 commented Dec 17, 2024

I’ve been able to reproduce the issue now. Unfortunately, I didn’t write getLocalizedURL, and over the years, the function has grown complex and hard to fully understand. I’ve personally never used this function since a simple route(...) call has always been sufficient for my needs.

That said, I’m assigning this ticket to myself and will take a closer look.

By the way, @rsmondejar, could you clarify how using Route Model Binding in the controller affects the outcome of getLocalizedURL?"

@aurawindsurfing
Copy link

I have this solution that works for the moment:

I use as well spatie/laravel-translatable to store slug transations.

Here is temporary solution for my Category model slug.

Category Model - nothing special, just getRouteKeyName to always resolve model by slug and not by id.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Spatie\Sluggable\HasTranslatableSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;

class Category extends Model
{

    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::createWithLocales(collect(config(LaravelLocalization::getSupportedLocales()))->keys()->toArray())
            ->generateSlugsFrom(function ($model, $locale) {
                return "{$locale} {$model->parent?->name} {$model->name}";
            })
            ->saveSlugsTo('slug');
    }

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

}

web.php - also added localized Livewire route since it is needed for livewire to work.

Route::group([
    'prefix' => LaravelLocalization::setLocale(),
    'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath', 'localize' ]
], function(){
    Route::get(LaravelLocalization::transRoute('routes.category'), HomePage::class)->name('category');

    Livewire::setUpdateRoute(function ($handle) {
        return Route::post('/livewire/update', $handle);
    });

});

en/routes.php - again nothing special here.

return [
    "category"          => "category/{slug}",
];

pl/routes.php

return [
    "category"          => "kategoria/{slug}",
];

navigation links - menu links generated from categories

            <flux:navlist.group expandable :expanded="(bool)$category->expanded" heading="{{$category->name}}" class="lg:grid">
                @foreach($category->children as $child)
                    <flux:navlist.item
                        href="{{LaravelLocalization::getURLFromRouteNameTranslated(App::currentLocale(), 'routes.category', ['slug' => $child->slug]) }}"
                        icon="{{$child->icon}}">{{$child->name}}
                    </flux:navlist.item>
                @endforeach
            </flux:navlist.group>

And finally language switcher livewire component:

<div>
    <flux:radio.group variant="segmented">
        @foreach($routes as $route)
            <a rel="alternate" hreflang="{{ $route['code'] }}" href="{{ $route['url'] }}">
                <flux:radio label="{{ $route['code'] }}"/>
            </a>
        @endforeach
    </flux:radio.group>
</div>

LanguageSwitcher.php

That the important bit: php $this->currentRouteName === 'category' Other then that it just returns set of routes to populate language switcher for current page.

<?php

namespace App\Livewire;

use App\Models\Category;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Livewire\Component;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class LanguageSwitcher extends Component
{
    public $currentRouteName;

    public $currentRouteParameters;

    public $locales;

    public $routes = [];

    public function mount(): void
    {
        $this->currentRouteName = Route::getCurrentRoute()->getName();
        $this->currentRouteParameters = Route::getCurrentRoute()->parameters();
        $this->locales = LaravelLocalization::getSupportedLocales();

        foreach($this->locales as $localeCode => $properties){
            $this->routes[$localeCode] = [
                'code' => $localeCode,
                'url' => LaravelLocalization::getLocalizedURL($localeCode, null, $this->currentRouteParameters, true),
            ];
        }

        if ($this->currentRouteName === 'category') {
            $this->routes = [];
            $category = Category::whereJsonContainsLocale('slug', App::currentLocale(), $this->currentRouteParameters['slug'])->first();
            $slugs = $category->getTranslations("slug");

            foreach($this->locales as $localeCode => $properties){
                $this->routes[$localeCode] = [
                    'code' => $localeCode,
                    'url' => LaravelLocalization::getLocalizedURL($localeCode, null, ['slug' => $slugs[$localeCode]], true),
                ];
            }
        }

    }

    public function render()
    {
        return view('livewire.language-switcher');
    }
}

Hope this helps someone. Obviously you would need to handle the exception for any model that uses translated slugs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants