elixir-cldr/cldr_units

[Question] List units by category?

Closed this issue · 8 comments

Hey Kip! We're excited to dive into another one of your libraries - is it not possible to simply list all units by category? i.e if I wanted to list all value volume units, I'm not seeing a way to do this. Am I missing something?

Thanks!

@coladarci Glad you're taking it for a spin. Its maybe my favourite because of the very flexible approach the CLDR team has taken.

Can definitely do as you ask, I'll need to add it in. The is a caveat to be understood.

Units can be composed. For example:

iex> {:ok, unit} = Cldr.Unit.new 3, "square_hectare_per_light_year"
{:ok, #Cldr.Unit<"square_hectare_per_light_year", 3>}

iex> Cldr.Unit.base_unit unit
{:ok, "square_square_meter_per_meter"}

Since this is valid but derived unit, I can never return it as a value for a given category.

The compromise I make is that categories, and the units in a category, are those units which are translatable without composition (ie they have entries in CLDR for each locale for localisation). I think this fits most use cases.

Therefore yes, I can return all the units for a category. Where "all" means those units that can be translated. Not those that are derived or composed.

Make sense? Will work for you?

If you let me know the use case then I can probably give a clearer answer.

Yes! We are making very simple wrappers around these, identical to how you did Money. The two we are making are Volume and Weight; just want to be able to get a list of possible values for UI purposes. No rush, no big deal, pumped with the library 🙌🏻🚀🚀💎💎

As of this commit the basic functions to support retrieving units by category exist.

In addition, I added the long-overdue function Cldr.Unit.measurement_system_from_locale/1 that returns the measurement system (metric, UK, US) applicable to the specified locale.

Next steps this weekend are:

  1. Add a function to filter a list of unit types by measurement system. That way you can choose to show any unit, or only units in one or more measurement systems. This makes it easy to localise the unit list. Why? If you have a user in Europe and you list all of the volume units you would list the metric ones. In the US, the US ones and so on.

  2. Add a function to localise a list of units (which are atoms) into their localised display format that you can put in a UI. This requires an update to ex_cldr version 2.19.0 since I had not previously been packaging the unit display names.

@kipcole9 There's a typo in one of the function definitions that you might want to address before releasing this. The readme also has the same typo.
313eecc#diff-662130a1c7f523fbc1a14b20281561e71fe5c822fa72e341da485851e97657a9R1275

As of this commit, the basic functionality you're after is in place. The function that does the localisation is Cldr.Unit.display_name/1.

Examples

# Get the units for a category and enumerate their display names
# in the current locale and default style
iex> {:ok, units} = Cldr.Unit.known_units_for_category(:volume)
{:ok,
 [:jigger, :cubic_yard, :pinch, :drop, :cup_metric, :acre_foot,
  :cubic_centimeter, :liter, :pint, :quart_imperial, :tablespoon, :deciliter,
  :cubic_foot, :megaliter, :milliliter, :cup, :centiliter,
  :dessert_spoon_imperial, :fluid_ounce_imperial, :teaspoon, :hectoliter,
  :dessert_spoon, :cubic_meter, :barrel, :cubic_kilometer, :fluid_ounce, :dram,
  :cubic_mile, :bushel, :cubic_inch, :pint_metric, :quart, :gallon_imperial,
  :gallon]}
iex> Enum.map(units, &Cldr.Unit.display_name/1)
["jigger", "cubic yards", "pinch", "drop", "metric cups", "acre-feet",
 "cubic centimeters", "liters", "pints", "Imp. quart", "tablespoons",
 "deciliters", "cubic feet", "megaliters", "milliliters", "cups", "centiliters",
 "Imp. dessert spoon", "Imp. fluid ounces", "teaspoons", "hectoliters",
 "dessert spoon", "cubic meters", "barrels", "cubic kilometers", "fluid ounces",
 "dram", "cubic miles", "bushels", "cubic inches", "metric pints", "quarts",
 "Imp. gallons", "gallons"]

# Get the units for a category and enumerate their display names
# in a specific locale and style
iex> Enum.map(units, &Cldr.Unit.display_name(&1, locale: "de", style: :short))
["Jigger", "yd³", "Prise", "Trpf.", "Ta", "Acre-Feet", "cm³", "Liter", "pt",
 "qt Imp", "EL", "dl", "ft³", "Ml", "ml", "Cups", "cl", "Imp. DL",
 "Imp. fl oz", "TL", "hl", "DL", "m³", "bbl", "km³", "fl oz", "Flüssigdram",
 "mi³", "Bushel", "in³", "mpt", "qt", "Imp. gal", "gal"]

# Filter only metric units first
# Note that most metric units support SI prefixes (mega, giga, milli, ... there are 15 prefixes)
# Is it useful to expand those prefixes for UI purposes?
iex> metric_units = Enum.filter(units, &Cldr.Unit.measurement_system?(&1, :metric))             
[:cup_metric, :liter, :cubic_meter, :pint_metric]

# Filter US system units then enumerate display names
iex> us_units = Enum.filter(units, &Cldr.Unit.measurement_system?(&1, [:ussystem]))             
[:jigger, :cubic_yard, :pinch, :drop, :pint, :tablespoon, :cubic_foot, :cup,
 :teaspoon, :dessert_spoon, :barrel, :fluid_ounce, :dram, :cubic_mile, :bushel,
 :cubic_inch, :quart, :gallon]
iex> Enum.map(us_units, &Cldr.Unit.display_name(&1, locale: "de", style: :short))               
["Jigger", "yd³", "Prise", "Trpf.", "pt", "EL", "ft³", "Cups", "TL", "DL",
 "bbl", "fl oz", "Flüssigdram", "mi³", "Bushel", "in³", "qt", "gal"]

Open Items

  • Support an option to expand SI prefixes for metric units?
  • Is the API acceptable? Reasonable?
  • Any other suggestions?

As of this commit, measurement systems are resolved for all units returned by Cldr.Unit.known_units/0 as well as binary composable units.

Feedback is welcome, otherwise I think the requirements of your original request are now met by:

  • Cldr.Unit.display_name/2
  • Cldr.Unit.known_units_by_category/0
  • Cldr.Unit.known_units_for_category/1
  • Cldr.Unit.measurement_system_units/0
  • Cldr.Unit.measurement_system_from_locale/{2, 3}
  • Cldr.Unit.measurement_system_for_territory/1
  • Cldr.Unit.measurement_systems_for_unit/1

I will now work on an update to ex_cldr_html to add a select helper.

I will finish up the work to support #18 too however if you need this functionality now I can provide an interim release.

As of this commit, cldr_html adds the Cldr.HTML.Unit.select/3 function to aid in Phoenix form creation.

For example:

iex> Cldr.HTML.Unit.select(:my_form, :my_field, units: [:foot, :inch], selected: :foot, locale: "th")
....

Still some additional tests and some dialyzer noise to quieten but otherwise in good shape.

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

  • Fix readme example for MyApp.Cldr.Unit.convert/2. Thanks to @DamienFF. Closes #16.

  • Add missing <backend>.convert!/2

Enhancements

  • Supports the definition of custom units in config.exs. Units can be defined and operated on however they cannot yet be localised (that functionality will be in place before release). See the examples in dev.exs.

  • Add Cldr.Unit.display_name/2

  • Add Cldr.Unit.known_units_by_category/0

  • Add Cldr.Unit.known_units_for_category/1

  • Add Cldr.Unit.measurement_system_units/0

  • Add Cldr.Unit.measurement_system_from_locale/{2, 3}

  • Add Cldr.Unit.measurement_system_for_territory/1

  • Add Cldr.Unit.measurement_systems_for_unit/1

  • Improve Cldr.Unit.IncompatibleUnit exception error message

  • Deprecate Cldr.Unit.measurement_systems/0 in favour of Cldr.Unit.measurement_systems_by_territory/0

  • Requires ex_cldr version ~> 2.19 which includes the localised display name of units