Cómo dividir la salida formateada en partes para aplicar estilos

Usa formatToParts() para acceder a componentes individuales de la salida formateada para estilos personalizados

Introducción

El método format() en los formateadores de JavaScript devuelve cadenas completas como "$1.234,56" o "15 de enero de 2025". Esto funciona bien para visualización simple, pero no puedes aplicar estilos diferentes a partes individuales. No puedes poner el símbolo de moneda en negrita, colorear el nombre del mes de forma diferente o aplicar marcado personalizado a componentes específicos.

JavaScript proporciona el método formatToParts() para resolver este problema. En lugar de devolver una única cadena, devuelve un array de objetos, cada uno representando una parte de la salida formateada. Cada parte tiene un tipo como currency, month o element, y un valor que contiene la cadena real. Luego puedes procesar estas partes para aplicar estilos personalizados, construir diseños complejos o integrar contenido formateado en interfaces de usuario enriquecidas.

El método formatToParts() está disponible en múltiples formateadores Intl, incluyendo NumberFormat, DateTimeFormat, ListFormat, RelativeTimeFormat y DurationFormat. Esto lo convierte en un patrón consistente en todo el formateo de internacionalización en JavaScript.

Por qué las cadenas formateadas no se pueden estilizar fácilmente

Cuando recibes una cadena formateada como "$1,234.56", no puedes identificar fácilmente dónde termina el símbolo de moneda y dónde comienza el número. Diferentes configuraciones regionales colocan los símbolos en posiciones diferentes. Algunas configuraciones regionales usan separadores diferentes. Analizar estas cadenas de manera confiable requiere lógica compleja que duplica las reglas de formateo ya implementadas en la API Intl.

Considera un panel de control que muestra cantidades monetarias con el símbolo de moneda en un color diferente. Con format(), necesitarías:

  1. Detectar qué caracteres son el símbolo de moneda
  2. Tener en cuenta los espacios entre el símbolo y el número
  3. Manejar diferentes posiciones de símbolos en distintas configuraciones regionales
  4. Analizar la cadena cuidadosamente para evitar romper el número

Este enfoque es frágil y propenso a errores. Cualquier cambio en las reglas de formato de la configuración regional rompe tu lógica de análisis.

El mismo problema existe para fechas, listas y otros resultados formateados. No puedes analizar de forma fiable cadenas formateadas para identificar componentes sin reimplementar las reglas de formato específicas de la configuración regional.

El método formatToParts() elimina este problema al proporcionar los componentes por separado. Recibes datos estructurados que te indican exactamente qué parte es cuál, independientemente de la configuración regional.

Cómo funciona formatToParts

El método formatToParts() funciona de forma idéntica a format() excepto por su valor de retorno. Creas un formateador con las mismas opciones, luego llamas a formatToParts() en lugar de format().

El método devuelve un array de objetos. Cada objeto contiene dos propiedades:

  • type: identifica lo que representa la parte, como currency, month o literal
  • value: contiene la cadena real para esa parte

Las partes aparecen en el mismo orden en que aparecerían en la salida formateada. Puedes verificar esto uniendo todos los valores, lo que produce exactamente la misma salida que llamar a format().

Este patrón es consistente en todos los formateadores que admiten formatToParts(). Los tipos de partes específicos varían según el formateador, pero la estructura es siempre la misma.

Dividir números formateados en partes

El formateador NumberFormat proporciona formatToParts() para desglosar números formateados. Esto funciona para números básicos, monedas, porcentajes y otros estilos numéricos.

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

const parts = formatter.formatToParts(1234.56);
console.log(parts);

Esto produce un array de objetos:

[
  { type: "currency", value: "$" },
  { type: "integer", value: "1" },
  { type: "group", value: "," },
  { type: "integer", value: "234" },
  { type: "decimal", value: "." },
  { type: "fraction", value: "56" }
]

Cada objeto identifica lo que representa la parte y proporciona su valor. El tipo currency representa el símbolo de moneda. El tipo integer representa los dígitos del número entero. El tipo group representa el separador de miles. El tipo decimal representa el punto decimal. El tipo fraction representa los dígitos después del decimal.

Puedes verificar que las partes coinciden con la salida formateada:

const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// Output: "$1,234.56"

Las partes concatenadas producen exactamente la misma salida que llamar a format().

Estilizar símbolos de moneda en números formateados

El caso de uso principal para formatToParts() es aplicar diferentes estilos a diferentes componentes. Puedes procesar el array de partes para envolver tipos específicos en elementos HTML.

Poner en negrita el símbolo de moneda:

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

