MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React组件的国际化与本地化

2021-08-016.1k 阅读

一、React 国际化与本地化简介

在当今全球化的互联网环境下,前端应用需要支持多种语言和地区设置,以满足不同用户群体的需求。React 作为主流的前端框架,提供了丰富的工具和方法来实现组件的国际化(Internationalization,简称 i18n)与本地化(Localization,简称 l10n)。

国际化主要涉及到将应用的文本内容、日期、时间、数字等可本地化的元素进行抽象和提取,使得应用能够根据用户的语言偏好动态加载相应的翻译文本。本地化则侧重于根据不同地区的文化、习惯等对应用进行定制,比如日期格式、货币显示、排序规则等。

二、React 国际化库介绍

  1. react - i18next
    • 概述:react - i18next 是 React 应用中最常用的国际化库之一。它基于 i18next 库,提供了与 React 集成的高阶组件(HOC)和钩子(Hook),方便在 React 组件中进行国际化操作。
    • 安装:可以通过 npm 或 yarn 安装。
npm install react - i18next i18next
# 或者
yarn add react - i18next i18next
- **基本使用**:
import React from'react';
import { useTranslation } from'react - i18next';

function MyComponent() {
    const { t, i18n } = useTranslation();
    return (
        <div>
            <p>{t('welcomeMessage')}</p>
            <button onClick={() => i18n.changeLanguage('fr')}>Switch to French</button>
        </div>
    );
}

export default MyComponent;

在上述代码中,useTranslation 钩子用于获取翻译函数 t 和 i18next 实例 i18nt 函数用于翻译文本,i18n.changeLanguage 方法用于切换语言。

  1. react - intl
    • 概述:react - intl 是由 FormatJS 团队开发的用于 React 应用国际化的库。它专注于格式化日期、时间、数字和货币等,同时也支持文本翻译。
    • 安装
npm install react - intl
# 或者
yarn add react - intl
- **基本使用**:
import React from'react';
import { IntlProvider, FormattedMessage } from'react - intl';

const messages = {
    en: {
        welcomeMessage: 'Welcome to our app'
    },
    fr: {
        welcomeMessage: 'Bienvenue dans notre application'
    }
};

function App() {
    return (
        <IntlProvider locale="en" messages={messages.en}>
            <FormattedMessage id="welcomeMessage" />
        </IntlProvider>
    );
}

export default App;

这里,IntlProvider 组件用于提供国际化上下文,FormattedMessage 组件用于显示翻译后的文本。

三、配置 React 国际化

  1. 初始化 i18next
    • 配置文件:在使用 react - i18next 时,通常需要创建一个 i18next 的配置文件。例如,在项目根目录下创建 i18n.js 文件。
import i18next from 'i18next';
import { initReactI18next } from'react - i18next';

// 引入语言资源
import translationEN from './locales/en/translation.json';
import translationFR from './locales/fr/translation.json';

const resources = {
    en: {
        translation: translationEN
    },
    fr: {
        translation: translationFR
    }
};

i18next
   .use(initReactI18next)
   .init({
        resources,
        lng: 'en', // 默认语言
        interpolation: {
            escapeValue: false
        }
    });

export default i18next;
- **在应用中使用**:在 React 应用的入口文件(如 `index.js`)中导入配置好的 i18next。
import React from'react';
import ReactDOM from'react - dom';
import App from './App';
import i18next from './i18n';

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);
  1. 配置 react - intl
    • Provider 配置:在 react - intl 中,需要在应用的顶层组件包裹 IntlProvider
import React from'react';
import ReactDOM from'react - dom';
import App from './App';
import { IntlProvider } from'react - intl';

const messages = {
    en: {
        welcomeMessage: 'Welcome to our app'
    },
    fr: {
        welcomeMessage: 'Bienvenue dans notre application'
    }
};

ReactDOM.render(
    <React.StrictMode>
        <IntlProvider locale="en" messages={messages.en}>
            <App />
        </IntlProvider>
    </React.StrictMode>,
    document.getElementById('root')
);
- **动态切换语言**:要动态切换语言,需要重新渲染 `IntlProvider` 并传递新的 `locale` 和 `messages`。可以通过状态管理(如 Redux 或 React 上下文)来实现。

