I have a component that needs strings from the backend. I currently request the .po file from the server, convert it to .json and return it to my React component. I then want to be able to display those strings whilst replacing the correct values in the string, i.e.
<FormattedMessage id={dynamicId} values={dynamicVals} />
dynamicId is pulled from a separate api call, as well as dynamicVals.
My problem is that these strings are not bundled like all of my other app strings, so react-intl is unaware of them. How can I add these strings to the library client-side/async? I've attempted using defineMessages and addLocaleData, but I either am doing something incorrectly, or am not using the right api methods. Does addLocaleData provide the means to adding strings to the library? Is this possible to do?
In summary:
How can I receive
{
notifications.friendships.nowfriends: "{name} is now your friend"
}
from the api and display it using:
<FormattedMessage id='notifications.friendships.nowfriends' values={{ name: 'StackOver Bro' }} />
Thanks for the help in advance.
In case anyone wants to know what I ended up doing...
Since I already had the strings and the variables to interpolate with, I bypassed the localization library and just used the following function (thanks to the answers on this question, especially #user2501097 and #Bryan Rayner)
/**
* see: https://stackoverflow.com/questions/29182244/convert-a-string-to-a-template-string
*
* Produces a function which uses template strings to do simple interpolation from objects.
*
* Usage:
* var makeMeKing = generateTemplateString('${name} is now the king of {country}!');
*
* console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
* // Logs 'Bryan is now the king of Scotland!'
*/
const generateTemplateString = (function () {
const cache = {}
function generateTemplate(template) {
let fn = cache[template]
if (!fn) {
// Replace ${expressions} (etc) with ${map.expressions}.
const sanitized = template
.replace(/\$?\{([\s]*[^;\s\{]+[\s]*)\}/g, (_, match) => {
return `\$\{map.${match.trim()}\}`
})
// Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
.replace(/(\$\{(?!map\.)[^}]+\})/g, '')
fn = Function('map', `return \`${sanitized}\``)
cache[template] = fn
}
return fn
}
return generateTemplate
}())
export default generateTemplateString
Related
Suppose I have a function in Idris that does some computation. For simplicity, let it be stringly typed for now.
f: String -> String
How can I compile this function to JavaScript so that it can then be called from any ordinary JavaScript code?
If that is too easy, suppose f, instead of String, deals with Double or even a custom Idris data type.
I know I can compile a whole module with a Main.main function and a more or less inscrutable blob of JavaScript will be output. Can I maybe extract my function from there by hand? How should I go about it?
P.S. Despite my answering myself, I am still looking for a better solution, so welcome.
Using this example, it seems at least with the Node backend this is doable. I've marked interact as export and added a library descriptor:
module Main
import Data.String
f: Double -> Double
f x = x + 1
export interact: String -> String
interact s = let x = parseDouble s in
case x of
Nothing => "NaN"
Just x => show (f x)
main: IO ()
main = do
s <- getLine
putStrLn (interact s)
lib : FFI_Export FFI_JS "" []
lib = Data String "String" $
Fun interact "interact" $
Fun main "main" $
End
I have then compiled with the --interface flag (this fails with --codegen javascript...):
idris --codegen node --interface --output ExportToJS.js ExportToJS.idr
and the resulting .js file has this at the end:
module.exports = {
interact: Main__interact,
main: Main__interact
};
}.call(this))
This should allow you to do require("./ExportToJavaScript.js").interact("42") from Node, and there is probably an equivalent to use from a browser.
Yes, you can extract any function by hand.
Build a module as follows:
module Main
import Data.String
f: Double -> Double
f x = x + 1
interact: String -> String
interact s = let x = parseDouble s in
case x of
Nothing => "NaN"
Just x => show (f x)
main: IO ()
main = do
s <- getLine
putStrLn (interact s)
Compile it as follows:
% idris --codegen javascript --output Main.js Main.idr
A file called Main.js will be created. There will be several megabytes of more or less inscrutable JavaScript code, just as you say.
Edit this file by hand and edit it similarly to this:
--- Resistors.js
+++ Resistors-default.js
## -1,7 +1,5 ##
"use strict";
-(function(){
-
const $JSRTS = {
throw: function (x) {
throw x;
## -36130,7 +36128,3 ##
}
}
}
-
-
-$_0_runMain();
-}.call(this))
Now notice this JS file has comments in it marking the JS functions with their Idris names. For instance, corresponding to our interact function there will be located this JS function:
// Main.interact
function Main__interact($_0_arg){
const $_1_in = Data__String__parseDouble($_0_arg);
if(($_1_in.type === 1)) {
const $cg$3 = Main__bestMatch_39_($_1_in.$1, Main__manyResistors_39_());
let $cg$2 = null;
$cg$2 = $cg$3.$1;
return Prelude__Show__Main___64_Prelude__Show__Show_36_Schema_58__33_show_58_0($cg$2);
} else {
return "NaN";
}
}
If you attach this JS file to a web page as a script, you may then open JS console in a browser and interact with your Idris functions, like this:
Main__interact("10")
"11"
Hope this helps!
const fs = require('fs')
const jsdocFinder = /\/\*\*\n(.+?)\*\//gs
/**
* Convert JSDocs from a file into JSON.
* #function
* #param {String[]|String} dirs The directory or directories of the file(s) to convert.
*/
function interpret (dirs = []) {
if (typeof dir === 'string') dirs = [dirs]
const types = {}
for (const dir of dirs) {
const file = fs.readFileSync(dir, 'utf8')
const docs = jsdocFinder.exec(file)
console.log(docs)
}
return types
}
module.exports = interpret
This is my code for a function that's supposed to convert JSDocs to JSON data. In the for loop, I use a regex to capture any text in between /**\n and */. Unfortunately, it doesn't seem to be working on the files. I have logged what file equals and it should match. I have tested the Regex and it should work fine.
https://i.imgur.com/2FlmeBq.png
docs just equals null every time.
I figured out, since Windows uses CR LF, I need to do \r\n.
I've a problem integrating the Firebase with React-Native. The code below doesn't generate a listview as I expected. My assumption is that messages.val() doesn't return a correct format. When I try to console log "messages" variable it returns as follow
Object {text: "hello world", user_id: 1}
Code :
class Test extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
};
}
componentWillMount() {
this.dataRef = new Firebase("https://dummy.firebaseio.com/");
this.dataRef.on('child_added', function(snapshot){
var messages = snapshot.val();
this.setState({
dataSource: this.state.dataSource.cloneWithRows(messages)
});
}.bind(this));
}
renderRow(rowData, sectionID, rowID) {
console.log(this.state.dataSource);
return (
<TouchableHighlight
underlayColor='#dddddd'>
<View>
<Text>{rowData.user_id}</Text>
<Text>{rowData.text}</Text>
</View>
</TouchableHighlight>
)
}
render() {
return (
<View>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
automaticallyAdjustContentInsets={false} />
</View>
);
}
}
I do not know what data you have in your Firebase database, but from what I understand, you should get multiple "on_child_added" events for all items you have, so you should not pass it to "cloneWithRows" method. You should pass the whole dataset to it.
While the documentation on react native side is a bit "silent" currently about how the ListView data source works and what should be passed to "cloneWithRows", documentation in the code (ListViewDataSource.js) is pretty good in fact, and it's explicit, that you should always provide full data set to "cloneWithRows" method (similarly to view reconciliation, the datasource will automatically calculate the difference and only modify the data that has actually changed).
Also, there is a very good write-up by #vjeux on why they implemented ListView the way they did, including explaining the optimisation strategies they chose (different than iOS's UITableView).
So in your case you should rather accumulate all the rows somewhere else and only pass the whole array of messages to cloneWithRows or relay on the incremental behaviour of cloneWithRows and continuously append the incoming elements to cloneWithRows as they come as in below example (it's supposed to be fast so give it a try).
The documentation copy&paste from ListViewDataSource.js:
/**
* Provides efficient data processing and access to the
* `ListView` component. A `ListViewDataSource` is created with functions for
* extracting data from the input blob, and comparing elements (with default
* implementations for convenience). The input blob can be as simple as an
* array of strings, or an object with rows nested inside section objects.
*
* To update the data in the datasource, use `cloneWithRows` (or
* `cloneWithRowsAndSections` if you care about sections). The data in the
* data source is immutable, so you can't modify it directly. The clone methods
* suck in the new data and compute a diff for each row so ListView knows
* whether to re-render it or not.
*
* In this example, a component receives data in chunks, handled by
* `_onDataArrived`, which concats the new data onto the old data and updates the
* data source. We use `concat` to create a new array - mutating `this._data`,
* e.g. with `this._data.push(newRowData)`, would be an error. `_rowHasChanged`
* understands the shape of the row data and knows how to efficiently compare
* it.
*
* ```
* getInitialState: function() {
* var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
* return {ds};
* },
* _onDataArrived(newData) {
* this._data = this._data.concat(newData);
* this.setState({
* ds: this.state.ds.cloneWithRows(this._data)
* });
* }
* ```
*/
I have a JS udf that looks as so:
is_match.outputSchema = 'matched:chararray, match_against:chararray, match_candidate:chararray';
function is_match (match_against, match_candidate) {
var mre = new RegExp(match_against);
return { word:mre.test(match_candidate), word:match_against, word:match_candidate };
}
The Pig that calls it looks like such:
register '<full path omitted>my_match.js' using javascript as js_match;
regexes = load <stuff> using PigStorage() as ( regex:chararray );
tests = load <stuff> using PigStorage() as ( agent:chararray );
regexes = distinct regexes;
tests = distinct tests;
tests = cross regexes, tests;
matched = foreach tests generate js_match.is_match( regex, agent );
What I get is a ton of empty tuples:
((,,))
((,,))
((,,))
((,,))
If I switch the function in JS to look like so:
is_match.outputSchema = 'foo:int';
function is_match (foo, bar) {
return 1;
}
I actually get:
(1.0)
(1.0)
(1.0)
which is what I expect. However, when I change the return from JS to return any of my actual data, it won't. If I make the return statement 'return 1;', I get the 1's.
I am not sure why I am unable to return values from the larger JS function and I am able to return less complex data that "comes through." It should be returning "something" each and every time. For our purposes, tests looks like:
(.oo,foobar)
(.oo,bazfoobar)
(.oo,foobarbaz)
([Ff]oo,Bar)
([Ff]oo,bar)
where the first column is an expression, and the second column is a string. I'm just trying to run through a giant list of strings with a giant list of expressions.
I have made an i18n object in javascript like the following to manage the languages in my javascript files
i18n = {
currentCulture: 'pt_PT',
pt_PT : {
message_key : "text in portuguese"
},
en_US: {
message_key : "text in english",
},
/**
* translate
*/
__ : function(key,culture){
return this.culture.key;
},
/**
* returns the active user culture
*/
getUserCulture : function(){
return this.currentCulture;
},
/**
* sets the current culture
*/
setCulture : function(culture){
this.currentCulture = culture;
}
}
I need to return the correct message based on the key and culture params of the translate function.
The problem is that in the line return this.culture.key; javascript is trying to find a "culture" propriety in the i18n object.
How can i make it call, for example this.pt_PT.message_key?
Thanks for your help.
Thanks everyone who posted the solution. I can only accepted one anwser so i accept the first one.
Use bracket notation. Assuming culture is 'pt_PT' and key is 'message_key':
return this[culture][key];
Replace:
this.culture.key
with:
this[culture][key]
Javascript objects are associative arrays, and you can use array syntax to look up properties:
return this[culture][key];