Expanding a business globally requires establishing a local presence to effectively reach and engage the intended audience. As part of our journey to broaden our horizons and tap into new markets, we recognized the importance of internationalization (i18n) and localization (l10n). Supporting multiple languages and metric systems is essential for providing a seamless user experience across different regions. Initially, we operated with a single language and metric system, but to thrive in our global expansion and North American pilots, we needed to integrate imperial metrics, diverse fuel measurement units, and local languages. This posed significant challenges given our existing codebase, making it imperative to develop a centralized and scalable implementation to support new regions without disrupting ongoing development.
Understanding Internationalization vs. Localization
Before delving into the details of our solution, it is essential to distinguish between internationalization (i18n) and localization (l10n). Internationalization involves designing software so it can be adapted to various regions without engineering changes. Localization, on the other hand, is the process of adapting software to a specific region, including translating text and adjusting formats.
Initial Challenges in Unit Conversion
At the outset, our system lacked a centralized implementation for unit conversions. We had to manage different metric systems in various parts of the code, leading to fragmented and inefficient codebases. Each version of our application required distinct handling for languages and metric systems, making updates cumbersome and prone to errors. This decentralized approach hindered our ability to swiftly adapt to new markets.
Transitioning to a Centralized and Scalable Implementation
To address these challenges, we developed a centralized implementation leveraging the `convert-units` npm library for conventional unit conversions. This includes conversions for fuel (liters to gallons), distance (kilometers to miles), and more. We created custom hooks that provide wrappers for key entities like Fuel, Distance, Mileage, and Speed. These hooks accept values in the metric system and manage the necessary conversions based on the current configuration.
Entities and Their Implementations
We categorized our entities into two types: Primary Entities and Custom Entities.
- Primary Entities: These entities depend on the `convert-units` library and support all the standard conversions. For example, the Volume entity manages standard conversions such as liters to gallons without additional customization. In our case, it will simply take care of conversions like Lts → Gal.
- Custom Entities: These entities incorporate custom business logic tailored to specific use cases. For instance, in the US, natural gas is sold in Gallon Equivalents, which can be Diesel Gallon Equivalents (DGE) or Gasoline Gallon Equivalents (GGE). We developed a custom entity called ‘Fuel’ to handle these unique conversions. The Fuel wrapper accepts values in liters or kilograms (for CNG or LNG vehicles) and generates a converter function that supports conversions like liters to gallons and kilograms to DGE or GGE.
Implementing the New Approach
To effectively utilize the new implementation, follow these steps:
- Generate Entities Based on Account Configuration: Entities will be created according to the context of the account configuration, ensuring that they align with the specific needs and settings of the user.
- Using Unit Symbols: The following code snippet demonstrates how to use the unit symbols from the generated entities:
const getTitles = (options) => {
const { Distance, Speed, Fuel, Mileage } = getUnits(options.config);
const titles = [
{ name: "Vehicle No.", size: 30 },
{ name: `Distance (${Distance.unit.symbol})`, size: 30 },
{ name: `Fuel Consumed (${Fuel.unit.symbol})`, size: 30 },
{ name: `Mileage (${Mileage.unit.symbol})`, size: 20 },
{ name: `Speed (${Speed.unit.symbol})`, size: 30 },
];
return titles;
};
- Applying Converter Functions: Here’s how to use the converter functions in practice. The Fuel function acts as the converter, accepting values in various units and handling the necessary conversions internally:
const { Fuel } = getUnits(options.config); // considering imperial units
const fuel_value = 15; // fuel value in Liters
// Convert fuel value to gallons with rounding off
Fuel(fuel_value); // returns 4, i.e., rounded off converter value in Gallons
// Convert fuel value to gallons with 2 decimal places
Fuel(fuel_value, { decimalPlaces: 2 }); // returns 3.96, converted value in Gallons up to 2 decimal precision
// Convert fuel value to gallons with a specific string format
Fuel(fuel_value, { stringFormat: " " }); // returns "4 Gal", converted value as per provided template
// Convert fuel value to milliliters, overriding config-wise conversions
Fuel(fuel_value, { requiredIn: 'ml' }); // returns 15000, requiredIn will override the config-wise conversions and convert to milliliters
We have implemented various entities, including:
- Distance
- Volume
- Mass
- Pressure
- Speed
- Engine Hours
- Mileage
Benefits of the Centralized Implementation
Our new implementation offers several advantages:
- Customizable Options: It offers options like decimal place customization, locale-based strings, and templates.
- Centralized Updates: Conversion factors can be updated at a single point, simplifying the introduction of new metric systems.
- Consistent Unit Symbols: Using direct unit symbols from entities prevents hardcoded values, allowing easy updates (e.g., changing ‘Lts’ to ‘Ltr’ through configuration).
- Simplified Code: Handling unit conversions with a single wrapper function call reduces complexity and potential errors.
Limitations and Considerations
Despite its numerous benefits, our implementation does have some limitations:
- Complex Derived Entities: Certain complex derived entities may not be supported, necessitating custom conversion functions.
- Developer Understanding: It is crucial for developers to understand when to use specific wrapper functions. Wrappers like Fuel and Volume may seem similar but have different contexts, and misapplication could cause issues.
The Path Forward: Scalability and Adaptability
In the rapidly evolving global market, having a scalable and adaptable implementation is essential for internationalizing a platform. Whether through a feature-rich conversion library or a bespoke framework, the solution must be manageable and flexible to accommodate future requirements. Our centralized approach ensures that we can efficiently support new regions and metric systems, facilitating smooth and uninterrupted global expansion.
By centralizing our unit conversions and making them easily configurable, we have established a robust system that caters to the needs of our growing international audience. Our next step is to tackle another critical aspect of global expansion: efficient and comprehensive language translation across our platform. Stay tuned for our upcoming blog, where we delve into the implementation of a centralized translation system and the benefits it brings to our international operations.
We’re looking forward to meeting you