用i18next使你的应用国际化-React

ref: https://www.i18next.com/

i18next是一个用JavaScript编写的国际化框架。

i18next为您提供了一个完整的解决方案,本地化您的产品从web端到移动端和桌面端。

在react项目中安i18next依赖:

  • i18next
  • react-i18next
  • i18next-browser-languagedetector,用于检测用户语言
npm install i18next react-i18next i18next-browser-languagedetector

创建i18n.js文件:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(LanguageDetector) // 检测用户语言
  .use(initReactI18next) // 将i18n实例传递给react-i18next
  .init({ // 初始化 i18next
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // React已经转义了
    },
    resources: {
      en: {
        translation: {
          // 此处放置翻译内容
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          }
        }
      }
    }
  });

export default i18n;

index.js中导入i18n.js

React>=18.0.0的版本:

import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';

// 导入 i18n (需要绑定)
import './i18n';

const root = createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

更早的版本:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

// 导入 i18n (需要绑定)
import './i18n';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

使用:在第一个文本中使用了Trans组件,在第二个文本中使用了useTranslation (hook) :

// App.js
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';

function App() {
  const { t } = useTranslation();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

语言切换器

// App.jsx
...

const lngs = {
  en: { nativeName: 'English' },
  de: { nativeName: 'Deutsch' }
};

function App() {
...
    <img src={logo} className="App-logo" alt="logo" />
    <div>
      {Object.keys(lngs).map((lng) => (
        <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => i18n.changeLanguage(lng)}>
          {lngs[lng].nativeName}
        </button>
      ))}
    </div>
    <p>
      <Trans i18nKey="description.part1">
        Edit <code>src/App.js</code> and save to reload.
      </Trans>
    </p>
...
}

react language switcher

自i18next v21以来,i18next. resolvedlanguage被设置为当前解析的语言,并且可以用作主要使用的语言,上例中有使用。

i18next.language vs. i18next.languages
i18next.language;
// 设置为当前检测或设置的语言

i18next.languages;
// 设置为将用于查找翻译值的语言代码数组
// 设置语言后,将使用新的语言代码填充此数组
// 除非被覆盖,否则将使用该代码的不太特定的版本填充此数组,以用于回退目的,然后是回退语言列表

// 初始化回退语言
i18next.init({
    fallbackLng: ["es", "fr", "en-US", "dev"]
});
// 改变语言
i18next.changeLanguage("en-US-xx");
// 新语言和它的更一般的形式,然后是回退
i18next.languages; // ["en-US-xx", "en-US", "en", "es", "fr", "dev"]
// 再次改变语言
i18next.changeLanguage("de-DE");
// 不保留上一次设置的语言
i18next.languages; // ["de-DE", "de", "es", "fr", "en-US", "dev"]

处理复数和插值

示例:计算语言变化的次数

// App.js
...
import { useState } from 'react';

...

function App() {
  ...
  const [count, setCounter] = useState(0);

  return (
    ...
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
              i18n.changeLanguage(lng);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
   ...
  );
}

export default App;

拓展翻译源:

// i18n.js
...

...
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times'
        }
      },
      de: {
        translation: {
          description: {
            part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt'
        }
      }
    }

...

i18next会根据count数值选择正确的复数格式,了解更多 - Plurals

react pluralization

处理多种复数:

// 翻译源:
{
  "key_zero": "zero",
  "key_one": "singular",
  "key_two": "two",
  "key_few": "few",
  "key_many": "many",
  "key_other": "other"
}

// 使用:
t('key', {count: 0}); // -> "zero"
t('key', {count: 1}); // -> "singular"
t('key', {count: 2}); // -> "two"
t('key', {count: 3}); // -> "few"
t('key', {count: 4}); // -> "few"
t('key', {count: 5}); // -> "few"
t('key', {count: 11}); // -> "many"
t('key', {count: 99}); // -> "many"
t('key', {count: 100}); // -> "other"

这些复数是用Intl API简化的。你可能需要Polyfill Intl.PluralRules的API,如果它不可用,它将退回到i18next JSON格式v3复数处理。

注:变量名必须是count并且必须存在:i18next.t('key', {count: 1});

