The solution was a bit more complex that I thought, because I don't use a prefix for default language. I decided to create a custom Service Provider and return an array with locales associated with URLs for these locales (I will also use this array for the language switcher component):
public function boot()
{
view()->composer('*', function ($view) {
$languageUrls = [];
$currentLocale = request()->segment(1);
$locales = config('app.available_locales'); // ['en', 'de', 'es']
$defaultLocale = config('app.fallback_locale'); // 'en'
// Remove language prefix if not default from segments
if ($currentLocale !== $defaultLocale && in_array($currentLocale, $locales)) {
$noPrefixSegments = request()->segments();
array_shift($noPrefixSegments);
$noPrefixSegments = implode('/', $noPrefixSegments);
}
// Keep all segments
else {
$noPrefixSegments = request()->segments();
$noPrefixSegments = implode('/', $noPrefixSegments);
}
// Generate an array of locales associated with URLs
foreach ($locales as $locale) {
if ($locale === $defaultLocale) {
$languageUrls[$locale] = $noPrefixSegments;
} else {
$languageUrls[$locale] = $locale . '/' . $noPrefixSegments;
}
}
return $view->with('languageUrls', $languageUrls);
});
}
Then in the blade template I simply generate all hreflangs:
@foreach ($languageUrls as $language => $url)
<link rel="alternate" hreflang="{{ $language }}" href="{{ url('/') }}/{{ $url }}" />
@endforeach
which for https://example.com/es/category/page
gives me what I've expected:
<link rel="alternate" hreflang="en" href="https://example.com/category/page" />
<link rel="alternate" hreflang="es" href="https://example.com/es/category/page" />
<link rel="alternate" hreflang="de" href="https://example.com/de/category/page" />
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…