言代碼實(shí)戰(zhàn)指南:構(gòu)建精準(zhǔn)國(guó)際化應(yīng)用)
1. 項(xiàng)目緣起為什么我們需要一份靠譜的語(yǔ)言縮寫(xiě)列表在數(shù)字化的世界里處理多語(yǔ)言內(nèi)容幾乎成了開(kāi)發(fā)、運(yùn)營(yíng)、產(chǎn)品經(jīng)理乃至內(nèi)容創(chuàng)作者的日常。無(wú)論是為網(wǎng)站配置國(guó)際化i18n語(yǔ)言包還是在數(shù)據(jù)庫(kù)中存儲(chǔ)用戶的語(yǔ)言偏好又或者是在API接口中定義請(qǐng)求頭里的Accept-Language我們總繞不開(kāi)一個(gè)看似簡(jiǎn)單卻極易出錯(cuò)的東西語(yǔ)言縮寫(xiě)。你可能遇到過(guò)這樣的場(chǎng)景產(chǎn)品經(jīng)理說(shuō)“我們要支持法語(yǔ)”你興沖沖地在代碼里寫(xiě)上了fr。上線后法國(guó)用戶反饋界面顯示異常。一查才發(fā)現(xiàn)法國(guó)本土除了法語(yǔ)還有布列塔尼語(yǔ)、阿爾薩斯語(yǔ)等而瑞士法語(yǔ)區(qū)的用戶期望的可能是fr-CH。又或者你從某個(gè)開(kāi)源庫(kù)的文檔里復(fù)制了一段語(yǔ)言代碼列表結(jié)果發(fā)現(xiàn)它把簡(jiǎn)體中文標(biāo)成了zh-CN把繁體中文標(biāo)成了zh-TW但在某些國(guó)際標(biāo)準(zhǔn)里它們更精確的表示是zh-Hans和zh-Hant。這些細(xì)微的差別輕則導(dǎo)致界面顯示錯(cuò)誤重則可能引發(fā)本地化內(nèi)容推送失敗影響用戶體驗(yàn)。因此一份準(zhǔn)確、全面、且附帶使用場(chǎng)景說(shuō)明的語(yǔ)言縮寫(xiě)列表絕不是簡(jiǎn)單的信息羅列而是一份能幫你避開(kāi)無(wú)數(shù)坑的“地圖”。它需要告訴你在什么情況下該用哪種標(biāo)準(zhǔn)不同標(biāo)準(zhǔn)之間有何差異以及在實(shí)際應(yīng)用中如何選擇。本文的目的就是為你繪制這樣一張地圖并結(jié)合我在多個(gè)跨國(guó)項(xiàng)目中的實(shí)戰(zhàn)經(jīng)驗(yàn)告訴你如何正確地使用它們。2. 語(yǔ)言縮寫(xiě)的核心標(biāo)準(zhǔn)ISO 639 與 IETF BCP 47在深入列表之前我們必須先理解支撐這些縮寫(xiě)的兩大核心標(biāo)準(zhǔn)體系。不同的標(biāo)準(zhǔn)適用于不同的場(chǎng)景用錯(cuò)了標(biāo)準(zhǔn)就像拿著地圖卻看錯(cuò)了圖例。2.1 ISO 639語(yǔ)言標(biāo)識(shí)的基石ISO 639是國(guó)際標(biāo)準(zhǔn)化組織制定的一套用于表示語(yǔ)言名稱的代碼標(biāo)準(zhǔn)。它有幾個(gè)部分最常用的是ISO 639-1 (兩位字母代碼)這是最廣為人知、最常用的形式。它涵蓋世界上主要語(yǔ)言代碼簡(jiǎn)潔。例如en- 英語(yǔ)zh- 中文es- 西班牙語(yǔ)fr- 法語(yǔ)de- 德語(yǔ)ja- 日語(yǔ)ko- 韓語(yǔ)ru- 俄語(yǔ)使用場(chǎng)景適用于對(duì)語(yǔ)言區(qū)分粒度要求不高的場(chǎng)景如簡(jiǎn)單的網(wǎng)站語(yǔ)言切換、內(nèi)容的大類分類。它的優(yōu)點(diǎn)是極其簡(jiǎn)短易于記憶和傳播。ISO 639-2 (三位字母代碼)當(dāng)639-1的兩位代碼不夠用時(shí)例如某些沒(méi)有廣泛書(shū)面形式的語(yǔ)言或圖書(shū)館、文獻(xiàn)領(lǐng)域需要更精細(xì)的分類時(shí)會(huì)使用三位代碼。它又分為“術(shù)語(yǔ)型”(T)和“文獻(xiàn)型”(B)兩種通常我們使用術(shù)語(yǔ)型。例如eng- 英語(yǔ) (術(shù)語(yǔ)型)chi/zho- 中文 (這里就體現(xiàn)了B/T的區(qū)別chi是文獻(xiàn)型zho是術(shù)語(yǔ)型在實(shí)際技術(shù)應(yīng)用中強(qiáng)烈建議使用zho以避免歧義)fre/fra- 法語(yǔ) (fra為術(shù)語(yǔ)型)使用場(chǎng)景主要用于圖書(shū)編目、學(xué)術(shù)研究等專業(yè)領(lǐng)域。在現(xiàn)代軟件開(kāi)發(fā)中除非對(duì)接特定傳統(tǒng)系統(tǒng)否則較少直接使用。ISO 639-3 (三位字母代碼)旨在覆蓋全球所有已知的語(yǔ)言包括方言、古語(yǔ)等數(shù)量超過(guò)7000種。它是639-2的超集。例如cmn- 普通話 (官話)yue- 粵語(yǔ)wuu- 吳語(yǔ)使用場(chǎng)景語(yǔ)言學(xué)研究、非常精細(xì)的語(yǔ)言識(shí)別和分類。對(duì)于大多數(shù)應(yīng)用產(chǎn)品來(lái)說(shuō)粒度過(guò)于細(xì)致。實(shí)戰(zhàn)經(jīng)驗(yàn)一首選 ISO 639-1對(duì)于絕大多數(shù)商業(yè)軟件和互聯(lián)網(wǎng)產(chǎn)品ISO 639-1的兩位代碼是首選。它足夠通用被幾乎所有操作系統(tǒng)、瀏覽器、開(kāi)發(fā)庫(kù)所支持。在數(shù)據(jù)庫(kù)設(shè)計(jì)時(shí)用一個(gè)CHAR(2)字段來(lái)存儲(chǔ)用戶語(yǔ)言偏好既節(jié)省空間又高效。記住一個(gè)原則如無(wú)特殊必要不要引入更復(fù)雜的代碼體系。2.2 IETF BCP 47現(xiàn)代應(yīng)用的事實(shí)標(biāo)準(zhǔn)如果說(shuō)ISO 639定義了“語(yǔ)言”本身那么IETF的BCP 47通常表現(xiàn)為RFC 5646則定義了如何完整地描述一個(gè)“語(yǔ)言區(qū)域”。它更像一個(gè)構(gòu)造規(guī)則通過(guò)子標(biāo)簽Subtags的組合來(lái)精確表達(dá)。一個(gè)BCP 47語(yǔ)言標(biāo)簽的格式通常為語(yǔ)言-腳本-地區(qū)-變體-擴(kuò)展-私有。最常用的是“語(yǔ)言-地區(qū)”對(duì)。語(yǔ)言子標(biāo)簽通常使用ISO 639-1的兩位代碼首選或639-2/3的三位代碼。腳本子標(biāo)簽可選使用ISO 15924四位字母代碼用于區(qū)分書(shū)寫(xiě)系統(tǒng)。這是解決“簡(jiǎn)體中文”與“繁體中文”問(wèn)題的關(guān)鍵。Hans- 簡(jiǎn)體中文Hant- 繁體中文Cyrl- 西里爾字母Latn- 拉丁字母地區(qū)子標(biāo)簽可選使用ISO 3166-1的兩位字母國(guó)家代碼或UN M.49的三位數(shù)字地區(qū)代碼。CN- 中國(guó)TW- 中國(guó)臺(tái)灣地區(qū)US- 美國(guó)GB- 英國(guó)001- 全球數(shù)字代碼組合示例與解析zh-CN中文中國(guó)大陸地區(qū)。這是一個(gè)常見(jiàn)的“語(yǔ)言-地區(qū)”組合。但它隱含了“使用簡(jiǎn)體中文”的意思因?yàn)橹袊?guó)大陸主要使用簡(jiǎn)體。然而從標(biāo)準(zhǔn)角度看它并未明確指定腳本。zh-Hans中文簡(jiǎn)體腳本。這是表示“簡(jiǎn)體中文”更精確的方式它不綁定特定區(qū)域適用于所有使用簡(jiǎn)體中文的地區(qū)如中國(guó)大陸、新加坡、馬來(lái)西亞華人社區(qū)。zh-Hans-CN中文簡(jiǎn)體腳本中國(guó)大陸地區(qū)。這是最完整、最無(wú)歧義的表示明確了語(yǔ)言、書(shū)寫(xiě)形式和地理區(qū)域。zh-Hant中文繁體腳本。表示“繁體中文”不綁定區(qū)域。zh-Hant-TW中文繁體腳本中國(guó)臺(tái)灣地區(qū)。常用于臺(tái)灣地區(qū)使用的繁體中文。zh-Hant-HK中文繁體腳本中國(guó)香港地區(qū)。香港地區(qū)使用的繁體中文在詞匯、用語(yǔ)上與臺(tái)灣地區(qū)略有不同。en-US英語(yǔ)美國(guó)地區(qū)。美式英語(yǔ)。en-GB英語(yǔ)英國(guó)地區(qū)。英式英語(yǔ)。es-ES西班牙語(yǔ)西班牙地區(qū)。歐洲西班牙語(yǔ)。es-MX西班牙語(yǔ)墨西哥地區(qū)。拉丁美洲西班牙語(yǔ)的一種。實(shí)戰(zhàn)經(jīng)驗(yàn)二理解“語(yǔ)言-地區(qū)”與“語(yǔ)言-腳本”的區(qū)別這是最容易混淆的地方。zh-CN和zh-Hans看似都指向簡(jiǎn)體中文但側(cè)重點(diǎn)不同。zh-CN強(qiáng)調(diào)“在中國(guó)大陸使用的語(yǔ)言”其默認(rèn)腳本是簡(jiǎn)體但理論上也可能包含其他腳本雖然極少。zh-Hans則純粹強(qiáng)調(diào)“使用簡(jiǎn)體漢字書(shū)寫(xiě)的中文”與地域無(wú)關(guān)。在涉及內(nèi)容本地化Localization時(shí)優(yōu)先使用“語(yǔ)言-地區(qū)”如zh-CN,en-US因?yàn)樗说赜蛭幕?xí)慣在涉及純粹的文字呈現(xiàn)或字體選擇時(shí)使用“語(yǔ)言-腳本”如zh-Hans,zh-Hant更準(zhǔn)確。對(duì)于中文一個(gè)穩(wěn)妥的實(shí)踐是在系統(tǒng)內(nèi)部使用zh-Hans和zh-Hant而在面向用戶的界面上顯示“簡(jiǎn)體中文中國(guó)”、“繁體中文臺(tái)灣”等友好名稱。3. 核心語(yǔ)言縮寫(xiě)列表與使用指南下面我將結(jié)合ISO 639-1和BCP 47整理一份在軟件開(kāi)發(fā)、內(nèi)容管理中最常遇到的語(yǔ)言標(biāo)簽列表并附上關(guān)鍵說(shuō)明。3.1 全球主要語(yǔ)言基于ISO 639-1ISO 639-1 代碼英語(yǔ)名稱本地名稱示例典型BCP 47擴(kuò)展語(yǔ)言-地區(qū)主要使用地區(qū)/說(shuō)明arArabic???????ar-SA(沙特),ar-EG(埃及)阿拉伯語(yǔ)注意是從右向左書(shū)寫(xiě)(RTL)。deGermanDeutschde-DE(德國(guó)),de-AT(奧地利),de-CH(瑞士)德語(yǔ)。瑞士德語(yǔ)(de-CH)在日期、數(shù)字格式上有所不同。enEnglishEnglishen-US(美國(guó)),en-GB(英國(guó)),en-AU(澳大利亞)必須區(qū)分地區(qū)拼寫(xiě)、日期、貨幣格式差異大。esSpanishEspa?oles-ES(西班牙),es-MX(墨西哥),es-AR(阿根廷)西班牙語(yǔ)變體極多詞匯、發(fā)音、甚至語(yǔ)法有差異。frFrenchFran?aisfr-FR(法國(guó)),fr-CA(加拿大),fr-BE(比利時(shí))加拿大法語(yǔ)(fr-CA)與法國(guó)法語(yǔ)在詞匯和用語(yǔ)上區(qū)別明顯。itItalianItalianoit-IT意大利語(yǔ)。jaJapanese日本語(yǔ)ja-JP日語(yǔ)。通常不需要進(jìn)一步細(xì)分。koKorean???ko-KR韓語(yǔ)。ptPortuguesePortuguêspt-PT(葡萄牙),pt-BR(巴西)歐洲葡萄牙語(yǔ)和巴西葡萄牙語(yǔ)差異巨大務(wù)必區(qū)分。ruRussianРусскийru-RU俄語(yǔ)。使用西里爾字母。zhChinese中文見(jiàn)下方詳細(xì)分解中文情況最復(fù)雜必須處理簡(jiǎn)繁體。3.2 中文的詳細(xì)分解重點(diǎn)與難點(diǎn)中文處理是國(guó)際化中的重中之重也是最易出錯(cuò)的部分。下面這個(gè)表格清晰地展示了各種組合BCP 47 標(biāo)簽說(shuō)明常見(jiàn)對(duì)應(yīng)友好名稱使用場(chǎng)景建議zh中文泛指中文避免單獨(dú)使用歧義太大。zh-Hans中文簡(jiǎn)體腳本簡(jiǎn)體中文推薦。用于表示所有簡(jiǎn)體中文內(nèi)容不綁定地區(qū)。zh-Hant中文繁體腳本繁體中文推薦。用于表示所有繁體中文內(nèi)容不綁定地區(qū)。zh-CN中文中國(guó)大陸地區(qū)簡(jiǎn)體中文中國(guó)隱含簡(jiǎn)體。適用于針對(duì)中國(guó)大陸市場(chǎng)的完整本地化。zh-SG中文新加坡地區(qū)簡(jiǎn)體中文新加坡新加坡簡(jiǎn)體用詞習(xí)慣與大陸略有不同。zh-TW中文中國(guó)臺(tái)灣地區(qū)繁體中文臺(tái)灣臺(tái)灣繁體。注意政治敏感性在列表中宜與zh-Hant-TW等同。zh-HK中文中國(guó)香港地區(qū)繁體中文香港香港繁體。zh-MO中文中國(guó)澳門(mén)地區(qū)繁體中文澳門(mén)澳門(mén)繁體。zh-Hans-CN中文簡(jiǎn)體腳本中國(guó)大陸簡(jiǎn)體中文中國(guó)大陸最精確、最無(wú)歧義的表示法。zh-Hant-TW中文繁體腳本臺(tái)灣地區(qū)繁體中文臺(tái)灣最精確、最無(wú)歧義的表示法。zh-Hant-HK中文繁體腳本香港地區(qū)繁體中文香港最精確、最無(wú)歧義的表示法。實(shí)戰(zhàn)經(jīng)驗(yàn)三中文標(biāo)簽的存儲(chǔ)與顯示策略內(nèi)部存儲(chǔ)在數(shù)據(jù)庫(kù)、配置文件、代碼常量中強(qiáng)烈建議使用zh-Hans和zh-Hant。這分離了“書(shū)寫(xiě)系統(tǒng)”和“地域文化”邏輯更清晰。例如你可以用zh-Hans來(lái)標(biāo)記一份文檔的書(shū)寫(xiě)形式而用CN、SG來(lái)標(biāo)記其目標(biāo)市場(chǎng)。用戶界面顯示給用戶選擇時(shí)不要顯示zh-Hans這樣的代碼。應(yīng)該顯示本地化的語(yǔ)言名稱如“簡(jiǎn)體中文”、“繁體中文”。如果需要區(qū)分地區(qū)可以顯示“簡(jiǎn)體中文中國(guó)大陸”、“繁體中文香港”。HTTPAccept-Language瀏覽器發(fā)送的通常是zh-CN,zh;q0.9,en;q0.8。你的后端服務(wù)應(yīng)該能正確解析并將zh-CN映射到你的zh-Hans資源或者更精確的zh-Hans-CN資源如果你有。字體回退在CSS中你可以針對(duì)不同腳本指定字體font-family: “PingFang SC”, “Microsoft YaHei”, sans-serif;對(duì)應(yīng)zh-Hans而font-family: “PingFang TC”, “Microsoft JhengHei”, sans-serif;對(duì)應(yīng)zh-Hant。3.3 其他需要特別注意的語(yǔ)言塞爾維亞語(yǔ)它同時(shí)使用西里爾字母和拉丁字母書(shū)寫(xiě)??梢允褂胹r-Cyrl塞爾維亞語(yǔ)西里爾字母和sr-Latn塞爾維亞語(yǔ)拉丁字母來(lái)精確區(qū)分。維吾爾語(yǔ)在中國(guó)新疆地區(qū)使用阿拉伯字母書(shū)寫(xiě)。標(biāo)準(zhǔn)代碼是ugISO 639-1完整標(biāo)簽可以是ug-Arab-CN?;浾Z(yǔ)作為中文的一種主要方言在ISO 639-3中有獨(dú)立代碼yue。在YouTube等平臺(tái)你可以看到y(tǒng)ue作為一個(gè)可選語(yǔ)言。但在大多數(shù)產(chǎn)品中它通常被歸入zh-Hant或zh-Hans下的變體處理。庫(kù)爾德語(yǔ)有ku庫(kù)爾德語(yǔ)泛稱、kmr北庫(kù)爾德語(yǔ)常用、ckb中庫(kù)爾德語(yǔ)等代碼書(shū)寫(xiě)系統(tǒng)也有拉丁和阿拉伯之分。4. 在實(shí)戰(zhàn)中應(yīng)用從配置到代碼理解了標(biāo)準(zhǔn)和列表關(guān)鍵在于如何用起來(lái)。下面我以幾個(gè)典型場(chǎng)景為例說(shuō)明如何實(shí)際操作。4.1 場(chǎng)景一網(wǎng)站/應(yīng)用國(guó)際化i18n配置假設(shè)你使用流行的i18n庫(kù)如 i18next, react-i18n, vue-i18n。1. 資源文件命名與組織一種清晰的結(jié)構(gòu)是按BCP 47標(biāo)簽建立文件夾或文件名。locales/ ├── en-US/ │ ├── common.json │ └── product.json ├── zh-Hans/ │ ├── common.json │ └── product.json ├── zh-Hant/ │ ├── common.json │ └── product.json └── ja-JP/ ├── common.json └── product.json為什么這樣組織這分離了語(yǔ)言和地區(qū)。zh-Hans下的資源可以被zh-CN和zh-SG共享基礎(chǔ)翻譯然后通過(guò)地區(qū)特定的擴(kuò)展或覆寫(xiě)來(lái)調(diào)整用詞差異例如“軟件”在大陸叫“軟件”在新加坡可能更常用“軟體”雖然都是簡(jiǎn)體。2. 初始化i18n庫(kù)// 以 i18next 為例 import i18n from i18next; import { initReactI18next } from react-i18next; import Backend from i18next-http-backend; // 從后端加載資源 import LanguageDetector from i18next-browser-languagedetector; // 檢測(cè)瀏覽器語(yǔ)言 i18n .use(Backend) .use(LanguageDetector) .use(initReactI18next) .init({ fallbackLng: en, // 回退語(yǔ)言 supportedLngs: [en, zh-Hans, zh-Hant, ja], // 支持的語(yǔ)言列表 nonExplicitSupportedLngs: true, // 重要允許檢測(cè)到zh-CN時(shí)回退到zh-Hans backend: { loadPath: /locales/{{lng}}/{{ns}}.json, // 資源路徑 }, detection: { order: [querystring, cookie, localStorage, navigator, htmlTag], caches: [cookie], }, interpolation: { escapeValue: false, }, });關(guān)鍵配置解析nonExplicitSupportedLngs: true這個(gè)選項(xiàng)至關(guān)重要。當(dāng)瀏覽器語(yǔ)言是zh-CN時(shí)檢測(cè)器會(huì)依次嘗試加載zh-CN-zh-en的資源。由于我們支持zh-Hansi18next在zh-CN失敗后會(huì)聰明地嘗試去掉地區(qū)碼然后匹配到zh但我們的支持列表里只有zh-Hans和zh-Hant。此時(shí)nonExplicitSupportedLngs會(huì)嘗試將zh-CN轉(zhuǎn)換為zh然后尋找最匹配的支持語(yǔ)言。通常你需要配置loadPath邏輯或使用后端映射將zh-CN的請(qǐng)求指向zh-Hans資源。更穩(wěn)健的后端映射示例Node.js/Expressapp.get(/locales/:lng/:ns.json, (req, res) { let { lng, ns } req.params; // 語(yǔ)言標(biāo)簽映射 const languageMap { zh-CN: zh-Hans, zh-SG: zh-Hans, zh-TW: zh-Hant, zh-HK: zh-Hant, zh-MO: zh-Hant, en-US: en, en-GB: en, // ... 其他映射 }; const mappedLng languageMap[lng] || lng; // 檢查 mappedLng 是否在支持列表中 if (supportedLngs.includes(mappedLng)) { const filePath path.join(__dirname, locales, mappedLng, ${ns}.json); res.sendFile(filePath); } else { res.status(404).send(Language not found); } });4.2 場(chǎng)景二數(shù)據(jù)庫(kù)存儲(chǔ)用戶語(yǔ)言偏好在設(shè)計(jì)用戶表時(shí)如何存儲(chǔ)language字段方案A簡(jiǎn)單推薦使用VARCHAR(5)存儲(chǔ)BCP 47語(yǔ)言標(biāo)簽。CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), language VARCHAR(5) DEFAULT en -- 存儲(chǔ)如 zh-Hans, en-US, ja );優(yōu)點(diǎn)直接、靈活可以存儲(chǔ)最精確的標(biāo)簽。前端提交什么就存什么。缺點(diǎn)查詢和統(tǒng)計(jì)時(shí)需要處理例如統(tǒng)計(jì)所有中文用戶需要查詢LIKE zh%或更復(fù)雜的解析。方案B規(guī)范化分離語(yǔ)言和地區(qū)。CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), language_code CHAR(2), -- ISO 639-1, 如 zh, en region_code CHAR(2), -- ISO 3166-1, 如 CN, US, NULLable script_code VARCHAR(4) -- ISO 15924, 如 Hans, Hant, NULLable );優(yōu)點(diǎn)結(jié)構(gòu)清晰便于基于語(yǔ)言、地區(qū)、腳本進(jìn)行多維度的分析和查詢。缺點(diǎn)復(fù)雜度高寫(xiě)入和讀取時(shí)需要組合/解析。對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)過(guò)度設(shè)計(jì)。我的建議對(duì)于90%的應(yīng)用方案A足夠了。使用短字符串存儲(chǔ)完整標(biāo)簽并在業(yè)務(wù)邏輯層建立一套清晰的映射和回退規(guī)則。例如當(dāng)用戶選擇“中文簡(jiǎn)體”時(shí)前端可以提交zh-Hans當(dāng)從瀏覽器獲取到zh-CN時(shí)通過(guò)后端映射或配置將其關(guān)聯(lián)到zh-Hans資源。4.3 場(chǎng)景三操作系統(tǒng)與瀏覽器環(huán)境識(shí)別在Node.js后端或?yàn)g覽器端你需要獲取系統(tǒng)或環(huán)境的語(yǔ)言設(shè)置。瀏覽器端// 獲取瀏覽器優(yōu)先語(yǔ)言數(shù)組 const browserLanguages navigator.languages; // 例如[zh-CN, zh, en-US, en] // 獲取單個(gè)首選語(yǔ)言 const userLanguage navigator.language || navigator.userLanguage; // 例如zh-CN注意navigator.language返回的是單個(gè)字符串而navigator.languages返回一個(gè)按優(yōu)先級(jí)排序的數(shù)組后者更可靠。你應(yīng)該用這個(gè)數(shù)組作為Accept-Language的模擬交給你的語(yǔ)言檢測(cè)邏輯處理。Node.js 后端可以從HTTP請(qǐng)求頭Accept-Language中解析。const acceptLanguage req.headers[accept-language]; // 例如zh-CN,zh;q0.9,en;q0.8,en-GB;q0.7 // 需要使用類似 accepts 或 locale 這樣的庫(kù)來(lái)解析 const Accept require(accepts); const accept Accept(req); const locale accept.languages([en, zh-Hans, zh-Hant, ja]); // 返回匹配的第一個(gè)實(shí)戰(zhàn)經(jīng)驗(yàn)四語(yǔ)言檢測(cè)的優(yōu)先級(jí)策略永遠(yuǎn)不要完全依賴自動(dòng)檢測(cè)。應(yīng)提供一個(gè)明顯的語(yǔ)言切換器讓用戶手動(dòng)選擇。自動(dòng)檢測(cè)的邏輯應(yīng)該是1) 用戶上次手動(dòng)選擇并保存的語(yǔ)言2) URL參數(shù)如?langzh-Hans3) Cookie或本地存儲(chǔ)4) 瀏覽器Accept-Language頭5) 應(yīng)用默認(rèn)語(yǔ)言如英語(yǔ)。用戶的明確選擇權(quán)必須高于系統(tǒng)的猜測(cè)。5. 常見(jiàn)陷阱與最佳實(shí)踐總結(jié)在多年與多語(yǔ)言打交道的經(jīng)歷中我踩過(guò)不少坑也總結(jié)出一些鐵律。陷阱一混淆“語(yǔ)言”與“區(qū)域設(shè)置”en是語(yǔ)言en-US是區(qū)域設(shè)置。區(qū)域設(shè)置除了語(yǔ)言還包含數(shù)字格式1,234.56 vs 1.234,56、日期格式MM/DD/YYYY vs DD/MM/YYYY、貨幣符號(hào)$ vs €、排序規(guī)則等。如果你的應(yīng)用只做了文本翻譯但數(shù)字、日期格式還是原來(lái)的樣子體驗(yàn)會(huì)非常割裂。解決方案是使用完整的國(guó)際化庫(kù)如JavaScript的IntlAPI它能根據(jù)語(yǔ)言標(biāo)簽自動(dòng)處理這些格式。陷阱二硬編碼語(yǔ)言列表不要在代碼里寫(xiě)死const languages [en, zh-CN, ja]。一旦要新增一種語(yǔ)言就需要改代碼、重新部署。最佳實(shí)踐是將支持的語(yǔ)言列表作為配置文件如i18n.config.json或從后端API動(dòng)態(tài)獲取。這樣運(yùn)營(yíng)人員或產(chǎn)品經(jīng)理可以在后臺(tái)管理界面直接添加新語(yǔ)言前端自動(dòng)呈現(xiàn)。陷阱三翻譯資源的鍵名使用源語(yǔ)言錯(cuò)誤示例{ “Hello”: “你好”, “Goodbye”: “再見(jiàn)” }。當(dāng)源語(yǔ)言如英語(yǔ)的鍵名需要修改時(shí)“Hello”改成“Hi”所有其他語(yǔ)言的翻譯文件都需要同步修改鍵名極易出錯(cuò)。正確做法是使用業(yè)務(wù)邏輯相關(guān)的、語(yǔ)義化的鍵名。 正確示例{ “greeting.hello”: “你好”, “greeting.goodbye”: “再見(jiàn)” }。英文文件里是{ “greeting.hello”: “Hello”, “greeting.goodbye”: “Goodbye” }。這樣無(wú)論源語(yǔ)言文本如何變化鍵名穩(wěn)定不變。陷阱四忽略文本長(zhǎng)度變化德語(yǔ)單詞通常比英語(yǔ)長(zhǎng)中文短語(yǔ)通常比英文短。UI設(shè)計(jì)時(shí)必須考慮文本擴(kuò)展通常預(yù)留30%-50%的額外空間和收縮。使用CSS屬性如text-overflow: ellipsis時(shí)要小心確保關(guān)鍵信息不被截?cái)?。?duì)于按鈕、標(biāo)簽等固定寬度的元素可以采用最小寬度min-width或彈性布局。陷阱五缺乏上下文給翻譯者把一句“Save”扔給翻譯者他可能翻譯成“保存”動(dòng)詞或“儲(chǔ)蓄”名詞。必須提供上下文。專業(yè)的做法是使用帶有描述性的鍵名并在翻譯管理平臺(tái)如Crowdin, Transifex或JSON文件中添加注釋。{ “button.save”: { “description”: “The label for the button that saves a document”, “defaultMessage”: “Save” } } // 在中文文件中 { “button.save”: “保存” }最佳實(shí)踐清單內(nèi)部統(tǒng)一使用BCP 47標(biāo)簽優(yōu)先使用“語(yǔ)言-腳本”如zh-Hans或“語(yǔ)言-地區(qū)”如en-US形式。建立中央映射表處理瀏覽器語(yǔ)言標(biāo)簽到內(nèi)部標(biāo)簽的轉(zhuǎn)換如zh-CN-zh-Hans。分離“文本翻譯”和“區(qū)域格式化”使用Intl等標(biāo)準(zhǔn)API處理日期、數(shù)字、貨幣。設(shè)計(jì)彈性的UI適應(yīng)文本長(zhǎng)度變化。永遠(yuǎn)提供手動(dòng)語(yǔ)言切換入口并持久化用戶選擇。對(duì)翻譯資源進(jìn)行版本控制并與代碼版本關(guān)聯(lián)。在開(kāi)發(fā)早期就引入國(guó)際化框架而不是事后補(bǔ)救。