Cannot agree more with Accepted answer, in addition to this sharing details here on one of my recent working implementations which might be helpful as well:
You could still make use of OOTB Canonical Rendering to achieve the first line of HTML without manipulating the OOTB implementation and ensuring it works for other sites (single lang etc).
<link rel="canonical" href="https://yourwebsite.com.com/fr-ch/solutions" />
However for the alternatives -> create a new custom rendering which could be cloned from Canonicals named CanonicalAlternatives:
- Add a new Site Configuration property
allowedLanguages
which can be added to SXA site under Site groupings Other Properties, which is a list of comma separated allowed Site languages for your site which exists for a given site. ex: fr-ch
and de-ch
in your case.(It is good to have this config as although you may check if version or language item exists , this config allows you to restrict alternatives to your site especially when adding new languages which are yet to be tested).
- Then in repository I would grab all languages and process the allowed languages only to generate the Href(full link language URL) and HrefLang for my alternates through Link Manager (code below).
- Add this cloned Rendering(CanonicalAlternatives.cshtml) to the metadata partial design after Canonical (Head placeholder) so that it emits the alternatives.
Ex View cshtml
@model CanonicalAlternates
@foreach (var item in Model.Items)
{
<link rel="alternate" href="@item.Href" hreflang="@item.HrefLang" />
}
Repository (example for demo)
public CanonicalAlternates GetModel(){
var model = new CanonicalAlternates();
using (new EnforceVersionPresenceDisabler())
{
var item = _context.Item;
if (item == null)
{
return;
}
var urlOptions = GetUrlOptions();
var langs = item.Languages
.OrderBy(x => x.CultureInfo.Name)
.ToList();
var allowedLanguages = //Get List of AllowedLanguages List from Site Properties ex: site.Properties["allowedLanguages"] split by comma and get range -> fr-CH, de-ch;
foreach (var language in langs)
{
if(!allowedLanguages.Contains(language.Name, StringComparer.OrdinalIgnoreCase))
{
continue;
}
var item2 = item.Database.GetItem(item.ID, language);
if (item2 != null && item2.Versions.Count > 0)
{
urlOptions.Language = language;
var fullLink = LinkManager.GetItemUrl(item2, urlOptions);//get full url along with scheme and targethostname
var hrefLang = //Get hrefLang from Language Item, you could store this in Display Tag item on Language for ease of use to get actual language tag
model.Items.Add(new CanonicalAlternate
{
Href = href,
HrefLang = hrefLang
});
}
}
}
return model;
}
//Model ***CanonicalAlternates*** is List of ***CanonicalAlternate*** which has Href and HrefLang Properties
ex:
This would generate the alternates as below
<link rel="alternate" href="https://yourwebsite.com.com/fr-ch/solutions" hreflang="fr-CH" />
<link rel="alternate" href="https://yourwebsite.com.com/de-ch/loesungen" hreflang="de-CH" />