astro-i18next
astro-i18next copied to clipboard
localizePath not working on react client
Hi there, I'm setting up localized links thanks to localizePath
. However, it throws an error when used in a React component with client:load
. I guess it's somehow linked to the fact that it's supposed to run server side.
Here are the errors show in the console:
My app's output is set to
static
, see the repo: https://github.com/florian-lefebvre/portfolio/tree/v5/astro
This seems related to localizePath
relying on Astro.url.pathname
which becomes an issue for interactive, non-astro components.
I personally set the Astro.url.pathname
to a svelte store to access outside the astro context, but again this does not work when the component is interactive.
---
// Shared state, e.g. svelte store
import { currentPathname } from "./store";
// Set svelte store in astro layout
currentPathname.set(Astro.url.pathname);
---
Perhaps localizePath
could accept the current pathname as a parameter instead of relying on Astro.url.pathname
?
<script>
import { localizePath } from "astro-i18next";
import { currentPathname } from "./store";
localizePath('about', currentPathname);
</script>
Hey @florian-lefebvre and @mvllow, in case you didn't find a better solution, this is mine:
astro-i18next.config.js
/** @type {import('astro-i18next').AstroI18nextConfig} */
export default {
...,
routes: {
en: {
'huis-ibiza': {
index: 'ibiza-villas',
'[slug]': {
index: '[slug]',
book: {
index: 'book',
'thank-you': 'thank-you',
},
enquiry: {
index: 'enquiry',
'thank-you': 'thank-you',
},
},
},
contact: {
index: 'contact-us',
},
'over-ons': {
index: 'about-us',
},
},
},
...
};
src/utils/routing.js
import i18nConfig from '../../astro-i18next.config';
const routes = i18nConfig.routes;
const flattenedRoutes = flattenRoutes(routes);
function flattenRoutes(routes) {
const flattenedRoutes = {};
for (const [langCode, langRoutes] of Object.entries(routes)) {
flattenedRoutes[langCode] = {};
for (const [route, routeData] of Object.entries(langRoutes)) {
if (typeof routeData === 'string') {
flattenedRoutes[langCode][`/${route}`] = `/en/${routeData}`;
} else {
flattenedRoutes[langCode][`/${route}`] = `/en/${routeData.index}`;
flattenRoutesHelper(
flattenedRoutes[langCode],
route,
routeData,
`/en/${routeData.index}`
);
}
}
}
return flattenedRoutes;
}
function flattenRoutesHelper(flattenedRoutes, route, routeData, baseRoute) {
for (const [subRoute, subRouteData] of Object.entries(routeData)) {
if (subRoute === 'index') continue;
if (typeof subRouteData === 'string') {
flattenedRoutes[`/${route}/${subRoute}`] = `${baseRoute}/${subRouteData}`;
} else {
flattenedRoutes[
`/${route}/${subRoute}`
] = `${baseRoute}/${subRouteData.index}`;
flattenRoutesHelper(
flattenedRoutes,
`${route}/${subRoute}`,
subRouteData,
`${baseRoute}/${subRouteData.index}`
);
}
}
}
export function localizePath(url, locale = 'nl') {
const defaultLanguage = 'nl';
// If the default language is the same as the specified language, return the original url
if (defaultLanguage === locale) {
return url;
}
return flattenedRoutes[locale][url];
}
this will return an object like this:
{
"en": {
"/huis-ibiza": "/en/ibiza-villas",
"/huis-ibiza/[slug]": "/en/ibiza-villas/[slug]",
"/huis-ibiza/[slug]/book": "/en/ibiza-villas/[slug]/book",
"/huis-ibiza/[slug]/book/thank-you": "/en/ibiza-villas/[slug]/book/thank-you",
"/huis-ibiza/[slug]/enquiry": "/en/ibiza-villas/[slug]/enquiry",
"/huis-ibiza/[slug]/enquiry/thank-you": "/en/ibiza-villas/[slug]/enquiry/thank-you",
"/contact": "/en/contact-us",
"/over-ons": "/en/about-us"
}
}
client-side component
import { localizePath } from '@utils/routing';
// normal href
const href = localizePath('/huis-ibiza', 'en');
// in case you have a dynamic key you can use .replace method
const hrefWithDynamicRoute = localizePath('/huis-ibiza/[slug]', 'en').replace('[slug]', props.house.slug);
Regards
@fprl I found a new way:
npm i react-i18next@^12
#The astro-i18next depends on i18next@^22, so we need to install react-i18next with the same dependencies.
/** @type {import('astro-i18next').AstroI18nextConfig} */
export default {
defaultLocale: "en",
locales: ["en", "zh"],
i18next: {
debug: true, // convenient during development to check for missing keys
},
load: ["server", "client"], // load i18next server and client side
i18nextServerPlugins: {
"{initReactI18next}": "react-i18next",
},
i18nextClientPlugins: {
"{initReactI18next}": "react-i18next",
},
};
Add a custom hook:
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
export const useLocalization = () => {
const { i18n } = useTranslation();
const localizePath = useCallback(
(path: string) => {
const currentLanguage = i18n.language;
if (currentLanguage === "en") {
return path;
}
return `/${currentLanguage}${path}`;
},
[i18n.language]
);
// todo: localize url
// const localizeUrl = useCallback(
// (url: string) => {
// },
// [i18n.language]
// );
return { localizePath };
};
use custom hook in react component:
import { useTranslation } from "react-i18next";
import { useLocalization } from "hooks/useLocalization";
// ...
const { t } = useTranslation();
const { localizePath } = useLocalization();
// ...