整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          正確使用這些HTML標簽屬性會有意想不到的效果

          正確使用這些HTML標簽屬性會有意想不到的效果

          前端開發的都知道HTML標簽很多,但常用的也就那么幾個。個人覺得在標簽上面小程序做得很好,只有26個標簽,而且是我們常用到的。文章介紹一些常用的HTML標簽,以及一些非常有用的HTML屬性。

          本次不詳細討論標簽的使用,有興趣的朋友可以關注我下篇關于常用標簽的使用注意事項的文章。

          以下是小編最常用的標簽,可以說幾乎開發網頁就用到這么22個標簽。

          22個最常用的標簽

          1.點擊“下載” 鏈接,下載圖片或文件而不是預覽,直接添加一個download屬性就可以

          <a href="large.jpg" download>下載</a>

          2.disable="disable"禁止點擊屬性,要去除時,請用js刪除該屬性。

          3.<button type="button"></button>必須指明類型type="button",否則默認為submit,造成頁面刷新。

          4.input type="file" accept="image,excel,word” 上傳控件的accept屬性

          默認是可以上傳所有文件。

          accept:表示可以選擇的文件MIME類型,多個MIME類型用英文逗號分開。

          5.favicon.ico圖標是網站的縮略標志,可以顯示在瀏覽器標簽、地址欄左邊和收藏夾,是展示網站個 性的縮略logo標志

          <head>

          <link rel="shortcut icon" href="/favicon.ico"/>

          <link rel="bookmark" href="/favicon.ico"/>

          </head>

          6.圖片加載慢時用logo圖片代替,onerror當圖片不存或加載不出來才生效。

          7.HTML使用標簽的refresh功能來刷新或跳轉頁面

          <meta http-equiv="refresh" content="3" />表示頁面每隔3秒鐘刷新一次。

          <meta http-equiv="refresh" content="3; url=<#ZC_BLOG_HOST#>" />表示頁面3秒后跳轉到 <#ZC_BLOG_HOST#>頁面。

          說明:若果我們網站的首頁是一個index.asp頁面,當它遭到CC攻擊是,我們把index.asp頁面打開的內容保存到index.html(新建文本并保存)中。然后清空index.asp的內容(清空之前注意備份),將上面的refresh語句復制進來。這樣就可以有效的防止CC攻擊。

          多國內企業正積極開拓國際市場,如Shopee、阿里的Lazada、字節的TikTok、拼多多海外版Temu、以及服裝快消領域的Shein等。當國內市場存量業務達到峰值預期時,海外業務成為各公司未來收入增長的主要動力,因此,國際化已成為越來越重要的職業發展方向。

          國內IT企業收入天花板: 「10億X2元X365天=7300億元」,也就是10億人口,企業每天賺取用戶2元,保持365天,就是單業務增長的營收天花板(大部分業務賺不到2元,用戶量也沒到10億)。比如視頻如果60元一個月那會員營收天花板就可以這么預估. 甚至比這個還低, 畢竟用戶會流失, 拉新也要成本, 運營成本是在遞增的。

          國際化不僅僅是多語言文案適配這么簡單,而是一全套的工程化解決方案。筆者覺得更重要的是「從業人員需要具備全球視野,對多元文化有包容心和敬畏心理,同時知識面要求也較高」。比如,了解SEA、US、UK等常見地區的簡寫,尊重伊斯蘭教的齋月節等習俗。對于服務全球用戶的產品來說,對應產品的要求更加復雜,多樣性體現在不同的文化習俗差異上,其實即便在龐大的中國內部也存在南北差異。了解的越多越發現這個世界的“多樣性”。

          概念說明

          蘋果鍵盤怎么賣多國?

          蘋果鍵盤有很多型號不同型號的布局不一樣https://www.apple.com/shop/product/MK2A3J/A/magic-keyboard-japanese

          apple-keyboard

          那如何模仿蘋果造一把可以賣到世界各地的鍵盤?

          1. 電路板等硬件配件統一生產
          2. 制定三種布局方案(Arabic, Russian, Ukrainian歸為一種, Chinese (Zhuyin)和Korean為一種, Japanese為一種),單獨開孔
          3. 鍵帽印刷不同語言的文案
          4. MacOS開發語言輸入軟件適配不同鍵盤的語言輸入

          其中2,3,4都是為產品的全球化服務

          全球化=國際化i18n+本地化l10n

          https://en.wikipedia.org/wiki/Internationalization_and_localization

          globalization

          1. 產品設計和開發部署需要需要考慮國際化i18n
            1. 多語言
            2. 多布局,如阿拉伯語的RTL
            3. 多貨幣
            4. 全球多地區多機房部署(離用戶越近服務體驗越好,數據物理存儲隔離符合各個國家數據安全要求)
          2. 產品本地化L10N是國際化后的「可選」流程,需要引入「本地化團隊」轉化和質量驗收,再投入本地化市場
            1. 本地化步驟是可選的,如英美產品UI,語言基本一致可互通,本地化投入少
            2. 中文地區等有簡體繁體,不同地區用語習慣不一樣,也要特別兼顧,如香港的粵語和廣州的粵語,在一些用詞有區別,像吸管(廣州)-飲筒(香港),你可以看https://www.zhihu.com/question/20663233
            3. 阿拉伯語,希伯來語等地區RTL的閱讀習慣,對產品改動較大需要特殊適配

          國際化

          產品面向全球用戶,需要做語言適配,針對不同國家地區的用戶提供對應語言的版本。本質是「文本替換」,也要考慮文本閱讀方向,比如阿拉伯語和希伯來語是從右到左。

          可以看下Apple的做法,對不同國家地區提供不同服務

          • Apple US 對應鏈接 https://www.apple.com/
          • Apple CN 對應鏈接 https://www.apple.com.cn/
          • Apple HK 對應鏈接 https://www.apple.com/hk/en/

          常見地區語言對應關系可以看 ISO 3166-1(https://baike.baidu.com/item/ISO%203166-1/5269555?fr=ge_ala)

          Intl

          MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl

          瀏覽器中的 Intl 是一個內置的 JavaScript 對象,用于國際化(Internationalization)支持。它提供了處理日期、時間、數字格式化以及貨幣和語言的能力,以便網站能夠根據用戶的語言和地區習慣來顯示內容。在 JavaScript 中,您可以使用 Intl 對象來執行以下操作:

          1. 格式化日期和時間:使用 Intl.DateTimeFormat 對象可以格式化日期和時間,并根據用戶的地區偏好顯示日期和時間。
          2. 格式化數字:使用 Intl.NumberFormat 對象可以格式化數字,并根據用戶的地區偏好顯示數字。
          3. 處理貨幣:使用 Intl.NumberFormat 對象結合指定的貨幣代碼可以格式化貨幣,并根據用戶的地區偏好顯示貨幣。
          4. 語言和區域設置信息:使用 Intl.getCanonicalLocales 方法可以獲取支持的語言和區域設置信息。關于標點符號,通常在國際化的環境下,標點符號使用英文是比較常見的做法,因為英文標點符號在全球范圍內都有通用性,可以避免因為地域差異而引起的誤解。在 Intl 對象中,一般不會直接涉及到標點符號的處理,而是主要用于處理日期、時間、數字和貨幣等格式化需求。

          前置知識

          語言標識

          全地球有N個民族,有的民族有自己的語言, 有的民族用其他國家民族傳遞過來的語言, 融化吸收然后發展自己的文字。

          按照ISO標準(https://zh.m.wikipedia.org/wiki/ISO_639-1),語言可以用大類+小類表示, 比如「zh」就是漢語,是一個大類,而「zh-CN」就是簡體中文的縮寫, 新加坡華人眾多久了就有「zh-SG」, 表示的是新加坡使用的中文,其次還有「zh-HK/zh-TW和zh-Hant/zh-Hans」等等

          語言聲明是三段式結構 [language]-[script]-[region] , 如zh-Hans-CN表示中國地區簡體中文, zh-Hant表示所有中文繁體

          Language Code Table(http://www.lingoes.net/zh/translator/langcode.htm)

          一起來看下蘋果官網是如何適配多國語言的

          澳門apple https://www.apple.com/mo/

          香港apple

          英文 https://www.apple.com/hk/en/

          中文 https://www.apple.com/hk/

          中國大陸地區apple https://www.apple.com.cn/

          臺灣apple https://www.apple.com/tw/

          新加坡apple https://www.apple.com/sg/

          日本apple https://www.apple.com/jp/

          可以看到有的是根域名下通過ISO地區的path比如**/hk/**這樣來區分的,有的是直接換域名,比如中國大陸地區

          文字閱讀順序

          按照普通的中文和英文順序,都是LTR,上到下,都是世界范圍通用的

          而ar阿拉伯語, ur烏都語, he希伯來語都是特殊的從右到左, 即RTL的一般會通過標簽的dir屬性標識, 比如下面的解釋HTML dir Attribute(https://www.w3schools.com/tags/att_global_dir.asp)

          HEBREW是指希伯來語,這是一種在以色列廣泛使用的語言,也是猶太教的宗教經典文本的原始語言。它屬于阿夫羅亞細亞語系,有著悠久的歷史和文化價值。「希伯來語有其獨特的書寫系統,從右向左書寫。」上圖的概念很少人普及, 因為非國際化產品不需要多語言, 做需要支持海外業務和全球應用的同學可以多了解下. 傳統的英文, 中文簡體, 拉丁文等都是上圖LATIN的閱讀順序, 如果用上「top, 下bottom, 左left, 右right」代表我們的習慣, 也就是「Z」這樣的順序. 即行到行是從上到下的順序, 行內閱讀順序是從左到右.

          文檔流和閱讀順序

          即left→right, top→bottom的順序,有主次分別,left→right的優先級高于top→bottom

          而Web標準對其定義是下面這樣的

          • left=inline-start
          • right=inline-end
          • top=block-start
          • bottom=block-end

          講個笑話, 古代書籍就是按照 writing-mode: vertical-rl 排版的

          joke

          布局

          content-flows

          比如 margin: left 或者 text-align: left 在多語言場景都是不合適的,你的左右不是其他人的左右。

          而應該用 margin-inline-starttext-align: start 替代,即inline軸和block軸

          // 下面兩兩相等,請拋棄left/right/top/bottom等屬性
          // https://web.dev/learn/css/logical-properties/#terminology
          
          margin-left: 1px
          margin-inline-start: 1px
          
          margin-right: 1px
          margin-inline-end: 1px
          
          margin-top: 1px
          margin-block-start: 1px
          
          margin-bottom: 1px
          margin-block-end: 1px
          
          text-align: left
          text-align: start
          
          text-align: right
          text-align: end
          
          max-width: 100px
          max-inline-width: 100px
          max-inline-size: 150px
          
          max-height: 100px
          max-block-width: 100px
          
          padding-left: 1px
          padding-inline-start: 1px
          
          padding-top: 1px
          padding-block-start: 1px
          
          top: 0.2em;
          inset-block-start: 0.2em;
          
          bottom: 0.2em;
          inset-block-end: 0.2em;
          
          left: 2px;
          inset-inline-start: 2px;
          
          right: 2px;
          inset-inline-end: 2px;
          
          border-bottom: 1px solid red;
          border-block-end: 1px solid red;
          
          border-bottom-right-radius: 1em;
          border-end-end-radius: 1em;
          
          height: 160px;
          block-size: 160px;
          
          width: 160px;
          inline-size: 160px;
          
          

          也可以看下面的例子

          • https://codepen.io/web-dot-dev/pen/gOxXOLK
          • https://codepen.io/web-dot-dev/pen/mdMqdOx

          如上兩個例子通過margin-inline-start等屬性,再在html元素上添加 dir: rtl 就可以實現多語言的閱讀順序兼容

          由此, 常見的布局也會更新為以下形式,常見的物理盒模型用于尺寸計算, 邏輯盒模型用于國際化處理

          盒子模型

          writing mode 決定 content-flows

          上面寫了文檔有inline and block flow,對應english的left和right,top和bottom。而 writing-mode 可以修改content-flows,比如下面的值

          /* 關鍵值 */
          writing-mode: horizontal-tb;
          writing-mode: vertical-rl;
          writing-mode: vertical-lr;
          

          可以這么理解 writing-mode: horizontal-tb ,前面的horizontal/vertical是指的inline軸的方向,

          https://codepen.io/manfredhu/pen/xxWdpaK

          vi和vb

          視口寬高viewport在這里也有特殊含義. 比如寬高vw和vh也被取代,用 vi(viewport inline) 和 vb(viewport block)替代

          1%寬度=1vw=1vi 1%高度=1vh=1vb
          

          JS的scrollLeft

          DOM的API可以通過「Element.scrollLeft」獲取到元素滾動的距離,下圖是一個實際例子

          scrollLeft的rtl

          這里在最后做了一個遮罩(綠色邊框區域),內部藍色部分類似一個走馬燈,通過overflow:hidden將藍色高亮部分超出的區域遮住

          當藍色部分滾動到最后,綠色遮罩隱藏,達到一個遮蓋,滾動到最后消失的效果,代碼如下

          const ref=document.querySelector('.tiktok-table__container') // 父節點,藍色區域
          const ref2=document.querySelector('.tiktok-table__container > table') // 子節點,表格區域
          const bufferWidth=30 // 留一點buffer空間
          if (ref && ref2 && ref.clientWidth + ref.scrollLeft >=ref2.clientWidth - bufferWidth) {
            // 滾動到最后隱藏綠色遮罩
            setTableRightMask(false)
          } else {
            setTableRightMask(true)
          }
          

          但是在RTL下,神奇的事情就發生了,scrollLeft居然是負數

          這是因為RTL的實現是通過HTML標簽增加屬性 dir="rtl” 實現的,會將文檔完全翻轉過來,所以scrollLeft就會是負數。因為此時(0, 0)這個原點已經是表格右邊了

          解決方法也很簡單,取絕對值唄,這樣就忽略了方向的影響

          locale

          根據ISO標準對全球國家地區進行劃分https://en.wikipedia.org/wiki/ISO_3166-2. 如 "US" 表示美國,"CN" 表示中國. 還有常見的如「zh-CN, en-US, en-GB等」

          • CN是國家地區碼, 根據國際標準 ISO 3166-1 規定的國家和地區代碼。ISO 3166-1 是用于標識國家和地區的國際標準,每個國家或地區都有一個唯一的兩字母代碼。"CN" 代表中華人民共和國(People's Republic of China),即中國
          • zh-CN是語言地區碼, 它通常用于表示中文("zh" 代表中文)以及特定的地區或國家,這里 "CN" 代表中國。"zh" 代表中文,這是根據國際標準 ISO 639-1 規定的語言代碼。ISO 639-1 是用于標識語言的國際標準,每個語言都有一個唯一的兩字母代碼。"zh" 代表中文,但不區分中文的不同方言,如普通話和粵語。
          • zh-Hans-CN 表示中國大陸地區的簡體中文,還有"zh-Hans-SG" 可用于表示新加坡的官方簡化中文,"zh-Hans-TW" 可用于表示臺灣的官方簡化中文

          Intl. Locale

          舉個說下Intl API對于locale的定義

          const korean=new Intl.Locale('ko', {
            script: 'Kore',
            region: 'KR',
            hourCycle: 'h23',
            calendar: 'gregory',
          });
          
          const japanese=new Intl.Locale('ja-Jpan-JP-u-ca-japanese-hc-h12');
          
          console.log(korean.baseName, japanese.baseName);
          // Expected output: "ko-Kore-KR" "ja-Jpan-JP"
          

          可以看到Intl. Locale就是把傳入的字符串拆解為 [language]-[script]-[region] 的組成.

          • ja:代表語言代碼,表示日語(Japanese)
          • Jpan:代表腳本代碼,表示使用日語文字(Japanese script)
          • JP:代表地區代碼,表示日本(Japan) 這里的 u-ca-japanese 表示unicode calendar也就是日歷格式(日本日歷與眾不同), hc表示hourCycle這里hc-h12表示12小時制. u-ca-japanesehc-h12 的順序無關, 也就是說如下兩種用法完全等價
          const japanese=new Intl.Locale('ja-Jpan-JP-u-ca-japanese-hc-h12');
          const japanese2=new Intl.Locale('ja-Jpan-JP-u-hc-h12-ca-japanese');
          

          -u (unicode)可以理解為額外擴展插件, 插件系統支持以下擴展. 如上使用calendar擴展和hourCycle擴展



          calendarca (extension)caseFirstkf (extension)collationco (extension)hourCyclehc (extension)numberingSystemnu (extension)numerickn (extension)

          calendar:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar)

          caseFirst:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/caseFirst)

          collation:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/collation)

          hourCycle:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle)

          numberingSystem:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/numberingSystem)

          numeric:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/numeric)

          語言聲明

          <html lang="en-US">
          <!-- 說明頁面語言是美式英文 -->
          <p>I felt some <span lang="de">schadenfreude</span>.</p> <!-- lang不是只有html標簽才有, 其他標簽也可以添加 -->
          <a href="/path/to/german/version" hreflang="de" lang="de">Deutsche Version</a> <!-- a標簽上可以用lang表示顯示的文本的語言, 也可以用hreflang表示跳轉頁面的語言 -->
          

          language設置

          語言屬性通常代表語言標識符的主要部分。它標識了用于表達語言的基本信息。例如,在 BCP 47 標準中,語言標識符通常包含了語言的主要代碼部分,例如 "en" 代表英語,"es" 代表西班牙語。

          const locales=['en', 'de', 'ja'];
          const displayNames=new Intl.DisplayNames('en-US', { type: 'language' });
          locales.forEach(locale=> console.log(displayNames.of(locale)));
          
          // English
          // German
          // Japanese
          

          如果已經有如上代碼, 再進一步給這些內容添加樣式是非常簡單的, 我們可以使用CSS的選擇器. 如 [lang|="fr"] 或者 :lang(fr)

          [lang|="fr"] 選擇屬性 lang=fr 或者lang屬性以fr開頭的元素, 如 lang=fr-CA

          script設置

          腳本屬性是語言標識符的可選部分,表示使用的書寫系統或文字的風格。這是一個輔助信息,用于更精確地表示特定語言的書寫習慣。例如,"Hans" 代表簡體中文,"Latn" 代表拉丁文。

          script的of支持傳入BCP47規范的二字碼(https://en.wikipedia.org/wiki/IETF_language_tag), 如zh

          const scriptNames=new Intl.DisplayNames('en-US', { type: 'script' });
          console.log(scriptNames.of('Hans')); // output:Simplified
          console.log(scriptNames.of('Hant')); // output:Traditional
          console.log(scriptNames.of('Latn')); // output:Latin
          
          const scriptNames=new Intl.DisplayNames('zh-CN', { type: 'script' });
          console.log(scriptNames.of('Hans')); // output:簡體
          console.log(scriptNames.of('Hant')); // output:繁體
          console.log(scriptNames.of('Latn')); // output:拉丁文
          

          region設置

          region 的of支持傳入https://en.wikipedia.org/wiki/ISO_3166-2 里的國家二字碼

          const regionNamesInEnglish=new Intl.DisplayNames(['en'], { type: 'region' });
          const regionNamesInTraditionalChinese=new Intl.DisplayNames(['zh-Hant'], { type: 'region' });
          console.log(regionNamesInEnglish.of('US'));
          // Expected output: "United States"
          console.log(regionNamesInTraditionalChinese.of('US'));
          // Expected output: "美國"
          

          文本

          閱讀順序聲明

          文本的閱讀順序聲明

          html標簽dir屬性

          可以通過html標簽的dir屬性設置

          <html dir="ltr">
          <html dir="rtl">
          

          css屬性writing-mode(https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode)

          writing-mode: horizontal-tb;
          writing-mode: vertical-lr;
          writing-mode: vertical-rl;
          

          文本行的布局方向, 以及塊的排列方向。如果想作用在整個文檔需要設置html標簽, 則全局生效

          「第一個屬性horizontal/vertical指的是行塊的排列, 第二個屬性則是指文本內容的流向(content flows)」

          翻轉

          形如 「?」 這種符號類,在多語言下是不一樣的

          比如ar阿拉伯語和ur烏爾都斯語問號是RTL的,即 「?」(https://zh.m.wiktionary.org/wiki/%D8%9F)是OK的

          而he希伯來語是LTR的,即 「?」 是OK的

          是不是很神奇?一個問號也能玩出花來

          線索管理頁-英文和阿拉伯語

          常見的需要RTL的語言有下面這些

          1. 「阿拉伯語(AR)」:阿拉伯語是使用 RTL 方向書寫的最著名的語言之一。它是中東和北非地區的主要語言,以及伊斯蘭教的官方語言。
          2. 「希伯來語(HE)」:希伯來語是猶太人的宗教和文化語言,以及以色列的官方語言。它也是一個使用 RTL 方向書寫的語言。
          3. 「波斯語(FA)」:波斯語,也稱為法爾西語,是伊朗的官方語言,以及一些中東國家的官方或輔助語言。它使用 RTL 方向書寫。
          4. 「烏爾都語(UR)」:烏爾都語是巴基斯坦和印度的官方語言之一,以及一種使用 RTL 方向書寫的語言。
          5. 「帕斯圖語(PS)」:帕斯圖語是阿富汗的官方語言之一,也是使用 RTL 方向書寫的語言。
          const rtlLangs=[
              'ar', // 阿拉伯語
              'ur', // 巴基斯坦
              'he', // 以色列
              'he-IL', // 希伯來語(以色列)
              'fa-IR', // 波斯語(伊朗)
              'ps' // 帕斯圖語
          ];
          

          多語言文案

          l10n本地化的一個比較多工作量的部分是文本的翻譯, 一種文本到N種文本的翻譯需要引入本地化團隊. 技術實現上選擇也很多

          程序打包嵌入文案

          通過 key: text 映射, 比如 t('key') 最后程序跑出來就是text文案, 這種方式不會依賴其他東西, 跟普通網頁一樣內容都是CDN文件. 缺點是文案做為靜態資源需要用戶額外獲取, 如果處理不好替換錯誤就展示 key 內容而不是

          • vue i18n:https://kazupon.github.io/vue-i18n/zh/started.html#html
          • react i18n:https://react.i18next.com

          以下例子以Vue為例, 配置如「en.json, fr.json」等等的靜態配置文案, 打包嵌入CDN的JS文件里

          Vue i18n example:https://codesandbox.io/p/sandbox/o7n6pkpwoy?file=%2Fstore.js%3A10%2C14

          接口獲取

          程序運行時通過接口拿文案,可以通過html標簽添加query參數 lang=xxx 標記頁面語言, 或者cookie標記語言選擇

          實時翻譯替換

          加載翻譯的腳本, 在切換語言的時候替換掉加載的文本。好處是加載的腳本是當前語言所需要的, 不會有其他語言的冗余. 缺點是依賴一個翻譯服務, 如果翻譯服務宕機了網頁就不能正常訪問了

          User -> gateway -> SSR -> i18n cache -> read-time translation services(實時翻譯服務)
          

          占位符與單復數處理-ICU語法

          DevPal - ICU message editor:https://devpal.co/icu-message-editor/?data=I%20have%20%7Bnum%2C%20plural%2C%20one%7B%7Bnumber%7D%20%23%20apple%7D%20other%7B%23%20apples%7D%7D%2Cbut%20it%27s%20too%20small%0A

          ICU語法即通用的有if-else邏輯的DSL,如下DSL可以根據傳入的值換取不同的表示,常用于國際化業務

          I have {num, plural, one{{number} # apple} other{# apples}},but it's too small
          

          Intl. Segmenter 分段器

          如果你用過vim一定知道w(word)可以移動到下個單詞, 英文里把文本分為單詞、句子和段落,同理中文也是

          • 「w」 word 一個單詞
          • 「s」 sentence 一個句子
          • 「p」 paragraph 一個段落
          const segmenter=new Intl.Segmenter('en-US', { granularity: 'word' });
          
          const text='This is a sample text for demonstration purposes.';
          
          // 使用 Segmenter 對文本進行分割
          const segments=[...segmenter.segment(text)];
          console.log(segments);
          
          // 0: {segment: 'This', index: 0, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 1: {segment: ' ', index: 4, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 2: {segment: 'is', index: 5, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 3: {segment: ' ', index: 7, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 4: {segment: 'a', index: 8, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 5: {segment: ' ', index: 9, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 6: {segment: 'sample', index: 10, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 7: {segment: ' ', index: 16, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 8: {segment: 'text', index: 17, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 9: {segment: ' ', index: 21, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 10: {segment: 'for', index: 22, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 11: {segment: ' ', index: 25, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 12: {segment: 'demonstration', index: 26, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 13: {segment: ' ', index: 39, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          // 14: {segment: 'purposes', index: 40, input: 'This is a sample text for demonstration purposes.', isWordLike: true}
          // 15: {segment: '.', index: 48, input: 'This is a sample text for demonstration purposes.', isWordLike: false}
          

          Intl. Segmenter分段器可以把句子, 段落, 文章等按照配置切割為不同的segment數組, 結構類似正則, 有segment屬性

          再舉個例子, 中文語境下「真的」其實是一個詞

          // 創建分段器,指定語言環境和分段類型為'word'
          const segmenter=new Intl.Segmenter(['en', 'zh'], { granularity: 'word' });
          
          // 要分割的字符串
          const text='Hello世界Hello world';
          
          // 使用分段器分割字符串
          const segments=segmenter.segment(text);
          
          // 遍歷并打印每個分段的結果
          for (const segment of segments) {
              console.log(`Segment: ${segment.segment}, Index: ${segment.index}, IsWordLike: ${segment.isWordLike}`);
          }
          // Segment: Hello, Index: 0, IsWordLike: true
          // Segment: 世界, Index: 5, IsWordLike: true
          // Segment: Hello, Index: 7, IsWordLike: true
          // Segment:  , Index: 12, IsWordLike: false
          // Segment: world, Index: 13, IsWordLike: true
          
          const str="我真的很強, 強哥的強";
          const segmenterJa=new Intl.Segmenter("zh-CN", { granularity: "word" });
          
          const segments=segmenterJa.segment(str);
          console.log(Array.from(segments));
          
          // 0: {segment: '我', index: 0, input: '我真的很強, 強哥的強', isWordLike: true}
          // 1: {segment: '真的', index: 1, input: '我真的很強, 強哥的強', isWordLike: true}
          // 2: {segment: '很', index: 3, input: '我真的很強, 強哥的強', isWordLike: true}
          // 3: {segment: '強', index: 4, input: '我真的很強, 強哥的強', isWordLike: true}
          // 4: {segment: ',', index: 5, input: '我真的很強, 強哥的強', isWordLike: false}
          // 5: {segment: ' ', index: 6, input: '我真的很強, 強哥的強', isWordLike: false}
          // 6: {segment: '強', index: 7, input: '我真的很強, 強哥的強', isWordLike: true}
          // 7: {segment: '哥', index: 8, input: '我真的很強, 強哥的強', isWordLike: true}
          // 8: {segment: '的', index: 9, input: '我真的很強, 強哥的強', isWordLike: true}
          // 9: {segment: '強', index: 10, input: '我真的很強, 強哥的強', isWordLike: true}
          
          

          時間&時區

          國際化會有時區劃分問題, 時區產生于太陽下地球自轉導致的晝夜交替. 而全球不同國家地區當地時間與UTC時間是不一致的. 全球大部分人都可以說自己早上起床, 晚上睡覺. 上下文是通的. 但是這個早上的時間根據UTC來定義是不一樣的

          GMT和UTC

          • GTM=Greenwich Mean Time,GTM是英國格林威治時間,但是與太陽時偏差較大,已成為歷史不再作為標準
          • UTC=「協調世界時(UTC: Coordinated Universal Time)- 由原子鐘提供」

          時間的往事--記一次與夏令時的斗智斗勇:https://jiangyixiong.top/2021/05/25/%E6%97%B6%E9%97%B4%E7%9A%84%E5%BE%80%E4%BA%8B%E2%80%94%E2%80%94%E8%AE%B0%E4%B8%80%E6%AC%A1%E4%B8%8E%E5%A4%8F%E4%BB%A4%E6%97%B6%E7%9A%84%E6%96%97%E6%99%BA%E6%96%97%E5%8B%87

          GMT 標準時間 全球時區查詢:https://time.artjoey.com/cn

          通過NTP協議(https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E6%99%82%E9%96%93%E5%8D%94%E5%AE%9A), 讓計算機在全球網絡里保持時間一致

          「Offset與Timezone」

          • Offset即偏移量,比如中國在東八區,Offset是+08:00:00
          • 而東八區不止包括中國時間,而是一組東西經符合一個區域的集合,比如
          東八區={CST(中國標準時),SGT(新加坡時間),AWST(澳洲西部標準時)... }
          

          如何獲取當前用戶的時區信息

          // 所在地區的時區標識符, 如 America/New_York
          const timeZone=new Intl.DateTimeFormat().resolvedOptions().timeZone;
          console.log("用戶時區偏移:" + timeZone); // 用戶時區偏移:Asia/Shanghai
          
          // 獲取本地時間與UTC時間偏移值,最小單位是分鐘. 如"-480", 表示-8小時. 其中正負表示UTC前后, 如美國東部時間是UTC-5, 中國北京時間是UTC+8
          const date=new Date();
          const timeZoneOffset=date.getTimezoneOffset();
          console.log("時區偏移:" + timeZoneOffset); // 時區偏移:-480
          

          Intl是新的瀏覽器API, 與Math類似是全局靜態對象, 專門用于處理國際化和本地化業務. 其下的DateTimeFormat可以處理時間相關國際化問題

          DST

          DST (Daylight saving time),日光節約時,夏令時/冬令時等等名稱。「它會在每年春天的某一天將時鐘向后撥一小時,又在秋天的某一天將時鐘向前撥動一個小時。」非國際化業務很少遇到這個情況,主要因為「中國不實行夏令時/冬令時。」

          • 為什么要實行夏令時?一戰時德國率先實行,將每年夏天增加1h,冬天較少1h
          • 會產生什么現象?因為是行政約定,每年都可以自由選擇某天某時進入夏令時,各國自由發布。IANA會存儲(https://www.iana.org/time-zones)同步各國DST,計算機每小時同步時間后會在某一秒發生「突變」,比如1:59到2點的時候突變會1:00
          • 計算機如何表示時間?計算機都有一個unixTime,它表示當前時間距離世界標準時的1970年1月1日0點0分0秒的毫秒數,是一個絕對值,也就是UTC時間
          • 但是不同地區設備會根據本地有一個格式化,將UTC時間轉化為本地時間,比如中國在東八區
          2021-03-14 01:59:59 GMT-08:00(太平洋標準時間,PST)
          2021-03-14T01:59:59.000-08:00(ISO格式表示)
          2021-03-14T09:59:59.000Z(轉換為UTC時間并以ISO格式表示)
          
          // 下一秒時間突變
          2021-03-14 03:00:00 GMT-07:00(太平洋夏令時間,PDT)
          2021-03-14T03:00:00.000-07:00(ISO格式表示)
          2021-03-14T10:00:00.000Z(轉換為UTC時間并以ISO格式表示)
          
          // 原始時間字符串
          const timeString="2021-03-14T09:59:59.000Z";
          
          // 將時間字符串轉換為 Date 對象
          const date=new Date(timeString);
          const pstOutput=date.toLocaleString("en-US", { timeZone: "America/Los_Angeles", hour12: false });
          console.log(pstOutput); // 3/14/2021, 01:59:59
          
          // 獲取時間戳
          const timestamp=date.getTime();
          
          // 增加1秒
          const newTimestamp=timestamp + 1000;
          
          // 創建新的 Date 對象并格式化為 PDT 時間
          const newDate=new Date(newTimestamp);
          const pdtOutput=newDate.toLocaleString("en-US", { timeZone: "America/Los_Angeles", hour12: false });
          
          console.log(pdtOutput); // 3/14/2021, 03:00:00
          

          時間處理

          Dayjs插件

          dayjs: https://day.js.org/docs/zh-CN/i18n/i18n

          國際化支持 https://github.com/iamkun/dayjs/tree/dev/src/locale

          原理:通過拉取多語言文案輸出不同的formated日期時間字符串

          可以看這個demo

          Days of the week:https://codesandbox.io/s/dayjs-dynamic-locale0import-forked-wnk2zq?file=/src/index.js

          因我本地系統設置了每周第一天為星期日

          Intl API

          const date=new Date();
          const formattedDate=new Intl.DateTimeFormat('en-US').format(date);
          console.log(formattedDate); // 10/29/2023
          const formattedDate=new Intl.DateTimeFormat('zh-CN').format(date);
          console.log(formattedDate); // 2023/10/29
          

          本地時間輸出

          // 創建 DateTimeFormat 對象,并指定語言和地區
          const dateFormatterCN=new Intl.DateTimeFormat('zh-CN', {
            year: 'numeric',
            month: 'long', // 使用完整的月份名稱
            day: 'numeric',
          });
          console.log(dateFormatterCN.format(new Date('2024-04-28'))); // 2024年4月28日
          
          const dateFormatterUS=new Intl.DateTimeFormat('en-US', {
            year: 'numeric',
            month: 'long', // 使用完整的月份名稱
            day: 'numeric',
          });
          console.log(dateFormatterUS.format(new Date('2024-04-28'))); // April 28, 2024
          

          「Intl. RelativeTimeFormat」相對時間

          「Intl.RelativeTimeFormat」 是 JavaScript 中的國際化 API,用于格式化相對時間,例如“1 小時前”或“2 天后”。這個 API 可以根據不同的語言和地區設置,以自然語言的方式呈現相對時間,使應用程序能夠更好地適應多語言環境。

          const rtf1=new Intl.RelativeTimeFormat('zh', { style: 'short' });
          
          console.log(rtf1.format(3, 'quarter'));
          // Expected output: "3個季度后"
          
          console.log(rtf1.format(-1, 'day'));
          // Expected output: "1天前"
          
          const rtf2=new Intl.RelativeTimeFormat('jp', { numeric: 'auto' });
          
          console.log(rtf2.format(2, 'day'));
          // Expected output: "后天"
          

          我們知道中文語境是一萬以上可以縮寫為1萬, 或者是 1 0000. 也就是4位數字. 比如 1 2345 6789或者1’2345’6789(’是萬位分隔符)可以一眼看出來是一億兩千三百四十五萬六千七百八十九. 而如果是123, 456, 789可能很多人會愣很久重新數才知道是多少. 但是現在很多銀行APP都在推跟歐美一樣的屬于后者的千位分隔符. 可以看這篇討論覺得寫的在理

          設計產品時,你是如何掉入從眾的陷阱中的?– 人人都是產品經理:https://www.woshipm.com/pd/1500589.html)

          類似以上例子可以再看下面的舉例, 可以發現在德語和法語下, 千分位分隔符分別是.和 (空格)

          const number=1234567.89;
          const formattedNumber=new Intl.NumberFormat('zh-CN').format(number);
          console.log(formattedNumber); // 1,234,567.89
          
          const number=1234567.89;
          const formattedNumber=new Intl.NumberFormat('en-US').format(number);
          console.log(formattedNumber); // 1,234,567.89
          
          const number=1234567.89;
          const formattedNumber=new Intl.NumberFormat('de-DE').format(number);
          console.log(formattedNumber); // 1.234.567,89
          
          const number=1234567.89;
          const formattedNumber=new Intl.NumberFormat('fr-FR').format(number);
          console.log(formattedNumber); // 1 234 567,89
          

          單復數

          英文復數是要加s的, 比如apples

          const numbers=[1, 2, 5, 10, 100];
          for (const number of numbers) {
            const pluralRules=new Intl.PluralRules('en-US'); // 使用英語環境
            const pluralForm=pluralRules.select(number);
          
            console.log(`In English, ${number} item${pluralForm !=='one' ? 's' : ''}.`);
          }
          // In English, 1 item.
          // In English, 2 items.
          // In English, 5 items.
          // In English, 10 items.
          // In English, 100 items.
          

          再比如順序, 第一第二第三, 英文分別為 first, second, third, fourth, fifth. 聰明的你一定發現規律了. 除了123后面就是數字+th. 簡寫是1st 2nd. 根據下表可以發現規律

          1. 1 → st, 后面除了11外. 21-91都是21st, 91st這種
          2. 2→ nd, 后面除了12外. 22-92都是22nd, 92nd這種
          3. 3 → rd, 后面除了13外. 23-93都是23rd, 93rd這種
          4. 其他都是補th

          數字英文第N1One1st2Two2nd3Three3rd4Four4th10Ten10th11Eleven11th12Twelve12th13Thirteen13th20Twenty20th21Twenty-one21st30Thirty22nd31Thirty-one21st100One hundred100th

          const enOrdinalRules=new Intl.PluralRules("en-US", { type: "ordinal" });
          
          const suffixes=new Map([
            ["one", "st"],
            ["two", "nd"],
            ["few", "rd"],
            ["other", "th"],
          ]);
          const formatOrdinals=(n)=> {
            const rule=enOrdinalRules.select(n);
            const suffix=suffixes.get(rule);
            return `${n}${suffix}`;
          };
          
          formatOrdinals(0); // '0th'
          formatOrdinals(1); // '1st'
          formatOrdinals(2); // '2nd'
          formatOrdinals(3); // '3rd'
          formatOrdinals(4); // '4th'
          formatOrdinals(11); // '11th'
          formatOrdinals(21); // '21st'
          formatOrdinals(42); // '42nd'
          formatOrdinals(103); // '103rd'
          

          數字格式化

          整數分隔和小數分隔

          常見的整數分隔符號有千分位分隔, 比如 1000,000 也有萬位分隔比如 1000 0000 . 不同語言不一樣

          常見的小數分隔符號 . , 比如 1000.00 . 不同語言不一樣

          const number=1234567.89;
          // 格式化為默認數字格式
          const formattedNumber=new Intl.NumberFormat().format(number);
          console.log(formattedNumber); // 輸出: 1,234,567.89
          // 格式化為指定語言環境的數字格式
          const formattedNumberDE=new Intl.NumberFormat('de-DE').format(number);
          console.log(formattedNumberDE); // 輸出: 1.234.567,89
          // 格式化為指定語言環境的數字格式
          const formattedNumberFR=new Intl.NumberFormat('fr-FR').format(number);
          console.log(formattedNumberFR); // 輸出: 1 234 567,89
          const formattedNumberCN=new Intl.NumberFormat('zh-CN').format(number);
          console.log(formattedNumberCN)  // 輸出: 1,234,567.89
          

          也可以通過參數配置控制小數部分最多/最少有多少位

          const number=1234567.89123;
          const formattedNumber=new Intl.NumberFormat('en-US', {
            style: 'decimal', // 可選 'decimal' 表示常規數字格式
            maximumFractionDigits: 3, // 小數部分最多顯示三位
          }).format(number);
          console.log(formattedNumber); // 輸出: 1,234,567.891
          

          百分比

          正常百分比是0-100數字+%, 但是法語環境百分比符號習慣是 '% '而不是'%', 多了一個空格

          const percentage=0.75;
          // 使用默認語言環境
          const formattedPercentageDefault=new Intl.NumberFormat('fr-FR', {
            style: 'percent'
          }).format(percentage);
          console.log(formattedPercentageDefault); // 輸出: '75 %'
          // 使用指定語言環境
          const formattedPercentageFR=new Intl.NumberFormat('fr-FR', {
            style: 'percent',
            minimumFractionDigits: 2,
            maximumFractionDigits: 2,
          }).format(percentage);
          console.log(formattedPercentageFR); // 輸出: '75,00 %'
          // 使用默認語言環境
          const formattedPercentageUS=new Intl.NumberFormat('en-US', {
            style: 'percent'
          }).format(percentage);
          console.log(formattedPercentageUS); // 輸出: '75%'
          // 使用指定語言環境
          const formattedPercentageCN=new Intl.NumberFormat('zh-CN', {
            style: 'percent',
            minimumFractionDigits: 2,
            maximumFractionDigits: 2,
          }).format(percentage);
          console.log(formattedPercentageCN); // 輸出: '75.00%'
          

          縮寫

          console.log(new Intl.NumberFormat('en-US', { notation: "compact" , compactDisplay: "short", maximumFractionDigits: 2 }).format(987654321)) // 987.65M
          console.log(new Intl.NumberFormat('zh-CN', { notation: "compact" , compactDisplay: "short", maximumFractionDigits: 2 }).format(987654321)) // 9.88億
          

          貨幣

          貨幣符號

          比如人民幣是 , 美元是 $ , 歐元 , 英鎊

          new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).formatToParts().filter(i=> i.type==='currency')[0].value // '$'
          new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).formatToParts().filter(i=> i.type==='currency')[0].value // '¥'
          new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).formatToParts().filter(i=> i.type==='currency')[0].value // ''
          

          貨幣格式化

          用常見的幾個經濟體和身邊用的多的case舉例說明, 注意看輸出

          // 美元 $是美元符號
          const numberUSD=123456789.12;
          const formattedNumberUSD=new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(numberUSD);
          console.log(formattedNumberUSD); // $123,456,789.12
          
          // 人民幣 ¥是人民幣符號
          const numberCNY=123456789.12;
          const formattedNumberCNY=new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(numberCNY);
          console.log(formattedNumberCNY); // ¥123,456,789.12
          
          // 歐元 是歐元符號
          const numberEUR=123456789.12;
          const formattedNumberEUR=new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(numberEUR);
          console.log(formattedNumberEUR); // 123.456.789,12 
          
          // 日元
          const numberJPY=123456789.12;
          const formattedNumberJPY=new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(numberJPY);
          console.log(formattedNumberJPY); // ¥123,456,789
          
          // 英鎊 £是英鎊符號
          const numberGBP=123456789.12;
          const formattedNumberGBP=new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(numberGBP);
          console.log(formattedNumberGBP); // £123,456,789.12
          
          // 港幣
          const numberHKD=123456789.12;
          const formattedNumberHKD=new Intl.NumberFormat('zh-HK', { style: 'currency', currency: 'HKD' }).format(numberHKD);
          console.log(formattedNumberHKD); // HK$123,456,789.12
          
          // 韓元
          const numberKRW=123456789.12;
          const formattedNumberKRW=new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(numberKRW);
          console.log(formattedNumberKRW); // ?123,456,789.12
          

          貨幣的兼容性兜底可以用 Number.prototype.toLocaleString 實現, 也可以用formatjs提供的polyfill

          // 美元 $是美元符號
          const numberUSD=123456789.12;
          const formattedNumberUSD=new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(numberUSD);
          const formatttdNumberUSDByLocaleString=Number(numberUSD).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
          console.log(formattedNumberUSD); // $123,456,789.12
          console.log(numberUSD.toLocaleString()) // 123,456,789.12
          console.log(formatttdNumberUSDByLocaleString) // $123,456,789.12
          

          貨幣單位顯示

          比如美國是美元, 中國有人民幣. 可以直接格式化出來

          currencyNames=new Intl.DisplayNames(["zh-Hans"], { type: "currency" });
          console.log(currencyNames.of("USD")); // "美元"
          console.log(currencyNames.of("EUR")); // "歐元"
          console.log(currencyNames.of("TWD")); // "新臺幣"
          console.log(currencyNames.of("CNY")); // "人民幣"
          
          currencyNames=new Intl.DisplayNames(["zh-Hant"], { type: "currency" });
          console.log(currencyNames.of("USD")); // "美元"
          console.log(currencyNames.of("EUR")); // "歐元"
          console.log(currencyNames.of("TWD")); // "新臺幣"
          console.log(currencyNames.of("CNY")); // "人民幣"
          

          排序&列表

          Intl. Collator

          常見的電話本, 地址簿排序. 不同語言因為字母轉換后排序不一致. 「Intl.Collator」 是 JavaScript 的國際化 API 之一,用于字符串比較和排序,以便在多語言環境中執行正確的排序操作。它允許你創建一個 「Collator」 對象,用于根據特定語言和區域設置執行字符串比較和排序,考慮到不同語言的差異。

          console.log(['Z', 'a', 'z', '?'].sort(new Intl.Collator('de').compare));
          // Expected output: Array ["a", "?", "z", "Z"]
          console.log(['Z', 'a', 'z', '?'].sort(new Intl.Collator('sv').compare));
          // Expected output: Array ["a", "z", "Z", "?"]
          console.log(['Z', 'a', 'z', '?'].sort(new Intl.Collator('de', { caseFirst: 'upper' }).compare));
          // Expected output: Array ["a", "?", "Z", "z"]
          
          //創建一個Intl.Collator對象
          const collator=new Intl.Collator('en-US', { sensitivity: 'base', usage: 'sort' });
          
          // 可以看出以下輸出是按照拼音排序, guang jin mei ming tian yang
          console.log(['今','天','陽','光', '明', '媚'].sort(new Intl.Collator('zh').compare)); // ['光', '今', '媚', '明', '天', '陽']
          

          可以發現 options可以傳遞參數usage和sensitivity, 有如下取值

          1. 「排序方式 (usage)」: 「usage」 選項指定排序的目的
          2. 'sort':用于排序。
          3. 'search':用于搜索操作,通常不區分大小寫。
          4. 「敏感性 (sensitivity)」: 「sensitivity」 選項指定字符串比較的敏感性級別
          5. 'base':基本敏感性,不區分重音符號。
          6. 'accent':考慮重音符號,但不區分大小寫。
          7. 'case':區分大小寫,同時考慮重音符號。
          8. 'case':區分大小寫,但不考慮重音符號。
          9. 大小寫 (caseFirst)
          10. "upper":表示大寫字母(uppercase)在排序中優先考慮。這意味著排序會先考慮所有大寫字母,然后再考慮小寫字母。在 「caseFirst: "upper"」 情況下,大寫字母會排在小寫字母之前。
          11. "lower":表示小寫字母(lowercase)在排序中優先考慮。這意味著排序會先考慮所有小寫字母,然后再考慮大寫字母。在 「caseFirst: "lower"」 情況下,小寫字母會排在大寫字母之前。
          12. "false":表示不指定大寫字母和小寫字母的排序順序,它們會一起排序,不區分大小寫。
          13. ignorePunctuation: boolean, 表示是否忽略標點符號

          有如下的應用方式

          字符串比較

          const collator=new Intl.Collator('en-US', { sensitivity: 'base', usage: 'sort' }); //創建一個Intl.Collator對象
          const result=collator.compare('apple', 'Banana');
          console.log(result); // 根據配置輸出 -1(apple 在 Banana 前面)
          

          數組排序

          我們知道英文字母默認按照ASCII排序, 而如果需要AaBb這樣排序只能自己寫排序回調

          
          // 創建一個自定義Collator對象
          const customCollator=new Intl.Collator('en-US', {
            sensitivity: 'base',
            usage: 'sort',
            ignorePunctuation: true,
            caseFirst: 'false',
          });
          // 自定義比較函數, 忽略空格并不區分大小寫
          function customCompare(a, b) {
            // 移除字符串中的空格并轉為小寫后再比較
            const stringA=a.replace(/\\s/g, '').toLowerCase();
            const stringB=b.replace(/\\s/g, '').toLowerCase();
            if (stringA < stringB) {
              return -1;
            }
            if (stringA > stringB) {
              return 1;
            }
            return 0;
          }
          const data=['Apple', 'banana', 'cherry', 'apple pie', 'Banana Split', 'cherry tart'];
          const data2=data.slice()
          // 老方式: 使用sort回調排序
          console.log(data.sort(customCompare)); // 輸出排序結果:['Apple', 'apple pie', 'banana', 'Banana Split', 'cherry', 'cherry tart']
          // 新方式: 使用自定義Collator對象進行排序
          console.log(data2.sort(customCollator.compare)); // 輸出排序結果:['Apple', 'apple pie', 'banana', 'Banana Split', 'cherry', 'cherry tart']
          

          可以發現兩種方式結果一樣, 但是明顯Intl. Collator更加優雅, 是配置化的.

          Intl.「ListFormat」

          「Intl.ListFormat」 是 JavaScript 的國際化 API 之一,它用于格式化列表,以便在多語言環境中創建自然語言的列表表示。Intl.ListFormat 允許你指定列表項的連接方式(如逗號、"和" 等),以及列表項的樣式和語言設置。

          const listFormatter=new Intl.ListFormat('en-US', { style: 'long', type: 'disjunction' });
          const items=['apples', 'bananas', 'cherries'];
          const formattedList=listFormatter.format(items);
          console.log(formattedList); // 根據配置輸出例如:"apples, bananas, or cherries"
          
          const listFormatter=new Intl.ListFormat('en-US', { style: 'short', type: 'conjunction' });
          const items=['apples', 'bananas', 'cherries'];
          const formattedList=listFormatter.format(items);
          console.log(formattedList); // 根據配置輸出例如:"apples, bananas, & cherries"
          
          const listFormatter=new Intl.ListFormat('en-US', { style: 'narrow', type: 'conjunction' });
          const items=['apples', 'bananas', 'cherries'];
          const formattedList=listFormatter.format(items);
          console.log(formattedList); // 根據配置輸出例如:"apples, bananas, cherries"
          

          可以發現 options可以傳遞參數style和type, 有如下取值

          1. 「樣式 (style)」: 「style」 選項指定列表的樣式,有三個可能的值
          2. 'long':使用完整的語言表達,例如 "A, B, and C"。
          3. 'short':使用縮略形式,例如 "A, B, & C"。
          4. 'narrow':使用極簡的形式,例如 "A B C"。
          5. 「連接方式 (type)」: 「type」 選項指定連接列表項的方式,有兩個可能的值
          6. 'conjunction':使用 "和"(默認值),例如 "A、B和C"。
          7. 'disjunction':使用 "或",例如 "A、B或C"。

          日歷

          日歷是一種常見的東西, 在中國我們經常接觸到公歷和農歷,公歷全稱格里高利歷, 英文gregory。

          現在國家節日很多都是跟隨農歷的,比如春節,中秋節等。以前家家人手一本農歷, 上面會今日宜做什么, 現在很少見但是老人家還是信這個。

          而與此相同, 每個地方都有自己的歷法

          1. 「伊斯蘭歷(Hijri Calendar)」:也稱為伊斯蘭農歷,是伊斯蘭教的官方日歷,基于月亮的循環。伊斯蘭歷的年份比公歷年份短,每年有12個月,因此季節日期會變化。
          2. 「希伯來歷(Hebrew Calendar)」:希伯來歷是猶太教的官方日歷,基于太陽和月亮的周期。它有13個月,其中一些月份可以有不同的天數,以保持與農歷季節的一致性。
          3. 「農歷(Lunar Calendar)」:農歷基于月亮的循環,不同地區和文化有不同的農歷系統,如中國農歷、韓國農歷、越南農歷等。
          4. 「波斯歷(Persian Calendar)」:波斯歷,也稱波斯太陽歷,是伊朗和一些鄰近國家使用的太陽歷,與公歷有一些差異。
          5. 「印度歷法(Indian Calendar)」:印度有多種歷法,包括維基歷(Vikram Samvat)、國民歷法(Saka Samvat)、泰米爾歷法(Tamil Calendar)等。
          6. 「巴哈伊歷(Bahá'í Calendar)」:巴哈伊信仰使用的獨特日歷,包括19個月,每個月19天。
          7. 「民族歷法」:一些文化和民族擁有自己的獨特歷法,用于紀念特定歷史事件和節日。
          const date=new Date(); // 當前日期, Mon Oct 30 2023 20:00:50 GMT+0800 (中國標準時間)
          const formattedDate=new Intl.DateTimeFormat('ar-SA-u-ca-islamic', { year: 'numeric', month: 'long', day: 'numeric' }).format(date);
          console.log(formattedDate); // ?? ???? ????? ???? ??
          
          const date=new Date(1994, 1, 26);
          const formattedDate=new Intl.DateTimeFormat('zh-CN-u-ca-chinese', { year: 'numeric', month: 'long', day: 'numeric' }).format(date);
          console.log(formattedDate); // 1994甲戌年正月17
          
          // 不同語言下不同日歷的名稱
          const calendarNames=new Intl.DisplayNames('en-US', { type: 'calendar' });
          console.log(calendarNames.of('gregory')); // 輸出:Gregorian
          console.log(calendarNames.of('islamic')); // 輸出:Islamic
          
          const calendarNames=new Intl.DisplayNames('zh-CN', { type: 'calendar' });
          console.log(calendarNames.of('gregory')); // 輸出:公歷
          console.log(calendarNames.of('islamic')); // 輸出:伊斯蘭歷
          

          除了上述gregory格里高利歷, 取值還有下面這些

          1. "buddhist":佛教歷(Buddhist Calendar)
          2. "chinese":中國農歷(Chinese Lunar Calendar)
          3. "coptic":科普特歷(Coptic Calendar)
          4. "ethiopic":埃塞俄比亞歷(Ethiopic Calendar)
          5. "gregory":格里高利歷(Gregorian Calendar,即公歷)
          6. "hebrew":希伯來歷(Hebrew Calendar)
          7. "indian":印度歷法(Indian Calendar),包括維基歷(Vikram Samvat)等
          8. "islamic":伊斯蘭歷(Islamic Calendar)
          9. "persian":波斯歷(Persian Calendar)
          10. "islamic-civil":伊斯蘭歷的公民版本(Islamic Civil Calendar),通常用于文書、合同等民事事務

          星期

          不得不說日本的和歷, 真的是很神奇. 不是中文的周一到周日, 也不是Sunday-Saturday. 首先日本還有皇帝, 有皇帝就有年號. 常見下面的一些年份

          1. 「明治時代 (Meiji Era)」:
          2. 年號:明治(Meiji)
          3. 年份范圍:1868年 - 1912年
          4. 注釋:明治時代標志著日本的近代化和工業化的開始。
          5. 「大正時代 (Taisho Era)」:
          6. 年號:大正(Taisho)
          7. 年份范圍:1912年 - 1926年
          8. 注釋:大正時代是日本的一個相對短暫時期,也標志著日本的一些政治和社會變革。
          9. 「昭和時代 (Showa Era)」:
          10. 年號:昭和(Showa)
          11. 年份范圍:1926年 - 1989年
          12. 注釋:昭和時代見證了日本的戰爭和戰后重建,以及日本成為現代工業強國。
          13. 「平成時代 (Heisei Era)」:
          14. 年號:平成(Heisei)
          15. 年份范圍:1989年 - 2019年
          16. 注釋:平成時代包括了日本的經濟繁榮和一些社會變革。
          17. 「令和時代 (Reiwa Era)」: 令和系奧特曼(https://baike.baidu.com/item/%E4%BB%A4%E5%92%8C%E7%B3%BB%E5%A5%A5%E7%89%B9%E6%9B%BC/50340521)
          18. 年號:令和(Reiwa)
          19. 年份范圍:2019年 - 至今
          20. 注釋:令和時代是日本當前的年號,始于2019年5月1日,標志著新的時代的開始。

          可以看到日本的日歷起始時周日(日), 但是周一到周六分別對應月火水木金土. 與眾不同

          // 獲取今天過去7天的日期
          // 日期對象
          const today=new Date();
          
          // 創建一個選項對象,指定輸出的語言和風格
          const optionsCN={ weekday: 'long' };
          const optionsJP={ weekday: 'long' };
          
          // 獲取過去一周的日期
          console.log('\n過去一周的日期:');
          const CNArr=[]
          const JPArr=[]
          for (let i=0; i < 7; i++) {
            const pastDate=new Date(today);
            pastDate.setDate(today.getDate() - i);
            CNArr.unshift(new Intl.DateTimeFormat('zh-CN', optionsCN).format(pastDate))
           JPArr.unshift(new Intl.DateTimeFormat('ja-JP', optionsJP).format(pastDate))
          }
          console.log('CNArr', CNArr.join(' ')) // CNArr 星期二 星期三 星期四 星期五 星期六 星期日 星期一
          console.log('JPArr', JPArr.join(' ')) // JPArr 火曜日 水曜日 木曜日 金曜日 土曜日 日曜日 月曜日
          

          日歷單位

          const dateTimeFields=new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
          console.log(dateTimeFields.of('era')); // 輸出:Era, 紀元的意思
          console.log(dateTimeFields.of('year')); // 輸出:Year
          console.log(dateTimeFields.of('month')); // 輸出:Month
          console.log(dateTimeFields.of('day')); // 輸出:Day
          console.log(dateTimeFields.of('weekday')); // 輸出:Day of the week
          console.log(dateTimeFields.of('hour')); // 輸出:Hour
          console.log(dateTimeFields.of('minute')); // 輸出:Minute
          console.log(dateTimeFields.of('second')); // 輸出:Second
          console.log(dateTimeFields.of('quarter')); // 輸出:Quarter
          
          const dateTimeFields=new Intl.DisplayNames('ja-JP', { type: 'dateTimeField' });
          console.log(dateTimeFields.of('era')); // 輸出:時代
          console.log(dateTimeFields.of('year')); // 輸出:年
          console.log(dateTimeFields.of('month')); // 輸出:月
          console.log(dateTimeFields.of('day')); // 輸出:日
          console.log(dateTimeFields.of('weekday')); // 輸出:曜日
          console.log(dateTimeFields.of('hour')); // 輸出:時
          console.log(dateTimeFields.of('minute')); // 輸出:分
          console.log(dateTimeFields.of('second')); // 輸出:秒
          console.log(dateTimeFields.of('quarter')); // 輸出:四半期
          

          每周第一天

          en-US一般每周第一天是周日, 而zh-CN一般每周第一天是周一. 可以通過如下信息判斷

          Intl. Locale函數返回屬性里firstDay是一個數字,其中 0或7 表示星期日,1 表示星期一,依此類推。不同的地區和文化可能會將每周的第一天設置為不同的日期,因此這個屬性可以幫助你確定每周的起始日期,例如,星期天或星期一。

          (new Intl.Locale('zh-CN')).weekInfo // {"firstDay":1,"weekend":[6,7],"minimalDays":1}
          (new Intl.Locale('en-US')).weekInfo // {"firstDay":7,"weekend":[6,7],"minimalDays":1}
          
          const locale='zh-CN'
          console.log(new Intl.DateTimeFormat(locale, { weekday: "long" }).format(new Date(0, 0, new Intl.Locale(locale).weekInfo.firstDay))) // '星期一'
          
          const locale='en-US'
          console.log(new Intl.DateTimeFormat(locale, { weekday: "long" }).format(new Date(0, 0, new Intl.Locale(locale).weekInfo.firstDay))) // 'Sunday'
          

          能生效的原因就是 new Date(0,0,7) 等價于 new Date(1900,0,7) 對應1900.1.7(UTC+0), 此時對應us的sunday. 而 new Date(0,0,1) 對應1900.1.1(UTC+0), 對應cn的星期一

          如何寫一個國際化日歷組件

          1. zh-CN下是星期一開頭
          2. en-US是Sunday開頭
          3. 日語環境符合 火曜日 水曜日 木曜日 金曜日 土曜日 日曜日 月曜日等描述

          Intl-region-language-calendar:https://codesandbox.io/p/devbox/intl-region-language-calendar-xm7ls8?embed=1&file=%2Fsrc%2FApp.tsx

          實現細節

          1. 如何獲取一周的第一天
          const localeUS='en-US'
          new Intl.DateTimeFormat(localeUS, { weekday: "long" }).format(new Date(0, 0, new Intl.Locale(localeUS).weekInfo.firstDay)) // 'Sunday'
          
          const localeCN='zh-CN'
          new Intl.DateTimeFormat(localeCN, { weekday: "long" }).format(new Date(0, 0, new Intl.Locale(localeCN).weekInfo.firstDay)) // '星期一'
          
          1. 輸出一周7天
          const locale="en-US";
          const firstDay=new Intl.Locale(locale).weekInfo.firstDay;
          const formatInstace=new Intl.DateTimeFormat(locale, { weekday: "long" });
          for (let i=0; i < 7; i++) {
            console.log(formatInstace.format(new Date(0, 0, firstDay + i)));
          }
          // Sunday
          // Monday
          // Tuesday
          // Wednesday
          // Thursday
          // Friday
          // Saturday
          
          const localeCN="zh-CN";
          const firstDayCN=new Intl.Locale(localeCN).weekInfo.firstDay;
          const formatInstaceCN=new Intl.DateTimeFormat(localeCN, { weekday: "long" });
          for (let i=0; i < 7; i++) {
            console.log(formatInstaceCN.format(new Date(0, 0, firstDayCN + i)));
          }
          // 星期一
          // 星期二
          // 星期三
          // 星期四
          // 星期五
          // 星期六
          // 星期日
          

          Intl支持

          支持查詢

          可以通過API, Intl.supportedValuesOf 獲取到所有支持的

          const calendars=Intl.supportedValuesOf("calendar");
          console.log(calendars); // 輸出所有支持的日歷系統
          // (18) ['buddhist', 'chinese', 'coptic', 'dangi', 'ethioaa', 'ethiopic', 'gregory', 'hebrew', 'indian', 'islamic', 'islamic-civil', 'islamic-rgsa', 'islamic-tbla', 'islamic-umalqura', 'iso8601', 'japanese', 'persian', 'roc']
          
          const currencies=Intl.supportedValuesOf("currency");
          console.log(currencies); // 輸出所有支持的貨幣代碼
          // (159) ['AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BTN', 'BWP', 'BYN', 'BZD', 'CAD', 'CDF', 'CHF', 'CLP', 'CNY', 'COP', 'CRC', 'CUC', 'CUP', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EGP', 'ERN', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'INR', 'IQD', 'IRR', 'ISK', 'JMD', 'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LYD', 'MAD', 'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRU', 'MUR', 'MVR', 'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', …]
          
          const timeZones=Intl.supportedValuesOf("timeZone");
          console.log(timeZones); // 輸出所有支持的時區
          // (428) ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmera', 'Africa/Bamako', 'Africa/Bangui', 'Africa/Banjul', 'Africa/Bissau', 'Africa/Blantyre', 'Africa/Brazzaville', 'Africa/Bujumbura', 'Africa/Cairo', 'Africa/Casablanca', 'Africa/Ceuta', 'Africa/Conakry', 'Africa/Dakar', 'Africa/Dar_es_Salaam', 'Africa/Djibouti', 'Africa/Douala', 'Africa/El_Aaiun', 'Africa/Freetown', 'Africa/Gaborone', 'Africa/Harare', 'Africa/Johannesburg', 'Africa/Juba', 'Africa/Kampala', 'Africa/Khartoum', 'Africa/Kigali', 'Africa/Kinshasa', 'Africa/Lagos', 'Africa/Libreville', 'Africa/Lome', 'Africa/Luanda', 'Africa/Lubumbashi', 'Africa/Lusaka', 'Africa/Malabo', 'Africa/Maputo', 'Africa/Maseru', 'Africa/Mbabane', 'Africa/Mogadishu', 'Africa/Monrovia', 'Africa/Nairobi', 'Africa/Ndjamena', 'Africa/Niamey', 'Africa/Nouakchott', 'Africa/Ouagadougou', 'Africa/Porto-Novo', 'Africa/Sao_Tome', 'Africa/Tripoli', 'Africa/Tunis', 'Africa/Windhoek', 'America/Adak', 'America/Anchorage', 'America/Anguilla', 'America/Antigua', 'America/Araguaina', 'America/Argentina/La_Rioja', 'America/Argentina/Rio_Gallegos', 'America/Argentina/Salta', 'America/Argentina/San_Juan', 'America/Argentina/San_Luis', 'America/Argentina/Tucuman', 'America/Argentina/Ushuaia', 'America/Aruba', 'America/Asuncion', 'America/Bahia', 'America/Bahia_Banderas', 'America/Barbados', 'America/Belem', 'America/Belize', 'America/Blanc-Sablon', 'America/Boa_Vista', 'America/Bogota', 'America/Boise', 'America/Buenos_Aires', 'America/Cambridge_Bay', 'America/Campo_Grande', 'America/Cancun', 'America/Caracas', 'America/Catamarca', 'America/Cayenne', 'America/Cayman', 'America/Chicago', 'America/Chihuahua', 'America/Ciudad_Juarez', 'America/Coral_Harbour', 'America/Cordoba', 'America/Costa_Rica', 'America/Creston', 'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', 'America/Dawson', 'America/Dawson_Creek', 'America/Denver', 'America/Detroit', 'America/Dominica', 'America/Edmonton', 'America/Eirunepe', …]
          

          低版本兼容

          Intl是瀏覽器對i18n提供的底層API, 用于處理國際化相關內容. 附帶browerstack 云真機測試工具(caniuse推薦): https://live.browserstack.com/dashboard如果沒處理好兼容性問題直接使用API, 會報JS Error. 內容為Intl.DisplayNames is not a constructor

          對應的, 一些操作系統低版本的用戶(長期不升級系統)會遇到JS Error導致白屏

          Google pixel4(2019, October 15發行)

          formatjs

          可以用formatjs提供的polyfill做低版本兼容: https://formatjs.io/docs/getting-started/installation

          1. 支持關系有先后依賴, 需要按照依賴順序依次倒入對應的包
          2. 移動端場景如小程序, 如果全量導入移動端場景會讓包體積爆炸(普通壓縮包Gzip后2.6M, 全量導入后會到9.1M). 最后polyfill按照順序依次導入, 且移動端場景只兜底英文部分, 限制包體積在3M內
          async function loadPolyfill() {
              // 如果當前環境不支持 Intl 或者 Intl.DisplayNames
              if (!window.Intl || !window.Intl.DisplayNames) {
                  window.Intl=window.Intl || {}
                  // 加載 polyfill
                  await import('@formatjs/intl-getcanonicallocales/polyfill-force')
                  await import('@formatjs/intl-locale/polyfill-force')
                  await import('@formatjs/intl-displaynames/polyfill-force')
                  await import('@formatjs/intl-displaynames/locale-data/en')
                  return false
              } else {
                  // 當前環境支持 Intl.DisplayNames API,不需要 Polyfill
                  return true
              }
          }
          

          Why not Babel?

          眾所周知Babel有一個babel-preset-env(https://www.babeljs.cn/docs/babel-preset-env#how-does-it-work), 用于在編譯代碼時智能(基于core-js-compat(https://www.npmjs.com/package/core-js-compat))引入helper和polyfill 智能的含義: 可以設置最低兼容的瀏覽器(https://github.com/browserslist/browserslist#queries)和代碼, 動態引用所需的helper和polyfill

          // babel.config.js
          module.exports={
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage', // 根據每個文件里面,用到了哪些es的新特性和targets導入polyfill,更加精簡
                  corejs: 3, // 指定 core-js 版本
                  targets: "> 0.25%, not dead" // 指定目標瀏覽器, 選取全球使用率超過 0.25% 的瀏覽器版本
                },
              ],
            ],
          };
          

          「babel底層使用core-js(https://github.com/zloirock/core-js)進行polyfill, 但是core-js不包含Intl API部分的polyfill(https://github.com/zloirock/core-js?tab=readme-ov-file#missing-polyfills), 所以babel并不能為Intl API做polyfill」

          Nodejs使用

          1. 安裝
          npm i @formatjs/intl
          
          1. 使用
          import {createIntl, createIntlCache} from '@formatjs/intl'
          
          // This is optional but highly recommended
          // since it prevents memory leak
          const cache=createIntlCache()
          
          const intlFr=createIntl(
            {
              locale: 'fr-FR',
              messages: {},
            },
            cache
          )
          const intlEn=createIntl(
            {
              locale: 'en-US',
              message: {},
              cache
            }
          )
          
          // Call imperatively
          console.log(intlFr.formatNumber(2000000000000)) // 2 000 000 000 000
          console.log(intlEn.formatNumber(2000000000000)) // 2,000,000,000,000

          作者:ManfredHu

          來源-微信公眾號:字節前端 ByteFE

          出處:https://mp.weixin.qq.com/s/PByp6Pmc3vp7b0acyPT8yA

          .大家應該都知道wordpecss網址的文章如圖1分享到QQ空間,是可以顯示并自動獲取文章的縮略圖片,這樣顯得有檔次的感覺。

          圖1文章分享到QQ空間可以顯示縮略圖片

          1.2 那如果文章分享到微信朋友圈呢?博主試過在電腦網頁或者直接微信分享文章,到朋友圈都只能看到空白的縮略圖片,如圖2這樣總感覺不好看

          網上也看到很多教程,需要開通微信公眾號等等方面,博主覺得沒必要,畢竟公眾號每年也要教幾百塊錢的哈,還不如用來續費服務器噠,后來無意中發現一個很簡單的辦法,可以分享文章到微信朋友圈可以抓取文章圖片的方法,反正我目前測試是可以的,分享經驗的目地只是大家交流學習哦。如有錯誤之處請大家提出,謝謝(如圖3打開手機游覽器,進入自己的網站然后找到箭頭所示分享到朋友圈,圖4的可以正常顯示鏈接文章的圖片了。)


          主站蜘蛛池模板: 国产一区二区三区免费观在线| 精品无码人妻一区二区三区| 无码乱人伦一区二区亚洲| 人妻少妇久久中文字幕一区二区| 鲁丝丝国产一区二区| 久久影院亚洲一区| 国产成人精品一区二区A片带套| 清纯唯美经典一区二区| 日韩三级一区二区| 精品人妻一区二区三区浪潮在线| 一区免费在线观看| 国产成人午夜精品一区二区三区| 亚洲日本精品一区二区| 精品视频一区二区三区四区五区| 怡红院美国分院一区二区| 91一区二区三区四区五区| 中文字幕在线播放一区| 国产一区中文字幕| 国产福利日本一区二区三区| 久久精品一区二区三区AV| 无码精品人妻一区二区三区中| 成人国产一区二区三区| 香蕉久久AⅤ一区二区三区| 韩国一区二区三区| 蜜芽亚洲av无码一区二区三区| 日本伊人精品一区二区三区| 日本一区二区三区在线观看| A国产一区二区免费入口| 亚洲AV无码一区二区三区国产 | 午夜天堂一区人妻| 中文字幕aⅴ人妻一区二区| chinese国产一区二区| 极品少妇伦理一区二区| 日本一区高清视频| 国产一区二区三区免费看| 国产一区二区三区免费在线观看| 福利片免费一区二区三区| 国产亚洲福利一区二区免费看 | 欲色aV无码一区二区人妻 | 精品国产亚洲一区二区三区在线观看| 国产日韩精品视频一区二区三区|