polyfill: 只需导入它以确保Intl.PluralRules在您的环境中可用:

npm install intl-pluralrules
import 'intl-pluralrules'

如果Intl.PluralRules已经存在,包含一个selectRange()方法,并且支持多种语言环境,那么将不会加载polyfill。

格式化

在i18next和Luxon的帮助下使用不同的日期格式来处理日期和时间。

npm install luxon

创建一个Footer组件并在App.js中引用:

import './Footer.css';

const Footer = ({ t }) => (
  <div className="Footer">
    <div>{t('footer.date', { date: new Date() })}</div>
  </div>
);

export default Footer;

// 在App.js中引入并这样使用
// <Footer t={t} />

导入luxon并定义一个格式函数,如文档中所述,并添加新的翻译key:

...
import { DateTime } from 'luxon';

...
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
      // format: (value, format, lng) => { // 遗留使用方法
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    resources: {
      en: {
        translation: {
          ...
          footer: {
            date: 'Today is {{date, DATE_HUGE}}'
          }
        }
      },
      de: {
        translation: {
          ...
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

// 新的使用方法
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

English:

react english

German:

react german

Context

基于当前时间的特定问候信息,即早上、晚上等。

创建一个getGreetingTime函数,并使用结果作为页脚翻译的内容:

// Footer.jsx
import { DateTime } from 'luxon';
import './Footer.css';

const getGreetingTime = (d = DateTime.now()) => {
	const split_afternoon = 12; // 24小时制分配下午
	const split_evening = 17; // 24小时制分配晚上
	const currentHour = parseFloat(d.toFormat('hh'));

	if (currentHour >= split_afternoon && currentHour <= split_evening) {
		return 'afternoon';
	} else if (currentHour >= split_evening) {
		return 'evening';
  }
	return 'morning';
}

const Footer = ({ t }) => (
  <div className="Footer">
    <div>{t('footer.date', { date: new Date(), context: getGreetingTime() })}</div>
  </div>
);

export default Footer;

添加i18next-http-backend插件(从服务器加载翻译)和翻译key:

npm install i18next-http-backend
...
import Backend from 'i18next-http-backend';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    ...
    resources: {
      en: {
        translation: {
          ...
          footer: {
            date: 'Today is {{date, DATE_HUGE}}',
            date_morning: 'Good morning! Today is {{date, DATE_HUGE}} | Have a nice day!',
            date_afternoon: 'Good afternoon! It\'s {{date, DATE_HUGE}}',
            date_evening: 'Good evening! Today was the {{date, DATE_HUGE}}'
          }
        }
      },
      de: {
        translation: {
          ...
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}',
            date_morning: 'Guten Morgen! Heute ist {{date, DATE_HUGE}} | Wünsche einen schönen Tag!',
            date_afternoon: 'Guten Tag! Es ist {{date, DATE_HUGE}}',
            date_evening: 'Guten Abend! Heute war {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

...

效果:

react translations

分离翻译和代码

在i18next-http-backend的帮助下将翻译从代码中分离出来,并将它们放在专用的json文件中。

将翻译文件移动到public文件夹:

public locales

移除翻译代码:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
      // format: (value, format, lng) => {
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    }
  });

i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

现在翻译是异步加载的啦,所以用一个Subspense组件包装你的应用来确保你防止以下错误:

Uncaught Error: App suspended while rendering, but no fallback UI was specified.

// App.jsx
import { Suspense } from 'react';

function App() {
  ...
}

// 在这里,app从页面捕获suspense,以防翻译未加载完成
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}

如果你想支持一种新的语言,你只需要创建一个新的文件夹和一个新的翻译json文件。这使您可以直接将翻译json发送给一些专业的翻译人员。或者,如果您正在使用翻译管理系统,您可以使用cli同步文件。

具体代码可在这里查看。

多个namespaces

将翻译拆分为多个文件,使用:在调用useTranslation时指定它

const { t } = useTranslation(['translation', 'common']);
// ...
// t('look.deep', { ns: 'common' })

[withTranslation](withTranslation (HOC) - react-i18next documentation):

withTranslation(['translation', 'common'])(MyComponent);
// ...
// t('look.deep', { ns: 'common' })

[Translation](Translation (render prop) - react-i18next documentation):

<Translation ns={['translation', 'common']}>
{
  (t) => <p>{t('look.deep', { ns: 'common' })}</p>
}
</Translation>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python学习时与chatgpt4对话的一些感悟

今天学SCENIC教程&#xff0c;看到里面有一句不是很懂 If you run this from a python script instead of a Jupyter notebook, please enclose the code in a if __name__ __main__: construct. 现在把和chatgpt4问答的内容发上来&#xff0c;确实是很厉害 没有太看懂&…

QT中日期和时间类

QT中日期和时间类 QDateQTimeQDateTime QDate QDate类可以封装日期信息也可以通过这个类得到日期相关的信息, 包括:年, 月, 日。 // 构造函数 QDate::QDate(); QDate::QDate(int y, int m, int d);// 公共成员函数 // 重新设置日期对象中的日期 bool QDate::setDate(int year…

数据结构---LRU CACHE

什么是LRU 通过之前的学习我们知道计算机在处理任务的时候是先将数据从硬盘中提取出来加载进内存&#xff0c;然后再将内存中的数据加载进入cpu进行计算&#xff0c;但是这里存在一个问题cpu的计算速度非常快&#xff0c;而内存中加载数据的速度又很慢&#xff0c;所以为了提供…

【数据仓库】Apache Hive初体验

为什么使用Hive&#xff1f; 使用Hadoop MapReduce直接处理数据所面临的问题&#xff1a; 人员学习成本太高需要掌握ava语言MapReduce实现&#xff0c;复杂查询逻辑开发难度太大&#xff01; 1&#xff0c;使用Hive处理数据的好处操作接口采用类SQL语法&#xff0c;提供快速开发…

Vue学习Day3——生命周期\组件化

一、Vue生命周期 Vue生命周期&#xff1a;就是一个Vue实例从创建 到 销毁 的整个过程。 生命周期四个阶段&#xff1a;① 创建 ② 挂载 ③ 更新 ④ 销毁 1.创建阶段&#xff1a;创建响应式数据 2.挂载阶段&#xff1a;渲染模板 3.更新阶段&#xff1a;修改数据&#xff0c;更…

C#多线程

C#多线程 C#多线程是C#学习中必不可少的知识&#xff0c;在实际开发中也能有效的提升用户体验&#xff0c;和程序性能。 文章目录 C#多线程前言一、什么是线程、什么是进程、什么是协程&#xff1f;协程优点缺点 线程优点缺点&#xff1a; 进程优点缺点&#xff1a; 二、C# 中…

FANUC机器人SRVO-217故障报警原因分析及参考解决办法

FANUC机器人SRVO-217故障报警原因分析及参考解决办法 如下图所示,示教器提示:SRVO-217紧急停止电路板未找到, 查阅手册可以看到以下的报警说明: 故障原因: 通电时未能识别紧急停止电路板或者增设的安全I/O装置。连接有多个安全I/O装置的系统中,在报警信息的最后,会显示发…

Redis实战(3)——缓存模型与缓存更新策略

1 什么是缓存? 缓存就是数据交换的缓冲区&#xff0c; 是存贮数据的临时区&#xff0c;一般读写性能较高 \textcolor{red}{是存贮数据的临时区&#xff0c;一般读写性能较高} 是存贮数据的临时区&#xff0c;一般读写性能较高。缓存可在多个场景下使用 以一次 w e b 请求为例…

如何少走弯路?蚓链助力零售企业实现数字化转型

基于大环境下的数据驱动&#xff0c;创新业务模式成为了后疫情时代下零售企业冲破困局、拓展业务的必然趋势&#xff0c;新零售概念应运而生。新零售结合数字化应用技术为传统零售企业打造线上营销生态链&#xff0c;帮助企业积累数据&#xff0c;盘活数据实现更大营收价值。 …

微信读书:长期投资(阅读摘录)

微信读书&#xff1a;长期投资&#xff08;阅读摘录&#xff09; 所有投资高手的时间精力都投向了这三大块&#xff1a;行动、思考、读书。 我们把耐心发挥到了极致&#xff0c;这正是价值投资的关键特征之一。 通常在牛市中想要跑赢大盘&#xff0c;难度非常大。 实际上&am…

QTday2信号和槽

点击登录按钮,关闭Widget登录窗口,打开QQList窗口 widget.cpp #include "widget.h"void my_setupUI(Widget *w);Widget::Widget(QWidget *parent): QWidget(parent) {my_setupUI(this); }Widget::~Widget() { }void Widget::login_slots() {//fixemit jump_signal(…

飞行动力学 - 第15节-part 1-操纵力与铰链力矩 之 基础点摘要

飞行动力学 - 第15节-part 1-操纵力与铰链力矩 之 基础点摘要 1. HOTAS全拼2. 操纵杆力&铰链力矩3. 铰链力矩4. 气动补偿&#xff08;Aerodynamic Balancing&#xff09;5. 参考资料 1. HOTAS全拼 Hands On Throttle And Stick 2. 操纵杆力&铰链力矩 操纵杆力&#…

Vue2 第三节 数据代理和事件处理

1.Object.defineProperty 方法 2.数据代理 3.Vue中的数据代理 4.事件的基本使用 5.事件修饰符 6.键盘事件 一.Object.defineProperty 方法 &#xff08;1&#xff09;学习Object.defineProperty为下一节数据代理做准备 &#xff08;2&#xff09;更加高级的给对象添加属…

【LeetCode】383. 赎金信

题目&#xff1a;383. 赎金信 由于此题只含有小写字母,并且magazine里面的字母不可重复使用. 故首先用一个长度为26的整形数组记录magazine里字母出现的次数。 再用这个整形数组跟ransomeNote进行遍历比较&#xff0c;当数组中出现-1时&#xff0c;说明false,否则true. 代码&am…

智慧园区安保人员巡更巡检解决方案,蓝牙信标主动式蓝牙定位导航系统

一、需求分析 目前&#xff0c;大部分写字楼&#xff0c;工厂&#xff0c;学校&#xff0c;银行&#xff0c;车站等场景对安保人员的管理依然靠手填单子记录作业情况&#xff0c;在缺乏信息化手段的情况下&#xff0c;靠人员自觉性或者RFID巡更棒&#xff0c;在这些传统方式下…

el-table数据处理

在写表格时遇到&#xff0c;后端返回的数据是对象&#xff0c;并且缺少字段 1.每一条数据加上 一个字段 2.将对象转成数组 以下是数据 {"groupA": {"groupName": null,"orgName": null,"orgId": null,"allPeoper": &quo…

libcomposite: Unknown symbol config_group_init (err 0)

加载libcomposite.ko 失败 问题描述 如图&#xff0c;在做USB OTG 设备模式的时候需要用到libcomposite.ko驱动&#xff0c;加载失败了。 原因&解决方法 有一个依赖叫configfs.ko的驱动没有安装。可以从内核代码的fs/configfs/configfs.ko中找到这个驱动。先加载confi…

从零开始理解Linux中断架构(23)中断运行临界区和占先调度

Linux在内核中定义了6种运行临界区。 in_interrupt in_interrupt在驱动中使用频率最高的函数了,in_interrupt()就是指示Core是否正在中断处理中,包含了硬中断,软中断运行临界区。如果在中断处理中,则不能调用__do_softirq执行软中断处理。硬中断中不可调度不可中断,所有…

【NLP】温和解读:transformer的核心思想

变压器模型及其关键组件的概述。 一、介绍 在这篇博文中&#xff0c;我将讨论本世纪最具革命性的论文“注意力是你所需要的一切”&#xff08;Vaswani et al.&#xff09;。首先&#xff0c;我将介绍自我注意机制&#xff0c;然后介绍变形金刚的架构细节。在之前的博客文章《从…

STM32 SPI学习

SPI 串行外设设备接口&#xff08;Serial Peripheral Interface&#xff09;&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线。 SCK时钟信号由主机发出。 SPI接口主要应用在存储芯片。 SPI相关引脚&#xff1a;MOSI&#xff08;输出数据线&#xff…