یکی از اصلیترین کاربردهای مجموعه Intl، خروجی گرفتن از دادههای ساختاریافته در قالب متنهای بومیسازیشده است. این ویژگی شباهت زیادی به نرمافزارهای ترجمه دارد، اما به جای ترجمه کردن متون دلخواه، دادههایی مثل تاریخ، اعداد و لیستها را میگیرد و آنها را بر اساس قوانین خاص هر فرهنگ فرمت میکند.
اشیای Intl.DateTimeFormat ،Intl.DurationFormat ،Intl.ListFormat ،Intl.NumberFormat و Intl.RelativeTimeFormat هر کدام برای فرمت کردن یک نوع دادهی مشخص طراحی شدهاند. هر نمونه (Instance) از این اشیاء دو متد بسیار کلیدی را در اختیارتان میگذارد:
format(): یک داده را میگیرد و رشته متنی بومیسازیشدهی آن را بر اساس لوکال و تنظیماتِ اعمالشده برمیگرداند.
formatToParts(): همان داده را میگیرد و همان رشته متنی را تولید میکند، اما آن را به صورت یک آرایه از تکههای مجزا (Parts) خروجی میدهد. هر تکه یک شیء است که نوع (type) و مقدار (value) آن بخش را مشخص میکند. این متد برای سناریوهای پیشرفته (مثلاً ترکیب متن فرمتشده با استایلهای CSS متفاوت یا متون دیگر) فوقالعاده کاربردی است.
بیا یک نمونه استفاده واقعی از Intl.NumberFormat و تفاوت این دو متد را ببینیم:
// ۱. ساخت یک شیء فرمتکننده برای واحد پولی دلار آمریکا:
const price = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
// ۲. استفاده از متد format برای دریافت رشته متنی نهایی:
console.log(price.format(5.259)); // خروجی: $5.26
// یا استفاده از متد formatToParts برای دریافت تکهتکهی اجزای عدد:
console.log(price.formatToParts(5.259));
/*
خروجی به این صورت خواهد بود:
[
{ type: "currency", value: "$" },
{ type: "integer", value: "5" },
{ type: "decimal", value: "." },
{ type: "fraction", value: "26" }
]
*/
toLocaleString() استفاده کنیم؟شما همیشه مجبور نیستید برای فرمت کردن، یک شیء فرمتکننده کامل بسازید. برای استفادههای گذری و ساده، میتوانید مستقیماً متد toLocaleString() را روی خودِ داده صدا بزنید و لوکال و تنظیمات را به عنوان آرگومان به آن پاس بدهید. این متد در نمونههای مختلفی مثل Temporal.PlainDate.prototype.toLocaleString()، Temporal.Duration.prototype.toLocaleString() و Number.prototype.toLocaleString() پیادهسازی شده است:
console.log(
(5.259).toLocaleString("en-US", {
style: "currency",
currency: "USD",
}),
); // خروجی: $5.26
⚠️ یک نکته بسیار مهم درباره کارایی (Performance): متد
toLocaleString()از نظر کارایی پتانسیل ضعیفتری نسبت به ساخت یک شیء فرمتکننده دارد؛ زیرا هر بار که صدا زده میشود، سیستم باید یک فرآیند جستجوی سنگین را در پایگاه داده بزرگِ بومیسازیِ مرورگر انجام دهد. اگر قرار است کدهای فرمتکننده شما در یک حلقه یا به دفعات زیاد با آرگومانهای یکسان اجرا شوند، حتماً یک بار شیء فرمتکننده را بسازید و از متدformat()آن استفاده کنید؛ چرا که شیء فرمتکننده آرگومانها را به خاطر میسپارد و بخشی از پایگاه داده را کش (Cache) میکند تا فرآیندهای بعدی با سرعت بسیار بالاتری انجام شوند.
شیء Intl.DateTimeFormat وظیفه فرمت کردن تاریخها، زمانها و بازههای زمانی را بر عهده دارد. این شیء ورودیهای خود را در قالب یکی از این فرمها میپذیرد: شیء معمولی Date یا اشیای مدرن اکمااسکریپت مثل Temporal.PlainDateTime ،Temporal.PlainTime ،Temporal.PlainDate ،Temporal.PlainYearMonth یا Temporal.PlainMonthDay.
📌 نکته: شما نمیتوانید یک شیء
Temporal.ZonedDateTimeرا مستقیماً به این فرمتکننده پاس بدهید، چون منطقه زمانی در داخل خود آن شیء فیکس شده است. برای این کار باید از متد اختصاصی خودش یعنیTemporal.ZonedDateTime.prototype.toLocaleString()استفاده کنید یا ابتدا آن را به یکTemporal.PlainDateTimeتبدیل کنید.
رایجترین سناریوهای بومیسازی تاریخ و زمان عبارتند از:
نمایش یک تاریخ و زمان مشخص در سیستمهای تقویم دیگر (مثل شمسی، قمری، عبری یا چینی).
نمایش یک زمان واقعیِ مشخص (Instant) در یک منطقه زمانی (Time Zone) دیگر.
انتخاب و جداسازی بخشهای خاصی از تاریخ و زمان (مثلاً فقط نمایش سال و ماه، یا نحوه نمایش روز هفته مثل "پنجشنبه" یا "پنج").
نمایش تاریخ بر اساس قراردادهای محلی؛ مثل MM/DD/YYYY در آمریکا، DD/MM/YYYY در انگلیس، یا YYYY/MM/DD در ژاپن.
نمایش زمان بر اساس ساعت ۱۲ ساعته یا ۲۴ ساعته.
برای تنظیم شکل ظاهری خروجی، ابتدا باید تقویم (calendar) و منطقه زمانی (timeZone) را مشخص کنید. این کار با گزینه calendar (یا پسوند -ca- در تگ زبان) و گزینه timeZone انجام میشود:
اشیای استاندارد Date نماینده یک لحظه منحصربهفرد (Instant) در منطقه زمانی کاربر و در تقویم میلادی (ISO 8601) هستند. این اشیاء با حفظ دقیق آن لحظه، به تقویم و منطقه زمانی درخواستی شما تبدیل میشوند (در نتیجه اجزای ساعت و روز آنها ممکن است تغییر کند).
اشیای مختلف Temporal از قبل یک تقویم داخلی درون خود دارند؛ بنابراین گزینه calendar ارسالی شما باید با تقویم خودِ شیء هماهنگ باشد (مگر اینکه تقویم شیء "iso8601" باشد که در این صورت به تقویم درخواستی تبدیل میشود). این اشیاء منطقه زمانی ندارند، پس مستقیماً بدون هیچ تبدیلی در منطقه زمانی دادهشده نمایش داده میشوند.
بیا در کد زیر ببینیم چطور ترکیب تقویمها و مناطق زمانی مختلف، خروجیهای متفاوتی از یک لحظه زمانیِ کاملاً یکسان تولید میکند (فرض کنید منطقه زمانی سیستم روی UTC تنظیم شده است):
const targetDate = new Date(2022, 0, 1); // اول ژانویه ۲۰۲۲ نیمهشب به وقت محلی
const results = [];
for (const calendar of ["gregory", "hebrew"]) {
for (const timeZone of ["America/New_York", "Asia/Tokyo"]) {
const df = new Intl.DateTimeFormat("en-US", {
calendar,
timeZone,
dateStyle: "full",
timeStyle: "full",
});
results.push({ calendar, timeZone, output: df.format(targetDate) });
}
}
console.table(results);
خروجی این آزمایش به این صورت خواهد بود:
| calendar | timeZone | output |
'gregory' | 'America/New_York' | 'Friday, December 31, 2021 at 7:00:00 PM Eastern Standard Time' |
'gregory' | 'Asia/Tokyo' | 'Saturday, January 1, 2022 at 9:00:00 AM Japan Standard Time' |
'hebrew' | 'America/New_York' | 'Friday, 27 Tevet 5782 at 7:00:00 PM Eastern Standard Time' |
'hebrew' | 'Asia/Tokyo' | 'Saturday, 28 Tevet 5782 at 9:00:00 AM Japan Standard Time' |
یک تاریخ یا زمان از اجزای مختلفی تشکیل شده است: weekday (روز هفته)، era (عصر/دوره)، year (سال)، month (ماه)، day (روز)، dayPeriod (قفل زمانی مثل ب.ظ/ق.ظ)، hour (ساعت)، minute (دقیقه)، second (ثانیه)، fractionalSecondDigits (اعشار ثانیه) و timeZoneName (نام منطقه زمانی).
برای اینکه مشخص کنید کدام یک از این اجزاء در خروجی ظاهر شوند، دو راه پیش رو دارید:
پیکربندی دستی (Manual): تکتک اجزای مورد نیاز را با نام خودشان در شیء گزینهها تنظیم کنید. در این حالت فقط و فقط همان اجزایی که نوشتید در خروجی ظاهر میشوند.
میانبرهای آماده (dateStyle و timeStyle): این دو گزینه مجموعههای پیشفرضی از اجزاء هستند که بر اساس لوکالِ انتخابی، به طور خودکار بهترین ترکیب را باز میکنند.
⚠️ نکته بسیار مهم: شما باید فقط یکی از این دو روش را انتخاب کنید. استفاده همزمان از تنظیمات دستی و میانبرهای
dateStyle/timeStyleباعث بروز خطا خواهد شد.
در پسزمینه، وقتی شما ترکیبی از اجزاء را درخواست میکنید، شیء DateTimeFormat به دنبال یک «قالب یا Template» آماده میگردد که با درخواست شما مطابقت داشته باشد تا مقادیر را درون آن جایگذاری کند. اگر برای ترکیب درخواستی شما قالبی پیدا نشود، گزینهای به نام formatMatcher تعیین میکند که سیستم چطور با بزرگتر، کوچکتر یا حذف کردن برخی اجزاء، به یک تفاهم برسد.
بیا چند روش رایج برای فرمت کردن این اجزاء را ببینیم:
const df1 = new Intl.DateTimeFormat("en-US", {
dateStyle: "full",
timeStyle: "full",
});
const df2 = new Intl.DateTimeFormat("en-US", {
era: "short",
year: "numeric",
month: "long",
day: "numeric",
});
const df3 = new Intl.DateTimeFormat("en-US", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZoneName: "shortOffset",
});
const targetDate = new Date(2022, 0, 1, 12, 34, 56);
console.log(df1.format(targetDate)); // Saturday, January 1, 2022 at 12:34:56 PM Coordinated Universal Time
console.log(df2.format(targetDate)); // January 1, 2022 AD
console.log(df3.format(targetDate)); // 12:34:56 PM GMT
علاوه بر این موارد، گزینههای دیگری مثل hourCycle (برای تنظیم دقیق ساعت ۲۴ ساعته یا ۱۲ ساعته و نمایش نیمهشب به صورت 00:00 یا 12:00) و numberingSystem (برای تغییر رسمالخط اعداد) در دسترس شما قرار دارند.
formatRange()به غیر از متد معمولی، متد بسیار کاربردی دیگری به نام formatRange() وجود دارد که یک بازه و محدوده زمانی را فرمت میکند. این متد دو شیء تاریخ-زمان از یک نوع را میگیرد، هر کدام را فرمت کرده، آنها را با یک جداکننده استاندارد بازه (مثل خط تیره بلند –) به هم متصل میکند و بخشهای مشترک آنها (مثلاً سال یا ماه مشترک) را به طور هوشمند حذف میکند تا از تکرار جلوگیری شود:
const springBreak = {
start: new Date(2023, 2, 10),
end: new Date(2023, 2, 26),
};
const df = new Intl.DateTimeFormat("en-US", { dateStyle: "long" });
console.log(df.formatRange(springBreak.start, springBreak.end));
// خروجی: March 10 – 26, 2023 (ماه و سال تکرار نشدهاند)
فرمت کردن اعداد با شیء Intl.NumberFormat انجام میشود. این ابزار ورودیهای خود را به صورت عدد معمولی (Number)، رشته متنی (String) یا مقادیر BigInt میپذیرد. پاس دادن رشته یا BigInt به شما اجازه میدهد اعدادی را که برای قالب استاندارد جاوااسکریپت بیش از حد بزرگ یا کوچک هستند، بدون از دست رفتن دقت، فرمت کنید.
رایجترین سناریوهای بومیسازی اعداد عبارتند از:
نمایش اعداد در سیستمهای عددیِ خطوط دیگر (مثل چینی، عربی یا رومی).
اعمال قراردادهای محلیِ جداسازی؛ مثل نماد ممیز اعشار (در انگلیسی . اما در بسیاری از فرهنگهای اروپایی ,) یا گروهبندی ارقام (گروهبندی ۳ رقمی در انگلیسی، اما ممکن است در فرهنگهای دیگر ۴ یا ۲ رقمی باشد و از علامتهای ,، فضای خالی یا . استفاده کند).
نمایش اعداد با نمادهای فشرده و علمی؛ مثل "3.7 million" یا "2 thousand".
نمایش عدد در قالب واحد پولی (Currency) با اعمال نمادهای خاص هر ارز و قوانین گرد کردن مخصوص آن.
نمایش عدد به صورت درصد (Percentage) با اعمال ضرب در ۱۰۰ و قوانین محلی آن.
نمایش عدد به همراه واحدهای اندازهگیری بومیسازیشده (مثل "meters" یا "liters").
برای تعیین شکل ظاهری، ابتدا باید سیستم عددی (numberingSystem) و سپس استایل کلی (style) را انتخاب کنید. گزینه style بافتار و مفهوم اصلی عدد را مشخص میکند و روی مقادیر پیشفرض بقیه گزینهها تأثیر میگذارد. مقدار آن میتواند یکی از گزینههای "decimal" (اعشاری معمولی)، "percent" (درصد)، "currency" (واحد پول) یا "unit" (واحد اندازهگیری) باشد. اگر ارز یا واحد اندازهگیری را انتخاب کنید، باید گزینههای مربوط به خودشان یعنی currency یا unit را هم حواستان باشد که پاس بدهید:
const results = [];
const num = 1234567.89;
for (const options of [
{ style: "decimal" }, // عدد بدون واحد و بعد مشخص
{ style: "percent" }, // درصد (عدد خودکار در ۱۰۰ ضرب میشود)
{ style: "currency", currency: "USD" }, // واحد پولی دلار آمریکا
{ style: "unit", unit: "meter" } // واحد اندازهگیری طول به متر
]) {
const nf = new Intl.NumberFormat("en-US", options);
results.push({ style: options.style, output: nf.format(num) });
}
console.table(results);
خروجی این کد به این صورت نمایش داده میشود:
| style | output |
'decimal' | '1,234,567.89' |
'percent' | '123,456,789%' |
'currency' | '$1,234,567.89' |
'unit' | '1,234,567.89 m' |
گروه بعدی گزینهها مشخص میکنند که بخش عددی باید چه ظاهری داشته باشد. اگر میخواهید اعداد بسیار بزرگ را به شکلی خواناتر نمایش دهید، میتوانید گزینه notation را روی "scientific" (علمی) یا "engineering" (مهندسی) تنظیم کنید که هر دو از ساختار نمادین 1.23e+6 استفاده میکنند. تفاوتشان در این است که مدل مهندسی همیشه توان را مضربی از عدد ۳ قرار میدهد تا عدد اصلی بین ۱ و ۱۰۰۰ باقی بماند. همچنین میتوانید notation را روی "compact" تنظیم کنید تا از عبارتهای انسانی و خلاصه استفاده شود:
const results = [];
for (const options of [
{ notation: "scientific" },
{ notation: "engineering" },
{ notation: "compact", compactDisplay: "short" }, // short حالت پیشفرض است
{ notation: "compact", compactDisplay: "long" },
]) {
const nf = new Intl.NumberFormat("en-US", options);
results.push({
notation: options.compactDisplay ? `${options.notation}-${options.compactDisplay}` : options.notation,
output: nf.format(12000),
});
}
console.table(results);
خروجی این آزمایش به این صورت است:
| notation | output |
'scientific' | '1.2E4' |
'engineering' | '12E3' |
'compact-short' | '12K' |
'compact-long' | '12 thousand' |
گاهی اوقات برای جلوگیری از طولانی شدن عدد، مایلید آن را گرد کنید. گزینههای مربوط به تنظیم ارقام عبارتند از:
minimumIntegerDigits (حداقل ارقام بخش صحیح)
minimumFractionDigits (حداقل ارقام اعشار)
maximumFractionDigits (حداکثر ارقام اعشار)
minimumSignificantDigits (حداقل ارقام با معنی)
maximumSignificantDigits (حداکثر ارقام با معنی)
roundingPriority (اولویت گرد کردن)
roundingIncrement (پلههای گرد کردن، مثلاً ۱۰ تا ۱۰ تا)
roundingMode (حالت گرد کردن مثل قطع کردن، گرد کردن به بالا یا پایین)
ایده کلی کار با این گزینهها ساده است: سیستم ابتدا تعداد رقمهای اعشاری که میخواهید حفظ کنید را مشخص میکند و سپس بقیه ارقام اضافی را بر اساس متد گرد کردن درخواستی شما به بالا یا پایین هدایت میکند:
const results = [];
const targetNum = 1234.56789;
for (const options of [
{ minimumFractionDigits: 4, maximumFractionDigits: 4 },
{ minimumSignificantDigits: 4, maximumSignificantDigits: 4 },
{ minimumFractionDigits: 0, maximumFractionDigits: 0, roundingMode: "floor" },
{ minimumFractionDigits: 0, maximumFractionDigits: 0, roundingMode: "floor", roundingIncrement: 10 },
]) {
const nf = new Intl.NumberFormat("en-US", options);
results.push({ options, output: nf.format(targetNum) });
}
console.table(results);
خروجی این بخش به این صورت خواهد بود:
| options | output |
{ minimumFractionDigits: 4, maximumFractionDigits: 4 } | '1,234.5679' |
{ minimumSignificantDigits: 4, maximumSignificantDigits: 4 } | '1,235' |
{ minimumFractionDigits: 0, maximumFractionDigits: 0, roundingMode: "floor" } | '1,234' |
{ minimumFractionDigits: 0, maximumFractionDigits: 0, roundingMode: "floor", roundingIncrement: 10 } | '1,230' |
از گزینههای کاربردی دیگر میتوان به useGrouping (برای فعال یا غیرفعال کردن جداکننده سه رقمی) و signDisplay (برای کنترل نمایش یا عدم نمایش علامت مثبت و منفی) اشاره کرد. متد formatRange() در اینجا هم وجود دارد و بازهای از دو عدد را با رعایت قوانین بومیسازی به هم متصل میکند (nf.formatRange(1.63, 1.95); // 1.63–1.95 m).
احتمالاً شما هم پیش از این برای چسباندن اعضای یک آرایه کدی شبیه به این نوشتهاید:
const fruits = ["apple", "banana", "cherry"];
console.log(`I like ${fruits.join(", ")}.`); // I like apple, banana, cherry.
این کد به هیچ عنوان بینالمللیسازی نشده است! در برخی زبانها، علامت جداکننده لیست اصلاً ویرگول یا کاما نیست. از طرفی در بیشتر زبانها (از جمله انگلیسی و فارسی)، شما قبل از آخرین آیتم به یک حرف ربط (مثل "و" یا "یا") نیاز دارید. حتی اضافه کردن دستی کلمه "and" هم مشکل را در زبان انگلیسی کاملاً حل نمیکند، چون بحث معروفی به نام «کامای آکسفورد» وجود دارد که آیا قبل از and کاما بگذاریم یا نه!
شیء Intl.ListFormat دقیقاً برای حل همین چالش ساخته شده است. این ابزار آرایهای از رشتهها را میگیرد و آنها را کاملاً بر اساس قوانین زبانی به هم متصل میکند تا مفاهیمی مثل اجتماع (and)، انتخاب (or) یا لیستی از واحدها را نشان دهد:
const fruits = ["apple", "banana", "cherry"];
// حالت اجتماع (Conjunction) به معنی «و»
const lfAnd = new Intl.ListFormat("en-US", { style: "long", type: "conjunction" });
console.log(`I like ${lfAnd.format(fruits)}.`);
// خروجی با رعایت کامای آکسفورد: I like apple, banana, and cherry.
// حالت انتخاب (Disjunction) به معنی «یا»
const lfOr = new Intl.ListFormat("en-US", { style: "long", type: "disjunction" });
console.log(`I can give you ${lfOr.format(fruits)}.`);
// خروجی: I can give you apple, banana, or cherry.
شیء Intl.RelativeTimeFormat وظیفه فرمت کردن تفاوت و فواصل زمانی را بر عهده دارد (مثلاً برای سیستمهای گفتگوی آنلاین یا شبکههای اجتماعی). این ابزار دو آرگومان به عنوان ورودی میگیرد: یک عدد (مثبت یا منفی) و یک واحد زمانی (مثل "day"، "hour" یا "minute").
این ابزار چند کار پیچیده را همزمان انجام میدهد:
واحد زمانی را بومیسازی کرده و قوانین جمع و مفرد آن را اعمال میکند (مثل تفاوت "1 day" و "2 days").
عبارت مناسب را برای زمانهای گذشته (منفی) و آینده (مثبت) انتخاب میکند (مثل "in 1 day" در برابر "1 day ago").
در صورت تنظیم، عبارات خاص و بومی برای بازههای نزدیک را جایگزین میکند (مثلاً به جای "1 day ago" کلمه خودمانیِ "yesterday" را مینویسد).
const rtf = new Intl.RelativeTimeFormat("en-US", { numeric: "auto" });
console.log(rtf.format(1, "day")); // tomorrow (به خاطر تنظیم حالت auto)
console.log(rtf.format(2, "day")); // in 2 days
console.log(rtf.format(-1, "hour")); // 1 hour ago
شیء Intl.DurationFormat برای فرمت کردن بازههای طولانیِ زمان مثل «۳ ساعت و ۴ دقیقه و ۵ ثانیه» کاربرد دارد. این ابزار در واقع یک عملیات مستقلِ پایه نیست، بلکه در پشت صحنه از ترکیب ساختارهای Intl.NumberFormat و Intl.ListFormat استفاده میکند تا تکتک اجزا را فرمت کرده و در نهایت با یک جداکننده لیستِ بومی به هم متصل کند.
این شیء ورودی خود را به صورت یک شیء استاندارد Temporal.Duration یا یک شیء معمولی جاوااسکریپتی با همان ویژگیها دریافت میکند:
console.log(
new Intl.DurationFormat("en-US", { style: "long" })
.format({ hours: 3, minutes: 4, seconds: 5 })
);
// خروجی فوقالعاده تمیز: 3 hours, 4 minutes, and 5 seconds
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript