Learning F# with Falco: Response Localization

1 week ago 7

02 Jul, 2025

F# has for years been a language I've had a crush on from afar, but never had any actual experience with. I recently picked up Falco to implement a lofty idea for a side project and an opportunity to get to know F# better. Falco is a new-ish web framework but has gained some traction, and since I'm going into this with pretty limited knowledge of F# and even less of it's web frameworks, I figured why not start with the tip of the spear.

Since it is so new, it doesn't have too much documentation and samples laying around. As I'm learning it, I thought I'd help fill the void with some examples that I hope are not too misguided.

I come from a C#/ASP.NET Core background, where localization is rather straightforward:

  1. Create .resx file(s) - these will generate Designer files which in turn provide intellisense
  2. Configure the web server for how you want to determine which language to use (cookie, headers, query parameters..)
  3. Call Resources.MyMagicString

1 and 2 are the luckily pretty much the same in Falco but unfortunately 3 is not, since the FSharp.Configuration ResXProvider does not support .NET Core, so we're missing out on the IDE support. All in all though, it's pretty painless to get localization going.

Getting the language from the request url

For this part we can reach for thw dotnet ecosystem by using

wapp.UseRequestLocalization(fun options -> options.RequestCultureProviders <- [| RouteDataRequestCultureProvider() //allow changing language AcceptLanguageHeaderRequestCultureProvider() //default from headers |]

With the RouteDataRequestCultureProvider we need to tell the system that the first part of the url (/en, /fi in this example) is the culture and that is bound by the middleware to set the thread culture:

let endpoints = [ get "/{culture:regex(^(en|fi)$)?}" handleGet post "/{culture:regex(^(en|fi)$)?}/download" handleDownload get "/{culture:regex(^(en|fi)$)?}/download/file/{key}/{name}" (fun ctx -> task { let route = Request.getRoute ctx let key = route.GetString "key" let name = route.GetString "name" return! handleFileDownload key name ctx }) ] --- wapp.UseFalco(endpoints)

Reading localized text from .resx

As mentioned, in F# land we don't have (as far as I know...) the nice DX of the Designer files, so we have to do the actual localization a bit more crudely:

let private resourceManager = ResourceManager("Namespace.Of.Resource", Assembly.GetExecutingAssembly()) let localize value = let localized = resourceManager.GetString(value, Thread.CurrentThread.CurrentUICulture) match localized with | "" | null -> value | _ -> localized

With that we can localize string with localize "PageTitle". Unfortunate that we have to play with plain strings, but not the end of the world. Working with Riders Localization Manager makes .resx still the most appealing option for me.

Since everything in the Falco Markup (which I'm still not sure if I like or not) is functions, it's trivial to extend to create your own html fragments. Here's a simple wrapper to localize a given string and return it as a _text element:

let _ltext value = _text (localize value)

And so we can swap _text to _ltext and have localization included. Lovely.

As I got the implementation working, one component insisted on staying in the original rendered language regardless of the current thread culture.

let dateSelection = _fieldset ... // and usage: _div [ _class_ "card-body" ] [ dateSelection ]

The reason might seem obvious for those who've worked with F# for more than a few days, but had me scratching my head for a while. Basically it came down to the difference between a value and a function:

Since let dateselection = has no parameters, it is a value. As a value, it's only evaluated once, so the localization inside always stays the same. Adding a dummy unit parameter () makes it a function but feels weird:

let dateSelection () = _fieldset ... // and usage: _div [ _class_ "card-body" ] [ dateSelection () ]

I think it would be more idiomatic F# to give the culture as a parameter to all component rendering functions, but ultimately it would be just noise on the calling side, so maybe this is fine. After all, I wouldn't know idiomatic F# if it punched me in the gut yet...

Conclusions

All in all the localization flow is, naturally, very similar to how localization works in ASP.NET Core with e.g. Razor Pages. The .resx interaction is a disappointing downgrade, but still straightforward enough to . The ease of expanding Falco markup shines here, and I look forward to working with the framework more as I delve deeper into open Finnish environment data.

All of the code above is available in a larger context in the project repo

Thoughts, comments? Send me an email!

#dotnet #falco #fsharp #tech

Read Entire Article