React - intl with external translation file - javascript

I'm trying to implement language support in the base version of a react app (create using npm create-react etc...)
I've installed react-intl but every time I search on google a guideline, I found a lot of not-working solutions.
What I want is using the classic <FormattedMessage id="myid" > in my component.
Messages are stored in a SEPARATE file called './locales/en.json' or './locales/en.js'.
In my app.js im using
import {addLocaleData, IntlProvider} from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
addLocaleData(enLocaleData);
and I used:
<IntlProvider locale='en' messages="i-dont-know-what-insert-here">
<Index />
</IntlProvider>
I've tried a lot of solution.
FIRST APPROACH:
en.json is built like this:
{
"base.user":"Username"
[...]
}
SECOND APPROACH:
en.js is build like this:
export const ENGLISH = {
lang: 'en',
messages: {
"base.user":"Username"
[...]
}
}
These are the example I found on google but I don't know how to use these files inside the app.
As next step, I have to allow the user to change language pressing a flag button but for now, I will happy to see translation files working.
Thanks.

I've found a solution so I will post here for others.
FIRST: I've used second approach with lang.js files.
So you have:
export const ENGLISH = {
lang: 'en',
messages: {
"base.user":"Username"
[...]
}
}
In your app.js or in your root.js where you are using IntlProvider, include the files with:
import { ENGLISH } from './locales/en';
In you app.js (or your root js) use this:
class App extends Component {
constructor(props)
{
super(props);
this.state = {
locale : 'en',
messages : ENGLISH.messages
}
this.changeLanguage = this.changeLanguage.bind(this);
}
and then:
<IntlProvider locale={this.state.locale} messages={this.state.messages}>
<Index changeLanguage = {this.changeLanguage} />
</IntlProvider>
You can see the changeLanguage. That is the second question I post above.
This is the changeLanguage method:
changeLanguage(lang)
{
switch(lang)
{
case "it": this.setState({local:lang}); this.setState({messages:ITALIAN.messages}); break;
default: this.setState({locale:'en'}); this.setState({messages:ENGLISH.messages}); break;
}
}
Passing the method to the Index component, allow it to change language using some sort of event.
In your child Component (in my case Index) you have to define a changeLanguage method mapped on father method:
changeLanguage = (lang) => {
this.props.changeLanguage(lang);
}
Finally, if you have a flag button (o something else) you can use:
onClick={ () => this.changeLanguage('it') }
React will reload component automatically so every
<FormattedMessage ...
you have, will be translated dinamically

Related

Load function from external script using #loadable/component in React

I have a JSON file with several filepaths to scripts that I want to be able to load dynamically into my React app, to build each component based on specifications that are in the metadata. Currently I have the metadata in my app as a Metadata data object.
metadata.json:
{
"component1": { "script": "./createFirstLayer.js" },
"component2": { "script": "./createSecondLayer.js" }
}
Each script exports a function that I want to be able to use to construct the component. For troubleshooting purposes, it currently only returns a simple message.
function createFirstLayer(name) {
return name + " loaded!";
}
export default createFirstLayer;
I did some research and identified the #loadable/component package. Using this package as import loadable from "#loadable/component";, I attempted to load my script into App.js like this:
async componentDidMount() {
Object.keys(Metadata).forEach(function(name) {
console.log(Metadata[name].script);
var createLayer = loadable(() => import(Metadata[name].script));
var message = createLayer(name);
console.log(message);
});
}
Everything I have tried throws the TypeError createLayer is not a function. How can I get the function loaded?
I have also attempted the lazy method.
I have recreated a working demo of my problem here.
EDIT: I have tried to put this at the top of my app
const scripts = {};
Object.keys(Metadata).forEach(async function(name) {
import(Metadata[name].script).then((cb) => scripts[name] = cb);
});
This causes the TypeError Unhandled Rejection (Error): Cannot find module './createFirstLayer.js'. (anonymous function)
src/components lazy /^.*$/ groupOptions: {} namespace object:66
I have also attempted
const scripts = {};
Object.keys(Metadata).forEach(async function(name) {
React.lazy(() => import(Metadata[name].script).then((cb) => scripts[name] = cb));
});
My goal is to be able to call the appropriate function to create particular layer, and match them up in the metadata.
You don't need #loadable/component for two reasons.
You can accomplish your goal with dynamic imports
'#loadable/component' returns a React Component object, not your function.
To use dynamic imports simply parse your JSON the way you were, but push the call to the import's default function into state. Then all you have to do is render the "layers" from within the state.
Like this:
import React, { Component } from "react";
import Metadata from "./metadata.json";
class App extends Component {
constructor(props) {
super(props);
this.state = { messages: [] };
}
async componentDidMount() {
Object.keys(Metadata).forEach(name=> import(`${Metadata[name].script}`).then(cb =>
this.setState((state, props) => ({ messages: [...state.messages, cb.default(cb.default.name)] }))));
}
render() {
return (
<div className="App">
{this.state.messages.map((m, idx) => (
<h1 key={idx}>{m}</h1>
))}
</div>
);
}
}
export default App;
Here is the working example

How do you use edit.js and save.js when using #wordpress/create-block

I'm just now learning WP block development, however, I've been creating NextJS React apps for over a year now, and have an okay grasp on React in general.
The WP documentation points to using npx #wordpress/create-block to generate a new block file structure. This is pretty cool, and saves a lot of typing! Out of the box, it looks like this:
registerBlockType( 'create-block/some-block-name, {
.... other stuff ...
edit: Edit,
save,
}
This command generates and edit.js and a save.js. In concept, that seems pretty self explanatory, but how do you pass attributes through to create editable blocks?
registerBlockType( 'create-block/some-block-name', {
.... other stuff ...
edit: Edit, <--- how do we pass attributes to this? (points to edit.js)
save,
}
All documentation or examples I can find simply pass (props) into edit: and save: in the index.js file. Like this:
registerBlockType( 'create-block/some-block-name', {
.... other stuff ...
edit: (props) => {
... do some stuff ...
},
save: (props) => {
... do some stuff ...
},
}
What is the new WordPress standard for this? Should I be using edit.js and save.js, or just do all the work in the index.js file, and ignore these files?
I've been able to figure this out.
I was getting lost in the WordPress documentation, as most of it refers to using the edit and save parts of the index.js for functionality. There is other documentation that has you put the functionality into the edit.js and save.js files.
The combination of passing the array of {attributes, className, setAttributes} in the react hook like way, as well as using the WP useBlockProps() was what I was missing in the documentation.
Here's a generic index.js file that registers a block:
import { registerBlockType } from '#wordpress/blocks';
import './style.scss';
import Edit from './edit';
import save from './save';
registerBlockType( 'my-custom-blocks/header-bg-line', {
attributes: {
content: {
type: "string",
source: "html",
selector: "h2",
},
},
supports: {
html: true,
},
edit: Edit,
save,
});
Here are the sample edit.js and save.js files that work correctly:
edit.js
import { useBlockProps, RichText } from '#wordpress/block-editor';
export default function Edit({ attributes, setAttributes, className }) {
const blockProps = useBlockProps();
return (
<h2 {...blockProps}>
<RichText
tagName="span"
value={attributes.content}
allowedFormats={["core/italic"]}
onChange={(content) => setAttributes({ content })}
placeholder={__("Heading with a background line...")}
/>
</h2>
);
}
save.js
import { useBlockProps, RichText } from '#wordpress/block-editor';
export default function save({ attributes }) {
const blockProps = useBlockProps.save();
return (
<h2 {...blockProps}>
<RichText.Content
tagName="span"
value={attributes.content}
/>
</h2>
);
}
Look inside the index.js file. The save.js and edit.js are just imported there. I think it is up to you, whether you use them or simply put everything inside index.js. If your functions get large, it is probably better to put them in separate files.

Expected T, got Object in Vue-Prop of custom Object-Type

Description
Hello there,
I would like to share my Vue-components using bit.dev.
I got a Vue-component like this:
<template>
...
</template>
<script>
import CustomItem from "../../../../objects/CustomItem";
export default {
name: "Test",
props: {
item: {
type: CustomItem,
},
},
};
</script>
As you can see, this component requires the prop to be a specific object.
This is the CustomObject
export default class CustomItem {
constructor ({id, name}) {
this.id = id;
this.name = name;
}
// provide cool functions here
}
This works fine in my project, but not if I include this this way:
<template>
<div v-if="!$wait.is('item.loading')">
<MyComponent :item="item"/>
</div>
</template>
<script>
import MyComponent from '#bit/myproject.my-component'
import CustomItem from '#bit/myproject.custom-item';
export default {
name: 'Home',
components: {MyComponent},
data () {
return {
item: {}
};
},
beforeRouteEnter (to, _from, next) {
const promises = [
axios.get (`/api/item/1`)
];
next (vm => {
vm.$wait.start ('item.loading');
axios.all (promises)
.then (([itemRes]) => {
vm.item = new CustomItem(itemRes.data.data);
}).finally(()=>{
vm.$wait.end ('item.loading');
});
});
},
};
</script>
In this case I get this error:
[Vue warn]: Invalid prop: type check failed for prop "item". Expected T, got Object
found in
---> <MyComponent> at resources/js/components/Items/MyComponent.vue
What did I miss here?
Edit
As I can see, the component #bit/myproject.my-component which has been provided by bit.dev, provides a packed and minified version of my component. In there, the prop looks like this:
props:{item:{type:function t(e){var n=e.id,r=e.name})...
So I guess this is why this happens.
Basically it seems that in your project actually there are 2 classes CustomItem:
the one you imported relatively:
import CustomItem from "../../../../objects/CustomItem";
and the one you imported as an external package:
import CustomItem from '#bit/myproject.custom-item';
So I would try first to check if it works when you unify your imports to one form:
import CustomItem from '#bit/myproject.custom-item';
But is JS world things are not simple sometimes and even this may not help you - sometimes even referring to CustomItem in this way does not guarantee that there won't be more than one CustomItem in your production codebase. The solution I would suggest is to enforce 'duck-typing' in a custom props validator, if it's really important to check the type of the prop. You still cannot use JS instanceof as it won't work, even checking item.prototype.name === 'CustomItem' is not a good idea, as class names are changed during code minimalization, so duck-typing seems to be the only reasonable solution for you.

Do React or Next.js Internally Clean Class Properties?

In the code provided below I am able to see this.mBaseService is defined above the first debugger but not below the second debugger.
Is anyone able to explain why this is the case? One theory I had is that React or Next.js may be internally clearing properties.
import Link from 'next/link';
import React, { Component, Fragment } from 'react';
import ReactDropzone from 'react-dropzone';
import { Container, Row, Col, Button, Jumbotron, ListGroup, ListGroupItem } from 'reactstrap';
import DefaultHomeView from '../components/default-home-view';
import Page from '../components/page';
import EteeReport from '../components/etee-report';
import Layout from '../components/layout';
import { ServiceBaseService } from '../services/service-base/service-base.service';
export default class extends Page {
mBaseService = new ServiceBaseService();
constructor(props) {
super(props);
this.state = {
files: [],
};
console.log('it exists!', this.mBaseService);
debugger;
//this.mBaseService = new ServiceBaseService();
}
fHandleOnDrop = async files => {
debugger;
const oResponse = await this.mBaseService.fpPost('/reports', {
oDataToPost: JSON.stringify(files),
});
// TODO: if valid csv then parse and chart the data
this.setState({
files: this.state.files.concat(files),
});
};
fClearFiles = () => {
this.setState({
files: [],
});
};
render() {
return (
<Layout {...this.props} navmenu={false} container={false}>
{!this.state.files.length && (
<DefaultHomeView {...this.props} fHandleOnDrop={this.fHandleOnDrop} files={this.state.files} />
)}
{this.state.files.length && <EteeReport {...this.props} {...this.state} fClearFiles={this.fClearFiles} />}
</Layout>
);
}
}
This problem is likely specific to how the code is transpiled with Babel. As explained in this related answer, class fields (arrow methods) are transpiled to constructor code and this is replaced with _this, _this2, etc. temporary variables where needed to mimic the behaviour of lexical this in arrows.
A property may not be available on this in debugger but on temporary _this? variable which is considered proper context in original code.
In this specific case this is caused by the fact that fHandleOnDrop is passed as callback:
<DefaultHomeView {...this.props} fHandleOnDrop={this.fHandleOnDrop} files={this.state.files} />
This means this.props.fHandleOnDrop() has been dereferenced and is being called with wrong this , while the function uses _this? variable internally to refer to proper context:
fHandleOnDrop = async files => {
console.log(this.mBaseService) // should be ok
eval('console.log(this.mBaseService)') // should fail
This likely wouldn't be the case if Babel was configured to not transpile arrows to ES5 target by not using es2015 preset.
Regardless of these concerns, there is always a chance that this behaviour is specific to particular development tools.

Blacklist React components

Is there a way to define a function to hook before each component in my app is mounted?
The idea is that if a component is blacklisted it doesn't mount at all.
The solution must leave the components unmodified for backward compatibility and should run in production (so rewire and other testing tools are probably off the table but open to suggestions :) )
Example
//something like this...
ReactDOM.beforeEachComponentMount( (component, action) => {
if(isBlacklisted(component)){
action.cancelMountComponent();
}
}
Could you write a simple Babel plugin that transforms blacklisted components to a noop functional component () => {} at compile time?
You could wrap the required components inside a higher order component that checks whether the component is blacklisted or not.
for example :
class YourComponent extends Component {
constructor(props){
super(props);
}
render(){
return(
// your component goes here ..
);
}
}
export default WithPermission(YourComponent);
check if the component needs to be rendered or not inside the HOC WithPermission.
function withPermission(YourComponent) {
class WithPermission extends React.Component {
constructor(props) {
super(props);
}
// you can check the props inside ComponentDidMount and set a flag if
// the component satisfies the criteria for rendering.
render() {
const {blacklistedComponents,...rest} = this.props;
if(!blackListedComponents){
return <YourComponent {...rest} />
}
else{
return null;
}
}
}
}
There is no such functionality out of box.
You may shim React rendering cycle, I mean shim React.createElement method and validate component before it is added to VDOM
All JSX is processed through React.createElement
e.g. at the start of app add
let React = require('react');
let originalCreateElement = React.createElement;
React.createElement = function() {
let componentConstructorOrStringTagName = arguments[0];
if (isBlacklisted(componentConstructorOrStringTagName)) {
return null;
}
return originalCreateElement.apply(this, arguments);
}
The best idea I can think of is to "shim" react and Component
if you are using webpack you can use this:
https://webpack.js.org/guides/shimming/
in the bottom line that means instead of importing react you will import your own class of react.
In your new class you could extend React Component and place a check on the render function or something similar.
You could implement a custom ESLint rule and catch this as soon as a dev tries to use a blacklisted components. The id-blacklist rule is similar to what you want, but at the identifier level. The source code looks simple. Maybe you can adapt it to disallow more then just identifiers.
Consider the following solution:
Let there be a file where you declare which components are blacklisted:
let blacklist = [{
name: 'secretComponent',
invoke: (props)=> {
return <SecretComponent ...props />
},
isBlacklisted: true
},{
name: 'home',
invoke: (props)=> {
return <HomeComponent ...props />
},
isBlacklisted: false
},{
name: 'login',
invoke: (props)=> {
return <LoginComponent ...props />
},
isBlacklisted: false
}];
Define a Higher Order Component like below:
function renderIfNotBlacklisted(name) {
let component = blacklist.map(x=> x.name == name); //blacklist from above
if (!component.isBlacklisted){
return component.invoke();
} //else can be handled as you will
//You can keep a default component to render or send empty values
}
Call this component in the render function wherever you want this feature to work. This way you have a centralized location to managed blacklisted components (blacklist.json can be in the root of react project or fetched from API on first run)

Categories

Resources