JS `Temporal API` `DateTimeFormat` 与 `Intl.Locale` 的高级国际化定制

大家好!欢迎来到今天的国际化定制小课堂。我是你们的老朋友,今天咱们不聊鸡汤,只啃硬骨头,一起深入 Temporal APIDateTimeFormatIntl.Locale 的世界,玩转高级国际化定制。

首先,咱们得明确一个核心思想:国际化不仅仅是翻译文本那么简单,它涉及到日期、时间、数字、货币等等各种格式的本地化,让你的应用在不同文化背景下都能像在家一样舒适。

第一部分:Temporal API 的崭新世界

Temporal API 是 JavaScript 中处理日期和时间的新一代 API,旨在替代老旧的 Date 对象,解决其设计上的种种缺陷。它引入了许多新的类型,例如 Temporal.PlainDateTemporal.PlainTimeTemporal.PlainDateTimeTemporal.ZonedDateTime 等,让我们能更精确、更灵活地处理日期和时间。

  1. Temporal.PlainDate:纯粹的日期

    Temporal.PlainDate 只包含年、月、日,不涉及时区或时间信息。

    const plainDate = Temporal.PlainDate.from({ year: 2024, month: 10, day: 27 });
    console.log(plainDate.toString()); // 输出: 2024-10-27
  2. Temporal.PlainTime:纯粹的时间

    Temporal.PlainTime 只包含小时、分钟、秒、毫秒等时间信息,同样不涉及时区。

    const plainTime = Temporal.PlainTime.from({ hour: 10, minute: 30, second: 0 });
    console.log(plainTime.toString()); // 输出: 10:30:00
  3. Temporal.PlainDateTime:日期和时间的组合

    Temporal.PlainDateTimeTemporal.PlainDateTemporal.PlainTime 的结合体,依然不带时区信息。

    const plainDateTime = Temporal.PlainDateTime.from({ year: 2024, month: 10, day: 27, hour: 10, minute: 30 });
    console.log(plainDateTime.toString()); // 输出: 2024-10-27T10:30:00
  4. Temporal.ZonedDateTime:带时区的日期和时间

    Temporal.ZonedDateTimeTemporal API 中最强大的类型之一,它包含了日期、时间和时区信息。

    const zonedDateTime = Temporal.ZonedDateTime.from({
        year: 2024,
        month: 10,
        day: 27,
        hour: 10,
        minute: 30,
        timeZone: 'America/Los_Angeles'
    });
    console.log(zonedDateTime.toString()); // 输出类似: 2024-10-27T10:30:00-07:00[America/Los_Angeles]

第二部分:DateTimeFormat 的格式化艺术

Intl.DateTimeFormat 对象是 ECMAScript 国际化 API 的一部分,用于格式化日期和时间,使其符合特定区域设置的约定。

  1. 基本用法

    const date = new Date();
    const formatter = new Intl.DateTimeFormat('zh-CN'); // 中国大陆的区域设置
    console.log(formatter.format(date)); // 输出类似: 2024/10/27
  2. 选项配置

    Intl.DateTimeFormat 允许你通过选项对象来定制日期和时间的格式。

    选项 描述 可能的值
    localeMatcher 指定用于区域设置匹配的算法。 "best fit" (默认), "lookup"
    timeZone 指定时区。 IANA 时区名称,例如 "America/Los_Angeles"
    formatMatcher 指定用于格式化的算法。 "best fit" (默认), "basic"
    hour12 是否使用 12 小时制。 true, false (默认,使用 24 小时制)
    hourCycle 指定 12 小时制的小时周期。 "h11" (0-11), "h12" (1-12), "h23" (0-23), "h24" (1-24)
    weekday 星期几的显示方式。 "narrow", "short", "long"
    era 纪元的显示方式。 "narrow", "short", "long"
    year 年份的显示方式。 "numeric", "2-digit"
    month 月份的显示方式。 "numeric", "2-digit", "narrow", "short", "long"
    day 日期的显示方式。 "numeric", "2-digit"
    hour 小时的显示方式。 "numeric", "2-digit"
    minute 分钟的显示方式。 "numeric", "2-digit"
    second 秒的显示方式。 "numeric", "2-digit"
    fractionalSecondDigits 小数秒的位数。 1, 2, 3
    timeZoneName 时区名称的显示方式。 "short", "long", "shortOffset", "longOffset", "shortGeneric", "longGeneric"
    calendar 使用的日历系统。 例如 "gregory", "chinese", "islamic"
    numberingSystem 使用的数字系统。 例如 "arab", "latn"
    const date = new Date();
    const options = {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        weekday: 'long',
        hour: 'numeric',
        minute: 'numeric',
        timeZone: 'America/Los_Angeles',
        timeZoneName: 'short'
    };
    const formatter = new Intl.DateTimeFormat('zh-CN', options);
    console.log(formatter.format(date)); // 输出类似: 2024年10月27日 星期日 上午10:30 PDT
  3. formatRange 的妙用

    formatRange 方法可以格式化日期范围,这在显示事件持续时间或时间跨度时非常有用。

    const startDate = new Date(2024, 0, 1); // 2024年1月1日
    const endDate = new Date(2024, 0, 10); // 2024年1月10日
    const formatter = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' });
    console.log(formatter.formatRange(startDate, endDate)); // 输出类似: 2024年1月1日 - 2024年1月10日
  4. Temporal API 结合

    DateTimeFormat 也能与 Temporal API 的类型无缝衔接。

    const zonedDateTime = Temporal.ZonedDateTime.from({
        year: 2024,
        month: 10,
        day: 27,
        hour: 10,
        minute: 30,
        timeZone: 'America/Los_Angeles'
    });
    const formatter = new Intl.DateTimeFormat('zh-CN', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        hour: 'numeric',
        minute: 'numeric',
        timeZoneName: 'short'
    });
    console.log(formatter.format(zonedDateTime)); // 输出类似: 2024年10月27日 上午10:30 PDT

