当前位置: 首页 > news >正文

前端国际化实战:从需求到落地的完整实践

"我们要开拓东南亚市场了!"产品经理小王兴奋地告诉我这个消息。作为技术负责人,我立刻意识到这意味着我们需要对整个系统进行国际化改造。说实话,虽然之前也做过一些多语言的项目,但面对一个正在运行的大型系统,国际化改造的挑战还是不小。

回想起上周的需求评审会,我们讨论了很多细节问题:不同语言的排版布局、日期时间格式、货币单位转换等等。经过一番头脑风暴,我们制定了一个详细的改造计划。今天就来分享这个过程中的实战经验。

需求分析

首先,我们需要明确国际化的具体需求。通过和产品、运营的深入沟通,我们确定了几个关键点:

第一个挑战是语言切换。系统要支持英语、泰语、越南语和印尼语,而且要能够根据用户的浏览器设置自动选择默认语言。"用户第一次访问时,要让他感觉这就是为他准备的。"产品经理特别强调这一点。

第二个挑战是内容适配。不同语言的文字长度差异很大,比如英文的"Submit"翻译成泰语后会长很多。而且有些语言还有特殊的书写方向和字体要求。

第三个挑战是本地化处理。日期、时间、货币这些格式在不同地区都有各自的规范。"不能让用户看到 2024/12/3,他们习惯的可能是 3/12/2024。"运营同学提醒道。

技术方案

经过调研,我们设计了一个灵活的国际化方案。就像搭建一个多语言的图书馆,我们需要有清晰的分类系统(语言配置),便捷的检索方式(语言切换),以及统一的管理制度(翻译流程)。

首先是语言包的组织方式:

