How to set lang attribute on <html> tag in SSR mode with SvetleKit - sveltekit

I know user's preferred language in cookie and I am able to init locales with it (using server hook).
And I would like to also render correct lang attribute which matches selected locale.

There are docs on specifically that.
If your content is available in multiple languages, you should set the lang attribute based on the language of the current page. You can do this with SvelteKit's handle hook:
src/app.html
<html lang="%lang%">
src/hooks.server.ts
import type { RequestEvent, Handle } from '#sveltejs/kit';
export const handle = (({ event, resolve }) => {
return resolve(event, {
transformPageChunk: ({ html }) =>
html.replace('%lang%', get_lang(event))
});
}) satisfies Handle;

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

Test url params in react-testing-library without react-router

I have a widget that uses React but not the react-router package. One component checks for the existence of a url param via the URLSearchParams API and then opens or closes using styling:
function App() {
const openWidget =
new URLSearchParams(window.location.search)
.get("openWidget")
?.toLowerCase() === "true";
const [show, setShow] = useState(openWidget);
return (<StyledComponent $show={show}>something</StyledComponent>) // set height to 0 or 100vh based on $show
}
However, I can't figure out how to test this in rtl. My current test is like so:
const location = {
...window.location,
search: "openWidget=true",
};
Object.defineProperty(window, "location", {
value: location,
});
customRender(<ChatWindow />, { contextProps: amendedMockContext });
const chatWindow = screen.getByTestId("chat-window");
expect(chatWindow).toHaveStyle("max-height: 100vh");
But when I log out window.location, I get this: { search: '' }. My test throws this error:
- Expected
- max-height: 100vh;
+ max-height: 0px;
What's the right way to test values using search params?
Note: I have other tests using the same customRender and amendedMockContext - it has nothing to do with them.
EDIT: I tried making a minimal reproducible example, and it works just fine: Code Sandbox . But in my actual project it still doesn't work. The code sandbox is set up identically (context, styles, customerRender, everything) yet that one works, and in my own project window.location.search still returns an empty string. I am utterly dumbfounded.
The last comment in this issue on GitHub Jest repository will help you
https://github.com/facebook/jest/issues/5124#issuecomment-914606939
Using history.replaceState should allow you to change URL search parameter.
Check the other replies in the issue I shared, seems that most of the techniques that were working before to change window.location are not working anymore in the last versions of Jest.
Example
Following your comment about the URL parameter, the point to note here is that, for this test in particular, the only thing that is important for you is the query string, so you can use any valid origin as parameter and eventually, if in another test you need a specific origin, you can pass it to the function. Let me show you an example, created starting from the link I shared.
function changeJSDOMURL(search, url = "https://www.example.com/") {
const newURL = new URL(url);
newURL.search = new URLSearchParams(search);
const href = `${window.origin}${newURL.pathname}${newURL.search}${newURL.hash}`;
history.replaceState(history.state, null, href);
}
// This will consider the default URL, it is OK for your test
changeJSDOMURL({ openWidget: "true" });
// If you need to pass an origin different from the default one
changeJSDOMURL({ openWidget: "true" }, "https://www.something.io");

NextJS: Push new query string value to an existing asPath or update the existing query string

I am kinda lost on how the NextJS router system works:
I have articles by categories that can be:
Medical
Charity
Wedding
Funeral
I am having a navbar where the user can display articles by category, and can also search by a keyword. The category feature is working perfectly.
This is how my navbar looks like:
That means if the user clicks on All, we are displaying all articles:
Here is the scenario:
When we click for example on Medical, the URL looks like this: http://localhost:3000/articles?category=medical
When we click in all, the URL looks like http://localhost:3000/articles
When we click in all and search the keyword COVID, the URL looks like this: http://localhost:3000/articles?search=covid
PROBLEM
I now want to combine categories and search:
If the user clicks in medical and searches for the keyword COVID, the URL must look like this: http://localhost:3000/articles?category=medical&search=covid.
After getting the search result, when the user changes the keyword, I want to replace the search query string with the new value.
HOW I TRIED TO DO?
I know NextJS router object has:
pathname: Current route. That is the path of the page in /pages
and asPath: Actual path (including the query) shown in the browser
const { push, asPath } = useRouter();
const onSearchKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
const keyword = e.currentTarget.value;
if (e.key === "Enter") {
url = !isEmpty(keyword.trim())
? {
pathname: asPath,
query: { search: keyword },
}
: {
pathname,
};
push(url);
}
};
But doing so is encoding the asPath and resulting in this URL: http://localhost:3000/article%3Fcategory=covid&search=covid which is displaying the 404 page not found.
So, I don't even know if I can replace the search with a new value as the user wants to perform a new search.
I solved the issue by not using the asPath: I instead used the pathname alongside the spread operator as follow:
url = !isEmpty(keyword.trim())
? {
pathname,
query: { ...query, search: keyword },
}: {
pathname: page,
};

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

React-admin: How to create user profile form in react-admin v3.2.*?

I've noticed that the official article on how to create user settings (profile) in React-Admin is outdated (https://marmelab.com/blog/2019/03/07/react-admin-advanced-recipes-user-profile.html).
I followed the example and tried to use a new DataProvider, but couldn't get Edit view working (it just showed blank Card component with no fields even though I've set them in a way that's described in the example).
I was searching for several days on how to implement it in a simplest/clean way, but there's a very small amount of information about it.
Does somebody know how to do it in react-admin 3.2.*?
It might be helpful for others who have the same issue.
Any help will be very appreciated! Thanks!
I had the same problem. Looking at the props passed toreact-admin's Edit, I saw the record prop was undefined. It was because the id field inside the record returned by the data provider's getOne method was different than the id prop hardcoded on the Edit component. Once that was set to match, both reading/editing works.
My working code:
// remove staticContext to avoid React 'unknown prop on DOM element' error
export const PrincipalEdit = ({ staticContext, ...props }: { staticContext: any; props: any }) => {
return (
// `id` has to match with `id` field on record returned by data provider's `getOne`
// `basePath` is used for re - direction
// but we specify no redirection since there is no list component
<Edit {...props} title="My Profile" id="my-profile" resource={b.PRINCIPAL} basePath="my-profile" redirect={false}>
<SimpleForm>
<TextInput source="firstName" validate={required()} />
</SimpleForm>
</Edit>
);
};
The issue is how the data is stored by react-admin at the moment (haven't checked how it was before). Now each data entry is saved by its id in the store object (the reasons are obvious). I think the best approach is to modify the data provider.
if (resource === 'profile') {
const { id, ...p } = params; // removes the id from the params
if (p.data)
delete p.data.id; // when updates there is data object that is passed to the backend
return dataProvider(type, resource, { ...p, id: '' }) // set the id just empty string to hack undefined as http param
.then(({ data }) => {
return Promise.resolve({
data: {
...data,
id // return the data with the initial id
}
});
});
}
This way the backend endpoint could return just the object at the main endpoint of the entity /profile. If you do not set the id prop to '' it will try to fetch /profile/undefined because the id prop was deleted from the params object. If you do not delete the id prop from the data object when updating, depending on the backend sql query (assuming you are using some kind of db) for updating a record, it may try to set or search by this id.
In the Edit component you can pass whatever id you want but something must be passed.
Additional:
If you are using NestJS as backend with the Crud package, these #Crud params may be helpful
...
params: {
id: { // the routes won't have params, very helpful for me/profile controller
primary: true,
disabled: true,
},
},
routes: {
only: ["getOneBase", "updateOneBase"],
},
...

Resources