Locale-aware Date/Time formatting package: pre-publish review

Followup to my previous post. I have created a package that can be used to do pure-Elm locale aware Date and Time.Posix formatting (kind of like a simplified version of JS’s Intl.DateTimeFormat). Apologies to aguzubiaga for not reaching out in collaboration, I procrastinated on this for a long time and then did all of it in a flurry of avoiding-my-actual-job-work.

Under the hood, it uses justinmimbs/date and ryannhg/date-format for the actual formatting work, so my main contribution is figuring out how to parse the Unicode standard format strings into the correct tokens and automating the process of getting the correct info out of the 375 JSON files in the most recent version of Unicode’s CLDR project.

Without further ado, I present:

elm-cldr

Naming

I decided to go with the name “elm-cldr” to make it clear that this is coming from the Unicode’s CLDR project. While I want the API to be familiar to those who know JS’s Intl API, it is not currently a feature-complete replacement for that API. It has the basics, and I think will cover a lot of use cases, but it would need to be expanded in order to cover the various options that can be passed into Intl.

Examples

There are fully operational examples in the ./examples directory, so that anyone who wants to review this before I publish it can start from those if they wish.

DCE and sizing

Because of Elm’s automatic DCE, the size that this library has is dependent on how many locales you want to include. However, every locale that you may wish to match against needs to be included. For this reason, the fromString function allows you to provide the list of locales that you would like to match against so that Elm will automatically eliminate all of the other locales. Two lists of locales are included to get you started, but you can build your own list any way that you want.

In order to get an idea of how big the file sizes were, the two examples take opposite approaches for using this. The “Static” example just uses a single locale, allowing 374 to be eliminated, while the “Dynamic” example includes all locales in the match list resulting in the maximum size. The examples are otherwise identical. I compiled each using elm make --optimize to a standalone .js file and then gzipped it using the gzip command on my mac. I then measured the file sizes using ls -lh:

  • dynamic.js: 2.4M
  • dynamic.js.gz: 93K
  • static.js: 183K
  • static.js.gz: 37K

Including all of the 375 locales does increase the size of the .js file by a lot, but gzip is able to bring the size differential down to something that is hopefully manageable.

My ask of you

If you are someone who is interested in a library like this existing, please take a look and provide your feedback! Would this help you? Do the names of things make sense? Now is the easiest time for me to make any changes, since I have not published it as a package yet.

8 Likes

First of all: thanks for doing this! It is an interesting project and I appreciate the time you poured into it!

Observations:

  1. Consider using https://elm-doc-preview.netlify.app/ to allow for a preview of your documentation (by adding the docs json to a branch that you will then delete to avoid weighting the repo, can go into details if needed)
  2. Have you considered having a load-a-locale-dynamically API? (probably with a Task Error Locale type) - this would probably solve the static vs dynamic conundrum. This would require the developer to put the serialized (binary?) locale somewhere and loading them? Maybe you could simply provide a Bytes.Decoder and Bytes.Encoder? :thinking:

I’m super excited for this as a package, thank you for your hard work! When you do release it I’d like to update my package wolfadex/locale-negotiation to build on top of your work, use your locales instead of my string based ones.

@aguzubiaga this might also help with your work on parsing Fluent files.

@miniBill I was using the local hot-reload version of that, but then completely forgot that there was an online version! Here it is: https://elm-doc-preview.netlify.app/?repo=enkidatron/elm-cldr&version=doc-preview.

To be honest, I had not considered a load-a-locale-dynamically API because my own primary use case serves the files statically so I do not have the luxury of a server that I can ask a locale from. I can see how that would be a useful addition, though! I don’t see how simply providing a Bytes.encoder and a Bytes.decoder would solve the problem. More specifically, I see how that would solve the problem if the server were running Elm code, but that is not a very common use case. Maybe I am not fully understanding your idea.

@wolfadex I actually looked at your library when writing the Internal.Locale.matchNearestLocale function! It doesn’t run the same algorithm as either your match or filter functions, but it is inspired by your match function.

One potential downside of basing your library on this one is that this one only includes the locales that are in the CLDR, which are identified only by unicode language IDs, not full-blown unicode locale identifiers. That is, this library does not have a way to represent the full-blown locale id “en-GB-u-cu-bgp-cf-account”. Maybe this is fine and the languages are the part that you want to negotiate; I just want you to be aware of this.

1 Like

@miniBill Having thought about the “load-a-locale-dynamically” question a bit more, I do think I was misunderstanding a bit. I was thinking of a flow such as: JS initialization passes in the browser’s navigator.language as part of the flags (e.g. “en-GB”) → Elm app sends a request to a back-end server requesting the Locale specification for that language identifier → Server sends back some data → Elm app expands that into a Locale data type. The Bytes.Encoder and Bytes.Decoder would be used as part of that last step in this flow, which is why the server would need to be running Elm code.

However, I don’t think that that is what you were actually proposing. You mentioned a Task Error Locale, which I do think would be really neat. However, I think that it would require Kernel code, which would require this to be part of elm-explorations (I think). At least, I cannot think of a way to write getDeviceLocale : Task x Locale without writing Kernel code. I could write getLocale : String -> Task Error Locale, but it would just be a Task wrapper around the current implementation that wouldn’t save any code size. One of my goals was to separate out how we get the Locale from how we use it, so that this library could be migrated into elm-explorations with minimal changes if the code size ends up being an issue and there is community desire.

In fact, I actually think that this might end up being kind of an API proving ground for a future Intl package. It seems that node supports ECMA-402, including the JS Intl API, so a Kernel-enabled version that only uses that JS API under the hood could work in a browser and on node. I am not familiar enough with WebAssembly to know how that would interact with a Kernel-enabled package, which is probably why they are locked down so tight.