Closed msheakoski closed 5 years ago
So you're using the same i18next instance on both apps?
create separate instances like: https://www.i18next.com/overview/api#createinstance
I have created two separate instances as you suggested in your last comment, but the second instance still contains translations from the namespace of the first instance:
import * as LanguageDetector from "i18next-browser-languagedetector";
import i18n from "i18next";
import {initReactI18next} from "react-i18next";
const newInstance1 = i18n.createInstance();
newInstance1
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
resources: {
"en": {translation: require("./translations/en.json")},
"en-GB": {translation: require("./translations/en-GB.json")},
},
});
export default newInstance1;
import * as LanguageDetector from "i18next-browser-languagedetector";
import i18n from "i18next";
import {initReactI18next} from "react-i18next";
const newInstance2 = i18n.createInstance();
newInstance2
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
resources: {
"en": {translation: require("./translations/en.json")},
},
});
export default newInstance2;
When I log the i18n instance in the second app via const {t, i18n} = this.props; console.log(i18n);
i18n.store.data contains translations from the first instance.
Ah and further your using same react-i18next for both apps? If so .use(initReactI18next)
won't work as it's one global state. You will need to use the I18nextProvider: https://react.i18next.com/latest/i18nextprovider#what-it-does to pass i18n to your JSX tree
Thank you for your advice, @jamuhl! I will give this a try in the next day and report my results. Does the <I18nextProvider>
tag need to be outside of my <App>
component, or can I put it as the outermost tag inside of my
class App {
render() {
return (
<I18nextProvider i18n={i18n}>
<div>Hello, world!</div>
<OtherComponent/>
</I18nextProvider>
);
}
}
Just remove the .use(initReactI18next)
in i18n.js and place the I18nextProvider where you like -> just must be a parent of all the other provided react-i18next components so they can pick up the i18next instance passed in from context
@msheakoski thanks, unfortunately, your CSB link leads to a 404
hey @jamuhl , i tried the solution suggested but it didn't worked.
I develop a component library that use 18next. the consumer of the npm, also use 18next. we created instance for each and pass it down using the provider component.
when i removed .use(initReactI18Next)
it corrupted the translation in the component and didn't help in the consumer with the initial issue. any other solution that might work ?
I18n.js file - in the components project:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
const resources = {
en: {
translation: require('./translations/messages_en.json'),
},
bg: {
translation: require('./translations/messages_bg.json'),
},
}
const widgetInstance = i18n.createInstance();
widgetInstance
// pass the i18n instance to react-i18next so the t function will be in the context.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
resources,
react: {
useSuspense: false,
},
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
})
.then(result => result)
.catch(error => console.error(error));
export default widgetInstance;
the component using it:
import i18next from '../../../locale/i18next';
....
<I18nextProvider i18n={i18next}>
<div className="myComponent"/>
</ I18nextProvider i18n={i18next}>
i18n.js in the consumer of the components npm library:
import i18next from ‘i18next’;
import {initReactI18next} from “react-i18next”;
const newInstance1 = i18next.createInstance();
export default function i18n(locale: string) {
void newInstance1.use(initReactI18next)
.use({
type: ‘backend’,
read: (
language: string,
namespace: string,
callback: (
err: any | null,
translations?: Record<string, string>,
) => void,
) => {
return import(`./locales/messages_${locale}.json`)
.then(translation => {
callback(null, translation);
})
.catch(error => {
callback(error);
});
},
})
.init({
lng: locale,
fallbackLng: “en”,
keySeparator: false,
});
return newInstance1;
}
the component using it in the provider in the consumer:
import i18n from ‘../../i18n’;
<I18nextProvider i18n={i18n(locale)}>
<LibrayComponent {...props} />
</I18nextProvider>
in your component library using the newly created instance don't use .use(initReactI18next)
only work with wrapping into the i18nextProvider
@jamuhl thx for responding, i dropped the .use(initReactI18next)
from the component library. and we have providers on the top component in the library and in the consumer.
(we tried also, to have provider only on the consumer) still, not working. any other solution ?
note* - the library component pass the i18n object exported from the i18n.ts, to the provider like this :
import i18next from '../../../locale/i18next';
<I18nextProvider i18n={i18next}>
while the consumer use it like this:
import i18n from ‘../../i18n’;
<I18nextProvider i18n={i18n(locale)}>
<Widget {...props} />
</I18nextProvider>
not sure what's going wrong in your setup...can only tell we're doing the same without any issue...even using 2 different backends -> loading two different locize projects...
ummmm... thx anyway, ill update when i find whats wrong
@jamuhl I am using vanillaJS in my package. I want to create 2 instances of i18n and use them with different options. I have a helper file to pull out translations from. Can you suggest me a way where i want to use instances of 18n and use 't' method to call for translations?
import XHR from 'i18next-xhr-backend';
import i18next from 'i18next';
import ICU from 'i18next-icu';
import LngDetector from 'i18next-browser-languagedetector';
//@ts-ignore
import languageMap from '@amzn/katal-localization/dist/webpack/localization-loader!';
export const i18n = i18next
.use(ICU)
.use(XHR)
.use(LngDetector)
.init(
{
debug: false,
fallbackLng: 'en-US',
load: 'currentOnly',
backend: {
loadPath: (languages: string[]) => {
return SERVER_MODE.mode === 'mons'
? `${CLOUD_PATH}${languageMap[languages[0]]}`
: `${languageMap[languages[0]]}`;
}
},
detection: {
order: ['htmlTag', 'navigator']
}
},
(err, t) => {
}
);
export const updateElementWithTranslation = (
elementSelector: string,
elementType: 'property' | 'attribute',
elementProperty: string,
stringId: string,
defaultString?: string,
formatArguments?: any) => {
let translation = getTranslation(stringId, defaultString, formatArguments);
const element = document.querySelector(elementSelector);
if (!element) {
throw new Error(`No element found at path '${elementSelector}'`);
} else {
switch (elementType) {
case 'attribute':
element.setAttribute(elementProperty, translation);
break;
case 'property':
(element as any)[elementProperty] = translation;
break;
default :
throw new Error(
`Unknown property type '${elementType}' (expected 'attribute' or 'property')`
);
}
}
};
const stringDebug = () => {
let url = window.location.href;
return url.includes('stringDebug=true') ||
url.includes('stringDebug=1');
};
export const getTranslation = (
stringId: string,
defaultString?: string,
formatArguments?: any
) => {
if (!formatArguments) {
formatArguments = {};
}
if (!defaultString) {
defaultString = "";
}
const marketplaceIds = ['SellerCentral', 'default'];
const strings = [];
marketplaceIds.forEach(function (item) {
strings.push(stringId + "." + item);
});
strings.push(stringId);
strings.push(defaultString);
const translation = i18next.t(strings, formatArguments);
return stringDebug() ? `${translation}[${stringId}]` : translation;
};
@pranjalg8 sorry, but I neither understand your issue - nor what you like to do
If you see i am calling i18next.t
inside getTranslation
.
Now my requirement is such that i want to use different options, particularly for detection
detection: {
order: ['queryString','htmlTag', 'navigator']
lookupQueryString: 'language'
}
instead of what's already in my file above for a particular page in my app.
So essentially i want to have 2 instances of i18next both with different configurations but in the same app such that i can selectively call particular instance on different pages.
@jamuhl Did you get my query now? I want to have multiple i18next instances, but not in a react app. Mine is in VanillaJS.
I've read this thread now multiple times but I still cannot figure out how to create a react app with multiple instances running at the same time. I have created a codesandbox with my problem here: codesandbox demo.
What I am trying to do is have 2 instances of reacti18Next running on the same application. I want parts of my app translated with for example instance1
and other parts with instance2
. Whatever I try, I cannot seem to get this to work and I am now even wondering if this is even possible with reactI18Next? Could someone take a look at my codesandbox example and tell me if it is even possible what I am trying to do?
@Mertakus using instances is meant for different areas inside an app - not mainly on the same view. By using https://react.i18next.com/latest/i18nextprovider
theoretically, you can use the Provider inside views too - just a lot of work. Or pass in the i18n instance to useTranslation https://github.com/i18next/react-i18next/blob/master/src/useTranslation.js#L7
or create a custom hook using useTranslation and setting that instance eg. useInstance1Translation
In my scenario, I need to be able to point the backend from an unauthenticated translation resource to an authenticated one. I'm not sure if this is the best approach, but I created two instances of i18n, each with different backend configs. I initialize the public one immediately on app-load, then, once the user has logged in, I initialize the authenticated one. This works fine in the app, but I cannot get my tests to work.
First: If I want to create two instances of i18n, can they be nested within each other in the same component tree?
Second: How can I ensure that my test environment can actually access the unmocked t
function so I can verify that the translations (and snapshots) are yielding the correct values?
@angusryer simpler would be to create a custom backend that handles both or using https://github.com/i18next/i18next-chained-backend on the same instance
having multiple instances is doable by using: https://react.i18next.com/latest/i18nextprovider (allows also nesting if the right context provider wraps the inner components)
mixing instances in same component could be some extra work as you can't use the provider in that case but have to import the needed instances yourself and use the t
function from them
regarding tests...if those are unit tests even if you use the unmocked t...you still somehow inject an i18n instance to the component (using provider or whatever)...I would move those assertions for right i18n usage to e2e tests.
@angusryer simpler would be to create a custom backend that handles both or using https://github.com/i18next/i18next-chained-backend on the same instance
having multiple instances is doable by using: https://react.i18next.com/latest/i18nextprovider (allows also nesting if the right context provider wraps the inner components)
mixing instances in same component could be some extra work as you can't use the provider in that case but have to import the needed instances yourself and use the
t
function from themregarding tests...if those are unit tests even if you use the unmocked t...you still somehow inject an i18n instance to the component (using provider or whatever)...I would move those assertions for right i18n usage to e2e tests.
Thanks for helping me explore this. I managed to remove the need for two instances by just fetching another translation resource after login and using addResourceBundle
to merge it with the existing one from the first instance. This ended up being quite easy 🤗
For tests, after removing one i18n
instance, I created a __mock__
folder with a near-copy of my actual i18n config module (but made to be synchronous). Then I had to add jest.mock('../my/custom/i18n')
to all my test files
Is that possible to enable typings somehow if multiple instances are used?
Is that possible to enable typings somehow if multiple instances are used?
I don't think so... but maybe @pedrodurek has an idea for the future?
How do you guys mock <I18nextProvider i18n={i18n}>
? I keep getting the error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Even though the component works perfectly during normal operation. I have mocked this so far:
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate hook can use it without a warning being shown
useTranslation: () => {
return {
t: (str: string) => str + MOCK_TRANSLATION_POSTFIX,
i18n: {
// eslint-disable-next-line @typescript-eslint/no-empty-function
changeLanguage: () => new Promise(() => {}),
},
};
},
}));
I have a page which contains 2 React apps, each having their own i18n.js config file like the one below. The last app to load seems to conflict with the "translation" namespace of the first app and triggers an error. The only way that I can have both of them load successfully is to import the i18n.js from the first app and add additional namespaces to it at runtime. However, doing it this way creates a dependency between two unrelated apps.
Occurs in react-i18next version i18next 14.1.1 / react-i18next 10.0.2
Expected behaviour I expected that two separate i18next instances would not overwrite each other's translation namespaces.