跳到主要内容前端国际化实现方案及最佳实践 | 极客日志JavaScript大前端
前端国际化实现方案及最佳实践
探讨了前端国际化的重要性及常见误区,指出硬编码和简单替换无法处理复数、日期货币格式等问题。推荐使用 i18next 库配合 React Hooks 进行多语言管理,利用 Intl API 处理本地化格式化。文章提供了配置示例、复数规则、RTL 支持及文件分离命名空间等最佳实践,强调根据实际需求把握国际化范围以提升用户体验而非炫技。
静心8 浏览 前端国际化实现方案及最佳实践
毒舌时刻
国际化?听起来就像是前端工程师为了显得自己很专业而特意搞的一套复杂流程。你以为随便加个 i18n 库就能实现国际化?别做梦了!到时候你会发现,翻译文件比代码还多,维护起来比代码还麻烦。
你以为翻译就是简单的文本替换?别天真了!不同语言的语法结构不同,直接替换会导致语法错误。还有那些所谓的国际化库,看起来高大上,用起来却各种问题。
为什么你需要这个
- 全球用户:国际化可以让你的应用支持全球用户,扩大用户群体。
- 用户体验:使用用户的母语可以提高用户体验,增加用户粘性。
- 市场竞争力:支持多语言的应用在国际市场上更具竞争力。
- 合规要求:某些国家和地区要求应用提供当地语言支持。
- 品牌形象:支持多语言可以提升品牌的国际化形象。
反面教材
function Welcome() {
return <h1>Welcome to our app!</h1>;
}
const translations = {
en: { welcome: 'Welcome to our app!', login: 'Login', register: 'Register' },
zh: { welcome: '欢迎使用我们的应用!', login: '登录', register: '注册' }
};
function Welcome() {
const lang = 'zh';
return <h1>{translations[lang].welcome}</h1>;
}
function ItemCount() {
lang = ;
;
}
() {
;
}
() {
;
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
{ count }
const
'en'
return
<p>You have {count} item(s) in your cart.</p>
function
OrderDate
{ date }
return
<p>Order date: {date.toLocaleString()}</p>
function
ProductPrice
{ price }
return
<p>Price: ${price}</p>
- 硬编码文本,难以维护和翻译
- 简单文本替换,无法处理复杂的翻译场景
- 忽略复数形式,在不同语言中可能不正确
- 忽略日期和时间格式,在不同地区可能显示错误
- 忽略货币格式,在不同国家可能显示错误
正确的做法
使用 i18next
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
const resources = {
en: {
translation: {
welcome: 'Welcome to our app!',
login: 'Login',
register: 'Register',
itemCount: 'You have {{count}} item(s) in your cart.',
itemCount_plural: 'You have {{count}} items in your cart.',
orderDate: 'Order date: {{date}}',
productPrice: 'Price: {{price}}'
}
},
zh: {
translation: {
welcome: '欢迎使用我们的应用!',
login: '登录',
register: '注册',
itemCount: '您的购物车中有 {{count}} 件商品。',
orderDate: '订单日期:{{date}}',
productPrice: '价格:{{price}}'
}
}
};
i18n
.use(initReactI18next)
.init({
resources,
lng: 'en',
fallbackLng: 'en',
interpolation: { escapeValue: false }
});
export default i18n;
import React from 'react';
import { useTranslation } from 'react-i18next';
function Welcome() {
const { t } = useTranslation();
return <h1>{t('welcome')}</h1>;
}
function ItemCount({ count }) {
const { t } = useTranslation();
return <p>{t('itemCount', { count })}</p>;
}
import React from 'react';
import { useTranslation } from 'react-i18next';
function LanguageSelector() {
const { i18n } = useTranslation();
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('zh')}>中文</button>
</div>
);
}
处理复数形式
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
const resources = {
en: {
translation: {
itemCount: 'You have {{count}} item in your cart.',
itemCount_plural: 'You have {{count}} items in your cart.'
}
},
zh: {
translation: {
itemCount: '您的购物车中有 {{count}} 件商品。'
}
}
};
i18n
.use(initReactI18next)
.init({
resources,
lng: 'en',
fallbackLng: 'en',
interpolation: { escapeValue: false }
});
import React from 'react';
import { useTranslation } from 'react-i18next';
function ItemCount({ count }) {
const { t } = useTranslation();
return <p>{t('itemCount', { count, plural: count !== 1 })}</p>;
}
处理日期和时间格式
function FormatDate({ date }) {
const { i18n } = useTranslation();
const locale = i18n.language;
return (
<p>
{new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date)}
</p>
);
}
import React from 'react';
import { useTranslation } from 'react-i18next';
function OrderDate({ date }) {
const { t, i18n } = useTranslation();
const locale = i18n.language;
const formattedDate = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
return <p>{t('orderDate', { date: formattedDate })}</p>;
}
处理货币格式
function FormatPrice({ price, currency = 'USD' }) {
const { i18n } = useTranslation();
const locale = i18n.language;
return (
<p>
{new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(price)}
</p>
);
}
import React from 'react';
import { useTranslation } from 'react-i18next';
function ProductPrice({ price, currency = 'USD' }) {
const { t, i18n } = useTranslation();
const locale = i18n.language;
const formattedPrice = new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(price);
return <p>{t('productPrice', { price: formattedPrice })}</p>;
}
处理 RTL 语言
function isRTL(lang) {
const rtlLanguages = ['ar', 'he', 'fa', 'ur'];
return rtlLanguages.includes(lang);
}
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
function App() {
const { i18n } = useTranslation();
useEffect(() => {
if (isRTL(i18n.language)) {
document.documentElement.dir = 'rtl';
document.documentElement.lang = i18n.language;
} else {
document.documentElement.dir = 'ltr';
document.documentElement.lang = i18n.language;
}
}, [i18n.language]);
return (
<div>
{/* 应用内容 */}
</div>
);
}
最佳实践
{
"welcome": "Welcome to our app!",
"login": "Login",
"register": "Register"
}
{
"welcome": "欢迎使用我们的应用!",
"login": "登录",
"register": "注册"
}
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
interpolation: { escapeValue: false }
});
export default i18n;
{
"login": "Login",
"register": "Register"
}
{
"welcome": "Welcome to our app!"
}
import React from 'react';
import { useTranslation } from 'react-i18next';
function Home() {
const { t } = useTranslation(['home', 'common']);
return (
<div>
<h1>{t('welcome')}</h1>
<button>{t('login', { ns: 'common' })}</button>
<button>{t('register', { ns: 'common' })}</button>
</div>
);
}
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
function DynamicComponent() {
const { t, i18n } = useTranslation();
useEffect(() => {
i18n.loadNamespaces('dynamic');
}, [i18n]);
return <p>{t('dynamicText', { ns: 'dynamic' })}</p>;
}
毒舌点评
国际化确实很重要,但我见过太多开发者滥用这个特性,导致应用变得过于复杂。
想象一下,当你为了支持多语言,创建了大量的翻译文件,结果导致维护成本增加,这真的值得吗?
还有那些过度使用国际化库的开发者,为了使用某个库,而忽略了项目的实际需求,结果导致代码变得过于复杂。
所以,在进行国际化时,一定要把握好度。不要为了国际化而国际化,要根据实际情况来决定国际化的范围。
当然,对于面向全球用户的应用来说,国际化是必要的。但对于只面向特定地区用户的应用,过度的国际化反而会增加开发成本和维护难度。
最后,记住一句话:国际化的目的是为了提高用户体验,而不是为了炫技。如果你的国际化实现导致用户体验变得更差,那你就失败了。