React intl template string throws a missed id warning - reactjs

I'm trying to inject some values into translated sentence via react-intl.
Following official documentation I was able to define some template message into my Localization file and it looks like that.
ratingMsgTemplate: {
id: "_ratingMsg",
defaultMessage: "{pointCount, plural, one {{point}} other {{points}}}"
},
this field passed directly inside localization object.
than I'm using a custom Plural component which is very simple
import * as React from "react";
import { injectIntl } from "react-intl";
import { inject, observer } from "mobx-react";
const i18nPluralNumber = (props: any) => {
const { locale, intl, msgId, ...msgParams } = props;
let finalMessage;
if (!(msgId in locale.messages)) {
console.warn("Id not found in i18n messages list: " + msgId);
finalMessage = msgId;
} else {
finalMessage = locale.messages[msgId];
}
return (
<span className="plural-number-intl">
{intl.formatMessage(finalMessage, { ...msgParams })}
</span>
);
};
export default inject("locale")(injectIntl(observer(i18nPluralNumber)));
here is the example of use
<I18nPluralNumber
msgId="ratingMsgTemplate"
pointCount={shopPoints}
point={formatMessage("point")}
points={formatMessage("points")}
/>
It works like a charm except this pesky thing.
In console I'm receiving this message:
I18nPluralNumber.tsx:19 [React Intl] Missing message: "_ratingMsg" for locale: "de", using default message as fallback.
and it's correct because there is no any _ratingMsg id inside my translate file. Actually I added this id only because it's necessary following react-intl docs and without this ID it isn't working at all
Could anyone give some tip/advice how to manage this stuff?
I'll be appreciated for any info.

If it helps anyone. I didn't found any normal solution except this crutch.
I've added the id with this key and duplicated the defaultMessage for each of it.
eg
ratingMsgTemplate: {
id: "_ratingMsg",
defaultMessage: "{pointCount, plural, one {{point}} other {{points}}}"
},
I just create the id - _ratingMsg with the defaultMessage value
_ratingMsg: "{pointCount, plural, one {{point}} other {{points}}}",
ratingMsgTemplate: {
id: "_ratingMsg",
defaultMessage: "{pointCount, plural, one {{point}} other {{points}}}"
},
now it doesn't throws warning about missed ID and it works like a template string.
Weird but I didn'd find any better solution

Related

How to access current value from writable?

I want to create a custom wrapper for i18n to translate content of the site by clicking the lang button.
Currently, I have something like this.
<script>
import { localization } from './localiztion.ts';
</script>
<p>{localization.t("hello")}</p>
<button on:click={localization.toggleLocale}></button>
p which holds a text (which should be translated) and button which triggers translation.
To split logic from UI I moved localization logic into a different file. It looks like this
const resources = {
"en": {
"hello": "Hello",
},
"uk": {
"hello": "Привіт"
}
}
export function createLocalization() {
let store = writable("en");
return {
unsubscribe: store.unsubscribe,
toggleLocale: () => {
store.update((previousLocale) => {
let nextLocale = previousLocale === "en" ? "uk" : "en";
return nextLocale;
});
},
t: (key: string): string => {
// How to get access to the current store value and return it back to UI?
// I need to do something like this
return resources[store][key]
}
}
}
export const localization = createLocalization();
The problem I have I need to access the current local from within a t function. How can I do this?
I could pass it from UI like
// cut
<p>{localization.t("hello", $localization)}</p>
// cut
by doing this I achieve what I want, but the solution is too cumbersome.
Any advice on how I can do this?
You could get the store value via get, but this is be a bad idea, as it would lose reactivity. I.e. a language change would not update your text on the page.
A better approach is defining it as a store. Since stores currently have to be at the top level to be used with $ syntax, it is more ergonomic to split it into a separate derived store:
export let locale = writable("en"); // Wrap it to restrict it more
export let translate = derived(
locale,
$locale => key => resources[$locale][key],
);
This way you can import this store, which contains a function for translating keys:
import { translate } from '...';
// ...
$translate('hello')
REPL
(The stores can of course also be created differently and e.g. injected via a context instead of importing them.)

