laravel-localization icon indicating copy to clipboard operation
laravel-localization copied to clipboard

Support for Laravel Octane

Open ju5t opened this issue 3 years ago • 18 comments

Laravel has recently released a beta version of Laravel Octane.

Because a lot of things are cached, the list of routes doesn't update between requests. This means all prefixed routes do not exist, as Octane didn't make them available. Although Octane is a beta, this caching is unlikely to change.

It would be awesome if Octane support can be added.

edit: See https://github.com/laravel/octane/issues/113 for a workaround/solution. For me this still caused some translation issues, which I will try to demonstrate in more detail later, unless someone beats me to it :)

ju5t avatar Apr 06 '21 20:04 ju5t

@ju5t which kind of translation issues do you get? Just beginning to use laravel-localization for a side project

pmochine avatar Jun 26 '21 16:06 pmochine

@pmochine most details are in https://github.com/laravel/octane/issues/113.

We ended up writing our own simplified localisation support. This was much easier for us. It isn't something we can share at this point in time, as it's not packaged up nicely.

We haven't moved onto Octane though. We're using Forge and you can't switch; you have to migrate.

ju5t avatar Jun 26 '21 21:06 ju5t

can't seem to get this to work on octane, tried what is suggested here https://github.com/laravel/octane/issues/113. No luck

abishekrsrikaanth avatar Aug 28 '21 00:08 abishekrsrikaanth

Did someone managed to resolve this guys ??

omarherri avatar Sep 03 '21 22:09 omarherri

Hello, I have my own package very similar to this one, and I adapted it to laravel octane, therefore, adapting this package to octane has not been very difficult, here I tell you how to make it work in octane. Routes are always required to be cached.

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Mcamara\LaravelLocalization\LaravelLocalization;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        //Remove singleton
        $this->app->offsetUnset(LaravelLocalization::class);

        //Add bind (Necessary for each cycle to restart)
        $this->app->bind(LaravelLocalization::class, function () {
            return new LaravelLocalization();
        });
    }
}
namespace App\Listeners;

use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class ReloadRoutes
{
    private static $_last_locale = null;
    /**
     * Handle the event.
     *
     * @param  mixed  $event
     * @return void
     */
    public function handle($event): void
    {
        $locale = LaravelLocalization::setLocale();

        $path = $this->makeLocaleRoutesPath($event->sandbox, $locale);
        if (self::$_last_locale != $locale && file_exists($path) && is_file($path)) {
            self::$_last_locale = $locale;
            include $path;
        }
    }

    /**
     * @param Application $app
     * @param string $locale
     * @return string
     */
    protected function makeLocaleRoutesPath($app, $locale = '')
    {
        $path = $app->getCachedRoutesPath();

        if (!$locale) {
            return $path;
        }

        return substr($path, 0, -4) . '_' . $locale . '.php';
    }
}

In config/octane.php add:

...
'listeners' => [
        ...
        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            App\Listeners\ReloadRoutes::class,
        ],
        ...
],
...

With this it should work, but I have to do more tests.

mcolominas avatar Nov 29 '21 19:11 mcolominas

Hi gents,

I've also managed to partially make it work with Octane. My way is as per described below:

Since the service provider registers its facade as a singleton we need to switch it to a way proposed by Octane's documentation:

What I did?

  1. disabled an auto-discover for the package in composer.json:
    "extra": {
        "laravel": {
            "dont-discover": [
                "mcamara/laravel-localization"
            ]
        }
    },

  1. created my own service provider for that (to have a better control over this as I'm still didn't make it work 100%)
namespace App\Providers;

use Mcamara\LaravelLocalization\LaravelLocalization;
use Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider;

class LocalizationServiceProvider extends LaravelLocalizationServiceProvider
{
    protected function registerBindings()
    {
        $this->app->bind(LaravelLocalization::class, fn () => new LaravelLocalization());
        $this->app->alias(LaravelLocalization::class, 'laravellocalization');
    }
}
  1. registered it in the appropriate config in app.php
        /*
         * Application Service Providers...
         */
        App\Providers\LocalizationServiceProvider::class,
        ...
        App\Providers\AppServiceProvider::class,
  1. manually registered there as well the facade as its often referenced in blade files
    'aliases' => [
        ...
        'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class,
    ]

jangaraev avatar Mar 17 '22 05:03 jangaraev

