Summary#
Each language of a page has one address: the source language's is the page's own path (/p/kiambu), every other
language's is the same path under its code (/sv/p/kiambu), served in place (Which language a reader gets — and switching it). Search engines are
told so in three places, by the platform, for every app that speaks more than one language.
Signature#
<!-- the head of every public page, served as /sv/p/kiambu -->
<link rel="alternate" hreflang="en" href="https://shop.example.com/p/kiambu">
<link rel="alternate" hreflang="sv" href="https://shop.example.com/sv/p/kiambu">
<link rel="alternate" hreflang="x-default" href="https://shop.example.com/p/kiambu">
<link rel="canonical" href="https://shop.example.com/sv/p/kiambu">
<!-- a served sitemap: each <url> once per language, each carrying the whole set -->
<url>
<loc>https://shop.example.com/sv/p/kiambu</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://shop.example.com/p/kiambu"/>
<xhtml:link rel="alternate" hreflang="sv" href="https://shop.example.com/sv/p/kiambu"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://shop.example.com/p/kiambu"/>
</url>Description#
The head of every public page — one a signed-out visitor may open, which is all a crawler ever reads — names the
page in every language the app speaks and x-default (the unprefixed page, which chooses by Accept-Language), as
absolute addresses. A page with no head { } of its own gets them too. A client-side navigation re-applies them with
the rest of the head, so they always describe the page on screen.
The canonical follows the served language. A page's head { Canonical = … } is written once, as the source
language's address ("/p/" + slug). Served as /sv/p/kiambu, the platform writes it — and og:url — as
/sv/p/kiambu: a Swedish page whose canonical named the English one would be folded into it, and never shown to a
Swedish searcher. A canonical on another host is the page's own statement and is left as written.
What the head says is in the served language. A head runs in the language the page is served in, so a
[Translatable] member ([Translatable] — an entity's own words in every language) reads as that language: the title, og:title, the description and the
structured data of /sv/p/kiambu are the product's Swedish words. An address a head builds for itself — a product's
url in its structured data — should be the page in that language too; Session.Language says which
(Osysharp.Shop's ProductUrl does this).
A served sitemap ([Serve("/sitemap.xml")], [Serve]) is written as for one language — a <url> per page, its
<loc> absolute — and the platform lists each page once per language, every copy carrying the full set of
<xhtml:link rel="alternate" hreflang> and whatever else the <url> held (lastmod, priority). A <url> that
already names its alternates is left as written.
An app in one language gets none of this: its pages, heads and sitemap are exactly what its source says.
Examples#
entity Coffee {
[Required, MaxLength(80)] string Slug;
[Required, MaxLength(120), Translatable] string Name;
[MaxLength(300), Translatable] string? Notes;
security { allow read when IsAnonymous || IsAuthenticated; }
}
[Route("/coffee/{slug}")]
[AllowAnonymous]
[Title("Coffee")]
component CoffeePage(string slug) {
var coffee = Coffee.Where(c => c.Slug == slug).FirstOrDefault();
head {
Title = coffee == null ? "Not found" : coffee.Name;
Description = coffee?.Notes;
Canonical = "/coffee/" + slug; // the source language's address — moved into the served one
}
render { Text(coffee?.Name ?? "Not found"); }
}
[Serve("/sitemap.xml")]
[AllowAnonymous]
string Sitemap() {
var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">";
foreach (var c in Coffee.OrderBy(c => c.Slug).ToList()) {
xml = xml + "<url><loc>" + App.BaseUrl + "/coffee/" + c.Slug + "</loc></url>";
}
return xml + "</urlset>";
}
translations sv {
title "Coffee" => "Kaffe";
}See also#
- Which language a reader gets — and switching it — which language a reader gets, and the
/sv/address - head — a page's
head { } - [Serve] — a served sitemap
- [Translatable] — an entity's own words in every language — data read in the served language