如何集成 next-i18next, nextjs & locize



我需要整合来自NextJS项目中的I18本地化。我发现React-I18Next与I18&找到,但不与NextJ集成。另一方面,Next-I18Next与NextJs&本地I18文件,但似乎无法使用(几乎没有示例(。还有另一种解决方案可以使用吗?可以使用Next-I18Next?

完成

thks。

编辑2020年3月:检查https://github.com/unlyed/next-right-now boererplate,它使用下面的配置并提供真实的用途-case示例Next.js 9(无服务器( i18next/react-i18next 找到。(免责声明:我是作者(


谢谢@quebone的自动撤离。我用它来改进自己的配置,该配置与Next.js一起使用Typescript,但是我不使用next-i18next,因为它与下一个serverless模式尚不兼容。

因此,如果您在无服务器模式下使用下一个(例如,现在使用ZEIT(,则请按照以下配置。

utils/i18nextLocize.ts

import { isBrowser } from '@unly/utils';
import { createLogger } from '@unly/utils-simple-logger';
import i18next from 'i18next';
import map from 'lodash.map';
import { initReactI18next } from 'react-i18next';
import { LOCALE_EN, LOCALE_FR } from './locale';
const logger = createLogger({
  label: 'utils/i18nextLocize',
});
/**
 * Common options shared between all locize/i18next plugins
 *
 * @see https://github.com/locize/i18next-node-locize-backend#backend-options
 * @see https://github.com/locize/i18next-locize-backend#backend-options
 * @see https://github.com/locize/locize-node-lastused#options
 * @see https://github.com/locize/locize-editor#initialize-with-optional-options
 */
export const locizeOptions = {
  projectId: '7867a172-62dc-4f47-b33c-1785c4701b12',
  apiKey: isBrowser() ? null : process.env.LOCIZE_API_KEY, // XXX Only define the API key on the server, for all environments (allows to use saveMissing)
  version: process.env.APP_STAGE === 'production' ? 'production' : 'latest', // XXX On production, use a dedicated production version
  referenceLng: 'fr',
};
/**
 * Specific options for the selected Locize backend.
 *
 * There are different backends for locize, depending on the runtime (browser or node).
 * But each backend shares a common API.
 *
 * @see https://github.com/locize/i18next-node-locize-backend#backend-options
 * @see https://github.com/locize/i18next-locize-backend#backend-options
 */
export const locizeBackendOptions = {
  ...locizeOptions,
  loadPath: 'https://api.locize.io/{{projectId}}/{{version}}/{{lng}}/{{ns}}',
  addPath: 'https://api.locize.io/missing/{{projectId}}/{{version}}/{{lng}}/{{ns}}',
  allowedAddOrUpdateHosts: [
    'localhost',
  ],
};
/**
 * Configure i18next with Locize backend.
 *
 * - Initialized with pre-defined "lang" (to make sure GraphCMS and Locize are configured with the same language)
 * - Initialized with pre-fetched "defaultLocales" (for SSR compatibility)
 * - Fetches translations from Locize backend
 * - Automates the creation of missing translations using "saveMissing: true"
 * - Display Locize "in-context" Editor when appending "/?locize=true" to the url (e.g http://localhost:8888/?locize=true)
 * - Automatically "touches" translations so it's easier to know when they've been used for the last time,
 *    helping translators figuring out which translations are not used anymore so they can delete them
 *
 * XXX We don't rely on https://github.com/i18next/i18next-browser-languageDetector because we have our own way of resolving the language to use, using utils/locale
 *
 * @param lang
 * @param defaultLocales
 */
const i18nextLocize = (lang, defaultLocales): void => {
  logger.info(JSON.stringify(defaultLocales, null, 2), 'defaultLocales');
  // Plugins will be dynamically added at runtime, depending on the runtime (node or browser)
  const plugins = [ // XXX Only plugins that are common to all runtimes should be defined by default
    initReactI18next, // passes i18next down to react-i18next
  ];
  // Dynamically load different modules depending on whether we're running node or browser engine
  if (!isBrowser()) {
    // XXX Use "__non_webpack_require__" on the server
    // loads translations, saves new keys to it (saveMissing: true)
    // https://github.com/locize/i18next-node-locize-backend
    const i18nextNodeLocizeBackend = __non_webpack_require__('i18next-node-locize-backend');
    plugins.push(i18nextNodeLocizeBackend);
    // 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-node-lastused
    const locizeNodeLastUsed = __non_webpack_require__('locize-node-lastused');
    plugins.push(locizeNodeLastUsed);
  } else {
    // XXX Use "require" on the browser, always take the "default" export specifically
    // loads translations, saves new keys to it (saveMissing: true)
    // https://github.com/locize/i18next-locize-backend
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const i18nextLocizeBackend = require('i18next-locize-backend').default;
    plugins.push(i18nextLocizeBackend);
    // InContext Editor of locize ?locize=true to show it
    // https://github.com/locize/locize-editor
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const locizeEditor = require('locize-editor').default;
    plugins.push(locizeEditor);
  }
  const i18n = i18next;
  map(plugins, (plugin) => i18n.use(plugin));
  i18n.init({ // XXX See https://www.i18next.com/overview/configuration-options
    resources: defaultLocales,
    debug: process.env.APP_STAGE !== 'production',
    saveMissing: true,
    lng: lang, // XXX We don't use the built-in i18next-browser-languageDetector because we have our own way of detecting language, which must behave identically for both GraphCMS I18n and react-I18n
    fallbackLng: lang === LOCALE_FR ? LOCALE_EN : LOCALE_FR,
    ns: 'common',
    defaultNS: 'common',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    backend: locizeBackendOptions,
    locizeLastUsed: locizeOptions,
    editor: {
      ...locizeOptions,
      onEditorSaved: async (lng, ns): Promise<void> => {
        // reload that namespace in given language
        await i18next.reloadResources(lng, ns);
        // trigger an event on i18n which triggers a rerender
        // based on bindI18n below in react options
        i18next.emit('editorSaved');
      },
    },
    react: {
      bindI18n: 'languageChanged editorSaved',
      useSuspense: false, // Not compatible with SSR
    },
    load: 'languageOnly', // Remove if you want to use localization (en-US, en-GB)
  });
};
export default i18nextLocize;

另外,由于下一步将提早渲染(SSR((它不会等待i18next加载初始翻译(,因此这将导致服务器不获取翻译,而SSR提供的页面将不包含正确的句子。

为避免这种情况,您需要手动为当前语言预先提取所有命名空间。此步骤必须在getInitialProps功能中的pages/_app.tsx中完成。(我正在使用TSX,但是您可以使用JSX,JS等(

pages/_app.tsx

import { ApolloProvider } from '@apollo/react-hooks';
import fetch from 'isomorphic-unfetch';
import get from 'lodash.get';
import { NextPageContext } from 'next';
import NextApp from 'next/app';
import React from 'react';
import Layout from '../components/Layout';
import withData from '../hoc/withData';
import i18nextLocize, { backendOptions } from '../utils/i18nextLocize';
import { LOCALE_FR, resolveBestCountryCodes, resolveBrowserBestCountryCodes } from '../utils/locale';

class App extends NextApp {
  /**
   * Initialise the application
   *
   * XXX Executed on the server-side only
   *
   * @param props
   * @see https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle
   */
  static async getInitialProps(props): Promise<any> {
    const { ctx } = props;
    const { req, res }: NextPageContext = ctx;
    let publicHeaders = {};
    let bestCountryCodes;
    if (req) {
      bestCountryCodes = resolveBestCountryCodes(req, LOCALE_FR);
      const { headers } = req;
      publicHeaders = {
        'accept-language': get(headers, 'accept-language'),
        'user-agent': get(headers, 'user-agent'),
        'host': get(headers, 'host'),
      };
    } else {
      bestCountryCodes = resolveBrowserBestCountryCodes();
    }
    const lang = get(bestCountryCodes, '[0]', 'en').toLowerCase(); // TODO Should return a locale, not a lang. i.e: fr-FR instead of fr
    // calls page's `getInitialProps` and fills `appProps.pageProps` - XXX See https://nextjs.org/docs#custom-app
    const appProps = await NextApp.getInitialProps(props);
    // Pre-fetching locales for i18next, for the "common" namespace
    // XXX We do that because if we don't, then the SSR fails at fetching those locales using the i18next "backend" and renders too early
    //  This hack helps fix the SSR issue
    //  On the other hand, it seems that once the i18next "resources" are set, they don't change for that language
    //  so this workaround could cause sync issue if we were using multiple namespaces, but we aren't and probably won't
    const defaultLocalesResponse = await fetch(
      backendOptions
        .loadPath
        .replace('{{projectId}}', backendOptions.projectId)
        .replace('{{version}}', backendOptions.version)
        .replace('{{lng}}', lang)
        .replace('{{ns}}', 'common'));
    const defaultLocales = {
      [lang]: {
        common: await defaultLocalesResponse.json(),
      }
    };
    appProps.pageProps = {
      ...appProps.pageProps,
      bestCountryCodes, // i.e: ['EN', 'FR']
      lang, // i.e: 'en'
      defaultLocales: defaultLocales,
    };
    return { ...appProps };
  }
  render() {
    const { Component, pageProps, apollo }: any = this.props;
    i18nextLocize(pageProps.lang, pageProps.defaultLocales); // Apply i18next configuration with Locize backend
    // Workaround for https://github.com/zeit/next.js/issues/8592
    const { err }: any = this.props;
    const modifiedPageProps = { ...pageProps, err };
      return (
        <ApolloProvider client={apollo}>
          <Layout {...modifiedPageProps}>
            <Component {...modifiedPageProps} />
          </Layout>
        </ApolloProvider>
      );
  }
  componentDidCatch(error, errorInfo) {
    // This is needed to render errors correctly in development / production
    super.componentDidCatch(error, errorInfo);
  }
}
// Wraps all components in the tree with the data provider
export default withData(App);

然后,您可以使用HOC或钩子在页面/组件中使用翻译。这是使用HOC与我的索引页面的示例:

pages/index.tsx

import React from 'react';
import { withTranslation } from 'react-i18next';
import { compose } from 'recompose';
import Head from '../components/Head';
const Home = (props: any) => {
  const { organisationName, bestCountryCodes, t } = props;
  return (
    <div>
      <Head />
      <div className="hero">
        <div>{t('welcome', 'Bonjour auto')}</div>
        <div>{t('missingShouldBeAdded', 'Missing sentence, should be added automatically')}</div>
        <div>{t('missingShouldBeAdded2', 'Missing sentence, should be added automatically')}</div>
      </div>
    </div>
  );
};
export default compose(
  withTranslation(['common']),
)(Home);

请参阅官方文档以获取更多示例:

  • https://react.i18next.com/latest/usetranslation-hook
  • https://react.i18next.com/latest/withtranslation-hoc

请注意,自动添加丢失的句子在Localhost上对我不起作用,但在线工作正常。

编辑:最终使其在Local主持器中起作用,不确定如何。


请注意,您需要安装其他依赖项才能使此工作:

  • @types/webpack-env:允许将__non_webpack_require__与Typescript一起使用。

还请注意,我使用自定义语言环境检测器,但是您可能想在https://github.com/i18next/i18next-browser-langueadetector上使用推荐的检测器

我从位置获得了答案。非常感谢!

const isNode = require("detect-node");
const i18nextLocizeBackend = require("i18next-locize-backend");
const { localeSubpaths } = require("next/config").default().publicRuntimeConfig;
const NextI18Next = require("next-i18next/dist/commonjs");
const use = [];
if (isNode) {
  const i18nextNodeLocizeBackend = eval(
    "require('i18next-node-locize-backend')"
  );
  use.push(i18nextNodeLocizeBackend);
} else {
  use.push(i18nextLocizeBackend.default);
}
module.exports = new NextI18Next({
  otherLanguages: ["de"],
  localeSubpaths,
  use,
  saveMissing: true,
  backend: {
    loadPath: "https://api.locize.io/{{projectId}}/{{version}}/{{lng}}/{{ns}}",
    addPath: "https://api.locize.io/missing/{{projectId}}/{{version}}/{{lng}}/{{ns}}",
    referenceLng: "en",
    projectId: "9dc2239d-a752-4973-a6e7-f622b2b76508",
    apiKey: "9f019666-2e71-4c58-9648-e6a4ed1e15ae",
    version: "latest"
  }
});

带有新版本,它变得更简单:https://github.com/isaachinman/next-i18next/pull/705/705/705/files#diff-bda3f4b5c1b38db38db2dcdcde53a9833a9832453245324531

这里一个很好的例子:https://github.com/locize/next-i18next-locize

最新更新