Intl.ListFormat API
배열을 로케일을 인식하는 읽기 쉬운 목록으로 서식 지정
소개
사용자에게 여러 항목을 표시할 때, 개발자들은 종종 배열을 쉼표로 연결하고 마지막 항목 앞에 "and"를 추가합니다:
const users = ["Alice", "Bob", "Charlie"];
const message = users.slice(0, -1).join(", ") + ", and " + users[users.length - 1];
// "Alice, Bob, and Charlie"
이 접근 방식은 영어 구두점 규칙을 하드코딩하여 다른 언어에서는 제대로 작동하지 않습니다. 일본어는 다른 조사를 사용하고, 독일어는 다른 간격 규칙을 가지며, 중국어는 다른 구분자를 사용합니다. Intl.ListFormat API는 각 로케일의 관례에 따라 목록을 형식화하여 이 문제를 해결합니다.
Intl.ListFormat의 기능
Intl.ListFormat는 배열을 모든 언어의 문법 및 구두점 규칙을 따르는 사람이 읽기 쉬운 목록으로 변환합니다. 이는 모든 언어에서 나타나는 세 가지 유형의 목록을 처리합니다:
- 접속 목록은 항목을 연결하기 위해 "그리고"를 사용합니다 ("A, B, 그리고 C")
- 이접 목록은 대안을 제시하기 위해 "또는"을 사용합니다 ("A, B, 또는 C")
- 단위 목록은 접속사 없이 측정값을 형식화합니다 ("5 ft, 2 in")
이 API는 구두점부터 단어 선택, 간격까지 각 언어가 이러한 목록 유형을 어떻게 형식화하는지 알고 있습니다.
기본 사용법
로케일과 옵션으로 포맷터를 생성한 다음, 배열과 함께 format()을 호출합니다:
const formatter = new Intl.ListFormat("en", {
type: "conjunction",
style: "long"
});
const items = ["bread", "milk", "eggs"];
console.log(formatter.format(items));
// "bread, milk, and eggs"
포맷터는 다음과 같은 특수한 경우를 포함하여 모든 길이의 배열을 처리합니다:
formatter.format([]); // ""
formatter.format(["bread"]); // "bread"
formatter.format(["bread", "milk"]); // "bread and milk"
목록 유형이 접속사를 제어함
type 옵션은 형식화된 목록에 나타나는 접속사를 결정합니다.
접속 목록
모든 항목이 함께 적용되는 목록에는 type: "conjunction"를 사용하세요. 이것이 기본 유형입니다:
const formatter = new Intl.ListFormat("en", { type: "conjunction" });
console.log(formatter.format(["HTML", "CSS", "JavaScript"]));
// "HTML, CSS, and JavaScript"
일반적인 사용 사례로는 선택된 항목 표시, 기능 나열, 모두 적용되는 여러 값 표시 등이 있습니다.
선택 목록
대안이나 선택지를 제시하는 목록에는 type: "disjunction"을 사용하세요:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
console.log(formatter.format(["credit card", "debit card", "PayPal"]));
// "credit card, debit card, or PayPal"
이는 옵션 목록, 여러 해결책이 있는 오류 메시지, 사용자가 하나의 항목을 선택하는 모든 상황에서 나타납니다.
단위 목록
접속사 없이 표시되어야 하는 측정값과 기술적 값에는 type: "unit"을 사용하세요:
const formatter = new Intl.ListFormat("en", { type: "unit" });
console.log(formatter.format(["5 feet", "2 inches"]));
// "5 feet, 2 inches"
단위 목록은 측정값, 기술 사양, 복합 값에 적합합니다.
목록 스타일로 상세도 제어
style 옵션은 포맷팅이 얼마나 상세하게 표시될지 조정합니다. long, short, narrow 세 가지 스타일이 있습니다.
const items = ["Monday", "Wednesday", "Friday"];
const long = new Intl.ListFormat("en", { style: "long" });
console.log(long.format(items));
// "Monday, Wednesday, and Friday"
const short = new Intl.ListFormat("en", { style: "short" });
console.log(short.format(items));
// "Monday, Wednesday, and Friday"
const narrow = new Intl.ListFormat("en", { style: "narrow" });
console.log(narrow.format(items));
// "Monday, Wednesday, Friday"
영어에서는 대부분의 목록에서 long과 short가 동일한 출력을 생성합니다. narrow 스타일은 접속사를 생략합니다. 다른 언어에서는 특히 선택 목록에서 스타일 간에 더 많은 차이가 나타납니다.
다양한 언어에서의 목록 형식 지정 방법
각 언어마다 고유한 목록 형식 지정 규칙이 있습니다. Intl.ListFormat는 이러한 차이점을 자동으로 처리합니다.
영어는 쉼표, 공백 및 접속사를 사용합니다:
const en = new Intl.ListFormat("en");
console.log(en.format(["Tokyo", "Paris", "London"]));
// "Tokyo, Paris, and London"
독일어는 동일한 쉼표 구조를 사용하지만 다른 접속사를 사용합니다:
const de = new Intl.ListFormat("de");
console.log(de.format(["Tokyo", "Paris", "London"]));
// "Tokyo, Paris und London"
일본어는 다른 구분자와 조사를 사용합니다:
const ja = new Intl.ListFormat("ja");
console.log(ja.format(["東京", "パリ", "ロンドン"]));
// "東京、パリ、ロンドン"
중국어는 완전히 다른 문장 부호를 사용합니다:
const zh = new Intl.ListFormat("zh");
console.log(zh.format(["东京", "巴黎", "伦敦"]));
// "东京、巴黎和伦敦"
이러한 차이점은 문장 부호뿐만 아니라 공백 규칙, 접속사 배치 및 문법적 조사에까지 확장됩니다. 단일 접근 방식을 하드코딩하면 다른 언어에서 문제가 발생합니다.
사용자 정의 렌더링을 위한 formatToParts 사용
formatToParts() 메서드는 문자열 대신 객체 배열을 반환합니다. 각 객체는 형식이 지정된 목록의 한 부분을 나타냅니다:
const formatter = new Intl.ListFormat("en");
const parts = formatter.formatToParts(["red", "green", "blue"]);
console.log(parts);
// [
// { type: "element", value: "red" },
// { type: "literal", value: ", " },
// { type: "element", value: "green" },
// { type: "literal", value: ", and " },
// { type: "element", value: "blue" }
// ]
각 부분에는 type과 value가 있습니다. type은 목록 항목의 경우 "element", 형식 지정 문장 부호 및 접속사의 경우 "literal"입니다.
이 구조는 요소와 리터럴에 다른 스타일이 필요한 사용자 정의 렌더링을 가능하게 합니다:
const formatter = new Intl.ListFormat("en");
const items = ["Alice", "Bob", "Charlie"];
const html = formatter.formatToParts(items)
.map(part => {
if (part.type === "element") {
return `<strong>${part.value}</strong>`;
}
return part.value;
})
.join("");
console.log(html);
// "<strong>Alice</strong>, <strong>Bob</strong>, and <strong>Charlie</strong>"
이 접근 방식은 실제 목록 항목에 사용자 정의 표현을 적용하면서 로케일에 맞는 문장 부호를 유지합니다.
성능을 위한 포매터 재사용
Intl.ListFormat 인스턴스를 생성하는 것은 오버헤드가 있습니다. 포매터를 한 번 생성하고 재사용하세요:
// 한 번 생성
const listFormatter = new Intl.ListFormat("en", { type: "conjunction" });
// 여러 번 재사용
function displayUsers(users) {
return listFormatter.format(users.map(u => u.name));
}
function displayTags(tags) {
return listFormatter.format(tags);
}
여러 로케일을 사용하는 애플리케이션의 경우, 포매터를 맵에 저장하세요:
const formatters = new Map();
function getListFormatter(locale, options) {
const key = `${locale}-${options.type}-${options.style}`;
if (!formatters.has(key)) {
formatters.set(key, new Intl.ListFormat(locale, options));
}
return formatters.get(key);
}
const formatter = getListFormatter("en", { type: "conjunction", style: "long" });
console.log(formatter.format(["a", "b", "c"]));
이 패턴은 여러 로케일과 구성을 지원하면서 반복적인 초기화 비용을 줄입니다.
오류 메시지 포맷팅
폼 유효성 검사는 종종 여러 오류를 생성합니다. 선택지를 제시하기 위해 분리 목록으로 포맷팅하세요:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function validatePassword(password) {
const errors = [];
if (password.length < 8) {
errors.push("최소 8자 이상");
}
if (!/[A-Z]/.test(password)) {
errors.push("대문자");
}
if (!/[0-9]/.test(password)) {
errors.push("숫자");
}
if (errors.length > 0) {
return `비밀번호는 ${formatter.format(errors)}을(를) 포함해야 합니다.`;
}
return null;
}
console.log(validatePassword("weak"));
// "비밀번호는 최소 8자 이상, 대문자, 또는 숫자을(를) 포함해야 합니다."
분리 목록은 사용자가 이러한 문제 중 하나를 수정해야 함을 명확히 하며, 포맷팅은 각 로케일의 규칙에 맞게 조정됩니다.
선택된 항목 표시
사용자가 여러 항목을 선택할 때, 결합 목록으로 선택 항목을 포맷팅하세요:
const formatter = new Intl.ListFormat("en", { type: "conjunction" });
function getSelectionMessage(selectedFiles) {
if (selectedFiles.length === 0) {
return "선택된 파일 없음";
}
if (selectedFiles.length === 1) {
return `${selectedFiles[0]} 선택됨`;
}
return `${formatter.format(selectedFiles)} 선택됨`;
}
console.log(getSelectionMessage(["report.pdf", "data.csv", "notes.txt"]));
// "report.pdf, data.csv 및 notes.txt 선택됨"
이 패턴은 파일 선택, 필터 선택, 카테고리 선택 및 모든 다중 선택 인터페이스에 적용됩니다.
긴 목록 처리하기
많은 항목이 있는 목록의 경우, 형식을 지정하기 전에 잘라내는 것을 고려하세요:
const formatter = new Intl.ListFormat("en", { type: "conjunction" });
function formatUserList(users) {
if (users.length <= 3) {
return formatter.format(users);
}
const visible = users.slice(0, 2);
const remaining = users.length - 2;
return `${formatter.format(visible)}, and ${remaining} others`;
}
console.log(formatUserList(["Alice", "Bob", "Charlie", "David", "Eve"]));
// "Alice, Bob, and 3 others"
이는 전체 개수를 표시하면서 가독성을 유지합니다. 정확한 임계값은 인터페이스 제약 조건에 따라 달라집니다.
브라우저 지원 및 대체 방안
Intl.ListFormat는 2021년 4월 이후 모든 최신 브라우저에서 작동합니다. 지원 브라우저에는 Chrome 72+, Firefox 78+, Safari 14.1+ 및 Edge 79+가 포함됩니다.
기능 감지로 지원 여부를 확인하세요:
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat("en");
return formatter.format(items);
} else {
// 구형 브라우저를 위한 대체 방안
return items.join(", ");
}
더 넓은 호환성을 위해 @formatjs/intl-listformat와 같은 폴리필을 사용하세요. 필요한 환경에만 설치하세요:
if (typeof Intl.ListFormat === "undefined") {
await import("@formatjs/intl-listformat/polyfill");
}
현재 브라우저 지원 상황을 고려할 때, 대부분의 애플리케이션은 폴리필 없이 Intl.ListFormat를 직접 사용할 수 있습니다.
피해야 할 일반적인 실수
반복적으로 새 포맷터를 생성하는 것은 리소스 낭비입니다:
// 비효율적
function display(items) {
return new Intl.ListFormat("en").format(items);
}
// 효율적
const formatter = new Intl.ListFormat("en");
function display(items) {
return formatter.format(items);
}
사용자 대면 텍스트에 array.join()을 사용하면 지역화 문제가 발생합니다:
// 다른 언어에서 문제 발생
const text = items.join(", ");
// 모든 언어에서 작동
const formatter = new Intl.ListFormat(userLocale);
const text = formatter.format(items);
영어 접속사 규칙이 보편적으로 적용된다고 가정하면 다른 로케일에서 잘못된 출력이 발생합니다. 항상 생성자에 사용자의 로케일을 전달하세요.
빈 배열을 처리하지 않으면 예상치 못한 출력이 발생할 수 있습니다:
// 방어적 코딩
function formatItems(items) {
if (items.length === 0) {
return "No items";
}
return formatter.format(items);
}
format([]) 는 빈 문자열을 반환하지만, 명시적인 빈 상태 처리는 사용자 경험을 향상시킵니다.
Intl.ListFormat를 사용해야 하는 경우
본문에 여러 항목을 표시할 때마다 Intl.ListFormat를 사용하세요. 여기에는 내비게이션 경로, 선택된 필터, 유효성 검사 오류, 사용자 목록, 카테고리 태그 및 기능 목록이 포함됩니다.
테이블이나 옵션 메뉴와 같은 구조화된 데이터 표시에는 사용하지 마세요. 이러한 컴포넌트는 본문 목록 규칙과는 별개로 자체 서식 요구 사항이 있습니다.
이 API는 수동 문자열 연결 및 결합 패턴을 대체합니다. 사용자에게 표시되는 텍스트에 join(", ")을 작성할 때마다 Intl.ListFormat가 더 나은 로케일 지원을 제공하는지 고려하세요.