diff --git a/client-tauri/package.json b/client-tauri/package.json index 1b6f068fb..466785cdc 100644 --- a/client-tauri/package.json +++ b/client-tauri/package.json @@ -14,10 +14,13 @@ "@tauri-apps/api": "^1.5.1", "archiver": "^6.0.1", "bootstrap": "^5.3.2", + "i18next": "^23.6.0", + "i18next-browser-languagedetector": "^7.1.0", "path-browserify": "^1.0.1", "react": "^18.2.0", "react-bootstrap": "^2.9.1", "react-dom": "^18.2.0", + "react-i18next": "^13.3.1", "react-icons": "^4.11.0", "react-router-bootstrap": "^0.26.2", "react-router-dom": "^6.18.0" diff --git a/client-tauri/src/App.tsx b/client-tauri/src/App.tsx index c503ef4f3..fe7bda096 100644 --- a/client-tauri/src/App.tsx +++ b/client-tauri/src/App.tsx @@ -1,3 +1,5 @@ +import { Suspense } from 'react'; + import { Routes, Route, Outlet } from "react-router-dom"; import Home from "./pages/Home"; import About from "./pages/About"; @@ -6,11 +8,28 @@ import NoMatch from "./pages/NoMatch"; import NavBar from "./components/NavBar"; import 'bootstrap/dist/css/bootstrap.min.css'; -import { Col, Container, Row } from "react-bootstrap"; +import { Container } from "react-bootstrap"; + +import i18n from "i18next"; +import { useTranslation, initReactI18next } from "react-i18next"; +import LanguageDetector from 'i18next-browser-languagedetector'; +import ar from './locales/ar.json'; +import en from './locales/en.json'; + +import './general.css' + +i18n + .use(LanguageDetector) + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + fallbackLng: "en", + resources: { ar,en }, + }); export default function App() { + return ( -
+ {/* Routes nest inside one another. Nested route paths build upon parent route paths, and nested route elements render inside parent route elements. See the note about below. */} @@ -26,13 +45,14 @@ export default function App() { } /> -
+ ); } function Layout() { + const { t } = useTranslation(); return ( -
+
{/* An renders whatever child route is currently active, diff --git a/client-tauri/src/components/LanguagePicker.tsx b/client-tauri/src/components/LanguagePicker.tsx new file mode 100644 index 000000000..2610b3ecb --- /dev/null +++ b/client-tauri/src/components/LanguagePicker.tsx @@ -0,0 +1,30 @@ + + +import NavDropdown from 'react-bootstrap/NavDropdown'; +import { useTranslation } from 'react-i18next'; +import { BsGlobe2 } from 'react-icons/bs'; + +function generateSublist() { + const { t, i18n } = useTranslation(); + const out: JSX.Element[] = []; + const languages = i18n.options.resources; + for (var key in languages) { + const lang: any = languages[key].translation; + const staticKey = key; + out.push(( + i18n.changeLanguage(staticKey)}> + {lang.language?.flag} + {lang.language?.name} + + )); + } + return <>{out}; +} + +export default function LanguagePicker() { + return ( + }> + {generateSublist()} + + ); +} \ No newline at end of file diff --git a/client-tauri/src/components/NavBar.css b/client-tauri/src/components/NavBar.css index abe47888a..04ef30212 100644 --- a/client-tauri/src/components/NavBar.css +++ b/client-tauri/src/components/NavBar.css @@ -103,6 +103,8 @@ height: 16px; vertical-align: middle; transform: translateY(-2px); + margin-left: 4px; + margin-right: 4px; } /* .icon+.icon { @@ -110,7 +112,8 @@ } */ .nav-icon span { - margin-left: 9px; + margin-left: 4px; + margin-right: 4px; } .nav-item-separator { diff --git a/client-tauri/src/components/NavBar.tsx b/client-tauri/src/components/NavBar.tsx index 35b01d670..cafc3fe92 100644 --- a/client-tauri/src/components/NavBar.tsx +++ b/client-tauri/src/components/NavBar.tsx @@ -1,12 +1,19 @@ + +import { AiOutlineMergeCells, AiOutlineSplitCells } from "react-icons/ai"; +import { BiCrop } from "react-icons/bi"; import { BsTools, BsSortNumericDown, BsArrowClockwise, BsFileEarmarkX, BsLayoutSplit, BsPalette, BsArrowUpSquare, Bs1Square, BsFileEarmarkPdf, BsArrowLeftRight, BsFileEarmarkImage, BsFileEarmark, BsFiletypeHtml, BsLink, BsFiletypeMd, BsFileEarmarkWord, BsFiletypePpt, BsFiletypeTxt, - BsFiletypeXml + BsFiletypeXml, BsLock, BsUnlock, BsShieldLock, BsDroplet, BsAward, BsEraserFill, BsCardList, BsClipboardData, BsFile, BsFileEarmarkRichtext, + BsFileZip, BsFiletypeJs, BsFonts, BsImages, BsInfoCircle, BsSearch, BsShieldCheck, BsVectorPen, BsWrench } from "react-icons/bs"; -import { AiOutlineMergeCells, AiOutlineSplitCells } from "react-icons/ai"; +import { GiScales } from "react-icons/gi"; import { LuLayoutGrid } from "react-icons/lu"; +import { MdOutlineScanner } from "react-icons/md"; +import { PiArrowsInLineVertical } from "react-icons/pi"; import { SlSizeFullscreen } from "react-icons/sl"; -import { BiCrop } from "react-icons/bi"; +import { TfiSpray } from "react-icons/tfi"; +import { TbNumbers } from "react-icons/tb"; import { IconType } from "react-icons"; import Container from 'react-bootstrap/Container'; @@ -14,7 +21,9 @@ import Nav from 'react-bootstrap/Nav'; import Navbar from 'react-bootstrap/Navbar'; import NavDropdown from 'react-bootstrap/NavDropdown'; import { LinkContainer } from 'react-router-bootstrap'; +import { useTranslation } from 'react-i18next'; +import LanguagePicker from "./LanguagePicker"; import Logo from '../../public/stirling-pdf-logo.svg' import './NavBar.css'; @@ -36,53 +45,107 @@ function convertToNavLink(item: NavInfoItem, index: number) { function convertToNavDropdownItem(item: NavInfoItem | null) { if (item == null) return ; - return {item.displayText}; + + return ( + + + + {item.displayText} + + + ); } function convertToNavDropdown(sublist: NavInfoSublist) { + var myTitle = <> + + + {sublist.displayText} + + ; + return ( - {sublist.displayText}} id="basic-nav-dropdown"> + {sublist.sublist.map(convertToNavDropdownItem)} ); } +function NavBar() { + const { t, i18n } = useTranslation(); - /* A "layout route" is a good place to put markup you want to - share across all the pages on your site, like navigation. */ -function Layout() { const navInfo = [ - {displayText: "PDF Multi Tool", icon: BsTools, dest: "/home", tooltip: "Merge, Rotate, Rearrange, and Remove pages"}, - {displayText: "Page Operations", icon: BsFileEarmarkPdf, sublist: [ - { displayText: "Merge", icon: AiOutlineMergeCells, dest: "/dashboard", tooltip: "Easily merge multiple PDFs into one." }, - { displayText: "Split", icon: AiOutlineSplitCells, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Organise", icon: BsSortNumericDown, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Rotate", icon: BsArrowClockwise, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Remove", icon: BsFileEarmarkX, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Multi-Page Layout", icon: LuLayoutGrid, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Adjust page size/scale", icon: SlSizeFullscreen, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Auto Split Pages", icon: BsLayoutSplit, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Adjust Colors/Contrast", icon: BsPalette, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Crop PDF", icon: BiCrop, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "Extract page(s)", icon: BsArrowUpSquare, dest: "/nothing-here", tooltip: "fghjgfhj" }, - { displayText: "PDF to Single Large Page", icon: Bs1Square, dest: "/nothing-here", tooltip: "fghjgfhj" }, + {displayText: t('multiTool.title'), icon: BsTools, dest: "/home", tooltip: t('home.multiTool.desc')}, + {displayText: t('navbar.pageOps'), icon: BsFileEarmarkPdf, sublist: [ + { displayText: t('home.merge.title'), icon: AiOutlineMergeCells, dest: "/dashboard", tooltip: t('home.merge.desc') }, + { displayText: t('home.split.title'), icon: AiOutlineSplitCells, dest: "/nothing-here", tooltip: t('home.split.desc') }, + { displayText: t('home.pdfOrganiser.title'), icon: BsSortNumericDown, dest: "/nothing-here", tooltip: t('home.pdfOrganiser.desc') }, + { displayText: t('home.rotate.title'), icon: BsArrowClockwise, dest: "/nothing-here", tooltip: t('home.rotate.desc') }, + { displayText: t('home.removePages.title'), icon: BsFileEarmarkX, dest: "/nothing-here", tooltip: t('home.removePages.desc') }, + { displayText: t('home.pageLayout.title'), icon: LuLayoutGrid, dest: "/nothing-here", tooltip: t('home.pageLayout.desc') }, + { displayText: t('home.scalePages.title'), icon: SlSizeFullscreen, dest: "/nothing-here", tooltip: t('home.scalePages.desc') }, + { displayText: t('home.autoSplitPDF.title'), icon: BsLayoutSplit, dest: "/nothing-here", tooltip: t('home.autoSplitPDF.desc') }, + { displayText: t('home.adjust-contrast.title'), icon: BsPalette, dest: "/nothing-here", tooltip: t('home.adjust-contrast.desc') }, + { displayText: t('home.crop.title'), icon: BiCrop, dest: "/nothing-here", tooltip: t('home.crop.desc') }, + { displayText: t('home.extractPage.title'), icon: BsArrowUpSquare, dest: "/nothing-here", tooltip: t('home.extractPage.desc') }, + { displayText: t('home.PdfToSinglePage.title'), icon: Bs1Square, dest: "/nothing-here", tooltip: t('home.PdfToSinglePage.desc') }, ]}, - {displayText: "Convert", icon: BsArrowLeftRight, sublist: [ - { displayText: "Image to PDF", icon: BsFileEarmarkImage, dest: "/dashboard" }, - { displayText: "File to PDF", icon: BsFileEarmark, dest: "/nothing-here" }, - { displayText: "HTML to PDF", icon: BsFiletypeHtml, dest: "/nothing-here" }, - { displayText: "URL/Website To PDF", icon: BsLink, dest: "/nothing-here" }, - { displayText: "Markdown to PDF", icon: BsFiletypeMd, dest: "/nothing-here" }, + {displayText: t('navbar.convert'), icon: BsArrowLeftRight, sublist: [ + { displayText: t('home.imageToPdf.title'), icon: BsFileEarmarkImage, dest: "/dashboard", tooltip: t('home.imageToPdf.desc') }, + { displayText: t('home.fileToPDF.title'), icon: BsFileEarmark, dest: "/nothing-here", tooltip: t('home.fileToPDF.desc') }, + { displayText: t('home.HTMLToPDF.title'), icon: BsFiletypeHtml, dest: "/nothing-here", tooltip: t('home.HTMLToPDF.desc') }, + { displayText: t('home.URLToPDF.title'), icon: BsLink, dest: "/nothing-here", tooltip: t('home.URLToPDF.desc') }, + { displayText: t('home.MarkdownToPDF.title'), icon: BsFiletypeMd, dest: "/nothing-here", tooltip: t('home.MarkdownToPDF.desc') }, null, - { displayText: "PDF to Image", icon: BsFileEarmarkImage, dest: "/nothing-here" }, - { displayText: "PDF to Word", icon: BsFileEarmarkWord, dest: "/nothing-here" }, - { displayText: "PDF to Presentation", icon: BsFiletypePpt, dest: "/nothing-here" }, - { displayText: "PDF to RTF (Text)", icon: BsFiletypeTxt, dest: "/nothing-here" }, - { displayText: "PDF to HTML", icon: BsFiletypeHtml, dest: "/nothing-here" }, - { displayText: "PDF to XML", icon: BsFiletypeXml, dest: "/nothing-here" }, - { displayText: "PDF to PDF/A", icon: BsFileEarmarkPdf, dest: "/nothing-here" }, + { displayText: t('home.pdfToImage.title'), icon: BsFileEarmarkImage, dest: "/nothing-here", tooltip: t('home.pdfToImage.desc') }, + { displayText: t('home.PDFToWord.title'), icon: BsFileEarmarkWord, dest: "/nothing-here", tooltip: t('home.PDFToWord.desc') }, + { displayText: t('home.PDFToPresentation.title'), icon: BsFiletypePpt, dest: "/nothing-here", tooltip: t('home.PDFToPresentation.desc') }, + { displayText: t('home.PDFToText.title'), icon: BsFiletypeTxt, dest: "/nothing-here", tooltip: t('home.PDFToText.desc') }, + { displayText: t('home.PDFToHTML.title'), icon: BsFiletypeHtml, dest: "/nothing-here", tooltip: t('home.PDFToHTML.desc') }, + { displayText: t('home.PDFToXML.title'), icon: BsFiletypeXml, dest: "/nothing-here", tooltip: t('home.PDFToXML.desc') }, + { displayText: t('home.pdfToPDFA.title'), icon: BsFileEarmarkPdf, dest: "/nothing-here", tooltip: t('home.pdfToPDFA.desc') }, + ]}, + {displayText: t('navbar.security'), icon: BsShieldCheck, sublist: [ + { displayText: t('home.addPassword.title'), icon: BsLock, dest: "/dashboard", tooltip: t('home.addPassword.desc') }, + { displayText: t('home.removePassword.title'), icon: BsUnlock, dest: "/nothing-here", tooltip: t('home.removePassword.desc') }, + { displayText: t('home.permissions.title'), icon: BsShieldLock, dest: "/nothing-here", tooltip: t('home.permissions.desc') }, + { displayText: t('home.watermark.title'), icon: BsDroplet, dest: "/nothing-here", tooltip: t('home.watermark.desc') }, + { displayText: t('home.certSign.title'), icon: BsAward, dest: "/nothing-here", tooltip: t('home.certSign.desc') }, + { displayText: t('home.sanitizePdf.title'), icon: TfiSpray, dest: "/nothing-here", tooltip: t('home.sanitizePdf.desc') }, + { displayText: t('home.autoRedact.title'), icon: BsEraserFill, dest: "/nothing-here", tooltip: t('home.autoRedact.desc') }, + ]}, + {displayText: t('navbar.other'), icon: BsCardList, sublist: [ + { displayText: t('home.ocr.title'), icon: BsSearch, dest: "/dashboard", tooltip: t('home.ocr.desc') }, + { displayText: t('home.addImage.title'), icon: BsFileEarmarkRichtext, dest: "/nothing-here", tooltip: t('home.addImage.desc') }, + { displayText: t('home.compressPdfs.title'), icon: BsFileZip, dest: "/nothing-here", tooltip: t('home.compressPdfs.desc') }, + { displayText: t('home.extractImages.title'), icon: BsImages, dest: "/nothing-here", tooltip: t('home.extractImages.desc') }, + { displayText: t('home.changeMetadata.title'), icon: BsClipboardData, dest: "/nothing-here", tooltip: t('home.changeMetadata.desc') }, + { displayText: t('home.ScannerImageSplit.title'), icon: MdOutlineScanner, dest: "/nothing-here", tooltip: t('home.ScannerImageSplit.desc') }, + { displayText: t('home.sign.title'), icon: BsVectorPen, dest: "/nothing-here", tooltip: t('home.sign.desc') }, + { displayText: t('home.flatten.title'), icon: PiArrowsInLineVertical, dest: "/nothing-here", tooltip: t('home.flatten.desc') }, + { displayText: t('home.repair.title'), icon: BsWrench, dest: "/nothing-here", tooltip: t('home.repair.desc') }, + { displayText: t('home.removeBlanks.title'), icon: BsFile, dest: "/nothing-here", tooltip: t('home.removeBlanks.desc') }, + { displayText: t('home.compare.title'), icon: GiScales, dest: "/nothing-here", tooltip: t('home.compare.desc') }, + { displayText: t('home.add-page-numbers.title'), icon: TbNumbers, dest: "/nothing-here", tooltip: t('home.add-page-numbers.desc') }, + { displayText: t('home.auto-rename.title'), icon: BsFonts, dest: "/nothing-here", tooltip: t('home.auto-rename.desc') }, + { displayText: t('home.getPdfInfo.title'), icon: BsInfoCircle, dest: "/nothing-here", tooltip: t('home.getPdfInfo.desc') }, + { displayText: t('home.showJS.title'), icon: BsFiletypeJs, dest: "/nothing-here", tooltip: t('home.showJS.desc') }, ]}, ] as Array; - + + + + + + + + + + + + + + + return ( @@ -94,7 +157,7 @@ function Layout() { -