Skip to content

Commit eede461

Browse files
author
AlexVakhovski
committed
Fix paginated routes, enhance test coverage
1 parent 81b3ab9 commit eede461

File tree

5 files changed

+102
-37
lines changed

5 files changed

+102
-37
lines changed

README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ Route::localizedRoutes(function () {
6565
}, ['web']);
6666
```
6767

68+
### Checking if Route is Already Localized
69+
You can easily check if your route name is already localized before generating or using it.
70+
```php
71+
@if (Route::isLocalized($name))
72+
// It returns TRUE if The route name is already localized (e.g., "en.blog.index")
73+
@endif
74+
```
75+
6876
This automatically generates localized routes like:
6977

7078
- `/en/about`
@@ -87,7 +95,7 @@ Or generate URLs with parameters:
8795
<a href="{{ Route::localize('blog.show', ['post' => $post]) }}">View Post</a>
8896
```
8997

90-
### Middleware: `localize.setLocale`
98+
### Middleware: `SetLocaleMiddleware`
9199
Middleware to automatically detect and set the application locale based on the first segment of the URL.
92100

93101
Registered alias as `localize.setLocale`
@@ -132,7 +140,17 @@ Route::localizedRoutes(function () {
132140
```
133141
This is a simpler and more compact approach, especially useful for smaller projects.
134142

135-
### Pagination-Aware Route Generation
143+
### Middleware: `PaginatedMiddleware`
144+
Middleware that transforms paginated URLs by cleaning query parameters.
145+
146+
Registered alias as `localize.paginated`.
147+
148+
When a paginated route like `/page/{page?}` is used:
149+
150+
* It ensures that the first page `blog/page/1` does not have a query string or URL segment like `/page/1`.
151+
* Instead, the first page URL is clean, such as `/blog`, improving SEO and URL readability.
152+
* If the user accesses `blog/page/2`, `blog/page/3`, etc., the page number remains in the URL.
153+
136154
When building localized URLs with pagination, you can define your routes like this:
137155
```php
138156
use Illuminate\Support\Facades\Route;
@@ -143,7 +161,7 @@ Route::localizedRoutes(function () {
143161
Route::get('/page/{page?}', [BlogController::class, 'index'])->name('paginated');
144162
Route::get('/{post}', [BlogController::class, 'show'])->name('show');
145163
});
146-
}, ['web', 'localize.setLocale']);
164+
}, ['web', 'localize.setLocale', 'localize.paginated']);
147165

148166
```
149167

@@ -202,7 +220,7 @@ Your custom provider should implement:
202220
interface LanguageProviderInterface
203221
{
204222
public function getLanguages(): array;
205-
public function getLocaleBySegment(string $segment): string;
223+
public function getLocaleBySegment(?string $segment = null): string;
206224
}
207225
```
208226
This gives you the flexibility to load languages from the database, API, or any other source.
@@ -236,7 +254,7 @@ class DatabaseLanguageProvider implements LanguageProviderInterface
236254
}, $languages->all());
237255
}
238256

