Cómo formatear rangos como 3-5 o 100-200

Utiliza JavaScript para mostrar rangos numéricos con formato apropiado según la configuración regional

Introducción

Los rangos numéricos aparecen en todas las interfaces de usuario. Los rangos de precios se muestran como $100-$200, los números de página como 1-10, y las estimaciones de cantidad como 3-5 elementos. Estos rangos comunican que un valor se encuentra entre dos puntos extremos, proporcionando a los usuarios información acotada en lugar de un solo número preciso.

Cuando se codifica de forma fija el separador entre valores de rango, se asume que todos los usuarios siguen las mismas convenciones tipográficas. Los hablantes de inglés suelen utilizar guiones o rayas para los rangos, pero otros idiomas utilizan diferentes símbolos o palabras. El alemán usa "bis" entre números, mientras que algunos idiomas colocan espacios alrededor de los separadores.

JavaScript proporciona el método formatRange() en Intl.NumberFormat para manejar el formato de rangos automáticamente. Este método aplica convenciones específicas del idioma tanto a los números como al separador, asegurando que los rangos se muestren correctamente para usuarios de todo el mundo.

Por qué los rangos numéricos necesitan formato específico según la configuración regional

Diferentes culturas desarrollaron distintas convenciones para expresar rangos. Estas convenciones involucran tanto el símbolo separador como el espaciado a su alrededor.

En inglés estadounidense, los rangos típicamente utilizan un guion medio sin espacios: 3-5, 100-200. En algunas guías de estilo, aparecen espacios alrededor del guion: 3 - 5. La convención exacta varía según el contexto y los estándares de publicación.

En alemán, los rangos a menudo utilizan "bis" como separador: 3 bis 5, 100 bis 200. Este enfoque basado en palabras hace que la relación de rango sea explícita en lugar de depender de la puntuación.

En español, los rangos pueden usar un guion como en inglés o la palabra "a": 3-5 o 3 a 5. La elección depende de la región específica de habla hispana y del contexto.

Cuando se formatean rangos que incluyen moneda o unidades, la complejidad aumenta. Un rango de precios podría mostrarse como $100-$200 en inglés estadounidense, pero como 100 €-200 € en alemán, o 100-200 € con el símbolo apareciendo solo una vez. Diferentes configuraciones regionales colocan los símbolos de moneda de manera diferente y manejan la repetición de forma distinta.

El formato manual de rangos requiere conocer estas convenciones e implementar lógica específica según la configuración regional. La API Intl encapsula este conocimiento, aplicando el formato apropiado según la configuración regional.

Uso de formatRange para formatear rangos de números

El método formatRange() acepta dos números y devuelve una cadena formateada que representa el rango. Crea una instancia de Intl.NumberFormat con la configuración regional y las opciones deseadas, luego llama a formatRange() con los valores de inicio y fin.

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatRange(3, 5));
// Resultado: "3–5"

console.log(formatter.formatRange(100, 200));
// Resultado: "100–200"

console.log(formatter.formatRange(1000, 5000));
// Resultado: "1,000–5,000"

El formateador aplica separadores de miles a ambos números en el rango y utiliza un separador apropiado entre ellos. Para el inglés estadounidense, se trata de un guion medio sin espacios.

Puedes formatear el mismo rango para diferentes configuraciones regionales cambiando el identificador de localización.

const usFormatter = new Intl.NumberFormat("en-US");
console.log(usFormatter.formatRange(100, 200));
// Resultado: "100–200"

const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(100, 200));
// Resultado: "100–200"

const esFormatter = new Intl.NumberFormat("es-ES");
console.log(esFormatter.formatRange(100, 200));
// Resultado: "100-200"

Cada configuración regional aplica sus propias convenciones para el separador y el espaciado. La API maneja estos detalles automáticamente según los estándares tipográficos de la configuración regional.

Formateo de rangos de moneda

El formateo de rangos funciona con cualquier opción de formateo de números, incluida la moneda. Cuando formateas rangos de moneda, el formateador maneja tanto la ubicación del símbolo de moneda como el separador de rango.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

console.log(formatter.formatRange(100, 200));
// Resultado: "$100 – $200"

console.log(formatter.formatRange(1000, 5000));
// Resultado: "$1,000 – $5,000"

El formateador coloca el símbolo de moneda antes de cada número en el rango. Esto deja claro que ambos valores representan cantidades de moneda.

