SvelteKit i18n and FOWL

Perhaps my favourite JavaScript APIs live within the Internationalization namespace. A few neat things the Intl global allows: Natural alphanumeric sorting Relative date and times Currency formatting It’s powerful stuff and the browser or runtime provides locale data for free! […]


This content originally appeared on dbushell.com (blog) and was authored by dbushell.com (blog)

Perhaps my favourite JavaScript APIs live within the Internationalization namespace.

A few neat things the Intl global allows:

It’s powerful stuff and the browser or runtime provides locale data for free!

That means timezones, translations, and local conventions are handled for you. Remember moment.js? That library with locale data is over 600 KB (uncompressed). That’s why JavaScript now has the Internationalization API built-in.

SvelteKit

SvelteKit and similar JavaScript web frameworks allow you to render a web page server-side and “hydrate” in the browser. In theory, you get the benefits of an accessible static website with the progressively enhanced delights of a modern “web app”.

I’m building attic.social with SvelteKit. It’s an experiment without much direction. I added a bookmarks feature and used Intl.DateTimeFormat to format dates.

<script>
  const dateFormat = new Intl.DateTimeFormat(undefined, {
    dateStyle: "medium",
    timeStyle: "short",
  });
</script>

<!-- later in the template -->
<time datetime={entry.createdAt}>
  {dateFormat.format(new Date(entry.createdAt))}
</time>

Perfect! Or was it? Disaster strikes! See this GIF:

formatted date flipping between US and British English
This is a GIF with a hard G not a JIF

What is happening here?

Because I don’t specify any locale argument in the DateTimeFormat constructor it uses the runtime’s default. When left unconfigured, many environments will default to en-US. I spotted this bug only in production because I’m hosting on a Cloudflare worker. SvelteKit’s first render is server-side using en-US but subsequent renders use en-GB in my browser. My eyes are briefly sullied by the inferior US format!

Is there a name for this effect? If not I’m coining: “Flash of Wrong Locale” (FOWL).

Fixing FOWL

To combat FOWL we must ensure that SvelteKit has the user’s locale before any templates are rendered. Browsers may request a page with the Accept-Language HTTP header.

The place to read headers is hooks.server.ts.

import { acceptsLanguages } from "$lib/negotiation";
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  const languages = acceptsLanguages(event.request);
  event.locals.locale = languages[0] === "*" ? undefined : languages[0];
  return resolve(event);
};

I’ve vendored the @std/http negotiation library to parse the request header. If no locales are provided it returns * which I change to undefined. SvelteKit’s event.locals is an object to store custom data for the lifetime of a single request.

Event locals are not directly accessible to SvelteKit templates. That could be dangerous. We must use a page or layout load function to forward the data.

import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({ locals }) => {
  return {
    locale: locals.locale,
  };
};

Now we can update the original example to use the locale data.

<script lang="ts">
  import type { PageProps } from "./$types";
  
  let { data }: PageProps = $props();
  
  const dateFormat = $derived(
    new Intl.DateTimeFormat(data.locale, {
      dateStyle: "medium",
      timeStyle: "short",
    }),
  );
</script>

<!-- later in the template -->
<time datetime={entry.createdAt}>
  {dateFormat.format(new Date(entry.createdAt))}
</time>

I don’t think the $derived rune is strictly necessary but it stops a compiler warning.

This should eliminate FOWL unless the Accept-Language header is missing. Privacy focused browsers like Mullvad Browser use a generic en-US header to avoid fingerprinting. That means users opt-out of internationalisation but FOWL is still gone.

If there is a cache in front of the server that must vary based on the Accept-Language header. Otherwise one visitor defines the locale for everyone who follows unless something like a session cookie bypasses the cache.

You could provide a custom locale preference to override browser settings. I’ve done that before for larger SvelteKit projects. Link that to a session and store it in a cookie, or database. Naturally, someone will complain they don’t like the format they’re given. This blog post is guaranteed to elicit such a comment. You can’t win!

Oh, Safari

Why can’t you be normal, Safari? Despite using the exact same en-GB locale, Safari still commits FOWL by using an “at” word instead of a comma.

formatted date flipping between US and British English in Safari browser

Who’s fault is this? The ECMAScript standard recommends using data from Unicode CLDR. I don’t feel inclined to dig deeper. It’s a JavaScriptCore quirk because Bun does the same. That is unfortunate because it means the standard is not quite standard across runtimes.

By the way, the i18n and l10n abbreviations are kinda lame to be honest. It’s a fault of my design choices that “internationalisation” didn’t fit well in my title.


Thanks for reading! Follow me on Mastodon and Bluesky. Subscribe to my Blog and Notes or Combined feeds.


This content originally appeared on dbushell.com (blog) and was authored by dbushell.com (blog)


Print Share Comment Cite Upload Translate Updates
APA

dbushell.com (blog) | Sciencx (2026-03-11T15:00:00+00:00) SvelteKit i18n and FOWL. Retrieved from https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/

MLA
" » SvelteKit i18n and FOWL." dbushell.com (blog) | Sciencx - Wednesday March 11, 2026, https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/
HARVARD
dbushell.com (blog) | Sciencx Wednesday March 11, 2026 » SvelteKit i18n and FOWL., viewed ,<https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/>
VANCOUVER
dbushell.com (blog) | Sciencx - » SvelteKit i18n and FOWL. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/
CHICAGO
" » SvelteKit i18n and FOWL." dbushell.com (blog) | Sciencx - Accessed . https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/
IEEE
" » SvelteKit i18n and FOWL." dbushell.com (blog) | Sciencx [Online]. Available: https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/. [Accessed: ]
rf:citation
» SvelteKit i18n and FOWL | dbushell.com (blog) | Sciencx | https://www.scien.cx/2026/03/11/sveltekit-i18n-and-fowl/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.