在我之前的文章中,我介紹了 Stimulus——一個由 Basecamp 創建的簡單的 JavaScript 框架。今天我將討論 Stimulus 應用程式的國際化,因為該框架不提供任何開箱即用的國際化工具。國際化是重要的一步,特別是當您的應用程式被世界各地的人們使用時,因此對如何進行國際化的基本了解可能真的會派上用場。
當然,由您決定實作哪種國際化解決方案,無論是 jQuery.I18n、Polyglot 或其他解決方案。在本教程中,我想向您展示一個名為 I18next 的流行 I18n 框架,它具有許多很酷的功能,並提供許多額外的第三方插件來進一步簡化開發過程。即使具有所有這些功能,I18next 也不是一個複雜的工具,您不需要學習大量文件即可開始使用。
在本文中,您將了解如何借助 I18next 程式庫在 Stimulus 應用程式中啟用 I18n 支援。具體來說,我們將討論:
原始程式碼可在教學 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
)。
現在我們將繼續將 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(() => { // <--- this.localize() }, '2000') }) }
编写 localize()
方法本身的代码:
localize() { loci18n('.users') }
如您所见,我们只需要将选择器传递给 loc-i18next 插件即可。内部的所有元素(设置了 data-i18n
属性)都将自动本地化。
现在调整 renderUsers
方法。现在,我们只翻译“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 文件添加一个新的“语言切换器”组件:
<ul data-controller="languages" class="language-switcher"></ul>
在 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 `<li data-action="click->languages#switchLanguage" data-lang="${lang.code}">${lang.title}</li>` }).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" ]
此外,为 li
s 添加值为 switcher
的 data-target
属性:
initialize() { // ... this.element.innerHTML = languages.map((lang) => { return `<li data-action="click->languages#switchLanguage" data-target="languages.switcher" data-lang="${lang.code}">${lang.title}</li>` }).join('') // ... }
另一个需要考虑的重要事项是翻译文件可能需要一些时间来加载,我们必须等待此操作完成才能允许切换区域设置。因此,让我们利用 loaded
回调:
initialize() { i18n.on('loaded', (loaded) => { // <--- let languages = [ {title: 'English', code: 'en'}, {title: 'Русский', code: 'ru'} ] this.element.innerHTML = languages.map((lang) => { return `<li data-action="click->languages#switchLanguage" data-target="languages.switcher" data-lang="${lang.code}">${lang.title}</li>` }).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 中添加 ?lang
GET 参数。在 History API 的帮助下,可以轻松地添加 GET 参数而不重新加载页面:
set currentLang(lang) { if(i18n.language !== lang) { i18n.changeLanguage(lang) window.history.pushState(null, null, `?lang=${lang}`) // <--- } if(this.currentLang !== lang) { this.data.set("currentLang", lang) loci18n('body') this.highlightCurrentLang() } }
我们今天要实现的最后一件事是能够根据用户的偏好设置区域设置。一个名为 LanguageDetector 的插件可以帮助我们解决这个任务。添加新的 Yarn 包:
yarn add i18next-browser-languagedetector
在 i18n/config.js 文件中导入 LanguageDetector
:
import LngDetector from 'i18next-browser-languagedetector'
现在调整配置:
const i18n = i18next.use(I18nXHR).use(LngDetector).init({ // <--- // other options go here... detection: { order: ['querystring', 'navigator', 'htmlTag'], lookupQuerystring: 'lang', } }, function(err, t) { if (err) return console.error(err) });
order
选项列出了插件应尝试的所有技术(按重要性排序),以便“猜测”首选区域设置:
querystring
表示检查包含区域设置代码的 GET 参数。lookupQuerystring
设置要使用的 GET 参数的名称,在我们的例子中是 lang
。navigator
表示从用户的请求中获取语言环境数据。htmlTag
涉及从 html
标记的 lang
属性获取首选区域设置。在本文中,我们介绍了 I18next——一种轻松翻译 JavaScript 应用程序的流行解决方案。您已经学习了如何将 I18next 与 Stimulus 框架集成、配置它以及以异步方式加载翻译文件。此外,您还了解了如何在区域设置之间切换以及如何根据用户的偏好设置默认语言。
I18next 有一些额外的配置选项和许多插件,因此请务必浏览其官方文档以了解更多信息。另请注意,Stimulus 不会强制您使用特定的本地化解决方案,因此您也可以尝试使用 jQuery.I18n 或 Polyglot 等解决方案。
這就是今天的全部內容!感謝您的閱讀,直到下次。
以上是使用 I18Next 本地化刺激應用程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!