const parts = formatter.formatToParts(1234.56);
const html = parts
  .map(part => {
    if (part.type === "currency") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>$</strong>1,234.56"

Este enfoque funciona para cualquier lenguaje de marcado. Puedes generar HTML, JSX o cualquier otro formato procesando el array de partes.

Estilizar las porciones decimales de manera diferente:

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

const parts = formatter.formatToParts(1234.5);
const html = parts
  .map(part => {
    if (part.type === "decimal" || part.type === "fraction") {
      return `<span class="text-gray-500">${part.value}</span>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "1,234<span class="text-gray-500">.50</span>"

Este patrón es común en visualizaciones de precios donde la porción decimal aparece más pequeña o más clara.

Dividir fechas formateadas en partes

El formateador DateTimeFormat proporciona formatToParts() para desglosar fechas y horas formateadas.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);

Esto produce un array de objetos:

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2025" }
]

El tipo month representa el nombre o número del mes. El tipo day representa el día del mes. El tipo year representa el año. El tipo literal representa espaciado, puntuación u otro texto insertado por el formateador.

Estilizar nombres de meses en fechas formateadas

Puedes aplicar estilos personalizados a los componentes de fecha usando el mismo patrón que con los números.

Poner en negrita el nombre del mes:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "month") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>January</strong> 15, 2025"

Estilizar múltiples componentes de fecha:

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const html = parts
  .map(part => {
    switch (part.type) {
      case "weekday":
        return `<span class="font-bold">${part.value}</span>`;
      case "month":
        return `<span class="text-blue-600">${part.value}</span>`;
      case "year":
        return `<span class="text-gray-500">${part.value}</span>`;
      default:
        return part.value;
    }
  })
  .join("");

console.log(html);
// Output: "<span class="font-bold">Wednesday</span>, <span class="text-blue-600">January</span> 15, <span class="text-gray-500">2025</span>"

Este control granular permite un estilo preciso para cada componente.

Dividir listas formateadas en partes

El formateador ListFormat proporciona formatToParts() para desglosar listas formateadas.

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
console.log(parts);

Esto produce un array de objetos:

[
  { type: "element", value: "apples" },
  { type: "literal", value: ", " },
  { type: "element", value: "oranges" },
  { type: "literal", value: ", and " },
  { type: "element", value: "bananas" }
]

El tipo element representa cada elemento de la lista. El tipo literal representa separadores y conjunciones añadidos por el formateador.

Estilizando elementos de lista individualmente

Puedes aplicar estilos personalizados a los elementos de lista usando el mismo patrón.

Poniendo elementos de lista en negrita:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
const html = parts
  .map(part => {
    if (part.type === "element") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>apples</strong>, <strong>oranges</strong>, and <strong>bananas</strong>"

Estilizando elementos de lista específicos:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);

let itemIndex = 0;
const html = parts
  .map(part => {
    if (part.type === "element") {
      const currentIndex = itemIndex++;
      if (currentIndex === 0) {
        return `<span class="text-green-600">${part.value}</span>`;
      }
      return part.value;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<span class="text-green-600">apples</span>, oranges, and bananas"

Este enfoque te permite resaltar elementos específicos mientras mantienes el formato apropiado según la configuración regional.

Dividiendo tiempos relativos formateados en partes

El formateador RelativeTimeFormat proporciona formatToParts() para desglosar expresiones de tiempo relativo.

const formatter = new Intl.RelativeTimeFormat("en-US", {
  numeric: "auto"
});

const parts = formatter.formatToParts(-1, "day");
console.log(parts);

Esto produce un array de objetos:

[
  { type: "literal", value: "yesterday" }
]

Para tiempos relativos numéricos:

const formatter = new Intl.RelativeTimeFormat("en-US", {
  numeric: "always"
});

const parts = formatter.formatToParts(-3, "day");
console.log(parts);
// [
//   { type: "integer", value: "3" },
//   { type: "literal", value: " days ago" }
// ]

El tipo integer representa el valor numérico. El tipo literal representa la unidad de tiempo relativo y la dirección.

Dividiendo duraciones formateadas en partes

El formateador DurationFormat proporciona formatToParts() para desglosar duraciones formateadas.

const formatter = new Intl.DurationFormat("en-US", {
  style: "long"
});

const parts = formatter.formatToParts({
  hours: 2,
  minutes: 30,
  seconds: 15
});
console.log(parts);

Esto produce un array de objetos similar a:

[
  { type: "integer", value: "2" },
  { type: "literal", value: " hours, " },
  { type: "integer", value: "30" },
  { type: "literal", value: " minutes, " },
  { type: "integer", value: "15" },
  { type: "literal", value: " seconds" }
]

El tipo integer representa valores numéricos. El tipo literal representa nombres de unidades y separadores.

Construir HTML a partir de partes formateadas

Puedes crear una función reutilizable que procese las partes y aplique reglas de estilo de forma consistente.

function formatWithStyles(parts, styleMap) {
  return parts
    .map(part => {
      const style = styleMap[part.type];
      if (style) {
        return `<span class="${style}">${part.value}</span>`;
      }
      return part.value;
    })
    .join("");
}

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

const parts = numberFormatter.formatToParts(1234.56);
const html = formatWithStyles(parts, {
  currency: "font-bold text-gray-700",
  integer: "text-2xl",
  fraction: "text-sm text-gray-500"
});

console.log(html);
// Output: "<span class="font-bold text-gray-700">$</span><span class="text-2xl">1</span>,<span class="text-2xl">234</span>.<span class="text-sm text-gray-500">56</span>"

Este patrón separa las reglas de estilo de la lógica de formateo, facilitando su mantenimiento y reutilización.

Comprender el orden de las partes específico de la configuración regional

El array de partes mantiene las reglas de formato específicas de la configuración regional automáticamente. Diferentes configuraciones regionales colocan los componentes en diferentes órdenes y usan diferentes formatos, pero formatToParts() maneja estas diferencias.

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

console.log(usdFormatter.formatToParts(1234.56));
// [
//   { type: "currency", value: "$" },
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

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

console.log(eurFormatter.formatToParts(1234.56));
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "." },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "," },
//   { type: "fraction", value: "56" },
//   { type: "literal", value: " " },
//   { type: "currency", value: "€" }
// ]

El formateo alemán coloca la moneda después del número con un espacio. El separador de grupo es un punto y el separador decimal es una coma. Tu código de estilo procesa el array de partes de la misma manera independientemente de la configuración regional, y el formateo se adapta automáticamente.

Crear visualizaciones formateadas accesibles

Puedes usar formatToParts() para añadir atributos de accesibilidad a la salida formateada. Esto ayuda a los lectores de pantalla a anunciar los valores correctamente.

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

function formatAccessible(number) {
  const parts = formatter.formatToParts(number);
  const formatted = parts.map(part => part.value).join("");

  return `<span aria-label="${number} US dollars">${formatted}</span>`;
}

console.log(formatAccessible(1234.56));
// Output: "<span aria-label="1234.56 US dollars">$1,234.56</span>"

Esto garantiza que los lectores de pantalla anuncien tanto el valor de visualización formateado como el valor numérico subyacente con el contexto adecuado.

Combinar formatToParts con componentes de framework

Los frameworks modernos como React pueden usar formatToParts() para construir componentes de manera eficiente.

function CurrencyDisplay({ value, locale, currency }) {
  const formatter = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency
  });

  const parts = formatter.formatToParts(value);

  return (
    <span className="currency-display">
      {parts.map((part, index) => {
        if (part.type === "currency") {
          return <strong key={index}>{part.value}</strong>;
        }
        if (part.type === "fraction" || part.type === "decimal") {
          return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
        }
        return <span key={index}>{part.value}</span>;
      })}
    </span>
  );
}

Este componente aplica diferentes estilos a diferentes partes mientras mantiene el formateo adecuado para cualquier configuración regional y moneda.

Cuándo usar formatToParts

Usa format() cuando necesites una cadena formateada simple sin ninguna personalización. Este es el caso común para la mayoría de los escenarios de visualización.

Usa formatToParts() cuando necesites:

  • Aplicar diferentes estilos a diferentes partes de la salida formateada
  • Construir HTML o JSX con contenido formateado
  • Añadir atributos o metadatos a componentes específicos
  • Integrar salida formateada en diseños complejos
  • Procesar salida formateada de forma programática
  • Crear diseños visuales personalizados que requieran control granular

El método formatToParts() tiene un poco más de sobrecarga que format() porque crea un array de objetos en lugar de una sola cadena. Esta diferencia es insignificante para aplicaciones típicas, pero si formateas miles de valores por segundo, format() tiene mejor rendimiento.

Para la mayoría de aplicaciones, elige según tus necesidades de estilo en lugar de preocupaciones de rendimiento. Si no necesitas personalizar la salida, usa format(). Si necesitas estilo o marcado personalizado, usa formatToParts().

Tipos de partes comunes entre formateadores

Diferentes formateadores producen diferentes tipos de partes, pero algunos tipos aparecen en múltiples formateadores:

  • literal: espaciado, puntuación u otro texto añadido por el formateo. Aparece en fechas, números, listas y duraciones.
  • integer: dígitos de números enteros. Aparece en números, tiempos relativos y duraciones.
  • decimal: separador decimal. Aparece en números.
  • fraction: dígitos decimales. Aparece en números.

Los tipos específicos de formateador incluyen:

  • Números: currency, group, percentSign, minusSign, plusSign, unit, compact, exponentInteger
  • Fechas: weekday, era, year, month, day, hour, minute, second, dayPeriod, timeZoneName
  • Listas: element
  • Tiempos relativos: los valores numéricos aparecen como integer, el texto aparece como literal

Comprender estos tipos te ayuda a escribir código de estilo que maneje correctamente cualquier salida de formateador.