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

Usa 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 artículos. Estos rangos comunican que un valor se encuentra entre dos extremos, proporcionando a los usuarios información acotada en lugar de un único número preciso.

Cuando codificas el separador entre valores de rango de forma fija, asumes que todos los usuarios siguen las mismas convenciones tipográficas. Los hablantes de inglés suelen usar guiones o rayas para rangos, pero otros idiomas usan símbolos o palabras diferentes. 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 formateo de rangos automáticamente. Este método aplica convenciones específicas de la configuración regional 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 diferentes convenciones para expresar rangos. Estas convenciones involucran tanto el símbolo separador como el espaciado alrededor de él.

En inglés estadounidense, los rangos suelen usar una raya sin espacios: 3-5, 100-200. En algunas guías de estilo, aparecen espacios alrededor de la raya: 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 usan "bis" como separador: 3 bis 5, 100 bis 200. Este enfoque basado en palabras hace explícita la relación de rango 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 hispanohablante específica y del contexto.

Al formatear rangos que incluyen moneda o unidades, la complejidad aumenta. Un rango de precios puede 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 formateo manual de rangos requiere conocer estas convenciones e implementar lógica específica para cada 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 numéricos

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 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));
// Output: "3–5"

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

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

El formateador aplica separadores de miles a ambos números del rango y utiliza un separador apropiado entre ellos. Para el inglés estadounidense, esto es una raya ene sin espacios.

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

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

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

const esFormatter = new Intl.NumberFormat("es-ES");
console.log(esFormatter.formatRange(100, 200));
// Output: "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 colocació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));
// Output: "$100 – $200"

console.log(formatter.formatRange(1000, 5000));
// Output: "$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 monetarias.

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));
// Output: "$100 – $200"

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

console.log(deFormatter.formatRange(100, 200));
// Output: "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 del 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 genera "~$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 parezcan 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 preserva 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 garantiza una precisión consistente en toda la visualización del rango.

Puedes combinar el formateo de decimales 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 configuración regional 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 usa comas para separadores de miles y una raya media para el rango. El alemán usa puntos para miles y una raya media. El francés usa espacios para miles y una raya media. El japonés usa comas para miles y una tilde ondulada (~) para el rango.

Estas diferencias se extienden al formateo de moneda.

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 configuración regional 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.

Combinación de formatRange con notación compacta

El formato de rangos funciona con notación compacta, lo que permite 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 del rango. Esto mantiene la salida concisa mientras transmite la información del rango.

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

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 puede no usar notación compacta mientras que el valor final sí, o pueden usar diferentes indicadores de magnitud. El formateador toma estas decisiones según el tamaño de cada valor.

Uso de formatRangeToParts para estilo personalizado

El método formatRangeToParts() devuelve un array de objetos que representan las partes del rango formateado. Esto permite aplicar estilos 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 ambos.

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);
// Output: <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 permite aplicar clases CSS, añadir tooltips o implementar otros comportamientos personalizados mientras se preserva el formato correcto específico de la configuración regional.

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 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"
}

Al trabajar con entrada 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 cadena se analizan como números, preservando la precisión sin problemas de conversión de punto flotante.

Cuándo usar formatRange vs formato manual

Usa formatRange() al mostrar rangos a los usuarios. Esto se aplica a rangos de precios, rangos de cantidad, rangos de medición, números de página o cualquier otro valor delimitado. El método garantiza el formato correcto específico del idioma sin requerir que implementes la lógica del separador.

Evita formatRange() cuando necesites mostrar múltiples valores separados que no estén relacionados semánticamente como un rango. Por ejemplo, mostrar una lista de precios como "$100, $150, $200" debe 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 sea un rango numérico. Si estás mostrando una comparación o diferencia, usa el formato apropiado para ese contexto en lugar del formato de rango.