Page flickers when lazy-loading translations with i18next-http-backend - javascript

I'm not sure if this is actually an issue with the plugin and my configurations, or if it is just standard behaviour.
I'm using react-i18next with i18next-http-backend to load translation files from the public folder. The lazy-loading is a nice bonus but I'm actually using the backend plugin because the files are managed externally (by non-programmers) and I can't know upfront which files exist. I'm not working server-side here so I can't read from the file system directly.
Problem: I have a collapsible section whose content is only rendered on expanding the section. When that content requires a translation file that was not previously loaded, the fetching of the file seems to trigger a page reload: it flickers and scrolls up.
It seems strange to me that the page is flickering because of fetching a file. I suppose that i18next is updating because it's loading a new namespace and that causes the flickering. Does that make sense? If so, is there a way to tell the http-backend all the namespaces (i.e, all the filenames in /public/locales), still keeping the lazy-loading? Am I missing something in my configuration?
Here's my configuration:
import en from './en/translation.json';
import de from './de/translation.json';
const localResources = { ...de, ...en };
const customLocalBackend = {
type: 'backend',
init: function (services, backendOptions, i18nextOptions) {
/* use services and options */
},
read: function (language, namespace, callback) {
callback(null, localResources[language][namespace]);
},
};
export const i18n = i18next
.use(initReactI18next)
.use(ChainedBackend)
.use(LanguageDetector)
.init({
lng: 'de',
fallbackLng: 'de',
supportedLngs: ['de','en'],
load: 'languageOnly',
debug: true,
backend: {
backends: [
HttpBackend, // load resources from /public folder
customLocalBackend, // load local resources
],
backendOptions: [
{
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
{},
],
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
keySeparator: false,
});
Thanks for any help!

The problem was caused by a <React.Suspense fallback={<div />}> that was wrapping the full react app. When a new translation was injected at run time, the fallback div was rendered, causing the "flickering".
I removed the wrapper and set react: { useSuspense: false } in the i18n configuration and everything is working as expected now.

Related

Cannot get Barba JS transition to work on page change

I'm trying to get Barba JS, alongside GSAP, implemented on my React website.
For reference, I have followed this video tutorial here, this tutorial of course, is not in React.
Here is my folder structure which showcases all of the relevant files for this transition effect:
theme
public
index.html
src
components
Header
Header.js
pages
Homepage
Contact
utils
anim.js
helpers.js
App.js
index.js
I have the following packages installed:
gsap - version 3.8.0
#barba/core - version 2.9.7
Current results
No console errors and no compilation errors.
When switching pages, there's no transition. It almost feels like barba isn't initiated.
Demo:
As the demo involves a few files, I have created a codesandbox here.
Edit:
Have updated my barba transition code and have added debug: true. Then, when hovering over my contact page button, the console shows the error: [#barba/core] Error: Fetch error at XMLHttpRequest.o.onerror?
import { pageTransition } from "./helpers";
import barba from '#barba/core';
export function delay(n) {
n = n || 2000;
return new Promise((done) => {
setTimeout(() => {
done();
}, n);
});
}
barba.init({
debug: true,
sync: true,
transitions: [
{
async leave(data){
const done = this.async();
pageTransition();
await delay(1000);
done();
}
}
]
});
Dificult to say but you can set debug: true, inside the initialization of barba so it will spit out logs of whats happening ;)
barba.init({
debug: true,
sync: false,
//views das paginas
views: [{.....
I have since concluded that Barba JS is not compatible with React. Seems like the library needs updating to work with React Router.
More details here

Adding external javascript to document page in Docusaurus v2

I'm building a component library of just HTML snippet and corresponding js/css and I'm using Docusaurus to document those compoents. I have a document page for each component. On the document page there is an example of each component. I'd like to make the components functional (click events, keyboard nav, etc.) so I have attached the component javascript via a plugin:
module.exports = function (context, options) {
return {
name: 'docusaurus-plugin-component-assets',
injectHtmlTags() {
return {
headTags: [
{
tagName: 'link',
attributes: {
rel: 'stylesheet',
href: 'https://example.com/css/component.min.css',
},
},
],
postBodyTags: [
{
tagName: 'script',
attributes: {
src: 'https://example.com/js/component.min.js',
},
},
],
};
},
};
};
In my docusaurus.config.js I've added my plugin:
...
plugins: [
'docusaurus-plugin-sass',
path.resolve(__dirname, 'src/plugins/docusaurus-plugin-component-assets.js')
],
...
This successfully adds the stylesheet and javascript in the correct locations. However the javascript never executes. It appears that my component javascript fires before the documentation app loads.
What am I missing? What is the correct way to add external javascript to documentation pages?
EDIT: I'm using "#docusaurus/core": "2.0.0-beta.0",
I struggled with this too (on Docusaurus v2). Eventually I understood what the Client Modules documentation was saying and did the following, which worked for me both for the initial page load and the page loaded after a navigation event. (Since this is a single-page app, it's not a full page load when you're just navigating around the documentation, and I had to handle that case separately.)
Create a new file at plugins/my-script.js (or whatever you want to call it).
Add clientModules: [require.resolve('./plugins/my-script')], to your config in docusaurus.config.js.
Insert code like this into your new file:
import ExecutionEnvironment from '#docusaurus/ExecutionEnvironment';
const doYourCustomStuff = () => {
// your JS code goes here
}
export function onRouteDidUpdate({location, previousLocation}) {
// Don't execute if we are still on the same page; the lifecycle may be fired
// because the hash changes (e.g. when navigating between headings)
if (location.pathname === previousLocation?.pathname) return;
doYourCustomStuff();
}
if (ExecutionEnvironment.canUseDOM) {
// We also need to setCodeRevealTriggers when the page first loads; otherwise,
// after reloading the page, these triggers will not be set until the user
// navigates somewhere.
window.addEventListener('load', () => {
setTimeout(doYourCustomStuff, 1000);
});
}
Then put your JS code inside the given function.
Caveat: your effects will still be broken when hot-loading changes from a yarn start dev environment. The not-too-painful workaround is to manually reload in such cases.
You can embed your components in Live Code block Docusaurus V2.
You will need to install the package using npm or yarn.
npm install --save #docusaurus/theme-live-codeblock
module.exports = {
plugins: ['#docusaurus/theme-live-codeblock'],
themeConfig: {
liveCodeBlock: {
/**
* The position of the live playground, above or under the editor
* Possible values: "top" | "bottom"
*/
playgroundPosition: 'bottom',
},
},
};
You can find detailed information about this process by using the link below.
https://docusaurus.io/docs/markdown-features/code-blocks#interactive-code-editor
It should be noted that this only works with react components.

Using i18next for a react production build causes the translation to display only strings

I believe I have searched all parts of the internet for an answer and fixes but none seem to work.
Issue :
When building a react app for production that uses i18n as a translation will cause the production build to only display the strings only as a result and not the translated text itself (see picture reference)
The weird part is that inside localhost when the development server is running it display the translation correctly. (see picture for reference)
After building the production build, inside the browser console (using chrome) it displays the following error : Fetch API cannot load file:///Documents/streaming_site/build/static/locales/en/translation.json. URL scheme must be "http" or "https" for CORS request.
After seeing this error, I imediately came to the assumption that it was inside my i18next.js file that was causing the issue. Here is the file :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import XHR from 'i18next-xhr-backend';
// don't want to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const Languages = ['en', 'fr'];
i18n
.use(XHR)
// load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
// learn more: 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({
lng : 'en',
react: {
useSuspense: false,
wait: true
},
fallbackLng: 'en',
debug: false,
whitelist: Languages,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
nsSeperator : false,
keySeperator : false,
backend:{
loadPath: () => {
var cors = require('cors');
var app = cors();
// check the domain
const host = window.location.host;
return (host === 'index.html#' ? '':'') + '/static/locales/{{lng}}/{{ns}}.json';
},
}
});
export default i18n;
Furhtermore inside my index.js file I added these extra fixes to ensure that the app would properly display the translated text on a production build :
i18next.init({// <-- This was added
interpolation: {escapeValue: false},
});
i18next.init().then(() => // <-- This was added
ReactDOM.render(
<React.StrictMode>
<Suspense fallback={<div>Loading...</div>}>// <-- This was added
<I18nextProvider i18n={i18next}>// <-- This was added
<App />
</I18nextProvider>
</Suspense>
</React.StrictMode>,
document.getElementById('root')
)
);
Alas, the same error occurs (the one from the third point) after making an npm run build
This leads me to believe that it is impossible without a backend server hosting the translation.json files for en and fr to have them be accessed locally.
My questions is the following: Is it possible to have i18n translation run locally after building the production build without needing a server to host the json files? If yes how would I proceed?
For references I have tried a few fixes from this website such as :
React i18next Backend-Path different in local and production environment
react-i18next doesn't translate
Allow Access-Control-Allow-Origin header using HTML5 fetch API
I've gotten close when I attempted --> res.header('Access-Control-Allow-Origin', "*"); although it comes at the cost of posing some security issue and I don't want that. Although if this is a potential fix, I am willing to try it.
So here I am out of ideas... :/
Looks like there is some misconfig of the i18next service.
First of all, i18next-http-backend & i18next-xhr-backend are solving the same problem, fetching json files.
Remove the second one (i18next-xhr-backend) since it is deprecated in favor of i18next-http-backend.
Change the backend.loadPath to be a string, your code doesn't make sense.
it should look like:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
// don't want to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const Languages = ['en', 'fr'];
i18n
.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({
lng: 'en',
react: {
useSuspense: false,
wait: true,
},
fallbackLng: 'en',
debug: false,
whitelist: Languages,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
nsSeperator: false,
keySeperator: false,
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
export default i18n;

react-i18next: How to sync Browser and Express LanguageDetector?

In my server's routes, I do something like this (Note: The i18next-express-middleware is already in the pipeline, and Server Side Rendering is working good):
export default function (req, res) {
const lng = req.language.toUpperCase()
console.log(lng) // Always display [EN]
MyModel
.findOne(query, { fieldEN: 1, fieldFR: 1 })
.exec()
.then(res => reply(res[`field${lng}`]))
.catch(err => handle(err))
}
So I try to return the right version of the field, base on the USER's selected language.
But no matter what language is selected on the browser side, on the server side it is always set to the default, EN.
Is there a way to let the server side LanguageDetector know about the current language on the browser side ?
This is my Client i18n init:
import i18n from 'i18next'
import Backend from 'i18next-xhr-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n
.use(Backend)
.use(LanguageDetector)
.init({
whitelist: ['en', 'fr'],
fallbackLng: 'en',
preload: ['en', 'fr'],
// debug: true,
interpolation: {
escapeValue: false
},
ns: ['home', 'channel', 'common'],
defaultNS: 'home',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
},
react: {
wait: true,
bindI18n: 'languageChanged loaded',
bindStore: 'added removed',
nsMode: 'default'
}
})
export default i18n
And this is my server i18n init:
import i18n from 'i18next'
import Backend from 'i18next-node-fs-backend'
import { LanguageDetector } from 'i18next-express-middleware'
i18n
.use(Backend)
.use(LanguageDetector)
.init({
whitelist: ['en', 'fr'],
fallbackLng: 'en',
preload: ['en', 'fr'],
// debug: true,
interpolation: {
escapeValue: false
},
ns: ['home', 'channel', 'common'],
defaultNS: 'home',
backend: {
loadPath: `${__dirname}/public/locales/{{lng}}/{{ns}}.json`,
jsonIndent: 2
}
})
export default i18n
In fact the issue was not my i18n init config.
When doing SSR, we must keep in mind that we have two distinct instances of i18next, one running on the server, and the other in the browser.
On the instance that you create on the server, the current language will be detected base on your config. Then you will render on the server, with something like this:
<I18nextProvider i18n={i18n_}>
<StaticRouter location={req.url} context={{}}>
<Route component={App} />
</StaticRouter>
</I18nextProvider>
But when your browser app will launch, it will use whatever it will detect as the current language from its own config.
So the first thing to do, is to initialize the browser app with i18n info from the server. So when building the markup server side, just create two properties on window object to hold initialStore and initialLanguage (Note: I use hbs):
window.__initialI18nStore__ = {{json initialI18nStore}}
window.__initialLanguage__ = {{json initialLanguage}}
Then on the browser app, you will render with something like this:
<I18nextProvider
i18n={i18n}
initialI18nStore={window.__initialI18nStore__}
initialLanguage={window.__initialLanguage__}
>
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
</I18nextProvider>
So the browser app will use the same language and store from the server.
But you are not done yet. Initializing the browser app with the same language don't link both instances of i18next, if you change the language on the browser, and press refresh, you will still have the previous language, coming from the server, because the server instance of i18next is not aware of the change you did.
The thing that fix all this is the caches setting, for the LanguageDetector. You have to specify it on both config, browser and server:
detection: {
caches: [..., 'cookie', ...]
}
When you use the cookie for example, then each time the browser change the language, the cookie will be updated, and then on the server side, the behavior of the Language detector will be altered, it will first try to get the current language from the cookie, and set it as req.i18n.language. So both instance will now be in sync through that cookie.
May be you should read the example razzle-ssr on react-i18next github repo, and check these links to learn more about LanguageDetector options:
for browser and for node
Guess issue on github https://github.com/i18next/react-i18next/issues/471 gives the answer...
And no stackoverflow...i won't write the full reply here again ;)
With code pasted here it will / should work...but reading above issue - there is a missing piece - creating a new instance (beside the one getting in req) and setting the language there -> leads to desync if relying on detection and not setting initialLanguage right...

reactJS i18n reloading page

I'm using react-i18next in my reactjs app.
Problem is when I change the language the app reloads and always starts from main route.
is there a way to redirect on same page or change language without reload page?
thanks
UPDATE
I18n.js
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import {de} from "../../locales/de";
import {en} from "../../locales/en";
i18n
.use(LanguageDetector)
.init({
resources: {
en: en,
de: de
},
fallbackLng: 'de',
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
keySeparator: '.',
interpolation: {
escapeValue: false, // not needed for react!!
formatSeparator: ','
},
react: {
wait: true
}
});
export default i18n;
Change Language:
const { t, i18n } = this.props;
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
how do you change language? using querystring?
if you call i18next.changeLanguage(lng); there won't be a change, just rerender in new language...
as a sample see: https://github.com/i18next/react-i18next/blob/master/example/webpack2/app/components/View.js#L50
Had the same issue. I was using i18n-js and react navigation to manifest stacks and tabs. This thread was the only one I could find on the internet. However, it did not lead me to the solution. Anyway, I still managed to sort it out and would share my solution.
Check if you also translate the names of your stacks and tabs, most likely it should be the tab one. The translated name will make react navigation lost as it no longer recognize the pages being recorded in the route, as the tab names are completely new in another language. That is why it jumps to the page wherever it finds it is the top of all navigators. The walkaround is to use label for the texts you want to translate and display while keeping a static name for your pages.

Categories

Resources