在上一篇文章中,我介绍了Stimulus(一种由Basecamp创建的适度JavaScript框架)。 今天,我将讨论Stimulus应用程序的国际化,因为该框架没有提供任何开箱即用的I18n工具。 国际化是重要的一步,尤其是当您的应用程序被世界各地的人们使用时,因此对如何使用它的基本了解可能会派上用场。
当然,由您决定要实施哪种国际化解决方案,无论是jQuery.I18n , Polyglot还是其他。 在本教程中,我想向您展示一个流行的I18n框架,称为I18next ,该框架具有许多很酷的功能,并提供了许多其他第三方插件来进一步简化开发过程。 即使具有所有这些功能,I18next也不是一个复杂的工具,您无需学习大量文档即可上手。
在本文中,您将学习如何借助I18next库在Stimulus应用程序中启用I18n支持。 具体来说,我们将讨论:
- I18next配置
- 翻译文件并异步加载
- 一口气进行翻译和整页翻译
- 处理复数形式和性别信息
- 在语言环境之间切换,并将所选语言环境保留在GET参数中
- 根据用户的偏好设置语言环境
源代码可在教程GitHub repo中获得 。
引导刺激应用
为了开始,让我们克隆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集成到我们的应用程序中。 在我们的项目中添加两个库: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
方法。 现在,我们只翻译“登录”一词:
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 }
。
性别信息也应予以考虑。 男性和女性均可使用英语中的“上载”一词,但在俄语中则变为“загрузил”或“загрузила”,因此我们再次需要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非常简单-我们获取当前使用的语言的值并将其返回。
设置器比较复杂。 首先,如果当前设置的语言与所选语言不相等,则使用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
构造工作,我们需要通过以下方式定义Stimulus目标:
static targets = [ "switcher" ]
另外,为li
添加data-target
属性和switcher
值:
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中保留语言环境
切换语言环境后,我想向包含所选语言代码的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之类的东西。
今天就这些! 感谢您的阅读,直到下一次。
翻译自: https://code.tutsplus.com/tutorials/translating-stimulus-apps-with-i18next--cms-30770