How to access current value from writable? - sveltekit

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.)

Related

Export image node to markdown with Lexical

I create a new node: 'ImageNode', similar to this: https://lexical.dev/docs/concepts/nodes#extending-decoratornode
This is working, but I need to export markdown content, for this, I'm using $convertToMarkdownString.
My problem is images inserted in the editor, aren't being exported as markdown. My console log show just basic transforms.
How can I export the image node to markdown?
I need to create a new transform to markdown?
Thanks!
(Copy-pasting from discussions) ImagePlugin (just like the toolbar) is part of the playground only, so the transformer is not exposed to NPM. We expect to make ImagePlugin as part an individual #lexical/image package in the future but only once we it's generic enough to cater most use cases and guarantee no major breaking cases in the immediate future.
For now, you may want to copy-paste this bit from the playground:
export const IMAGE: TextMatchTransformer = {
export: (node, exportChildren, exportFormat) => {
if (!$isImageNode(node)) {
return null;
}
return `![${node.getAltText()}](${node.getSrc()})`;
},
importRegExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))/,
regExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))$/,
replace: (textNode, match) => {
const [, altText, src] = match;
const imageNode = $createImageNode(src, altText, 800);
textNode.replace(imageNode);
},
trigger: ')',
type: 'text-match',
};

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>
)
}

How does document.getSelection work under reactjs environment?

I was working on a electron-react project for epub file. Right now, I am planning to make the app capable of selecting text field and highlight it.
To achieve it, I was trying to use web's Window.getSelection api. However, there are some really weird things come up like in this.
In short, I am unable to capture the Selection object. It seems like even if I log the Selection object, this object could somehow jump to something else. Also, I cannot even serialize the Selection object with JSON.stringfy. This is super surprising, and this is my first time seeing something like this (I will get empty object to stringfy the Selection object).
So how to use Window.getSelection properly under react-electron environment? Or this api will not work well for text content which is generated by react's dangerouslySetInnerHTML?
Looks like the window.getSelection api needs to toString the selected object.
const getSelectionHandler = () => {
const selection = window.getSelection();
console.log("Got selection", selection.toString());
};
Super simple textarea and button to get selection react demo
this solution might help someone, catch last selection
const selection = useRef('');
const handleSelection = () => {
const text = document.getSelection().toString();
if (text) {
selection.current = text;
}
}
useEffect(() => {
document.addEventListener('selectionchange', handleSelection);
return () => {
document.removeEventListener('selectionchange', handleSelection);
};
});

ReactJS - Async Dynamic Component Loading

Situation
I receive json from a cms that describes the content that needs to display on any given page. This project has 50+ components so rather than require all of them on every page I'd rather cherry pick them as needed.
Question
How can I
Make sure all components are available for import (I assume this requires some webpack trickery)
When converting the json's content node to jsx, making sure that any component described is rendered out.
Current Thoughts
I can loop through the raw jsx and collect all the tags for a given page then attempt a load for each tag via something like
const name = iteration.tagName;
dynCmps[name] = someAsynchronousLoad(path + name);
Then dispatch a redux event when loading is complete to kick off a fresh render of the page.
As for converting raw text content to react js I'm using ReactHtmlParser
best resources so far
Dynamic loading of react components
http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack/
This had me stumped for a couple of days. After chatting with a colleague about it for some time it was decided that the amount of work it would take to offload the performance hit of loading all the components upfront is not work it for our scenario of 30-50 components.
Lazy loading CAN BE used but I decided against it as the extra 10ms of loading (if that) isn't going to be noticeable at all.
import SomeComponent from "./SomeComponent.js"
const spoofedComponents = {
SomeComponent: <SomeComponent />
}
const replaceFunc = (attribs, children) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
// lower case is important here because react converts everything to lower case during text-to-html conversion - only react components can be camel case whereas html is pascal case.
if(attribs.name === key.toLowerCase()) {
return spoofedComponents[key];
}
}
return <p>unknown component</p>
}
...
//inside render
const raw = "<SomeComponent><SomeComponent />"
// it's VERY important that you do NOT use self-closing tags otherwise your renders will be incomplete.
{parse(raw, {
replace: replaceFunc
})}
In my case I have 30+ components imported and mapped to my spoofedComponents constant. It's a bit of a nuissance but this is necessary as react needs to know everything about a given situation so that the virtual dom can do what it is supposed to - save on display performance. The pros are that now a non-developer (editor) can build a layout using a WYSIWYG and have it display using components that a developer made.
Cheers!
Edit
I'm still stuck on adding customized props & children.
Edit
Basic props are working with
const spoofedComponents = {
SomeComponent: (opts) => {
let s = {};
if(opts.attribs.style)
s = JSON.parse(opts.attribs.style);
if(opts.attribs.classname) {
opts.attribs.className = opts.attribs.classname;
delete opts.attribs.classname;
}
return <APIRequest {...opts.attribs} style={s}>{opts.children[0].data}</APIRequest>
}
}
...
const replaceFunc = (opts) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
if(opts.name === key.toLowerCase()) {
const cmp = spoofedComponents[key](opts);
return cmp;
}
}
return <p>unknown component</p>
}
Now to figure out how to add child components dynamically..
EDIT
This is working well enough that I'm going to leave it as is. Here is the updated replaceFunc
const replaceFunc = (obj) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
if(obj.name === key.toLowerCase()) {
if(obj.attribs.style)
obj.attribs.style = JSON.parse(obj.attribs.style);
if(obj.attribs.classname) {
obj.attribs.className = obj.attribs.classname;
delete obj.attribs.classname;
}
return React.createElement(spoofedComponents[key], obj.attribs, obj.children[0].data)
}
}
return obj; //<p>unknown component</p>
}

