如何使用 i18next 正确地国际化 React 应用程序 首先:“为什么要使用 i18next?” 让我们开始吧……🎉🥳 恭喜 🎊🎁 👍

2025-05-24

如何使用 i18next 正确地国际化 React 应用程序

首先:“为什么选择 i18next?”

让我们开始吧...

🎉🥳 恭喜 🎊🎁

👍

为软件用户克服语言障碍是一个重要课题。
英语已不再是互联网的通用语言。
截至2020 年 3 月,只有 25.9% 的互联网用户使用英语。
如果您的网站未进行本地化,用户很可能会跳过您的网站。
因此,如果没有多语言网站,您可能会错失大量潜在用户。

JavaScript 生态系统中有很多国际化框架。您可以在这里找到一些 JavaScript 国际化框架的详细信息。
在本文中,我们将使用i18next框架对React.js应用进行国际化

目录

首先:“为什么选择 i18next?”

说到 React 本地化,最受欢迎的是i18next及其 React 扩展react-i18next,理由如下:

i18next 创建于 2011 年末。它比您现在使用的大多数库都要古老,包括您的主要前端技术(react、vue 等)。

➡️ 可持续

根据 i18next 开源的时间长度,没有任何实际的 i18n 案例是无法通过 i18next 解决的。

➡️成熟

i18next 可以在任何 javascript(以及一些非 javascript - .net、elm、iOS、android、ruby 等)环境中使用,可以使用任何 UI 框架、任何 i18n 格式,...可能性无穷无尽

➡️ 可扩展

与其他常规 i18n 框架相比,i18next 具有许多功能和可能性。

➡️丰富

您可以在这里找到有关 i18next 的特殊之处及其工作原理的更多信息。

让我们开始吧...

先决条件

确保已安装 Node.js 和 npm。在开始使用react-i18next之前,最好先熟悉一些简单的 HTML、JavaScript 和 React.js 基础。

入门

使用您自己的 React 项目或创建一个新项目,即使用create-react-app

npx create-react-app my-app

我们将调整应用程序,使其能够根据用户的偏好检测语言。
此外,我们还将创建一个语言切换器,使内容在不同语言之间切换。

让我们安装一些 i18next 依赖项:

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
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          // here we will place our translations...
        }
      }
    }
  });

export default i18n;


Enter fullscreen mode Exit fullscreen mode

让我们将该文件导入到 index.js 文件的某个位置:

对于 React >= 18.0.0 使用:



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

// import i18n (needs to be bundled ;))
import './i18n';

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


Enter fullscreen mode Exit fullscreen mode

对于较旧的 React 版本,请使用:



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

// import i18n (needs to be bundled ;))
import './i18n';

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


Enter fullscreen mode Exit fullscreen mode

现在让我们尝试将一些硬编码文本移到翻译中。

我们对第一段文本使用了Trans 组件,对第二段文本使用了useTranslation 钩子:



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;


Enter fullscreen mode Exit fullscreen mode

这些文本现在是翻译资源的一部分:



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

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          }
        }
      }
    }
  });

export default i18n;


Enter fullscreen mode Exit fullscreen mode

语言切换器

现在让我们定义一个语言切换器:



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

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

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

  return (
    <div className="App">
      <header className="App-header">
        <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>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode

并添加一些新语言的翻译:



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

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    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;


Enter fullscreen mode Exit fullscreen mode

🥳太棒了,您刚刚创建了您的第一个语言切换器!

得益于i18next-browser-languagedetector,它现在会尝试检测浏览器语言,并在您提供翻译的情况下自动使用该语言。语言切换器中手动选择的语言将持久保存在 localStorage 中,下次您访问该页面时,该语言将作为首选语言。

插值和复数

i18next 不仅仅提供标准的 i18n 功能,
它还能处理复数插值

让我们计算一下每次语言改变的次数:



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

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

function App() {
  const { t, i18n } = useTranslation();
  const [count, setCounter] = useState(0);

  return (
    <div className="App">
      <header className="App-header">
        <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);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
        <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;


Enter fullscreen mode Exit fullscreen mode

...并扩展翻译资源:



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

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    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'
        }
      }
    }
  });

export default i18n;


Enter fullscreen mode Exit fullscreen mode

i18next 将根据计数值选择正确的复数形式。有关复数插值
的更多信息,请参阅i18next 官方文档

