Locale not loading with next-18next serverSideTranslations - internationalization

I've set up my application that in every page it fetches the locale from serverSide like this:
export const getServerSideProps: GetServerSideProps = withSSRUserAuth(
async ctx => {
const { locale } = ctx;
return {
props: {
...(await serverSideTranslations(
locale ?? "pt",
["welcome", "toast"],
i18nextConfig
)),
},
};
}
);
My i18nextConfig:
const isDevelopment = process.env.NODE_ENV === "development"
/**
* #type {import('next-i18next').UserConfig}
*/
module.exports = {
i18n: {
defaultLocale: 'pt',
locales: ['pt', 'en'],
},
debug: isDevelopment,
fallbackLng: "pt",
reloadOnPrerender: isDevelopment,
returnObjects: true,
};
It's pretty much equal what is stated in the docs, however, locale seems to never load. It's always undefined. How can I make it detect the browser locale?

Related

Cannot see data in view page source even though Cache of Apollo Client have data

I don't know why in another page, I use this way just different query and I can see data in view page source, but in this page , it not work. I wondering it cause I use localStorage value as params, i don't think problem come from query.
interface Props {
__typename?: 'ProductOfBill';
amount: number,
name: string,
totalPrice: number,
type: string,
unitPrice: number,
}
const Cart = () => {
const [products,setProducts] = useState<Props[]>([])
const { data } = useGetSomeProductQuery({
variables: { productList: productListForBill()},
notifyOnNetworkStatusChange: true
});
useEffect(() =>{
if(data?.getSomeProduct){
setProducts(data.getSomeProduct)
}
},[data])
return (
<>
...
</>
);
};
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo();
await apolloClient.query<GetSomeProductQuery>({
query: GetSomeProductDocument,
variables: { productList: productListForBill() },
});
return addApolloState(apolloClient, {
props: {},
});
};
export default Cart;
I get localStorage value from this method.
export const productListForBill = () : GetProductForBill[] =>{
const returnEmtpyArray : GetProductForBill[] = []
if(typeof window !== "undefined"){
if(localStorage.getItem("products"))
{
const tempProduct = JSON.parse(localStorage.getItem("products") || "")
if(Array.isArray(tempProduct)){
return tempProduct
}
}
}
return returnEmtpyArray
}
and I custom Apollo Client like doc of Nextjs in github
import { useMemo } from 'react'
import { ApolloClient, HttpLink, InMemoryCache, NormalizedCacheObject } from '#apollo/client'
import merge from 'deepmerge'
import isEqual from 'lodash/isEqual'
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
interface IApolloStateProps {
[APOLLO_STATE_PROP_NAME]?: NormalizedCacheObject
}
let apolloClient : ApolloClient<NormalizedCacheObject>
function createApolloClient() {
return new ApolloClient({
//type of "window"=== undifined
ssrMode: true,
link: new HttpLink({
uri: "http://localhost:4000/graphql",
credentials: "include",
}),
cache: new InMemoryCache()
)}
}
export function initializeApollo(initialState : NormalizedCacheObject | null = null) {
const _apolloClient = apolloClient ?? createApolloClient()
if (initialState) {
const existingCache = _apolloClient.extract()
cache
const data = merge(existingCache, initialState, {
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
})
_apolloClient.cache.restore(data)
}
if (typeof window === 'undefined') return _apolloClient
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function addApolloState(client : ApolloClient<NormalizedCacheObject>, pageProps: { props: IApolloStateProps }) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
export function useApollo(pageProps : IApolloStateProps) {
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}
Answering
Cannot see data in view page source even though Cache of Apollo Client have data
These are client side methods, value will not be visible in view source but in evaluated source, look in the elements panel in chrome.

GraphQL Error: Field "image" must not have a selection since type "String" has no subfields

Description
I am new and learning GatsbyJs/NetlifyCM/GraphQL so I am sorry if I come across as stupid. I, however, have been trying to fix this issue for 5 days now and am at my wits end. I am trying to implement NetlifyCMS into a GatsbyJS blog starter. I seem to have everything working except the images. Graphql is querying the image URL as "img/image.jpg" which is what is set in the MD file, and that works fine. However, when I make a new post with NetlifyCMS it sets the URL to "/img/image.jpg" and graphql can't seem to find it. The extra slash in front causes the error. If I remove the slash OR put a dot in front of the slash, "./img/image.jpg" it builds fine which leads me to believe relative paths may be working? I have read issue 4123, issue 13938, issue 5990, As well as multiple other similar issues and blogs and still, am unable to get it to work. I have tried implementing gatsby-remark-relative-images and gatsby-plugin-netlify-cms but neither seems to fix it.
Any help would be appreciated. I am newer at web development and very new to gatsby/graphql. Thank you.
Steps to reproduce
remove or add a / in front of the image URL in the MD file. Here is a link to the repo to clone/download https://github.com/AaronCuddeback/blog.aaroncuddeback.com
Environment
System:
OS: Windows 10
CPU: (16) x64 Intel(R) Core(TM) i9-9900KF CPU # 3.60GHz
Binaries:
Node: 12.13.0 - C:\Program Files\nodejs\node.EXE
npm: 6.13.0 - ~\AppData\Roaming\npm\npm.CMD
Languages:
Python: 2.7.17 - /usr/bin/python
Browsers:
Edge: 44.18362.387.0
npmPackages:
gatsby: 2.18.2 => 2.18.2
gatsby-image: 2.2.33 => 2.2.33
gatsby-plugin-canonical-urls: 2.1.15 => 2.1.15
gatsby-plugin-emotion: 4.1.15 => 4.1.15
gatsby-plugin-feed: 2.3.21 => 2.3.21
gatsby-plugin-google-analytics: 2.1.28 => 2.1.28
gatsby-plugin-netlify: ^2.1.25 => 2.1.25
gatsby-plugin-netlify-cms: ^4.1.28 => 4.1.28
gatsby-plugin-netlify-cms-paths: ^1.3.0 => 1.3.0
gatsby-plugin-postcss: 2.1.15 => 2.1.15
gatsby-plugin-react-helmet: 3.1.15 => 3.1.15
gatsby-plugin-sharp: 2.3.2 => 2.3.2
gatsby-plugin-sitemap: 2.2.21 => 2.2.21
gatsby-plugin-typescript: 2.1.19 => 2.1.19
gatsby-remark-abbr: 2.0.0 => 2.0.0
gatsby-remark-copy-linked-files: 2.1.30 => 2.1.30
gatsby-remark-images: 3.1.33 => 3.1.33
gatsby-remark-prismjs: 3.3.24 => 3.3.24
gatsby-remark-relative-images: ^0.2.3 => 0.2.3
gatsby-remark-responsive-iframe: 2.2.27 => 2.2.27
gatsby-remark-smartypants: 2.1.16 => 2.1.16
gatsby-source-filesystem: 2.1.38 => 2.1.38
gatsby-transformer-json: 2.2.19 => 2.2.19
gatsby-transformer-remark: 2.6.37 => 2.6.37
gatsby-transformer-sharp: 2.3.5 => 2.3.5
gatsby-transformer-yaml: 2.2.17 => 2.2.17
File Contents:
gatsby-config.js:
const path = require('path');
module.exports = {
siteMetadata: {
title: 'Ghost',
description: 'The professional publishing platform',
siteUrl: 'https://gatsby-casper.netlify.com', // full path to blog - no ending slash
},
mapping: {
'MarkdownRemark.frontmatter.author': 'AuthorYaml',
},
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/static/img`,
name: 'uploads',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/content`,
name: 'content',
},
},
{
resolve: `gatsby-plugin-netlify-cms-paths`,
options: {
cmsConfig: `/static/admin/config.yml`,
},
},
'gatsby-plugin-sitemap',
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
`gatsby-plugin-netlify-cms-paths`,
{
resolve: `gatsby-remark-relative-images`,
options: {
name: 'uploads', // Must match the source name ^
},
},
{
resolve: 'gatsby-remark-images',
options: {
maxWidth: 1170,
quality: 90,
},
},
{
resolve: 'gatsby-remark-responsive-iframe',
options: {
wrapperStyle: 'margin-bottom: 1rem',
},
},
'gatsby-remark-prismjs',
'gatsby-remark-copy-linked-files',
'gatsby-remark-smartypants',
'gatsby-remark-abbr',
],
},
},
'gatsby-transformer-json',
{
resolve: 'gatsby-plugin-canonical-urls',
options: {
siteUrl: 'https://gatsby-casper.netlify.com',
},
},
'gatsby-plugin-emotion',
'gatsby-plugin-typescript',
'gatsby-transformer-sharp',
'gatsby-plugin-react-helmet',
'gatsby-transformer-yaml',
'gatsby-plugin-feed',
{
resolve: 'gatsby-plugin-postcss',
options: {
postCssPlugins: [require('postcss-color-function'), require('cssnano')()],
},
},
{
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: 'UA-XXXX-Y',
// Puts tracking script in the head instead of the body
head: true,
// IP anonymization for GDPR compliance
anonymize: true,
// Disable analytics for users with `Do Not Track` enabled
respectDNT: true,
// Avoids sending pageview hits from custom paths
exclude: ['/preview/**'],
// Specifies what percentage of users should be tracked
sampleRate: 100,
// Determines how often site speed tracking beacons will be sent
siteSpeedSampleRate: 10,
},
},
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-netlify`,
],
};
gatsby-node.js
const path = require('path');
const _ = require('lodash');
const { fmImagesToRelative } = require('gatsby-remark-relative-images');
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node);
// Sometimes, optional fields tend to get not picked up by the GraphQL
// interpreter if not a single content uses it. Therefore, we're putting them
// through `createNodeField` so that the fields still exist and GraphQL won't
// trip up. An empty string is still required in replacement to `null`.
switch (node.internal.type) {
case 'MarkdownRemark': {
const { permalink, layout, primaryTag } = node.frontmatter;
const { relativePath } = getNode(node.parent);
let slug = permalink;
if (!slug) {
slug = `/${relativePath.replace('.md', '')}/`;
}
// Used to generate URL to view this content.
createNodeField({
node,
name: 'slug',
value: slug || '',
});
// Used to determine a page layout.
createNodeField({
node,
name: 'layout',
value: layout || '',
});
createNodeField({
node,
name: 'primaryTag',
value: primaryTag || '',
});
}
}
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
allMarkdownRemark(
limit: 2000
sort: { fields: [frontmatter___date], order: ASC }
filter: { frontmatter: { draft: { ne: true } } }
) {
edges {
node {
excerpt
timeToRead
frontmatter {
title
tags
date
draft
image {
childImageSharp {
fluid(maxWidth: 3720) {
aspectRatio
base64
sizes
src
srcSet
}
}
publicURL
}
author {
id
bio
avatar {
children {
... on ImageSharp {
fixed(quality: 90) {
src
}
}
}
}
}
}
fields {
layout
slug
}
}
}
}
allAuthorYaml {
edges {
node {
id
}
}
}
}
`);
if (result.errors) {
console.error(result.errors);
throw new Error(result.errors);
}
// Create post pages
const posts = result.data.allMarkdownRemark.edges;
// Create paginated index
const postsPerPage = 6;
const numPages = Math.ceil(posts.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/' : `/${i + 1}`,
component: path.resolve('./src/templates/index.tsx'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
});
});
posts.forEach(({ node }, index) => {
const { slug, layout } = node.fields;
const prev = index === 0 ? null : posts[index - 1].node;
const next = index === posts.length - 1 ? null : posts[index + 1].node;
createPage({
path: slug,
// This will automatically resolve the template to a corresponding
// `layout` frontmatter in the Markdown.
//
// Feel free to set any `layout` as you'd like in the frontmatter, as
// long as the corresponding template file exists in src/templates.
// If no template is set, it will fall back to the default `post`
// template.
//
// Note that the template has to exist first, or else the build will fail.
component: path.resolve(`./src/templates/${layout || 'post'}.tsx`),
context: {
// Data passed to context is available in page queries as GraphQL variables.
slug,
prev,
next,
primaryTag: node.frontmatter.tags ? node.frontmatter.tags[0] : '',
},
});
});
// Create tag pages
const tagTemplate = path.resolve('./src/templates/tags.tsx');
const tags = _.uniq(
_.flatten(
result.data.allMarkdownRemark.edges.map(edge => {
return _.castArray(_.get(edge, 'node.frontmatter.tags', []));
}),
),
);
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag,
},
});
});
// Create author pages
const authorTemplate = path.resolve('./src/templates/author.tsx');
result.data.allAuthorYaml.edges.forEach(edge => {
createPage({
path: `/author/${_.kebabCase(edge.node.id)}/`,
component: authorTemplate,
context: {
author: edge.node.id,
},
});
});
};
exports.onCreateWebpackConfig = ({ stage, actions }) => {
// adds sourcemaps for tsx in dev mode
if (stage === `develop` || stage === `develop-html`) {
actions.setWebpackConfig({
devtool: 'eval-source-map',
});
}
};