四、翻译文本

  1. 使用 react - i18next
    • 简单文本翻译:如前面示例所示,使用 t 函数翻译文本。翻译键通常在语言资源文件(如 translation.json)中定义。
import React from'react';
import { useTranslation } from'react - i18next';

function MyComponent() {
    const { t } = useTranslation();
    return <p>{t('greeting')}</p>;
}

export default MyComponent;

en/translation.json 中:

{
    "greeting": "Hello"
}

fr/translation.json 中:

{
    "greeting": "Bonjour"
}
- **带参数的翻译**:有时候翻译文本需要包含动态参数。
import React from'react';
import { useTranslation } from'react - i18next';

function MyComponent() {
    const { t } = useTranslation();
    const name = 'John';
    return <p>{t('greetingWithName', { name })}</p>;
}

export default MyComponent;

en/translation.json 中:

{
    "greetingWithName": "Hello, {{name}}!"
}

fr/translation.json 中:

{
    "greetingWithName": "Bonjour, {{name}}!"
}
  1. 使用 react - intl
    • 简单文本翻译:使用 FormattedMessage 组件。
import React from'react';
import { IntlProvider, FormattedMessage } from'react - intl';

const messages = {
    en: {
        greeting: 'Hello'
    },
    fr: {
        greeting: 'Bonjour'
    }
};

function App() {
    return (
        <IntlProvider locale="en" messages={messages.en}>
            <FormattedMessage id="greeting" />
        </IntlProvider>
    );
}

export default App;
- **带参数的翻译**:`FormattedMessage` 组件也支持带参数的翻译。
import React from'react';
import { IntlProvider, FormattedMessage } from'react - intl';

const messages = {
    en: {
        greetingWithName: 'Hello, {name}!'
    },
    fr: {
        greetingWithName: 'Bonjour, {name}!'
    }
};

function App() {
    const name = 'John';
    return (
        <IntlProvider locale="en" messages={messages.en}>
            <FormattedMessage id="greetingWithName" values={{ name }} />
        </IntlProvider>
    );
}

export default App;

五、日期和时间格式化

  1. 使用 react - intl
    • 基本格式化react - intl 提供了 FormattedDateFormattedTime 组件用于格式化日期和时间。
import React from'react';
import { IntlProvider, FormattedDate, FormattedTime } from'react - intl';

const now = new Date();

function App() {
    return (
        <IntlProvider locale="en">
            <div>
                <FormattedDate value={now} />
                <FormattedTime value={now} />
            </div>
        </IntlProvider>
    );
}

export default App;
- **自定义格式化**:可以通过 `format` 属性自定义日期和时间的格式。
import React from'react';
import { IntlProvider, FormattedDate, FormattedTime } from'react - intl';

const now = new Date();

function App() {
    return (
        <IntlProvider locale="en">
            <div>
                <FormattedDate value={now} format="yyyy - MM - dd" />
                <FormattedTime value={now} format="HH:mm:ss" />
            </div>
        </IntlProvider>
    );
}

export default App;
  1. 使用 i18next - xhr - backend 和 moment - js(react - i18next 扩展)
    • 安装依赖
npm install i18next - xhr - backend moment moment - js
# 或者
yarn add i18next - xhr - backend moment moment - js
- **配置 i18next**:在 `i18n.js` 中添加如下配置。
import i18next from 'i18next';
import { initReactI18next } from'react - i18next';
import Backend from 'i18next - xhr - backend';
import moment from'moment';
import 'moment - js';

// 引入语言资源
import translationEN from './locales/en/translation.json';
import translationFR from './locales/fr/translation.json';

const resources = {
    en: {
        translation: translationEN
    },
    fr: {
        translation: translationFR
    }
};

i18next
   .use(Backend)
   .use(initReactI18next)
   .init({
        resources,
        lng: 'en',
        interpolation: {
            escapeValue: false
        },
        backend: {
            loadPath: '/locales/{{lng}}/{{ns}}.json'
        }
    });

// 配置 moment 语言
i18next.on('languageChanged', function (lng) {
    moment.locale(lng);
});

