Locale change works well in my next.js app, except for with dynamic routes. In the browser address bar I do get the transition from http://localhost:3000/client/home/profile to http://localhost:3000/de/client/home/profile, but the page gives a 404 error...
My dynamic page [tab].tsx
import router from 'next/router'
import dynamic from 'next/dynamic'
import styled from 'styled-components'
import {useTranslation, i18n} from 'next-i18next'
import {useEffect, useState, useContext} from 'react'
import {serverSideTranslations} from 'next-i18next/serverSideTranslations'
import Layout from 'layouts'
import {DB, PATH} from 'utils/constants'
import {GlobalContext} from 'utils/contexts'
import {Tabs, LanguageSelector} from 'components/ui'
import {ChartData, ChartDataPoint, UsageStats} from 'utils/types'
import {getData, toTitleCase, isClient, emailVerified} from 'utils/helpers'
const Docs = dynamic(() => import('components/client/docs'))
const Stats = dynamic(() => import('components/client/stats'))
const Profile = dynamic(() => import('components/client/profile'))
const Tab = ({tab}) => {
const {t} = useTranslation()
return (
<Layout>
<LanguageSelector />
<Tabs
basePath={'/client/home'}
tab={tab}
tabs={[
{
slug: 'stats',
label: t('client.home.stats'),
component: <Stats data={data} />
},
{
slug: 'profile',
label: t('client.home.profile'),
component: <Profile client={client} />
},
{
slug: 'docs',
label: t('client.home.docs'),
component: <Docs />
}
]}
/>
</Layout>
)
}
export const getStaticProps = async ({params, locale}) => ({
props: {
tab: params.tab,
...await serverSideTranslations(locale)
}
})
export const getStaticPaths = async () => {
return {
paths: [
{params: {tab: 'stats'}},
{params: {tab: 'profile'}},
{params: {tab: 'docs'}}
],
fallback: false
}
}
export default Tab
I do the locale switching in my LanguageSelector.tsx:
import router from 'next/router'
import {i18n} from 'next-i18next'
import {useState, useEffect} from 'react'
import {Select} from '.'
import {LANGUAGES} from 'utils/constants'
export const LanguageSelector = () => {
const [locale, setLocale] = useState(i18n.language)
useEffect(() => {
const {pathname, asPath, query} = router
router.push({pathname, query}, asPath, {locale})
}, [locale])
return (
<Select
borderless
isSearchable={false}
value={i18n.language}
options={LANGUAGES.filter(language => language !== i18n.language)}
onValueChange={setLocale}
/>
)
}
Any ideas where my mistake is?
If you are using getStaticPaths with in-build Next.js i18n routing then you also need to return locale key with your paths, like that:
export const getStaticPaths = ({ locales }) => {
return {
paths: [
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true,
}
}
More info in the docs
Related
Next translation is displaying key instead of lang
common:menu.1.title common:read_more
i18n.js
module.exports = {
locales: ['en', 'de', 'cs'],
defaultLocale: 'en',
redirectToDefaultLang: true,
pages: {
'*': ['common'],
'/404': ['home'],
'/': ['home'],
'/about': ['about'],
},
interpolation: {
prefix: '${',
suffix: '}',
},
logger: true,
logBuild: true,
loadLocaleFrom: (locale, namespace) =>
import(`./public/locales/${locale}/${namespace}`).then((m) => m.default),
}
this is my next.config.js
const nextTranslate = require('next-translate')
module.exports = nextTranslate()
_app.js
import I18nProvider from 'next-translate/I18nProvider';
class MyApp extends App {
render () {
const { Component, pageProps, store } = this.props
return (
<I18nProvider lang={'en'} >
<Component {...pageProps} />
<GoTop scrollStepInPx="50" delayInMs="16.66" />
</I18nProvider>
);
}
}
export default MyApp
and following HOC
import React, {Component} from "react";
import withTranslation from 'next-translate/withTranslation'
class NavBarLink extends Component {
render() {
const { t, lang } = this.props.i18n
const description = t('menu.1.title')
const description2 = t('read_more')
return <p>{description + ' '+ description2}</p>
}
}
export default withTranslation(NavBarLink, 'common')
The return value is common:menu.1.title common:read_more
Please can someone tell me what is missing in my code?
In new version of next-translate wouldn't need to provide I18nProvider as said here.
But when you provider that you should provide all namespaces you wanna use.
You'd better to look at this migration guide.
I have an edit page that will be rendered with an id parameter and it works fine when application is running but while building the nextjs app I get this error
[Error: ENOENT: no such file or directory, rename 'C:\Users\Ahsan Nisar\Documents\GitHub\customer-portal\frontend.next\export\en\companies\edit[id].html' -> 'C:\Users\Ahsan Nisar\Documents\GitHub\customer-portal\frontend.next\server\pages\en\companies\edit[id].html']
the full error
I am not sure what this error is related to or what mistake am I making in my code that this error is occuring during build time.
Here is the code of my page
import { WithAuthorization } from 'common/roq-hocs';
import { MainLayout } from 'layouts';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import React, { FunctionComponent } from 'react';
import { CompaniesEditView } from 'views/companies-edit';
const CompanyCreatePage: FunctionComponent = () => {
const { t } = useTranslation('companiesEdit');
return (
<MainLayout title={t('title')}>
<WithAuthorization
permissionKey="companies.update"
failComponent={
<div className="mt-16 text-2xl text-center text-gray-600">
<span>{t('noView')}</span>
</div>
}
>
<CompaniesEditView />
</WithAuthorization>
</MainLayout>
);
};
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ['common', 'companiesEdit'])),
},
});
export const getStaticPaths = () => ({
paths: ['/companies/edit/[id]'],
fallback: true,
});
export default CompanyCreatePage;
I think that the problem might be that you are not returning the expected paths model in getStaticPaths function.
Minimal example of this page:
import { GetStaticPaths, GetStaticProps } from 'next';
import { useRouter } from 'next/router';
const CompanyCreatePage = () => {
const router = useRouter();
const { id } = router.query;
return (
<div>
<h1>Company Create Page Content for id: {id}</h1>
</div>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
// Get all possible 'id' values via API, file, etc.
const ids = ['1', '2', '3', '4', '5']; // Example
const paths = ids.map(id => ({
params: { id },
}));
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async context => {
return { props: {} };
};
export default CompanyCreatePage;
Then, navigating to the page /users/edit/3/ returns the following content
Take into account that the fallback param in getStaticPaths changes the behavior of getStaticProps function. For reference, see the documentation
I´m looking for way to load static texts into storybook via next-translate.
My code looks like this, but it´s loading my locale files, but not writing them properly.
This is storybook preview.js:
import '../src/styles/global/global.scss';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/core/styles';
import theme from '../src/utils/theme';
import I18nProvider from 'next-translate/I18nProvider';
import commonCS from '../locales/cs/common.json';
export const decorators = [(Story) => themeDecorator(Story)];
const themeDecorator = (Story) => {
console.log(commonCS.homepage_title);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<I18nProvider lang={'cs-CS'} namespaces={{ commonCS }}>
<Story />
</I18nProvider>
</ThemeProvider>
);
};
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: { expanded: true },
};
And this is my storybook storie:
import React from 'react';
import HeaderContact from './HeaderContact';
import I18nProvider from 'next-translate/I18nProvider';
import useTranslation from 'next-translate/useTranslation';
import commonCS from '../../../locales/cs/common.json';
export default {
title: 'HeaderContact',
component: HeaderContact,
};
export const Basic = () => {
const { t } = useTranslation('common');
return (
<HeaderContact
link="mailto:info#numisdeal.com"
text={t('homepage_title')}
/>
);
};
My local file common.json:
{
"homepage_title": "Blog in Next.js",
"homepage_description": "This example shows a multilingual blog built in Next.js with next-translate"
}
And my translate config i18n.json
{
"locales": ["cs", "en", "de"],
"defaultLocale": "cs",
"pages": {
"*": ["common"]
}
}
I would be very glad for some help.
Thanks!
Roman
Here is the solution.
preview.js
import '../src/styles/global/global.scss';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/core/styles';
import theme from '../src/utils/theme';
import I18nProvider from 'next-translate/I18nProvider';
import commonCS from '../locales/cs/common.json';
export const decorators = [(Story) => themeDecorator(Story)];
const themeDecorator = (Story) => {
console.log(commonCS.homepage_title);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<I18nProvider lang={'cs'} namespaces={{ common: commonCS }}>
<Story />
</I18nProvider>
</ThemeProvider>
);
};
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: { expanded: true },
};
Storie:
import React from 'react';
import HeaderContact from './HeaderContact';
export default {
title: 'HeaderContact',
component: HeaderContact,
};
export const Basic = () => {
return <HeaderContact link="mailto:info#numisdeal.com" />;
};
Component:
import React from 'react';
import AlternateEmailIcon from '#material-ui/icons/AlternateEmail';
import useTranslation from 'next-translate/useTranslation';
import styles from './HeaderContact.module.scss';
export interface IHeaderContact {
link: string;
text?: string;
}
export default function HeaderContact(props: IHeaderContact) {
const { link, text } = props;
const { t } = useTranslation('common');
const preklad = t('homepage_title');
return (
<a href={link} className={styles.headerLink}>
<AlternateEmailIcon fontSize="small" />
<span>
{/* {text} */}
{preklad}
</span>
</a>
);
}
I'm using a stack navigator from react navigation v5 with redux. The stack navigator should render AuthScreen when isAuthenticated is false and render HomeScreen when isAuthenticated changes to true.
AppNav.js
import React, {useState, useEffect} from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import { connect } from 'react-redux';
import AuthScreen from '../screens/AuthScreen';
import HomeScreen from '../screens/HomeScreen';
const Stack = createStackNavigator();
const AppNav = ({isAuthenticated}) => {
const [logged, setLogged] = useState(false)
useEffect(() => {
if(isAuthenticated) {
console.log(logged);
setLogged(true);
} else {
setLogged(false);
console.log(logged);
}
},[isAuthenticated])
return (
<NavigationContainer>
<Stack.Navigator>
{
!logged?
<Stack.Screen name='Auth' component={AuthScreen} options={{headerShown: false}} />
:<Stack.Screen name='Home' component={HomeScreen} options={{headerShown: false}} />
}
</Stack.Navigator>
</NavigationContainer>
);
}
const mapStateToProps = ({isAuthenticated}) => {
return {
isAuthenticated
};
}
export default connect(mapStateToProps, null)(AppNav)
userAction.js
import {LOGIN_WITH_FACEBOOK} from './types';
export const loginWithFacebook = () => async(dispatch) => {
dispatch( { type: LOGIN_WITH_FACEBOOK, payload: {isAuthenticated: true} } );
}
userReducer.js
import {LOGIN_WITH_FACEBOOK} from '../actions/types.js';
const INITIAL_STATE = {
isAuthenticated: false
};
const userReducer = (state=INITIAL_STATE, action) => {
switch(action.type){
case LOGIN_WITH_FACEBOOK:
return {...state, ...action.payload};
default:
return state;
}
}
export default userReducer
rootReducer.js
import {combineReducers} from 'redux';
import userReducer from './userReducer';
const rootReducer = combineReducers({
user: userReducer
})
export default rootReducer
Fixed it by accessing the key of userReducer which is state.user.
AppNav.js
const mapStateToProps = (state) => {
return {
isAuthenticated: state.user.isAuthenticated
};
}
export default connect(mapStateToProps, null)(AppNav)
I'm new to Redux. It's really confusing to understand basic syntax. None of the bugs are found so It's hard to figure out what's wrong with my code.
It worked well last week, I don't remember what I have changed.
//child component
import React, { Component } from 'react';
import { SingleDatePicker } from 'react-dates';
import moment from 'moment';
class InputForms extends Component {
state = {
inputs: ['input-0'],
title: '',
tag: '',
createdAt: moment(),
imageLinks: [''],
calendarFocused: false,
error: '',
}
appendInput(e) {
const newInput = `input-${this.state.inputs.length}`;
this.setState({ inputs: this.state.inputs.concat([newInput]) });
}
onTitleChange = (e) => {
const title = e.target.value;
this.setState(() => ({ title }));
};
onTagChange = (e) => {
const tag = e.target.value;
this.setState(() => ({ tag }));
};
onImageLinkChange = (e) => {
const imageLinks = e.target.value;
this.setState(() => ({ imageLinks: this.state.imageLinks.concat([imageLinks]) }));
};
onDateChange = (createdAt) => {
if (createdAt) {
this.setState(() => ({ createdAt }));
}
};
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarFocused: focused }));
};
onSubmit = (e) => {
e.preventDefault();
if (!this.state.title || !this.state.imageLinks) {
this.setState(() => ({ error: '제목과 이미지링크를 입력해주세요' }));
} else {
this.setState(() => ({ error: '' }));
this.props.onSubmit({
title: this.state.title,
tag: this.state.tag,
createdAt: this.state.createdAt.valueOf(),
imageLinks: this.state.imageLinks,
});
}
}
render() {
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="제목을 입력하세요"
required
value={this.state.title}
onChange={this.onTitleChange}
/>
<input
type="text"
placeholder="태그를 입력하세요"
value={this.state.tag}
onChange={this.onTagChange}
/>
<SingleDatePicker
date={this.state.createdAt}
onDateChange={this.onDateChange}
focused={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false}
/>
{this.state.inputs.map((input, key) => {
return <input
key={input}
type="text"
required
value={this.state.imageLinks}
onChange={this.onImageLinkChange}
placeholder={`이미지링크 ${key + 1}`}
/>
})}
<button>저장</button>
</form>
</div>
)
}
}
export default InputForms;
//parent component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import configureStore from '../store/configureStore';
import InputForms from './InputForms';
import { addPost } from '../actions/posts';
const store = configureStore();
class CreatePost extends Component {
onSubmit = (post) => {
this.props.addPost(post);
this.props.history.push('/');
};
render(){
return(
<div>
<InputForms onSubmit={this.onSubmit}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch, props) => ({
addPost: (post) => dispatch(addPost(post))
});
export default connect(undefined, mapDispatchToProps)(CreatePost);
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import registerServiceWorker from './registerServiceWorker';
import AppRouter from './routers/AppRouter';
import 'normalize.css/normalize.css';
import './style/style.css';
import 'react-dates/lib/css/_datepicker.css';
import 'react-dates/initialize';
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(jsx, document.getElementById('root'));
registerServiceWorker();
//action
import database from '../firebase/firebase';
//Add Posts
export const addPost = (post) => ({
type: 'ADD_POST',
post
});
//reducer
const postReducerDefaultState = [];
export default (state = postReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_POST':
return [
...state,
action.post
];
default:
return state;
}
};
In your reducer, you return as below
return [ ...state, action.post];
Reducer doesnt return array, but instead returning objects. Secondly, action.post is a value, you need to assign this to key, something like below:
return { ...state, post: action.post };