第三部分:Intl.Locale 的区域设置魔法

Intl.Locale 对象表示一个 Unicode 区域设置标识符,它提供了访问区域设置相关信息的接口,例如语言、脚本、国家/地区、变体等。

  1. 创建 Intl.Locale 对象

    const locale = new Intl.Locale('zh-CN');
    console.log(locale.language); // 输出: zh
    console.log(locale.script); // 输出: Hans
    console.log(locale.region); // 输出: CN
  2. 访问区域设置信息

    Intl.Locale 提供了许多属性和方法来访问区域设置信息。

    属性/方法 描述
    language 语言代码。
    script 脚本代码。
    region 国家/地区代码。
    baseName 不包含扩展名和私有使用的基本区域设置名称。
    toString() 返回区域设置标识符字符串。
    maximize() 返回包含尽可能多信息的区域设置。
    minimize() 返回包含尽可能少信息的区域设置。
    getTextInfo() 返回文本方向信息。
    timeZones 返回与该区域设置关联的时区列表(需要浏览器支持)。
    hourCycles 返回该区域设置支持的小时周期列表(需要浏览器支持)。
    calendars 返回该区域设置支持的日历系统列表(需要浏览器支持)。
    numberingSystems 返回该区域设置支持的数字系统列表(需要浏览器支持)。
    const locale = new Intl.Locale('en-US-u-ca-islamic-nu-arab');
    console.log(locale.calendar); // 输出: islamic
    console.log(locale.numberingSystem); // 输出: arab
    console.log(locale.toString()); // 输出: en-US-u-ca-islamic-nu-arab
  3. DateTimeFormat 结合,实现更精细的控制

    Intl.Locale 可以与 DateTimeFormat 结合,实现更精细的日期和时间格式控制。

    const locale = new Intl.Locale('ar-AE-u-ca-islamic-nu-arab');
    const options = {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        calendar: locale.calendar,
        numberingSystem: locale.numberingSystem
    };
    const formatter = new Intl.DateTimeFormat(locale.toString(), options);
    const date = new Date();
    console.log(formatter.format(date)); // 输出符合阿拉伯伊斯兰历的日期格式