替代文本

💡 i18next 还能够处理具有多种复数形式的语言,例如阿拉伯语:



// translation resources:
{
  "key_0": "zero",
  "key_1": "singular",
  "key_2": "two",
  "key_3": "few",
  "key_4": "many",
  "key_5": "other"
}

// usage:
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"


Enter fullscreen mode Exit fullscreen mode

格式化

现在,让我们看看如何在i18nextLuxon的帮助下使用不同的日期格式来处理日期和时间。

npm install luxon

我们希望有一个页脚显示当前日期:



import './Footer.css';

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

export default Footer;

// imported in our App.js and used like this
// <Footer t={t} />


Enter fullscreen mode Exit fullscreen mode

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



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

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    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',
          footer: {
            date: 'Today is {{date, DATE_HUGE}}'
          }
        }
      },
      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',
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

😎 太棒了,现在我们有了特定语言的日期格式!

英语:

德语:

语境

那么根据当前时间的具体问候信息怎么样?例如早上、晚上等等。
这要归功于i18next 的上下文功能。

让我们创建一个 getGreetingTime 函数并使用其结果作为页脚翻译的上下文信息:



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

const getGreetingTime = (d = DateTime.now()) => {
    const split_afternoon = 12; // 24hr time to split the afternoon
    const split_evening = 17; // 24hr time to split the evening
    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;


Enter fullscreen mode Exit fullscreen mode

并添加一些上下文特定的翻译键:



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
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    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',
          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: {
          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',
          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}}'
          }
        }
      }
    }
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

😁 是的,它有效!

将翻译与代码分开

将翻译放在 i18n.js 文件中虽然可行,但对于翻译人员来说不太方便。
让我们将翻译与代码分离,并将它们放在专门的 json 文件中。

因为这是一个 Web 应用程序,i18next-http-backend将帮助我们做到这一点。

npm install i18next-http-backend

将翻译移至公共文件夹:

调整 i18n.js 文件以使用 i18next-http-backend:



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
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    }
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

现在翻译是异步加载的,因此请确保使用Suspense组件包装您的应用程序以防止出现此错误:Uncaught Error: App suspended while rendering, but no fallback UI was specified.



import { Suspense } from 'react';

function App() {
  // your app's code...
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}


Enter fullscreen mode Exit fullscreen mode

现在你的应用看起来还是一样的,但翻译是分开的。
如果你想支持一种新的语言,只需创建一个新的文件夹和一个新的翻译 json 文件。
这样你就可以将翻译发送给一些译员。
或者,如果你正在使用翻译管理系统,你可以直接使用 cli 同步文件

💡 顺便说一句:得益于i18next 的命名空间功能,您还可以拥有多个翻译文件

🧑‍💻 第一部分的代码可以在这里找到。

更好的翻译管理

将译文发送给一些译员或翻译机构,您可以更好地掌控翻译,并与他们直接联系。但这也意味着您的工作量会增加。
这是一种传统方式。但请注意,四处发送文件总会造成一定的开销。

有没有更好的选择?

一定!

i18next 有助于翻译应用程序,这很棒 - 但还有更多作用。

  • 您如何整合任何翻译服务/机构?
  • 您如何跟踪新内容或已删除的内容?
  • 您如何处理正确的版本控制?
  • 如何在不部署完整应用程序的情况下部署翻译更改?
  • 以及更多...

正在寻找这样的东西❓

这看起来怎么样?

首先,您需要在locize注册并登录
然后在 locize 中创建一个新项目并添加您的翻译。您可以使用命令行界面 (cli)、导入单独的 json 文件或通过API来添加翻译

完成后,我们将i18next-locize-backend替换i18next-http-backend

npm install i18next-locize-backend

将翻译导入 locize 后,删除 locales 文件夹:

调整 i18n.js 文件以使用 i18next-locize-backend,并确保从 locize 项目中复制 project-id 和 api-key:



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

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

i18next-locize-backend提供了直接从 locize 检索可用语言的功能,让我们使用它:



import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState, Suspense, useEffect } from 'react';
import Footer from './Footer'

