I'm implementing internationalization for a ReactJS webapp.
How can I avoid loading all language files?
import ru from './ru';
import en from './en';
// next lines are not important for this question from here
import locale from 'locale';
const supported = new locale.Locales(["en", "ru"])
let language = 'ru';
const acceptableLanguages = {
ru: ru,
en: en,
}
if (typeof window !== 'undefined') {
const browserLanguage = window.navigator.userLanguage || window.navigator.language;
const locales = new locale.Locales(browserLanguage)
language = locales.best(supported).code
}
// till here
// and here i'm returning a static object, containing all language variables
const chooseLang = () => {
return acceptableLanguages[language];
}
const lang = chooseLang();
export default lang;
Unfortunately there is no way to dynamically load modules in ES6.
There is an upcoming HTML Loader Spec which will allow for this functionality, so you could use a polyfill in order to use that.
const chooseLang = () => System.import(`./${language}`);
export default chooseLang;
However, this would now be promise-based so it would need to be called like so:
import language from "./language";
language.chooseLang().then(l => {
console.log(l);
});
But bear in mind, that spec could change radically (or be dropped altogether).
Another alternative would be to not store your localizations as Javascript modules, but as JSON instead, e.g.
en.json
{ "hello_string": "Hi!" }
language.js
const chooseLang = () => {
return fetch(`./${language}.json`)
.then(response => response.json());
};
Again, this would be promise based so would need to be accessed as such:
import language from "./language";
language.chooseLang().then(l => {
console.log(l.hello_string);
});
That solution would be fully ES6-compliant and would not rely on possible future features.
Looks like I am late here, but I would like to answer the approach I am using. I have an async component:
import React from 'react';
export default (loader, collection) => (
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.Component = null;
this.state = { Component: AsyncComponent.Component };
}
componentWillMount() {
if (!this.state.Component) {
loader().then((Component) => {
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
if (this.state.Component) {
return (
<this.state.Component { ...this.props } { ...collection } />
)
}
return null;
}
}
);
And we call it using:
const page1 = asyncComponent(() => import('./page1')
.then(module => module.default), { name: 'page1' });
and then we use it with:
<Route path='/page1' component= {page1}/>
This will ensure that loading is done dynamically.
Take a look at the react-loadable package - if you can somehow encapsulate your language files into components, this might solve your problem.
Related
I have established a websocket connect from my server to my client machine. I have parsed the data into an object and would like to access the data for representation on my front end.
import './App.css';
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { Component } from 'react';
const client = new W3CWebSocket('ws://xyz:9080/user');
class App extends Component {
componentDidMount() {
client.open = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const object = JSON.parse(e.data);
console.log(object.Snapshot);
}
client.onclose = () => {
console.log("Closed...");
}
}
render() {
return (<div className="App">
<h2>{ object }</h2>
</div>
);
}
}
export default App;
I want to access my object variable from the on message function and use it as a variable in my render function. How do I approach this?
You need to add local state to your class. State is a fairly foundational part of react and how it is able to reactively rerender components, so it sounds like you need to spend some time reading the docs to familiarize yourself with the basics.
That said, I'll provide an updated version of your code for demonstration purposes. Note that you used client.open when you meant client.onopen, so I've made that correction below:
import "./App.css";
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { Component } from "react";
const client = new W3CWebSocket("ws://xyz:9080/user");
class App extends Component {
constructor(props) {
super(props);
this.state = { object: "" };
}
componentDidMount() {
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const object = JSON.parse(e.data);
this.setState({ object: object });
console.log(object.Snapshot);
};
client.onclose = () => {
console.log("Closed...");
};
}
render() {
return (
<div className="App">
<h2>{this.state.object}</h2>
</div>
);
}
}
export default App;
Also, since it seems that you're probably just starting out with react, I would strongly recommend that instead of the old-style class-based components, you use learn to use hooks and functional components, which is just an overall much cleaner and easier to reason about way to write react code. We could rewrite your code as follows using the useState and useEffect hooks in an App function:
import "./App.css";
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { useEffect, useState } from "react";
export default function App() {
const [object, setObject] = useState("");
useEffect(() => {
const client = new W3CWebSocket("ws://xyz:9080/user");
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const newObj = JSON.parse(e.data);
setObject(newObj);
console.log(newObj.Snapshot);
};
client.onclose = () => {
console.log("Closed...");
};
return () => client.OPEN && client.close();
}, []);
return (
<div className="App">
<h2>{object}</h2>
</div>
);
}
Note per the docs that useEffect with an empty dependency array is more or less equivalent to componentDidMount. Note also that even though client is defined in a local scope, it won't be garbage-collected, because it is referenced in the cleanup closure (the return value of the arrow function passed to useEffect).
Finally, note that I haven't used the websocket package before, so I don't know if your usage is correct or optimal. This answer is about how to manage state in react, not how to use websocket in a react application.
Let's say I want to create a React package that will have two components, one to preload assets, and another to play/use those assets. Usage would look like this:
// Usage
import { PreloaderComponent, NotificationComponent } from 'module';
const Consumer: React.FC = () => {
render (
<>
<PreloaderComponent />
...
{ condition && <NotificationComponent />}
</>
)
}
I believe I'll need to persist state in my package... something like
// package
const assetStore = () => {
const path = 'path.mp3';
const loadedAsset;
const preload = () => {
loadedAsset = new Asset(path);
}
const getAsset = () => {
// check if preloaded
// if not, load
return loadedAsset;
}
return {
preload,
getAsset
};
}
const PreloaderComponent: null = () => {
const store = assetStore();
assetStore.preload();
return null;
}
const NotificationComponent: React.FC = () => {
// if (already instantiated)
// get access to previously instantiated store
const assetObject = assetStore.getAsset();
assetObject.play()
render (
<div> // or whatever
)
}
export {
PreloaderComponent,
NotificationComponent
};
But the above code won't work, since the NotificationComponent doesn't have access to the previously instantiated store. I considered a factory pattern but then you'd need to instantiate that factory somewhere.
How would you preload the assets by calling one component, then use those assets in another? Thanks.
A context might be the way to go. The docs describe when to use contexts like this:
Context is designed to share data that can be considered “global” for a tree of React components
So an example would be an AssetContext with a useContext-hook to simplify things:
import React, { useCallback, useContext, useState } from "react";
const AssetContext = React.createContext();
const AssetProvider = (props) => {
const [assets, setAssets] = useState([]);
const value = {
assets,
addAsset: (asset) => {
setAssets([...assets, asset]);
},
clear: () => setAssets([])
};
return <AssetContext.Provider value={value} {...props} />;
};
const useAssets = () => useContext(AssetContext)
You can use the data provided by the context with useAssets():
const Preloader = () => {
const {addAsset} = useAssets();
useCallback(() => {
addAsset({play: () => console.log('sth')})
})
return <div>
{/* */}
</div>
}
const Notifier = () => {
const {assets} = useAssets();
// example usage based on your code
const [firstAsset] = assets
if(firstAsset) {
firstAsset.play();
}
return <div>
{/* */}
</div>
}
Don't forget to encapuslate those components within the AssetProvider. It's not required to put them directly as the children of the provider but somewhere bellow it.
export default function App() {
return (
<AssetProvider>
<Preloader />
<Notifier />
</AssetProvider>
);
}
Im trying to make a site that could identify the country and set the language.
To do this in react i use to call thw window.navigator.language. The entire file:
import * as pt from "./pt";
import * as en from './en';
let translate;
if (window.navigator.language == 'pt-PT' || window.navigator.language == 'pt-BR') {
translate = pt.default;
} else {
translate = en.default;
}
export default translate
the pt / en files its just JSONS with all texts.
But window doesnt exist in nextJS
That error appears: ReferenceError: window is not defined
I need to use out of a react file.
Because i just import and use like this:
{Translate.header.label_opts_clients}
I will import and I use this in a XML (react) file and in a JSON file
like:
export const headerOptions = [
{
title: "`${Translate.header.label_opts_home}`",
...
How can i do this?
I know that exist useRouter(hook), so if you need to take the url in a page you can use.
I already had that problem, and i solved with this:
...
const { pathname } = useRouter()
const [crumbs, setCrumbs] = useState<string[]>();
useEffect(()=>{
renderCrumbs()
},[pathname])
const setHref = (links:string[], index:number|string) => {
let linkPath = ""
for (let i = 0; i <= index; i++) {
linkPath = linkPath + "/" + links[i]
}
return linkPath
}
const renderCrumbs = () => {
let links = pathname.split('/').slice(1);
let limit = links.length;
let array = [];
...
But this just work in a function component.
I tried to put the condition in a function and return the let translate, and use the translate with parentheses ({Translate().header.something}) but didnt work.
I tried to use this too (but doesnt work):
if (window !== undefined) {
// browser code
}
And i cant use hooks or components did/will mounth.
My code pictures:
My folder organization
The translates
The translate code
How i will import
The EN JSON
My other JSON that i need to use the translate
trying to do a useEffect in my _app (laoult)
Window verification in JS script and brought the JSON file to translate archive
The window object can be referenced once the DOM is rendered. Usually, it is used in useEffect hook like so -
useEffect(() => {
console.log(window) // window is not undefined here
}, [])
in your case, from what I understand, you are trying to check the language and return the content from the pt file and further use it in another react component as header options.
Here's what might work,
Change your translate file to a function -
import * as pt from './pt'
import * as en from './en'
const translate = (window) => {
const { language } = window.navigator
if(language == 'pt-PT' || language == 'pt-BR') return pt.default
else return en.default
}
export default translate
Now use it in your react file in a useEffect function -
import translate from 'YOUR_PATH_TO_TRANSLATE'
const SomeReactComponent = (props) => {
useEffect(() => {
const translatedData = translate(window) // now window is defined
let headerData = {
title: translatedData.header.label_opts_home
...
}
}, []) // componentDidMount in classes
}
Or, if you want to get the JSON header data from a separate file, you can create a separate file like so -
import translate from 'YOUR_PATH_TO_TRANSLATE'
const getHeaderData = (window) => {
const translatedData = translate(window) // now window is defined
let headerData = {
title: translatedData.header.label_opts_home
...
}
return headerData
}
and then, use it in your main component like so,
import getHeaderData from 'YOUR_PATH_TO_GETHEADERDATA'
const SomeReactComponent = (props) => {
useEffect(() => {
let headerData = getHeaderData(window)
}, [])
}
I hope this works.
I found.
If you are using NextJS v10.0.0 you can use a new advanced feature.
Internationalized Routing - i18n
https://nextjs.org/docs/advanced-features/i18n-routing
First of all you need to config your next.config.js and add the i18n module export.
If you already had some other plugins (like me) you will need to put them together.
const withImages = require('next-images')
const path = require('path')
module.exports = withImages({
esModule: false,
i18n: {
locales: ['en-US', 'pt-BR', 'pt-PT', 'es-ES'],
defaultLocale: 'pt-BR',
},
});
With my project configured for the languages that i want, I went to my translation file and used a next hook - useRouter from next/router
import * as pt from "./pt";
import * as en from './en';
import { useRouter } from "next/router"
export const traducao = () =>{
let routes = useRouter();
let translate;
if (routes.locale == 'pt-PT' || routes.locale == 'pt-BR') {
translate = pt.default;
} else {
translate = en.default;
}
return translate
}
And the i just use in my project like a function:
{traducao().homeText.button_text}
Work well, recognizes the browser language and switche.
But unfortunaly you cant use in a Json, because you are using a hook, and hooks just works in a function component.
I'm starting with react native, and when using a library called react native paper, I've come across a statement where the state is being assigned to a const as shown below.
import * as React from 'react';
import { Searchbar } from 'react-native-paper';
export default class MyComponent extends React.Component {
state = {
firstQuery: '',
};
render() {
const { firstQuery } = this.state;
return (
<Searchbar
placeholder="Search"
onChangeText={query => { this.setState({ firstQuery: query }); }}
value={firstQuery}
/>
);
}
}
Beginning of the 'Render' method, you could see const { firstQuery } = this.state;
Could someone please explain why the state is being assigned to a const named 'firstQuery', and even if it have a reason, how will the assignment correctly map the property 'firstQuery' inside the state object to the const ?
Thanks in advance. The code sample is from https://callstack.github.io/react-native-paper/searchbar.html#value
That syntax is not React nor React Native. It's just Javascript's syntax, called destructuring.
const { firstQuery } = this.state;
is equivalent to
const firstQuery = this.state.firstQuery;
just a short-hand shortcut syntax, you see 2 firstQuerys? People just don't want duplication in code, so they invented it.
See the vanilla javascript snippet below:
const object = {
name: 'Aby',
age: 100,
}
const { name, age } = object;
// instead of
// const name = object.name;
console.log(name, age);
console.log(object.name, object.age);
//=========================================
// imagine:
const obj = {
veryLongPropertyNameToType: 420
}
const { veryLongPropertyNameToType } = obj;
// instead of
// const veryLongPropertyNameToType = obj.veryLongPropertyNameToType;
Like another answer mentioned, it's just JavaScript syntax aka destructuring. If you're feeling confused and wished to just use the "vanilla" JavaScript syntax, you can take a look at below.
import * as React from 'react';
import { Searchbar } from 'react-native-paper';
export default class MyComponent extends React.Component {
state = {
firstQuery: '',
};
render() {
return (
<Searchbar
placeholder="Search"
onChangeText={query => { this.setState({ firstQuery: query }); }}
value={this.state.firstQuery} // <<<<<<<<<<< LOOK HERE
/>
);
}
}
At work, we have a custom solution for translations. The implementation is as follows:
In the smarty templates, get_string('unique_string_identifier', 'Default string') gets called in order to fetch a translated string.
The strings are stored in an SQL database.
If the string exists in the database, for the selected language (stored in session), the translated string is returned.
Else the default string is returned.
I'm currently in the process of rewriting parts of the application using React.js, and I'm implementing a javascript version of get_string (calling it getString).
The getString function lives in a global module called translate.
I need a way...
...to extract all the string identifiers and default strings from my files.
...for the react application to know which strings to request from the server (via api)
What I think would be a perfect solution is to create a babel transform that moves all getString calls to the top scope, leaving a variable as reference. This would allow me to solve both problems with relative ease.
import React from 'react'
import {getString} from 'translate'
export default class TestComponent extends React.Component {
render() {
const translatedString = getString('unique_string_identifier_1', 'Default string 1')
return <div>
{getString('unique_string_identifier_2', 'Default string 2')}
</div>
}
}
Would become something like:
import React from 'react'
import {getString} from 'translate'
const _getStringRef0 = getString('unique_string_identifier_1', 'Default string 1')
const _getStringRef1 = getString('unique_string_identifier_2', 'Default string 2')
export default class TestComponent extends React.Component {
render() {
const translatedString = _getStringRef0
return <div>
{_getStringRef1}
</div>
}
}
How would I go about doing this?
I've changed the requirements slightly, so...
import React from 'react'
import {getString, makeGetString} from 'translate'
const _ = makeGetString({
prefix: 'unique_prefix'
})
export default class TestComponent extends React.Component {
render() {
const translatedString = getString('unique_string_identifier_1', 'Default string 1 %s', dynamic1, dynamic2)
return <div>
{getString('unique_string_identifier_2', 'Default string 2')}
{_('string_identifier_3')}
</div>
}
}
becomes...
import React from 'react'
import {getString, makeGetString} from 'translate'
const _getString = getString('unique_string_identifier_1', 'Default string 1 %s');
const _getString2 = getString('unique_string_identifier_2', 'Default string 2');
const _ = makeGetString({
prefix: 'unique_prefix'
})
const _ref = _('string_identifier_3');
export default class TestComponent extends React.Component {
render() {
const translatedString = _getString(dynamic1, dynamic2)
return <div>
{_getString2()}
{_ref()}
</div>
}
}
This is actually what I have:
module.exports = function(babel) {
const {types: t} = babel
const origFnNames = [
'getString',
'makeGetString',
]
const getStringVisitor = {
CallExpression(path) {
const callee = path.get('callee')
if(callee && callee.node && this.fnMap[callee.node.name]) {
this.replacePaths.push(path)
}
}
}
const makeGetStringVisitor = {
VariableDeclaration(path) {
path.node.declarations.forEach((decl) => {
if(!(decl.init && decl.init.callee && !decl.parent)) {
return
}
const fnInfo = this.fnMap[decl.init.callee.name]
if(fnInfo && fnInfo.name === 'makeGetString') {
this.fnMap[decl.id.name] = {
name: decl.id.name,
path
}
}
})
}
}
return {
visitor: {
ImportDeclaration(path) {
if(path.node.source.value === 'translate') {
const fnMap = {}
path.node.specifiers.forEach((s) => {
if(origFnNames.indexOf(s.imported.name) !== -1) {
fnMap[s.local.name] = {
name: s.imported.name,
path
}
}
})
path.parentPath.traverse(makeGetStringVisitor, {fnMap})
const replacePaths = []
path.parentPath.traverse(getStringVisitor, {fnMap, replacePaths})
delete fnMap.makeGetString
Object.keys(fnMap).map((k) => {
const fnInfo = fnMap[k]
const paths = replacePaths.filter((p) => p.get('callee').node.name === fnInfo.name)
const expressions = paths.map((rPath) => {
const id = rPath.scope.generateUidIdentifierBasedOnNode(rPath.node)
const args = rPath.node.arguments
rPath.replaceWith(t.callExpression(id, args.slice(2)))
const expr = t.callExpression(t.identifier(fnInfo.name), args.slice(0, 2))
return t.variableDeclaration('const', [t.variableDeclarator(id, expr)])
})
fnInfo.path.insertAfter(expressions)
})
}
}
}
}
}