JavaScript에서 or를 사용하여 목록 형식을 지정하는 방법
Intl.ListFormat을 type disjunction과 함께 사용하여 모든 언어에서 대안을 올바르게 형식화하세요
소개
애플리케이션은 종종 사용자에게 선택 사항이나 대안을 제시합니다. 파일 업로드 컴포넌트는 "PNG, JPEG 또는 SVG" 파일을 허용합니다. 결제 양식은 결제 수단으로 "신용카드, 직불카드 또는 PayPal"을 허용합니다. 오류 메시지는 인증 실패를 해결하기 위해 "사용자 이름, 비밀번호 또는 이메일 주소"를 수정할 것을 제안합니다.
이러한 목록은 "또는"을 사용하여 대안을 나타냅니다. 문자열 연결로 수동으로 형식을 지정하면 다른 언어에서는 작동하지 않습니다. 언어마다 구두점 규칙, "또는"에 해당하는 단어, 쉼표 배치 규칙이 다르기 때문입니다. disjunction 타입을 사용하는 Intl.ListFormat API는 모든 언어에 대해 이러한 대안 목록을 올바르게 형식화합니다.
선언적 목록이란
선언적 목록은 일반적으로 하나의 옵션이 적용되는 대안을 제시합니다. "disjunction"이라는 단어는 분리 또는 대안을 의미합니다. 영어에서 선언적 목록은 접속사로 "or"를 사용합니다.
const paymentMethods = ["credit card", "debit card", "PayPal"];
// Desired output: "credit card, debit card, or PayPal"
이는 모든 항목이 함께 적용됨을 나타내기 위해 "and"를 사용하는 연결 목록과 다릅니다. 선언적 목록은 선택을 전달하고, 연결 목록은 조합을 전달합니다.
선언적 목록의 일반적인 사용 사례에는 결제 옵션, 파일 형식 제한, 문제 해결 제안, 검색 필터 대안, 그리고 사용자가 여러 가능성 중 하나의 옵션을 선택하는 모든 인터페이스가 포함됩니다.
수동 형식 지정이 실패하는 이유
영어 사용자는 선언적 목록을 "A, B, or C"로 작성하며 항목 사이에 쉼표를 사용하고 마지막 항목 앞에 "or"를 사용합니다. 이 패턴은 다른 언어에서는 작동하지 않습니다.
// Hardcoded English pattern
const items = ["apple", "orange", "banana"];
const text = items.slice(0, -1).join(", ") + ", or " + items[items.length - 1];
// "apple, orange, or banana"
이 코드는 스페인어, 프랑스어, 독일어 및 대부분의 다른 언어에서 잘못된 출력을 생성합니다. 각 언어에는 선언적 목록에 대한 고유한 형식 규칙이 있습니다.
스페인어는 앞에 쉼표 없이 "o"를 사용합니다.
Expected: "manzana, naranja o plátano"
English pattern produces: "manzana, naranja, or plátano"
프랑스어는 앞에 쉼표 없이 "ou"를 사용합니다:
Expected: "pomme, orange ou banane"
English pattern produces: "pomme, orange, or banane"
독일어는 앞에 쉼표 없이 "oder"를 사용합니다:
Expected: "Apfel, Orange oder Banane"
English pattern produces: "Apfel, Orange, or Banane"
일본어는 다른 구두점과 함께 조사 "か"(ka)를 사용합니다:
Expected: "りんご、オレンジ、またはバナナ"
English pattern produces: "りんご、オレンジ、 or バナナ"
이러한 차이는 단순한 단어 대체를 넘어섭니다. 구두점 배치, 띄어쓰기 규칙, 문법 조사는 모두 언어에 따라 다릅니다. 수동 문자열 연결로는 이러한 복잡성을 처리할 수 없습니다.
disjunction 타입과 함께 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는 각 경우에 올바른 구두점과 접속사를 자동으로 적용합니다.
disjunction 스타일 이해하기
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; // e.g., "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 인스턴스를 생성하는 것은 오버헤드가 있습니다. 포매터를 한 번 생성하고 재사용하세요:
// Create once at module level
const disjunctionFormatter = new Intl.ListFormat("en", { type: "disjunction" });
// Reuse in multiple functions
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 {
// Fallback for older browsers
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을 직접 사용할 수 있습니다.
피해야 할 일반적인 실수
선언형 대신 접속형 타입을 사용하면 잘못된 의미가 생성됩니다:
// Wrong: suggests all methods required
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"
// Correct: suggests choosing one method
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"
포매터를 반복적으로 생성하면 리소스가 낭비됩니다:
// Inefficient
function formatOptions(options) {
return new Intl.ListFormat("en", { type: "disjunction" }).format(options);
}
// Efficient
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function formatOptions(options) {
return formatter.format(options);
}
문자열에 "or"를 하드코딩하면 현지화가 불가능합니다:
// Breaks in other languages
const text = items.join(", ") + ", or other options";
// Works across languages
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
const allItems = [...items, "other options"];
const text = formatter.format(allItems);
빈 배열을 처리하지 않으면 예상치 못한 출력이 발생할 수 있습니다:
// Defensive
function formatPaymentMethods(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return formatter.format(methods);
}
format([])가 빈 문자열을 반환하지만, 명시적인 빈 상태 처리는 사용자 경험을 개선합니다.
선택 목록을 사용해야 하는 경우
일반적으로 하나의 옵션이 적용되는 대안이나 선택지를 제시할 때 선택 목록을 사용합니다. 여기에는 결제 수단 선택, 파일 형식 제한, 인증 오류 제안, 검색 필터 옵션, 계정 유형 선택이 포함됩니다.
모든 항목이 함께 적용되어야 하는 경우에는 선택 목록을 사용하지 마십시오. 대신 접속 목록을 사용하십시오. 예를 들어, "이름, 이메일, 비밀번호가 필요합니다"는 하나가 아닌 모든 필드를 제공해야 하므로 접속을 사용합니다.
선택 의미가 없는 중립적인 열거에는 선택 목록을 사용하지 마십시오. 측정값과 기술 사양은 일반적으로 선택이나 접속 대신 단위 목록을 사용합니다.
이 API는 대안에 대한 수동 문자열 연결 패턴을 대체합니다. 사용자 대면 텍스트에 대해 "or"로 항목을 결합하는 코드를 작성할 때마다 선택 유형의 Intl.ListFormat이 더 나은 로케일 지원을 제공하는지 고려하십시오.