# hreflang

## Hvad det er

På et link angiver `hreflang`, hvilket sprog siden i den anden ende er skrevet
på. HTML tillader attributten på elementerne `a`, `area` og `link`, men den er
blevet kendt for en bestemt brug: et sæt `link rel="alternate"`-elementer i
sidens head, ét pr. sprogversion, hvormed et site fortæller søgemaskiner, at
flere adresser rummer det samme indhold på forskellige sprog.

```html
<link rel="alternate" hreflang="en" href="https://example.com/guide/" />
<link rel="alternate" hreflang="da" href="https://example.com/da/guide/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/guide/" />
```

HTML-standarden kalder attributten »rent vejledende«. Værdien skal være et
gyldigt sprogmærke efter BCP 47, og en browser skal tage en hentet sides sprog
fra siden selv og aldrig fra det link, der førte derhen. Sammen med
`rel="alternate"` markerer et `hreflang`, der afviger fra dokumentets eget
sprog, at målet er en oversættelse.

Søgemaskinerne har gjort den vejledende bemærkning til et fast regelsæt. Google
beskriver tre ligeværdige måder at levere sættet på: link-elementer i head, en
HTTP-header af typen `Link` til PDF'er og andre filer uden head, og poster i et
XML-sitemap. Hver version skal nævne sig selv og alle de andre versioner med
fuldt kvalificerede adresser. Et par tæller kun, når begge sider nævner
hinanden, så et fremmed site ikke kan erklære sig selv for en oversættelse af
ens egne sider. Koderne er et sprog efter ISO 639-1, eventuelt efterfulgt af en
region efter ISO 3166-1, og en region alene er ugyldig. Den reserverede værdi
`x-default` udpeger siden til læsere, hvis sprog ikke passer til nogen af de
andre, og ifølge Google er den tænkt til sider, hvor man vælger sprog.

To naboer er lette at forveksle med den. Attributten `lang` siger, hvilket sprog
teksten foran læseren er på; `hreflang` siger, hvilket sprog en side, der linkes
til, er på. Google oplyser, at den ikke bruger nogen af dem til at afgøre en
sides sprog, så `hreflang` kan ikke gøre en side dansk; den fortæller en
søgemaskine, at en dansk side, den allerede har genkendt, hører sammen med en
engelsk. Og en kanonisk URL lægger dubletter sammen til én indekseret adresse,
mens `hreflang` holder hver version i indekset og siger, hvilken der skal vises
til hvem.

## Hvorfor det betyder noget

Det, `hreflang` afgør, er, om sprogversionerne af en side udgør én klynge eller
bedømmes som hver sin side. Står de alene, konkurrerer hver version for sig, og
en søgemaskine kan vise den engelske side til en dansk læser eller liste begge
versioner i de samme resultater. Erklæres de som en klynge, forstås de som ét
stykke indhold, og søgemaskinen kan tilbyde den version, der passer til læserens
sprogindstillinger.

Klyngen giver kun mening, når versionerne virkelig er det samme indhold. Et site
på ét sprog har slet ikke brug for `hreflang`, og to sprogafdelinger, der
adskiller sig i substansen, er heller ikke en klynge. Regionale varianter med
små forskelle, for eksempel `en-GB`- og `en-US`-sider med hver sine priser,
hører også sammen i én.

Omkostningen er bogføring, og den vokser med kvadratet på antallet af sprog.
Hver version af hver side bærer hele listen, så når en oversættelse tilføjes,
flyttes eller trækkes tilbage, ændres head på alle søskendesiderne, og en liste,
der er rigtig på de fleste sider og forkert på nogle få, fejler i stilhed: de
par, der peger begge veje, bliver brugt, og resten bliver smidt væk uden nogen
synlig fejl. Derfor egner `hreflang` sig dårligt til håndredigering og godt til
en generator, der udleder hver sides sæt af én liste. Det, `hreflang` ændrer,
er, hvilken af flere versioner en søgende får at se.

## Sådan ser det ud i praksis

Dette site
[udgiver hver side på tre sprog fra ét indholdstræ](/da/portfolio/built-a-multilingual-static-site-in-hugo-126/),
så hver side har et fast sæt søskende at angive.

Én skabelon afgør, hvilke oversættelser der annonceres, og fem steder læser den:
`hreflang`-sættet i head, `og:locale:alternate`-taggene til
linkforhåndsvisninger, alternativerne i sitemappet, `inLanguage` i sitets
strukturerede data og, efter endnu en fratrækning, den sprogstribe, læseren ser.
Listen rummer siden selv og giver dermed den selvreference, reglerne kræver.
Koderne nævner kun sprog, `en`, `da` og `uk`, så hver version tilbydes alle
læsere af det sprog, uanset hvor de bor, og den engelske post skrives en gang
til som `x-default`. Head og sitemap bærer begge sættet, og begge gengives fra
den samme liste, så de kan ikke glide fra hinanden.

