JavaScript에서 '또는'가 포함된 목록을 포맷하는 방법
type disjunction이 있는 Intl.ListFormat를 사용하여 모든 언어에서 대안을 올바르게 포맷하세요
소개
애플리케이션은 종종 사용자에게 선택지나 대안을 제시합니다. 파일 업로드 컴포넌트는 "PNG, JPEG 또는 SVG" 파일을 허용합니다. 결제 양식은 결제 방법으로 "신용 카드, 직불 카드 또는 PayPal"을 허용합니다. 오류 메시지는 인증 실패를 해결하기 위해 "사용자 이름, 비밀번호 또는 이메일 주소"를 수정하도록 제안합니다.
이러한 목록은 대안을 나타내기 위해 "또는"을 사용합니다. 문자열 연결로 수동 형식을 지정하면 다른 언어에서는 작동하지 않습니다. 이는 언어마다 구두점 규칙, "또는"에 해당하는 단어, 쉼표 배치 규칙이 다르기 때문입니다. 선언적 유형의 Intl.ListFormat API는 이러한 대안 목록을 모든 언어에 맞게 올바르게 형식화합니다.
이접적 목록이란 무엇인가
이접적 목록은 일반적으로 하나의 옵션이 적용되는 대안을 제시합니다. "이접"이라는 단어는 분리 또는 대안을 의미합니다. 영어에서 이접적 목록은 "or"를 접속사로 사용합니다:
const paymentMethods = ["credit card", "debit card", "PayPal"];
// 원하는 출력: "credit card, debit card, or PayPal"
이는 모든 항목이 함께 적용됨을 나타내기 위해 "and"를 사용하는 접속적 목록과 다릅니다. 이접적 목록은 선택을 전달하고, 접속적 목록은 조합을 전달합니다.
이접적 목록의 일반적인 맥락에는 결제 옵션, 파일 형식 제한, 문제 해결 제안, 검색 필터 대안, 그리고 사용자가 여러 가능성 중에서 하나의 옵션을 선택하는 모든 인터페이스가 포함됩니다.
수동 형식 지정이 실패하는 이유
영어 사용자는 이접적 목록을 "A, B, or C"와 같이 항목 사이에 쉼표를 넣고 마지막 항목 앞에 "or"를 넣어 작성합니다. 이 패턴은 다른 언어에서는 작동하지 않습니다:
// 하드코딩된 영어 패턴
const items = ["apple", "orange", "banana"];
const text = items.slice(0, -1).join(", ") + ", or " + items[items.length - 1];
// "apple, orange, or banana"
이 코드는 스페인어, 프랑스어, 독일어 및 대부분의 다른 언어에서 잘못된 출력을 생성합니다. 각 언어마다 이접적 목록에 대한 고유한 형식 지정 규칙이 있습니다.
스페인어는 앞에 쉼표 없이 "o"를 사용합니다:
예상: "manzana, naranja o plátano"
영어 패턴 결과: "manzana, naranja, or plátano"
프랑스어는 앞에 쉼표 없이 "ou"를 사용합니다:
예상: "pomme, orange ou banane"
영어 패턴 결과: "pomme, orange, or banane"
독일어는 앞에 쉼표 없이 "oder"를 사용합니다:
예상: "Apfel, Orange oder Banane"
영어 패턴 결과: "Apfel, Orange, or Banane"
일본어는 다른 구두점과 함께 "か" (ka) 조사를 사용합니다:
예상: "りんご、オレンジ、またはバナナ"
영어 패턴 결과: "りんご、オレンジ、 or バナナ"
이러한 차이점은 단순한 단어 대체를 넘어섭니다. 구두점 배치, 간격 규칙 및 문법적 조사는 모두 언어마다 다릅니다. 수동 문자열 연결은 이러한 복잡성을 처리할 수 없습니다.
분리 타입으로 Intl.ListFormat 사용하기
Intl.ListFormat API는 언어별 규칙에 따라 목록을 형식화합니다. 대안 목록을 형식화하려면 type 옵션을 "disjunction"으로 설정하세요:
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"
포맷터는 모든 배열 길이를 처리합니다:
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"
API는 각 경우에 맞는 올바른 구두점과 접속사를 자동으로 적용합니다.
분리 스타일 이해하기
'style' 옵션은 형식화 상세도를 제어합니다. 세 가지 스타일이 있습니다: long, short, 그리고 narrow. 기본값은 long 스타일입니다.
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"
영어에서는 세 가지 스타일 모두 분리 목록에 대해 동일한 출력을 생성합니다. 다른 언어에서는 더 많은 변형을 보여줍니다. 독일어는 long 스타일에서 "oder"를 사용하고 narrow 스타일에서는 약어를 사용할 수 있습니다. 이러한 차이는 여러 격식 수준이나 더 긴 접속사를 가진 언어에서 더 분명하게 나타납니다.
일반적으로 narrow 스타일은 공간이 제한된 레이아웃에서 공간을 절약하기 위해 공백을 제거하거나 더 짧은 접속사를 사용합니다. 표준 텍스트에는 long 스타일을, 적당히 컴팩트한 디스플레이에는 short 스타일을, 모바일 인터페이스나 컴팩트한 테이블과 같은 공간 제약이 있는 경우에는 narrow 스타일을 사용하세요.
서로 다른 언어에서 선택적 목록이 표시되는 방식
각 언어는 자체 규칙에 따라 선택적 목록을 형식화합니다. Intl.ListFormat는 이러한 차이점을 자동으로 처리합니다.
영어는 "or"와 함께 쉼표를 사용합니다:
const en = new Intl.ListFormat("en", { type: "disjunction" });
console.log(en.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG, or SVG"
스페인어는 "o"와 함께 쉼표를 사용하며 마지막 접속사 앞에는 쉼표가 없습니다:
const es = new Intl.ListFormat("es", { type: "disjunction" });
console.log(es.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG o SVG"
프랑스어는 "ou"와 함께 쉼표를 사용하며 마지막 접속사 앞에는 쉼표가 없습니다:
const fr = new Intl.ListFormat("fr", { type: "disjunction" });
console.log(fr.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG ou SVG"
독일어는 "oder"와 함께 쉼표를 사용하며 마지막 접속사 앞에는 쉼표가 없습니다:
const de = new Intl.ListFormat("de", { type: "disjunction" });
console.log(de.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG oder SVG"
일본어는 다른 구두점과 조사를 사용합니다:
const ja = new Intl.ListFormat("ja", { type: "disjunction" });
console.log(ja.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG、またはSVG"
중국어는 중국어 구두점을 사용합니다:
const zh = new Intl.ListFormat("zh", { type: "disjunction" });
console.log(zh.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG或SVG"
이러한 예시는 API가 각 언어의 문법 및 구두점 규칙에 어떻게 적응하는지 보여줍니다. 적절한 로케일을 제공하면 동일한 코드가 모든 언어에서 작동합니다.
결제 옵션 형식화하기
결제 양식은 여러 결제 방법 선택지를 제시합니다. 선택적 목록으로 형식화하세요:
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."
국제 애플리케이션의 경우, 사용자의 로케일을 전달하세요:
const userLocale = navigator.language; // 예: "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)}.`;
}
이 접근 방식은 결제 흐름, 결제 방법 선택기 및 사용자가 결제 방법을 선택하는 모든 인터페이스에서 작동합니다.
파일 업로드 제한 사항 형식 지정
파일 업로드 컴포넌트는 시스템이 허용하는 파일 유형을 지정합니다:
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"
이 패턴은 이미지 업로드, 문서 제출 및 형식 제한이 있는 모든 파일 입력에 적용됩니다.
문제 해결 제안 형식 지정
오류 메시지는 종종 문제를 해결하기 위한 여러 방법을 제안합니다. 이러한 제안을 선언적 목록으로 표시합니다:
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."
선언적 목록은 사용자가 언급된 필드 중 하나를 수정해야 하며, 반드시 모든 필드를 수정할 필요는 없다는 것을 명확히 합니다.
검색 필터 대안 형식 지정
검색 인터페이스는 활성 필터를 표시합니다. 필터가 대안을 제시할 때는 선언적 목록을 사용합니다:
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"
이는 카테고리 필터, 태그 선택 및 선택된 값이 조합이 아닌 대안을 나타내는 모든 필터 인터페이스에 적용됩니다.
성능을 위한 포매터 재사용
Intl.ListFormat 인스턴스를 생성하는 것은 오버헤드가 있습니다. 포매터를 한 번 생성하고 재사용하세요:
// 모듈 레벨에서 한 번 생성
const disjunctionFormatter = new Intl.ListFormat("en", { type: "disjunction" });
// 여러 함수에서 재사용
function formatPaymentMethods(methods) {
return disjunctionFormatter.format(methods);
}
function formatFileTypes(types) {
return disjunctionFormatter.format(types);
}
function formatErrorSuggestions(suggestions) {
return disjunctionFormatter.format(suggestions);
}
여러 로케일을 지원하는 애플리케이션의 경우, 포매터를 캐시에 저장하세요:
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"
이 패턴은 애플리케이션 전체에서 여러 로케일을 지원하면서 초기화 비용을 줄입니다.
사용자 정의 렌더링을 위한 formatToParts 사용
'formatToParts()' 메서드는 포맷된 목록의 각 부분을 나타내는 객체 배열을 반환합니다. 이를 통해 사용자 정의 스타일링이 가능합니다:
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" }
// ]
각 부분은 'type'과 'value'를 가집니다. 'type'은 목록 항목의 경우 '"element"'이고, 구두점과 접속사의 경우 '"literal"'입니다.
이를 사용하여 요소와 리터럴에 다른 스타일을 적용할 수 있습니다:
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>"
이 접근 방식은 실제 항목에 사용자 정의 표현을 적용하면서 로케일에 맞는 구두점과 접속사를 유지합니다.
브라우저 지원 및 호환성
Intl.ListFormat는 2021년 4월 이후 모든 최신 브라우저에서 작동합니다. 지원되는 브라우저에는 Chrome 72+, Firefox 78+, Safari 14.1+, Edge 79+가 포함됩니다.
API를 사용하기 전에 지원 여부를 확인하세요:
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
return formatter.format(items);
} else {
// 구형 브라우저를 위한 대체 방법
return items.join(", ");
}
더 넓은 호환성을 위해 @formatjs/intl-listformat와 같은 폴리필을 사용하세요. 필요한 경우에만 설치하세요:
if (typeof Intl.ListFormat === "undefined") {
await import("@formatjs/intl-listformat/polyfill");
}
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
현재 브라우저 지원 상황을 고려할 때, 대부분의 애플리케이션은 폴리필 없이 Intl.ListFormat를 직접 사용할 수 있습니다.
피해야 할 일반적인 실수
disjunction 대신 conjunction 타입을 사용하면 잘못된 의미가 생성됩니다:
// 잘못됨: 모든 방법이 필요하다고 제안함
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"
// 올바름: 한 가지 방법을 선택하라고 제안함
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"
새 포매터를 반복적으로 생성하면 리소스가 낭비됩니다:
// 비효율적
function formatOptions(options) {
return new Intl.ListFormat("en", { type: "disjunction" }).format(options);
}
// 효율적
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function formatOptions(options) {
return formatter.format(options);
}
문자열에 "or"을 하드코딩하면 현지화가 불가능합니다:
// 다른 언어에서 작동하지 않음
const text = items.join(", ") + ", or other options";
// 모든 언어에서 작동함
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
const allItems = [...items, "other options"];
const text = formatter.format(allItems);
빈 배열을 처리하지 않으면 예상치 못한 출력이 발생할 수 있습니다:
// 방어적 코딩
function formatPaymentMethods(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return formatter.format(methods);
}
format([]) 는 빈 문자열을 반환하지만, 명시적인 빈 상태 처리는 사용자 경험을 향상시킵니다.
선언적 목록을 사용해야 하는 경우
일반적으로 하나의 옵션이 적용되는 대안이나 선택지를 제시할 때 선언적 목록을 사용하세요. 여기에는 결제 방법 선택, 파일 형식 제한, 인증 오류 제안, 검색 필터 옵션, 계정 유형 선택 등이 포함됩니다.
모든 항목이 함께 적용되어야 하는 경우에는 선언적 목록을 사용하지 마세요. 대신 접속 목록을 사용하세요. 예를 들어, "이름, 이메일 및 비밀번호가 필요합니다"는 하나만이 아닌 모든 필드가 제공되어야 하므로 접속을 사용합니다.
선택 의미가 없는 중립적인 열거에는 선언적 목록을 사용하지 마세요. 측정값과 기술 사양은 일반적으로 선언이나 접속 대신 단위 목록을 사용합니다.
API는 대안을 위한 수동 문자열 연결 패턴을 대체합니다. 사용자 대면 텍스트에 "또는"로 항목을 연결하는 코드를 작성할 때마다 선언 유형의 Intl.ListFormat가 더 나은 로케일 지원을 제공하는지 고려하세요.