Localization: how to use ex_cldr (correctly) in phoenix templates?

Hi all!

I’m currently working on a phoenix web application which should be available in different languages. Therefore I need to localize e.g. numbers, datetimes etc. I’ve correctly set up ex_cldr, but I wonder how to use it correctly in phoenix templates? The default Cldr.to_string/1 function works for numbers but I need to use different formats for datetimes throughout the application. As the Cldr.to_string/1 function cannot take further parameters (e.g. a keyword list), I think the only option is to use one of the Cldr.Datetime.to_string functions, but the returned data is an ok/error tuple {:ok, "formated_string"} | {:error, ...}. Using the “bang function” is not always possible, because some datetime fields in the schema are allowed to be nil.

So, do I need to write some kind of “wrapper function(s)” on my own? In Rails, when the type of data is not the expected one, either nil or a string with the unformatted data is returned. What is the best practice here?

I’ve written the following wrapper function, but is there a default way to work with ex_cldr in phoenix?

def l(formatter, value, options \\ []) when is_atom(formatter) do
    case apply(formatter, :to_string, [value, options]) do
      {:ok, formatted_value} -> formatted_value
      _formatting_failed -> Keyword.get(options, :default, "")

which is called in a template with e.g.

<%= l(Cldr.DateTime, schema.my_datetime, format: "dd.MM.YYYY - HH:mm") %>
# => 03.10.2022 - 09:32

Many thanks!

@railsmechanic I’m definitely open to suggestions.

I thought about this a lot when I implemented the Cldr.Chars protocol. There are many common options for formatting numbers, dates, units and so on - but not always the case. Given that the contract for Cldr.to_string/1 is return a string its likely the right approach (things like templates were part of the motivation for it). I’m just not sure what to do if an error is returned in the underlying protocol implementation. Just return ""? Return "" and log an error?

What do you think would work best?

I don’t think errors should be swallowed, but supporting nil would make things more friendly in a templating context. String.Charts.to_string also special cases nil to turn into "" instead of "nil".

Thanks for the input @LostKobrakai. What do you think should happen if there is an error return from the protocol implementation?

IMHO In a templating context, I want to be able to return either nil or a customizable default value. This makes my work much easier. For me, the customizable default value is some kind of error handling.

My thoughs are a bit more specific. I generally think errors should not be swallowed. Errors should raise and be fixed. Depending on the API nil as input is not an error case. This feels like one of those places.

Fallbacks are a good way to deal with errors, e.g. when input is user controlled (and therefore by definition not fixable) or when showing nothing for a value is deemed a better tradeoff than not showing anything. The latter is not a decision that can be made by a library though. E.g. in an online shop not being able to format the price certainly means we want to show nothing rather than no price, which might in the worst case mean you’re required to give things away for free.

Fallbacks on error are imo better handled in userland than by a library, given the problem is not specific to a single librarys API. I might use ex_cldr here and some other library there. Instead of needing to be aware of how each of them handle fallbacks for errors (and they might not do that in the first place) I generally prefer to have one way of dealing with such in a codebase if needed.

:+1: You’re absolutely right with your explanations. The typical “it depends…” answer of DEVs :rofl:
In my case, the simple fallback of nil or a given default value is sufficient, so I’ll stick with my wrapper.

For online shops it might be better to show a “error 500 - something went wrong on our side” message instead of showing no or invalid prices.

I will confirm that the Cldr.Chars protocol implementations all return a "" if the argument or result is nil to conform with String.Chars upon which it is based.

Apart from that, I agree with @LostKobrakai that errors should not be swallowed and therefore I don’t think a change to the current APIs is warranted.

I do care about making apps localisable as easier as possible - its definitely a goal of the libraries - so I appreciate understanding your experience and requirements and I’ll definitely think more on how this might be improved.