如何使用 i18next 正确地国际化 React 应用程序
首先:“为什么选择 i18next?”
让我们开始吧...
🎉🥳 恭喜 🎊🎁
👍
为软件用户克服语言障碍是一个重要课题。
英语已不再是互联网的通用语言。
截至2020 年 3 月,只有 25.9% 的互联网用户使用英语。
如果您的网站未进行本地化,用户很可能会跳过您的网站。
因此,如果没有多语言网站,您可能会错失大量潜在用户。
JavaScript 生态系统中有很多国际化框架。您可以在这里找到一些 JavaScript 国际化框架的详细信息。
在本文中,我们将使用i18next框架对React.js应用进行国际化。
目录
首先:“为什么选择 i18next?”
说到 React 本地化,最受欢迎的是i18next及其 React 扩展react-i18next,理由如下:
i18next 创建于 2011 年末。它比您现在使用的大多数库都要古老,包括您的主要前端技术(react、vue 等)。
➡️ 可持续
根据 i18next 开源的时间长度,没有任何实际的 i18n 案例是无法通过 i18next 解决的。
➡️成熟
i18next 可以在任何 javascript(以及一些非 javascript - .net、elm、iOS、android、ruby 等)环境中使用,可以使用任何 UI 框架、任何 i18n 格式,...可能性无穷无尽。
➡️ 可扩展
与其他常规 i18n 框架相比,i18next 具有许多功能和可能性。
➡️丰富
您可以在这里找到有关 i18next 的特殊之处及其工作原理的更多信息。
让我们开始吧...
先决条件
确保已安装 Node.js 和 npm。在开始使用react-i18next之前,最好先熟悉一些简单的 HTML、JavaScript 和 React.js 基础。
入门
使用您自己的 React 项目或创建一个新项目,即使用create-react-app。
npx create-react-app my-app
我们将调整应用程序,使其能够根据用户的偏好检测语言。
此外,我们还将创建一个语言切换器,使内容在不同语言之间切换。
让我们安装一些 i18next 依赖项:
npm install i18next react-i18next i18next-browser-languagedetector
让我们准备一个 i18n.js 文件:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
// here we will place our translations...
}
}
}
});
export default i18n;
让我们将该文件导入到 index.js 文件的某个位置:
对于 React >= 18.0.0 使用:
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
// import i18n (needs to be bundled ;))
import './i18n';
const root = createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
对于较旧的 React 版本,请使用:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// import i18n (needs to be bundled ;))
import './i18n';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
现在让我们尝试将一些硬编码文本移到翻译中。
我们对第一段文本使用了Trans 组件,对第二段文本使用了useTranslation 钩子:
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
function App() {
const { t } = useTranslation();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
</div>
);
}
export default App;
这些文本现在是翻译资源的一部分:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
}
}
}
}
});
export default i18n;
语言切换器
现在让我们定义一个语言切换器:
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
const lngs = {
en: { nativeName: 'English' },
de: { nativeName: 'Deutsch' }
};
function App() {
const { t, i18n } = useTranslation();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div>
{Object.keys(lngs).map((lng) => (
<button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => i18n.changeLanguage(lng)}>
{lngs[lng].nativeName}
</button>
))}
</div>
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
</div>
);
}
export default App;
并添加一些新语言的翻译:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
}
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
}
}
}
}
});
export default i18n;
🥳太棒了,您刚刚创建了您的第一个语言切换器!
得益于i18next-browser-languagedetector,它现在会尝试检测浏览器语言,并在您提供翻译的情况下自动使用该语言。语言切换器中手动选择的语言将持久保存在 localStorage 中,下次您访问该页面时,该语言将作为首选语言。
插值和复数
i18next 不仅仅提供标准的 i18n 功能,
它还能处理复数和插值。
让我们计算一下每次语言改变的次数:
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState } from 'react';
const lngs = {
en: { nativeName: 'English' },
de: { nativeName: 'Deutsch' }
};
function App() {
const { t, i18n } = useTranslation();
const [count, setCounter] = useState(0);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div>
{Object.keys(lngs).map((lng) => (
<button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
i18n.changeLanguage(lng);
setCounter(count + 1);
}}>
{lngs[lng].nativeName}
</button>
))}
</div>
<p>
<i>{t('counter', { count })}</i>
</p>
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
</div>
);
}
export default App;
...并扩展翻译资源:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
},
counter_one: 'Changed language just once',
counter_other: 'Changed language already {{count}} times'
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
},
counter_one: 'Die Sprache wurde erst ein mal gewechselt',
counter_other: 'Die Sprache wurde {{count}} mal gewechselt'
}
}
}
});
export default i18n;
i18next 将根据计数值选择正确的复数形式。有关复数和插值
的更多信息,请参阅i18next 官方文档。
💡 i18next 还能够处理具有多种复数形式的语言,例如阿拉伯语:
// translation resources:
{
"key_0": "zero",
"key_1": "singular",
"key_2": "two",
"key_3": "few",
"key_4": "many",
"key_5": "other"
}
// usage:
t('key', {count: 0}); // -> "zero"
t('key', {count: 1}); // -> "singular"
t('key', {count: 2}); // -> "two"
t('key', {count: 3}); // -> "few"
t('key', {count: 4}); // -> "few"
t('key', {count: 5}); // -> "few"
t('key', {count: 11}); // -> "many"
t('key', {count: 99}); // -> "many"
t('key', {count: 100}); // -> "other"
格式化
现在,让我们看看如何在i18next和Luxon的帮助下使用不同的日期格式来处理日期和时间。
npm install luxon
我们希望有一个页脚显示当前日期:
import './Footer.css';
const Footer = ({ t }) => (
<div className="Footer">
<div>{t('footer.date', { date: new Date() })}</div>
</div>
);
export default Footer;
// imported in our App.js and used like this
// <Footer t={t} />
导入 luxon 并定义一个格式函数,如文档中所述,并添加新的翻译键:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { DateTime } from 'luxon';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
},
counter_one: 'Changed language just once',
counter_other: 'Changed language already {{count}} times',
footer: {
date: 'Today is {{date, DATE_HUGE}}'
}
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
},
counter_one: 'Die Sprache wurde erst ein mal gewechselt',
counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
footer: {
date: 'Heute ist {{date, DATE_HUGE}}'
}
}
}
}
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
😎 太棒了,现在我们有了特定语言的日期格式!
语境
那么根据当前时间的具体问候信息怎么样?例如早上、晚上等等。
这要归功于i18next 的上下文功能。
让我们创建一个 getGreetingTime 函数并使用其结果作为页脚翻译的上下文信息:
import { DateTime } from 'luxon';
import './Footer.css';
const getGreetingTime = (d = DateTime.now()) => {
const split_afternoon = 12; // 24hr time to split the afternoon
const split_evening = 17; // 24hr time to split the evening
const currentHour = parseFloat(d.toFormat('hh'));
if (currentHour >= split_afternoon && currentHour <= split_evening) {
return 'afternoon';
} else if (currentHour >= split_evening) {
return 'evening';
}
return 'morning';
}
const Footer = ({ t }) => (
<div className="Footer">
<div>{t('footer.date', { date: new Date(), context: getGreetingTime() })}</div>
</div>
);
export default Footer;
并添加一些上下文特定的翻译键:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';
i18n
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
},
counter_one: 'Changed language just once',
counter_other: 'Changed language already {{count}} times',
footer: {
date: 'Today is {{date, DATE_HUGE}}',
date_morning: 'Good morning! Today is {{date, DATE_HUGE}} | Have a nice day!',
date_afternoon: 'Good afternoon! It\'s {{date, DATE_HUGE}}',
date_evening: 'Good evening! Today was the {{date, DATE_HUGE}}'
}
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
},
counter_one: 'Die Sprache wurde erst ein mal gewechselt',
counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
footer: {
date: 'Heute ist {{date, DATE_HUGE}}',
date_morning: 'Guten Morgen! Heute ist {{date, DATE_HUGE}} | Wünsche einen schönen Tag!',
date_afternoon: 'Guten Tag! Es ist {{date, DATE_HUGE}}',
date_evening: 'Guten Abend! Heute war {{date, DATE_HUGE}}'
}
}
}
}
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
😁 是的,它有效!
将翻译与代码分开
将翻译放在 i18n.js 文件中虽然可行,但对于翻译人员来说不太方便。
让我们将翻译与代码分离,并将它们放在专门的 json 文件中。
因为这是一个 Web 应用程序,i18next-http-backend将帮助我们做到这一点。
npm install i18next-http-backend
将翻译移至公共文件夹:
调整 i18n.js 文件以使用 i18next-http-backend:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';
i18n
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
}
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
现在翻译是异步加载的,因此请确保使用Suspense组件包装您的应用程序以防止出现此错误:Uncaught Error: App suspended while rendering, but no fallback UI was specified.
import { Suspense } from 'react';
function App() {
// your app's code...
}
// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
return (
<Suspense fallback="...is loading">
<App />
</Suspense>
);
}
现在你的应用看起来还是一样的,但翻译是分开的。
如果你想支持一种新的语言,只需创建一个新的文件夹和一个新的翻译 json 文件。
这样你就可以将翻译发送给一些译员。
或者,如果你正在使用翻译管理系统,你可以直接使用 cli 同步文件。
💡 顺便说一句:得益于i18next 的命名空间功能,您还可以拥有多个翻译文件
🧑💻 第一部分的代码可以在这里找到。
更好的翻译管理
将译文发送给一些译员或翻译机构,您可以更好地掌控翻译,并与他们直接联系。但这也意味着您的工作量会增加。
这是一种传统方式。但请注意,四处发送文件总会造成一定的开销。
有没有更好的选择?
一定!
i18next 有助于翻译应用程序,这很棒 - 但还有更多作用。
- 您如何整合任何翻译服务/机构?
- 您如何跟踪新内容或已删除的内容?
- 您如何处理正确的版本控制?
- 如何在不部署完整应用程序的情况下部署翻译更改?
- 以及更多...
正在寻找这样的东西❓
- 易于集成
- 持续部署?持续本地化!
- 轻松管理翻译文件
- 订购专业翻译
- 分析与统计
- 从我们的内容分发网络(CDN)中获利
- 翻译版本控制
- 自动和按需机器翻译
- 无风险:随身携带您的数据
- 透明且公平的定价
- 以及更多...
这看起来怎么样?
首先,您需要在locize注册并登录。
然后在 locize 中创建一个新项目并添加您的翻译。您可以使用命令行界面 (cli)、导入单独的 json 文件或通过API来添加翻译。
完成后,我们将用i18next-locize-backend替换i18next-http-backend。
npm install i18next-locize-backend
将翻译导入 locize 后,删除 locales 文件夹:
调整 i18n.js 文件以使用 i18next-locize-backend,并确保从 locize 项目中复制 project-id 和 api-key:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';
const locizeOptions = {
projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
referenceLng: 'en',
};
i18n
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
backend: locizeOptions
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
i18next-locize-backend提供了直接从 locize 检索可用语言的功能,让我们使用它:
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState, Suspense, useEffect } from 'react';
import Footer from './Footer'
function App() {
const { t, i18n } = useTranslation();
const [count, setCounter] = useState(0);
const [lngs, setLngs] = useState({ en: { nativeName: 'English' }});
useEffect(() => {
i18n.services.backendConnector.backend.getLanguages((err, ret) => {
if (err) return // TODO: handle err...
setLngs(ret);
});
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div>
{Object.keys(lngs).map((lng) => (
<button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
i18n.changeLanguage(lng);
setCounter(count + 1);
}}>
{lngs[lng].nativeName}
</button>
))}
</div>
<p>
<i>{t('counter', { count })}</i>
</p>
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
<Footer t={t} />
</div>
);
}
// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
return (
<Suspense fallback="...is loading">
<App />
</Suspense>
);
}
保存缺失的翻译
由于使用了saveMissing 功能,在开发应用程序时,新的密钥会自动添加到 locize。
只需传入saveMissing: true
i18next 选项:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';
const locizeOptions = {
projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
referenceLng: 'en',
};
i18n
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
backend: locizeOptions,
saveMissing: true
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
每次您使用新密钥时,它将被发送到 locize,即:
<div>{t('new.key', 'this will be added automatically')}</div>
将导致定位如下:
👀 但还有更多……
借助locize-lastused插件,您将能够在 locize 中查找和过滤已使用或不再使用的键。
借助locize插件,您将能够在 locize InContext Editor中使用您的应用程序。
最后,借助自动机器翻译工作流程和使用saveMissing 功能,在开发应用程序时,新密钥不仅会自动添加到 locize,还会使用机器翻译自动翻译成目标语言。
观看此视频,了解自动机器翻译工作流程是怎样的!
npm install locize-lastused locize
在 i18n.js 中使用它们:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';
const locizeOptions = {
projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
referenceLng: 'en',
};
i18n
// locize-lastused
// sets a timestamp of last access on every translation segment on locize
// -> safely remove the ones not being touched for weeks/months
// https://github.com/locize/locize-lastused
.use(LastUsed)
// locize-editor
// InContext Editor of locize
.use(locizePlugin)
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
backend: locizeOptions,
locizeLastUsed: locizeOptions,
saveMissing: true
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
📦让我们为生产做好准备🚀
现在,我们准备将应用程序投入生产。
首先,在 locize 中创建一个专用于生产的版本。不要为该版本启用自动发布,而是手动发布,或者通过API或CLI进行发布。
最后,为该生产版本启用 Cache-Control max-age 。
让我们利用react-scripts 的环境功能。
让我们创建一个默认环境文件,一个用于开发,一个用于生产:
.环境变量:
SKIP_PREFLIGHT_CHECK=true
REACT_APP_VERSION=$npm_package_version
# locize
REACT_APP_LOCIZE_PROJECTID=0bbc223a-9aba-4a90-ab93-ab9d7bf7f780
REACT_APP_LOCIZE_REFLNG=en
.环境.开发:
REACT_APP_LOCIZE_VERSION=latest
REACT_APP_LOCIZE_APIKEY=aaad4141-54ba-4625-ae37-657538fe29e7
.环境.生产:
REACT_APP_LOCIZE_VERSION=production
现在让我们调整 i18n.js 文件:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';
const isProduction = process.env.NODE_ENV === 'production';
const locizeOptions = {
projectId: process.env.REACT_APP_LOCIZE_PROJECTID,
apiKey: process.env.REACT_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
referenceLng: process.env.REACT_APP_LOCIZE_REFLNG,
version: process.env.REACT_APP_LOCIZE_VERSION
};
if (!isProduction) {
// locize-lastused
// sets a timestamp of last access on every translation segment on locize
// -> safely remove the ones not being touched for weeks/months
// https://github.com/locize/locize-lastused
i18n.use(LastUsed);
}
i18n
// locize-editor
// InContext Editor of locize
.use(locizePlugin)
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
backend: locizeOptions,
locizeLastUsed: locizeOptions,
saveMissing: !isProduction // you should not use saveMissing in production
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
现在,在开发过程中,您将继续保存丢失的密钥并使用 lastused 功能。=> npm run start
并且在生产环境中,saveMissing 和 lastused 被禁用,并且 api-key 未暴露。=> npm run build && npm run serve
缓存:
合并版本:
🧑💻完整代码可以在这里找到。
还请查看此YouTube 视频中的代码集成部分。
🎉🥳 恭喜 🎊🎁
我希望您已经了解了一些有关i18next、React.js 本地化和现代本地化工作流程的新知识。
因此,如果您想将 i18n 主题提升到一个新的水平,那么值得尝试locize。
locize的创始人也是i18next的创造者。因此,使用locize就是直接支持i18next的未来。