Automatic Next-translate i18 - reactjs

I want to do internalisation in my nextJs project with i18 and next-translate.
It works great on my local machine, perhaps when I deploy it it doesn't react correctly.
Indeed in my local machine, it translates automatically when I go to localhost:3000, it redirects automaticly to localhost:3000/fr, but when I deploy it, it doesn't, it stays www.domain.com . On the website deployed I have a langage selector to test, when I click on FR it translates correctly. I don't understand why it doesn't automaticly like when I'm in local machine
When I use :
const { locale, defaultLocale } = useRouter();
on my local machine I have locale = fr (my langage in my explorer is set to french), but on the net it stays en-US
In my code I structured my translation with these methods :
-next.config.js
const nextTranslate = require('next-translate')
module.exports = nextTranslate({
webpack: (config, { isServer, webpack }) => {
return config;
},
i18n: {
localeDetection: true,
},
})
-i18n.json
{
"locales": ["en-US", "fr"],
"defaultLocale": "en-US",
"pages": {
"/": ["index"],
"/services": ["services"],
"/projects": ["projects"],
"/project-dormeuil": ["dormeuil"],
"/theme": ["theme"],
"*": ["layout"]
}
}
I have this hook to keep my translation during the transitions
export default function useTranslationMe(namespace, options) {
const { t, lang } = useTranslation(namespace, options);
const T = useMemo(() => t, [lang]);
return { t: T, lang };
}
In my index file for example I translate my text like so :
let {t, lang} = useTranslationMe('index');
return (
{t('intro-skills-index')}
)

Related

Next js Sitemap static export

