各位观众老爷,晚上好!我是今天的讲师,咱们今天聊聊前端国际化(i18n)和本地化(l10n),这俩兄弟在JavaScript应用里怎么玩儿。别怕,咱们不搞学术报告,就当是拉家常,保证你听完能上手。
开场白:啥是i18n和l10n?
简单说,i18n(Internationalization)就是让你的应用做好准备,能适应各种语言和文化。 它是一种架构,一种设计理念,把应用的代码和语言文字内容分离。而l10n(Localization)就是根据特定的语言和文化,把应用真正变成当地人习惯的样子。它是i18n的具体实现。
你可以把i18n想象成一个百变金刚的骨架,而l10n就是给这个骨架穿上不同国家地区的衣服和配饰。
第一部分:语言包,i18n的基石
要搞国际化,首先得有“语言包”。语言包就是一个JSON文件,里面放着各种语言对应的文本。 就像一个翻译字典,你的应用要显示什么文字,就去字典里查对应的翻译。
-
语言包的结构
// en.json { "greeting": "Hello, {name}!", "welcomeMessage": "Welcome to our website!", "product.name": "Awesome Product", "price": "Price: {price, number, currency}", "date": "Today is {today, date, ::yyyyMMdd}" } // zh-CN.json { "greeting": "你好,{name}!", "welcomeMessage": "欢迎来到我们的网站!", "product.name": "超棒的产品", "price": "价格:{price, number, currency}", "date": "今天是 {today, date, ::yyyyMMdd}" }
这里面,
greeting
、welcomeMessage
等等就是key,后面是对应语言的文本。注意,key的设计要统一规范,方便管理和维护。{name}
、{price}
、{today}
这些是占位符,后面我们会讲怎么替换。 -
语言包的加载
动态加载语言包是最佳实践。这样可以避免一次性加载所有语言,减少初始加载时间。你可以使用
fetch
或者XMLHttpRequest
来加载JSON文件。async function loadLocale(locale) { try { const response = await fetch(`./locales/${locale}.json`); if (!response.ok) { throw new Error(`Failed to load locale ${locale}`); } const data = await response.json(); return data; } catch (error) { console.error('Error loading locale:', error); return null; } }
这个函数会根据传入的
locale
参数,加载对应的JSON文件。
第二部分:i18n库的选择与使用
自己手写i18n逻辑当然可以,但没必要。现在有很多优秀的i18n库,能帮你省不少事。 比较流行的库有:
- i18next: 功能强大,支持多种框架,社区活跃。
- FormatJS (React Intl): 由Yahoo维护,专门为React设计的。
- Polymer i18n: 为Polymer框架设计的。
这里我们以 i18next
为例,演示一下怎么用。
-
安装 i18next
npm install i18next i18next-browser-languagedetector i18next-http-backend
-
配置 i18next
import i18next from 'i18next'; import HttpApi from 'i18next-http-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; i18next .use(HttpApi) .use(LanguageDetector) .init({ fallbackLng: 'en', // 默认语言 debug: true, // 开启debug模式,方便调试 detection: { order: ['localStorage', 'cookie', 'navigator', 'htmlTag', 'path', 'subdomain'], lookupLocalStorage: 'i18nextLng', lookupCookie: 'i18next', }, backend: { loadPath: '/locales/{{lng}}.json', // 语言包的路径 }, }); export default i18next;
这个配置做了几件事:
fallbackLng
: 设置默认语言,如果找不到用户设置的语言,就用这个。debug
: 开启debug模式,方便在控制台查看i18next的运行情况。detection
: 设置语言检测的顺序。 这里尝试从localStorage、cookie、浏览器设置等地方获取用户设置的语言。backend
: 设置语言包的加载路径。
-
使用 i18next
import i18next from './i18n'; // 引入配置好的i18next import { useEffect, useState } from 'react'; function MyComponent() { const [greeting, setGreeting] = useState(''); useEffect(() => { i18next.on('languageChanged', (lng) => { setGreeting(i18next.t('greeting', { name: 'World' })); }); setGreeting(i18next.t('greeting', { name: 'World' })); }, []); return ( <div> <h1>{greeting}</h1> <p>{i18next.t('welcomeMessage')}</p> </div> ); } export default MyComponent;
这里我们用
i18next.t()
函数来获取翻译后的文本。 第一个参数是key,第二个参数是占位符的值。
第三部分:Pluralization(复数形式)
不同语言对复数的处理方式不一样。 比如英语,只有单数和复数两种形式。 但俄语有三种,阿拉伯语有六种! 所以,在i18n里,处理复数形式是个大问题。
-
i18next 的复数支持
i18next 通过在key后面加上
_zero
、_one
、_two
、_few
、_many
、_other
来区分不同的复数形式。// en.json { "itemCount_zero": "No items", "itemCount_one": "One item", "itemCount_other": "{count} items" } // zh-CN.json { "itemCount_zero": "没有物品", "itemCount_one": "一个物品", "itemCount_other": "{count} 个物品" }
import i18next from './i18n'; function displayItemCount(count) { return i18next.t('itemCount', { count }); } console.log(displayItemCount(0)); // No items (en) / 没有物品 (zh-CN) console.log(displayItemCount(1)); // One item (en) / 一个物品 (zh-CN) console.log(displayItemCount(5)); // 5 items (en) / 5 个物品 (zh-CN)
i18next 会根据
count
的值,自动选择对应的复数形式。
第四部分:日期、时间和数字的格式化
不同国家/地区对日期、时间和数字的格式化方式也不一样。 比如美国用MM/DD/YYYY
,欧洲用DD/MM/YYYY
。 数字的小数点和千位分隔符也不一样。
-
JavaScript 内置的 Intl 对象
JavaScript 提供了内置的
Intl
对象,专门用来处理国际化相关的问题。-
Intl.DateTimeFormat
: 格式化日期和时间。const now = new Date(); const enUsFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); const deDeFormatter = new Intl.DateTimeFormat('de-DE', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(enUsFormatter.format(now)); // July 26, 2024 console.log(deDeFormatter.format(now)); // 26. Juli 2024
-
Intl.NumberFormat
: 格式化数字。const number = 1234567.89; const enUsFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }); const deDeFormatter = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR', }); console.log(enUsFormatter.format(number)); // $1,234,567.89 console.log(deDeFormatter.format(number)); // 1.234.567,89 €
-
-
在 i18next 中使用 Intl 对象
i18next 可以和
Intl
对象结合使用,实现更灵活的格式化。// en.json { "date": "Today is {{value, date}}", "price": "Price: {{value, number}}" } // js import i18next from './i18n'; i18next.services.formatter.add('date', (value, lng, options) => { const formatter = new Intl.DateTimeFormat(lng, options); return formatter.format(value); }); i18next.services.formatter.add('number', (value, lng, options) => { const formatter = new Intl.NumberFormat(lng, options); return formatter.format(value); }); console.log(i18next.t('date', { value: new Date() })); console.log(i18next.t('price', { value: 1234.56 }));
这里我们给 i18next 添加了两个自定义的formatter,分别用来格式化日期和数字。
第五部分:RTL(Right-to-Left)支持
有些语言是从右往左写的,比如阿拉伯语和希伯来语。 为了支持这些语言,你需要做一些额外的处理。
-
HTML 的
dir
属性给 HTML 元素添加
dir="rtl"
属性,可以改变文本的阅读方向。<div dir="rtl"> This text will be displayed from right to left. </div>
-
CSS 的调整
RTL 语言的布局和LTR语言是相反的。 你需要调整CSS样式,比如把
float: left
改成float: right
。/* LTR */ .container { float: left; } /* RTL */ [dir="rtl"] .container { float: right; }
-
i18next 的 RTL 支持
i18next 可以根据当前语言,自动添加
dir
属性到<html>
标签上。i18next.on('languageChanged', (lng) => { document.documentElement.setAttribute('dir', i18next.dir(lng)); });
第六部分:其他注意事项
-
翻译质量
翻译质量是i18n的关键。 最好请专业的翻译人员来翻译,或者使用机器翻译后再人工校对。
-
测试
一定要对不同语言进行测试,确保应用在各种语言环境下都能正常工作。
-
语言切换
提供方便的语言切换功能,让用户可以自由选择自己喜欢的语言。
-
持续集成
把i18n集成到持续集成流程中,确保每次代码提交都能自动检查i18n相关的问题。
-
处理特殊字符
注意处理不同语言的特殊字符,比如中文的引号和英文的引号不一样。
-
字符串连接
尽量避免在代码中拼接字符串,因为不同语言的语序可能不一样。
// 错误的做法 const message = "You have " + count + " messages."; // 正确的做法 const message = i18next.t('message', { count });
// en.json { "message": "You have {{count}} messages." } // zh-CN.json { "message": "你有 {{count}} 条消息。" }
总结
国际化和本地化是个复杂但有趣的过程。 选好工具,规范流程,多做测试,你的应用就能走向世界啦! 希望今天的讲解对你有所帮助。 谢谢大家!