// 按功能模块划分语言包
const messages = {en: {common: {submit: 'Submit',cancel: 'Cancel',confirm: 'Confirm'},auth: {login: 'Log In',register: 'Sign Up',forgotPassword: 'Forgot Password?'},dashboard: {welcome: 'Welcome back, {name}',totalUsers: '{count} users',activeToday: '{count} active today'}},th: {common: {submit: 'ส่ง',cancel: 'ยกเลิก',confirm: 'ยืนยัน'}// ... 其他泰语翻译}
}

然后是语言切换的核心逻辑:

// hooks/useI18n.ts
function useI18n() {const [locale, setLocale] = useState(() => {// 优先使用用户设置的语言const savedLocale = localStorage.getItem('locale')if (savedLocale) return savedLocale// 其次使用浏览器语言const browserLocale = navigator.language.split('-')[0]return supportedLocales.includes(browserLocale) ? browserLocale : 'en'})const formatMessage = useCallback((key: string, values?: Record<string, any>) => {const template = get(messages[locale], key, key)if (!values) return templatereturn template.replace(/\{(\w+)\}/g, (_, key) => values[key] || '')},[locale])const changeLocale = useCallback((newLocale: string) => {if (!supportedLocales.includes(newLocale)) returnsetLocale(newLocale)localStorage.setItem('locale', newLocale)// 更新 HTML 的 lang 属性document.documentElement.lang = newLocale// 更新 moment 的语言设置moment.locale(newLocale)}, [])return { locale, formatMessage, changeLocale }
}

对于日期和货币的处理,我们使用了专门的库:

// utils/formatter.ts
import { format } from 'date-fns'
import * as locales from 'date-fns/locale'export function formatDate(date: Date, locale: string) {const dateLocale = locales[locale] || locales.enUSreturn format(date, 'PPP', { locale: dateLocale })
}export function formatCurrency(amount: number, locale: string) {return new Intl.NumberFormat(locale, {style: 'currency',currency: getCurrencyByLocale(locale)}).format(amount)
}// 根据语言获取对应的货币
function getCurrencyByLocale(locale: string) {const currencyMap = {en: 'USD',th: 'THB',vi: 'VND',id: 'IDR'}return currencyMap[locale] || 'USD'
}

实践细节

在实际开发中,我们遇到了一些有趣的挑战。比如泰语的字体渲染问题,我们通过动态加载字体来解决:

// 动态加载字体
const loadFont = async (locale: string) => {const fontMap = {th: 'https://fonts.googleapis.com/css2?family=Noto+Sans+Thai&display=swap',vi: 'https://fonts.googleapis.com/css2?family=Noto+Sans+Vietnamese&display=swap'}if (!fontMap[locale]) returnconst link = document.createElement('link')link.rel = 'stylesheet'link.href = fontMap[locale]document.head.appendChild(link)
}

为了提高翻译的效率,我们开发了一个翻译管理平台,支持在线编辑和自动同步:

// 翻译同步服务
async function syncTranslations() {// 获取所有需要翻译的文本const texts = await extractTextsFromCode()// 对比已有翻译,找出缺失的部分const missingTranslations = findMissingTranslations(texts)// 使用翻译服务进行翻译const translations = await translateTexts(missingTranslations)// 更新语言包await updateLanguageFiles(translations)
}

效果验证

改造完成后,我们进行了全面的测试:

  • 不同语言环境下的页面布局
  • 各种日期和货币格式的显示
  • 动态切换语言的性能
  • 特殊字符的渲染

最让我印象深刻的是一位泰国用户的反馈:"感觉就像在用本地开发的应用一样自然。"这正是我们想要达到的效果。

经验总结

国际化改造的过程让我们学到了很多。就像装修一座老房子,你需要在不影响居住的情况下,把每个房间都改造成适合不同人居住的样子。这个过程需要:

细致的规划 - 就像要先确定每个房间的用途灵活的设计 - 能适应不同人的生活习惯周到的考虑 - 照顾到每个细节的体验

写在最后

前端国际化不仅仅是翻译文本,更是一次全方位的用户体验提升。正如那句话说的:"Think globally, act locally"(全球思维,本地行动),我们要在保持产品统一性的同时,让每个地区的用户都能获得最自然的使用体验。

有什么问题欢迎在评论区讨论,让我们一起探讨国际化实践的经验!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

相关文章:

前端国际化实战:从需求到落地的完整实践

"我们要开拓东南亚市场了&#xff01;"产品经理小王兴奋地告诉我这个消息。作为技术负责人,我立刻意识到这意味着我们需要对整个系统进行国际化改造。说实话,虽然之前也做过一些多语言的项目,但面对一个正在运行的大型系统,国际化改造的挑战还是不小。 回想起上周的…...

React的状态管理库-Redux

核心思想&#xff1a;单一数据源、状态是只读的、以及使用纯函数更新状态。 组成部分 Store&#xff08;存储&#xff09; 应用的唯一状态容器&#xff0c;存储整个应用的状态树,使用 createStore() 创建。 getState()&#xff1a;获取当前状态。dispatch(action)&#xff…...

【Android学习】RxJava

文章目录 资料连接1. Merge & Zip操作符: 合并数据源2. Map & FlapMap & ConcatMap & Buffer: 变换操作符3. retry & retryUntil & retryWhen : 错误处理操作符4. Transformer & Compose 转换符 资料连接 Android RxJava&#xff1a; 这是一份全面…...

Pycharm访问MySQL数据库·上

1.MySQL驱动模块Connector #导入数据库的驱动工具 import mysql.connector #连接数据库必备的条件 config {"host": "localhost","port": 3306,"user": "root","password": "888888","database&…...

【CUDA】CUBLAS

【CUDA】CUBLAS 在深入了解之前&#xff0c;提前运行预热&#xff08;warmup&#xff09;和基准测试&#xff08;benchmark runs&#xff09; 是获得准确执行时间的关键。如果不进行预热运行&#xff0c;cuBLAS 的首次运行会有较大的开销&#xff08;大约 45 毫秒&#xff09;…...

YOLOv8-ultralytics-8.2.103部分代码阅读笔记-predict.py

predict.py ultralytics\models\yolo\detect\predict.py 目录 predict.py 1.所需的库和模块 2.class DetectionPredictor(BasePredictor): 1.所需的库和模块 # Ultralytics YOLO &#x1f680;, AGPL-3.0 licensefrom ultralytics.engine.predictor import BasePredicto…...

细说Flash存储芯片W25Q128FW和W25Q16BV

目录 一、Flash存储芯片W25Q128FW 1、W25Q128硬件接口和连接 2、存储空间划分 3、数据读写的原则 4、操作指令 &#xff08;1&#xff09;“写使能”指令 &#xff08;2&#xff09;“读数据”指令 &#xff08;3&#xff09;“写数据”指令 5、状态寄存器SR1 二、Fl…...

python爬虫--小白篇【爬取B站视频】

目录 一、任务分析 二、网页分析 三、任务实现 一、任务分析 将B站视频爬取并保存到本地&#xff0c;经过分析可知可以分为四个步骤&#xff0c;分别是&#xff1a; 爬取视频页的网页源代码&#xff1b;提取视频和音频的播放地址&#xff1b;下载并保存视频和音频&#x…...

Three.js入门-模型加载

Three.js 支持多种 3D 模型格式&#xff0c;每种格式有其独特的优势和适用场景。根据项目的需求&#xff0c;选择合适的格式可以提高开发效率和用户体验。下面将详细介绍几种常见的模型格式及其特点&#xff0c;并补充每种格式的典型使用场景。 支持的模型类型及特点 Three.j…...

ECharts实现数据可视化入门详解

文章目录 ECharts实现数据可视化入门详解一、引言二、基础配置1.1、代码示例 三、动态数据与交互2.1、代码示例 四、高级用法1、多图表组合1.1、在同一容器中绘制多个图表1.2、创建多个容器并分别初始化 ECharts 实例1.3、实现多图联动 五、总结 ECharts实现数据可视化入门详解…...

C++(举例说明类的实例化方式)

太多的信息会让你抓不住重点&#xff0c;下面通过间短的举例说明了类的几种实例化方式&#xff0c;熟悉以后再阅读代码的时候就能减少疑惑。 1.直接实例化&#xff1a;使用类名直接实例化对象 MyClass obj; 2.使用 new 关键字动态分配内存&#xff1a;使用 new 关键字来在堆上…...

LeetCode32. 最长有效括号(2024冬季每日一题 32)

给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 示例 1&#xff1a; 输入&#xff1a;s “(()” 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 “()” 示例 2&#xff1a; 输入&#xff1a;s “…...

Textfocals ——基于大言模型的用户驱动型文本改进工具让用户在审阅自己的写作时对其进行修改

概述 论文地址&#xff1a;https://arxiv.org/abs/2403.01055 大规模语言模型可以生成媲美专业作家撰写的文本。目前使用的对话技术主要有两种&#xff1a;一种是交互式&#xff08;如 OpenAI 的 ChatGPT 和 Google 的 Gemini&#xff09;&#xff0c;另一种是预测性文本补全&…...

docker 部署 redis

docker 部署 redis 1. 下载 redis 镜像 # docker images | grep redis bitnami/redis 7.2.4-debian-11-r5 45de196aef7e 10 months ago 95.2MB2. docker-compose 部署 version: "3" services:redis:image: bitnami/redis:7.2.4-debian-11-…...

微信小程序横屏页面跳转后,自定义navbar样式跑了?

文章目录 问题原因&#xff1a;解决方案&#xff1a; 今天刚遇到的问题&#xff0c;横屏的页面完成操作后跳转页面后&#xff0c;自定义的tabbar样式乱了&#xff0c;跑到最顶了&#xff0c;真机调试后发现navbar跑到手机状态栏了&#xff0c;它正常应该跟右边胶囊一行。 知道问…...

回归预测 | MATLAB实现BiGRU(双向门控循环单元)多输入单输出

回归预测 | MATLAB实现BiGRU(双向门控循环单元)多输入单输出 文章目录 回归预测 | MATLAB实现BiGRU(双向门控循环单元)多输入单输出预测效果基本介绍程序设计参考资料致谢预测效果 基本介绍 BiGRU(双向门控循环单元)多输入单输出模型是一种结合了双向门控循环单元(BiGRU)的…...

智能时代的基石:神经网络

智能时代的基石&#xff1a;神经网络 第一节&#xff1a;神经网络简介 课程目标 本节课程旨在全面介绍神经网络的基本概念、结构以及其在历史发展中的重要里程碑。通过深入理解神经网络的工作原理和演变过程&#xff0c;学员将能够掌握神经网络在现实世界中的多种应用&#…...

红与黑,,

有一间长方形的房子&#xff0c;地上铺了红色、黑色两种颜色的正方形瓷砖。 你站在其中一块黑色的瓷砖上&#xff0c;只能向相邻&#xff08;上下左右四个方向&#xff09;的黑色瓷砖移动。 请写一个程序&#xff0c;计算你总共能够到达多少块黑色的瓷砖。 输入格式 输入包…...

嵌入式驱动开发详解16(音频驱动开发)

文章目录 前言WM8960简介I2S协议接口说明 SAI音频接口简介驱动框架简介设备树配置内核使能声卡设置与测试 后续参考文献 前言 该专栏主要是讲解嵌入式相关的驱动开发&#xff0c;但是由于ALSA驱动框架过于复杂&#xff0c;实现音频编解码芯片的驱动不是一个人能完成的&#xf…...

【嵌入式软件】跑开发板的前置服务配置

在嵌入式开发中,通常需要在 开发板和主机之间共享、传输和挂载文件。 这篇文章是关于如何在 Ubuntu 中配置 Samba、TFTP 和 NFS 协议的详细步骤。这些协议分别用于远程文件共享、文件传输和内核挂载文件系统。 如何安装协议: 参考:ubuntu18配置:详细的内容我手写了一份文档。…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...