Skellet mellem maskiner og læsere er bevidst.
[Den ukrainske version er indekseret og nævnt i sitemaps, mens striben i topbjælken udelader den](/da/credits/),
og i sprogkonfigurationen er det ét enkelt flag: en crawler får stadig besked om
ukrainsk i begge retninger, og kun læserens menu ændrer sig. Et andet flag, til
et sprog, der er bygget, men ikke klar, fjerner det fra begge lister og mærker
dets sider `noindex`. En side på sådan et sprog angiver heller ingen søskende,
for et `hreflang` fra en side, der ikke er annonceret, til en, der er, er et
par, som en søgemaskine kun ser den ene ende af.

Kopier følger samme logik. En side på et af de fokuserede subdomæner, hvis
kanoniske URL peger tilbage på hovedsitet, udskriver slet intet `hreflang`:
sættet læses på den side, en søgemaskine har godtaget som kanonisk, og en kopi,
der nævnte sin egen værts oversættelser, ville modsige sin egen kanoniske URL.
Sprogvælgeren virker stadig, for en læser, der vil have dansk, skal stadig have
dansk.

Et tjek af de byggede sider kræver, at hver side med alternativer har
`x-default` og sit eget sprog med, og at hvert alternativ er en bygget side på
et annonceret sprog, som peger tilbage. Reglen om selvreference gør nytte.
Generatoren har to lister med næsten samme navn, `.Translations`, der udelader
den aktuelle side, og `.AllTranslations`, der tager den med, og forveksler man
dem, forsvinder hver sides eget sprog fra head og fra sprogstriben på én gang,
så de to stadig er enige. Mod et bevidst ødelagt build var den ene påstand den
eneste i tjekket, der fangede 528 af de 792 sider.

## Hvor det går galt

**Søgende får versionen på det forkerte sprog.** En dansk læser bliver sendt til
den engelske side, eller begge versioner står side om side. Parrene peger kun én
vej: den danske side nævner den engelske, men den engelske lister stadig kun de
sprog, den havde før, og søgemaskiner ignorerer et par uden returlink. Byg hver
sides sæt af én liste, der rummer siden selv, og afprøv gensidigheden på det
byggede output.

**Et helt sprogs annoteringer gør ingenting.** Markuppen er der og er gyldig
HTML, men versionerne bliver behandlet, som om de intet har med hinanden at
gøre. Koderne betyder noget andet end tilsigtet: `ua` eller `dk`, hvor sprogene
hedder `uk` og `da`, `be` ment som Belgien, selv om det er koden for
hviderussisk, eller `en-UK`, hvor `en-GB` var ment, fordi `UK` er en reserveret
regionskode, som Google ser bort fra. Brug et sprog fra ISO 639-1, tilføj kun en
region fra ISO 3166-1, hvor indholdet faktisk er forskelligt fra land til land,
og husk, at et gyldigt BCP 47-mærke stadig kan være et, Google ikke
understøtter.

**Oversættelserne falder ud af indekset.** Kun originalsproget dukker op i
søgningen, og oversættelserne behandles som dubletter af det. Hver oversættelse
udpeger originalen som sin kanoniske URL, og det beder om, at oversættelsen
bliver lagt sammen med originalen, mens dens `hreflang` beder om, at den bliver
vist. Lad hver sprogversion være sin egen kanoniske URL; Googles råd er, at den
kanoniske side skal være på samme sprog.

**En side, der bad om at blive holdt ude af søgning, bliver annonceret
alligevel.** Et kladdesprog mærket `noindex` står blandt alternativerne på
udgivne sider eller lister dem i sit eget head. Annonceringen og direktivet er
sat to forskellige steder, så sitet giver en crawler to svar om den samme
adresse. Beslut det pr. sprog, annonceret i begge retninger eller i ingen af
dem, læg beslutningen i én indstilling, som alle lister læser, og tjek de
byggede sider mod den.

## Læs mere

- [HTML Standard: the hreflang attribute](https://html.spec.whatwg.org/multipage/links.html#attr-hyperlink-hreflang)
  — hvad attributten betyder på et link, og hvorfor en browser ikke må stole på
  den.
- [HTML Standard: link type "alternate"](https://html.spec.whatwg.org/multipage/links.html#rel-alternate)
  — hvordan `hreflang` og `type` beskriver en oversættelse eller et andet format
  af det samme dokument.
- [RFC 5646: Tags for Identifying Languages](https://www.rfc-editor.org/rfc/rfc5646)
  — den BCP 47-syntaks, attributtens værdi skal følge.
- [Google Search Central: localized versions of your pages](https://developers.google.com/search/docs/specialty/international/localized-versions)
  — de tre måder at levere sættet på, reglen om gensidighed og `x-default`.

<https://engineer.company/da/glossary/hreflang/>