Typescript useState React Hook Destructuring Error (Not Returning Array?)

I'm futzing about with a simple React app for the first time, and looking to set up a simple MVC to connect with an ASP.NET backend, all being done in Visual Studio 2019 (which has caused some headaches with React). However, now that I'm trying to implement models, I'm finding that all of the suggestions for strongly typing the useState hook aren't working for me.
According to a few tutorials like this one or this one, it should be as simple as adding a type in brackets, like const [state, setState] = useState<Type>({}), since it has a generic implementation. Unfortunately, for me, this throws the error "Uncaught TypeError: Invalid attempt to destructure non-iterable instance."
This thread here suggested that switching from an array to an object by changing the [] to {}, however that simply made the two parameters I passed be undefined, so I had no state or way to update said state.
I went down the path of reading until I had a brief understanding of array destructuring, so I understand that the idea is to pass an array with two constants that will be assigned in-order the elements of the array returned by the useState hook. So, I tried manually destructuring the array, setting a constant useState[0] and useState[1] separately. When I tried this for an untyped hook, it worked as expected. When I tried this for the typed hook, I got some errors about there not being elements, and upon printing out the object, found not an array like the untyped hook, but a boolean. It seems that a typed useState is returning the value "false" instead of an array.
At this point, my best guess is that I have some dependencies that don't implement the typed useState, but I'm really hitting a stone wall on troubleshooting. Anyone have some idea on what I'm doing wrong?
Edit: The testing file I have set-up -
import React, { useState } from 'react'
import 'bootstrap/dist/css/bootstrap.min.css';
import { Product } from '../Models/Product';
const Account = () => {
//Works as-intended
const test = useState(5);
//Returns false when logged
const test2 = useState<Product>({
"ProductID": "p#corn", "Description": "Delicious corn", "ImageLink": "https://testlink.com", "Name": "Corn", "Price": "2.99", "Category": "Food"
});
//What should work, to my understanding, however this makes the route crash when it's navigated to because of the "Inavlid attempt to destructure non-iterable instance
const [test3, setTest] = useState<Product>({});
function clickHandler() {
console.log(test)
}
function clickHandler2() {
console.log(test2)
}
return (
<div className='wrapper'>
<button onClick={clickHandler}>Test</button>
<button onClick={clickHandler2}>Test2</button>
</div>
)
}
export default Account;
The Product model -
export interface Product {
ProductID: string;
Description: string;
ImageLink: string;
Name: string;
Price: string;
Category: string;
}
Here's a CodeSandbox with an example somewhat related to your case, which demonstrates how useState is meant to work on everyone else's machine.
As you will see from testing it and messing with the example I shared, useState with a generic type specified should be fine and dandy and behave just as you expect.
That underlines that there's something about your local machine or project environment which is special to you.
import React, { useCallback, useState } from "react";
import "./styles.css";
interface Product {
Name: string;
Price: number;
}
const productA = {
Name: "Corn",
Price: 2.99
};
const productB = {
Name: "Frozen Orange Juice",
Price: 10_000
};
export default function Show() {
const [product, setProduct] = useState<Product>(productA);
const toggleProduct = () =>
setProduct(product === productA ? productB : productA);
return (
<div className="App" onClick={toggleProduct}>
<h1>{product.Name}</h1>
<h2>{product.Price}</h2>
</div>
);
}
Since you asked about how to get the best responses...
Ideally when posting try to create a problem case which other people can see failing. If you attempt to do this (e.g. using a CodeSandbox) but it works fine there, then it's a matter of working on cleaning up your own local environment until you figure out what is unique about your setup which makes code break for you but no-one else.
Ideally if you can't create it within a single page repro (sandbox), because it's something to do with your project structure, create a runnable reproduction by sharing the actual project structure and its code and configuration publically on github so people can recreate your problem by checking it out and building/running it. That way they will soon be able to track down what's distinctive about your project.
Often this process of creating a repro will help you figure out the problem anyway - e.g. you start with a vanilla create-react-app typescript skeleton from https://create-react-app.dev/docs/adding-typescript/ to populate a blank github repo and your problem case suddenly starts working there - now you have a basis for bisecting your problem by adding your project's special features to the simple repro until it breaks.