Diferentes configuraciones regionales colocan los símbolos de moneda de manera diferente.

const usFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

console.log(usFormatter.formatRange(100, 200));
// Resultado: "$100 – $200"

const deFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR",
  maximumFractionDigits: 0
});

console.log(deFormatter.formatRange(100, 200));
// Resultado: "100–200 €"

El formateador alemán coloca el símbolo del euro después del rango en lugar de antes de cada número. Esto sigue las convenciones tipográficas alemanas para rangos de moneda.

Qué sucede cuando los valores de rango son aproximadamente iguales

Cuando los valores de inicio y fin se redondean al mismo número después del formateo, el formateador colapsa el rango y puede añadir un símbolo de aproximación.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

console.log(formatter.formatRange(100, 200));
// Output: "$100 – $200"

console.log(formatter.formatRange(100, 120));
// Output: "$100 – $120"

console.log(formatter.formatRange(100.2, 100.8));
// Output: "~$100"

El tercer ejemplo muestra dos valores que se redondean al mismo número entero. En lugar de mostrar "$100 – $100", que no transmite información de rango, el formateador muestra "~$100". El símbolo de tilde indica que el valor es aproximado.

Este comportamiento se aplica cuando las opciones de formateo hacen que los valores de inicio y fin aparezcan idénticos.

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 1
});

console.log(formatter.formatRange(2.9, 3.1));
// Output: "~3"

console.log(formatter.formatRange(2.94, 2.96));
// Output: "~2.9"

El formateador inserta el símbolo de aproximación solo cuando es necesario. Cuando los valores se redondean a números diferentes, los muestra como un rango estándar.

Formateo de rangos con decimales

El formateo de rangos conserva la configuración de decimales de las opciones del formateador. Puedes controlar la precisión para ambos valores en el rango.

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(formatter.formatRange(3.5, 5.7));
// Output: "3.50–5.70"

console.log(formatter.formatRange(100, 200));
// Output: "100.00–200.00"

El formateador aplica la configuración de decimales a ambos números en el rango. Esto asegura una precisión consistente en toda la visualización del rango.

Puedes combinar el formateo decimal con moneda u otros estilos.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(formatter.formatRange(99.99, 199.99));
// Output: "$99.99 – $199.99"

Formateo de rangos numéricos en diferentes idiomas

El formateo de rangos se adapta a las convenciones de cada localidad para números, separadores y espaciado.

const enFormatter = new Intl.NumberFormat("en-US");
console.log(enFormatter.formatRange(1000, 5000));
// Output: "1,000–5,000"

const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(1000, 5000));
// Output: "1.000–5.000"

const frFormatter = new Intl.NumberFormat("fr-FR");
console.log(frFormatter.formatRange(1000, 5000));
// Output: "1 000–5 000"

const jaFormatter = new Intl.NumberFormat("ja-JP");
console.log(jaFormatter.formatRange(1000, 5000));
// Output: "1,000~5,000"

El inglés utiliza comas como separadores de miles y un guion medio para el rango. El alemán utiliza puntos para los miles y un guion medio. El francés utiliza espacios para los miles y un guion medio. El japonés utiliza comas para los miles y un guion ondulado (~) para el rango.

Estas diferencias se extienden al formateo de monedas.

const enFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});
console.log(enFormatter.formatRange(100, 200));
// Output: "$100.00 – $200.00"

const deFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR"
});
console.log(deFormatter.formatRange(100, 200));
// Output: "100,00–200,00 €"

const jaFormatter = new Intl.NumberFormat("ja-JP", {
  style: "currency",
  currency: "JPY"
});
console.log(jaFormatter.formatRange(100, 200));
// Output: "¥100~¥200"

Cada localidad aplica sus propias reglas para la colocación del símbolo de moneda, separadores decimales y separadores de rango. La API maneja todas estas variaciones automáticamente.

Combinando formatRange con notación compacta

El formateo de rangos funciona con notación compacta, permitiéndote mostrar rangos como 1K-5K o 1M-5M.

const formatter = new Intl.NumberFormat("en-US", {
  notation: "compact"
});

console.log(formatter.formatRange(1000, 5000));
// Output: "1K–5K"

console.log(formatter.formatRange(1000000, 5000000));
// Output: "1M–5M"