How to handle multiple components in a loop with React-Redux

I am working on a simple react-redux application. And I need my user to upload 2 or 5 files (depending on the user type). Let's say he can be either a simple user or an admin, the user has to upload 2 file and an admin 5. I use react-dropzone to upload my files
From my database I get an object with an array of object, it returns something like:
user: {
name: "John Doe",
docs: [
{
url: "http://path-to-image.png"
},
{
url: "http://path-to-image2.png"
}
]
}
To simplify, in my application I have a Container.js, a Component.js, and in the component i iterate in the docs array by doing a map, and then for each doc I render a new item, let's say FileItem.
The problem is more in the way to upload the files than how to handle the files from the database.
On each file I want to set some booleans in order to know if the user has chosen a picture and if the picture is submitting to the database.
So my actions look like this for now:
function changePictureA(picture){
return {
type: ADD_PICTURE_A,
picture: picture,
isPictureChanged: true,
isPictureSubmitting: false
}
}
export function changeFirstPicture(file){
return function(dispatch){
dispatch(changePictureA(file[0]));
}
}
This works well and I can see my action being dispatched but for now I have set this logic in the FileItem component. But like this file is going to be called several times (2 or 5 according to user's type), this logic can't be in this file right ?
What I really want to know is if I have to set all my values in my container, that mean to write something like this :
function mapStateToProps(state){
const { picture,isPictureChanged, isPictureSubmitting} = state.pictureAReducer;
return {
picture,
isPictureChanged,
isPictureSubmitting
}
}
export default connect(mapStateToProps)(KycItem);
So this is for one Item, if I have 5, I will have to do it 5 times and to have 5 reducers just for this ? Is that the best solution ? I mean, I will have to set long name variables like you can see whereas I would have prefer to just have isSubmitting, isChanged.
I hope your answers will help me !
This perfectly fits into redux todo list sample. Instead of creating reducers and controls for every separate picture you can just add id to the picture and use general actions:
function changePictureAction(id, picture){
return {
type: ADD_PICTURE,
id: id,
picture: picture,
isPictureChanged: true,
isPictureSubmitting: false
}
}
export function changePicture(id, file){
return function(dispatch){
dispatch(changePictureAction(id, file[id]));
}
}
Then you need to split Presentation and Container, on the presentation you need to have FileItem and FileItemsList, while Container handles all data binding logic:
// Container
import changePicture from './PictureActions'
import FileItemsList from './FileItemsList'
function mapStateToProps(state) {
return {
pictures: state.pictures
}
}
function mapDispatchToProps(dispatch) {
return { changePicture }
}
export default connect(mapStateToProps, mapDispatchToProps)(FileItemsList);
// FileItemsList.js
const FileItemsList = ({pictures, changePicture}) => {
const fileitems = pictures.map(pic =>
<FileItem key={pic.id} picture={pic.picture}
onChange={(picture)=>changePicture(pic.id, picture)}/>)
return <div>{fileitems}</div>
}
export default FileItemsList
Also you will need to add upload handlers and dispatchers

Resources