.NET Localization, Part 4: Localizing Units
by Satya Komatineni and Elena Tulchinskaya10/28/2002
Introduction
When a Web site is accessible by the international community, one of the considerations is how we present units of measure: Length, Width, Height, Weight, Area, Volume, etc. .NET provides some support by making available a RegionInfo class which identifies whether a locale is mks (metric) or fps (imperial). The goal of this article is to design a
localization scheme for working with units of measure that can satisfy the following requirements:
- Display quantities with appropriate unit labels on Web pages
- Read quantities from entry fields by taking into account the locale of the client and the locale of the server
- Print text labels according to the language of the locale
- Contain a variety of units in the same measurement system. For example: pounds, tons, feet, miles.
- Programmers on the server side usually work with only one set of units, whether metric or imperial
- Region information should be hidden, as much as is possible, from the programmer
On the server side, we will choose either metric or imperial and all calculations are performed in those units. It is only for display purposes that we convert the quantities between units, depending on the user's region.
Let us start with the design of teh basic classes involved and show you how basic OO principles make this process simple and logical.
|
Related Reading
.NET Framework Essentials |
Class Design: Units
At the root of this scheme there are two complementary concepts: a "Measure" and a "Unit". Measure is the quantity or the number value. Unit is its type; e.g., Miles.
public interface IUnit
{
string getName(); // name of the unit
string getShortLabel(); // short label
string getLongLabel(); // long label
// Conversion from a standard unit
double getConversionFactor();
// What is the comparable unit in the other
IUnit getMetricImperialEquivalent(); system
}
The interface IUnit stipulates that every unit that implements IUnit must
have a name (Foot), a short label (ft), and a long label (Foot). A conversion factor
for a unit represents the multiplication factor with respect to a standard unit. This
conversion factor will allow us to convert from one unit to any other unit. The equivalent unit
returned by the method getMetricImperialEquivalent() will allow us to find out
the equivalent unit in the other system. For instance, mile and kilometer are
equivalent, meaning they are comparable measures. This explanation should be
evident when we examine the definition for a "Foot" as an example of unit of
length:
public abstract class LengthUnit : IUnit {}
public abstract class WeightUnit : IUnit {}
public class Foot : LenghtUnit
{
public static Foot self = new Foot();
private Foot()
{
}
public string getName()
{
return "Foot";
}
public string getShortLabel()
{
return "ft";
}
public string getLongLabel()
{
return "Feet";
}
public double getConversionFactor()
{
return 1;
}
public IUnit getMetricImperialEquivalent()
{
return Units.Meter.self;
}
}
Having LengthUnit and WeightUnit will allow us the type safety in conversions. The term Units.Meter.self requires a bit of explanation. For example, if there is only one Meter object in the entire system, there is no need to have multiple objects
representing that type. So this is a singleton with a private constructor. Anyone that refers to it uses the "self" reference
pointing to the single instance. The assumption in Units.Meter.self or Units.Foot.self
is that Units represent the namespace, which is not shown in these examples.
Class Design: Measures
An IMeasure represents the quantitative value of a measure, along with its unit. For example, "50Ft" is a measure which has two parts: the number 50 and
the unit "Ft". The labels for a measure are derived from its underlying unit.
The function getAs() converts a measure from one unit to another, whereas the
function getValueAs() is a shortcut for getAs(), where it returns just the
quantity part of the measure.
public interface IMeasure
{
// What is the numerical value of the measure
double getValue();
// What is the unit of the above numerical
IUnit getUnit();
// Language sensitive short label
string getShortLabel();
// Language sensitive long label
string getLongLabel();
// Convert the number to a different unit
IMeasure getAs(IUnit convUnit);
// simplification of the above method
// just to get the value and not the unit.
double getValueAs(IUnit convUnit);
}
getShortLabel and getLongLabel will simply ask its unit what those values are. A Measure is a good candidate for an abstract class, as we can implement a good portion of that interface leaving only a few essential details for the derived classes.
public abstract class AMeasure:IMeasure
{
// place holder for the numerical value
private double m_value;
// corresponding unit object
private IUnit m_unit;
protected AMeasure(double value, IUnit unit)
{
m_value = value; m_unit = unit;
}
// Language sensitive short label
public string getShortLabel()
{
return m_unit.getShortLabel();
}
// Language sensitive long label
public string getLongLabel()
{
return m_unit.getLongLabel();
}
// Let the derived classes implement this
abstract IMeasure getAs(IUnit convUnit);
// simplifacation of the above method
// just to get the value and not the unit.
public double getValueAs(IUnit convUnit)
{
IMeasure m = getAs(convUnit);
return m.getValue();
}
} // end of AMeasure
The function getAs will return the measure in a different unit. This allows for converting from say, meters to kilometers, pounds to tons, etc. Let us implement measure for Length to investigate the implementation details of derived classes:
public class Length : AMeasure
{
public static LengthUnit standardUnit = Units.Foot.self;
public Length(double value, LegthUnit unit)
:base(value,unit){}
public override IMeasure getAs(IUnit targetUnit)
{
// convert the source unit to standard unit
// convert from the standard unit to the targetunit
if (!targetUnit is LengthUnit)
{
throw Exception("Can only convert between length units");
}
double srcConversionFactor =
getUnit().getConversionFactor();
double targetConversionFactor =
targetUnit.getConversionFactor();
double srcValue = getValue();
double targetValue =
srcValue * srcConversionFactor / targetConversionFactor;
return new Length(targetValue,targetUnit);
}
}
Using the above length example, it is not hard to imagine how one can write measures for weight, area, volume, etc.
It is conceivable to implement the getAs method by the abstract class AMeasure
while delegating to the derived classes the only responsibility of voting for
such a conversion.
Pages: 1, 2 |