export default i18next;
- **在组件中使用**:
import React from'react';
import { useTranslation } from'react - i18next';
import moment from'moment';

function MyComponent() {
    const { t } = useTranslation();
    const now = moment();
    return <p>{t('currentDate', { date: now.format('YYYY - MM - DD') })}</p>;
}

export default MyComponent;

en/translation.json 中:

{
    "currentDate": "The current date is {{date}}"
}

六、数字和货币格式化

  1. 使用 react - intl
    • 数字格式化react - intl 提供了 FormattedNumber 组件用于格式化数字。
import React from'react';
import { IntlProvider, FormattedNumber } from'react - intl';

function App() {
    const number = 1234.567;
    return (
        <IntlProvider locale="en">
            <FormattedNumber value={number} />
        </IntlProvider>
    );
}

export default App;
- **货币格式化**:使用 `FormattedNumber` 组件并设置 `style` 为 `'currency'` 和 `currency` 属性。
import React from'react';
import { IntlProvider, FormattedNumber } from'react - intl';

function App() {
    const amount = 1234.56;
    return (
        <IntlProvider locale="en" currency="USD">
            <FormattedNumber value={amount} style="currency" currency="USD" />
        </IntlProvider>
    );
}

export default App;
  1. 使用 numeral - js(react - i18next 扩展)
    • 安装依赖
npm install numeral - js
# 或者
yarn add numeral - js
- **在组件中使用**:
import React from'react';
import { useTranslation } from'react - i18next';
import numeral from 'numeral - js';

function MyComponent() {
    const { t } = useTranslation();
    const number = 1234.567;
    const formattedNumber = numeral(number).format('0,0.00');
    return <p>{t('formattedNumber', { number: formattedNumber })}</p>;
}

export default MyComponent;

en/translation.json 中:

{
    "formattedNumber": "The formatted number is {{number}}"
}

七、处理复数形式

  1. 使用 react - i18next
    • 配置语言资源:在语言资源文件中定义复数形式。例如,在 en/translation.json 中:
{
    "itemCount": {
        "one": "There is {{count}} item",
        "other": "There are {{count}} items"
    }
}

fr/translation.json 中:

{
    "itemCount": {
        "one": "Il y a {{count}} élément",
        "other": "Il y a {{count}} éléments"
    }
}
- **在组件中使用**:
import React from'react';
import { useTranslation } from'react - i18next';

function MyComponent() {
    const { t } = useTranslation();
    const count = 5;
    return <p>{t('itemCount', { count, count: count })}</p>;
}

export default MyComponent;
  1. 使用 react - intl
    • 配置语言资源:在 messages 对象中定义复数形式。
const messages = {
    en: {
        itemCount: {
            one: 'There is {count} item',
            other: 'There are {count} items'
        }
    },
    fr: {
        itemCount: {
            one: 'Il y a {count} élément',
            other: 'Il y a {count} éléments'
        }
    }
};
- **在组件中使用**:
import React from'react';
import { IntlProvider, FormattedMessage } from'react - intl';

const messages = {
    en: {
        itemCount: {
            one: 'There is {count} item',
            other: 'There are {count} items'
        }
    },
    fr: {
        itemCount: {
            one: 'Il y a {count} élément',
            other: 'Il y a {count} éléments'
        }
    }
};

function App() {
    const count = 3;
    return (
        <IntlProvider locale="en" messages={messages.en}>
            <FormattedMessage id="itemCount" values={{ count }} />
        </IntlProvider>
    );
}

export default App;

八、本地化排序

  1. 字符串排序
    • 使用 Intl.Collator:在 JavaScript 中,可以使用 Intl.Collator 进行本地化字符串排序。
const fruits = ['banana', 'apple', 'cherry'];
const collator = new Intl.Collator('en', { sensitivity: 'base' });
fruits.sort(collator.compare);
console.log(fruits);

在 React 组件中,可以将其封装成一个函数并使用。

import React from'react';

function useLocalizedSort() {
    const collator = new Intl.Collator('en', { sensitivity: 'base' });
    return (array) => array.sort(collator.compare);
}

