工程化与框架系列(28)--前端国际化实现

前端国际化实现 🌍

引言

前端国际化(i18n)是现代Web应用中的重要组成部分,它能够让应用支持多语言和多地区的用户使用。本文将深入探讨前端国际化的实现方案和最佳实践,包括文本翻译、日期时间格式化、货币处理等方面。

国际化概述

前端国际化主要包括以下方面:

  • 文本翻译:界面文字的多语言支持
  • 日期时间:不同地区的日期时间格式
  • 货币处理:不同地区的货币格式
  • 数字格式:不同地区的数字表示方式
  • 文字方向:从左到右(LTR)和从右到左(RTL)的布局支持

国际化工具实现

国际化管理器

// 国际化管理器类
class I18nManager {private static instance: I18nManager;private currentLocale: string;private messages: Map<string, Record<string, string>>;private numberFormatter: Intl.NumberFormat;private dateFormatter: Intl.DateTimeFormat;private currencyFormatter: Intl.NumberFormat;private constructor() {this.currentLocale = 'en-US';this.messages = new Map();// 初始化格式化器this.numberFormatter = new Intl.NumberFormat(this.currentLocale);this.dateFormatter = new Intl.DateTimeFormat(this.currentLocale);this.currencyFormatter = new Intl.NumberFormat(this.currentLocale, {style: 'currency',currency: 'USD'});// 初始化事件分发this.initializeEventDispatcher();}// 获取单例实例static getInstance(): I18nManager {if (!I18nManager.instance) {I18nManager.instance = new I18nManager();}return I18nManager.instance;}// 设置语言环境setLocale(locale: string): void {this.currentLocale = locale;// 更新格式化器this.numberFormatter = new Intl.NumberFormat(locale);this.dateFormatter = new Intl.DateTimeFormat(locale);this.currencyFormatter = new Intl.NumberFormat(locale, {style: 'currency',currency: this.getCurrencyCode(locale)});// 触发语言变更事件this.dispatchLocaleChange();}// 获取当前语言环境getLocale(): string {return this.currentLocale;}// 加载语言包loadMessages(locale: string, messages: Record<string, string>): void {this.messages.set(locale, messages);}// 获取翻译文本translate(key: string, params?: Record<string, any>): string {const messages = this.messages.get(this.currentLocale);if (!messages) {return key;}let text = messages[key] || key;// 替换参数if (params) {Object.entries(params).forEach(([key, value]) => {text = text.replace(`{${key}}`, String(value));});}return text;}// 格式化数字formatNumber(value: number): string {return this.numberFormatter.format(value);}// 格式化日期formatDate(date: Date, options?: Intl.DateTimeFormatOptions): string {if (options) {return new Intl.DateTimeFormat(this.currentLocale, options).format(date);}return this.dateFormatter.format(date);}// 格式化货币formatCurrency(value: number): string {return this.currencyFormatter.format(value);}// 获取文字方向getTextDirection(): 'ltr' | 'rtl' {return ['ar', 'he'].includes(this.currentLocale.split('-')[0])? 'rtl': 'ltr';}// 初始化事件分发private initializeEventDispatcher(): void {document.addEventListener('DOMContentLoaded', () => {this.updateDocumentDirection();});}// 更新文档方向private updateDocumentDirection(): void {document.documentElement.dir = this.getTextDirection();document.documentElement.lang = this.currentLocale;}// 触发语言变更事件private dispatchLocaleChange(): void {const event = new CustomEvent('localechange', {detail: { locale: this.currentLocale }});document.dispatchEvent(event);// 更新文档方向this.updateDocumentDirection();}// 获取货币代码private getCurrencyCode(locale: string): string {const region = locale.split('-')[1] || 'US';const currencyMap: Record<string, string> = {'US': 'USD','GB': 'GBP','EU': 'EUR','CN': 'CNY','JP': 'JPY'// 添加更多货币映射};return currencyMap[region] || 'USD';}
}// 使用示例
const i18n = I18nManager.getInstance();// 加载英文语言包
i18n.loadMessages('en-US', {'greeting': 'Hello, {name}!','farewell': 'Goodbye!','items_count': 'You have {count} items.'
});// 加载中文语言包
i18n.loadMessages('zh-CN', {'greeting': '你好,{name}!','farewell': '再见!','items_count': '你有 {count} 个物品。'
});// 切换语言
i18n.setLocale('zh-CN');// 使用翻译
console.log(i18n.translate('greeting', { name: 'John' })); // 输出:你好,John!
console.log(i18n.translate('items_count', { count: 5 })); // 输出:你有 5 个物品。// 格式化数字和日期
console.log(i18n.formatNumber(1234567.89)); // 输出:1,234,567.89
console.log(i18n.formatDate(new Date())); // 输出:2024/2/20
console.log(i18n.formatCurrency(99.99)); // 输出:¥99.99

组件国际化

// 国际化组件装饰器
function withI18n<T extends { new (...args: any[]): any }>(Component: T
) {return class extends Component {private i18n: I18nManager;private localeChangeHandler: () => void;constructor(...args: any[]) {super(...args);this.i18n = I18nManager.getInstance();this.localeChangeHandler = this.onLocaleChange.bind(this);// 监听语言变更事件document.addEventListener('localechange', this.localeChangeHandler);}// 组件销毁时移除事件监听disconnectedCallback() {document.removeEventListener('localechange', this.localeChangeHandler);if (super.disconnectedCallback) {super.disconnectedCallback();}}// 语言变更处理private onLocaleChange(): void {this.requestUpdate();}// 翻译辅助方法protected t(key: string, params?: Record<string, any>): string {return this.i18n.translate(key, params);}// 格式化数字protected formatNumber(value: number): string {return this.i18n.formatNumber(value);}// 格式化日期protected formatDate(date: Date): string {return this.i18n.formatDate(date);}// 格式化货币protected formatCurrency(value: number): string {return this.i18n.formatCurrency(value);}};
}// 国际化文本组件
@withI18n
class I18nText extends HTMLElement {private key: string;private params: Record<string, any>;constructor() {super();this.key = '';this.params = {};}// 观察的属性static get observedAttributes() {return ['key', 'params'];}// 属性变化处理attributeChangedCallback(name: string,oldValue: string,newValue: string) {if (name === 'key') {this.key = newValue;} else if (name === 'params') {try {this.params = JSON.parse(newValue);} catch (e) {this.params = {};}}this.updateContent();}// 更新内容private updateContent(): void {this.textContent = this.t(this.key, this.params);}
}// 注册组件
customElements.define('i18n-text', I18nText);// 国际化日期组件
@withI18n
class I18nDate extends HTMLElement {private date: Date;private format: Intl.DateTimeFormatOptions;constructor() {super();this.date = new Date();this.format = {};}// 观察的属性static get observedAttributes() {return ['value', 'format'];}// 属性变化处理attributeChangedCallback(name: string,oldValue: string,newValue: string) {if (name === 'value') {this.date = new Date(newValue);} else if (name === 'format') {try {this.format = JSON.parse(newValue);} catch (e) {this.format = {};}}this.updateContent();}// 更新内容private updateContent(): void {this.textContent = this.formatDate(this.date);}
}// 注册组件
customElements.define('i18n-date', I18nDate);// 使用示例
const template = `<div><i18n-text key="greeting" params='{"name":"John"}'></i18n-text><i18n-date value="2024-02-20"></i18n-date></div>
`;

路由国际化

// 国际化路由管理器
class I18nRouter {private static instance: I18nRouter;private i18n: I18nManager;private routes: Map<string, I18nRoute>;private currentRoute: I18nRoute | null;private constructor() {this.i18n = I18nManager.getInstance();this.routes = new Map();this.currentRoute = null;this.initializeRouter();}// 获取单例实例static getInstance(): I18nRouter {if (!I18nRouter.instance) {I18nRouter.instance = new I18nRouter();}return I18nRouter.instance;}// 注册路由registerRoute(route: I18nRoute): void {this.routes.set(route.name, route);}// 获取路由URLgetRouteUrl(name: string,params?: Record<string, string>): string {const route = this.routes.get(name);if (!route) {throw new Error(`Route "${name}" not found`);}const locale = this.i18n.getLocale();let path = route.paths[locale] || route.paths['en-US'];// 替换路径参数if (params) {Object.entries(params).forEach(([key, value]) => {path = path.replace(`:${key}`, value);});}return `/${locale}${path}`;}// 导航到路由navigate(name: string,params?: Record<string, string>): void {const url = this.getRouteUrl(name, params);window.history.pushState(null, '', url);this.handleRoute();}// 初始化路由器private initializeRouter(): void {// 监听popstate事件window.addEventListener('popstate', () => {this.handleRoute();});// 监听语言变更document.addEventListener('localechange', () => {this.updateRoute();});// 处理初始路由this.handleRoute();}// 处理路由private handleRoute(): void {const path = window.location.pathname;const [, locale, ...segments] = path.split('/');// 设置语言if (locale && locale !== this.i18n.getLocale()) {this.i18n.setLocale(locale);}// 查找匹配的路由const matchedRoute = this.findMatchingRoute(segments.join('/'));if (matchedRoute) {this.currentRoute = matchedRoute;this.renderRoute(matchedRoute);}}// 更新当前路由private updateRoute(): void {if (this.currentRoute) {const params = this.extractRouteParams();this.navigate(this.currentRoute.name, params);}}// 查找匹配的路由private findMatchingRoute(path: string): I18nRoute | null {const locale = this.i18n.getLocale();for (const route of this.routes.values()) {const routePath = route.paths[locale] || route.paths['en-US'];if (this.matchPath(path, routePath)) {return route;}}return null;}// 匹配路径private matchPath(path: string, pattern: string): boolean {const pathSegments = path.split('/');const patternSegments = pattern.split('/');if (pathSegments.length !== patternSegments.length) {return false;}return patternSegments.every((segment, index) => {if (segment.startsWith(':')) {return true;}return segment === pathSegments[index];});}// 提取路由参数private extractRouteParams(): Record<string, string> {if (!this.currentRoute) {return {};}const locale = this.i18n.getLocale();const routePath = this.currentRoute.paths[locale] || this.currentRoute.paths['en-US'];const currentPath = window.location.pathname.split('/').slice(2).join('/');const params: Record<string, string> = {};const pathSegments = currentPath.split('/');const patternSegments = routePath.split('/');patternSegments.forEach((segment, index) => {if (segment.startsWith(':')) {const paramName = segment.slice(1);params[paramName] = pathSegments[index];}});return params;}// 渲染路由private renderRoute(route: I18nRoute): void {const params = this.extractRouteParams();route.component.render(params);}
}// 路由配置接口
interface I18nRoute {name: string;paths: Record<string, string>;component: {render: (params: Record<string, string>) => void;};
}// 使用示例
const router = I18nRouter.getInstance();// 注册路由
router.registerRoute({name: 'home',paths: {'en-US': '/home','zh-CN': '/首页'},component: {render: () => {document.getElementById('app')!.innerHTML = `<h1><i18n-text key="home_title"></i18n-text></h1>`;}}
});router.registerRoute({name: 'product',paths: {'en-US': '/product/:id','zh-CN': '/产品/:id'},component: {render: (params) => {document.getElementById('app')!.innerHTML = `<h1><i18n-text key="product_title" params='{"id":"${params.id}"}'></i18n-text></h1>`;}}
});// 导航到路由
router.navigate('home');
router.navigate('product', { id: '123' });

最佳实践与建议

  1. 文本管理

    • 使用键值对管理文本
    • 支持参数替换
    • 处理复数形式
    • 维护翻译文档
  2. 格式处理

    • 使用Intl API
    • 处理时区问题
    • 支持不同数字系统
    • 考虑货币转换
  3. 布局适配

    • 支持RTL布局
    • 处理文本长度变化
    • 适配不同字体
    • 考虑文化差异
  4. 性能优化

    • 按需加载语言包
    • 缓存翻译结果
    • 优化重渲染
    • 减少格式化开销

总结

前端国际化需要考虑以下方面:

  1. 文本翻译管理
  2. 日期时间处理
  3. 货币数字格式化
  4. 布局方向适配
  5. 性能优化策略

通过合理的架构设计和优化措施,可以构建出优秀的国际化应用。

学习资源

  1. Intl API文档
  2. i18n最佳实践
  3. RTL布局指南
  4. 区域设置标准
  5. 国际化测试方法

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/32114.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wireshark 如何关闭混杂模式 wireshark操作

Fiddler和Wireshark都是进行抓包的工具&#xff1a;所谓抓包就是将网络传输发送与接收的数据包进行截获、重发、编辑、转存等操作&#xff0c;也用来检查网络安全。抓包也经常被用来进行数据截取等。黑客常常会用抓包软件获取你非加密的上网数据&#xff0c;然后通过分析&#…

IDEA2024又一坑:连接Docker服务连不上,提示:Cannot run program “docker“: CreateProcess error=2

为新电脑安装了IDEA2024版&#xff0c;因为局域网中安装有Docker,所以这台电脑上没有安装&#xff0c;当运行时发现死活连不上Docker报&#xff1a;Cannot run program “docker“: CreateProcess error2 分析&#xff1a; Docker服务有问题 其它电脑都能连&#xff0c;排除 网…

文件包含漏洞第一关

一、什么是文件包含漏洞 1.文件包含漏洞概述 和SQL注入等攻击方式一样&#xff0c;文件包含漏洞也是一种注入型漏洞&#xff0c;其本质就是输入一段用户能够控制的脚本或者代码&#xff0c;并让服务端执行。 什么叫包含呢&#xff1f;以PHP为例&#xff0c;我们常常把可重复使…

网络安全事件响应--应急响应(windows)

应用系统日志 Windows主要有以下三类日志记录系统事件&#xff1a;应用程序日志、系统日志和安全日志。 系统和应用程序日志存储着故障排除信息&#xff0c;对于系统管理员更为有用。安全日志记录着事件审计信息&#xff0c;包括用户验证&#xff08;登录、远程访问等&#x…

C++蓝桥杯基础篇(九)

片头 嗨&#xff01;小伙伴们&#xff0c;大家好~ 今天我们将学习蓝桥杯基础篇&#xff08;十&#xff09;&#xff0c;学习函数相关知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、函数基础 一个典型的函数定义包括以下部分&#xff1a;返回类型、函数名…

JVM内存结构笔记01-运行时数据区域

文章目录 前言运行时数据区域1.程序计数器定义特点总结 2.虚拟机栈2.1 定义局部变量表 ★操作数栈动态链接方法返回地址(方法出口) 2.2 栈内存溢出演示栈内存溢出 java.lang.StackOverflowError 2.3问题辨析1. 垃圾回收是否涉及栈内存&#xff1f;2. 栈内存分配越大越好吗&…

01-简单几步!在Windows上用llama.cpp运行DeepSeek-R1模型

1.llama.cpp介绍 Llama.cpp 是一个开源的、轻量级的项目&#xff0c;旨在实现 Meta 推出的开源大语言模型 Llama 的推理&#xff08;inference&#xff09;。Llama 是 Meta 在 2023 年开源的一个 70B 参数的高质量大语言模型&#xff0c;而 llama.cpp 是一个用 C 实现的轻量化…

对开源VLA sota π0的微调——如何基于各种开源数据集、以及你自己的私有数据集微调π0(含我司的微调实践)

前言 25年2.4日&#xff0c;几个月前推出π0的公司Physical Intelligence (π)宣布正式开源π0及π0-FAST&#xff0c;如之前所介绍的&#xff0c;他们对用超过 10,000 小时的机器人数据进行了预训练 该GitHub代码仓库「 π0及π0-FAST的GitHub地址&#xff1a;github.com/Ph…

Redis网络模型

redis为什么快 1.主要原因是因为redis是基于内存操作的&#xff0c;比起直接操作磁盘速度快好几倍 2.基于内存的数据库瓶颈主要是在网络io这一块&#xff0c;redis网络模型采用io多路复用技术能够高效的处理并发连接。 3.redis使用单线程执行命令&#xff0c;可以避免上下文…

PyTorch系列教程:Tensor.view() 方法详解

这篇简明扼要的文章是关于PyTorch中的tensor.view()方法的介绍与应用&#xff0c;与reshape()方法的区别&#xff0c;同时给出示例进行详细解释。 Tensor基础 Tensor(张量)的视图是一个新的Tensor&#xff0c;它与原始Tensor共享相同的底层数据&#xff0c;但具有不同的形状或…

Python数据分析之数据可视化

Python 数据分析重点知识点 本系列不同其他的知识点讲解&#xff0c;力求通过例子让新同学学习用法&#xff0c;帮助老同学快速回忆知识点 可视化系列&#xff1a; Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…

swift -(5) 汇编分析结构体、类的内存布局

一、结构体 在 Swift 标准库中&#xff0c;绝大多数的公开类型都是结构体&#xff0c;而枚举和类只占很小一部分 比如Bool、 Int、 Double、 String、 Array、 Dictionary等常见类型都是结构体 ① struct Date { ② var year: Int ③ var month: Int ④ …

推荐一个比较好的开源的工作流引擎

由于DeepSeek等AI大模型的出现&#xff0c;工作流模式再次流行起来&#xff0c;低代码甚至零代码就可以实现应用开发&#xff0c;而且有DeepSeek这样的超级AI作为大脑&#xff0c;人人都可以开发自动化工作流。 比如搭建邮件助手工作流&#xff0c;可以自动润色各种邮件内容。…

CarPlanner:用于自动驾驶大规模强化学习的一致性自回归轨迹规划

25年2月来自浙大和菜鸟网络的论文“CarPlanner: Consistent Auto-regressive Trajectory Planning for Large-scale Reinforcement Learning in Autonomous Driving”。 轨迹规划对于自动驾驶至关重要&#xff0c;可确保在复杂环境中安全高效地导航。虽然最近基于学习的方法&a…

Fedora41安装MySQL8.4.4

Fedora41安装MySQL8.4.4 Fedora41用yum仓库安装MySQL8.4.4 笔记250310下载安装启动mysqld服务查看生成的初始密码 , 用初始密码登录登录后,必须修改初始密码才能执行其它操作可选设置降低密码强度要求, 使用简单密码降低 validate_password 组件对密码强度的要求 用SET GLOBAL命…

信息安全意识之安全组织架构图

一、信息安全技术概论1.网络在当今社会中的重要作用2.信息安全的内涵 网络出现前&#xff1a;主要面向数据的安全&#xff0c;对信息的机密性、完整性和可用性的保护&#xff0c;即CIA三元组 网络出现后&#xff0c;还涵盖了面向用户的安全&#xff0c;即鉴别&#xff0c;授权&…

安卓Android与iOS设备管理对比:企业选择指南

目录 一、管理方式差异 Android Enterprise方案包含三种典型模式&#xff1a; Apple MDM方案主要提供两种模式&#xff1a; 二、安全防护能力 Android系统特点&#xff1a; 三、应用管理方案 四、设备选择建议 五、典型场景推荐 需求场景 推荐方案 六、决策建议要点…

linunx ubuntu24.04.02装libfuse2导致无法开机进不了桌面解决办法

osu.appimage运行需要libfuse2 然后我就下了fuse,打了两把第二天无法开机 这样是不能开机的 这样是可以开机的 解决办法一&#xff1a;玩星火商店的osu&#xff0c;好了问题解决 解决办法二&#xff1a; 在这个页面 ctrl alt f2进入tty6 sudo apt install ubuntu-desktop 进…

mysql-8.0.41-winx64 手动安装详细教程(2025版)

mysql-8.0.41-winx64 手动安装详细教程&#xff08;2025版&#xff09; 一、下载安装包二、配置环境变量三、安装配置四、启动 MySQL 服务&#xff0c;修改密码 一、下载安装包 安装地址如下&#xff1a; https://dev.mysql.com/downloads/mysql/使用7-zip或其他解压软件&…

wireguard搭配udp2raw部署内网

前言 上一篇写了使用 wireguard 可以非常轻松的进行组网部署&#xff0c;但是如果服务器厂商屏蔽了 udp 端口&#xff0c;那就没法了 针对 udp 被服务器厂商屏蔽的情况&#xff0c;需要使用一款 udp2raw 或 socat 类似的工具&#xff0c;来将 udp 打包成 tcp 进行通信 这里以…