使用 I18Next 本地化刺激应用程序
在我之前的文章中,我介绍了 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(() => { // <--- 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" ]
此外,为 lis 添加值为 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 中保留区域设置
切换语言环境后,我想在包含所选语言代码的 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 本地化刺激应用程序的详细内容,更多请关注红帽云邮其它相关文章!