From d406be62cc07056f2f64ef68556e0e709bdaa556 Mon Sep 17 00:00:00 2001 From: MarkMelior Date: Sun, 14 Jul 2024 15:51:07 +0300 Subject: [PATCH] add Link component for i18n, add category ui, add translate, add githubPath for code mdx --- README.md | 2 + app/[lang]/projects/[category]/page.tsx | 29 ++++++--- middleware.ts | 14 ++++- package.json | 3 +- pnpm-lock.yaml | 59 +++++-------------- projects/best-practice/app-router-auth/en.mdx | 2 +- .../app-router-auth/examples/auth.tsx | 7 ++- projects/best-practice/app-router-auth/ru.mdx | 2 +- .../services/get-projects-by-category.ts | 27 --------- .../auth/ui/logout-button/logout-button.tsx | 5 +- .../locale-switcher/locale-switcher.tsx | 12 ---- src/mdx-components.tsx | 12 +++- src/shared/config/i18n/dictionaries/en.json | 7 ++- src/shared/config/i18n/dictionaries/ru.json | 7 ++- src/shared/config/i18n/get-lang.ts | 10 ++++ src/shared/config/i18n/link.tsx | 46 +++++++++++++++ src/shared/config/i18n/use-dictionary.ts | 37 ++++++++++++ src/shared/config/i18n/use-lang.ts | 26 ++++++++ src/shared/config/index.ts | 5 +- src/shared/hooks/useCopy/useCopy.tsx | 21 ++++--- src/shared/types/github-path.ts | 2 +- src/shared/ui/code-block/code-block.tsx | 9 ++- src/shared/ui/code-block/code.tsx | 10 +++- src/shared/ui/copy-button/copy-button.tsx | 10 +++- .../download-cv-button/download-cv-button.tsx | 3 +- src/shared/ui/link/link.tsx | 49 --------------- .../sidebar-navigation/sidebar-navigation.tsx | 2 +- src/shared/ui/text/text.tsx | 2 +- src/widgets/footer/footer-navigation.tsx | 3 +- src/widgets/footer/footer.tsx | 19 +++--- src/widgets/light/light.module.scss | 10 ++-- src/widgets/light/light.tsx | 2 +- src/widgets/navbar/navbar.tsx | 3 +- 33 files changed, 260 insertions(+), 197 deletions(-) create mode 100644 src/shared/config/i18n/link.tsx create mode 100644 src/shared/config/i18n/use-dictionary.ts create mode 100644 src/shared/config/i18n/use-lang.ts delete mode 100644 src/shared/ui/link/link.tsx diff --git a/README.md b/README.md index cabf936..b0f1241 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ - [middleware.ts](/middleware.ts) - основной функционал работы i18n - [getLang()](src/shared/config/i18n/get-lang.ts) - возвращает выбранный язык `en | ru` +- [useLang()](src/shared/config/i18n/use-lang.ts) - как `getLang()`, только для клиентского компонента - [getDictionary()](src/shared/config/i18n/dictionaries.ts) - возвращает объект с переводами `ключ-значение` +- [useDictionary()](src/shared/config/i18n/use-dictionary.ts) - как `getDictionary()`, только для клиентского компонента - [type Dictionary](src/shared/config/i18n/dictionaries.ts) - [/dictionary](src/shared/config/i18n/dictionaries/) - директория с json файлами переводов diff --git a/app/[lang]/projects/[category]/page.tsx b/app/[lang]/projects/[category]/page.tsx index 4e04f85..c5cae67 100644 --- a/app/[lang]/projects/[category]/page.tsx +++ b/app/[lang]/projects/[category]/page.tsx @@ -1,6 +1,7 @@ import { getProjectsByCategory } from '@/entity/project'; +import { Link } from '@/shared/config'; +import { Header } from '@/widgets'; import { Metadata } from 'next'; -import Link from 'next/link'; export type ProjectCategoryPageProps = { params: { category: string }; @@ -13,16 +14,26 @@ export default async function ProjectCategoryPage({ return ( <> -

{category.title}

+
-

Projects:

- {projects.map((project) => ( - <> - - {project.title} - {project.description} +
+ {projects.map((project) => ( + + {project.title} + + {project.description} + - - ))} + ))} +
); } diff --git a/middleware.ts b/middleware.ts index ddbc0f2..38491a5 100644 --- a/middleware.ts +++ b/middleware.ts @@ -24,7 +24,7 @@ export function middleware(request: NextRequest) { pathname === `/${i18n.defaultLocale}` ) { // The incoming request is for /en/whatever, so we'll reDIRECT to /whatever - return NextResponse.redirect( + const response = NextResponse.redirect( new URL( pathname.replace( `/${i18n.defaultLocale}`, @@ -36,6 +36,8 @@ export function middleware(request: NextRequest) { headers: requestHeaders, }, ); + // response.cookies.set('NEXT_LOCALE', i18n.defaultLocale); + return response; } const pathnameIsMissingLocale = i18n.locales.every( @@ -46,7 +48,7 @@ export function middleware(request: NextRequest) { if (pathnameIsMissingLocale) { // Now for EITHER /en or /nl (for example) we're going to tell Next.js that the request is for /en/whatever // or /nl/whatever, and then reWRITE the request to that it is handled properly. - return NextResponse.rewrite( + const response = NextResponse.rewrite( new URL( `/${i18n.defaultLocale}${pathname}${request.nextUrl.search}`, request.nextUrl.href, @@ -57,13 +59,19 @@ export function middleware(request: NextRequest) { }, }, ); + // response.cookies.set('NEXT_LOCALE', i18n.defaultLocale); + return response; } - return NextResponse.next({ + // const lang = (pathname.split('/')[1] as Locale) || i18n.defaultLocale; + + const response = NextResponse.next({ request: { headers: requestHeaders, }, }); + // response.cookies.set('NEXT_LOCALE', lang); + return response; } export const config = { diff --git a/package.json b/package.json index 6933a11..d48bb28 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "framer-motion": "^11.2.13", "gray-matter": "^4.0.3", "jose": "^5.6.3", + "js-cookie": "^3.0.5", "next": "14.2.4", "next-mdx-remote": "^5.0.0", "next-themes": "^0.3.0", @@ -27,7 +28,6 @@ "react-dom": "^18.3.1", "react-icons": "^5.2.1", "react-syntax-highlighter": "^15.5.0", - "rehype-mdx-code-props": "^3.0.1", "remark-gfm": "^4.0.0", "tailwind-merge": "^2.4.0", "unist-util-visit": "^5.0.0", @@ -37,6 +37,7 @@ "devDependencies": { "@tailwindcss/typography": "^0.5.13", "@types/bcryptjs": "^2.4.6", + "@types/js-cookie": "^3.0.6", "@types/mdx": "^2.0.13", "@types/node": "^20.14.10", "@types/react": "^18.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb5015a..58fb2cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: jose: specifier: ^5.6.3 version: 5.6.3 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 next: specifier: 14.2.4 version: 14.2.4(react-dom@18.3.1)(react@18.3.1)(sass@1.77.7) @@ -56,9 +59,6 @@ dependencies: react-syntax-highlighter: specifier: ^15.5.0 version: 15.5.0(react@18.3.1) - rehype-mdx-code-props: - specifier: ^3.0.1 - version: 3.0.1 remark-gfm: specifier: ^4.0.0 version: 4.0.0 @@ -82,6 +82,9 @@ devDependencies: '@types/bcryptjs': specifier: ^2.4.6 version: 2.4.6 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/mdx': specifier: ^2.0.13 version: 2.0.13 @@ -3285,6 +3288,10 @@ packages: '@types/unist': 3.0.2 dev: false + /@types/js-cookie@3.0.6: + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + dev: true + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: false @@ -4652,12 +4659,6 @@ packages: source-map: 0.7.4 dev: false - /estree-util-value-to-estree@3.1.2: - resolution: {integrity: sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==} - dependencies: - '@types/estree': 1.0.5 - dev: false - /estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} dependencies: @@ -5005,21 +5006,6 @@ packages: resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} dev: false - /hast-util-properties-to-mdx-jsx-attributes@1.0.0: - resolution: {integrity: sha512-MZEdAYiXC8wDBfntAc7syyWHbcg/X1h03DQ7IQ6MKagMttpYhnKqOZR/nia0657Dt2v2vuXB8YuKNExw0Fljew==} - dependencies: - '@types/estree': 1.0.5 - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - estree-util-value-to-estree: 3.1.2 - mdast-util-mdx-jsx: 3.1.2 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.12 - transitivePeerDependencies: - - supports-color - dev: false - /hast-util-to-estree@3.1.0: resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==} dependencies: @@ -5440,6 +5426,11 @@ packages: resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} dev: false + /js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6809,20 +6800,6 @@ packages: set-function-name: 2.0.2 dev: true - /rehype-mdx-code-props@3.0.1: - resolution: {integrity: sha512-BWWKn0N6r7/qd7lbLgv5J8of7imz1l1PyCNoY7BH0AOR9JdJlQIfA9cKqTZVEb2h2GPKh473qrBajF0i01fq3A==} - dependencies: - '@types/hast': 3.0.4 - hast-util-properties-to-mdx-jsx-attributes: 1.0.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-mdx: 3.0.0 - micromark-extension-mdxjs: 3.0.0 - unified: 11.0.5 - unist-util-visit-parents: 6.0.1 - transitivePeerDependencies: - - supports-color - dev: false - /remark-gfm@4.0.0: resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} dependencies: @@ -7204,12 +7181,6 @@ packages: engines: {node: '>=8'} dev: true - /style-to-js@1.1.12: - resolution: {integrity: sha512-tv+/FkgNYHI2fvCoBMsqPHh5xovwiw+C3X0Gfnss/Syau0Nr3IqGOJ9XiOYXoPnToHVbllKFf5qCNFJGwFg5mg==} - dependencies: - style-to-object: 1.0.6 - dev: false - /style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} dependencies: diff --git a/projects/best-practice/app-router-auth/en.mdx b/projects/best-practice/app-router-auth/en.mdx index 9cb79da..553e79b 100644 --- a/projects/best-practice/app-router-auth/en.mdx +++ b/projects/best-practice/app-router-auth/en.mdx @@ -17,7 +17,7 @@ Getting user data: `const user = await getUser();`. It's very simple :) test)) -```TypeScript filename="src/features/auth/api/jwt.ts" +```TypeScript filename="features/auth/api/jwt.ts" githubPath="src/features/auth/api/jwt.ts" import { SignJWT, jwtVerify } from 'jose'; import { SessionPayload } from '../types/definitions'; diff --git a/projects/best-practice/app-router-auth/examples/auth.tsx b/projects/best-practice/app-router-auth/examples/auth.tsx index 01215f5..a7e34f2 100644 --- a/projects/best-practice/app-router-auth/examples/auth.tsx +++ b/projects/best-practice/app-router-auth/examples/auth.tsx @@ -1,4 +1,5 @@ import { LogoutButton } from '@/features'; +import { getDictionary } from '@/shared/config'; import { CodeBlock } from '@/shared/ui'; import { Card } from '@nextui-org/react'; import { FiUserCheck } from 'react-icons/fi'; @@ -7,6 +8,7 @@ import { FormLoginExample } from './ui/form-login'; export const AuthExample = async () => { const user = await getUserExample(); + const dict = await getDictionary(); return ( <> @@ -15,16 +17,17 @@ export const AuthExample = async () => {

- You have successfully logged in as an {user?.name} + {`${dict.ui['auth-logged']} ${user?.name}`}

- + )}
{ -// const dir = path.join(process.cwd(), 'projects', category); -// const indexFile = path.join(dir, 'index.mdx'); -// const categoryMetadata = (await getMetadata(indexFile)) as CategoryMetadata; - -// const projectDirs = await fs.readdir(dir, { withFileTypes: true }); -// const projectMetadata = await Promise.all( -// projectDirs -// .filter((dirent) => dirent.isDirectory()) -// .map(async (dirent) => { -// const projectFile = path.join(dir, dirent.name, 'page.mdx'); -// const projectMetadata = await getMetadata(projectFile); -// return { -// ...projectMetadata, -// link: `/projects/${category}/${dirent.name}`, -// } as ProjectMetadata & { link: string }; -// }), -// ); - -// return { -// category: categoryMetadata, -// projects: projectMetadata, -// }; -// } diff --git a/src/features/auth/ui/logout-button/logout-button.tsx b/src/features/auth/ui/logout-button/logout-button.tsx index 25197db..4dbaedc 100644 --- a/src/features/auth/ui/logout-button/logout-button.tsx +++ b/src/features/auth/ui/logout-button/logout-button.tsx @@ -1,10 +1,11 @@ 'use client'; +import { Dictionary } from '@/shared/config'; import { Button } from '@nextui-org/react'; import { RxExit } from 'react-icons/rx'; import { logout } from '../../services/logout'; -export const LogoutButton = () => { +export const LogoutButton = ({ dict }: { dict: Dictionary['ui'] }) => { return ( ); }; diff --git a/src/features/locale-switcher/locale-switcher.tsx b/src/features/locale-switcher/locale-switcher.tsx index 62c1869..0c04041 100644 --- a/src/features/locale-switcher/locale-switcher.tsx +++ b/src/features/locale-switcher/locale-switcher.tsx @@ -64,17 +64,5 @@ export const LocaleSwitcher = ({ dict }: { dict: Dictionary['ui'] }) => { - // - // - // ); }; diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index 27f6e7d..5df89f5 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -1,18 +1,20 @@ import { Code, CodeBlock, StackVariants, Text } from '@/shared/ui'; import type { MDXComponents } from 'mdx/types'; +import { getDictionary } from './shared/config'; interface ExtendedCodeProps extends React.HTMLAttributes { filename?: string; - // [key: string]: any; // Для поддержки любых других пользовательских атрибутов + githubPath?: string; } export const MDXComponentsFormat: MDXComponents = { - code: (props: ExtendedCodeProps) => { + code: async (props: ExtendedCodeProps) => { const { children, className, ...rest } = props; const match = /language-(\w+)/.exec(className || ''); + const dict = await getDictionary(); if (!match) { - return ; + return ; } return ( @@ -20,6 +22,10 @@ export const MDXComponentsFormat: MDXComponents = { text={String(children)} language={match[1] as StackVariants} fileName={props?.filename} + dict={dict.ui} + github={{ + path: props?.githubPath, + }} {...rest} /> ); diff --git a/src/shared/config/i18n/dictionaries/en.json b/src/shared/config/i18n/dictionaries/en.json index e67f37a..7580cde 100644 --- a/src/shared/config/i18n/dictionaries/en.json +++ b/src/shared/config/i18n/dictionaries/en.json @@ -11,6 +11,9 @@ "copy-code": "Copy code", "copy-success": "Copied to clipboard", "copy-error": "Failed to copy", + "code-show": "Show code", + "code-hide": "Hide", + "code-view-github": "View code on GitHub", "footer-copyright": "Copyright © 2024 Mark Melior.", "footer-made": "Made with ❤️", "footer-edit": "Edit this page on GitHub", @@ -18,6 +21,8 @@ "footer-next": "Next", "navbar-title": "Simple App", "navbar-desc": "Small and modern pet-projects", - "change-lang": "Change language" + "change-lang": "Change language", + "auth-logged": "You have successfully logged in as an", + "auth-logout": "Logout" } } diff --git a/src/shared/config/i18n/dictionaries/ru.json b/src/shared/config/i18n/dictionaries/ru.json index 1559a56..ebca433 100644 --- a/src/shared/config/i18n/dictionaries/ru.json +++ b/src/shared/config/i18n/dictionaries/ru.json @@ -11,6 +11,9 @@ "copy-code": "Скопировать код", "copy-success": "Скопировано в буфер обмена", "copy-error": "Не удалось скопировать", + "code-show": "Показать код", + "code-hide": "Скрыть", + "code-view-github": "Посмотреть код на GitHub", "footer-copyright": "Копирайт © 2024 Mark Melior.", "footer-made": "Сделано с ❤️", "footer-edit": "Редактировать страницу на GitHub", @@ -18,6 +21,8 @@ "footer-next": "Далее", "navbar-title": "Simple App", "navbar-desc": "Небольшие и современные пет-проекты", - "change-lang": "Сменить язык" + "change-lang": "Сменить язык", + "auth-logged": "Вы успешно вошли в систему как", + "auth-logout": "Выйти" } } diff --git a/src/shared/config/i18n/get-lang.ts b/src/shared/config/i18n/get-lang.ts index a42fd92..a016586 100644 --- a/src/shared/config/i18n/get-lang.ts +++ b/src/shared/config/i18n/get-lang.ts @@ -6,9 +6,19 @@ import { Locale, i18n } from './i18n.config'; export const getLang = async (): Promise => { const pathname = await getPathname(); + // // Получаем язык из куки, если она установлена + // const localeCookie = cookies().get('NEXT_LOCALE')?.value as Locale; + + // // Если куки установлена и язык валиден, возвращаем его + // if (localeCookie && i18n.locales.includes(localeCookie)) { + // return localeCookie; + // } + const segment = pathname.split('/')[1] as Locale; + // Если сегмент URL валиден, возвращаем его if (i18n.locales.includes(segment)) return segment; + // В противном случае возвращаем язык по умолчанию return i18n.defaultLocale; }; diff --git a/src/shared/config/i18n/link.tsx b/src/shared/config/i18n/link.tsx new file mode 100644 index 0000000..4e3a95f --- /dev/null +++ b/src/shared/config/i18n/link.tsx @@ -0,0 +1,46 @@ +'use client'; + +import NextLink, { LinkProps as NextLinkProps } from 'next/link'; +import { FC } from 'react'; +import { UrlObject } from 'url'; +import { i18n } from './i18n.config'; +import { useLang } from './use-lang'; + +interface LinkProps extends NextLinkProps { + children: React.ReactNode; +} + +const isExternalLink = (href: string | UrlObject): boolean => { + return ( + typeof href === 'string' && + (href.startsWith('http://') || href.startsWith('https://')) + ); +}; + +const getLocalizedHref = ( + href: string | UrlObject, + lang: string, + defaultLocale: string, +): string | UrlObject => { + if (isExternalLink(href) || typeof href !== 'string') { + return href; + } + return lang === defaultLocale ? href : `/${lang}${href}`; +}; + +export const Link: FC< + Omit, keyof LinkProps> & + LinkProps & { + children?: React.ReactNode; + } & React.RefAttributes +> = ({ children, href, ...props }) => { + const lang = useLang(); + + const localizedHref = getLocalizedHref(href, lang, i18n.defaultLocale); + + return ( + + {children} + + ); +}; diff --git a/src/shared/config/i18n/use-dictionary.ts b/src/shared/config/i18n/use-dictionary.ts new file mode 100644 index 0000000..df2119a --- /dev/null +++ b/src/shared/config/i18n/use-dictionary.ts @@ -0,0 +1,37 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Dictionary } from './dictionaries'; +import { Locale } from './i18n.config'; +import { useLang } from './use-lang'; + +export const useDictionary = (locale?: Locale): Dictionary | undefined => { + const dictionaries = { + en: () => import('./dictionaries/en.json').then((module) => module.default), + ru: () => import('./dictionaries/ru.json').then((module) => module.default), + }; + + const [dict, setDict] = useState(); + + const lang = useLang(); + + useEffect(() => { + dictionaries[locale || lang]?.().then(setDict); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lang, locale]); + + return dict as Dictionary; +}; + +// * The main logic is in getDictionary() +// ! There are a lot of requests to the server +// export const useDictionary = (locale?: Locale): Dictionary | undefined => { +// const [dict, setDict] = useState(); +// const lang = useLang(); + +// useEffect(() => { +// getDictionary().then(setDict); +// }, [lang, locale]); + +// return dict; +// }; diff --git a/src/shared/config/i18n/use-lang.ts b/src/shared/config/i18n/use-lang.ts new file mode 100644 index 0000000..c1b0a16 --- /dev/null +++ b/src/shared/config/i18n/use-lang.ts @@ -0,0 +1,26 @@ +'use client'; + +import { usePathname } from 'next/navigation'; +import { Locale, i18n } from './i18n.config'; + +export const useLang = (): Locale => { + const pathname = usePathname(); + + const segment = pathname.split('/')[1] as Locale; + + if (i18n.locales.includes(segment)) return segment; + + return i18n.defaultLocale; +}; + +// * The main logic is in getLang() +// ! There are a lot of requests to the server +// export const useLang = (): Locale => { +// const [lang, setLang] = useState(i18n.defaultLocale); + +// useEffect(() => { +// getLang().then(setLang); +// }, []); + +// return lang; +// }; diff --git a/src/shared/config/index.ts b/src/shared/config/index.ts index 3e4373a..ea9d1d3 100644 --- a/src/shared/config/index.ts +++ b/src/shared/config/index.ts @@ -1,6 +1,9 @@ import { Dictionary, getDictionary } from './i18n/dictionaries'; import { getLang } from './i18n/get-lang'; import { i18n, Locale } from './i18n/i18n.config'; +import { Link } from './i18n/link'; +import { useDictionary } from './i18n/use-dictionary'; +import { useLang } from './i18n/use-lang'; -export { getDictionary, getLang, i18n }; +export { getDictionary, getLang, i18n, Link, useDictionary, useLang }; export type { Dictionary, Locale }; diff --git a/src/shared/hooks/useCopy/useCopy.tsx b/src/shared/hooks/useCopy/useCopy.tsx index f055614..16ab43a 100644 --- a/src/shared/hooks/useCopy/useCopy.tsx +++ b/src/shared/hooks/useCopy/useCopy.tsx @@ -1,9 +1,11 @@ +import { useDictionary } from '@/shared/config'; import { useState } from 'react'; import { useMessage } from '../useMessage/useMessage'; export const useCopy = () => { const { showMessage } = useMessage(); const [copied, setCopied] = useState(false); + const dict = useDictionary(); const handleCopy = (text: string) => { if (copied) { @@ -12,15 +14,18 @@ export const useCopy = () => { try { navigator.clipboard.writeText(text); - showMessage({ - content: 'Copied to clipboard!', - type: 'success', - }); + + dict?.ui && + showMessage({ + content: dict.ui['copy-success'], + type: 'success', + }); } catch (err) { - showMessage({ - content: 'Failed to copy!', - type: 'error', - }); + dict?.ui && + showMessage({ + content: dict.ui['copy-error'], + type: 'error', + }); } setCopied(true); diff --git a/src/shared/types/github-path.ts b/src/shared/types/github-path.ts index 3a7209c..88e0d6b 100644 --- a/src/shared/types/github-path.ts +++ b/src/shared/types/github-path.ts @@ -1,5 +1,5 @@ export interface GitHubPath { - path: string; + path?: string; owner?: string; repo?: string; } diff --git a/src/shared/ui/code-block/code-block.tsx b/src/shared/ui/code-block/code-block.tsx index 1fa3373..737fb1d 100644 --- a/src/shared/ui/code-block/code-block.tsx +++ b/src/shared/ui/code-block/code-block.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Dictionary } from '@/shared/config'; import { cn, gitHubRepoLink } from '@/shared/lib'; import { GitHubPath } from '@/shared/types/github-path'; import { Theme } from '@/shared/types/theme'; @@ -26,6 +27,7 @@ interface CodeBlockProps { className?: string; disableLineNumbers?: boolean; showHeader?: boolean; + dict: Dictionary['ui']; } export const CodeBlock: FC = ({ @@ -37,6 +39,7 @@ export const CodeBlock: FC = ({ showHeader = true, className, disableLineNumbers, + dict, }) => { const [isExpanded, setIsExpanded] = useState(false); const lines = text.split('\n'); @@ -61,7 +64,7 @@ export const CodeBlock: FC = ({ {fileName ? fileName : language}
{github?.path && ( - + )} - +
@@ -107,7 +110,7 @@ export const CodeBlock: FC = ({ onClick={() => setIsExpanded(!isExpanded)} className='bg-default-100 text-default-600 py-2 px-3 w-full text-left data-[pressed=true]:scale-100 hover:bg-default-200' > - {isExpanded ? 'Hide' : 'Show more'} + {isExpanded ? dict['code-hide'] : dict['code-show']} )} diff --git a/src/shared/ui/code-block/code.tsx b/src/shared/ui/code-block/code.tsx index 40fc81f..40b782f 100644 --- a/src/shared/ui/code-block/code.tsx +++ b/src/shared/ui/code-block/code.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Dictionary } from '@/shared/config'; import { useCopy } from '@/shared/hooks'; import { Button, Tooltip } from '@nextui-org/react'; import { FC } from 'react'; @@ -8,9 +9,14 @@ import { StackVariants } from '../stack-buttons/model/data'; interface CodeProps { text: string; language?: StackVariants; + dict: Dictionary['ui']; } -export const Code: FC = ({ text, language = 'TypeScript' }) => { +export const Code: FC = ({ + text, + dict, + language = 'TypeScript', +}) => { const { handleCopy } = useCopy(); // const [mounted, setMounted] = useState(false); // const { theme } = useTheme(); @@ -20,7 +26,7 @@ export const Code: FC = ({ text, language = 'TypeScript' }) => { // }, []); return ( - +