Translation for Non-Components. (I have already looked at Intl) - reactjs

Does app fabric have something that supports translation of data directly (NOTE: I don't want React's Intl since that is meant for localising data before it is rendered). I need to localise the data present in the state and update the state with the translated data.
React's intl is intended to be used with components and I require translation of data present outside components. I don't want to use the createIntl module since that requires manual provision of locale & messages.
This is the code that I'm dealing with. It is present outside a component.
export function func () {
client.get (url, {}).then (
response => {
// handle error
}).then(data => {
// This is where i want to translate the data based on the locale i am in
// so that the data this translated data is present in the state
}
// error-handling, more code ...
}

You can use i18next to translate data in plain js.
Basic Example:
import i18next from 'i18next';
i18next.init({
lng: 'en', // if you're using a language detector, do not define the lng option
debug: true,
resources: {
en: {
translation: {
"key": "hello world"
}
}
}
});
// initialized and ready to go!
// i18next is already initialized, because the translation resources where passed via init function
document.getElementById('output').innerHTML = i18next.t('key');
By the way, why not translate data on the server-side? You may encounter some cases like export excel which may require translation, too.

Related

Load a list of internal files in react

I'm implementing a localized react app using react-intl. I have a folder with all translations. I want to dynamically import all the translations and also get the filenames to display a dropdown with available languages on the page.
Currently I have to hardcode each language:
import {IntlProvider, FormattedMessage} from 'react-intl'
import messages_de from '../translations/de.json'
import messages_en from '../translations/en.json'
const messages = {
'de': messages_de,
'en': messages_en,
};
const i18nConfig = {
defaultLocale: 'en',
messages,
};
Is there a way to get a list of files (e.g. before compiling an app) which I can then access within react components?
I ended up with the following solution:
// search for .json files
const webpackContext = require.context('../translations', false, /\.json$/)
const filenames = webpackContext.keys() // => ['./de.json, './en.json']
const key_value_pairs = filenames.map(name => [name.match(/\/(\w+)\.json$/)[1], webpackContext(name)])
// make a dictionary from the array of arrays
const messages = Object.fromEntries(key_value_pairs)
This code imports dynamically all files into a dictionary where the key is the filename.
require.context takes a directory to search, a flag indicating whether subdirectories should be searched too, and a regular expression to match files against.
The returned function webpackContext is the most confusing part. Although it is a function, it also has properties, because everything is an object in js. webpackContext.keys() returns a list of filenames in given directory matching given regex.
Finally, webpackContext(name) imports a file.
The code above is equivalent to the following code:
import messages_de from '../translations/de.json'
import messages_en from '../translations/en.json'
const messages = {
'de': messages_de,
'en': messages_en,
};

How can I access intl.formatMessage() in mobx store in reactJS app?

I have two JSON files with names 'en.json' and 'fr.json' which has all the translations.
en.json
{ "General.LearnMore": "Learn More" }
I have a mobx store (.ts file) and I want to access intl.formatMessage() in that class.
It is easier in functional components. I can use useIntl() hook but how can I do the same in a store file (non-component).
When I write the below code in store file:
const messages = defineMessages({
SMS: {
id: 'General.LearnMore',
},
});
const cache = createIntlCache();
const locale = localStorage.getItem('locale')!;
const intl = createIntl({ locale, messages: {} }, cache);
console.log(intl.formatMessage({ id: 'General.select' }));
console.log(intl.formatMessage({ id: messages.sms.id }));
It gives this error:
What I am missing and How can I fix this? Please help!
In createIntl you pass an empty object to messages so intl doesn't find the ids.
To fix, import the en.json:
import enMessages from '../path/to/en.json'
and pass it on cretaeIntl:
const intl = createIntl({ locale, messages: enMessages }, cache);
If you created the messages object with defineMessages just for the purpose of getting value from intl - you can delete it, it doesn't give access to the key-value files (createIntl doesn't automatically get the data of intlProvider, it needs full initialition with locale and messages)
If you want the messages to be updated every time the locale is switched so here a good example
Hope this will help!

What is the difference between domElement and pageContext when using them within an spfx react project?