React-Intl pass translation as string variable and not object

I'm in the process of adding react-intl to a payment app I'm building but hitting a snag. I apologize if this has been addressed somewhere. I scoured the issues and documentation and couldn't find a direct answer on this (probably just overlooking it).
Use Case: Once a payment is processed I'd like to give the user the option to tweet a translated message indicating they've donated.
Problem: Twitter uses an iframe to "share tweets", and requires a text field as a string variable. When I pass my translation I get [object Object] in the tweet instead of the translated text. This makes sense based on my understanding of the translation engine. But I cant seem to find a way to pass a string rather than a translation object.
what I get when I use {translate('example_tweet')}
const translationText = object
what I need
const translationText = 'this is the translated text'
Question
How do I get the translated text as a string variable rather than an object to be rendered on a page?
Code
button
import { Share } from 'react-twitter-widgets'
import translate from '../i18n/translate'
export default function TwitterButton () {
return (
<Share
url='https://www.sampleSite.org' options={{
text: {translate('example_tweet')},
size: 'large'
}}
/>
)
}
translate
import React from 'react'
import { FormattedMessage } from 'react-intl'
const translate = (id, value = {}) => <FormattedMessage id={id} values={{ ...value }} />
export default translate
I was able to solve it without messing with react-intl. I built a function that scrapes the text I need from the page itself. So it really doesnt matter what the language is. I was hoping to figure out how to snag the translations as variables, but this gets the job done.
function makeTweetableUrl (text, pageUrl) {
const tweetableText = 'https://twitter.com/intent/tweet?url=' + pageUrl + '&text=' + encodeURIComponent(text)
return tweetableText
}
function onClickToTweet (e) {
e.preventDefault()
window.open(
makeTweetableUrl(document.querySelector('#tweetText').innerText, pageUrl),
'twitterwindow',
'height=450, width=550, toolbar=0, location=0, menubar=0, directories=0, scrollbars=0'
)
}
function TwitterButton ({ text, onClick }) {
return (
<StyledButton onClick={onClick}>{text}</StyledButton>
)
}

What's the proper way to grab an object from my Entities dictionary in a Normalized state and pass it to a component?