239-
public function getLocaleBySegment(string $segment): string
257+
public function getLocaleBySegment(?string $segment = null): string
240258
{
241259
$language = Language::where('slug', $segment)->first();
242260

src/LocalizeServiceProvider.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,14 @@ public function boot(Application $app, Router $router, Redirector $redirector, L
5252
->aliasMiddleware('localize.setLocale', SetLocaleMiddleware::class)
5353
->aliasMiddleware('localize.paginated', PaginatedMiddleware::class);
5454

55-
$router->macro('localize', function (string $name, array $parameters = [], bool $paginated = false) use ($app) {
55+
$router->macro('localize', function (string $name, array $parameters = []) use ($app) {
5656
return $app->make(LocalizeUrlGenerator::class)
57-
->generate($name, $parameters, $paginated);
57+
->generate($name, $parameters);
5858
});
5959

60-
$router->macro('hasLocalize', function (string $name) use ($router, $app): bool {
61-
$localizedName = $app->getLocale().'.'.$name;
62-
63-
return $router->has($localizedName) || $router->has($name);
60+
$router->macro('isLocalized', function (string $name) use ($app): bool {
61+
return $app->make(LocalizeUrlGenerator::class)
62+
->isLocalized($name);
6463
});
6564

6665
$router->macro('localizedRoutes', function (Closure $callback, array $middleware = []) {

src/LocalizeUrlGenerator.php

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,38 @@
1212

1313
namespace Alexwaha\Localize;
1414

15-
use Illuminate\Contracts\Config\Repository as ConfigContract;
15+
use Illuminate\Contracts\Config\Repository as Config;
1616
use Illuminate\Contracts\Routing\UrlGenerator as UrlGeneratorContract;
17+
use Illuminate\Routing\Router;
18+
use Illuminate\Routing\UrlGenerator;
1719
use Illuminate\Support\Str;
1820

1921
class LocalizeUrlGenerator
2022
{
21-
public UrlGeneratorContract $urlGenerator;
23+
public Router $router;
24+
25+
public Config $config;
2226

23-
public ConfigContract $config;
27+
public string $locale;
28+
29+
/**
30+
* @var UrlGenerator
31+
*/
32+
public UrlGeneratorContract $urlGenerator;
2433

25-
public function __construct(UrlGeneratorContract $urlGenerator, ConfigContract $config)
34+
public function __construct(Router $router, Config $config, UrlGeneratorContract $urlGenerator)
2635
{
27-
$this->urlGenerator = $urlGenerator;
36+
$this->router = $router;
2837
$this->config = $config;
38+
$this->locale = $this->config->get('app.locale');
39+
$this->urlGenerator = $urlGenerator;
2940
}
3041

31-
public function generate(string $name, array $parameters = [], bool $paginated = false): string
42+
public function generate(string $name, array $parameters = []): string
3243
{
33-
$locale = $this->config->get('app.locale');
44+
if (! $this->isLocalized($name)) {
45+
$name = $this->locale.'.'.$name;
46+
}
3447

3548
if (isset($parameters['page'])) {
3649
$page = (int) $parameters['page'];
@@ -39,22 +52,22 @@ public function generate(string $name, array $parameters = [], bool $paginated =
3952
unset($parameters['page']);
4053

4154
if (Str::endsWith($name, '.paginated')) {
42-
$name = Str::replace('.paginated', '.index', $name);
43-
}
44-
} else {
45-
if (Str::endsWith($name, '.index')) {
46-
$name = Str::replaceLast('.index', '.paginated', $name);
55+
$name = Str::replaceLast('.paginated', '.index', $name);
4756
}
4857
}
4958

59+
if ($page > 1 && Str::endsWith($name, '.index')) {
60+
$name = Str::replaceLast('.index', '.paginated', $name);
61+
}
5062
}
5163

52-
if (! $paginated) {
53-
$name = $locale.'.'.$name;
54-
}
55-
56-
$this->urlGenerator->defaults(['locale' => $locale]);
64+
$this->urlGenerator->defaults(['locale' => $this->locale]);
5765

5866
return $this->urlGenerator->route($name, $parameters);
5967
}
68+
69+
public function isLocalized(string $name): bool
70+
{
71+
return Str::startsWith($name, $this->locale.'.');
72+
}
6073
}

src/Middleware/PaginatedMiddleware.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Illuminate\Http\Request;
1818
use Illuminate\Routing\Redirector;
1919
use Illuminate\Routing\UrlGenerator;
20+
use Illuminate\Support\Str;
2021
use Symfony\Component\HttpFoundation\Response;
2122

2223
/**
@@ -47,7 +48,7 @@ public function handle(Request $request, Closure $next): Response
4748

4849
$currentRouteName = $request->route()->getName();
4950

50-
if ($page == 1 && str_ends_with($currentRouteName, 'paginated')) {
51+
if ($page == 1 && Str::endsWith($currentRouteName, 'paginated')) {
5152
$canonicalRouteName = substr($currentRouteName, 0, -strlen('paginated')).'index';
5253

5354
$routeParams = $request->route()->parameters();

tests/Unit/LocalizeUrlGeneratorTest.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
use Alexwaha\Localize\LocalizeUrlGenerator;
44
use Illuminate\Config\Repository;
5+
use Illuminate\Events\Dispatcher;
56
use Illuminate\Http\Request;
67
use Illuminate\Routing\Route;
78
use Illuminate\Routing\RouteCollection;
9+
use Illuminate\Routing\Router;
810
use Illuminate\Routing\UrlGenerator;
911

10-
it('can generate a localized URL for non-paginated route', function () {
12+
it('can generate a localized URL', function () {
1113
$routes = new RouteCollection;
1214

1315
$request = Request::create('/');
16+
$events = new Dispatcher;
17+
$router = new Router($events);
1418
$urlGenerator = new UrlGenerator($routes, $request);
1519

1620
$config = new Repository([
@@ -22,31 +26,61 @@
2226
$route = new Route(['GET'], '/en/home', ['as' => 'en.home']);
2327
$routes->add($route);
2428

25-
$localizer = new LocalizeUrlGenerator($urlGenerator, $config);
29+
$localizer = new LocalizeUrlGenerator($router, $config, $urlGenerator);
2630

2731
$url = $localizer->generate('home');
2832

2933
expect($url)->toBe('http://localhost/en/home');
3034
});
3135

32-
it('can generate a localized paginated URL when page > 1', function () {
33-
$routes = new RouteCollection;
36+
it('can generate a localized paginated URL with route names using index/paginated', function () {
37+
$events = new Dispatcher;
38+
$router = new Router($events);
3439

3540
$request = Request::create('/');
36-
$urlGenerator = new UrlGenerator($routes, $request);
3741

3842
$config = new Repository([
3943
'app' => [
4044
'locale' => 'en',
4145
],
4246
]);
4347

44-
$routes->add(new Route(['GET'], '/en/blog', ['as' => 'en.blog.index']));
45-
$routes->add(new Route(['GET'], '/en/blog/page/{page}', ['as' => 'en.blog.paginated']));
48+
$routes = $router->getRoutes();
49+
50+
$routes->add((new Route(['GET'], '/en/blog', ['as' => 'en.blog.index']))
51+
->middleware(['localize.setLocale', 'localize.paginated']));
52+
53+
$routes->add((new Route(['GET'], '/en/blog/page/{page?}', ['as' => 'en.blog.paginated']))
54+
->middleware(['localize.setLocale', 'localize.paginated']));
55+
56+
$urlGenerator = new UrlGenerator($routes, $request);
4657

47-
$localizer = new LocalizeUrlGenerator($urlGenerator, $config);
58+
$localizer = new LocalizeUrlGenerator($router, $config, $urlGenerator);
4859

4960
$url = $localizer->generate('blog.index', ['page' => 2]);
5061

5162
expect($url)->toBe('http://localhost/en/blog/page/2');
5263
});
64+
65+
it('can generate a localized paginated URL without route names index/paginated', function () {
66+
$routes = new RouteCollection;
67+
68+
$request = Request::create('/');
69+
$events = new Dispatcher;
70+
$router = new Router($events);
71+
$urlGenerator = new UrlGenerator($routes, $request);
72+
73+
$config = new Repository([
74+
'app' => [
75+
'locale' => 'en',
76+
],
77+
]);
78+
79+
$routes->add(new Route(['GET'], '/en/blog', ['as' => 'en.blog']));
80+
81+
$localizer = new LocalizeUrlGenerator($router, $config, $urlGenerator);
82+
83+
$url = $localizer->generate('blog', ['page' => 2]);
84+
85+
expect($url)->toBe('http://localhost/en/blog?page=2');
86+
});

0 commit comments

Comments
 (0)