function MyComponent() {
    const fruits = ['banana', 'apple', 'cherry'];
    const sortedFruits = useLocalizedSort()(fruits);
    return (
        <ul>
            {sortedFruits.map((fruit, index) => (
                <li key={index}>{fruit}</li>
            ))}
        </ul>
    );
}

export default MyComponent;
  1. 数字排序
    • 自定义排序函数:对于数字排序,虽然 JavaScript 的 Array.prototype.sort 本身可以处理基本的数字排序,但在本地化场景下,可能需要考虑不同地区的数字表示习惯。例如,一些地区使用逗号作为小数点,而另一些地区使用点号。
function localizedNumberSort(a, b, locale) {
    const numberA = Number(a.toString().replace(/[^\d.-]/g, ''));
    const numberB = Number(b.toString().replace(/[^\d.-]/g, ''));
    return numberA - numberB;
}

const numbers = ['1.23', '4,56', '7.89'];
const sortedNumbers = numbers.sort((a, b) => localizedNumberSort(a, b, 'en'));
console.log(sortedNumbers);

九、优化与性能考虑

  1. 代码拆分
    • 动态导入语言资源:为了减少初始加载时间,可以使用动态导入语言资源。在 react - i18next 中,可以结合 Webpack 的动态导入功能。
import React from'react';
import { useTranslation } from'react - i18next';

function MyComponent() {
    const { t, i18n } = useTranslation();
    const loadTranslation = (lng) => {
        import(`./locales/${lng}/translation.json`).then((translations) => {
            i18n.addResourceBundle(lng, 'translation', translations.default);
            i18n.changeLanguage(lng);
        });
    };

    return (
        <div>
            <p>{t('message')}</p>
            <button onClick={() => loadTranslation('fr')}>Switch to French</button>
        </div>
    );
}

export default MyComponent;
  1. 缓存翻译结果
    • Memoization:在频繁翻译相同文本的场景下,可以使用 memoization 技术缓存翻译结果。例如,在 react - i18next 中,可以自定义一个 memoized 版本的 t 函数。
import React from'react';
import { useTranslation } from'react - i18next';

function memoize(func) {
    const cache = new Map();
    return (key, options) => {
        const cacheKey = `${key}:${JSON.stringify(options)}`;
        if (cache.has(cacheKey)) {
            return cache.get(cacheKey);
        }
        const result = func(key, options);
        cache.set(cacheKey, result);
        return result;
    };
}

function MyComponent() {
    const { t } = useTranslation();
    const memoizedT = memoize(t);
    return <p>{memoizedT('message')}</p>;
}

export default MyComponent;

十、测试国际化与本地化

  1. 单元测试
    • 使用 Jest 和 react - testing - library:对于 React 组件的国际化测试,可以使用 Jest 和 react - testing - library。例如,测试一个使用 react - i18next 的组件。
import React from'react';
import { render, screen } from '@testing - library/react';
import { Provider } from'react - i18next';
import i18next from 'i18next';
import MyComponent from './MyComponent';

describe('MyComponent', () => {
    beforeEach(() => {
        i18next.init({
            resources: {
                en: {
                    translation: {
                        message: 'Test message'
                    }
                }
            },
            lng: 'en',
            interpolation: {
                escapeValue: false
            }
        });
    });

    it('should render translated text', () => {
        render(
            <Provider i18n={i18next}>
                <MyComponent />
            </Provider>
        );
        expect(screen.getByText('Test message')).toBeInTheDocument();
    });
});
  1. 集成测试
    • 使用 Cypress:对于集成测试,可以使用 Cypress。例如,测试语言切换功能。
describe('Language Switching', () => {
    it('should switch language', () => {
        cy.visit('/');
        cy.contains('Switch to French').click();
        cy.contains('Bienvenue dans notre application').should('be.visible');
    });
});

通过以上全面的介绍和代码示例,希望能帮助开发者在 React 项目中顺利实现组件的国际化与本地化,打造出更具全球化竞争力的前端应用。在实际项目中,需要根据项目的规模、需求和技术栈特点选择合适的国际化方案,并不断优化和测试,以提供良好的用户体验。