React internalisation SPA using i18next - javascript

I need to translate my app, but i don't knomw how to use useTranslation() in the top-level files (i store there some consts which contain some text). One of this file is
import { useTranslation } from "react-i18next";
const {t} = useTranslation()
export const selectThemeOptions = [
{ value: "choose", text: "Choose theme" },
{ value: "Algebra", text: "Algebra" },
{ value: "Geometry", text: "Geomerty" },
{ value: "Programming", text: "Programming" },
{ value: "Logic", text: "Logic" },
];
so in this case i have an error:
src\Configs\ThemesOfProblems.js
Line 3:13: React Hook "useTranslation" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
I need this array in my component, and it use in the next fragment :
<Form.Group as={Col} controlId="problemTheme">
<Form.Label>{t("userprofile.theme")}</Form.Label>
<Form.Select
name="theme"
value={values.theme}
onChange={handleChange}
isValid={touched.theme && !errors.theme}
isInvalid={!!errors.theme}
onBlur={handleBlur}
>
{selectThemeOptions.map((el, index) => {
return <option key={index} value={el.value}> {el.text} </option>
})}
</Form.Select>
</Form.Group>
And i've got a lot of such situations, i don't have any ideas how to do it

Basically it says it has to be called in a react component. It could be called in a functional component where you return your jsx or a class component that has a render method in it. If you call the function outside of one of these, then you will get this error.