I need to understand the difference between this:
this.domElement.getElementsByClassName
which references domElement. This is not usable as I get errors saying it doesn't exist on 'myproject'.
I also use this:
this.props.context.pageContext.web
etc..
What is the difference between the two or are they both different in their usage?
Please note, I understand what domElement is, but how it's used here is not what I understand.
I need to understand because I see a lot of demonstrations of features (which I need to use) that use domElement in their spfx project, although it's not usable by me!
C
pageContext is built by SharePoint client runtime (JS)libraries, it contains many SharePoint client side objects.
Stefan Bauer had shared good explanation for domElement below.
https://n8d.at/this-domelement-is-your-new-document-object-in-spfx-web-parts-development/
Update:
In webpart.ts, not in component .tsx.
Update:
Component property interface.
import { WebPartContext } from '#microsoft/sp-webpart-base';
export interface IReactSpFxProps {
description: string,
context: WebPartContext,
userToken:string
}
.ts
const element: React.ReactElement<IReactSpFxProps> = React.createElement(
ReactSpFx,
{
description: this.properties.description,
context:this.context,
userToken: accessToken
});
ReactDom.render(element, this.domElement);
.tsx:
public componentDidMount() {
var dom=this.props.context.domElement;

How does React Lazy fetch components?

I recently read about React Lazy and how it "loads" the components during run time when they are required to be rendered. I assume that "loading" here means fetching the component from the server and then rendering it.
So my question is, how does React manage this fetching of components? How does it know the exact path from where to fetch this component (given that our code will mention the relative path but fetching will require complete server path)? Does it depend on Webpack for this?
Let's look into the React code. React.lazy is defined as follows.
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
$$typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
if (__DEV__) {
// ... additional code only in development mode
}
return lazyType;
}
As you can see, React.lazy requires a Promise which resolves to a module with a default export containing a React component (freely cited by React Docs). This also means that not React resolves the file, but import() does. import() works as documented in the MDN.
The async import() is a new function in ES6 which is not available in all browsers but can be polyfilled by Webpack and Babel/Typescript/others.
What you often see is code like the following, which automatically splits the imported file away by Webpack.
import(/* webpackChunkName: "xyz" */ './component/XYZ')
This creates a new javascript xyz.js next to your bundle script.
If you don't use Webpack, you need to create those files by yourself. Webpack just reduces the work required from you. So you don't absolutely depend on Webpack. This approach might look like the following:
// ./component/xyz.js
export default function() { return <div>Component</div> }
// ./main.js
const OtherComponent = React.lazy(() => import('./component/xyz.js'));
export default function() { return <div>Component</div> }
And the file structure:
| public
|---| main.js
|---| component
|---| --- | main.js
As you see, no webpack required. It just makes your life easier.

React application with external plugins

I'm building a React application bundled using Parcel or Webpack.
The application should be able to embed external React components
developed by third-parties and hosted elsewhere as modern javascript modules:
// https://example.com/scripts/hello-plugin.js
import React from 'react';
export default class HelloPlugin extends React.Component {
render() {
return "Hello from external plugin!";
}
}
Host application loads these components using asynchronous import like this, for example:
// createAsyncComponent.tsx
import * as React from 'react';
import { asyncComponent } from 'react-async-component';
export default function createAsyncComponent(url: string) {
return asyncComponent({
resolve: () => import(url).then(component => component.default),
LoadingComponent: () => <div>Loading {url}....</div>,
ErrorComponent: ({ error }) => <div>Couldn't load {url}: {error.message}</div>,
})
}
But looks like bundlers don't allow importing arbitrary urls as external javascript modules.
Webpack emits build warnings: "the request of a dependency is an expression" and the import doesn't work. Parcel doesn't report any errors, but fails when import(url) occurs at runtime.
Webpack author recommends using scriptjs or little-loader for loading external scripts.
There is a working sample that loads an UMD component from arbitrary URL like this:
public componentDidMount() {
// expose dependencies as globals
window["React"] = React;
window["PropTypes"] = PropTypes;
// async load of remote UMD component
$script(this.props.url, () => {
const target = window[this.props.name];
if (target) {
this.setState({
Component: target,
error: null,
})
} else {
this.setState({
Component: null,
error: `Cannot load component at ${this.props.url}`,
})
}
});
}
Also, I saw a similar question answered a year ago where the suggested approach also involves passing variables via a window object.
But I'd like to avoid using globals given that most modern browsers support modules out of the box.
I'm wondering if it's possible. Perhaps, any way to instruct the bundler that my import(url) is not a request for the code-split chunk of a host application, but a request for loading an external Javascript module.
In the context of Webpack, you could do something like this:
import(/* webpackIgnore: true */'https://any.url/file.js')
.then((response) => {
response.main({ /* stuff from app plugins need... */ });
});
Then your plugin file would have something like...
const main = (args) => console.log('The plugin was started.');
export { main };
export default main;
Notice you can send stuff from your app's runtime to the plugin at the initialization (i.e. when invoking main at the plugin) of the plugins so you don't end up depending on global variables.
You get caching for free as Webpack remembers (caches) that the given URL has already loaded so subsequent calls to import that URL will resolve immediately.
Note: this seems to work in Chrome, Safari & firefox but not Edge. I never bothered testing in IE or other browsers.
I've tried doing this same sort of load with UMD format on the plugin side and that doesn't seem to work with the way Webpack loads stuff. In fact it's interesting that variables declared as globals, don't end up in the window object of your runtime. You'd have to explicitly do window.aGlobalValue = ... to get something on the global scope.
Obviously you could also use requirejs - or similar - in your app and then just have your plugins follow that API.
Listen to the Webpack author. You can't do (yet) what you're trying to do with Webpack.
You will have to follow his suggested route.

Resources