console.log(formatter.formatRange(1200, 4800));
// Output: "1.2K–4.8K"

El formateador aplica notación compacta a ambos valores en el rango. Esto mantiene la salida concisa mientras sigue transmitiendo la información del rango.

Cuando el rango abarca diferentes niveles de magnitud, el formateador maneja cada valor apropiadamente.

const formatter = new Intl.NumberFormat("en-US", {
  notation: "compact"
});

console.log(formatter.formatRange(500, 1500));
// Output: "500–1.5K"

console.log(formatter.formatRange(900000, 1200000));
// Output: "900K–1.2M"

El valor inicial podría no usar notación compacta mientras que el valor final sí, o podrían usar diferentes indicadores de magnitud. El formateador toma estas decisiones basándose en el tamaño de cada valor.

Uso de formatRangeToParts para estilización personalizada

El método formatRangeToParts() devuelve un array de objetos que representan las partes del rango formateado. Esto permite estilizar o manipular componentes individuales del rango.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

const parts = formatter.formatRangeToParts(100, 200);
console.log(parts);

La salida es un array de objetos, cada uno con propiedades type, value y source.

[
  { type: "currency", value: "$", source: "startRange" },
  { type: "integer", value: "100", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "currency", value: "$", source: "endRange" },
  { type: "integer", value: "200", source: "endRange" }
]

La propiedad type identifica lo que representa la parte: símbolo de moneda, entero, separador decimal o texto literal. La propiedad value contiene el texto formateado. La propiedad source indica si la parte pertenece al valor inicial, al valor final o es compartida entre ellos.

Puedes usar estas partes para crear HTML personalizado con diferentes estilos para diferentes componentes.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

const parts = formatter.formatRangeToParts(100, 200);
let html = "";

parts.forEach(part => {
  if (part.type === "currency") {
    html += `<span class="currency-symbol">${part.value}</span>`;
  } else if (part.type === "integer") {
    html += `<span class="amount">${part.value}</span>`;
  } else if (part.type === "literal") {
    html += `<span class="separator">${part.value}</span>`;
  } else {
    html += part.value;
  }
});

console.log(html);
// Salida: <span class="currency-symbol">$</span><span class="amount">100</span><span class="separator"> – </span><span class="currency-symbol">$</span><span class="amount">200</span>

Esta técnica te permite aplicar clases CSS, añadir tooltips o implementar otros comportamientos personalizados mientras preservas el formato correcto específico del idioma.

Manejo de casos extremos con formatRange

El método formatRange() incluye manejo de errores para entradas no válidas. Si cualquiera de los parámetros es undefined, se lanza un TypeError. Si cualquiera de los parámetros es NaN o no puede convertirse a un número, se lanza un RangeError.

const formatter = new Intl.NumberFormat("en-US");

try {
  console.log(formatter.formatRange(100, undefined));
} catch (error) {
  console.log(error.name);
  // Output: "TypeError"
}

try {
  console.log(formatter.formatRange(NaN, 200));
} catch (error) {
  console.log(error.name);
  // Output: "RangeError"
}

Cuando trabajes con entradas de usuario o datos de fuentes externas, valida que los valores sean números válidos antes de pasarlos a formatRange().

El método acepta números, valores BigInt o cadenas que representan números válidos.

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatRange(100, 200));
// Output: "100–200"

console.log(formatter.formatRange(100n, 200n));
// Output: "100–200"

console.log(formatter.formatRange("100", "200"));
// Output: "100–200"

Las entradas de tipo string se analizan como números, preservando la precisión sin problemas de conversión de punto flotante.

Cuándo usar formatRange vs formateo manual

Utiliza formatRange() cuando muestres rangos a los usuarios. Esto aplica a rangos de precios, rangos de cantidad, rangos de medidas, números de página o cualquier otro valor acotado. El método asegura un formateo correcto específico del idioma sin requerir que implementes lógica de separadores.

Evita formatRange() cuando necesites mostrar múltiples valores separados que no están semánticamente relacionados como un rango. Por ejemplo, mostrar una lista de precios como "$100, $150, $200" debería usar llamadas regulares a format() para cada valor en lugar de tratarlos como rangos.

También evita formatRange() cuando la relación entre valores no es un rango numérico. Si estás mostrando una comparación o diferencia, usa el formateo apropiado para ese contexto en lugar del formateo de rangos.