diff --git a/.vscode/settings.json b/.vscode/settings.json
index e0ac1ae..14e7153 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -138,5 +138,11 @@
},
"[handlebars]": {
"editor.defaultFormatter": "vscode.html-language-features"
- }
+ },
+ "i18n-ally.localesPaths": [
+ "content/i18n",
+ "components/x-date-pickers/locales"
+ ],
+ "i18n-ally.keystyle": "nested",
+ "i18n-ally.sourceLanguage": "bg"
}
\ No newline at end of file
diff --git a/components/languageSwitcher.tsx b/components/languageSwitcher.tsx
new file mode 100644
index 0000000..4131523
--- /dev/null
+++ b/components/languageSwitcher.tsx
@@ -0,0 +1,55 @@
+import { useState } from 'react';
+import { useRouter } from 'next/router';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
+import IconButton from '@mui/material/IconButton';
+import TranslateIcon from '@mui/icons-material/Translate';
+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;
+
+ const [anchorEl, setAnchorEl] = useState(null);
+
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const changeLanguage = (lng) => {
+ router.push(asPath, asPath, { locale: lng });
+ handleClose();
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default LanguageSwitcher;
diff --git a/components/layout.tsx b/components/layout.tsx
index b1c29fe..7c5639b 100644
--- a/components/layout.tsx
+++ b/components/layout.tsx
@@ -68,4 +68,5 @@ export default function Layout({ children }) {
);
-}
\ No newline at end of file
+}
+
diff --git a/components/sidebar.tsx b/components/sidebar.tsx
index acd412c..57d86b0 100644
--- a/components/sidebar.tsx
+++ b/components/sidebar.tsx
@@ -5,10 +5,17 @@ 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";
+import { useTranslations } from 'next-intl';
+import ProtectedPage from "pages/examples/protected";
+import ProtectedRoute from "./protectedRoute";
+import { UserRole } from "@prisma/client";
//get package version from package.json
const packageVersion = require('../package.json').version;
function SidebarMenuItem({ item, session, isSubmenu }) {
+ // const tMenu = useTranslations('menu');
+ const t = useTranslations('common');
const router = useRouter();
const isActive = router.pathname.includes(item.url);
@@ -91,6 +98,7 @@ export default function Sidebar({ isSidebarOpen, toggleSidebar }) {
const { data: session, status } = useSession();
const sidebarWidth = 226; // Simplify by using a constant
const sidebarRef = useRef(null);
+ const t = useTranslations('menu');
//const [locations, setLocations] = useState([]);
@@ -136,6 +144,7 @@ export default function Sidebar({ isSidebarOpen, toggleSidebar }) {
@@ -162,14 +174,16 @@ function UserSection({ session }) {
}
function SignInButton() {
+ const t = useTranslations('common');
return (
signIn()}>
-
+
);
}
function UserDetails({ session }) {
+ const t = useTranslations('common');
return (
<>
@@ -180,7 +194,7 @@ function UserDetails({ session }) {
>
diff --git a/components/sidemenuData.js b/components/sidemenuData.js
index b845493..e884535 100644
--- a/components/sidemenuData.js
+++ b/components/sidemenuData.js
@@ -1,6 +1,7 @@
import { UserRole } from "@prisma/client";
+
const sidemenu = [
{
id: "dashboard",
@@ -118,6 +119,12 @@ const sidemenu = [
url: "/cart/reports/coverMe",
roles: [UserRole.ADMIN, UserRole.POWERUSER],
},
+ {
+ id: "translations",
+ text: "Преводи",
+ url: "/cart/translations",
+ roles: [UserRole.ADMIN, UserRole.POWERUSER],
+ },
]
},
diff --git a/content/i18n/bg.json b/content/i18n/bg.json
new file mode 100644
index 0000000..0198cda
--- /dev/null
+++ b/content/i18n/bg.json
@@ -0,0 +1,35 @@
+{
+ "common": {
+ "greeting": "Здравей",
+ "farewell": "Довиждане",
+ "changeTo": "Смени на",
+ "BG": "Български",
+ "EN": "Английски",
+ "RU": "Руски",
+ "login": "Вход",
+ "logout": "Изход"
+ },
+ "menu": {
+ "dashboard": "Възможности",
+ "shedule": "График",
+ "myshedule": "Моя График",
+ "locations": "Местоположения",
+ "cart-report": "Отчет",
+ "cart-experience": "Случки",
+ "guidelines": "Напътствия",
+ "permits": "Разрешителни",
+ "contactAll": "Участници",
+ "feedback": "Отзиви",
+ "contactUs": "За връзка",
+ "admin": "Админ",
+ "cart-places": "Места",
+ "cart-publishers": "Вестители",
+ "cart-events": "План",
+ "cart-calendar": "Календар",
+ "cart-reports": "Отчети",
+ "statistics": "Статистика",
+ "coverMeLogs": "Замествания",
+ "translations": "Преводи"
+ },
+ "specialno-svidetelstvane-na-obshestveni-mesta-v-sofiya-kontakti": "Специално свидетелстване на обществени места в София - Контакти"
+}
\ No newline at end of file
diff --git a/content/i18n/bg.modified.json b/content/i18n/bg.modified.json
new file mode 100644
index 0000000..34515c1
--- /dev/null
+++ b/content/i18n/bg.modified.json
@@ -0,0 +1,34 @@
+{
+ "common": {
+ "greeting": "Здравей",
+ "farewell": "Довиждане",
+ "changeTo": "",
+ "BG": "Български",
+ "EN": "Английски",
+ "RU": "Руски",
+ "login": "Вход",
+ "logout": "Изход"
+ },
+ "menu": {
+ "dashboard": "Възможности",
+ "shedule": "График",
+ "myshedule": "Моя График",
+ "locations": "Местоположения",
+ "cart-report": "Отчет",
+ "cart-experience": "Случки",
+ "guidelines": "Напътствия",
+ "permits": "Разрешителни",
+ "contactAll": "Участници",
+ "feedback": "Отзиви",
+ "contactUs": "За връзка",
+ "admin": "Админ",
+ "cart-places": "Места",
+ "cart-publishers": "Вестители",
+ "cart-events": "План",
+ "cart-calendar": "Календар",
+ "cart-reports": "Отчети",
+ "statistics": "Статистика",
+ "coverMeLogs": "Замествания",
+ "translations": "Преводи"
+ }
+}
\ No newline at end of file
diff --git a/content/i18n/en.json b/content/i18n/en.json
new file mode 100644
index 0000000..626bb9e
--- /dev/null
+++ b/content/i18n/en.json
@@ -0,0 +1,12 @@
+{
+ "common": {
+ "greeting": "Hello",
+ "farewell": "Goodbye",
+ "changeTo": "Change to",
+ "BG": "Bulgarian",
+ "EN": "English",
+ "RU": "Russian",
+ "login": "login",
+ "logout": "logout"
+ }
+}
\ No newline at end of file
diff --git a/content/i18n/en.modified.json b/content/i18n/en.modified.json
new file mode 100644
index 0000000..4a17b81
--- /dev/null
+++ b/content/i18n/en.modified.json
@@ -0,0 +1,15 @@
+{
+ "common": {
+ "greeting": "Hello",
+ "farewell": "Goodbye",
+ "changeTo": "Change to",
+ "BG": "Bulgarian",
+ "EN": "English",
+ "RU": "Russian",
+ "login": "login",
+ "logout": "logout"
+ },
+ "menu": {
+ "dashboard": "Dashboard"
+ }
+}
\ No newline at end of file
diff --git a/content/i18n/ru.json b/content/i18n/ru.json
new file mode 100644
index 0000000..8163927
--- /dev/null
+++ b/content/i18n/ru.json
@@ -0,0 +1,10 @@
+{
+ "common": {
+ "greeting": "Здравей",
+ "farewell": "Довиждане",
+ "changeTo": "Смени на",
+ "BG": "[RU] Български",
+ "EN": "[RU] Английски",
+ "RU": "[RU] Руски"
+ }
+}
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index 2406980..ac0f205 100644
--- a/next.config.js
+++ b/next.config.js
@@ -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',
+ },
})
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 61a5430..28c3295 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 6ff1a1e..f952b7d 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 51e37c1..897ed80 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -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,40 @@ 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() {
+ // Replace the static import with a fetch request
+ const res = await fetch(`/api/translations/${router.locale}`);
+ if (res.ok) {
+ const localeMessages = await res.json();
+ console.log("Loaded messages for locale:", router.locale, localeMessages);
+ setMessages(localeMessages);
+ } else {
+ const localeMessages = await import(`../content/i18n/${router.locale}.json`); setMessages(localeMessages.default);
+ }
+ console.log("Loaded locale '", router.locale, "' ",);
+ //console.log("Loaded messages for locale:", router.locale, 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 +100,32 @@ export default function App({
return (
<>
-
-
-
-
-
+
+
+
+
+
+
+
>
)
}
+
+// 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
+// }
+// };
+// }
diff --git a/pages/_document.tsx b/pages/_document.tsx
index e498528..cdb83b8 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -13,7 +13,7 @@ class MyDocument extends Document {
-
+
diff --git a/pages/api/translations/[...locale].ts b/pages/api/translations/[...locale].ts
new file mode 100644
index 0000000..4deebec
--- /dev/null
+++ b/pages/api/translations/[...locale].ts
@@ -0,0 +1,84 @@
+import { NextApiRequest, NextApiResponse } from 'next';
+import fs from 'fs';
+import path from 'path';
+import common from "../../../src/helpers/common";
+
+function flattenTranslations(data) {
+ const result = {};
+
+ function recurse(cur, prop) {
+ if (Object(cur) !== cur) {
+ result[prop] = cur;
+ } else if (Array.isArray(cur)) {
+ for (let i = 0, l = cur.length; i < l; i++)
+ recurse(cur[i], prop ? prop + "." + i : "" + i);
+ if (l == 0)
+ result[prop] = [];
+ } else {
+ let isEmpty = true;
+ for (let p in cur) {
+ isEmpty = false;
+ recurse(cur[p], prop ? prop + "." + p : p);
+ }
+ if (isEmpty)
+ result[prop] = {};
+ }
+ }
+ recurse(data, "");
+ return result;
+}
+
+function unflattenTranslations(data) {
+ const result = {};
+
+ for (let i in data) {
+ const keys = i.split('.');
+ keys.reduce((r, e, j) => {
+ return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 === j ? data[i] : {}) : []);
+ }, result);
+ }
+ return result;
+}
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ const { locale } = req.query;
+ const filePath = path.join(process.cwd(), `content/i18n/${locale.join(".")}.json`);
+ const modifiedFilePath = path.join(process.cwd(), `content/i18n/${locale}.modified.json`);
+
+ switch (req.method) {
+ case 'GET':
+ let flat = common.parseBool(req.query.flat);
+ try {
+ const fileContents = fs.readFileSync(filePath, 'utf8');
+ let translations = JSON.parse(fileContents);
+ if (fs.existsSync(modifiedFilePath)) {
+ const modifiedTranslations = JSON.parse(fs.readFileSync(modifiedFilePath, 'utf8'));
+ translations = { ...translations, ...modifiedTranslations };
+ }
+ if (flat) {
+ translations = flattenTranslations(translations);
+ }
+ 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;
+ const reconstructedTranslations = unflattenTranslations(newTranslations);
+ fs.writeFileSync(filePath, JSON.stringify(reconstructedTranslations, 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`);
+ }
+}
diff --git a/pages/cart/translations/index.tsx b/pages/cart/translations/index.tsx
new file mode 100644
index 0000000..6929349
--- /dev/null
+++ b/pages/cart/translations/index.tsx
@@ -0,0 +1,110 @@
+import axiosInstance from '../../../src/axiosSecure';
+import { useState, useEffect } from 'react';
+import ProtectedRoute from "../../../components/protectedRoute";
+import { UserRole } from "@prisma/client";
+import Layout from 'components/layout';
+import { useRouter } from "next/router";
+import { ToastContainer, toast } from 'react-toastify';
+
+const locales = ['bg', 'en', 'ru'];
+
+const AdminTranslations = () => {
+ const [translations, setTranslations] = useState({});
+ // set locale to the current locale by default. get it from the useRouter
+ let router = useRouter();
+
+ const [locale, setLocale] = useState(router.locale);
+ const [baseTranslations, setBaseTranslations] = useState(locales[0]);
+
+ useEffect(() => {
+ axiosInstance.get(`/api/translations/${locale}?flat=true`).then(res => setTranslations(res.data));
+ axiosInstance.get(`/api/translations/${locales[0]}?flat=true`).then(res => setBaseTranslations(res.data));
+ }, [locale]);
+
+ const handleSave = () => {
+ axiosInstance.post(`/api/translations/${locale}`, translations)
+ .then(res => {
+ if (res.data.status === 'Updated') {
+ toast.success('Translations updated!');
+ } else {
+ toast.error('Something went wrong!');
+ }
+ })
+ .catch(err => toast.error('Failed to update translations: ' + err.message));
+ };
+
+ const handleChange = (key, value) => {
+ setTranslations(prev => ({ ...prev, [key]: value }));
+ };
+
+ return (
+
+
+
+
+
+
Edit Translations
+
+
+
+
+
+
+
+
+
+
+
+
+ | Key | {/* Adjusted width */}
+ Base Translation | {/* Adjusted width */}
+ Translation | {/* Adjusted width */}
+
+
+
+ {Object.entries(baseTranslations).map(([key, baseValue]) => (
+
+ | {key} |
+ {baseValue} |
+
+ |
+
+ ))}
+
+
+
+ {/*
*/}
+
+
+
+ );
+};
+
+export default AdminTranslations;
diff --git a/pages/contactUs.tsx b/pages/contactUs.tsx
index 6fb5088..b26dabd 100644
--- a/pages/contactUs.tsx
+++ b/pages/contactUs.tsx
@@ -6,7 +6,7 @@ const ContactsPage = () => {
return (
-
Специално свидетелстване на обществени места в София - Контакти
+
{t('specialno-svidetelstvane-na-obshestveni-mesta-v-sofiya-kontakti')}
- Янко Ванчев - +359 878 22 44 67
- Крейг Смит - +359 878 994 573
diff --git a/pages/dash.tsx b/pages/dash.tsx
index b53afa9..c18aa67 100644
--- a/pages/dash.tsx
+++ b/pages/dash.tsx
@@ -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
},
};
}