I have a state that looks like:
entities: {
pageDict: {
['someId1']: { name: 'page1', id: 'someId1' },
['someId2']: { name: 'page2', id: 'someId2' },
['someId3']: { name: 'page3', id: 'someId3' }
}
}
lists: {
pageIdList: ['someId1', 'someId2', 'someId3']
}
And a Component that looks like:
const Pages = ( props ) => {
return (
<div>
props.pageIdList.map(( pageId, key ) => {
return (
<Page
key={ key }
pageObj={ somethingHere } // What to do here?
/>
);
})
</div>
);
}
To grab the object, I would need to do:
let pageObj = state.entities.pageDict[ pageId ];
I guess I can pass the state.entities.pageDict as props from the Containers.. but I'm trying to look at the selectors pattern and looking at this:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
and I'm wondering if I'm doing this wrong, can someone give me some insight? I just want to make sure I'm doing this correctly.
EDIT: I got the idea of using Entities from the https://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html and the SoundCloud Redux project
EDIT2: I'm looking at things online like this https://github.com/reactjs/redux/issues/584 but I'm questioning if I'm using this incorrectly and I'm not sure how to apply this to my own project
EDIT3: I'm leaning on creating a selector that will get the pageIdList and return a list of the pageobjects from the pageDict.. that way I already have the object to pass into my Page component.
I think I follow what you're trying to do here. With Redux try thinking of your user interface as always displaying something immutable: rather than "passing something to it" it is "reading something from the state". That when when your state changes your user interface is updated (it isn't always this simple but it is a pretty good start).
If I read your answer correctly you have a Map of pages:
//data
{
id1: {...pageProperties} },
id2: {...pageProperties} },
id3: {...pageProperties} },
}
and your page list is the order these are displayed in:
ex:
[id2, id3, id1]
Your page object might look something like this:
//Page.js
class Page extends React.Component {
render() {
const { pageIdList, pageEntities } = this.props //note I'm using props because this is connected via redux
return (
<div>
{ pageIdList.map((pageId, index)=>{
return <Page key={index} pageEntity={pageEntities[pageId]} /> //I'm just pulling the object from the map here
}}
</div>
)
}
}
//connect is the Redux connect function
export default connect((store)=> { //most examples use "state" here, but it is the redux store
return {
pageEntities: store.pageEntities,
pageIdList: store.pageList
}
})(Page)
Now when we want to change something you update the state via a dispatch / action. That is reduced in the reducer to display the new state. There are a lot of example out there on how this works but the general idea is update the state and the components take care of displaying it.
The result of this code (and there might be some typeos) is that you should see:
Page id: 2, Page id: 3, Page id: 1 because the list of the pages in the state is 2, 3, 1 in the example I gave.
To answer your question specifically what the entity I'm pulling is the global Redux Store (not the internal component state). 'Map State to Props' is really 'Map Store to Props' as the 'state' is part of React and the 'store' is your Redux store.
Does that help? React+Redux is really nice once you figure it out, it took me a solid month to understand all the ins and outs but it really does simplify things in the long run.

How to configure Material-UI Autocomplete with secondaryText on dynamic data

I'm facing a issue and I feels like lacking documentation around this.
I'm build a site where I get an AutoComplete component to search for data in the database, and it's working fine, it gets the data, I've build validation working on this, everything goes ok.
If I use only one Text to render on the MenuItem it works fine, like display a name property I've got from database, but if I try to display something like Name as a primaryText and Size as a SecondaryText it simply doesn't render the autocomplete menuItem results, even being filled correctly.
For the record I'm trying to achieve something like this: https://cloud.githubusercontent.com/assets/9424409/17258323/33764c38-557b-11e6-808c-ac22287703d0.gif
But I can only make component work with something like this: https://cloud.githubusercontent.com/assets/9424409/17258376/66015990-557b-11e6-8c12-9016da6e1f2e.gif
Here is the code as it works and render my data, using only textKey:
this.dataSourceConfig = {text: 'textKey', value: 'valueKey', validationKey:'validationKey'};
this.state = {
dataSourceDrug:[{textKey: 'Text data goes here', valueKey: "", validationKey:'validation value goes here'}]}
render(){
return(
<AutoComplete
onNewRequest={this.onDrugNewRequest}
onUpdateInput={this.handleDrugUpdateInput}
searchText={this.state.valueDrug}
dataSource={this.state.dataSourceDrug}
dataSourceConfig={this.dataSourceConfig}
/>
)
}
So how can I configure this to work rendering primary and secondary text?
I've checked docs and even issues on git but it doesn't says much to me:
http://www.material-ui.com/#/components/auto-complete
https://github.com/callemall/material-ui/issues/4852
https://github.com/callemall/material-ui/blob/master/docs/src/app/components/pages/components/AutoComplete/ExampleDataSources.js
Ok, I found the answer thanks to the light #awzx answer brought to me.
I was working with dataSourceConfig and dataSource, and to work woth primary and secondary text, it does NOT work with dataSourceConfig, so I removed the attribute from my component
dataSourceConfig={this.dataSourceConfig}
And in my for to place the data I've done this:
var updatedDataSource = [];
for (var i = 0; i < response.length; ++i)
{
var _name = response[i].name;
var _size = response[i].size;
var _val = <MenuItem primaryText={_name} secondaryText={_size}/>
updatedDataSource.push({text:response[i].name, value:(_val), valueKey:response[i].id, validationKey:'validation string here'});
}
this.setState({dataSourceDrug:updatedDataSource});
You've to configure the secondary text in your dataSource like below (second example in Material UI documentation) :
import React from 'react';
import AutoComplete from 'material-ui/AutoComplete';
import MenuItem from 'material-ui/MenuItem';
const dataSource1 = [
{
text: 'text-value1',
value: (
<MenuItem
primaryText="text-value1"
secondaryText="☺"
/>
),
},
{
text: 'text-value2',
value: (
<MenuItem
primaryText="text-value2"
secondaryText="☺"
/>
),
},
];

Resources