Skip to content

Handling Nordic Characters Å Ä Ö Correctly in AI Output

10 min read · updated August 11, 2026

A Swedish user opens a list of names sorted by your application and sees Åberg at the top, next to Aberg. To them that is as obviously wrong as sorting Zetterberg between A and B would be to you. Å is not a decorated A; it is the twenty-seventh letter, and it comes after Z.

Å Ä Ö are the end of the alphabet

The Swedish alphabet has 29 letters. The first 26 are the Latin basics; the last three are å, ä, ö, in that order, after z. Finnish uses the same convention. This is not a collation preference or a regional habit — it is how the alphabet is taught, how dictionaries are organised and how phone books were printed.

Correct Swedish order:
  apa       a
  zebra     z
  åtta      å    27th letter
  ärt       ä    28th
  öl        ö    29th

What a code-point sort produces (identical to what most
default sorts produce, since a-z are 0x61-0x7A and the
Nordic letters are 0xE4, 0xE5, 0xF6):
  apa
  zebra
  ärt       U+00E4     <- ä before å: wrong, and
  åtta      U+00E5        by accident the other two are right
  öl        U+00F6

What an English-locale collation produces:
  apa
  ärt       ä treated as a variant of a  <- wrong
  åtta      å treated as a variant of a  <- wrong
  öl        ö treated as a variant of o  <- wrong
  zebra

Note the middle case, because it is the one that causes arguments: a raw code-point sort happens to put å ä ö after z, which looks right, and then puts ä before å, which is wrong. Being accidentally right about the hard part and wrong about the easy part is worse than being consistently wrong, because it survives a casual review.

The same code point, two correct answers

Here is the fact that makes locale-less sorting impossible rather than merely sloppy. ä is U+00E4 in Swedish and U+00E4 in German. The two languages sort it differently, and both are correct.

Input: ["Zahn", "Ähre", "Aal"]

German  (DIN 5007-1, ä sorts as a):     Aal, Ähre, Zahn
Swedish (ä is the 28th letter):         Aal, Zahn, Ähre

new Intl.Collator("de").compare("ä", "z")   // negative: ä before z
new Intl.Collator("sv").compare("ä", "z")   // positive: ä after z

There is no property of the character that decides this. The information lives in the language of the text, not in the text itself, which means a sorting function that does not take a locale argument is not under-configured — it is unanswerable. Any library that sorts strings without one has picked a language for you, and it has almost certainly picked English.

The same argument runs through accented-name sort order generally, and the German half of it is worked out in German umlaut and eszett normalization.

One encoding detail belongs here too, because it produces a duplicate that looks impossible. Å exists twice in Unicode: U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE, and U+212B ANGSTROM SIGN. The Angstrom sign has a canonical decomposition to U+00C5, so NFC merges them — which means a Swedish name pasted from a document that used the Angstrom sign compares unequal until you normalize, and equal afterwards. Normalize on ingest and the problem never appears.

Danish and Norwegian order them differently again

Swedish is not “the Nordic order”. Danish and Norwegian share an alphabet with each other and not with Swedish: their last three letters are æ, ø, å.

Swedish            : ... x  y  z  å  ä  ö
Danish / Norwegian : ... x  y  z  æ  ø  å
Finnish            : ... x  y  z  å  ä  ö     (as Swedish)

Sorting ["ål", "æble", "øl", "zoo"] -- one word for each of the
four Nordic letters, plus a z-word:

  da : zoo, æble, øl, ål        æ then ø then å
  sv : zoo, ål, æble, øl        å first; æ and ø are foreign letters
                                and are sorted with ä and ö

The two locales disagree about where å goes -- first of the three
in Swedish, last of the three in Danish -- so the same list has two
different correct orders and neither is discoverable from the text.

Two consequences follow. First, å is last in Danish and first of the three in Swedish, so a Scandinavian list sorted with the wrong one of the two locales is wrong in a way that native speakers notice instantly. Second, Danish and Swedish use different letters for the same sounds — Danish æ ø where Swedish writes ä ö — so a merged Nordic list has no single correct order at all and needs a chosen locale rather than a discovered one.

Danish adds one more rule worth knowing: aa is a historical spelling of å and is still current in proper names, so Danish collation tailorings sort Aarhus with Århus at the end of the alphabet rather than at the beginning. Whether your library implements that is worth testing if you handle Danish names.

Why model-generated sorted lists get this wrong

Ask a language model to alphabetise a list of Swedish surnames and you will frequently get Åberg near the top. The mechanism is straightforward and it is worth being precise about, because it determines the fix.

A model has no collation table. It is producing the most probable continuation given the prompt, and the overwhelming majority of sorted lists in its training data are sorted by English or code-point conventions. “Alphabetical” in that data means A-then-B, with accented characters treated as their unaccented base — so that is the pattern reproduced, regardless of what language the items are in. Asking for “Swedish alphabetical order” explicitly helps, because it shifts the conditioning, but it does not make the model consult a table it does not have; it makes the correct pattern more probable, which is a different guarantee.

The same reasoning applies to a second failure that shows up more often in generated text than in generated lists: dropping the diacritic entirely, producing Malmo for Malmö or Angstrom for Ångström. That is not a sorting problem but it has the same root — the unmarked form is more frequent in the training distribution.

The fix

  1. Do not ask a model to sort. Have it produce the items and sort them in code with a real collator. Ordering is a deterministic operation with a specification; routing it through a probabilistic system converts a solved problem into an unsolved one. This is the single highest-value change on this page.
  2. Pass a locale to every sort. new Intl.Collator(“sv”) in JavaScript, Collator.getInstance(new Locale(“sv”)) in Java, PyICU in Python, COLLATE “sv-SE-x-icu” in PostgreSQL. A sort with no locale is a bug that has not been noticed yet.
  3. Get the locale from the content, not from the server. A list of Swedish names is sorted in Swedish regardless of who is looking at it; a list the user will scan for a name they know is sorted in the user’s locale. These differ, and choosing which one applies is a product decision that should be written down.
  4. Normalize to NFC on ingest so that å is one code point and the Angstrom sign has been folded away. Collators handle decomposed input correctly, but every equality check around them does not. The forms are covered in NFC and NFKC normalization.
  5. Never ASCII-fold Nordic text for display or storage. ö→o and ø→o are acceptable in a slug or a search recall field and nowhere else. Malmo is not a place.
  6. Put one assertion in the test suite: sorting [“öl”, “apa”, “åtta”, “zebra”, “ärt”] with the Swedish collator must produce apa, zebra, åtta, ärt, öl. It fails the moment somebody removes a locale argument or a library upgrade changes a default.