I'm still struggling with routes and the homepage without locale chunk doesn't work even with the solution of foreach (LaravelLocalization::getSupportedLocales() ...

Will keep you guys updated if I achieve any meaningful results.

jangaraev avatar Mar 17 '22 05:03 jangaraev

Yeah guys, I've managed to make it work completely!

Once setup Laravel Octane via Roadrunner, I discovered several issues with this package:

  • locale from URL isn't recognized
  • homepage doesn't open when there is no locale chunk
  • links get generated using another locale
  • translatable routes are not recognized and built improperly

Here is my solution, it fixes all the issues above:

  1. Add a listener to hook into Octane's RequestReceived event (thanks to @mcolominas):
namespace App\Listeners;

use Illuminate\Foundation\Application;
use Laravel\Octane\Events\RequestReceived;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class LoadLocalizedRoutesCache
{
    private static $lastLocale;


    public function handle(RequestReceived $event): void
    {
        // passing request segment is crucial because the package doesn't
        // know the current locale as it was instantiated in service provider

        // there is also an option to don't pass the request segment in case
        // you don't use translatable routes (transRoute() in web.php) in your project
        // in this case the package will correctly resolve the locale and you
        // don't need to pass the 3rd param when binding in service provider
        $locale = LaravelLocalization::setLocale($event->request->segment(1));

        $path = $this->makeLocaleRoutesPath($event->sandbox, $locale);

        if (self::$lastLocale != $locale && is_file($path)) {
            self::$lastLocale = $locale;
            include $path;
        }
    }

    protected function makeLocaleRoutesPath(Application $app, $locale = ''): string
    {
        $path = $app->getCachedRoutesPath();

        if (!$locale) {
            return $path;
        }

        return substr($path, 0, -4) . '_' . $locale . '.php';
    }
}
  1. Reference the listener in Octane's config file:
    'listeners' => [
        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            \App\Listeners\LoadLocalizedRoutesCache::class
        ],
  1. disabled an auto-discover for the package in composer.json:
    "extra": {
        "laravel": {
            "dont-discover": [
                "mcamara/laravel-localization"
            ]
        }
    },
  1. created my own service provider instead of disabled native one in composer.json:
namespace App\Providers;

use Mcamara\LaravelLocalization\LaravelLocalization;
use Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider;

class LocalizationServiceProvider extends LaravelLocalizationServiceProvider
{
    protected function registerBindings()
    {
        $fn = fn () => new LaravelLocalization();

        // the conditional check below is important
        // when you do caching routes via `php artisan route:trans:cache` if binding
        // via `bind` used you will get incorrect serialized translated routes in cache
        // files and that's why you'll get broken translatable route URLs in UI

        // again, if you don't use translatable routes, you may get rid of this check
        // and leave only 'bind()' here

        // the 3rd parameter is important to be passed to 'bind'
        // otherwise the package's instance will be instantiated every time
        // you reference it and it won't get proper data for 'serialized translatable routes'
        // class variable, this will make impossible to use translatable routes properly
        // but oveall the package will still work stable except generating the same URLs
        // for translatable routes independently of locale

        if ($this->runningInOctane()) {
            $this->app->bind(LaravelLocalization::class, $fn, true);
        } else {
            $this->app->singleton(LaravelLocalization::class, $fn);
        }

        $this->app->alias(LaravelLocalization::class, 'laravellocalization');
    }

    private function runningInOctane(): bool
    {
        return !$this->app->runningInConsole() && env('LARAVEL_OCTANE');
    }
}
  1. registered it in the appropriate config in app.php
        /*
         * Application Service Providers...
         */
        App\Providers\LocalizationServiceProvider::class,
        ...
        App\Providers\AppServiceProvider::class,
  1. manually registered there as well the facade as its often referenced in blade files:
    'aliases' => [
        ...
        'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class,
    ]

My project is a typical Laravel project:

  • Laravel 8.83
  • Laravel Nova 3
  • LaravelLocalization 1.7
  • Blade + a bit of VueJS on frontend

We use several translatable routes in the project. Most of problems get from there. Basically adapting the package appeared not so hard, but making translatable routes work made me a bit nervous :-)

Notes:

  1. You don't need to touch your routes at all, no need to foreach (...) over locales as described in posts above.
  2. It's important to use routes cache there (php artisan route:trans:cache)

jangaraev avatar Mar 18 '22 07:03 jangaraev

@mcamara , can I prepare a PR with those changes?

jangaraev avatar Mar 18 '22 07:03 jangaraev

@Jangaraev please go ahead, we will really appreciate it

mcamara avatar Mar 18 '22 10:03 mcamara

can I prepare a PR with those changes?

@jangaraev Are you still planning to make a PR?

taai avatar Jun 02 '22 11:06 taai

@jangaraev you've saved my day dude. Thank you so much for the great solution :)

ilyasozkurt avatar Jun 19 '22 12:06 ilyasozkurt

@jangaraev Are you still planning to make a PR?

sorry, was sick for a long time. yep, in a week will try to propose something there.

jangaraev avatar Jun 20 '22 06:06 jangaraev

@jangaraev please use markdown correctly for highlight ```php, thanks

parallels999 avatar Mar 14 '23 16:03 parallels999

Is anybody using one of the workarounds described in this issue and everything still works? I just tested both and I had problems with all of them. Just wondering if I did something wrong or if this workaround just don't work anymore.

korridor avatar Aug 21 '23 09:08 korridor