You called const {t} = useTranslation(); outside of a React component, your selectThemeOptions file seems to be regular JavaScript due to the absence of JSX or a returning statement with your HTML.
Here is the correct way to do it:
/* Everything above this point is considered top-level, hence using your `useTranslation()` hook here would cause an error */
const Component = (props) => {
const { t } = useTranslation();
}
export default Component;
Here is a way to organise your translations:
• Your src folder should contain an i18n.js file with the following code:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from "../locales/en.json";
import ru from "../locales/ru.json";
const isReturningUser = "lang" in localStorage; // Returns true if user already used the website.
const savedLanguage = JSON.parse(localStorage.getItem("lang")); // Gets persisted language from previous visit.
// Get previously used language in case of returning user or set language to Russian by default.
const language = isReturningUser ? savedLanguage : "ru";
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
i18n.use(initReactI18next).init({
resources,
lng: language,
keyseparator: false,
interpolation: {
escapeValue: false,
},
});
export default i18n;
Your src folder should contain a locales folder with json files of the languages your application uses. Example: ru.json and en.json:
{
"choose": "Выбрать",
"chooseATheme": "Выбрать тему",
"algebra": "Алгебра",
"geometry": "Геометрия",
"programming": "Программирование",
"logic": "Логика",
}
Your component should look like this – note that the translations are in json files instead of your React component – :
import React from "react";
import { useTranslation } from "react-i18next";
const Component = (props) => {
const { t } = useTranslation();
const selectThemeOptions = [
{ value: t("choose"), text: t("chooseATheme") },
{ value: t("algebra"), text: t("algebra") },
{ value: t("geometry"), text: t("geometry") },
{ value: t("programming"), text: t("programming") },
{ value: t("logic"), text: t("logic") },
];
return( //Your UI )
}
export default Component;
This way, your translations wouldn't be hard-coded on your selectThemeOptions and will adapt to whichever translation your json locales contain.
Please tell me if this works.
Edit: If you want a concrete example of implementation of my solution here it is: https://github.com/YHADJRABIA/ecommerce/tree/main/src
Edit2: There might be a better solution of doing this, this is merely what worked for me.
Edit3: Following Nikita's comment, here's a solution to use the translation function outside of a react component —How to use react-i18next inside BASIC function (not component)?
P.S. Since you are from Belarus I assume that you want your translation to be made in Russian since Belarusian isn't as widely spoken.

Related

How do I load and run external Javascript code in React that have their definitions in the application and not in the imported file?

Basically, I'm trying to run a function that creates and adds a Recipe class to an array in React based on an external javascript file that is hosted online - but all the definitions are inside my React app.
The external file looks like (Recipes.js) this:
function LoadRecipes(){
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
The way I attempt to go on with this follows:
import React, {useState, useEffect} from 'react';
import RecipeManager from "../logic/RecipeManager.js";
const Recipe = RecipeManager.Recipe;
const recipesList = RecipeManager.recipesList;
const AddToRecipes = RecipeManager.AddToRecipes;
function RecipeController() {
const [loadingRecipes, setLoadingRecipes] = useState(true);
useEffect(() => {
const script = document.createElement("script");
script.src = "https://raw.githack.com/Soralei/extern/main/Recipes.js";
script.async = true;
script.onload = () => {
setLoadingRecipes(false);
}
document.body.appendChild(script);
}, []);
useEffect(() => {
if(!loadingRecipes){
window.LoadRecipes();
}
}, [loadingRecipes]);
return (
<div>
{loadingRecipes ? <p>Loading recipes...</p> : (
<>
<p>Recipes:</p>
{/*recipesList.map((a, index) => <p key={"r"+index}>{a.name}</p>)*/}
</>
)}
</div>
)
}
export default RecipeController
Note that I try to run the function using window.LoadRecipes() once the script has been imported. However, I get undefined errors when the function is run:
Recipes.js:3 Uncaught ReferenceError: AddToRecipes is not defined
at LoadRecipes (Recipes.js:3)
I'm also adding the content of RecipeManager.js for clarity. This is local logic, and the goal is to have the external function make use of it:
class Recipe{
constructor(options = {}){
this.name = options.name || "Unnamed Recipe";
this.components = options.components || [];
this.requirements = options.requirements || [];
this.craftedAt = options.craftedAt || "handcrafted";
}
}
const recipesList = [];
function AddToRecipes(Recipe){
recipesList.push(Recipe);
console.log(Recipe.name, "was added to the recipes list.");
}
const exported = {
Recipe: Recipe,
recipesList: recipesList,
AddToRecipes: AddToRecipes
}
export default exported;
Is this not possible, or am I just doing this entirely wrong?
Why am I doing this? The idea is to host the recipes online in a way that allows for other people to easily view, edit, and have the changes affect my app directly, while keeping most of the work in the React app.
You have to export the function to be able to access it.
Default export (only one per file):
function LoadRecipes(){
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
export default LoadRecipes; // export
You should import it like this:
import LoadRecipes from 'pathtofile';
Named export (multiple ones):
export function LoadRecipes() {
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
export const add (a, b) => a + b; // another one
Import like this (using { }):
import {
LoadRecipes,
add
} from 'pathtofile';
Named exports are useful to export several values. During the import, one will be able to use the same name to refer to the corresponding value. Concerning the default export, there is only a single default export per module. A default export can be a function, a class, an object or anything else. This value is to be considered as the “main” exported value since it will be the simplest to import.
You can read about JavaScript modules here

EditorJS in NextJS not able to load plugins

I am trying to get EditorJS working in NextJS. The editor loads fine without plugins, having the only paragraph as a block option. However, when I attempt to add plugins via tools prop console throws the following warning:
editor.js?9336:2 Module Tools was skipped because of TypeError: Cannot read property 'prepare' of undefined
When I click on the editor in the browser, it is throwing:
Uncaught TypeError: Cannot read property 'holder' of undefined
I have tested editor plugins in the normal React app, and they load fine. Meaning that the problem is in EditorJS and NextJS import and handling of plugins. I have tried to import editor and plugins in componentDidMount hook using require but had the same problem as with NextJS dynamic imports. Attempted to get component using React ref but found that currently NextJS has problems with getting components' refs, Tried suggested workaround but still had no result. The instance of the editor is not available until onChange is triggered, so plugins just cannot hook into the editor due to that 'prepare' property or the whole editor are being undefined until an event on editor has happened, but the editor outputs into the console that it is ready.
My component's code:
import React from "react";
import dynamic from "next/dynamic";
const EditorNoSSR = dynamic(() => import("react-editor-js"), { ssr: false });
const Embed = dynamic(() => import("#editorjs/embed"), { ssr: false });
class Editor extends React.Component {
state = {
editorContent: {
blocks: [
{
data: {
text: "Test text",
},
type: "paragraph",
},
],
},
};
constructor(props) {
super(props);
this.editorRef = React.createRef();
}
componentDidMount() {
console.log(this.editorRef.current);
console.log(this.editorInstance);
}
onEdit(api, newData) {
console.log(this.editorRef.current);
console.log(this.editorInstance);
this.setState({ editorContent: newData });
}
render() {
return (
<EditorNoSSR
data={this.state.editorContent}
onChange={(api, newData) => this.onEdit(api, newData)}
tools={{ embed: Embed }}
ref={(el) => {
this.editorRef = el;
}}
instanceRef={(instance) => (this.editorInstance = instance)}
/>
);
}
}
export default Editor;
Is there any solution to this problem? I know SSR is challenging with client side rendering of components that access DOM, but there was condition used that checked whether window object is undefined, however, it does not look like an issue in my situation.
UPDATE:
I have found a solution but it is rather not a NextJS way of solving the problem, however, it works. It does not require a react-editorjs and implemented as creation of EditorJS instance as with normal EditorJS.
class Editor extends React.Component {
constructor(props) {
super(props);
this.editor = null;
}
async componentDidMount() {
this.initEditor();
}
initEditor = () => {
const EditorJS = require("#editorjs/editorjs");
const Header = require("#editorjs/header");
const Embed = require("#editorjs/embed");
const Delimiter = require("#editorjs/delimiter");
const List = require("#editorjs/list");
const InlineCode = require("#editorjs/inline-code");
const Table = require("#editorjs/table");
const Quote = require("#editorjs/quote");
const Code = require("#editorjs/code");
const Marker = require("#editorjs/marker");
const Checklist = require("#editorjs/checklist");
let content = null;
if (this.props.data !== undefined) {
content = this.props.data;
}
this.editor = new EditorJS({
holder: "editorjs",
logLevel: "ERROR",
tools: {
header: Header,
embed: {
class: Embed,
config: {
services: {
youtube: true,
coub: true,
},
},
},
list: List,
inlineCode: InlineCode,
code: Code,
table: Table,
quote: Quote,
marker: Marker,
checkList: Checklist,
delimiter: Delimiter,
},
data: content,
});
};
async onSave(e) {
let data = await this.editor.saver.save();
this.props.save(data);
}
render() {
return (
<>
<button onClick={(e) => this.onSave(e)}>Save</button>
<div id={"editorjs"} onChange={(e) => this.onChange(e)}></div>
</>
);
}
}
This implementation works in NextJS
I will update code if I find a better solution.
UPDATE 2:
The answer suggested by Rising Odegua is working.
You have to create a seperate component and then import all your tools there:
import EditorJs from "react-editor-js";
import Embed from "#editorjs/embed";
import Table from "#editorjs/table";
import List from "#editorjs/list";
import Warning from "#editorjs/warning";
import Code from "#editorjs/code";
import LinkTool from "#editorjs/link";
import Image from "#editorjs/image";
import Raw from "#editorjs/raw";
import Header from "#editorjs/header";
import Quote from "#editorjs/quote";
import Marker from "#editorjs/marker";
import CheckList from "#editorjs/checklist";
import Delimiter from "#editorjs/delimiter";
import InlineCode from "#editorjs/inline-code";
import SimpleImage from "#editorjs/simple-image";
const CustomEditor = () => {
const EDITOR_JS_TOOLS = {
embed: Embed,
table: Table,
marker: Marker,
list: List,
warning: Warning,
code: Code,
linkTool: LinkTool,
image: Image,
raw: Raw,
header: Header,
quote: Quote,
checklist: CheckList,
delimiter: Delimiter,
inlineCode: InlineCode,
simpleImage: SimpleImage
};
return (
<EditorJs tools={EDITOR_JS_TOOLS} />
);
}
export default CustomEditor;
Then in your NextJS page, use a dynamic import like this:
let CustomEditor;
if (typeof window !== "undefined") {
CustomEditor = dynamic(() => import('../src/components/CustomEditor'));
}
And you can use your component:
return (
{CustomEditor && <CustomEditor />}
)
Source : https://github.com/Jungwoo-An/react-editor-js/issues/31

Why Does My React App Keep Failing To Compile?

I'm learning react and building a simple straightforward to do list app. However, it keeps throwing this error:
./src/App.js
Line 9:35: 'Todo' is not defined react/jsx-no-undef
Search for the keywords to learn more about each error.
I'm not sure why this error keeps throwing. I have defined my my ToDo js file - I believe I correctly exported it and then correctly imported in the App js file. Am I missing something?
App.Js File:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import ToDo from './ToDo.js'
import DoData from './DoData.js'
function App() {
const val = DoData.map(item => <Todo key = {item.id} item = {item}/>)
return (
{val}
);
}
export default App;
ToDo.js file:
import React from 'react'
function Todo(props){
return(
<div className = "todo-item">
<input type ="checkbox" checked = {props.things.completed}></input>
<p>{props.things.description}</p>
</div>
)
}
export default Todo
DoData.js:
let DoData = [
{
id: 1,
description: "go for a walk",
completed: true
},
{
id: 2,
description: "babysitting",
completed: true
},
{
id: 3,
description: "watch MadMen",
completed: false
},
{
id: 4,
description: "See the mailman",
completed: true
},
{
id: 5,
description: "Say hello to the world",
completed: false
}
]
export default DoData.js
In JavaScript, identifiers are case-sensitive
Check the case of the identifier in your import statement in the file ./App.Js vs the use of it:
import ToDo from './ToDo.js'
<Todo ... />
Notice the difference in casing between Todo & ToDo. You have 2 options to fix this linting error.
Either change your import to:
import Todo from './ToDo.js'
or
change your use to:
`<ToDo ... />`
Note: Also, double check your export statement in the file ./DoData.js.
While the identifier DoData definitely exists, it has no property called js, and so DoData.js will be undefined.
Option 1
Update
import ToDo from './ToDo.js'
to
import Todo from './ToDo.js'
as you are using <Todo key = {item.id} item = {item}/> in your app
Option 2
Update
<Todo key = {item.id} item = {item}/>
to
<ToDo key = {item.id} item = {item}/>
as you are importing component as ToDo
import ToDo from './ToDo.js'
As you are exporting ToDo as default in ToDo.js file, you can use it as any name as you want in your imports anywhere in your app. But make sure when you render the component, it matches the import name at top.

How do you test components that are wrapped with multiple higher order components?

import React from 'react'
import sinon from 'sinon'
import { mount } from 'enzyme'
import configureStore from 'redux-mock-store'
import FileListEditable from './index.js'
import {shallow} from 'enzyme'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
customer: {
clientId:'123'
}
}
const store = mockStore(initialState)
const minProps = {
files: []
}
const removeFile = sinon.spy()
const wrapper = shallow(
<FileListEditable
store={store}
{...minProps}
removeFile={removeFile} />,
{context: {store}})
test.skip('Component: <FileListEditable/>, renders', () => {
expect(wrapper.length).toBe(1)
expect(wrapper.find('Tag').length).toBe(0)
})
test.skip('Component <FileListEditable/>, Add and remove files', () => {
wrapper.setProps({
files: [
{
name: 'file1',
extension: 'txt'
},
{
name: 'file2',
extension: 'txt'
}
]
})
expect(wrapper.find('Tag').length).toBe(2)
wrapper.find('Tag').at(0).find('button').simulate('click')
expect(removeFile.called).toBe(true)
expect(removeFile.args[0][0]).toBe(0)
wrapper.find('Tag').at(1).find('button').simulate('click')
expect(removeFile.args[1][0]).toBe(1)
})
test.skip('Component <FileListEditable/>, File from documents will have link to that document', () => {
wrapper.setProps({
files: [
{
name: 'file1',
extension: 'txt',
id: 'file-document-id'
},
{
name: 'file2',
extension: 'txt'
}
]
})
expect(wrapper.find('Tag').at(0).find('a').length).toBe(1)
expect(wrapper.find('Tag').at(1).find('a').length).toBe(0)
})
These tests do not work because FileListEditable is wrapped with injectIntl and one of our own created higher order component. Which means when I use shallow rendering it will render the InjectIntl component and if I use mount I have to dive two layers. But I just can't seem to get it right. Is there a general solution for testing components that are wrapped with higher order components without having to care about the higher order component?
Thank you Daniel Lizik for sharing the link
https://github.com/airbnb/enzyme/issues/98#issuecomment-169761955
cited from the link:
Internally at Airbnb, we use a pattern like the following:
class MyComponent extends React.Component {
...
}
export default connect(MyComponent); // default export. used in your app.
export { MyComponent as PureMyComponent}; // pure component. used in tests
This will work fine with redux's connect function, but won't work out of the box with the decorator syntax. You could open a pull request to redux to have the #connect decorator expose the underlying wrapped component as a static prop like UnderlyingComponent or something like that
Does that help at all?

How to switch locale on user's request? (When using the React-Redux-i18n library)

I'm using the React-Redux-i18n binding library for redux to translate my React app.
I would also like to use React-Router to handle my routes.
I can get the example working, that is provided by React-Redux-i18n:
In app.js :
const translationsObject = {
en: {
application: {
title: 'Awesome app with i18n!',
hello: 'Hello, %{name}!'
},
date: {
long: 'MMMM Do, YYYY'
},
export: 'Export %{count} items',
export_0: 'Nothing to export',
export_1: 'Export %{count} item',
two_lines: 'Line 1<br />Line 2'
},
nl: {
application: {
title: 'Toffe app met i18n!',
hello: 'Hallo, %{name}!'
},
date: {
long: 'D MMMM YYYY'
},
export: 'Exporteer %{count} dingen',
export_0: 'Niks te exporteren',
export_1: 'Exporteer %{count} ding',
two_lines: 'Regel 1<br />Regel 2'
}
};
const store = createStore(
combineReducers({
...reducers,
i18n: i18nReducer
}),
applyMiddleware(thunk)
);
syncTranslationWithStore(store)
store.dispatch(loadTranslations(translationsObject));
store.dispatch(setLocale('nl'));
Then I can use the following in my components, to translate strings:
<Translate value="application.title"/>
Language can be changed by changing the following from for example 'nl', to 'en':
store.dispatch(setLocale('nl'));
How can I enable user's to change the language. For example:
<a href="/" onClick={changeLanguage('nl')}>Nl</a>
<a href="/" onClick={changeLanguage('en')}>En</a>
How to change setLocale, when clicking the different language links?
As the name react-redux-i18n suggests, it relies on redux to handle storing the i18n keys and values, i.e. they are stored in the reducers, specifically the i18nReducer, and the state of that reducer can be changed with actions, for example dispatch(setLocale). If you aren't already familiar with Redux, I recommend getting to know the basics.
To create a link that changes your language you essentially need to find a way to dispatch a setLocale action to the redux store with the parameter of your choosing with the onClick handler. Redux provides a nifty way of doing this by connecting store state and dispatch method to your component. For example like this:
import React, { Component, PropTypes } from 'react'
import { connect } from 'redux'
import { setLocale } from 'react-redux-i18n
class ChangeLanguageLink extends Component {
constructor(props) {
super(props)
this.changeLanguage = (e) => {
e.preventDefault() // don't navigate
this.props.changeLanguage(this.props.lang)
}
}
render() {
return (
<a href="#" onClick={ this.changeLanguage }>
{ this.props.lang.toUpperCase() }
</a>
)
}
}
ChangeLanguageLink.propTypes = {
lang: PropTypes.string.isRequired
}
// this will make this.props.changeLanguage available
const mapDispatchToProps = dispatch => {
// dispatch the react-redux-i18n setLocale action
const changeLanguage = lang => dispatch(setLocale(lang))
return { changeLanguage }
}
// using connect makes store.dispatch available for mapDispatchToProps
// and makes sure changeLanguage is added to the component props
#connect(null, mapDispatchToProps)
export class ChangeLanguageLinkContainer
extends ChangeLanguageLink {}
Then you can just utilize your language changing component like this:
<ChangeLanguageLink lang="en" />
<ChangeLanguageLink lang="nl" />
<!-- should produce html like this -->
EN
NL
Hope this helps!

Categories

Resources