Suave Locale
An internationalisation WebPart and library for Suave for use with React-Intl and Format.JS or other i18n systems.
This library's only purpose; to deliver:
var intlData = {
locale : 'en-GB',
messages: {
post: {
meta: 'Posted {ago}, {num, plural, one{# comment} other{# comments}}'
}
}
};Which you can then use with your i18n infrastructure, like such:
<FormattedMessage
message={this.getIntlMessage('post.meta')}
num={this.props.post.comments.length}
ago={<FormattedRelative value={this.props.post.date} />} />Usage (HTTP)
This part is Suave-specific.There are two moving parts; the negotiation and the localised contents. The library is built around these functions:
open Suave.Locale
type ReqSource = HttpRequest -> Choice<AcceptLanguage, unit>
type IntlSource = LanguageRange -> Choice<IntlData, unit>
type TryLangNeg = ReqSource list -> IntlSource list
-> HttpRequest -> Choice<IntlData, unit>
type LangNeg = HttpRequest -> IntlDataReqSource takes a Suave.Types.HttpRequest and maybe returns a range of
accepted languages. The most commonly used request source is
ReqSources.parseAcceptable which parses Accept-Language HTTP-header into a
range.
IntlSource takes a language range and maybe returns localised contents for
this range. You should have one of these that returns the default site language
last in the list: IntlSource list, and others for each supported language.
TryLangNeg is the type of Negotiate.negotiate; it's the actual language
negotiation function. You can turn it into a LangNeg by doing
let neg : LangNeg =
Negotiate.negotiate
[ ReqSources.parseQuery "locale"
ReqSources.parseCookie "locale"
ReqSources.parseAcceptable
ReqSources.always (Range [ "en" ])]
[ LangSources.testAndGetJson test get
LangSources.always (defaultLanguage appCtx.settings.rootPath) ]
|> Negotiate.assumeSourceThis first tries to find a query string, locale, then the cookie locale,
then the header Acceptable-Language and if all of these fail, the en locale.
Negotiate.assumeSource assumes we get a Choice1Of2 from both the
ReqSources and the LangSources.
Now you can feed the LangNeg into the combinator:
open Suave
open Suave.Successful
open Suave.Operators
open Suave.Filters
open Suave.Locale
let app (neg : LangNeg) : WebPart =
let print (intl : IntlData) =
OK (sprintf "You have locale '%s'" intl.locale)
let printLocalised (intl : IntlData) =
// or you can use Map.find or Map.tryFind
OK (intl |> IntlData.find "my.localised.greeing")
choose [
// => Content-Type: application/json; charset-utf-8
GET >=> path "/i18n/messages"
>=> Api.serveJson neg
// always returns results in the same language, but different content, so put a vary
// header in the result:
GET >=> path "/i18n/sample"
>=> Api.negotiate neg print
>=> Api.setVary
// this always returns with Vary and Content-Language headers
GET >=> path "/i18n/sample"
>=> Api.negotiateWithHeaders neg printLocalised
Browse.filesHome
]
startWebServer defaultConfig (app neg) // => blocks, prints startup infoAnd you have yourself a splendid new internationalisation system!

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.
