在我之前的文章中,我介紹了 Stimulus——一個由 Basecamp 創建的簡單的 JavaScript 框架。今天我將討論 Stimulus 應用程序的國際化,因為該框架不提供任何開箱即用的國際化工具。國際化是重要的一步,特別是當您的應用程序被世界各地的人們使用時,因此對如何進行國際化的基本了解可能真的會派上用場。
當然,由您決定實施哪種國際化解決方案,無論是 jquery.I18n、Polyglot 還是其他解決方案。在本教程中,我想向您展示一個名為 I18next 的流行 I18n 框架,它具有許多很酷的功能,并提供許多額外的第三方插件來進一步簡化開發過程。即使具有所有這些功能,I18next 也不是一個復雜的工具,您不需要學習大量文檔即可開始使用。
在本文中,您將了解如何借助 I18next 庫在 Stimulus 應用程序中啟用 I18n 支持。具體來說,我們將討論:
- I18下一個配置
- 翻譯文件并異步加載
- 執行翻譯并一次性翻譯整個頁面
- 處理復數和性別信息
- 在區域設置之間切換并將所選區域設置保留在 GET 參數中
- 根據用戶的偏好設置區域設置
源代碼可在教程 github 存儲庫中找到。
引導刺激應用程序
首先,讓我們克隆 Stimulus Starter 項目并使用 yarn 包管理器安裝所有依賴項:
git clone https://github.com/stimulusJS/stimulus-starter.git cd stimulus-starter yarn install
我們將構建一個簡單的 Web 應用程序來加載有關注冊用戶的信息。對于每個用戶,我們將顯示他/她的登錄名以及他/她迄今為止上傳的照片數量(這些照片是什么并不重要)。
此外,我們將在頁面頂部提供一個語言切換器。選擇語言后,界面應立即翻譯,無需重新加載頁面。此外,URL 應附加 ?locale GET 參數,指定當前使用的區域設置。當然,如果頁面加載時已提供此參數,則應自動設置正確的語言。
好的,讓我們繼續渲染我們的用戶。將以下代碼行添加到 public/index.html 文件中:
<div data-controller="users" data-users-url="/api/users/index.json"></div>
在這里,我們使用 users 控制器并提供一個用于加載用戶的 URL。在現實應用程序中,我們可能會有一個服務器端腳本,用于從數據庫中獲取用戶并使用 JSON 進行響應。然而,在本教程中,我們只需將所有必要的數據放入 public/api/users/index.json 文件中即可:
[ { "login": "johndoe", "photos_count": "15", "gender": "male" }, { "login": "annsmith", "photos_count": "20", "gender": "female" } ]
現在創建一個新的src/controllers/users_controller.js文件:
import { Controller } from "stimulus" export default class extends Controller { connect() { this.loadUsers() } }
一旦控制器連接到 dom,我們就會借助 loadUsers() 方法異步加載用戶:
loadUsers() { fetch(this.data.get("url")) .then(response => response.text()) .then(json => { this.renderUsers(json) }) }
此方法向給定 URL 發送獲取請求,獲取響應,最后呈現用戶:
renderUsers(users) { let content = '' JSON.parse(users).forEach((user) => { content += `<div>Login: ${user.login}<br>Has uploaded ${user.photos_count} photo(s)</div><hr>` }) this.element.innerHTML = content }
renderUsers() 依次解析 JSON,構造一個包含所有內容的新字符串,最后將此內容顯示在頁面上(this.element 是將返回控制器連接到的實際 DOM 節點,在我們的例子中是 div )。
I18下一個
現在我們將繼續將 I18next 集成到我們的應用程序中。向我們的項目添加兩個庫:I18next 本身和一個插件,以實現從后端異步加載翻譯文件:
yarn add i18next i18next-xhr-backend
我們將把所有與 I18next 相關的東西存儲在一個單獨的 src/i18n/config.js 文件中,所以現在就創建它:
import i18next from 'i18next' import I18nXHR from 'i18next-xhr-backend' const i18n = i18next.use(I18nXHR).init({ fallbackLng: 'en', whitelist: ['en', 'ru'], preload: ['en', 'ru'], ns: 'users', defaultNS: 'users', fallbackNS: false, debug: true, backend: { loadPath: '/i18n/{{lng}}/{{ns}}.json', } }, function(err, t) { if (err) return console.error(err) }); export { i18n as i18n }
讓我們從上到下了解這里發生了什么:
- use(I18nXHR) 啟用 i18next-xhr-backend 插件。
- fallbackLng 告訴它使用英語作為后備語言。
- whitelist 只允許設置英語和俄語。當然,您可以選擇任何其他語言。
- preload 指示從服務器預加載翻譯文件,而不是在選擇相應語言時加載它們。
- ns 表示“命名空間”,接受字符串或數組。在此示例中,我們只有一個命名空間,但對于較大的應用程序,您可以引入其他命名空間,例如 admin、cart、profile 等。每個命名空間都應該創建一個單獨的翻譯文件。
- defaultNS 將 users 設置為默認命名空間。
- fallbackNS 禁用名稱空間回退。
- debug 允許在瀏覽器的控制臺中顯示調試信息。具體來說,它會說明加載哪些翻譯文件、選擇哪種語言等。您可能希望在將應用程序部署到生產環境之前禁用此設置。
- backend 為 I18nXHR 插件提供配置并指定從何處加載翻譯。請注意,路徑應包含區域設置的標題,而文件應以命名空間命名并具有 .json 擴展名
- function(err, t) 是當 I18next 準備好時(或當出現錯誤時)運行的回調。
接下來,讓我們制作翻譯文件。俄語翻譯應放入 public/i18n/ru/users.json 文件中:
{ "login": "Логин" }
login 這里是翻譯鍵,而 Логин 是要顯示的值。
英文翻譯應該轉到 public/i18n/en/users.json 文件:
{ "login": "Login" }
為了確保 I18next 正常工作,您可以將以下代碼行添加到 i18n/config.js 文件內的回調中:
// config goes here... function(err, t) { if (err) return console.error(err) console.log(i18n.t('login')) }
在這里,我們使用一個名為 t 的方法,意思是“翻譯”。該方法接受翻譯鍵并返回相應的值。
但是,我們可能有很多 ui 部分需要翻譯,而使用 t 方法來翻譯會非常乏味。相反,我建議您使用另一個名為 loc-i18next 的插件,它允許您一次翻譯多個元素。
一次性翻譯
安裝loc-i18next插件:
yarn add loc-i18next
將其導入src/i18n/config.js文件的頂部:
import locI18next from 'loc-i18next'
現在提供插件本身的配置:
// other config const loci18n = locI18next.init(i18n, { selectorAttr: 'data-i18n', optionsAttr: 'data-i18n-options', useOptionsAttr: true }); export { loci18n as loci18n, i18n as i18n }
這里有幾點需要注意:
- locI18next.init(i18n) 基于之前定義的 I18next 實例創建一個新的插件實例。
- selectorAttr 指定使用哪個屬性來檢測需要本地化的元素?;旧?,loc-i18next 將搜索此類元素并使用 data-i18n 屬性的值作為翻譯鍵。
- optionsAttr 指定哪個屬性包含附加翻譯選項。
- useOptionsAttr 指示插件使用其他選項。
我們的用戶正在異步加載,因此我們必須等到此操作完成,然后才執行本地化。現在,我們簡單地設置一個計時器,在調用 localize() 方法之前等待兩秒——當然,這是一個臨時的 hack。
import { loci18n } from '../i18n/config' // other code... loadUsers() { fetch(this.data.get("url")) .then(response => response.text()) .then(json => { this.renderUsers(json) setTimeout(() => { // <p>編寫 localize() 方法本身的代碼:</p> <pre class="brush:javascript;toolbal:false;"> localize() { loci18n('.users') }
如您所見,我們只需要將選擇器傳遞給 loc-i18next 插件即可。內部的所有元素(設置了 data-i18n 屬性)都將自動本地化。
現在調整 renderUsers 方法?,F在,我們只翻譯“Login”一詞:
renderUsers(users) { let content = '' JSON.parse(users).forEach((user) => { content += `<div class="users">ID: ${user.id}<br><span data-i18n="login"></span>: ${user.login}<br>Has uploaded ${user.photos_count} photo(s)</div><hr>` }) this.element.innerHTML = content }
不錯!重新加載頁面,等待兩秒鐘,并確保每個用戶都顯示“登錄”字樣。
復數和性別
我們對部分界面進行了本地化,這真的很酷。盡管如此,每個用戶還有兩個字段:上傳的照片數量和性別。由于我們無法預測每個用戶將擁有多少張照片,因此應根據給定的數量將“照片”一詞正確地復數化。為此,我們需要之前配置的 data-i18n-options 屬性。要提供計數,應為 data-i18n-options 分配以下對象:{ “count”: YOUR_COUNT }。
性別信息也應考慮在內。英語中的“uploaded”一詞可以適用于男性和女性,但在俄語中它要么變成“загрузил”或“загрузила”,所以我們再次需要 data-i18n-options,其中有 { “context”: “GENDER” } 作為值。順便請注意,您可以利用此上下文來完成其他任務,而不僅僅是提供性別信息。
renderUsers(users) { let content = '' JSON.parse(users).forEach((user) => { content += `<div class="users"> <span data-i18n="login"></span>: ${user.login}<br><span data-i18n="uploaded" data-i18n-options="{ 'context': '${user.gender}' }"></span> <span data-i18n="photos" data-i18n-options="{ 'count': ${user.photos_count} }"></span> </div><hr>` }) this.element.innerHTML = content }
現在更新英文翻譯:
{ "login": "Login", "uploaded": "Has uploaded", "photos": "one photo", "photos_plural": "{{count}} photos" }
這里沒什么復雜的。由于對于英語,我們不關心性別信息(即上下文),因此翻譯鍵應該只是 uploaded。為了提供正確的復數翻譯,我們使用 photos 和 photos_plural 鍵。 {{count}} 部分為插值,將替換為實際數字。
至于俄語,事情就更復雜了:
{ "login": "Логин", "uploaded_male": "Загрузил уже", "uploaded_female": "Загрузила уже", "photos_0": "одну фотографию", "photos_1": "{{count}} фотографии", "photos_2": "{{count}} фотографий" }
首先,請注意,我們有兩個可能的上下文的 uploaded_male 和 uploaded_female 鍵。接下來,俄語中的復數規則也比英語中更復雜,因此我們必須提供不是兩個而是三個可能的短語。 I18next 支持多種開箱即用的語言,這個小工具可以幫助您了解應該為給定語言指定哪些復數鍵。
切換區域設置
我們已經完成了應用程序的翻譯,但用戶應該能夠在區域設置之間切換。因此,向 public/index.html 文件添加一個新的“語言切換器”組件:
在 src/controllers/languages_controller.js 文件中制作相應的控制器:
import { Controller } from "stimulus" import { i18n, loci18n } from '../i18n/config' export default class extends Controller { initialize() { let languages = [ {title: 'English', code: 'en'}, {title: 'Русский', code: 'ru'} ] this.element.innerHTML = languages.map((lang) => { return `
` }).join(”) } }
這里我們使用 initialize() 回調來顯示支持的語言列表。每個 li 都有一個 data-action 屬性,該屬性指定單擊元素時應觸發的方法(在本例中為 switchLanguage)。
現在添加 switchLanguage() 方法:
switchLanguage(e) { this.currentLang = e.target.getAttribute("data-lang") }
它只是獲取事件的目標并獲取 data-lang 屬性的值。
我還想為 currentLang 屬性添加 getter 和 setter:
get currentLang() { return this.data.get("currentLang") } set currentLang(lang) { if(i18n.language !== lang) { i18n.changeLanguage(lang) } if(this.currentLang !== lang) { this.data.set("currentLang", lang) loci18n('body') this.highlightCurrentLang() } }
getter 非常簡單——我們獲取當前使用的語言的值并返回它。
setter 更復雜。首先,如果當前設置的語言與所選語言不相等,我們使用 changeLanguage 方法。此外,我們將新選擇的語言環境存儲在 data-current-lang 屬性(在 getter 中訪問)下,使用 loc-i18next 插件本地化 HTML 頁面的主體,最后突出顯示當前使用的區域設置。
讓我們編寫 highlightCurrentLang() 的代碼:
highlightCurrentLang() { this.switcherTargets.forEach((el, i) => { el.classList.toggle("current", this.currentLang === el.getAttribute("data-lang")) }) }
這里我們迭代區域設置切換器的數組,并將它們的 data-lang 屬性的值與當前使用的區域設置的值進行比較。如果值匹配,則為切換器分配 current css 類,否則刪除該類。
為了使 this.switcherTargets 構建工作,我們需要按以下方式定義刺激目標:
static targets = [ "switcher" ]
此外,為 lis 添加值為 switcher 的 data-target 屬性:
initialize() { // ... this.element.innerHTML = languages.map((lang) => { return `
` }).join(”) // … }
另一個需要考慮的重要事項是翻譯文件可能需要一些時間來加載,我們必須等待此操作完成才能允許切換區域設置。因此,讓我們利用 loaded 回調:
initialize() { i18n.on('loaded', (loaded) => { // { return `
` }).join(”) this.currentLang = i18n.language }) }
最后,不要忘記從 loadUsers() 方法中刪除 setTimeout :
loadUsers() { fetch(this.data.get("url")) .then(response => response.text()) .then(json => { this.renderUsers(json) this.localize() }) }
在 URL 中保留區域設置
切換語言環境后,我想在包含所選語言代碼的 URL 中添加 ?lang GET 參數。在 history API 的幫助下,可以輕松地添加 GET 參數而不重新加載頁面:
set currentLang(lang) { if(i18n.language !== lang) { i18n.changeLanguage(lang) window.history.pushState(null, null, `?lang=${lang}`) // <h2>檢測區域設置</h2> <p></p> <p>我們今天要實現的最后一件事是能夠根據用戶的偏好設置區域設置。一個名為 LanguageDetector 的插件可以幫助我們解決這個任務。添加新的 Yarn 包:</p> <pre class="brush:shell;toolbal:false;">yarn add i18next-browser-languagedetector
在 i18n/config.js 文件中導入 LanguageDetector:
import LngDetector from 'i18next-browser-languagedetector'
現在調整配置:
const i18n = i18next.use(I18nXHR).use(LngDetector).init({ // <p>order 選項列出了插件應嘗試的所有技術(按重要性排序),以便“猜測”首選區域設置:</p>
- querystring?表示檢查包含區域設置代碼的 GET 參數。
- lookupQuerystring 設置要使用的 GET 參數的名稱,在我們的例子中是 lang 。
- navigator 表示從用戶的請求中獲取語言環境數據。
- htmlTag?涉及從 html 標記的 lang 屬性獲取首選區域設置。
結論
在本文中,我們介紹了 I18next——一種輕松翻譯 JavaScript 應用程序的流行解決方案。您已經學習了如何將 I18next 與 Stimulus 框架集成、配置它以及以異步方式加載翻譯文件。此外,您還了解了如何在區域設置之間切換以及如何根據用戶的偏好設置默認語言。
I18next 有一些額外的配置選項和許多插件,因此請務必瀏覽其官方文檔以了解更多信息。另請注意,Stimulus 不會強制您使用特定的本地化解決方案,因此您也可以嘗試使用 jQuery.I18n 或 Polyglot 等解決方案。
這就是今天的全部內容!感謝您的閱讀,直到下一次。