第四部分:高级定制技巧

  1. 自定义格式模式

    虽然 Intl.DateTimeFormat 提供了丰富的选项,但有时你可能需要更精细的控制。这时,你可以考虑使用第三方库,例如 date-fnsmoment.js (尽管 Moment.js 已经处于维护模式,不推荐用于新项目,但仍然可以借鉴其格式化模式)。这些库允许你使用自定义的格式模式来格式化日期和时间。

    // 使用 date-fns
    import { format } from 'date-fns';
    import { zhCN } from 'date-fns/locale'; // 引入中文区域设置
    
    const date = new Date();
    const formattedDate = format(date, 'yyyy年MM月dd日 EEEE', { locale: zhCN });
    console.log(formattedDate); // 输出类似: 2024年10月27日 星期日
  2. 处理相对时间

    相对时间,例如 "几分钟前"、"明天" 等,也是国际化中常见的需求。Intl.RelativeTimeFormat 对象可以帮助你处理相对时间。

    const rtf = new Intl.RelativeTimeFormat('zh-CN', { numeric: 'auto' });
    console.log(rtf.format(-1, 'day'));   // "昨天"
    console.log(rtf.format(1, 'day'));    // "明天"
    console.log(rtf.format(-3, 'hour'));  // "3小时前"
    console.log(rtf.format(2, 'minute')); // "2分钟后"
  3. 处理复数形式

    不同语言对复数的处理方式不同。Intl.PluralRules 对象可以帮助你根据数量选择正确的复数形式。

    const pr = new Intl.PluralRules('en-US');
    console.log(pr.select(0));  // "other"
    console.log(pr.select(1));  // "one"
    console.log(pr.select(2));  // "other"
    console.log(pr.select(1.5));// "other"
    
    const messages = {
      one:   "There is # message.",
      other: "There are # messages."
    };
    
    function format(n, message) {
        const rule = pr.select(n);
        const msg = messages[rule] || messages.other;
        return msg.replace("#", n);
    }
    
    console.log(format(1));    // "There is 1 message."
    console.log(format(2));    // "There are 2 messages."
  4. 利用 toLocaleString 简化操作

    虽然 Intl.DateTimeFormat 是专门用于格式化日期和时间的 API,但 JavaScript 的 Date 对象本身也提供了一个 toLocaleString 方法,可以方便地进行本地化格式化。

    const date = new Date();
    
    // 使用默认区域设置
    console.log(date.toLocaleString());
    
    // 指定区域设置和选项
    const options = {
       year: 'numeric',
       month: 'long',
       day: 'numeric',
       hour: 'numeric',
       minute: 'numeric',
       timeZone: 'America/Los_Angeles',
       timeZoneName: 'short'
    };
    console.log(date.toLocaleString('zh-CN', options)); // 输出类似: 2024年10月27日 上午10:30 PDT

    toLocaleString 方法在快速实现本地化格式化时非常方便,但如果你需要更精细的控制和更高级的功能,Intl.DateTimeFormat 仍然是更好的选择。

第五部分:实战演练

让我们来做一个简单的例子:一个多语言的事件倒计时器。

<!DOCTYPE html>
<html>
<head>
    <title>多语言事件倒计时器</title>
    <style>
        body {
            font-family: sans-serif;
        }
    </style>
</head>
<body>
    <h1>事件倒计时</h1>
    <label for="locale">选择语言:</label>
    <select id="locale">
        <option value="en-US">English (US)</option>
        <option value="zh-CN">中文 (中国)</option>
        <option value="ja-JP">日本語 (日本)</option>
    </select>
    <div id="countdown"></div>

    <script>
        const localeSelect = document.getElementById('locale');
        const countdownDiv = document.getElementById('countdown');
        const eventDate = new Date('2024-12-25T00:00:00'); // 圣诞节

        function updateCountdown() {
            const now = new Date();
            const diff = eventDate.getTime() - now.getTime();

            if (diff <= 0) {
                countdownDiv.textContent = '事件已发生!';
                return;
            }

            const days = Math.floor(diff / (1000 * 60 * 60 * 24));
            const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
            const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
            const seconds = Math.floor((diff % (1000 * 60)) / 1000);

            const locale = localeSelect.value;
            const dayUnit = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(days, 'day');
            const hourUnit = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(hours, 'hour');
            const minuteUnit = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(minutes, 'minute');
            const secondUnit = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(seconds, 'second');

            let countdownText;
            if (locale === 'zh-CN') {
                countdownText = `${days}天${hours}小时${minutes}分钟${seconds}秒`;
            } else if (locale === 'ja-JP') {
                countdownText = `${days}日${hours}時間${minutes}分${seconds}秒`;
            } else {
                countdownText = `${days} days, ${hours} hours, ${minutes} minutes, ${seconds} seconds`;
            }

            countdownDiv.textContent = countdownText;
        }

        localeSelect.addEventListener('change', updateCountdown);
        setInterval(updateCountdown, 1000); // 每秒更新一次
        updateCountdown(); // 初始更新
    </script>
</body>
</html>

这个例子演示了如何使用 Intl.RelativeTimeFormat 实现多语言的倒计时显示。你可以根据需要扩展这个例子,添加更多的语言支持和更复杂的格式化。

总结

Temporal APIDateTimeFormatIntl.Locale 是 JavaScript 国际化工具箱中的强大武器。掌握它们,你就能构建出真正全球化的应用,让用户无论身在何处,都能感受到你的用心。记住,国际化不仅仅是技术问题,更是一种对不同文化的尊重。希望今天的课程能帮助你更好地理解和应用这些 API,在国际化道路上越走越远!

今天的课程就到这里,谢谢大家!希望大家都能成为国际化大师!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注