React web app - i18n - translate with button click - javascript

I am trying to translate a React web app with the click of a button using i18n with the translations coming from .js files, not .json.
Here's the setup:
I have the i18n file:
const resources = {
en: enTranslation,
de: deTranslation,
};
i18n
.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: "de",
debug: true,
resources,
interpolation: {
escapeValue: false,
},
});
export default i18n;
The imported "enTranslation" and "deTranslation" files are .js files with the respective language options. I import image files into those to use for some variables so a JSON file wouldn't work.
Excerpt example:
import pen from "../images/pen.jpg";
import table from "../images/table.jpg";
export const enTranslation = {
translation: {
imagePen: pen,
imageTable: table
}
}
The setup of the web app is as follows:
I have a "home.js" file where I define the structure of the page, like this:
const Home = () => {
const { t, i18n } = useTranslation();
return (
<>
<HeroSection {...**heroSectionContent**} />
<InfoSection {...infoSectionContents[0]} />
<Services {...cardSectionContent} />
<InfoSection {...infoSectionContents[1]} />
);
};
export default Home;
// page content objects
const **heroSectionContent** = {
header: i18next.t("header"),
description: i18next.t("intro"),
};
The contents for the different sections are all stored in constants below the React components, I included the hereSectionContent as an example.
This all works fine and if I change the fallback language in the i18n file, it shows the correct text. However, I would like to include a language switch for the user to be able to change the language, preferably without reloading the website. One section is made up of a multi-page questionnaire so reloading would throw the user back to page 1 and lose all the entered data.
Here's the LanguageSwitcher function I'm using:
function LanguageSwitcher() {
const { i18n } = useTranslation();
return (
<div className="select">
<select
value={i18n.language}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
<option value="de">Deutsch</option>
<option value="en">English</option>
</select>
</div>
);
}
export default LanguageSwitcher;
I noticed that if I refer to any of the text of the translation files directly out of the React return component, it works. Example:
Instead of writing
<HeroP>{heroSectionContent.description}</HeroP>
Changing it to
<HeroP>{t("intro")}</HeroP>
Works!
Is there any way to get this language switcher to work without having to write the i18n references into the return section?
Sorry if something is rather unclear or poorly worded, still pretty new to React.
Many thanks in advance!

Related

i18next languages functionality is slow