useState depending on other state

I have this useSiren hook that should update its state with the incoming json argument but it doesnt.
On the first call the json is an empty object, because the fetch effect has not been run yet.
On the second call its also an empty object (triggered by loading getting set to true in App)
And on the third call its filled with valid data. However, the valid data is not applied. The state keeps its initial value.
I guess somehow setSiren must be called to update it, since initial state can only be set once. But how would I do that? Who should call `setSiren?
import { h, render } from 'https://unpkg.com/preact#latest?module';
import { useEffect, useState, useCallback } from 'https://unpkg.com/preact#latest/hooks/dist/hooks.module.js?module';
import htm from "https://unpkg.com/htm#latest/dist/htm.module.js?module";
const html = htm.bind(h);
function useFetch({
method = "GET",
autoFetch = true,
href,
body
}) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState()
const [response, setResponse] = useState()
const [isCancelled, cancel] = useState()
const [json, setJson] = useState({})
const sendRequest = async payload => {
try {
setLoading(true)
setError(undefined)
const response = await fetch(href.replace("http://", "https://"), {
method
})
const json = await response.json()
if (!isCancelled) {
setJson(json)
setResponse(response)
}
return json
} catch (err) {
if (!isCancelled) {
setError(err)
}
throw err
} finally {
setLoading(false)
}
}
if (autoFetch) {
useEffect(() => {
sendRequest(body)
return () => cancel(true)
}, [])
}
return [{
loading,
response,
error,
json
},
sendRequest
]
}
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
function Action(props) {
const [{ loading, error, json }, sendRequest] = useFetch({ autoFetch: false, href: props.href, method: props.method })
const requestAndUpdate = () => {
sendRequest().then(props.onRefresh)
}
return (
html`
<button disabled=${loading} onClick=${requestAndUpdate}>
${props.title}
</button>
`
)
}
function App() {
const [{ loading, json }, sendRequest] = useFetch({ href: "https://restlr.io/toggle/0" })
const [{ state, actions }, setSiren] = useSiren(json)
return (
html`<div>
<div>State: ${loading ? "Loading..." : (state.properties && state.properties.value)}</div>
${actions.map(action => html`<${Action} href=${action.href} title=${action.title || action.name} method=${action.method} onRefresh=${setSiren}/>`)}
<button disabled=${loading} onClick=${sendRequest}>
REFRESH
</button>
</div>
`
);
}
render(html`<${App}/>`, document.body)
Maybe what you want to do is to update the siren state when the json param changes? You can use a useEffect to automatically update it.
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
useEffect(() => { // here
setSiren(json)
}, [json])
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
The pattern mentioned by #awmleer is packaged in use-selector:
import { useSelectorValue } from 'use-selector';
const { entities=[], actions=[], title} = json;
const siren = useSelectorValue(() => ({
state: entities.find(entity => entity.class === 'state') || {},
actions,
title
}), [json]);
Disclosure I'm author and maintainer of use-selector

issue with slowly geting data from api to vue view

I have issue with very slowly getting data from laravel api to vue view, I did tutorial where I have:
import axios from 'axios';
const client = axios.create({
baseURL: '/api',
});
export default {
all(params) {
return client.get('users', params);
},
find(id) {
return client.get(`users/${id}`);
},
update(id, data) {
return client.put(`users/${id}`, data);
},
delete(id) {
return client.delete(`users/${id}`);
},
};
<script>
import api from "../api/users";
export default {
data() {
return {
message: null,
loaded: false,
saving: false,
user: {
id: null,
name: "",
email: ""
}
};
},
methods: {
onDelete() {
this.saving = true;
api.delete(this.user.id).then(response => {
this.message = "User Deleted";
setTimeout(() => this.$router.push({ name: "users.index" }), 1000);
});
},
onSubmit(event) {
this.saving = true;
api
.update(this.user.id, {
name: this.user.name,
email: this.user.email
})
.then(response => {
this.message = "User updated";
setTimeout(() => (this.message = null), 10000);
this.user = response.data.data;
})
.catch(error => {
console.log(error);
})
.then(_ => (this.saving = false));
}
},
created() {
api.find(this.$route.params.id).then(response => {
this.loaded = true;
this.user = response.data.data;
});
}
};
</script>
It's load data from api very slowly I see firstly empty inputs in view and after some short time I see data, if I open api data from laravel I see data immediately, so my question is How speed up it? Or maby I did something wrong?
Whenever I am using an API with Vue, I usually make most of my API calls before opening the Vue then passing it in like this.
<vue-component :user="'{!! $user_data !!}'"></vue-component>
But if you have to do it in the Vue component, I am not sure if this will show improvement over your method but I would set it up with the "mounted" like so.
export default {
mounted() {
api.find(this.$route.params.id).then(response => {
this.loaded = true;
this.user = response.data.data;
});
}
}
Also heres a good tutorial on Axios and how to use HTTP Requets with Vue.
Hopefully this answered your question, good luck!

tabBarOnPress stopped working react-navigation 3.3.2

After upgrading to react-navigation 3.3.2 the tabBarOnPress no longer fires. Code snippet below. Any ideas?
const Tabs = createBottomTabNavigator(
{
HomeStack,
WallStack,
MemeStack,
},
{
navigationOptions: ({navigation}) => ({
tabBarOnPress: () => {
console.log('tab pressed');
}
})
}
);
export default DrawerNav = createDrawerNavigator({
Tabs: Tabs
},{
drawerBackgroundColor: Colors.grayDark,
contentComponent: Sidebar
})
tabBarOnPress is working, but I'm using it like this. You need to pass it as a property of each stack. (don't forget to call the defaultHandler to keep the default behavior:
const DashboardStack = createStackNavigator(
{
Dashboard,
}
);
DashboardStack.navigationOptions = {
tabBarOnPress({ navigation, defaultHandler }) {
// do something
defaultHandler();
},
};
const ProfileStack = createStackNavigator(
{
Profile,
AccountInfo,
Membership,
Browser,
ConnectedAccounts,
}
);
ProfileStack.navigationOptions = {
tabBarOnPress({ navigation, defaultHandler }) {
// do something
defaultHandler();
},
};
const SpendingStack = createStackNavigator(
{
Budget,
Transactions,
}
);
SpendingStack.navigationOptions = {
tabBarOnPress({ navigation, defaultHandler }) {
// do something
defaultHandler();
},
};
export default createBottomTabNavigator(
{
SpendingStack,
DashboardStack,
ProfileStack,
}
);

Resources