From 6d2885de5934565e9e7c6f6cf6529713ae73b8cf Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 28 Apr 2024 22:56:41 +0300 Subject: [PATCH 01/11] i10n using NextIntl --- components/languageSwitcher.tsx | 24 +++++ components/layout.tsx | 3 +- components/sidebar.tsx | 2 + content/i18n/bg.json | 10 ++ content/i18n/en.json | 10 ++ content/i18n/ru.json | 10 ++ next.config.js | 4 + package-lock.json | 147 ++++++++++++++++++++++++++ package.json | 1 + pages/_app.tsx | 66 ++++++++++-- pages/api/translations/[...locale].ts | 37 +++++++ pages/dash.tsx | 1 + 12 files changed, 304 insertions(+), 11 deletions(-) create mode 100644 components/languageSwitcher.tsx create mode 100644 content/i18n/bg.json create mode 100644 content/i18n/en.json create mode 100644 content/i18n/ru.json create mode 100644 pages/api/translations/[...locale].ts diff --git a/components/languageSwitcher.tsx b/components/languageSwitcher.tsx new file mode 100644 index 0000000..e4c2663 --- /dev/null +++ b/components/languageSwitcher.tsx @@ -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 ( +
+ {locales.map((lng) => { + if (lng === locale) return null; + return ( + + ); + })} +
+ ); +}; + +export default LanguageSwitcher; \ No newline at end of file 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..1f562d8 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -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 }) {

+
diff --git a/content/i18n/bg.json b/content/i18n/bg.json new file mode 100644 index 0000000..84dfa1c --- /dev/null +++ b/content/i18n/bg.json @@ -0,0 +1,10 @@ +{ + "common": { + "greeting": "Здравей", + "farewell": "Довиждане", + "changeTo": "Смени на", + "BG": "Български", + "EN": "Английски", + "RU": "Руски" + } +} \ No newline at end of file diff --git a/content/i18n/en.json b/content/i18n/en.json new file mode 100644 index 0000000..59f2c2e --- /dev/null +++ b/content/i18n/en.json @@ -0,0 +1,10 @@ +{ + "common": { + "greeting": "Hello", + "farewell": "Goodbye", + "changeTo": "Change to", + "BG": "Bulgarian", + "EN": "English", + "RU": "Russian" + } +} \ No newline at end of file diff --git a/content/i18n/ru.json b/content/i18n/ru.json new file mode 100644 index 0000000..8f75c05 --- /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..084c2db 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,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 ( <> - - - - - + + + + + + + ) } + +// 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/api/translations/[...locale].ts b/pages/api/translations/[...locale].ts new file mode 100644 index 0000000..de67d51 --- /dev/null +++ b/pages/api/translations/[...locale].ts @@ -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`); + } +} 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 }, }; } From 59eff1965f7764a8053670f0fa4d67a7c42f9ade Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 28 Apr 2024 22:58:37 +0300 Subject: [PATCH 02/11] better langSwitcher --- components/languageSwitcher.tsx | 49 +++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/components/languageSwitcher.tsx b/components/languageSwitcher.tsx index e4c2663..4131523 100644 --- a/components/languageSwitcher.tsx +++ b/components/languageSwitcher.tsx @@ -1,4 +1,9 @@ +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 @@ -7,18 +12,44 @@ const LanguageSwitcher = () => { 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 (
- {locales.map((lng) => { - if (lng === locale) return null; - return ( - - ); - })} + + + + + {locales.map((lng) => { + if (lng === locale) return null; + return ( + changeLanguage(lng)}> + {t('changeTo')} {t(lng.toUpperCase())} + + ); + })} +
); }; -export default LanguageSwitcher; \ No newline at end of file +export default LanguageSwitcher; From 57be4d0e881e5f514b056c15714eecbb9a51b5cb Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 28 Apr 2024 23:11:00 +0300 Subject: [PATCH 03/11] translations admin page --- pages/cart/translations/index.js | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 pages/cart/translations/index.js diff --git a/pages/cart/translations/index.js b/pages/cart/translations/index.js new file mode 100644 index 0000000..d4a9352 --- /dev/null +++ b/pages/cart/translations/index.js @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react'; +import axios from 'axios'; + +const AdminTranslations = () => { + const [translations, setTranslations] = useState({}); + const [locale, setLocale] = useState('en'); + + useEffect(() => { + axios.get(`/api/translations/${locale}`).then(res => setTranslations(res.data)); + }, [locale]); + + const handleSave = () => { + axios.post(`/api/translations/${locale}`, translations).then(res => { + if (res.data.status === 'Updated') { + alert('Translations updated!'); + } + }); + }; + + const handleChange = (key, value) => { + setTranslations(prev => ({ ...prev, [key]: value })); + }; + + return ( + <> + +
+