I am using i18next in a Gatsby and React app to handle internationalization. The website is in french and english and works well. The only issue is that when I set the language to french and refresh, I notice a short delay where language is not yet loaded, so it gives me english version (which is the language I set for fallback), and quickly returns to french.
My i18next config file:
import i18n from "i18next";
import fr from "./i18n/fr.json";
import en from "./i18n/en.json";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
const resources = {
fr: {
translation: fr
},
en: {
translation: en
}
};
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: "en",
returnObjects: true,
interpolation: {
escapeValue: false
}
});
export default i18n;
Do you have any idea how to remove this delay and load the page directly to the chosen language ?
I had this problem. Firstly, I changed fallbackLng like this
import Cookies from 'js-cookie';
const cookieLanguage = Cookies.get('i18next');
fallbackLng: cookieLanguage ? cookieLanguage : 'en'
then I wrote in my onClick function
const [langChanged, setLangChanged] = useState(null);
const language = Cookies.get('i18next');
const changeLanguageHandler = (e) => {
const newLang = e.target.innerText.toLowerCase();
if (language !== newLang) {
window.location.reload(true);
broadcastChannel.postMessage("language changed");
setLangChanged(newLang);
}
}
And I used langChanged state like dependency in useEffect
useEffect(() => {
if (langChanged) {
i18next.changeLanguage(langChanged)
}
}, [langChanged])
So it returns same delay to every language
I suppose the fallback language is server-side rendered, and other languages are not.
If you click "view page source" in the browser you can see the original HTML code. I suppose you will see the english text, even if french is displayed on the page.
(Note that "inspect" will display french, because this includes all dynamic changes to the DOM, while "view page source" will display the original server response.)
So you need to render non-default languages on the server as well:
In case you store the language in a cookie, then you can query the cookie at the server-side, and send the correct text accordingly.
Alternatively you can use the URL to inform the server about the desired language (e.g. `/fr/mypage).
I don't use i18next, but you probably need to set the "initial store" to the desired language on the server-side, instead of set the fallback language as "initial store" and set the desired langage afterwards.
I read that you can set initialI18nStore and initialLanguage for the I18nextProvider
See also react.i18next Server Side Rendering

Next.js import with variables or conditionally import

import { keyFeatures } from 'common/data/AppClassic';
I am new to Next.js and using a template.
I have at least managed to succesfully add i18n, and I don't want to rebuild the whole template and the components... There is already a file in AppClassic that serves the content (pictures, text content ect). The easiest thing I thought of would be just duplicating this, and putting these files in different subpaths like 'en/common/data/AppClassic' or 'de/common/data/AppClassic' - And then somehow to import it with the dynamic locale const or conditionally render it, so if the locale const is 'en' then one file is imported, but if the const is 'de', then the other file is imported.
const router = useRouter();
const { locale } = router;
import { keyFeatures } from { locale } + '/common/data/AppClassic';
Is there a way to do something like that, and if so, could you provide some examples - since I have actually no Idea what I am doing.
I would be very grateful.
You could work your way with Next.js dynamic imports like the example:
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
For more info check their official docs:
https://nextjs.org/docs/advanced-features/dynamic-import

Making a classic javascript 3rd party app work within an existing react/node app

I have an app that does video chat already built in react/node.
I am trying to add a feature that opens up a shared whiteboard that the participants in the video chat can use.
I did some research and AwwApp is the one I like the best for this shared whiteboard functionality.
AwwApp does not have react specific code examples or modules. However, they do provide a straightforward widget that uses a .js file they host.
I am trying to implement this classic javascript widget into my existing react app and running into some issues - likely due to something simple that I'm just missing.
Here's the simple AwwApp quick start with the js file to include and how to call it the classic way: https://awwapp.com/apis/V2/docs/
Here is the code from my "AwwBoard.js" file that I am using to create a component that renders the whiteboard anywhere I want to import it and return in react:
import React, { Component } from 'react';
import Cookies from 'universal-cookie';
import Script from 'react-load-script'
// Get cookie for meeting room that this whiteboard instance will appear in. Will be used after I get whiteboard working.
const cookieMgr = new Cookies();
const room = cookieMgr.get('room');
// The main AwwApp external javascript file to include, I use react-load-script to load it below
const awwAppScript = 'https://awwapp.com/static/widget/js/aww3.min.js';
// Create a React component I can display using <AwwBoard /> anywhere I want a whieboard to appear
class AwwBoard extends Component {
handleScriptCreate() {
this.setState({ scriptLoaded: false });
};
handleScriptError() {
this.setState({ scriptError: true });
};
handleScriptLoad() {
this.setState({ scriptLoaded: true });
};
// Function I created to render the board. Per https://awwapp.com/apis/V2/docs/ - should embed the whiteboard widget in the div with id aww-wrapper below
handleBoardRender() {
var aww = new AwwBoard('#aww-wrapper', {
});
return aww;
};
render() {
return (
<div id="aww-wrapper">
<Script
url={awwAppScript}
onCreate={this.handleScriptCreate.bind(this)}
onError={this.handleScriptError.bind(this)}
onLoad={this.handleScriptLoad.bind(this)}
/>
{this.handleBoardRender()}
</div>
)
};
};
export default AwwBoard;
Any guidance would be greatly appreciated. I'm sure there is an easy way to do this, I'm just not seeing it after doing some of my own trial and error.
Thanks!

How can I set the index page of my Gatsby site to be one of the dynamically generated pages?

I have a Gatsby site that queries information from a Wordpress REST API with GraphQL to dynamically create the site pages. I'd like to set my index page to be the homepage that is being created dynamically i.e home.html
I saw this post that was similar
On Gatsby CMS how can i set the about page as a index page
However, they have an about.js file that corresponds to their about page, meaning they can export it as a component and use it in index or they can even just copy the contents of that file over to index.js. The homepage that I want to set as my index is being generated dynamically and using a GraphQL query that can't be used outside of the page.js template. So I don't see an easy way to copy that over to another file.
I guess my last option would be to set my server to point to the static file in public/home.html and serve that as the site root, but the person in that posting tries to deter people from doing that.
Any ideas?
Here is page.js template that generates the pages of the site:
const PageTemplate = ({ data }) => (
<Layout>
{<h1 dangerouslySetInnerHTML={{ __html: data.currentPage.title }} />}
{
renderBlocks(gatherBlocks(data.currentPage.acf.page_blocks, data))
}
</Layout>
);
export default PageTemplate;
export const pageQuery = graphql`
query ($id: String!) {
currentPage: wordpressPage(id: {eq: $id}) {
title
id
parent {
id
}
template
acf {
page_blocks {
block_type {
acf_fc_layout
cs_title
cs_text
}
wordpress_id
}
}
}
}
`;
And here is my index page:
import React from "react"
import Layout from "../components/global/Layout"
const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to the Tank Gatsby site.</p>
<p>Now go build something great.</p>
</Layout>
)
export default IndexPage
I experienced the same situation today. I used the following approach to use my dynamically created page with uri '/home'(fetched from wordpress using GraphQL query) as the home page of my Gatsby site:
Delete the default index.js file in your pages directory.
In gatsby-node.js file, change the uri
of page from '/home' to '/' just before using the CreatePage API.
Here is the sample code to achieve the desired result:
// loop through WordPress pages and create a Gatsby page for each one
pages.forEach(page => {
if(page.uri==='/home/')
page.uri = '/'
actions.createPage({
path: page.uri,
component: require.resolve(`./src/templates/${page.template.templateName}.js`),
context: {
id: page.id,
},
})
})
In the above code, pages refer to the pages fetched from WordPress using GraphQL.
I could not find an easy way to create index page programmatically. Made it work nonetheless, details below.
createRedirect is valid approach but might affect SEO and definitely affects E2E tests cause actual page content gets rendered with a small delay.
Another thing to consider is that having pages/index.js file is required in order to get index.html file generated on production build. This gets in the way of using createPage({ path: '/', ... cause in my case programmatically created index page was overwritten by the static one (made of pages/index.js). This looks like a bug to me (or rather not supported feature). Corresponding github issue.
looks like deletePage and createPage gatsby-node APIs work asynchronously, hence we have to delete index page created from static file and create the one we want in the same callback. Not 100% sure about this one, but that's my observation.
onCreatePage API is a good candidate since it gets called upon original index page creation and we can take that one out and replace it with the custom one, programmatically created.
There is a catch however - CreatePageArgs interface (unlike CreatePagesArgs) doesn't provide reference to graphql, hence fetching data might be tricky.
Final solution:
export function onCreatePage(args: CreatePageArgs): void {
const { page } = args;
if (page.path === '/') {
const { deletePage, createPage } = args.actions;
const indexPageComponentPath = path.resolve(
'./src/pages/index.tsx',
);
deletePage({
path: '/',
component: indexPageComponentPath,
});
createPage({
path: '/',
component: yourComponentPath,
});
}
}
There is a solution: use createRedirect in gatsby-node.js.
E.g.:
index.tsx
import React from 'react'
export default () => <></>
gatsby-node.js
...
exports.createPages = async ({ actions }) => {
const { createRedirect } = actions
createRedirect({
fromPath: '/',
toPath: '/home',
isPermanent: true,
redirectInBrowser: true,
})
}
...
I was able to address this by copying the contents of the page.js template into index.js , but instead of using a regular GraphQL query, which cannot be used outside of the page template, I used useStaticQuery instead and hardcoded the id of the index page I was retrieving data from.

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