i10n using NextIntl
This commit is contained in:
24
components/languageSwitcher.tsx
Normal file
24
components/languageSwitcher.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
// using https://next-intl-docs.vercel.app/docs/getting-started/pages-router
|
||||
const LanguageSwitcher = () => {
|
||||
const t = useTranslations('common');
|
||||
const router = useRouter();
|
||||
const { locale, locales, asPath } = router;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{locales.map((lng) => {
|
||||
if (lng === locale) return null;
|
||||
return (
|
||||
<button key={lng} onClick={() => router.push(asPath, asPath, { locale: lng })}>
|
||||
{t('changeTo')} {t(lng.toUpperCase())}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
@ -69,3 +69,4 @@ export default function Layout({ children }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { useRouter } from 'next/router';
|
||||
import sidemenu, { footerMenu } from './sidemenuData.js'; // Move sidemenu data to a separate file
|
||||
import axiosInstance from "src/axiosSecure";
|
||||
import common from "src/helpers/common";
|
||||
import LanguageSwitcher from "./languageSwitcher";
|
||||
//get package version from package.json
|
||||
const packageVersion = require('../package.json').version;
|
||||
|
||||
@ -145,6 +146,7 @@ export default function Sidebar({ isSidebarOpen, toggleSidebar }) {
|
||||
<div className="mt-auto">
|
||||
<hr className="border-gray-200 dark:border-gray-600 text-align-bottom" />
|
||||
<FooterSection />
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
10
content/i18n/bg.json
Normal file
10
content/i18n/bg.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"common": {
|
||||
"greeting": "Здравей",
|
||||
"farewell": "Довиждане",
|
||||
"changeTo": "Смени на",
|
||||
"BG": "Български",
|
||||
"EN": "Английски",
|
||||
"RU": "Руски"
|
||||
}
|
||||
}
|
10
content/i18n/en.json
Normal file
10
content/i18n/en.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"common": {
|
||||
"greeting": "Hello",
|
||||
"farewell": "Goodbye",
|
||||
"changeTo": "Change to",
|
||||
"BG": "Bulgarian",
|
||||
"EN": "English",
|
||||
"RU": "Russian"
|
||||
}
|
||||
}
|
10
content/i18n/ru.json
Normal file
10
content/i18n/ru.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"common": {
|
||||
"greeting": "Здравей",
|
||||
"farewell": "Довиждане",
|
||||
"changeTo": "Смени на",
|
||||
"BG": "[RU] Български",
|
||||
"EN": "[RU] Английски",
|
||||
"RU": "[RU] Руски"
|
||||
}
|
||||
}
|
@ -50,4 +50,8 @@ module.exports = withPWA({
|
||||
|
||||
return config;
|
||||
},
|
||||
i18n: { // using https://next-intl-docs.vercel.app/docs/getting-started/pages-router
|
||||
locales: ['bg', 'en', 'ru'],
|
||||
defaultLocale: 'bg',
|
||||
},
|
||||
})
|
147
package-lock.json
generated
147
package-lock.json
generated
@ -55,6 +55,7 @@
|
||||
"next": "^14.1.0",
|
||||
"next-auth": "^4.24.6",
|
||||
"next-connect": "^1.0.0",
|
||||
"next-intl": "^3.12.0",
|
||||
"next-pwa": "^5.6.0",
|
||||
"node-excel-export": "^1.4.4",
|
||||
"node-telegram-bot-api": "^0.64.0",
|
||||
@ -2194,6 +2195,92 @@
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
||||
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz",
|
||||
"integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.5.4",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
|
||||
"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
|
||||
"integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.6",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.25",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
|
||||
"integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.25",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/react": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.1.tgz",
|
||||
@ -8947,6 +9034,34 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
|
||||
"integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/fast-memoize": "1.2.1",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat/node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.25",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@ -10681,6 +10796,26 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.12.0.tgz",
|
||||
"integrity": "sha512-N3DHT6ce6K4VHVA3y2p3U7wfBx4c31qEgQSTFCFJuNnE7XYzy49O576ewEz7/k2YaB/U5bfxaWWaMMkskofwoQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.2.32",
|
||||
"negotiator": "^0.6.3",
|
||||
"use-intl": "^3.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next-pwa": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/next-pwa/-/next-pwa-5.6.0.tgz",
|
||||
@ -17428,6 +17563,18 @@
|
||||
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
|
||||
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.12.0.tgz",
|
||||
"integrity": "sha512-tTJBSaxpVF1ZHqJ5+1JOaBsPmvBPscXHR0soMNQFWIURZspOueLaMXx1XHNdBv9KZGwepBju5aWXkJ0PM6ztXg==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "^1.11.4",
|
||||
"intl-messageformat": "^9.3.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
@ -72,6 +72,7 @@
|
||||
"next": "^14.1.0",
|
||||
"next-auth": "^4.24.6",
|
||||
"next-connect": "^1.0.0",
|
||||
"next-intl": "^3.12.0",
|
||||
"next-pwa": "^5.6.0",
|
||||
"node-excel-export": "^1.4.4",
|
||||
"node-telegram-bot-api": "^0.64.0",
|
||||
|
@ -6,7 +6,10 @@ import "tailwindcss/tailwind.css"
|
||||
|
||||
import type { AppProps } from "next/app";
|
||||
import type { Session } from "next-auth";
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/router";
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
|
||||
// for fontawesome
|
||||
import Head from 'next/head';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
@ -22,10 +25,32 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
||||
// }
|
||||
|
||||
|
||||
export default function App({
|
||||
Component,
|
||||
pageProps: { session, ...pageProps },
|
||||
}: AppProps<{ session: Session }>) {
|
||||
export default function App({ Component, pageProps: { session, ...pageProps }, }: AppProps<{ session: Session }>) {
|
||||
console.log(pageProps);
|
||||
const router = useRouter();
|
||||
const [messages, setMessages] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Current locale:", router.locale);
|
||||
async function loadLocaleData() {
|
||||
const localeMessages = await import(`../content/i18n/${router.locale}.json`);
|
||||
console.log("Loaded locale '", router.locale, "' ",);
|
||||
//console.log("Loaded messages for locale:", router.locale, localeMessages.default);
|
||||
setMessages(localeMessages.default);
|
||||
}
|
||||
loadLocaleData();
|
||||
}, [router.locale]);
|
||||
|
||||
// useEffect(() => {
|
||||
// async function loadLocaleData() {
|
||||
// const locale = router.locale || 'en';
|
||||
// const messages = await fetch(`/api/translations/${locale}`).then(res => res.json());
|
||||
// setMessages(messages);
|
||||
// }
|
||||
|
||||
// loadLocaleData();
|
||||
// }, [router.locale]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const use = async () => {
|
||||
@ -67,11 +92,32 @@ export default function App({
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextIntlClientProvider
|
||||
locale={router.locale}
|
||||
timeZone="Europe/Sofia"
|
||||
messages={messages}
|
||||
>
|
||||
<SessionProvider session={session} >
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<Component {...pageProps} />
|
||||
</LocalizationProvider>
|
||||
</SessionProvider>
|
||||
</NextIntlClientProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// build time localization. Is it working for _app.tsx?d
|
||||
// export async function getStaticProps(context) {
|
||||
// const messages = (await import(`../content/i18n/${locale}.json`)).default;
|
||||
// console.log("Loaded messages for locale:", locale, messages);
|
||||
// return {
|
||||
// props: {
|
||||
// // You can get the messages from anywhere you like. The recommended
|
||||
// // pattern is to put them in JSON files separated by locale and read
|
||||
// // the desired one based on the `locale` received from Next.js.
|
||||
// // messages: (await import(`../content/i18n/${context.locale}.json`)).default
|
||||
// messages
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
37
pages/api/translations/[...locale].ts
Normal file
37
pages/api/translations/[...locale].ts
Normal file
@ -0,0 +1,37 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
type Translations = {
|
||||
[key: string]: string;
|
||||
};
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { locale } = req.query;
|
||||
|
||||
const filePath = path.join(process.cwd(), `content/i18n/${locale}.json`);
|
||||
const translations: Translations = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
|
||||
switch (req.method) {
|
||||
case 'GET':
|
||||
try {
|
||||
res.status(200).json(translations);
|
||||
} catch (error) {
|
||||
console.error('Error reading translation file:', error);
|
||||
res.status(500).json({ error: 'Failed to read translation file' });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST': try {
|
||||
const newTranslations = req.body;
|
||||
fs.writeFileSync(filePath, JSON.stringify(newTranslations, null, 2), 'utf8');
|
||||
res.status(200).json({ status: 'Updated' });
|
||||
} catch (error) {
|
||||
console.error('Error writing translation file:', error);
|
||||
res.status(500).json({ error: 'Failed to update translation file' });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
res.setHeader('Allow', ['GET', 'POST']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
}
|
@ -204,6 +204,7 @@ export const getServerSideProps = async (context) => {
|
||||
props: {
|
||||
initialItems: items,
|
||||
userId: session?.user.id,
|
||||
// messages: (await import(`../content/i18n/${context.locale}.json`)).default
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user