🌍 丝滑前端国际化:React + i18next 六语言实战

我是 前端多语言国际化彭于晏,我喂自己袋盐

最近和同事做了一个小游戏网站,我来处理国际化,现在很多公司产品涉及到海外用户,前端国际化(i18n) 已经从"锦上添花"变成了"必备能力"。一套良好的国际化方案,不仅能提升用户体验,还能让产品更快适应不同国家和地区。

本文将基于 React + i18next ,带你从零到一实现 6 种语言国际化 (中文、英文、葡萄牙语、法语、韩语、日语),并配合 framer-motion 动画 打造丝滑的语言切换体验。


🧪 为什么要用国际化?

场景 说明
产品出海 面向不同地区(北美、巴西、印度等),需要多语言支持
用户良好体验 用户看到的是自己熟悉的语言,降低使用门槛
市场推广与增长 本地化内容更容易被接受,增加留存与转化
团队协作与维护 统一的多语言文件管理方式,开发和运营团队都能快速调整文案

⚙️ 国际化方案选型

在 React 生态中,国际化的主流方案是 i18next ,它配合 react-i18nexti18next-browser-languagedetector 可以快速完成需求。

  • i18next:核心国际化引擎
  • react-i18next:React 适配层
  • i18next-browser-languagedetector:自动嗅探浏览器语言
  • framer-motion :丝滑的切换动画库 👉 官方文档

🛠️ 初始化项目国际化配置

1️⃣ 安装依赖

r 复制代码
npm install i18next react-i18next i18next-browser-languagedetector

2️⃣ 创建国际化文件

在项目根目录下新增 locales 文件夹,结构如下:

复制代码
locales/
  ├── i18n.ts
  ├── en.json
  ├── pt-BR.json
  ├── zh-CN.json
  ├── fr.json
  ├── ja.json
  └── ko.json

3️⃣ 配置 i18n.ts

javascript 复制代码
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

import en from "./en.json";
import pt_BR from "./pt-BR.json";
import zh_CN from "./zh-CN.json";
import fr from "./fr.json";
import ja from "./ja.json";
import ko from "./ko.json";
import store from "@/store";

const { app: { language } = {} } = store.getState();

const resources = {
  en: { translation: en },
  "pt-BR": { translation: pt_BR },
  "zh-CN": { translation: zh_CN },
  fr: { translation: fr },
  ja: { translation: ja },
  ko: { translation: ko },
};

i18n
  .use(LanguageDetector) // 自动检测浏览器语言
  .use(initReactI18next) // 集成到 React
  .init({
    resources,
    fallbackLng: language || "en", // 默认语言
    interpolation: { escapeValue: false }, // 不转义 HTML
  });

export default i18n;

💡 小技巧 :定义语言时要遵循 BCP-47 标准 ,因为 LanguageDetector 返回的就是这种格式。例如 zh-CNpt-BR


🔄 语言切换组件

我们需要一个下拉菜单来切换语言,并且配合 framer-motion 实现动画效果。

ini 复制代码
"use client";
import { changeLanguage } from "@/store/app/appSlice";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import { useAppDispatch, useAppSelector } from "@/store/hooks";
import { CDN } from "@/utils/env";
import i18n from "@/locales/i18n";

export default function LangDropdown() {
  const LANG_OPTIONS = [
    { code: "en", label: "English" },
    { code: "pt-BR", label: "Português" },
    { code: "fr", label: "Français" },
    { code: "ko", label: "한국어" },
    { code: "ja", label: "日本語" },
    { code: "zh-CN", label: "中文" },
  ];

  const dispatch = useAppDispatch();
  const selected = useAppSelector((state) => state.app.language);
  const [open, setOpen] = useState(false);

  return (
    <div className="relative my-[12px]">
      <div
        onClick={() => setOpen((prev) => !prev)}
        className="p-[8px] border border-solid border-[rgba(255,255,255,0.40)] h-[32px] rounded-[8px] hover:bg-[#8138ff27] text-white text-[14px] flex justify-between items-center cursor-pointer !select-none"
      >
        {LANG_OPTIONS.find((i) => i.code === selected)?.label}
        <span className="ml-2">
          <img
            className={`size-[18px] transition-transform duration-200 ${
              open ? "rotate-180" : "rotate-0"
            }`}
            src={`${CDN}images/arrow-down.svg`}
          />
        </span>
      </div>

      <AnimatePresence>
        {open && (
          <motion.ul
            initial={{ opacity: 0, scaleY: 0, originY: 1 }}
            animate={{ opacity: 1, scaleY: 1, originY: 1 }}
            exit={{ opacity: 0, scaleY: 0, originY: 0 }}
            transition={{ duration: 0.2 }}
            className="absolute bottom-full left-0 mb-2 w-full bg-[#332F48] rounded-[8px] max-h-[400px] shadow-lg overflow-y-auto z-50 "
          >
            {LANG_OPTIONS.map((lang) => (
              <li
                key={lang.code}
                className={`text-[14px] p-[8px] text-white hover:bg-[#8138ff27] cursor-pointer ${
                  lang.code === selected ? "bg-[#8138FF]" : ""
                }`}
                onClick={() => {
                  dispatch(changeLanguage(lang.code));
                  i18n.changeLanguage(lang.code);
                  setOpen(false);
                }}
              >
                {lang.label}
              </li>
            ))}
          </motion.ul>
        )}
      </AnimatePresence>
    </div>
  );
}

✨ 亮点:

  • AnimatePresence + motion.ul → 丝滑展开收起
  • rotate-180 → 下拉箭头旋转动画
  • Redux 状态同步 → 全局保存语言选择

效果图


📖 文案配置与使用

所有翻译统一放在 json 文件里,不同语言使用相同的 key。

en.json

json 复制代码
{
  "About": "About",
  "FAQ": "FAQ",
  "Feedback about us": "Feedback about us"
}

zh-CN.json

json 复制代码
{
  "About": "关于",
  "FAQ": "常见问题",
  "Feedback about us": "关于我们的反馈"
}

使用方式

javascript 复制代码
import { useTranslation } from "react-i18next";
const { t } = useTranslation();

function Footer() {
  return (
    <div>
      <span>{t("About")}</span> | <span>{t("FAQ")}</span>
    </div>
  );
}

🔚 总结

关键点 描述
为什么要国际化 产品出海、用户体验、本地化适配
主要依赖库 i18nextreact-i18nexti18next-browser-languagedetector
语言文件规范 使用 BCP-47 语言码(如 zh-CNpt-BR
动画优化 framer-motion 提供丝滑切换体验
使用方式 useTranslation() + JSON key-value

至此,前端国际化彭于晏6 种语言国际化配置和切换功能就完成了。是不是觉得很 easy? 🚀

相关推荐
LaiYoung_3 分钟前
前端国际化适配提速 90%!这款 JS 脚本 CLI 工具,自动提中文、分模块、做替换,比 AI 更稳定
前端·javascript·人工智能
低代码布道师16 分钟前
CSS 伪类与伪元素:深度解析
前端·css
星月前端21 分钟前
css3元素倒影效果属性:box-reflect
前端·css·css3
vivo互联网技术28 分钟前
微信小程序端智能项目工程化实践
前端·人工智能·微信小程序·端智能
前端灵派派30 分钟前
openlayer轨迹回放
前端
Trust yourself24336 分钟前
如何获取easy-ui的表格的分页大小
前端·javascript·ui·oracle
zz-zjx43 分钟前
shell编程从0基础--进阶 1
linux·运维·前端·chrome·bash
濮水大叔1 小时前
能够动态推断与生成DTO是Node生态的一个重要里程碑
前端·typescript·node.js