🌍 丝滑前端国际化: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? 🚀

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax