Cómo formatear listas con o en JavaScript
Usa Intl.ListFormat con tipo disyunción para formatear alternativas correctamente en cualquier idioma
Introducción
Las aplicaciones a menudo presentan a los usuarios opciones o alternativas. Un componente de carga de archivos acepta archivos "PNG, JPEG o SVG". Un formulario de pago permite "tarjeta de crédito, tarjeta de débito o PayPal" como métodos de pago. Un mensaje de error sugiere corregir "nombre de usuario, contraseña o dirección de correo electrónico" para resolver fallos de autenticación.
Estas listas utilizan "o" para indicar alternativas. Formatearlas manualmente con concatenación de cadenas falla en otros idiomas porque diferentes idiomas tienen diferentes reglas de puntuación, diferentes palabras para "o" y diferentes convenciones de colocación de comas. La API Intl.ListFormat con el tipo de disyunción formatea estas listas de alternativas correctamente para cualquier idioma.
Qué son las listas disyuntivas
Una lista disyuntiva presenta alternativas donde típicamente se aplica una opción. La palabra "disyunción" significa separación o alternativas. En español, las listas disyuntivas utilizan "o" como conjunción:
const paymentMethods = ["tarjeta de crédito", "tarjeta de débito", "PayPal"];
// Resultado deseado: "tarjeta de crédito, tarjeta de débito o PayPal"
Esto difiere de las listas conjuntivas que utilizan "y" para indicar que todos los elementos se aplican juntos. Las listas disyuntivas comunican elección, las listas conjuntivas comunican combinación.
Contextos comunes para listas disyuntivas incluyen opciones de pago, restricciones de formato de archivo, sugerencias de solución de problemas, alternativas de filtros de búsqueda y cualquier interfaz donde los usuarios seleccionan una opción entre múltiples posibilidades.
Por qué falla el formateo manual
Los hablantes de inglés escriben listas disyuntivas como "A, B, or C" con comas entre elementos y "or" antes del último elemento. Este patrón falla en otros idiomas:
// Patrón codificado en inglés
const items = ["apple", "orange", "banana"];
const text = items.slice(0, -1).join(", ") + ", or " + items[items.length - 1];
// "apple, orange, or banana"
Este código produce resultados incorrectos en español, francés, alemán y la mayoría de otros idiomas. Cada idioma tiene reglas de formato distintas para listas disyuntivas.
En español se usa "o" sin coma antes:
Esperado: "manzana, naranja o plátano"
El patrón inglés produce: "manzana, naranja, or plátano"
En francés se usa "ou" sin coma antes:
Esperado: "pomme, orange ou banane"
El patrón inglés produce: "pomme, orange, or banane"
En alemán se usa "oder" sin coma antes:
Esperado: "Apfel, Orange oder Banane"
El patrón inglés produce: "Apfel, Orange, or Banane"
En japonés se usa la partícula "か" (ka) con diferente puntuación:
Esperado: "りんご、オレンジ、またはバナナ"
El patrón inglés produce: "りんご、オレンジ、 or バナナ"
Estas diferencias van más allá del simple reemplazo de palabras. La colocación de puntuación, las reglas de espaciado y las partículas gramaticales varían según el idioma. La concatenación manual de cadenas no puede manejar esta complejidad.
Uso de Intl.ListFormat con tipo de disyunción
La API Intl.ListFormat formatea listas según reglas específicas del idioma. Establece la opción type a "disjunction" para formatear listas alternativas:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const paymentMethods = ["credit card", "debit card", "PayPal"];
console.log(formatter.format(paymentMethods));
// "credit card, debit card, or PayPal"
El formateador maneja arrays de cualquier longitud:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
console.log(formatter.format([]));
// ""
console.log(formatter.format(["credit card"]));
// "credit card"
console.log(formatter.format(["credit card", "PayPal"]));
// "credit card or PayPal"
console.log(formatter.format(["credit card", "debit card", "PayPal"]));
// "credit card, debit card, or PayPal"
La API aplica automáticamente la puntuación y conjunción correctas para cada caso.
Comprensión de los estilos de disyunción
La opción style controla la verbosidad del formato. Existen tres estilos: long, short y narrow. El estilo long es el predeterminado.
const items = ["email", "phone", "SMS"];
const long = new Intl.ListFormat("en", {
type: "disjunction",
style: "long"
});
console.log(long.format(items));
// "email, phone, or SMS"
const short = new Intl.ListFormat("en", {
type: "disjunction",
style: "short"
});
console.log(short.format(items));
// "email, phone, or SMS"
const narrow = new Intl.ListFormat("en", {
type: "disjunction",
style: "narrow"
});
console.log(narrow.format(items));
// "email, phone, or SMS"
En inglés, los tres estilos producen resultados idénticos para listas disyuntivas. Otros idiomas muestran más variación. El alemán usa "oder" en estilo largo y puede abreviar en estilo estrecho. Las diferencias se hacen más evidentes en idiomas con múltiples niveles de formalidad o palabras de conjunción más largas.
El estilo estrecho típicamente elimina espacios o usa conjunciones más cortas para ahorrar espacio en diseños limitados. Utiliza el estilo largo para texto estándar, el estilo corto para visualizaciones moderadamente compactas y el estilo estrecho para restricciones de espacio ajustadas como interfaces móviles o tablas compactas.
Cómo aparecen las listas disyuntivas en diferentes idiomas
Cada idioma formatea las listas disyuntivas según sus propias convenciones. Intl.ListFormat maneja estas diferencias automáticamente.
El inglés usa comas con "or":
const en = new Intl.ListFormat("en", { type: "disjunction" });
console.log(en.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG, or SVG"
El español usa comas con "o" y sin coma antes de la conjunción final:
const es = new Intl.ListFormat("es", { type: "disjunction" });
console.log(es.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG o SVG"
El francés usa comas con "ou" y sin coma antes de la conjunción final:
const fr = new Intl.ListFormat("fr", { type: "disjunction" });
console.log(fr.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG ou SVG"
El alemán usa comas con "oder" y sin coma antes de la conjunción final:
const de = new Intl.ListFormat("de", { type: "disjunction" });
console.log(de.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG oder SVG"
El japonés usa puntuación diferente y partículas:
const ja = new Intl.ListFormat("ja", { type: "disjunction" });
console.log(ja.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG、またはSVG"
El chino usa signos de puntuación chinos:
const zh = new Intl.ListFormat("zh", { type: "disjunction" });
console.log(zh.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG或SVG"
Estos ejemplos muestran cómo la API se adapta a las convenciones gramaticales y de puntuación de cada idioma. El mismo código funciona en todos los idiomas cuando se proporciona la configuración regional adecuada.
Formateo de opciones de pago
Los formularios de pago presentan múltiples opciones de métodos de pago. Formátelos con listas disyuntivas:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getPaymentMessage(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return `Pay with ${formatter.format(methods)}.`;
}
const methods = ["credit card", "debit card", "PayPal", "Apple Pay"];
console.log(getPaymentMessage(methods));
// "Pay with credit card, debit card, PayPal, or Apple Pay."
Para aplicaciones internacionales, pase la configuración regional del usuario:
const userLocale = navigator.language; // p. ej., "fr-FR"
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
function getPaymentMessage(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return `Pay with ${formatter.format(methods)}.`;
}
Este enfoque funciona en flujos de pago, selectores de métodos de pago y cualquier interfaz donde los usuarios elijan cómo pagar.
Restricciones de formato para la carga de archivos
Los componentes de carga de archivos especifican qué tipos de archivos acepta el sistema:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getAcceptedFormatsMessage(formats) {
if (formats.length === 0) {
return "No file formats accepted";
}
if (formats.length === 1) {
return `Accepted format: ${formats[0]}`;
}
return `Accepted formats: ${formatter.format(formats)}`;
}
const imageFormats = ["PNG", "JPEG", "SVG", "WebP"];
console.log(getAcceptedFormatsMessage(imageFormats));
// "Accepted formats: PNG, JPEG, SVG, or WebP"
const documentFormats = ["PDF", "DOCX"];
console.log(getAcceptedFormatsMessage(documentFormats));
// "Accepted formats: PDF or DOCX"
Este patrón funciona para cargas de imágenes, envíos de documentos y cualquier entrada de archivo con restricciones de formato.
Formateo de sugerencias para resolución de problemas
Los mensajes de error a menudo sugieren múltiples formas de resolver un problema. Presenta estas sugerencias como listas disyuntivas:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getAuthenticationError(missingFields) {
if (missingFields.length === 0) {
return "Authentication failed";
}
return `Please check your ${formatter.format(missingFields)} and try again.`;
}
console.log(getAuthenticationError(["username", "password"]));
// "Please check your username or password and try again."
console.log(getAuthenticationError(["email", "username", "password"]));
// "Please check your email, username, or password and try again."
La lista disyuntiva aclara que los usuarios necesitan corregir cualquiera de los campos mencionados, no necesariamente todos ellos.
Formateo de alternativas de filtros de búsqueda
Las interfaces de búsqueda muestran filtros activos. Cuando los filtros presentan alternativas, utiliza listas disyuntivas:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getFilterSummary(filters) {
if (filters.length === 0) {
return "No filters applied";
}
if (filters.length === 1) {
return `Showing results for: ${filters[0]}`;
}
return `Showing results for: ${formatter.format(filters)}`;
}
const categories = ["Electronics", "Books", "Clothing"];
console.log(getFilterSummary(categories));
// "Showing results for: Electronics, Books, or Clothing"
Esto funciona para filtros de categorías, selecciones de etiquetas y cualquier interfaz de filtro donde los valores seleccionados representan alternativas en lugar de combinaciones.
Reutilización de formateadores para mejorar el rendimiento
La creación de instancias de Intl.ListFormat tiene una sobrecarga. Crea formateadores una vez y reutilízalos:
// Crear una vez a nivel de módulo
const disjunctionFormatter = new Intl.ListFormat("en", { type: "disjunction" });
// Reutilizar en múltiples funciones
function formatPaymentMethods(methods) {
return disjunctionFormatter.format(methods);
}
function formatFileTypes(types) {
return disjunctionFormatter.format(types);
}
function formatErrorSuggestions(suggestions) {
return disjunctionFormatter.format(suggestions);
}
Para aplicaciones que soportan múltiples locales, almacena los formateadores en una caché:
const formatters = new Map();
function getDisjunctionFormatter(locale) {
if (!formatters.has(locale)) {
formatters.set(
locale,
new Intl.ListFormat(locale, { type: "disjunction" })
);
}
return formatters.get(locale);
}
const formatter = getDisjunctionFormatter("en");
console.log(formatter.format(["A", "B", "C"]));
// "A, B, or C"
Este patrón reduce los costos de inicialización mientras soporta múltiples locales en toda la aplicación.
Uso de formatToParts para renderizado personalizado
El método formatToParts() devuelve un array de objetos que representan cada parte de la lista formateada. Esto permite aplicar estilos personalizados:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const parts = formatter.formatToParts(["PNG", "JPEG", "SVG"]);
console.log(parts);
// [
// { type: "element", value: "PNG" },
// { type: "literal", value: ", " },
// { type: "element", value: "JPEG" },
// { type: "literal", value: ", or " },
// { type: "element", value: "SVG" }
// ]
Cada parte tiene un type y un value. El type puede ser "element" para los elementos de la lista o "literal" para la puntuación y las conjunciones.
Utiliza esto para aplicar diferentes estilos a elementos y literales:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const formats = ["PNG", "JPEG", "SVG"];
const html = formatter.formatToParts(formats)
.map(part => {
if (part.type === "element") {
return `<code>${part.value}</code>`;
}
return part.value;
})
.join("");
console.log(html);
// "<code>PNG</code>, <code>JPEG</code>, or <code>SVG</code>"
Este enfoque mantiene la puntuación y las conjunciones correctas según el locale mientras aplica una presentación personalizada a los elementos reales.
Soporte y compatibilidad de navegadores
Intl.ListFormat funciona en todos los navegadores modernos desde abril de 2021. El soporte incluye Chrome 72+, Firefox 78+, Safari 14.1+ y Edge 79+.
Verifica el soporte antes de usar la API:
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
return formatter.format(items);
} else {
// Alternativa para navegadores antiguos
return items.join(", ");
}
Para una compatibilidad más amplia, utiliza un polyfill como @formatjs/intl-listformat. Instálalo solo donde sea necesario:
if (typeof Intl.ListFormat === "undefined") {
await import("@formatjs/intl-listformat/polyfill");
}
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
Dado el soporte actual de los navegadores, la mayoría de las aplicaciones pueden usar Intl.ListFormat directamente sin polyfills.
Errores comunes a evitar
Usar el tipo conjunction en lugar de disjunction produce un significado incorrecto:
// Incorrecto: sugiere que todos los métodos son requeridos
const wrong = new Intl.ListFormat("en", { type: "conjunction" });
console.log(`Pay with ${wrong.format(["credit card", "debit card"])}`);
// "Pay with credit card and debit card"
// Correcto: sugiere elegir un método
const correct = new Intl.ListFormat("en", { type: "disjunction" });
console.log(`Pay with ${correct.format(["credit card", "debit card"])}`);
// "Pay with credit card or debit card"
Crear nuevos formateadores repetidamente desperdicia recursos:
// Ineficiente
function formatOptions(options) {
return new Intl.ListFormat("en", { type: "disjunction" }).format(options);
}
// Eficiente
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function formatOptions(options) {
return formatter.format(options);
}
Codificar "o" directamente en las cadenas impide la localización:
// Falla en otros idiomas
const text = items.join(", ") + ", or other options";
// Funciona en todos los idiomas
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
const allItems = [...items, "other options"];
const text = formatter.format(allItems);
No manejar arrays vacíos puede causar resultados inesperados:
// Defensivo
function formatPaymentMethods(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return formatter.format(methods);
}
Aunque format([]) devuelve una cadena vacía, el manejo explícito de estados vacíos mejora la experiencia del usuario.
Cuándo usar listas disyuntivas
Usa listas disyuntivas cuando presentes alternativas u opciones donde típicamente se aplica una opción. Esto incluye selección de métodos de pago, restricciones de formato de archivo, sugerencias de error de autenticación, opciones de filtros de búsqueda y elecciones de tipo de cuenta.
No uses listas disyuntivas cuando todos los elementos deben aplicarse juntos. Usa listas de conjunción en su lugar. Por ejemplo, "Nombre, correo electrónico y contraseña son obligatorios" usa conjunción porque todos los campos deben proporcionarse, no solo uno.
No uses listas disyuntivas para enumeraciones neutrales sin implicaciones de elección. Las medidas y especificaciones técnicas típicamente usan listas de unidades en lugar de disyunción o conjunción.
La API reemplaza los patrones de concatenación manual de cadenas para alternativas. Cada vez que escribirías código que une elementos con "o" para texto visible al usuario, considera si Intl.ListFormat con tipo de disyunción proporciona mejor soporte de localización.