Edit Translations

+ + {Object.keys(translations).map(key => ( +
+ + handleChange(key, e.target.value)} + /> +
+ ))} + +
+
+ + ); +}; + +export default AdminTranslations; From ee448205a0b05e1a4ae07231ca30b676471c43bd Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 28 Apr 2024 23:41:17 +0300 Subject: [PATCH 04/11] added first ally auto text extraction --- .vscode/settings.json | 8 +++++++- content/i18n/bg.json | 17 +++++++++-------- pages/_document.tsx | 2 +- pages/contactUs.tsx | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) 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/content/i18n/bg.json b/content/i18n/bg.json index 84dfa1c..c0f041b 100644 --- a/content/i18n/bg.json +++ b/content/i18n/bg.json @@ -1,10 +1,11 @@ { - "common": { - "greeting": "Здравей", - "farewell": "Довиждане", - "changeTo": "Смени на", - "BG": "Български", - "EN": "Английски", - "RU": "Руски" - } + "common": { + "greeting": "Здравей", + "farewell": "Довиждане", + "changeTo": "Смени на", + "BG": "Български", + "EN": "Английски", + "RU": "Руски" + }, + "specialno-svidetelstvane-na-obshestveni-mesta-v-sofiya-kontakti": "Специално свидетелстване на обществени места в София - Контакти" } \ No newline at end of file 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/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
  • From 8d49f93bec9b7160ddaeca05d03766f6fad7c5cd Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 28 Apr 2024 23:45:43 +0300 Subject: [PATCH 05/11] file renamed --- pages/cart/translations/{index.js => index.tsx} | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) rename pages/cart/translations/{index.js => index.tsx} (76%) diff --git a/pages/cart/translations/index.js b/pages/cart/translations/index.tsx similarity index 76% rename from pages/cart/translations/index.js rename to pages/cart/translations/index.tsx index d4a9352..e95757c 100644 --- a/pages/cart/translations/index.js +++ b/pages/cart/translations/index.tsx @@ -1,16 +1,22 @@ + +import axiosInstance from '../../../src/axiosSecure'; +import common from '../../../src/helpers/common'; import { useState, useEffect } from 'react'; -import axios from 'axios'; +import { Availability, UserRole } from "@prisma/client"; +import ProtectedRoute, { serverSideAuth } from "../../../components/protectedRoute"; const AdminTranslations = () => { + const [translations, setTranslations] = useState({}); const [locale, setLocale] = useState('en'); useEffect(() => { - axios.get(`/api/translations/${locale}`).then(res => setTranslations(res.data)); + console.log("Current locale:", locale, "Fetching translations..."); + axiosInstance.get(`/api/translations/${locale}`).then(res => setTranslations(res.data)); }, [locale]); const handleSave = () => { - axios.post(`/api/translations/${locale}`, translations).then(res => { + axiosInstance.post(`/api/translations/${locale}`, translations).then(res => { if (res.data.status === 'Updated') { alert('Translations updated!'); } From e39282a93efdf7bc72999423e342aa3fa0218b3d Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 28 Apr 2024 23:58:51 +0300 Subject: [PATCH 06/11] use modified fallback for translations --- pages/api/translations/[...locale].ts | 44 ++++++++-------- pages/cart/translations/index.tsx | 72 ++++++++++++++------------- 2 files changed, 61 insertions(+), 55 deletions(-) diff --git a/pages/api/translations/[...locale].ts b/pages/api/translations/[...locale].ts index de67d51..84cb6c7 100644 --- a/pages/api/translations/[...locale].ts +++ b/pages/api/translations/[...locale].ts @@ -1,35 +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')); +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { locale, type } = req.query; + const baseFilePath = path.join(process.cwd(), `content/i18n/${locale}.json`); + const modifiedFilePath = path.join(process.cwd(), `content/i18n/${locale}.modified.json`); + + let translations = {}; + + try { + translations = JSON.parse(fs.readFileSync(baseFilePath, 'utf8')); + if (fs.existsSync(modifiedFilePath)) { + const modifiedTranslations = JSON.parse(fs.readFileSync(modifiedFilePath, 'utf8')); + translations = { ...translations, ...modifiedTranslations }; + } + } catch (error) { + console.error('Error loading translation files:', error); + } switch (req.method) { case 'GET': + res.status(200).json(translations); + break; + case 'POST': try { - res.status(200).json(translations); + fs.writeFileSync(modifiedFilePath, JSON.stringify(req.body, null, 2), 'utf8'); + res.status(200).json({ status: 'Updated' }); } catch (error) { - console.error('Error reading translation file:', error); - res.status(500).json({ error: 'Failed to read translation file' }); + console.error('Error writing modified translation file:', error); + res.status(500).json({ error: 'Failed to update 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`); diff --git a/pages/cart/translations/index.tsx b/pages/cart/translations/index.tsx index e95757c..e272f7f 100644 --- a/pages/cart/translations/index.tsx +++ b/pages/cart/translations/index.tsx @@ -1,26 +1,26 @@ - import axiosInstance from '../../../src/axiosSecure'; -import common from '../../../src/helpers/common'; import { useState, useEffect } from 'react'; -import { Availability, UserRole } from "@prisma/client"; -import ProtectedRoute, { serverSideAuth } from "../../../components/protectedRoute"; +import ProtectedRoute from "../../../components/protectedRoute"; +import { UserRole } from "@prisma/client"; + +// Simulate importing locales from a config or define directly here +const locales = ['en', 'bg', 'ru']; const AdminTranslations = () => { - const [translations, setTranslations] = useState({}); const [locale, setLocale] = useState('en'); useEffect(() => { - console.log("Current locale:", locale, "Fetching translations..."); axiosInstance.get(`/api/translations/${locale}`).then(res => setTranslations(res.data)); }, [locale]); const handleSave = () => { - axiosInstance.post(`/api/translations/${locale}`, translations).then(res => { - if (res.data.status === 'Updated') { - alert('Translations updated!'); - } - }); + axiosInstance.post(`/api/translations/${locale}/modified`, translations) + .then(res => { + if (res.data.status === 'Updated') { + alert('Translations updated!'); + } + }); }; const handleChange = (key, value) => { @@ -28,30 +28,34 @@ const AdminTranslations = () => { }; return ( - <> - -
    -

    Edit Translations

    - - {Object.keys(translations).map(key => ( -
    - - handleChange(key, e.target.value)} - /> -
    + +
    +

    Edit Translations

    + + + + {Object.entries(translations).map(([key, value]) => ( + + + + + ))} + +
    {key} + handleChange(key, e.target.value)} + style={{ width: '100%' }} + /> +
    + +
    +
    ); }; From c779eba4698f0c3fe197d2c42268ac048075a00c Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 01:28:59 +0300 Subject: [PATCH 07/11] translation system working now --- pages/_app.tsx | 12 +++- pages/api/translations/[...locale].ts | 81 +++++++++++++++++++++------ pages/cart/translations/index.tsx | 72 ++++++++++++++---------- 3 files changed, 114 insertions(+), 51 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 084c2db..afb21f2 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -33,10 +33,18 @@ export default function App({ Component, pageProps: { session, ...pageProps }, } useEffect(() => { console.log("Current locale:", router.locale); async function loadLocaleData() { - const localeMessages = await import(`../content/i18n/${router.locale}.json`); + // 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); - setMessages(localeMessages.default); + } loadLocaleData(); }, [router.locale]); diff --git a/pages/api/translations/[...locale].ts b/pages/api/translations/[...locale].ts index 84cb6c7..4deebec 100644 --- a/pages/api/translations/[...locale].ts +++ b/pages/api/translations/[...locale].ts @@ -1,37 +1,82 @@ +import { NextApiRequest, NextApiResponse } from 'next'; import fs from 'fs'; import path from 'path'; -import { NextApiRequest, NextApiResponse } from 'next'; +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, type } = req.query; - const baseFilePath = path.join(process.cwd(), `content/i18n/${locale}.json`); + 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`); - let translations = {}; - - try { - translations = JSON.parse(fs.readFileSync(baseFilePath, 'utf8')); - if (fs.existsSync(modifiedFilePath)) { - const modifiedTranslations = JSON.parse(fs.readFileSync(modifiedFilePath, 'utf8')); - translations = { ...translations, ...modifiedTranslations }; - } - } catch (error) { - console.error('Error loading translation files:', error); - } - switch (req.method) { case 'GET': - res.status(200).json(translations); + 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 { - fs.writeFileSync(modifiedFilePath, JSON.stringify(req.body, null, 2), 'utf8'); + 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 modified translation file:', 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 index e272f7f..782c4f7 100644 --- a/pages/cart/translations/index.tsx +++ b/pages/cart/translations/index.tsx @@ -2,16 +2,22 @@ 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"; -// Simulate importing locales from a config or define directly here -const locales = ['en', 'bg', 'ru']; +const locales = ['bg', 'en', 'ru']; const AdminTranslations = () => { const [translations, setTranslations] = useState({}); - const [locale, setLocale] = useState('en'); + // 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}`).then(res => setTranslations(res.data)); + 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 = () => { @@ -28,34 +34,38 @@ const AdminTranslations = () => { }; return ( - -
    -

    Edit Translations

    - - - - {Object.entries(translations).map(([key, value]) => ( - - - - + + + +
    +

    Edit Translations

    +
    -
    {key} - handleChange(key, e.target.value)} - style={{ width: '100%' }} - /> -
    - -
    -
    + + + + {Object.entries(translations).map(([key, value]) => ( + + + + + + ))} + +
    {key}{baseTranslations[key]} + handleChange(key, e.target.value)} + style={{ width: '100%' }} + /> +
    + +
    +
    + ); }; From 56283ecd50cb69bb8506df2ca00c05d1e1cea785 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 01:33:33 +0300 Subject: [PATCH 08/11] menu translations and ability to add new translations using the base translation --- components/sidebar.tsx | 11 ++++- components/sidemenuData.js | 7 +++ content/i18n/bg.json | 26 ++++++++++- content/i18n/bg.modified.json | 34 ++++++++++++++ content/i18n/en.json | 4 +- content/i18n/en.modified.json | 15 +++++++ pages/cart/translations/index.tsx | 74 ++++++++++++++++++++----------- 7 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 content/i18n/bg.modified.json create mode 100644 content/i18n/en.modified.json diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 1f562d8..d13e202 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -6,10 +6,13 @@ import sidemenu, { footerMenu } from './sidemenuData.js'; // Move sidemenu data import axiosInstance from "src/axiosSecure"; import common from "src/helpers/common"; import LanguageSwitcher from "./languageSwitcher"; +import { useTranslations } from 'next-intl'; //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); @@ -92,6 +95,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([]); @@ -137,6 +141,7 @@ export default function Sidebar({ isSidebarOpen, toggleSidebar }) {
    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 index 84dfa1c..1283bc1 100644 --- a/content/i18n/bg.json +++ b/content/i18n/bg.json @@ -5,6 +5,30 @@ "changeTo": "Смени на", "BG": "Български", "EN": "Английски", - "RU": "Руски" + "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/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 index 59f2c2e..626bb9e 100644 --- a/content/i18n/en.json +++ b/content/i18n/en.json @@ -5,6 +5,8 @@ "changeTo": "Change to", "BG": "Bulgarian", "EN": "English", - "RU": "Russian" + "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/pages/cart/translations/index.tsx b/pages/cart/translations/index.tsx index 782c4f7..dac44fe 100644 --- a/pages/cart/translations/index.tsx +++ b/pages/cart/translations/index.tsx @@ -35,34 +35,56 @@ const AdminTranslations = () => { return ( - -
    -

    Edit Translations

    - - - - {Object.entries(translations).map(([key, value]) => ( - - - - - +
    +

    Edit Translations

    +
    + +
    -
    {key}{baseTranslations[key]} - handleChange(key, e.target.value)} - style={{ width: '100%' }} - /> -
    - + +
    +
    + + + + + + + + + + {Object.entries(baseTranslations).map(([key, baseValue]) => ( + + + + + + ))} + +
    KeyBase TranslationTranslation
    {key}{baseValue} + handleChange(key, e.target.value)} + className="block w-full text-base px-2 py-1 border border-gray-300 focus:ring-blue-500 focus:border-blue-500 rounded" + /> +
    +
    +
From 96360a9e9141f3964efd2d1f1e8f3330e2563680 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 01:47:35 +0300 Subject: [PATCH 09/11] tweak translation UI --- content/i18n/ru.json | 16 ++++----- pages/cart/translations/index.tsx | 55 ++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/content/i18n/ru.json b/content/i18n/ru.json index 8f75c05..8163927 100644 --- a/content/i18n/ru.json +++ b/content/i18n/ru.json @@ -1,10 +1,10 @@ { - "common": { - "greeting": "Здравей", - "farewell": "Довиждане", - "changeTo": "Смени на", - "BG": "[RU] Български", - "EN": "[RU] Английски", - "RU": "[RU] Руски" - } + "common": { + "greeting": "Здравей", + "farewell": "Довиждане", + "changeTo": "Смени на", + "BG": "[RU] Български", + "EN": "[RU] Английски", + "RU": "[RU] Руски" + } } \ No newline at end of file diff --git a/pages/cart/translations/index.tsx b/pages/cart/translations/index.tsx index dac44fe..c34c2e6 100644 --- a/pages/cart/translations/index.tsx +++ b/pages/cart/translations/index.tsx @@ -4,6 +4,7 @@ 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']; @@ -21,12 +22,15 @@ const AdminTranslations = () => { }, [locale]); const handleSave = () => { - axiosInstance.post(`/api/translations/${locale}/modified`, translations) + axiosInstance.post(`/api/translations/${locale}`, translations) .then(res => { if (res.data.status === 'Updated') { - alert('Translations 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) => { @@ -37,19 +41,30 @@ const AdminTranslations = () => {
-

Edit Translations

-
- - +
+
+

Edit Translations

+
+ +
+ +
+
+
@@ -71,20 +86,22 @@ const AdminTranslations = () => { value={translations[key] || ''} placeholder='Въведи превод...' onChange={e => handleChange(key, e.target.value)} - className="block w-full text-base px-2 py-1 border border-gray-300 focus:ring-blue-500 focus:border-blue-500 rounded" + className="block w-full text-base px-2 py-1 border border-gray-300 focus:ring-blue-500 focus:border-blue-500 rounded placeholder-gray-400" + style={{ transition: 'box-shadow .3s', boxShadow: translations[key] ? '0 0 0px 1px rgba(59, 130, 246, 0.5)' : 'none' }} /> + ))}
- + */}
From 5cac64260215affa10d986b1c513d09b4cad6841 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 01:50:38 +0300 Subject: [PATCH 10/11] show language switcher only in admins --- components/sidebar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/sidebar.tsx b/components/sidebar.tsx index d13e202..57d86b0 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -7,6 +7,9 @@ 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; @@ -151,7 +154,9 @@ export default function Sidebar({ isSidebarOpen, toggleSidebar }) {

- + + +
From e67c78b6a3489d2591e9b8f219f24d7bd57e7f53 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 29 Apr 2024 02:29:26 +0300 Subject: [PATCH 11/11] adjust more the UI - final --- pages/_app.tsx | 2 +- pages/cart/translations/index.tsx | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index afb21f2..897ed80 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -26,7 +26,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' export default function App({ Component, pageProps: { session, ...pageProps }, }: AppProps<{ session: Session }>) { - console.log(pageProps); + // console.log(pageProps); const router = useRouter(); const [messages, setMessages] = useState({}); diff --git a/pages/cart/translations/index.tsx b/pages/cart/translations/index.tsx index c34c2e6..6929349 100644 --- a/pages/cart/translations/index.tsx +++ b/pages/cart/translations/index.tsx @@ -40,7 +40,7 @@ const AdminTranslations = () => { return ( -
+

Edit Translations

@@ -70,24 +70,23 @@ const AdminTranslations = () => { - - - + {/* Adjusted width */} + {/* Adjusted width */} + {/* Adjusted width */} {Object.entries(baseTranslations).map(([key, baseValue]) => ( - +
KeyBase TranslationTranslationKeyBase TranslationTranslation
{key}{key} {baseValue} - handleChange(key, e.target.value)} - className="block w-full text-base px-2 py-1 border border-gray-300 focus:ring-blue-500 focus:border-blue-500 rounded placeholder-gray-400" - style={{ transition: 'box-shadow .3s', boxShadow: translations[key] ? '0 0 0px 1px rgba(59, 130, 246, 0.5)' : 'none' }} + className="block w-60hv text-base px-2 py-1 border border-gray-300 focus:ring-blue-500 focus:border-blue-500 rounded placeholder-gray-400" + style={{ width: '100%', resize: 'both', transition: 'box-shadow .3s', boxShadow: translations[key] ? '0 0 0px 1px rgba(59, 130, 246, 0.5)' : 'none' }} />