我需要将locize中的i18本地化集成到一个nextjs项目中。我发现react-i18next适用于i18和locize,但未与nextjs集成。另一方面,next-i18next与nextjs和本地i18文件一起很好地工作,但似乎不适用于locize(几乎没有示例)。 还有其他解决方案吗?可以用next-i18next完成吗?
谢谢。
答案 0 :(得分:2)
谢谢@quebone的自动应答。我用它来改进自己的配置,该配置将TypeScript与Next.js结合使用,但是我没有像您一样使用next-i18next
,因为它还与Next serverless
模式不兼容。
因此,如果您在无服务器模式下使用Next(例如,现在使用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;
此外,由于Next会提早渲染(SSR)(它不会等待i18next加载初始翻译),因此这将导致服务器无法提取翻译,并且SSR服务的页面将不包含正确的句子。
为避免这种情况,您需要手动预取当前语言所依赖的所有名称空间。必须在pages/_app.tsx
函数的getInitialProps
中完成此步骤。 (我正在使用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或Hook在页面/组件中使用翻译。这是在我的索引页上使用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);
有关更多示例,请参见官方文档:
请注意,自动添加缺少的句子对我来说在localhost上不起作用,但可以在网上正常运行。
编辑:最终使其在localhost中工作,不确定如何使用。
请注意,您需要安装其他依赖项才能正常工作:
还请注意,我使用的是自定义区域设置检测器,但您可能想在https://github.com/i18next/i18next-browser-languageDetector上使用推荐的检测器
答案 1 :(得分:0)
我从Locize那里得到了答案。非常感谢!
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"
}
});
答案 2 :(得分:0)