Skip to content

Instantly share code, notes, and snippets.

@renoirb
Created October 22, 2025 15:51
Show Gist options
  • Save renoirb/46816e7b2e4b9ed82b079c29b81f11c7 to your computer and use it in GitHub Desktop.
Save renoirb/46816e7b2e4b9ed82b079c29b81f11c7 to your computer and use it in GitHub Desktop.
Managing NumberFormat in an abstract way with Proxies to support numbers, date, temperature, etc.

Rationale

To avoid having too many Intl.NumberFormat and Intl.<...> adjacent spreaded around the code base and considering that not all browsers supports it for sure, and that there's the possibility that the code base making assumption that asking for using a locale (e.g. fr-CA) from the code base will be handled gracefully from a browser that doesn't have the language installed.

Let's have a way to have formatting created by a predictable way. Allow flexibility to have almost complete data configuration object we can get and hints about what's missing if we use them directly as they're returned.

const NUMBER_FORMAT_OPTIONS_ALIASES = [
//
'temperature',
] as const;
export type NumberFormatAlias = (typeof NUMBER_FORMAT_OPTIONS_ALIASES)[number];
export const createNumberFormatOptionsFor = (
alias: NumberFormatAlias
): Intl.NumberFormatOptions => {
if (!NUMBER_FORMAT_OPTIONS_ALIASES.includes(alias)) {
const these = NUMBER_FORMAT_OPTIONS_ALIASES.join(', ');
const message = `Unsupported alias "${alias}", we only support ${these}`;
throw new Error(message);
}
let out: Intl.NumberFormatOptions = {};
const _real: Intl.NumberFormatOptions = {};
const createRequiredKeyProxyHandler = (keyPath: string) => () =>
new Error(`Mising argument for "${keyPath}" in NumberFormatOptions`);
/**
* OK, gotta research more than quick experiment
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
*/
const handler: ProxyHandler<Intl.NumberFormatOptions> = {
get(target, prop, receiver) {
},
};
switch (true) {
case alias === 'temperature':
{
Object.assign(_real, {
style: 'unit',
unit: createRequiredKeyProxyHandler('unit'),
});
out = new Proxy(out, handler);
}
break;
}
return out;
};
@renoirb
Copy link
Author

renoirb commented Oct 22, 2025

This Gist is a follow up on continuing for other number formats than temperature so we get a uniform Intl.NumberFormatOptions and don't have too many of the following with just a slight variation.

const TEMPERATURE_UNITS = [
	/*                    */
	'celsius',
	'fahrenheit',
] as const;

export type NumberFormatStyleUnitTemperature =
	(typeof TEMPERATURE_UNITS)[number];

export interface ValueTemperatureProps {
	/**
	 * Which unit to use
	 *
	 * @default 'celcius'
	 */
	unit?: NumberFormatStyleUnitTemperature;
	value: number;
	/**
	 * Which locale to format the value
	 *
	 * @default 'fr-CA'
	 */
	locale?: Intl.LocalesArgument;
}

export const formatValueTemperature = ({
	locale = 'fr-CA',
	unit = 'celsius',
	value,
}: ValueTemperatureProps): string => {
	if (!TEMPERATURE_UNITS.includes(unit)) {
		const message = `Invalid unit "${unit}`;
		throw new Error(message);
	}
	const initialValue = `${value} °${unit[0].toUpperCase()}`;
	let formatted = initialValue;
	try {
		/**
		 * @see {@link https://v8.dev/features/intl-numberformat#units}
		 */
		const numberFormatOptions: Intl.NumberFormatOptions = {
			style: 'unit',
			unit,
		};
		const formatter = new Intl.NumberFormat(locale, numberFormatOptions);
		const attempt = formatter.format(value);
		formatted = attempt;
	} catch {
		// Nothing
	}

	return formatted;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment