elixir-cldr / cldr_units

Unit formatting (volume, area, length, ...) functions for the Common Locale Data Repository (CLDR)
Other
16 stars 13 forks source link

extending unit categories and units for domain specific units #18

Closed DamienFriot closed 3 years ago

DamienFriot commented 3 years ago

Hi,

We have a few specific categories and units in my domain (environmental footprints), e.g. transportation in person.km (person x km) or infrastructure in unit (for example for a building unit).

What would be the preferred way to extend the library with additional categories and units?

Thank you in advance for your answer.

kipcole9 commented 3 years ago

First of all, thanks for finding the library useful - its one of my favourites. And the way that the CLDR team have built the data it lets you work with all sorts of crazy compound units like:

iex> MyApp.Cldr.Unit.new "light year kilogram per cubic millimole", 3
{:ok, #Cldr.Unit<"kilogram_light_year_per_cubic_millimole", 3>}

Developing the code for this kind of generalised unit was a lot of fun and really tricky. But ... at the moment its not extensible. The good news is that the underlying data model isn't too complicated. If you can live with defining new units statically (ie configured in the Cldr backend and used at compile time) then I think I can get what. you want completed over the weekend. It's been on the to-do list for a while.

If you let me know what new base units you need (like person in your example) and what new compound units (like person kilometre) you are working with I can use them as test data.

kipcole9 commented 3 years ago

BTW, if you are curious about the underlying raw data, you can execute Cldr.Config.units/0.

DamienFriot commented 3 years ago

Thanks a lot for your answer! Defining units at compile time would be perfect. I am not in a hurry so I can wait or a few weeks before any update without any problems.

Two main base units -> compound units are of interest for a Transportation category:

In addition for an "Unspecified" category, e.g. infrastructure, the base unit "unit" is of interest. The compound units will be unit per year and unit per day.

kipcole9 commented 3 years ago

Not forgotten, have been working on a bunch of different experiments and I have a good path forward now. Expecting to complete the next version over this coming weekend.

kipcole9 commented 3 years ago

I expect to have release 3.4.0 out this weekend with custom unit support and some additional changes related to #19

kipcole9 commented 3 years ago

Good progress today. Customer units can be defined, even supporting SI prefixes and square/cube - not that I suspect that is important to you.

Examples

iex> Cldr.Unit.new :person_kilometer, 1 
{:ok, #Cldr.Unit<"person_kilometer", 1>}

iex> Cldr.Unit.new :vehicle_kilometer, 1
{:ok, #Cldr.Unit<"vehicle_kilometer", 1>}

# With an SI prefix
iex> Cldr.Unit.new :milliperson_kilometer, 1
{:ok, #Cldr.Unit<"milliperson_kilometer", 1>}

# Also cube or square
iex> Cldr.Unit.new :square_person_kilometer, 1
{:ok, #Cldr.Unit<"square_person_kilometer", 1>}

Configuration

Since units are defined at compile time, so custom units are defined. Here is an example from the repo's dev.exs configuration:


config :ex_cldr_units, :additional_units,
  vehicle: [base_unit: :unit, factor: 1, offset: 0, sort_before: :all],
  person: [base_unit: :unit, factor: 1, offset: 0, sort_before: :all]

Caveats and limitations

At the moment the mechanism to define a custom unit appears to be OK. However localisation (Cldr.Unit.to_string/2 and friends) will raise an exception since there is no way to define localisations yet. Thats the next step.

kipcole9 commented 3 years ago

As of this commit, custom units can now be localised. I need to write some tests and docs before release of this and the work in #19. Comments, suggestions are very welcome.

Configuration

The units themselves are configured as per the previous comment, in config.exs. Localisations are contained in a backend module. For example:

defmodule MyApp.Cldr do
  use Cldr.Unit.Additional

  use Cldr,
    locales: ["en", "fr", "de", "bs", "af", "af-NA", "se-SE"],
    default_locale: "en",
    providers: [Cldr.Number, Cldr.Unit, Cldr.List]

  unit_localization(:person, "en", :long,
    one: "{0} person",
    other: "{0} people",
    display_name: "people"
  )

  unit_localization(:person, "en", :short,
    one: "{0} per",
    other: "{0} pers",
    display_name: "people"
  )

  unit_localization(:person, "en", :narrow,
    one: "{0} p",
    other: "{0} p",
    display_name: "p"
  )
end

Note the use Cldr.Unit.Additional and the use of the macro unit_localization/4.

Example usage

iex> Cldr.Unit.to_string Cldr.Unit.new!(:person, 1)                          
{:ok, "1 person"}
iex> Cldr.Unit.to_string Cldr.Unit.new!(:person, 2), style: :short           
{:ok, "2 pers"}
iex> Cldr.Unit.to_string Cldr.Unit.new!(:person, 2), style: :narrow
{:ok, "2 p"}
iex> Cldr.Unit.to_string Cldr.Unit.new!(:person_kilometer, 2), style: :long  
{:ok, "2 person-kilometers"}
iex> Cldr.Unit.to_string Cldr.Unit.new!(:person_kilometer, 2), style: :short
{:ok, "2 per⋅km"}
iex> Cldr.Unit.to_string Cldr.Unit.new!(:person_kilometer, 2), style: :narrow
{:ok, "2 p⋅km"}
kipcole9 commented 3 years ago

I have published ex_cldr_units version 3.4.0-rc.0 and would welcome any feedback or suggestions. The changelog entry is:

Bug Fixes

Enhancements