I need help building a Sitemap for my NextJs project.
I build a headless cms using graphql and next, however, everything is statically generated.
I'm having a lot of issues creating a sitemap. I tried using the npm next-sitemap but all the info I find (youtube and forums) are for projects containing "serversideprops", when my project only contains "getStaticProps" and getStaticPaths. In addition to that I also require the map to handle dynamic paths [slug].js. ** I'm not using typescript
Here is what part of my [slug].js looks like:
> graphql query....
>
> export async function getStaticPaths() { const { posts } = await
> graphcms.request(SLUGLIST); return {
> paths: posts.map((post) => ({ params: { slug: post.slug } })),
> fallback: false, }; }
>
> export async function getStaticProps({ params }) { const slug =
> params.slug; const data = await graphcms.request(QUERY, { slug });
> const { posts } = await graphcms.request(QUERY2); const post =
> data.post; return {
> props: {
> post,
> posts,
> }, }; }
thanks everyone!
next-sitemap also creates the routes based on the static pages you generate. The thing is you have to run it after you generated your project.
Typically, you should have a configuration file like this at the base of your project:
next-sitemap.config.js
/** #type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://my-fallback-url.com/',
generateRobotsTxt: true,
trailingSlash: true,
targetDirectory: `${__dirname}/public`,
// Wherever are your pages stored
pagesDirectory: `${__dirname}/src/pages`,
};
and on your package.json
"scripts": {
... other configurations
"postbuild": "next-sitemap"
},
Which will trigger next-sitemap after your build is complete. You should then find all the generated xml files containing your sitemap.

"article seed # 0" => "article-seed-0" to navigate to other Page with that url?

I am routing dynamically with the compilation of static pages using gatsby-node.
const path = require('path');
const slugify = require('slugify');
const templateForContentType = {
articles: 'src/templates/ArticleDetail.tsx',
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
.
.
.
result.data.allStrapiFinancialEducationContents.nodes.forEach((node) => {
const contentType = contentTypes[node.content[0].strapi_component];
createPage({
path: `/articles/${slugify(node.title, {
lower: true,
})}`,
component: path.resolve(templateForContentType),
context: {
id: node.id,
},
});
});
};
gatsby compiles that statics files with the .title property
[
{title:"article-seed-0",
{title:"article-seed-1"
]
article-seed-0
...
article-seed-n
when I try to navigate to another page, it forms a url like this:
const {title:"article seed # 0" }
<Link to={article.title}>
this is the result:
Article%20Seed%20#0
the data comes to backend, is there any practical way for any kind of title to a url convention that can be associated with the way gatsby compiles these static files?
Gatsby compiles the static files with the given path:
path: `/articles/${slugify(node.title, {lower: true,})}`,
So, if you are using slugify to convert the title (node.title) to a valid path, it has nothing to do with Gatsby but slugify, which is removing the # to add a hyphen (-) in between among other things (indeed, it's creating a slug from a given string).
If your Link doesn't have the right target URL, just use slugify in the same way:
<Link to={slugify(article.title, {lower:true})}>

react-i18next: How can I get text in a language, that is not my current selected one?

I have the following problem: I use react with i18next and also use i18next-backend to load my translation-files. I have a languageSwitcher that I want to use not only to change the language but also to change the URL.
But I need to access the namespace 'routes' of the new language before changing the language.
How can I do this?
This is my i18n config:
i18n
.use(initReactI18next)
.use(detector)
.use(HttpApi)
.init({
defaultNS: 'routes',
fallbackLng: ['de'],
supportedLngs: ['de', 'en'],
detection: {
order: ['path'],
lookupFromPathIndex: 0,
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
keySeparator: false,
interpolation: {
format,
},
});
export default i18n;
My Langugage Selector uses this function:
changeLanguage = (ln) => {
const routes = i18n.getResourceBundle(i18n.language, 'routes');
const currentPathname = window.location.pathname.replace(/\/+$/, '');
let currentRouteKey = Object.keys(routes).find((key) => routes[key] === currentPathname);
if (ln === 'en' && currentPathname === '') {
currentRouteKey = '/en';
}
window.location.replace(this.props.t('routes:' + currentRouteKey, { lng: ln }));
};
German is my fallback language and the language change works when I start on the english version of my application, but it doesn't work when I start from the german version. I guess that's because the english file isn't loaded. How can I trigger this file?
Instead of full page reload (using location.replace) you can trigger i18next.changeLangauge to change the language, at the background the lib will load the missing language, and then you can use the History Api to change the URL.
// pseudo code
changeLanguage = (ln) => {
i18n.changeLanguage(lng, (err) => {
if (err) {
// handle language change error
}
const pathnameParts = window.location.pathname.split("/");
const currentLangPath = pathnameParts[1]; // skips the first slash
let newPathnameParts = pathnameParts;
if (i18n.options.supportedLngs.includes(currentLangPath)) {
newPathnameParts = newPathnameParts.slice(2); // removes the lang part
}
newPathnameParts = [lngSubPaths[lng]].concat(
newPathnameParts.filter(Boolean)
);
const newPathname = newPathnameParts.join("/");
if (newPathname !== window.location.pathname) {
window.history.pushState(null, "", newPathname);
}
});
};
Working codesandbox example

React - Import multiple images in given directory

const importAll = require =>
require.keys().reduce((acc, next) => {
acc[next.replace("./", "")] = require(next);
return acc;
}, {});
const images = importAll(
require.context('./images', false, /\.(png|jpe?g|svg)$/)
);
console.log(images);
I have the above block of code that imports all files in a given directory given that they end in the required type however, I want to reuse this code so that I can import it into different modules across my app and specify the path for each individual module.
I've also tried the below block of code but am new to this syntax, any help is appreciated!
const importAll = require =>
require.keys().reduce((acc, next) => {
acc[next.replace("./", "")] = require(next);
return acc;
}, {});
const images = (path) => importAll(
require.context(path, false, /\.(png|jpe?g|svg)$/)
);
export default {
images,
}
Found out that It's because require.context will only take a literal regex in webpack 4 and therefore, not a variable.
See here https://webpack.docschina.org/guides/dependency-management/
However, mcm-ham found a work around which can be viewed here https://github.com/webpack/webpack/issues/4772?fbclid=IwAR3m6kmghtm4EoV8IspcqEfFItdeMnzQHGiEkXbwqgOywXSUFxcWXxbyjRI#issuecomment-344556837

Electrode doesn't show dynamic data in page source

Using electrode, I noticed this weird behaviour -
When I view the page source after the page fully loads with all the api calls and data, I am only able to view the content that is static for example, the hyper links, headings, footer links etc.
I have created a custom token handler which checks the context object and populates the custom tokens present in the index.html.
So, whenever, I console.log(context.user.content), only the data that is static such as hyperlinks, headings, footer links are logged.
I guess this is the problem but I am not able to wrap my head around as to why electrode doesn't recognise the content being rendered dynamically.
Token-Handler.js file
import Helmet from 'react-helmet';
const emptyTitleRegex = /<title[^>]*><\/title>/;
module.exports = function setup(options) {
// console.log({ options });
return {
INITIALIZE: context => {
context.user.helmet = Helmet.renderStatic();
},
PAGE_TITLE: context => {
const helmet = context.user.helmet;
const helmetTitleScript = helmet.title.toString();
const helmetTitleEmpty = helmetTitleScript.match(emptyTitleRegex);
return helmetTitleEmpty ? `<title>${options.routeOptions.pageTitle}</title>` : helmetTitleScript;
},
REACT_HELMET_SCRIPTS: context => {
const scriptsFromHelmet = ["link", "style", "script", "noscript"]
.map(tagName => context.user.helmet[tagName].toString())
.join("");
return `<!--scripts from helmet-->${scriptsFromHelmet}`;
},
META_TAGS: context => {
console.log(context,'123') //this is where I am checking
return context.user.helmet.meta.toString();
}
};
};
default.js
module.exports = {
port: portFromEnv() || "3000",
webapp: {
module: "electrode-react-webapp/lib/express",
options: {
prodBundleBase: '/buy-used-car/js/',
insertTokenIds: false,
htmlFile: "./{{env.APP_SRC_DIR}}/client/index.html",
paths: {
"*": {
content: {
module: "./{{env.APP_SRC_DIR}}/server/views/index-view"
},
}
},
serverSideRendering: true,
tokenHandler: "./{{env.APP_SRC_DIR}}/server/token-handler"
}
}
};
Any clue anyone?
EDIT 1
However, any following updates that occur on the meta tags are rendered. I'm not sure that is something electrode allows or is a feature of react-helmet.
EDIT 2
SSR is enabled in electrode.
After digging in the docs, realised that there was a slight misunderstanding. So, if data needs to be present in the page source, it needs to be pre-rendered by the server.
Why it wasn't showing at the time I asked the question? Because, data was being evaluated at run-time due ot which the page source only rendered the static content.
Electrode already provides an abstraction, each component that is being rendered has an option to load with pre-fetched data. The catch here is, you have to evaluate what all data needs to be present at runtime because more data is directly proportional to page loading time (as the server won't resolve unless the api you are depending on returns you with either a success or failure )
In terms of implementation, each route has a parameter called init-top which is executed before your page loads.
const routes = [
{
path: "/",
component: withRouter(Root),
init: "./init-top",
routes: [
{
path: "/",
exact: true,
component: Home,
init: "./init-home"
},
in init-home, you can define it something on the lines of -
import reducer from "../../client/reducers";
const initNumber = async () => {
const value = await new Promise(resolve => setTimeout(() => resolve(123), 2000));
return { value };
};
export default async function initTop() {
return {
reducer,
initialState: {
checkBox: { checked: false },
number: await initNumber(),
username: { value: "" },
textarea: { value: "" },
selectedOption: { value: "0-13" }
}
};
}
So,now whenever you load the component, it is loaded with this initialState returned in init-home
I'll just post it here, in case anyone is stuck.

Resources