function App() {
  const { t, i18n } = useTranslation();
  const [count, setCounter] = useState(0);

  const [lngs, setLngs] = useState({ en: { nativeName: 'English' }});

  useEffect(() => {
    i18n.services.backendConnector.backend.getLanguages((err, ret) => {
      if (err) return // TODO: handle err...
      setLngs(ret);
    });
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <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);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
        <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>
      <Footer t={t} />
    </div>
  );
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}


Enter fullscreen mode Exit fullscreen mode

保存缺失的翻译

由于使用了saveMissing 功能,在开发应用程序时,新的密钥会自动添加到 locize。

只需传入saveMissing: truei18next 选项:



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

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions,
    saveMissing: true
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

每次您使用新密钥时,它将被发送到 locize,即:



<div>{t('new.key', 'this will be added automatically')}</div>


Enter fullscreen mode Exit fullscreen mode

将导致定位如下:

👀 但还有更多……

借助locize-lastused插件,您将能够在 locize 中查找和过滤已使用或不再使用的键

借助locize插件,您将能够在 locize InContext Editor中使用您的应用程序。

最后,借助自动机器翻译工作流程和使用saveMissing 功能,在开发应用程序时,新密钥不仅会自动添加到 locize,还会使用机器翻译自动翻译成目标语言。

观看此视频,了解自动机器翻译工作流程是怎样的!

npm install locize-lastused locize

在 i18n.js 中使用它们:



import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  .use(LastUsed)
  // locize-editor
  // InContext Editor of locize
  .use(locizePlugin)
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: true
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

自动机器翻译

上次使用的翻译过滤器

InContext 编辑器

📦让我们为生产做好准备🚀

现在,我们准备将应用程序投入生产

首先,在 locize 中创建一个专用于生产的版本。不要为该版本启用自动发布,而是手动发布,或者通过APICLI进行发布。
最后,为该生产版本启用 Cache-Control max-age 。

让我们利用react-scripts 的环境功能

让我们创建一个默认环境文件,一个用于开发,一个用于生产:

.环境变量:



SKIP_PREFLIGHT_CHECK=true

REACT_APP_VERSION=$npm_package_version

# locize
REACT_APP_LOCIZE_PROJECTID=0bbc223a-9aba-4a90-ab93-ab9d7bf7f780
REACT_APP_LOCIZE_REFLNG=en


Enter fullscreen mode Exit fullscreen mode

.环境.开发:



REACT_APP_LOCIZE_VERSION=latest
REACT_APP_LOCIZE_APIKEY=aaad4141-54ba-4625-ae37-657538fe29e7


Enter fullscreen mode Exit fullscreen mode

.环境.生产:



REACT_APP_LOCIZE_VERSION=production


Enter fullscreen mode Exit fullscreen mode

现在让我们调整 i18n.js 文件:



import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';

const isProduction = process.env.NODE_ENV === 'production';

const locizeOptions = {
  projectId: process.env.REACT_APP_LOCIZE_PROJECTID,
  apiKey: process.env.REACT_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
  referenceLng: process.env.REACT_APP_LOCIZE_REFLNG,
  version: process.env.REACT_APP_LOCIZE_VERSION
};

if (!isProduction) {
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  i18n.use(LastUsed);
}

i18n
  // locize-editor
  // InContext Editor of locize
  .use(locizePlugin)
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: !isProduction // you should not use saveMissing in production
  });

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

export default i18n;


Enter fullscreen mode Exit fullscreen mode

现在,在开发过程中,您将继续保存丢失的密钥并使用 lastused 功能。=> npm run start

并且在生产环境中,saveMissing 和 lastused 被禁用,并且 api-key 未暴露。=> npm run build && npm run serve

缓存

合并版本

🧑‍💻完整代码可以在这里找到。

还请查看YouTube 视频中的代码集成部分

还有i18next 速成课程视频

🎉🥳 恭喜 🎊🎁

我希望您已经了解了一些有关i18nextReact.js 本地化现代本地化工作流程的新知识。

因此,如果您想将 i18n 主题提升到一个新的水平,那么值得尝试locize

locize的创始人也是i18next的创造者。因此,使用locize就是直接支持i18next的未来

👍

文章来源:https://dev.to/adrai/how-to-properly-internationalize-a-react-application-using-i18next-3hdb
PREV
CSS Houdini 简介及 CSS 的未来 Houdini 是什么?CSS 的未来
NEXT
从 DevOps 角度来看容器与无服务器