diff --git a/.env.example b/.env.example index 3e6df42b..777ecca1 100644 --- a/.env.example +++ b/.env.example @@ -58,8 +58,9 @@ REDIS_URL=redis://default:password@localhost:6379 # SENTRY_DSN= # Crowdin (Optional) +CROWDIN_PROJECT_ID= CROWDIN_DISTRIBUTION_HASH= -CROWDIN_PERSONAL_TOKEN= +CROWDIN_ACCESS_TOKEN= # GitHub (OAuth, Optional) GITHUB_CLIENT_ID= diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index de135e15..4647f822 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,3 @@ github: AmruthPillai +open_collective: reactive-resume +custom: https://paypal.me/amruthde diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml index b5db1d0e..d04bda99 100644 --- a/.github/workflows/update-translations.yml +++ b/.github/workflows/update-translations.yml @@ -14,14 +14,13 @@ jobs: uses: actions/checkout@v4.1.1 - name: Sync Sources with Crowdin - uses: crowdin/github-action@v1 + uses: crowdin/github-action@v1.14.1 with: upload_sources: true - create_pull_request: false - upload_translations: false + upload_translations: true download_translations: false env: GITHUB_TOKEN: ${{ github.token }} CROWDIN_PROJECT_ID: "503410" - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }} + CROWDIN_ACCESS_TOKEN: ${{ secrets.CROWDIN_TOKEN }} diff --git a/.gitignore b/.gitignore index 9b2a9795..bef51e40 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,5 @@ libs/prisma # Lingui Compiled Messages apps/client/src/locales/_build/ -apps/client/src/locales \ No newline at end of file +apps/client/src/locales/*/* +!apps/client/src/locales/en-US/* \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg index 61681a81..2672bfca 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -pnpm exec commitlint --edit $1 +pnpm exec commitlint --edit $1 \ No newline at end of file diff --git a/README.md b/README.md index dd017777..aca590b7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume)](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume) [![GitHub Sponsors](https://img.shields.io/github/sponsors/AmruthPillai)](https://github.com/sponsors/AmruthPillai) [![Crowdin](https://badges.crowdin.net/reactive-resume/localized.svg)](https://crowdin.com/project/reactive-resume) +![Discord](https://img.shields.io/discord/1173518977851473940?label=discord&link=https%3A%2F%2Fdiscord.gg%2FhzwkZbyvUW) # Reactive Resume @@ -30,18 +31,21 @@ TODO: Add screenshots of major sections of the app - **Free, forever** and open-source - No telemetry, user tracking or advertising - You can self-host the application in less then 30 seconds -- **Available in 5+ languages**, and counting ([help add your language here](https://translate.rxresu.me/)) -- Use your email address (or a throw-away address, no problem) to create an account +- **Available in 20+ languages**, and counting ([help add/improve your language here](https://translate.rxresu.me/)) +- Use your email address (or a throw-away address) to create an account - You can also sign in with your GitHub or Google account, and even set up two-factor authentication for extra security - Create as many resumes as you like under a single account, optimising each resume for every job application based on it's description for a higher ATS score - **Bring your own OpenAI API key** and unlock features such as improving your writing, fixing spelling and grammar or changing the tone of your text in one-click +- Translate your resume into any language using ChatGPT and import it back for easier editing - Create single page resumes or a resume that spans multiple pages easily -- Mix-and-match your resume and make it yours by picking any colour +- Customize the colors and layouts to add a personal touch to your resume - Customise your page layout as you like just by dragging-and-dropping sections +- Jot down personal notes specific to your resume that's only visible to you - **Dozens of templates** to choose from, ranging from professional to modern to swanky - Supports printing resumes in A4 or Letter page formats -- Create your resume with any font hosted by [Google Fonts](https://fonts.google.com/), and a special inclusion of [Computer Modern](https://tug.org/FontCatalogue/computermodern/) to make your resumes look like they're made in LaTeX +- Design your resume with any font family and variant that's available on [Google Fonts](https://fonts.google.com/) - **Share a personalised link of your resume** to companies or recruiters for them to get the latest updates and as a bonus, you can track how many times your resume has been viewed or downloaded since it's creation +- You can track the number of views or downloads your public resume has received - Built with state-of-the-art (at the moment) and dependable technologies with the simplest and most human-readable code on all of GitHub - **MIT License**, so do what you like with the code as long as you credit the original author - And finally yes, there's a dark mode too 🌓 @@ -54,9 +58,10 @@ TODO: Add screenshots of major sections of the app - Redis (for caching, session storage and resume statistics) - Minio (for object storage: to store avatars, resume PDFs and previews) - Browserless (for headless chrome, to print PDFs and generate previews) -- Optional: An SMTP Server (to send password recovery emails) -- Optional: Sentry (for error tracing and performance monitoring) -- Optional: GitHub/Google OAuth Token (for quickly authenticating users) +- SMTP Server (to send password recovery emails) +- Sentry (for error tracing and performance monitoring) +- GitHub/Google OAuth (for quickly authenticating users) +- LinguiJS and Crowdin (for translation management and localization) ## Star History @@ -68,60 +73,6 @@ TODO: Add screenshots of major sections of the app -## Frequently Asked Questions - -
- Who are you, and why did you build Reactive Resume? - - I'm Amruth Pillai, just another run-off-the-mill developer working at Elara Digital GmbH in Berlin, Germany. I'm married to my beautiful and insanely supportive wife who has helped me in more ways than one in seeing this project to it's fruition. I am originally from Bengaluru, India where I was a developer at Postman (the API testing tool) for a short while. My hobbies include eating, living and breathing code. - -Back in my university days, I designed a really cool dark mode resume (link on my website) using Figma and I had a line of friends and strangers asking me to design their resume for them. - -While I could have charged everyone a hefty sum and retired even before I began, I decided to build the first version of Reactive Resume in 2019. Since then, it's gone through multiple iterations as I've learned a lot of better coding practices over the years. - -At the time of writing, Reactive Resume is probably one of the only handful of resume builders out there available to the world for free and without an annoying paywall at the end. While being free is often associated with software that's not of good quality, I strive to prove them wrong and build a product that people love using and are benefitted by it. - -My dream has always been to build something that at least a handful people use on a daily basis, and I'm extremely proud to say that Reactive Resume, over it's years of development, has **helped over half a million people build their resume**, and I hope it only increases from here and reaches more people who are in need of a good resume to kickstart their career but can't afford to pay for one. - -
- -
- How much does it cost to run Reactive Resume? - -It's not much honestly. DigitalOcean has graciously sponsored their infrastructure to allow me to host Reactive Resume on their platform. There's a small fee I pay to dependent services, to send emails for example, and the most of it goes to managing the domain and other related apps (documentation). - -I've spent countless hours and sleepless nights building the application though, and I honestly do not expect anything in return but to hear from you on how the app has helped you with your career. - -But if you do feel like supporting the developer and the future development of Reactive Resume, please donate (_only if you have some extra money lying around_) to me on my [GitHub Sponsors page](https://github.com/sponsors/AmruthPillai/). You can choose to donate one-time or sponsor a recurring donation. - -
- -
- Other than donating, how can I support you? - -**If you speak a language other than English**, sign up to be a translator on Crowdin, our translation management service. You can help translate the product to your language and share it among your community. Even if the language is already translated, it helps to sign up as you would be notified when there are new phrases to be translated. - -**If you work in the media, are an influencer or have lots of friends**, share the app with your circles and let them know so it can reach the people who need it the most. I’m also [open for interviews](mailto:hello@amruthpillai.com), although that’s wishful thinking. If you do mention Reactive Resume on your blog, let me know so that I can link back to you here. - -**If you found a bug or have an idea for a feature**, raise an issue on GitHub or shoot me a message and let me know what you’d like to see. I can’t promise that it’ll be done soon, but juggling work, life and open-source, I’ll definitely get to it when I can. - -
- -
- How does the OpenAI Integration work? How can I trust you with my API key? - -You should **absolutely not** trust me with your OpenAI API key. - -OpenAI has been a game-changer for all of us. I cannot tell you how much ChatGPT has helped me in my everyday work and with the development of Reactive Resume. It only makes sense that you leverage what AI has to offer and let it help you build the perfect resume. - -While most applications out there charge you a fee to use their AI services (rightfully so, because it isn’t cheap), you can choose to enter your own OpenAI API key on the Settings page (under OpenAI Integration). The key is stored in your browser’s local storage, which means that if you uninstall your browser, or even clear your data, the key is gone with it. All requests made to OpenAI are also sent directly to their service and does not hit the app servers at all. - -The policy behind “Bring your own Key” (BYOK) is [still being discussed](https://community.openai.com/t/openais-bring-your-own-key-policy/14538/46) and probably might change over a period of time, but while it’s available, I would keep the feature on the app. - -You are free to turn off all AI features (and not be aware of it’s existence) simply by not adding a key in the Settings page and still make use of all of the useful features that Reactive Resume has to offer. I would even suggest you to take the extra step of using ChatGPT to write your content, and simply copy it over to Reactive Resume. - -
- ## License Reactive Resume is packaged and distributed using the [MIT License](/LICENSE.md) which allows for commercial use, distribution, modification and private use provided that all copies of the software contain the same license and copyright. diff --git a/apps/artboard/src/pages/artboard.tsx b/apps/artboard/src/pages/artboard.tsx index 55b6c661..7257dbee 100644 --- a/apps/artboard/src/pages/artboard.tsx +++ b/apps/artboard/src/pages/artboard.tsx @@ -45,8 +45,14 @@ export const ArtboardPage = () => { ); }, [metadata]); - // Underline Links + // Typography Options useEffect(() => { + if (metadata.typography.hideIcons) { + document.querySelector("#root")!.classList.add("hide-icons"); + } else { + document.querySelector("#root")!.classList.remove("hide-icons"); + } + if (metadata.typography.underlineLinks) { document.querySelector("#root")!.classList.add("underline-links"); } else { diff --git a/apps/artboard/src/styles/main.css b/apps/artboard/src/styles/main.css index b5977513..57824846 100644 --- a/apps/artboard/src/styles/main.css +++ b/apps/artboard/src/styles/main.css @@ -10,6 +10,10 @@ @apply antialiased; } +#root.hide-icons .ph { + @apply hidden; +} + #root.underline-links a { @apply underline underline-offset-2; } diff --git a/apps/artboard/src/templates/azurill.tsx b/apps/artboard/src/templates/azurill.tsx index d7d28ce9..3ee6c221 100644 --- a/apps/artboard/src/templates/azurill.tsx +++ b/apps/artboard/src/templates/azurill.tsx @@ -230,6 +230,7 @@ const Profiles = () => { label={item.username} icon={ {item.network} { label={item.username} icon={ {item.network} { const basics = useArtboardStore((state) => state.resume.basics); const profiles = useArtboardStore((state) => state.resume.sections.profiles); + const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size); return (
@@ -83,8 +84,9 @@ const Header = () => { className="text-sm" icon={ {item.network} diff --git a/apps/artboard/src/templates/ditto.tsx b/apps/artboard/src/templates/ditto.tsx index f96eeae7..a1e78b72 100644 --- a/apps/artboard/src/templates/ditto.tsx +++ b/apps/artboard/src/templates/ditto.tsx @@ -79,13 +79,13 @@ const Header = () => { )} {basics.customFields.map((item) => ( - <> -
+ +
{[item.name, item.value].filter(Boolean).join(": ")}
- + ))}
@@ -234,6 +234,7 @@ const Profiles = () => { label={item.username} icon={ {item.network} { const basics = useArtboardStore((state) => state.resume.basics); const profiles = useArtboardStore((state) => state.resume.sections.profiles); + const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size); return (
@@ -81,8 +82,9 @@ const Header = () => { className="text-sm" icon={ {item.network} diff --git a/apps/artboard/src/templates/onyx.tsx b/apps/artboard/src/templates/onyx.tsx index 27eeee46..d041d3b5 100644 --- a/apps/artboard/src/templates/onyx.tsx +++ b/apps/artboard/src/templates/onyx.tsx @@ -27,6 +27,7 @@ import { TemplateProps } from "../types/template"; const Header = () => { const basics = useArtboardStore((state) => state.resume.basics); const profiles = useArtboardStore((state) => state.resume.sections.profiles); + const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size); return (
@@ -73,7 +74,7 @@ const Header = () => { {profiles.visible && profiles.items.length > 0 && (
{profiles.items @@ -86,8 +87,9 @@ const Header = () => { className="text-sm" icon={ {item.network} @@ -108,7 +110,7 @@ const Summary = () => { return (
-

{section.name}

+

{section.name}

({ return (
-

{section.name}

+

{section.name}

{ )} {basics.customFields.map((item) => ( - <> -
+ +
{[item.name, item.value].filter(Boolean).join(": ")}
- + ))}
@@ -234,6 +234,7 @@ const Profiles = () => { label={item.username} icon={ {item.network} { label={item.username} icon={ {item.network} { console.log("Custom command example: Login", email, password); }); + // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) diff --git a/apps/client/.eslintrc.json b/apps/client/.eslintrc.json index 1bc3020d..09650214 100644 --- a/apps/client/.eslintrc.json +++ b/apps/client/.eslintrc.json @@ -20,7 +20,13 @@ "react-hooks/exhaustive-deps": "off", // lingui - "lingui/no-unlocalized-strings": 2, + "lingui/no-unlocalized-strings": [ + 2, + { + "ignoreFunction": ["cn"], + "ignoreAttribute": ["alt"] + } + ], "lingui/t-call-in-function": 2, "lingui/no-single-variables-to-translate": 2, "lingui/no-expression-in-message": 2, diff --git a/apps/client/index.html b/apps/client/index.html index 7e85c5dd..6a93c019 100644 --- a/apps/client/index.html +++ b/apps/client/index.html @@ -39,10 +39,5 @@ - - - -
-
diff --git a/apps/client/public/sample-resumes/ditto.jpg b/apps/client/public/sample-resumes/ditto.jpg new file mode 100644 index 00000000..d2588b39 Binary files /dev/null and b/apps/client/public/sample-resumes/ditto.jpg differ diff --git a/apps/client/public/sample-resumes/ditto.pdf b/apps/client/public/sample-resumes/ditto.pdf new file mode 100644 index 00000000..7d1ec06c Binary files /dev/null and b/apps/client/public/sample-resumes/ditto.pdf differ diff --git a/apps/client/public/support-logos/crowdin-dark.svg b/apps/client/public/support-logos/crowdin-dark.svg new file mode 100644 index 00000000..43f17ebb --- /dev/null +++ b/apps/client/public/support-logos/crowdin-dark.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/client/public/support-logos/crowdin-light.svg b/apps/client/public/support-logos/crowdin-light.svg new file mode 100644 index 00000000..ec268df0 --- /dev/null +++ b/apps/client/public/support-logos/crowdin-light.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/client/public/support-logos/github-sponsors-dark.svg b/apps/client/public/support-logos/github-sponsors-dark.svg new file mode 100644 index 00000000..a14a449e --- /dev/null +++ b/apps/client/public/support-logos/github-sponsors-dark.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/apps/client/public/support-logos/github-sponsors-light.svg b/apps/client/public/support-logos/github-sponsors-light.svg new file mode 100644 index 00000000..0184950a --- /dev/null +++ b/apps/client/public/support-logos/github-sponsors-light.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/apps/client/public/support-logos/open-collective-dark.svg b/apps/client/public/support-logos/open-collective-dark.svg new file mode 100644 index 00000000..47674991 --- /dev/null +++ b/apps/client/public/support-logos/open-collective-dark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/client/public/support-logos/open-collective-light.svg b/apps/client/public/support-logos/open-collective-light.svg new file mode 100644 index 00000000..8b50a0b1 --- /dev/null +++ b/apps/client/public/support-logos/open-collective-light.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/client/public/support-logos/paypal.svg b/apps/client/public/support-logos/paypal.svg new file mode 100644 index 00000000..0dda71c5 --- /dev/null +++ b/apps/client/public/support-logos/paypal.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/client/src/components/icon.tsx b/apps/client/src/components/icon.tsx index da4c4756..99df43db 100644 --- a/apps/client/src/components/icon.tsx +++ b/apps/client/src/components/icon.tsx @@ -1,4 +1,3 @@ -import { t } from "@lingui/macro"; import { useTheme } from "@reactive-resume/hooks"; import { cn } from "@reactive-resume/utils"; @@ -26,7 +25,7 @@ export const Icon = ({ size = 32, className }: Props) => { src={src} width={size} height={size} - alt={t`Reactive Resume`} + alt="Reactive Resume" className={cn("rounded-sm", className)} /> ); diff --git a/apps/client/src/components/locale-switch.tsx b/apps/client/src/components/locale-switch.tsx new file mode 100644 index 00000000..d285dbdc --- /dev/null +++ b/apps/client/src/components/locale-switch.tsx @@ -0,0 +1,79 @@ +import { t } from "@lingui/macro"; +import { useLingui } from "@lingui/react"; +import { Check, Translate } from "@phosphor-icons/react"; +import { + Button, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + Popover, + PopoverContent, + PopoverTrigger, + ScrollArea, +} from "@reactive-resume/ui"; +import { cn } from "@reactive-resume/utils"; +import { useState } from "react"; + +import { changeLanguage } from "../providers/locale"; +import { useLanguages } from "../services/resume/translation"; + +export const LocaleSwitch = () => { + const { i18n } = useLingui(); + const { languages } = useLanguages(); + + const [open, setOpen] = useState(false); + + const options = languages.map((language) => ({ + label: language.name, + value: language.locale, + })); + + return ( + + + + + + + + {t`No results found`} + + +
+ {options.map((option) => ( + { + const option = options.find( + (option) => option.value.toLowerCase().trim() === selectedValue, + ); + + if (!option) return null; + + await changeLanguage(option.value); + setOpen(false); + }} + > + + {option.label}{" "} + ({option.value}) + + ))} +
+
+
+
+
+
+ ); +}; diff --git a/apps/client/src/components/logo.tsx b/apps/client/src/components/logo.tsx index 9db15213..9b799c86 100644 --- a/apps/client/src/components/logo.tsx +++ b/apps/client/src/components/logo.tsx @@ -1,4 +1,3 @@ -import { t } from "@lingui/macro"; import { useTheme } from "@reactive-resume/hooks"; import { cn } from "@reactive-resume/utils"; @@ -26,7 +25,7 @@ export const Logo = ({ size = 32, className }: Props) => { src={src} width={size} height={size} - alt={t`Reactive Resume`} + alt="Reactive Resume" className={cn("rounded-sm", className)} /> ); diff --git a/apps/client/src/libs/lingui.ts b/apps/client/src/libs/lingui.ts index aeb6408a..f652efa8 100644 --- a/apps/client/src/libs/lingui.ts +++ b/apps/client/src/libs/lingui.ts @@ -1,30 +1,17 @@ import { i18n } from "@lingui/core"; -import { t } from "@lingui/macro"; import { axios } from "./axios"; -type Locale = "en-US" | "de-DE" | "zu-ZA"; - export const defaultLocale = "en-US"; -export const getLocales = () => { - const locales = { - "en-US": t`English`, - "de-DE": t`German`, - } as Record; - - if (process.env.NODE_ENV === "development") { - // eslint-disable-next-line lingui/no-unlocalized-strings - locales["zu-ZA"] = "Pseudolocalization"; - } - - return locales; -}; +axios(`translation/${defaultLocale}`).then((response) => { + const messages = response.data; + i18n.loadAndActivate({ locale: defaultLocale, messages }); +}); export async function dynamicActivate(locale: string) { const response = await axios(`translation/${locale}`); - const messages = await response.data; + const messages = response.data; - i18n.load(locale, messages); - i18n.activate(locale); + i18n.loadAndActivate({ locale, messages }); } diff --git a/apps/client/src/locales/en-US/messages.po b/apps/client/src/locales/en-US/messages.po new file mode 100644 index 00000000..915f7aee --- /dev/null +++ b/apps/client/src/locales/en-US/messages.po @@ -0,0 +1,1640 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2023-11-10 13:15+0100\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: en-US\n" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Plural-Forms: \n" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:182 +msgid "You have enabled two-factor authentication successfully." +msgstr "You have enabled two-factor authentication successfully." + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:114 +msgid "{value, plural, one {Column} other {Columns}}" +msgstr "{value, plural, one {Column} other {Columns}}" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:20 +msgid "<0>I built Reactive Resume mostly by myself during my spare time, with a lot of help from other great open-source contributors.<1>If you like the app and want to support keeping it free forever, please donate whatever you can afford to give." +msgstr "<0>I built Reactive Resume mostly by myself during my spare time, with a lot of help from other great open-source contributors.<1>If you like the app and want to support keeping it free forever, please donate whatever you can afford to give." + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:51 +msgid "<0>I'm sure the app is not perfect, but I'd like for it to be.<1>If you faced any issues while creating your resume, or have an idea that would help you and other users in creating your resume more easily, drop an issue on the repository or send me an email about it." +msgstr "<0>I'm sure the app is not perfect, but I'd like for it to be.<1>If you faced any issues while creating your resume, or have an idea that would help you and other users in creating your resume more easily, drop an issue on the repository or send me an email about it." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:126 +msgid "<0>Note: By utilizing the OpenAI API, you acknowledge and accept the <1>terms of use and <2>privacy policy outlined by OpenAI. Please note that Reactive Resume bears no responsibility for any improper or unauthorized utilization of the service, and any resulting repercussions or liabilities solely rest on the user." +msgstr "<0>Note: By utilizing the OpenAI API, you acknowledge and accept the <1>terms of use and <2>privacy policy outlined by OpenAI. Please note that Reactive Resume bears no responsibility for any improper or unauthorized utilization of the service, and any resulting repercussions or liabilities solely rest on the user." + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:90 +msgid "<0>The community has spent a lot of time writing the documentation for Reactive Resume, and I'm sure it will help you get started with the app.<1>There are also a lot of examples to help you get started, and features that you might not know about which could help you build your perfect resume." +msgstr "<0>The community has spent a lot of time writing the documentation for Reactive Resume, and I'm sure it will help you get started with the app.<1>There are also a lot of examples to help you get started, and features that you might not know about which could help you build your perfect resume." + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:146 +msgid "<0>Two-factor authentication is currently disabled. You can enable it by adding an authenticator app to your account." +msgstr "<0>Two-factor authentication is currently disabled. You can enable it by adding an authenticator app to your account." + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:139 +msgid "<0>Two-factor authentication is enabled. You will be asked to enter a code every time you sign in." +msgstr "<0>Two-factor authentication is enabled. You will be asked to enter a code every time you sign in." + +#: apps/client/src/pages/home/sections/features/index.tsx:54 +msgid "8 design templates to choose from, more on the way" +msgstr "8 design templates to choose from, more on the way" + +#: apps/client/src/pages/home/page.tsx:18 +#: apps/client/src/pages/home/sections/hero/index.tsx:36 +msgid "A free and open-source resume builder" +msgstr "A free and open-source resume builder" + +#: apps/client/src/pages/home/components/footer.tsx:20 +#: apps/client/src/pages/home/sections/hero/index.tsx:41 +msgid "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume." +msgstr "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume." + +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:29 +msgid "A link has been copied to your clipboard." +msgstr "A link has been copied to your clipboard." + +#: apps/client/src/components/copyright.tsx:29 +msgid "A passion project by <0>Amruth Pillai" +msgstr "A passion project by <0>Amruth Pillai" + +#: apps/client/src/pages/auth/forgot-password/page.tsx:62 +msgid "A password reset link should have been sent to your inbox, if an account existed with the email you provided." +msgstr "A password reset link should have been sent to your inbox, if an account existed with the email you provided." + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:43 +msgid "A4" +msgstr "A4" + +#. Helper text to let the user know what filetypes are accepted. {accept} can be .pdf or .json. +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:272 +msgid "Accepts only {accept} files" +msgstr "Accepts only {accept} files" + +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:104 +msgid "Account" +msgstr "Account" + +#: apps/client/src/pages/builder/sidebars/left/sections/custom/section.tsx:131 +msgid "Add a custom field" +msgstr "Add a custom field" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-base.tsx:108 +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-base.tsx:147 +msgctxt "For example, add a new work experience, or add a new profile." +msgid "Add a new item" +msgstr "Add a new item" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:68 +msgid "Add a new item" +msgstr "Add a new item" + +#: apps/client/src/pages/builder/sidebars/left/index.tsx:80 +#: apps/client/src/pages/builder/sidebars/left/index.tsx:197 +msgid "Add a new section" +msgstr "Add a new section" + +#: apps/client/src/pages/builder/sidebars/right/sections/layout.tsx:267 +msgid "Add New Page" +msgstr "Add New Page" + +#: apps/client/src/components/ai-actions.tsx:70 +msgid "AI" +msgstr "AI" + +#: apps/client/src/pages/auth/register/page.tsx:72 +msgid "Already have an account?" +msgstr "Already have an account?" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:190 +msgid "An error occurred while importing your resume." +msgstr "An error occurred while importing your resume." + +#: apps/client/src/pages/auth/register/page.tsx:60 +msgid "An error occurred while trying to create a new account." +msgstr "An error occurred while trying to create a new account." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:120 +msgid "An error occurred while trying to create your resume." +msgstr "An error occurred while trying to create your resume." + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:108 +msgid "An error occurred while trying to enable two-factor authentication." +msgstr "An error occurred while trying to enable two-factor authentication." + +#: apps/client/src/services/resume/print.ts:36 +msgid "An error occurred while trying to print your resume." +msgstr "An error occurred while trying to print your resume." + +#: apps/client/src/pages/auth/reset-password/page.tsx:57 +msgid "An error occurred while trying to reset your password." +msgstr "An error occurred while trying to reset your password." + +#: apps/client/src/pages/auth/forgot-password/page.tsx:48 +msgid "An error occurred while trying to send your password recovery email." +msgstr "An error occurred while trying to send your password recovery email." + +#: apps/client/src/pages/auth/backup-otp/page.tsx:53 +#: apps/client/src/pages/auth/login/page.tsx:52 +#: apps/client/src/pages/auth/verify-otp/page.tsx:53 +msgid "An error occurred while trying to sign in to your account." +msgstr "An error occurred while trying to sign in to your account." + +#: apps/client/src/pages/auth/verify-email/page.tsx:40 +msgid "An error occurred while trying to verify your email address." +msgstr "An error occurred while trying to verify your email address." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:145 +msgid "An error occurred while validating the file." +msgstr "An error occurred while validating the file." + +#: apps/client/src/pages/home/sections/features/index.tsx:132 +msgid "and many more..." +msgstr "and many more..." + +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:57 +msgid "Anyone with the link can view and download the resume." +msgstr "Anyone with the link can view and download the resume." + +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:30 +msgid "Anyone with this link can view and download the resume. Share it on your profile or with recruiters." +msgstr "Anyone with this link can view and download the resume. Share it on your profile or with recruiters." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:83 +msgid "API Key" +msgstr "API Key" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:115 +msgid "Are you sure you want to delete this item?" +msgstr "Are you sure you want to delete this item?" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:163 +msgid "Are you sure you want to delete your resume?" +msgstr "Are you sure you want to delete your resume?" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:136 +msgid "Are you sure you want to disable two-factor authentication?" +msgstr "Are you sure you want to disable two-factor authentication?" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:38 +msgid "Are you sure you want to lock this resume?" +msgstr "Are you sure you want to lock this resume?" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:39 +msgid "Are you sure you want to unlock this resume?" +msgstr "Are you sure you want to unlock this resume?" + +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:94 +msgid "Are you sure?" +msgstr "Are you sure?" + +#. For example, Computer Science or Business Administration +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:73 +msgid "Area of Study" +msgstr "Area of Study" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:89 +msgid "Aspect Ratio" +msgstr "Aspect Ratio" + +#: apps/client/src/pages/home/sections/features/index.tsx:48 +msgid "Available in 20+ languages" +msgstr "Available in 20+ languages" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx:53 +msgid "Awarder" +msgstr "Awarder" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:249 +msgid "Back" +msgstr "Back" + +#: apps/client/src/pages/builder/sidebars/right/sections/theme.tsx:73 +msgid "Background Color" +msgstr "Background Color" + +#: apps/client/src/pages/auth/backup-otp/page.tsx:81 +msgid "Backup Code" +msgstr "Backup Code" + +#: apps/client/src/pages/auth/backup-otp/page.tsx:86 +msgid "Backup Codes may contain only lowercase letters or numbers, and must be exactly 10 characters." +msgstr "Backup Codes may contain only lowercase letters or numbers, and must be exactly 10 characters." + +#: apps/client/src/pages/builder/sidebars/left/index.tsx:57 +msgctxt "The Basics section of a Resume consists of User's Picture, Full Name, Location etc." +msgid "Basics" +msgstr "Basics" + +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:21 +msgid "Basics" +msgstr "Basics" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:202 +msgid "Border" +msgstr "Border" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:134 +msgid "Border Radius" +msgstr "Border Radius" + +#: apps/client/src/pages/public/page.tsx:76 +msgid "Built with" +msgstr "Built with" + +#: apps/client/src/components/copyright.tsx:27 +#: apps/client/src/pages/home/sections/contributors/index.tsx:20 +msgid "By the community, for the community." +msgstr "By the community, for the community." + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:122 +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:49 +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:170 +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:148 +msgid "Cancel" +msgstr "Cancel" + +#: apps/client/src/components/ai-actions.tsx:94 +#: apps/client/src/components/ai-actions.tsx:97 +msgid "Casual" +msgstr "Casual" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:92 +msgid "Center Artboard" +msgstr "Center Artboard" + +#: apps/client/src/pages/auth/reset-password/page.tsx:106 +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:121 +msgid "Change Password" +msgstr "Change Password" + +#: apps/client/src/components/ai-actions.tsx:88 +msgid "Change Tone" +msgstr "Change Tone" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:200 +msgid "Changed your mind about the name? Give it a new one." +msgstr "Changed your mind about the name? Give it a new one." + +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:69 +msgid "Check your email for the confirmation link to update your email address." +msgstr "Check your email for the confirmation link to update your email address." + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:155 +msgid "Circle" +msgstr "Circle" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:255 +msgid "Close" +msgstr "Close" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:212 +msgid "Code" +msgstr "Code" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:51 +msgid "Code must be exactly 6 digits long." +msgstr "Code must be exactly 6 digits long." + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:108 +msgid "Columns" +msgstr "Columns" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:39 +msgid "Company" +msgstr "Company" + +#: apps/client/src/components/ai-actions.tsx:106 +#: apps/client/src/components/ai-actions.tsx:109 +msgid "Confident" +msgstr "Confident" + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:98 +msgid "Confirm New Password" +msgstr "Confirm New Password" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:245 +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:252 +msgid "Continue" +msgstr "Continue" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-list-item.tsx:93 +msgid "Copy" +msgstr "Copy" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:129 +msgid "Copy Link to Resume" +msgstr "Copy Link to Resume" + +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:78 +msgid "Copy to Clipboard" +msgstr "Copy to Clipboard" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:158 +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:254 +msgid "Create" +msgstr "Create" + +#: apps/client/src/pages/auth/register/page.tsx:70 +msgid "Create a new account" +msgstr "Create a new account" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:146 +msgid "Create a new item" +msgstr "Create a new item" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:192 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/create-card.tsx:24 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/create-item.tsx:19 +msgid "Create a new resume" +msgstr "Create a new resume" + +#: apps/client/src/pages/auth/login/page.tsx:67 +msgctxt "This is a link to create a new account" +msgid "Create one now" +msgstr "Create one now" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:267 +msgid "Create Sample Resume" +msgstr "Create Sample Resume" + +#: apps/client/src/pages/home/sections/features/index.tsx:59 +msgid "Custom resume sections" +msgstr "Custom resume sections" + +#: apps/client/src/stores/resume.ts:45 +msgid "Custom Section" +msgstr "Custom Section" + +#: apps/client/src/pages/home/sections/features/index.tsx:57 +msgid "Customisable colour palettes" +msgstr "Customisable colour palettes" + +#: apps/client/src/pages/home/sections/features/index.tsx:58 +msgid "Customisable layouts" +msgstr "Customisable layouts" + +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:62 +msgid "Danger Zone" +msgstr "Danger Zone" + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:83 +msgid "Dark" +msgstr "Dark" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx:67 +#: apps/client/src/pages/builder/sidebars/left/dialogs/certifications.tsx:67 +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:81 +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:110 +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:72 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:72 +#: apps/client/src/pages/builder/sidebars/left/dialogs/publications.tsx:67 +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:67 +msgid "Date" +msgstr "Date" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:124 +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:172 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:148 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:106 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:159 +msgid "Delete" +msgstr "Delete" + +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:79 +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:94 +msgid "Delete Account" +msgstr "Delete Account" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:67 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:58 +#: apps/client/src/pages/builder/sidebars/left/dialogs/references.tsx:53 +#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:55 +msgid "Description" +msgstr "Description" + +#: apps/client/src/pages/home/sections/features/index.tsx:55 +msgid "Design single/multi page resumes" +msgstr "Design single/multi page resumes" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:150 +msgid "Disable" +msgstr "Disable" + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:155 +msgid "Disable 2FA" +msgstr "Disable 2FA" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:304 +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:222 +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:134 +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:124 +msgid "Discard" +msgstr "Discard" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:110 +msgid "Documentation" +msgstr "Documentation" + +#: apps/client/src/pages/auth/login/page.tsx:64 +msgid "Don't have an account?" +msgstr "Don't have an account?" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:88 +msgid "Don't know where to begin? Hit the docs!" +msgstr "Don't know where to begin? Hit the docs!" + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:107 +msgid "Don't see your language? <0>Help translate the app." +msgstr "Don't see your language? <0>Help translate the app." + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:40 +msgid "Donate to Reactive Resume" +msgstr "Donate to Reactive Resume" + +#: apps/client/src/pages/builder/sidebars/right/sections/export.tsx:56 +msgid "Download a JSON snapshot of your resume. This file can be used to import your resume in the future, or can even be shared with others to collaborate." +msgstr "Download a JSON snapshot of your resume. This file can be used to import your resume in the future, or can even be shared with others to collaborate." + +#: apps/client/src/pages/builder/sidebars/right/sections/export.tsx:74 +msgid "Download a PDF of your resume. This file can be used to print your resume, send it to recruiters, or upload on job portals." +msgstr "Download a PDF of your resume. This file can be used to print your resume, send it to recruiters, or upload on job portals." + +#: apps/client/src/pages/builder/_components/toolbar.tsx:136 +msgid "Download PDF" +msgstr "Download PDF" + +#: apps/client/src/pages/builder/sidebars/right/sections/statistics.tsx:58 +msgid "Downloads" +msgstr "Downloads" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:160 +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:256 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:132 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:95 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:154 +msgid "Duplicate" +msgstr "Duplicate" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:148 +msgid "Duplicate an existing item" +msgstr "Duplicate an existing item" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:194 +msgid "Duplicate an existing resume" +msgstr "Duplicate an existing resume" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-list-item.tsx:89 +msgid "Edit" +msgstr "Edit" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:180 +msgid "Effects" +msgstr "Effects" + +#: apps/client/src/pages/auth/forgot-password/page.tsx:87 +#: apps/client/src/pages/auth/login/page.tsx:86 +#: apps/client/src/pages/auth/register/page.tsx:135 +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:50 +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:183 +msgid "Email" +msgstr "Email" + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:159 +msgid "Enable 2FA" +msgstr "Enable 2FA" + +#: apps/client/src/pages/auth/reset-password/page.tsx:74 +msgid "Enter a new password below, and make sure it's secure." +msgstr "Enter a new password below, and make sure it's secure." + +#: apps/client/src/pages/auth/backup-otp/page.tsx:65 +msgid "Enter one of the 10 backup codes you saved when you enabled two-factor authentication." +msgstr "Enter one of the 10 backup codes you saved when you enabled two-factor authentication." + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:181 +msgid "Enter the 6-digit code from your authenticator app to verify that 2FA has been setup correctly." +msgstr "Enter the 6-digit code from your authenticator app to verify that 2FA has been setup correctly." + +#: apps/client/src/pages/auth/verify-otp/page.tsx:66 +msgid "Enter the one-time password provided by your authenticator app below." +msgstr "Enter the one-time password provided by your authenticator app below." + +#: apps/client/src/pages/auth/forgot-password/page.tsx:75 +msgid "Enter your email address and we will send you a link to reset your password if the account exists." +msgstr "Enter your email address and we will send you a link to reset your password if the account exists." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:285 +msgid "Errors" +msgstr "Errors" + +#: apps/client/src/pages/home/sections/support/index.tsx:81 +msgid "" +"Even if you're not in a position to contribute financially, you can still make a difference by\n" +"giving the GitHub repository a star, spreading the word to your friends, or dropping a quick\n" +"message to let me know how Reactive Resume has helped you. Your feedback and support are\n" +"always welcome and much appreciated!" +msgstr "" +"Even if you're not in a position to contribute financially, you can still make a difference by\n" +"giving the GitHub repository a star, spreading the word to your friends, or dropping a quick\n" +"message to let me know how Reactive Resume has helped you. Your feedback and support are\n" +"always welcome and much appreciated!" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:79 +#: apps/client/src/pages/builder/sidebars/right/sections/export.tsx:40 +msgid "Export" +msgstr "Export" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:257 +msgid "File" +msgstr "File" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:223 +msgid "Filetype" +msgstr "Filetype" + +#: apps/client/src/pages/home/sections/hero/index.tsx:34 +msgid "Finally," +msgstr "Finally," + +#: apps/client/src/components/ai-actions.tsx:81 +msgid "Fix Spelling & Grammar" +msgstr "Fix Spelling & Grammar" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/languages.tsx:51 +msgid "Fluency" +msgstr "Fluency" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/languages.tsx:65 +msgid "Fluency (CEFR)" +msgstr "Fluency (CEFR)" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:94 +msgid "Font Family" +msgstr "Font Family" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:135 +msgid "Font Size" +msgstr "Font Size" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:109 +msgid "Font Subset" +msgstr "Font Subset" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:121 +msgid "Font Variants" +msgstr "Font Variants" + +#: apps/client/src/pages/builder/sidebars/right/sections/notes.tsx:30 +msgid "For example, information regarding which companies you sent this resume to or the links to the job descriptions can be noted down here." +msgstr "For example, information regarding which companies you sent this resume to or the links to the job descriptions can be noted down here." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:107 +msgid "Forget" +msgstr "Forget" + +#: apps/client/src/pages/auth/login/page.tsx:122 +msgid "Forgot Password?" +msgstr "Forgot Password?" + +#: apps/client/src/pages/auth/forgot-password/page.tsx:73 +msgid "Forgot your password?" +msgstr "Forgot your password?" + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:32 +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:40 +msgid "Format" +msgstr "Format" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:49 +msgid "Found a bug, or have an idea for a new feature?" +msgstr "Found a bug, or have an idea for a new feature?" + +#: apps/client/src/pages/home/sections/features/index.tsx:43 +msgid "Free, forever" +msgstr "Free, forever" + +#: apps/client/src/components/ai-actions.tsx:112 +#: apps/client/src/components/ai-actions.tsx:115 +msgid "Friendly" +msgstr "Friendly" + +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:31 +msgid "Full Name" +msgstr "Full Name" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:216 +msgid "Generate a random title for your resume" +msgstr "Generate a random title for your resume" + +#: apps/client/src/pages/home/sections/hero/call-to-action.tsx:33 +msgid "Get Started" +msgstr "Get Started" + +#: apps/client/src/pages/auth/_components/social-auth.tsx:10 +msgid "GitHub" +msgstr "GitHub" + +#: apps/client/src/pages/home/sections/statistics/index.tsx:12 +msgid "GitHub Stars" +msgstr "GitHub Stars" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:201 +msgid "Give your old resume a new name." +msgstr "Give your old resume a new name." + +#: apps/client/src/pages/auth/verify-email/page.tsx:74 +#: apps/client/src/pages/home/sections/hero/call-to-action.tsx:18 +msgid "Go to Dashboard" +msgstr "Go to Dashboard" + +#: apps/client/src/pages/auth/_components/social-auth.tsx:17 +msgid "Google" +msgstr "Google" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:213 +msgid "Grayscale" +msgstr "Grayscale" + +#: apps/client/src/pages/dashboard/resumes/page.tsx:37 +msgid "Grid" +msgstr "Grid" + +#: apps/client/src/pages/home/sections/sample-resumes/index.tsx:17 +msgid "Have a look at some of the resume created to showcase the templates available on Reactive Resume. They also serve some great examples to help guide the creation of your own resume." +msgstr "Have a look at some of the resume created to showcase the templates available on Reactive Resume. They also serve some great examples to help guide the creation of your own resume." + +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:41 +msgid "Headline" +msgstr "Headline" + +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:106 +msgid "Here, you can update your account information such as your profile picture, name and username." +msgstr "Here, you can update your account information such as your profile picture, name and username." + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:63 +msgid "Here, you can update your profile to customize and personalize your experience." +msgstr "Here, you can update your profile to customize and personalize your experience." + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:191 +msgid "Hidden" +msgstr "Hidden" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:78 +msgid "Hide" +msgstr "Hide" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:179 +msgid "Hide Icons" +msgstr "Hide Icons" + +#: apps/client/src/pages/auth/login/page.tsx:106 +#: apps/client/src/pages/auth/register/page.tsx:161 +#: apps/client/src/pages/auth/reset-password/page.tsx:95 +msgid "Hold <0>Ctrl to display your password temporarily." +msgstr "Hold <0>Ctrl to display your password temporarily." + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:104 +msgid "Horizontal" +msgstr "Horizontal" + +#: apps/client/src/pages/home/sections/features/index.tsx:64 +msgid "Host your resume publicly" +msgstr "Host your resume publicly" + +#: apps/client/src/pages/home/sections/testimonials/index.tsx:70 +msgid "I always love to hear from the users of Reactive Resume with feedback or support. Here are some of the messages I've received. If you have any feedback, feel free to drop me an email at <0>{email}." +msgstr "I always love to hear from the users of Reactive Resume with feedback or support. Here are some of the messages I've received. If you have any feedback, feel free to drop me an email at <0>{email}." + +#: apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx:82 +msgid "Icon" +msgstr "Icon" + +#: apps/client/src/pages/home/sections/logo-cloud/index.tsx:47 +msgid "If this app has helped you with your job hunt, let me know by reaching out through <0>this contact form." +msgstr "If this app has helped you with your job hunt, let me know by reaching out through <0>this contact form." + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:139 +msgid "If you disable two-factor authentication, you will no longer be required to enter a verification code when logging in." +msgstr "If you disable two-factor authentication, you will no longer be required to enter a verification code when logging in." + +#: apps/client/src/pages/home/sections/support/index.tsx:59 +msgid "" +"If you're multilingual, we'd love your help in bringing the app to more languages and\n" +"communities. Don't worry if you don't see your language on the list - just give me a\n" +"shout-out on GitHub, and I'll make sure to include it. Ready to get started? Jump into\n" +"translation over at Crowdin by clicking the link below." +msgstr "" +"If you're multilingual, we'd love your help in bringing the app to more languages and\n" +"communities. Don't worry if you don't see your language on the list - just give me a\n" +"shout-out on GitHub, and I'll make sure to include it. Ready to get started? Jump into\n" +"translation over at Crowdin by clicking the link below." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:311 +msgid "Import" +msgstr "Import" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:210 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/import-card.tsx:24 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/import-item.tsx:18 +msgid "Import an existing resume" +msgstr "Import an existing resume" + +#: apps/client/src/components/ai-actions.tsx:76 +msgid "Improve Writing" +msgstr "Improve Writing" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:199 +msgid "In case you are unable to scan this QR Code, you can also copy-paste this link into your authenticator app." +msgstr "In case you are unable to scan this QR Code, you can also copy-paste this link into your authenticator app." + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:70 +msgid "In this section, you can change your password and enable/disable two-factor authentication." +msgstr "In this section, you can change your password and enable/disable two-factor authentication." + +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:64 +msgid "In this section, you can delete your account and all the data associated to your user, but please keep in mind that <0>this action is irreversible." +msgstr "In this section, you can delete your account and all the data associated to your user, but please keep in mind that <0>this action is irreversible." + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:83 +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:122 +msgid "Information" +msgstr "Information" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:39 +msgid "Institution" +msgstr "Institution" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/certifications.tsx:53 +msgid "Issuer" +msgstr "Issuer" + +#: apps/client/src/pages/auth/register/page.tsx:96 +msgctxt "Localized version of a placeholder name. For example, Max Mustermann in German or Jan Kowalski in Polish." +msgid "John Doe" +msgstr "John Doe" + +#: apps/client/src/pages/auth/register/page.tsx:117 +msgctxt "Localized version of a placeholder username. For example, max.mustermann in German or jan.kowalski in Polish." +msgid "john.doe" +msgstr "john.doe" + +#: apps/client/src/pages/auth/register/page.tsx:138 +msgctxt "Localized version of a placeholder email. For example, max.mustermann@example.de in German or jan.kowalski@example.pl in Polish." +msgid "john.doe@example.com" +msgstr "john.doe@example.com" + +#: apps/client/src/pages/builder/sidebars/right/sections/export.tsx:54 +msgid "JSON" +msgstr "JSON" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:145 +#: apps/client/src/pages/builder/sidebars/left/dialogs/interests.tsx:55 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:122 +#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:95 +msgid "Keywords" +msgstr "Keywords" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/url-input.tsx:40 +msgid "Label" +msgstr "Label" + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:96 +msgid "Language" +msgstr "Language" + +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:116 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:121 +msgid "Last updated {lastUpdated}" +msgstr "Last updated {lastUpdated}" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:65 +#: apps/client/src/pages/builder/sidebars/right/sections/layout.tsx:207 +msgid "Layout" +msgstr "Layout" + +#: apps/client/src/pages/home/sections/hero/call-to-action.tsx:39 +msgid "Learn more" +msgstr "Learn more" + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:44 +msgid "Letter" +msgstr "Letter" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:69 +msgid "Level" +msgstr "Level" + +#: apps/client/src/components/copyright.tsx:16 +msgid "Licensed under <0>MIT" +msgstr "Licensed under <0>MIT" + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:82 +msgid "Light" +msgstr "Light" + +#: apps/client/src/pages/home/sections/features/index.tsx:66 +msgid "Light or dark theme" +msgstr "Light or dark theme" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:152 +msgid "Line Height" +msgstr "Line Height" + +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/import-card.tsx:29 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/import-item.tsx:23 +msgid "LinkedIn, JSON Resume, etc." +msgstr "LinkedIn, JSON Resume, etc." + +#: apps/client/src/pages/dashboard/resumes/page.tsx:41 +msgid "List" +msgstr "List" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:95 +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:86 +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:81 +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:83 +msgid "Location" +msgstr "Location" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:51 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:142 +msgid "Lock" +msgstr "Lock" + +#: apps/client/src/pages/home/sections/features/index.tsx:61 +msgid "Lock a resume to prevent editing" +msgstr "Lock a resume to prevent editing" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:43 +msgid "Locking a resume will prevent any further changes to it. This is useful when you have already shared your resume with someone and you don't want to accidentally make any changes to it." +msgstr "Locking a resume will prevent any further changes to it. This is useful when you have already shared your resume with someone and you don't want to accidentally make any changes to it." + +#: apps/client/src/components/user-options.tsx:34 +#: apps/client/src/pages/home/sections/hero/call-to-action.tsx:23 +msgid "Logout" +msgstr "Logout" + +#: apps/client/src/pages/auth/verify-otp/page.tsx:70 +msgid "Lost your device?" +msgstr "Lost your device?" + +#: apps/client/src/pages/builder/sidebars/right/sections/layout.tsx:253 +msgid "Main" +msgstr "Main" + +#: apps/client/src/pages/home/sections/features/index.tsx:56 +msgid "Manage multiple resumes" +msgstr "Manage multiple resumes" + +#. The month and year should be uniform across all languages. +#: apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx:71 +#: apps/client/src/pages/builder/sidebars/left/dialogs/certifications.tsx:69 +#: apps/client/src/pages/builder/sidebars/left/dialogs/publications.tsx:69 +msgid "March 2023" +msgstr "March 2023" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:112 +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:74 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:74 +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:69 +msgid "March 2023 - Present" +msgstr "March 2023 - Present" + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:50 +msgid "Margin" +msgstr "Margin" + +#: apps/client/src/pages/home/sections/features/index.tsx:45 +msgid "MIT License" +msgstr "MIT License" + +#: apps/client/src/pages/auth/register/page.tsx:93 +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:53 +#: apps/client/src/pages/builder/sidebars/left/dialogs/interests.tsx:40 +#: apps/client/src/pages/builder/sidebars/left/dialogs/languages.tsx:37 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:44 +#: apps/client/src/pages/builder/sidebars/left/dialogs/publications.tsx:39 +#: apps/client/src/pages/builder/sidebars/left/dialogs/references.tsx:39 +#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:41 +#: apps/client/src/pages/builder/sidebars/left/sections/custom/section.tsx:50 +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:152 +msgid "Name" +msgstr "Name" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/certifications.tsx:39 +msgctxt "Name of the Certification" +msgid "Name" +msgstr "Name" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx:39 +msgid "Network" +msgstr "Network" + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:85 +msgid "New Password" +msgstr "New Password" + +#: apps/client/src/components/locale-switch.tsx:43 +msgid "No results found" +msgstr "No results found" + +#: apps/client/src/pages/home/sections/features/index.tsx:46 +msgid "No user tracking or advertising" +msgstr "No user tracking or advertising" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:144 +msgid "Note: This will make your account less secure." +msgstr "Note: This will make your account less secure." + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:80 +#: apps/client/src/pages/builder/sidebars/right/sections/notes.tsx:17 +msgid "Notes" +msgstr "Notes" + +#: apps/client/src/pages/auth/verify-otp/page.tsx:88 +msgid "One-Time Password" +msgstr "One-Time Password" + +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:124 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:77 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:146 +msgid "Open" +msgstr "Open" + +#: apps/client/src/pages/home/sections/features/index.tsx:44 +msgid "Open Source" +msgstr "Open Source" + +#: apps/client/src/services/openai/change-tone.ts:30 +#: apps/client/src/services/openai/fix-grammar.ts:28 +#: apps/client/src/services/openai/improve-writing.ts:28 +msgid "OpenAI did not return any choices for your text." +msgstr "OpenAI did not return any choices for your text." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:52 +#: apps/client/src/pages/home/sections/features/index.tsx:49 +msgid "OpenAI Integration" +msgstr "OpenAI Integration" + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:67 +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:169 +msgid "Options" +msgstr "Options" + +#: apps/client/src/pages/auth/layout.tsx:39 +msgctxt "The user can either login with email/password, or continue with GitHub or Google." +msgid "or continue with" +msgstr "or continue with" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:39 +msgid "Organization" +msgstr "Organization" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:72 +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:26 +msgid "Page" +msgstr "Page" + +#: apps/client/src/pages/builder/sidebars/right/sections/layout.tsx:237 +msgid "Page {0}" +msgstr "Page {0}" + +#: apps/client/src/pages/auth/login/page.tsx:101 +#: apps/client/src/pages/auth/register/page.tsx:156 +#: apps/client/src/pages/auth/reset-password/page.tsx:90 +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:76 +msgid "Password" +msgstr "Password" + +#: apps/client/src/pages/builder/sidebars/right/sections/export.tsx:72 +msgid "PDF" +msgstr "PDF" + +#: apps/client/src/pages/home/sections/features/index.tsx:60 +msgid "Personal notes for each resume" +msgstr "Personal notes for each resume" + +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:73 +msgid "Phone" +msgstr "Phone" + +#: apps/client/src/pages/auth/layout.tsx:68 +msgid "Photograph by Patrick Tomasso" +msgstr "Photograph by Patrick Tomasso" + +#: apps/client/src/pages/home/sections/features/index.tsx:63 +msgid "Pick any font from Google Fonts" +msgstr "Pick any font from Google Fonts" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/section.tsx:52 +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:120 +msgid "Picture" +msgstr "Picture" + +#: apps/client/src/pages/auth/verify-email/page.tsx:66 +msgid "Please note that this step is completely optional." +msgstr "Please note that this step is completely optional." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:227 +msgid "Please select a file type" +msgstr "Please select a file type" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:239 +msgid "Please store your backup codes in a secure location. You can use one of these one-time use codes to login in case you lose access to your authenticator app." +msgstr "Please store your backup codes in a secure location. You can use one of these one-time use codes to login in case you lose access to your authenticator app." + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:110 +msgid "Portrait" +msgstr "Portrait" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:54 +msgctxt "Position held at a company, for example, Software Engineer" +msgid "Position" +msgstr "Position" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:53 +msgid "Position" +msgstr "Position" + +#: apps/client/src/pages/home/sections/features/index.tsx:94 +msgid "Powered by" +msgstr "Powered by" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx:98 +msgid "Powered by <0>Simple Icons" +msgstr "Powered by <0>Simple Icons" + +#: apps/client/src/pages/builder/sidebars/right/sections/theme.tsx:43 +msgid "Primary Color" +msgstr "Primary Color" + +#: apps/client/src/components/ai-actions.tsx:100 +#: apps/client/src/components/ai-actions.tsx:103 +msgid "Professional" +msgstr "Professional" + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:61 +msgid "Profile" +msgstr "Profile" + +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:55 +msgid "Public" +msgstr "Public" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/publications.tsx:53 +msgid "Publisher" +msgstr "Publisher" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:69 +msgid "Raise an issue" +msgstr "Raise an issue" + +#: apps/client/src/components/copyright.tsx:38 +#: apps/client/src/pages/builder/page.tsx:39 +#: apps/client/src/pages/dashboard/resumes/page.tsx:20 +#: apps/client/src/pages/dashboard/settings/page.tsx:16 +#: apps/client/src/pages/home/components/footer.tsx:17 +#: apps/client/src/pages/home/page.tsx:18 +#: apps/client/src/pages/public/page.tsx:57 +#: apps/client/src/pages/public/page.tsx:78 +msgid "Reactive Resume" +msgstr "Reactive Resume" + +#: apps/client/src/pages/home/sections/logo-cloud/index.tsx:39 +msgid "Reactive Resume has helped people land jobs at these great companies:" +msgstr "Reactive Resume has helped people land jobs at these great companies:" + +#: apps/client/src/pages/home/sections/support/index.tsx:12 +msgid "Reactive Resume is a free and open-source project crafted mostly by me, and your support would be greatly appreciated. If you're inclined to contribute, and only if you can afford to, consider making a donation through any of the listed platforms. Additionally, donations to Reactive Resume through Open Collective are tax-exempt, as the project is fiscally hosted by Open Collective Europe." +msgstr "Reactive Resume is a free and open-source project crafted mostly by me, and your support would be greatly appreciated. If you're inclined to contribute, and only if you can afford to, consider making a donation through any of the listed platforms. Additionally, donations to Reactive Resume through Open Collective are tax-exempt, as the project is fiscally hosted by Open Collective Europe." + +#: apps/client/src/pages/home/sections/features/index.tsx:105 +msgid "Reactive Resume is a passion project of over 3 years of hard work, and with that comes a number of re-iterated ideas and features that have been built to (near) perfection." +msgstr "Reactive Resume is a passion project of over 3 years of hard work, and with that comes a number of re-iterated ideas and features that have been built to (near) perfection." + +#: apps/client/src/pages/home/sections/contributors/index.tsx:22 +msgid "Reactive Resume thrives thanks to its vibrant community. This project owes its progress to numerous individuals who've dedicated their time and skills. Below, we celebrate the coders who've enhanced its features on GitHub and the linguists whose translations on Crowdin have made it accessible to a broader audience." +msgstr "Reactive Resume thrives thanks to its vibrant community. This project owes its progress to numerous individuals who've dedicated their time and skills. Below, we celebrate the coders who've enhanced its features on GitHub and the linguists whose translations on Crowdin have made it accessible to a broader audience." + +#: apps/client/src/pages/builder/_components/toolbar.tsx:66 +msgid "Redo" +msgstr "Redo" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-list-item.tsx:97 +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:129 +msgid "Remove" +msgstr "Remove" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:83 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:128 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:86 +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx:150 +msgid "Rename" +msgstr "Rename" + +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:201 +msgid "Resend email confirmation link" +msgstr "Resend email confirmation link" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:124 +msgid "Reset" +msgstr "Reset" + +#: apps/client/src/pages/builder/sidebars/right/sections/layout.tsx:210 +msgid "Reset Layout" +msgstr "Reset Layout" + +#: apps/client/src/pages/auth/reset-password/page.tsx:72 +msgid "Reset your password" +msgstr "Reset your password" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:86 +msgid "Reset Zoom" +msgstr "Reset Zoom" + +#: apps/client/src/pages/dashboard/_components/sidebar.tsx:86 +#: apps/client/src/pages/dashboard/resumes/page.tsx:20 +#: apps/client/src/pages/dashboard/resumes/page.tsx:31 +msgid "Resumes" +msgstr "Resumes" + +#: apps/client/src/pages/home/sections/statistics/index.tsx:14 +msgid "Resumes Generated" +msgstr "Resumes Generated" + +#: apps/client/src/pages/home/sections/features/index.tsx:103 +msgid "Rich in features, not in pricing." +msgstr "Rich in features, not in pricing." + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:149 +msgid "Rounded" +msgstr "Rounded" + +#: apps/client/src/pages/home/sections/sample-resumes/index.tsx:15 +msgid "Sample Resumes" +msgstr "Sample Resumes" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:159 +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:255 +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:219 +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:131 +msgid "Save Changes" +msgstr "Save Changes" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:179 +msgid "Scan the QR code below with your authenticator app to setup 2FA on your account." +msgstr "Scan the QR code below with your authenticator app to setup 2FA on your account." + +#. Score or honors for the degree, for example, CGPA or magna cum laude +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:92 +msgid "Score" +msgstr "Score" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:98 +msgid "Search for a font family" +msgstr "Search for a font family" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:113 +msgid "Search for a font subset" +msgstr "Search for a font subset" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:126 +msgid "Search for a font variant" +msgstr "Search for a font variant" + +#: apps/client/src/components/locale-switch.tsx:42 +msgid "Search for a language" +msgstr "Search for a language" + +#: apps/client/src/pages/home/sections/features/index.tsx:53 +msgid "Secure with two-factor authentication" +msgstr "Secure with two-factor authentication" + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:68 +msgid "Security" +msgstr "Security" + +#: apps/client/src/pages/home/sections/features/index.tsx:47 +msgid "Self-host with Docker" +msgstr "Self-host with Docker" + +#: apps/client/src/pages/auth/forgot-password/page.tsx:97 +msgid "Send Email" +msgstr "Send Email" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:79 +msgid "Send me a message" +msgstr "Send me a message" + +#: apps/client/src/components/user-options.tsx:28 +#: apps/client/src/pages/dashboard/_components/sidebar.tsx:92 +#: apps/client/src/pages/dashboard/settings/page.tsx:16 +#: apps/client/src/pages/dashboard/settings/page.tsx:26 +msgid "Settings" +msgstr "Settings" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:170 +msgid "Setup two-factor authentication on your account" +msgstr "Setup two-factor authentication on your account" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:73 +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:39 +msgid "Sharing" +msgstr "Sharing" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:78 +msgid "Show" +msgstr "Show" + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:78 +msgid "Show Break Line" +msgstr "Show Break Line" + +#: apps/client/src/pages/builder/sidebars/right/sections/page.tsx:91 +msgid "Show Page Numbers" +msgstr "Show Page Numbers" + +#: apps/client/src/pages/builder/sidebars/right/sections/layout.tsx:254 +msgid "Sidebar" +msgstr "Sidebar" + +#: apps/client/src/pages/auth/backup-otp/page.tsx:96 +#: apps/client/src/pages/auth/login/page.tsx:118 +#: apps/client/src/pages/auth/verify-otp/page.tsx:98 +msgid "Sign in" +msgstr "Sign in" + +#: apps/client/src/pages/auth/register/page.tsx:75 +msgid "Sign in now" +msgstr "Sign in now" + +#: apps/client/src/pages/auth/login/page.tsx:62 +msgid "Sign in to your account" +msgstr "Sign in to your account" + +#: apps/client/src/pages/home/sections/features/index.tsx:52 +msgid "Sign in with Email" +msgstr "Sign in with Email" + +#: apps/client/src/pages/home/sections/features/index.tsx:50 +msgid "Sign in with GitHub" +msgstr "Sign in with GitHub" + +#: apps/client/src/pages/home/sections/features/index.tsx:51 +msgid "Sign in with Google" +msgstr "Sign in with Google" + +#: apps/client/src/pages/auth/register/page.tsx:172 +msgid "Sign up" +msgstr "Sign up" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:73 +msgid "Size (in px)" +msgstr "Size (in px)" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:242 +msgid "Slug" +msgstr "Slug" + +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:98 +#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:143 +msgid "Square" +msgstr "Square" + +#: apps/client/src/pages/dashboard/resumes/_layouts/list/_components/create-item.tsx:24 +msgid "Start building from scratch" +msgstr "Start building from scratch" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:199 +msgid "Start building your resume by giving it a name." +msgstr "Start building your resume by giving it a name." + +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/create-card.tsx:29 +msgid "Start from scratch" +msgstr "Start from scratch" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:76 +#: apps/client/src/pages/builder/sidebars/right/sections/statistics.tsx:23 +msgid "Statistics" +msgstr "Statistics" + +#: apps/client/src/pages/builder/sidebars/right/sections/statistics.tsx:38 +msgid "Statistics are available only for public resumes." +msgstr "Statistics are available only for public resumes." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:101 +msgid "Store Locally" +msgstr "Store Locally" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:173 +msgid "Store your backup codes securely" +msgstr "Store your backup codes securely" + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:101 +msgid "Stored" +msgstr "Stored" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx:101 +#: apps/client/src/pages/builder/sidebars/left/dialogs/certifications.tsx:95 +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:123 +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:138 +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:114 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:100 +#: apps/client/src/pages/builder/sidebars/left/dialogs/publications.tsx:95 +#: apps/client/src/pages/builder/sidebars/left/dialogs/references.tsx:81 +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:109 +msgid "Summary" +msgstr "Summary" + +#: apps/client/src/pages/builder/sidebars/right/sections/information.tsx:18 +msgid "Support the app by donating what you can!" +msgstr "Support the app by donating what you can!" + +#: apps/client/src/pages/home/sections/support/index.tsx:9 +msgid "Supporting Reactive Resume" +msgstr "Supporting Reactive Resume" + +#: apps/client/src/pages/home/sections/features/index.tsx:62 +msgid "Supports A4/Letter page formats" +msgstr "Supports A4/Letter page formats" + +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:81 +msgid "System" +msgstr "System" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:62 +#: apps/client/src/pages/builder/sidebars/right/sections/template.tsx:18 +msgid "Template" +msgstr "Template" + +#: apps/client/src/pages/home/sections/testimonials/index.tsx:68 +msgid "Testimonials" +msgstr "Testimonials" + +#: apps/client/src/pages/builder/sidebars/right/sections/theme.tsx:103 +msgid "Text Color" +msgstr "Text Color" + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:25 +msgid "That doesn't look like a valid OpenAI API key." +msgstr "That doesn't look like a valid OpenAI API key." + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:34 +msgid "The passwords you entered do not match." +msgstr "The passwords you entered do not match." + +#: apps/client/src/pages/public/page.tsx:104 +msgid "The resume you were looking for doesn't seem to exist, please check the link and try again." +msgstr "The resume you were looking for doesn't seem to exist, please check the link and try again." + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:71 +#: apps/client/src/pages/builder/sidebars/right/sections/theme.tsx:20 +#: apps/client/src/pages/dashboard/settings/_sections/profile.tsx:74 +msgid "Theme" +msgstr "Theme" + +#: apps/client/src/services/resume/update.ts:35 +msgid "There was an error while updating your resume." +msgstr "There was an error while updating your resume." + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:117 +msgid "This action can be reverted by clicking on the undo button in the floating toolbar." +msgstr "This action can be reverted by clicking on the undo button in the floating toolbar." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:165 +msgid "This action cannot be undone. This will permanently delete your resume and cannot be recovered." +msgstr "This action cannot be undone. This will permanently delete your resume and cannot be recovered." + +#: apps/client/src/pages/builder/_components/header.tsx:56 +msgid "This resume is locked, please unlock to make further changes." +msgstr "This resume is locked, please unlock to make further changes." + +#: apps/client/src/pages/builder/sidebars/right/sections/notes.tsx:23 +msgid "This section is reserved for your personal notes specific to this resume. The content here remains private and is not shared with anyone else." +msgstr "This section is reserved for your personal notes specific to this resume. The content here remains private and is not shared with anyone else." + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:230 +msgid "Tip: You can name the resume referring to the position you are applying for." +msgstr "Tip: You can name the resume referring to the position you are applying for." + +#: apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx:39 +msgctxt "Name of the Award" +msgid "Title" +msgstr "Title" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:210 +msgid "Title" +msgstr "Title" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:101 +msgid "Toggle Page Break Line" +msgstr "Toggle Page Break Line" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:114 +msgid "Toggle Page Numbers" +msgstr "Toggle Page Numbers" + +#: apps/client/src/pages/home/sections/features/index.tsx:65 +msgid "Track views and downloads" +msgstr "Track views and downloads" + +#: apps/client/src/pages/auth/verify-otp/page.tsx:63 +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:135 +msgid "Two-Factor Authentication" +msgstr "Two-Factor Authentication" + +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:84 +msgid "Type <0>delete to confirm deleting your account." +msgstr "Type <0>delete to confirm deleting your account." + +#. For example, Bachelor's Degree or Master's Degree +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:54 +msgid "Type of Study" +msgstr "Type of Study" + +#: apps/client/src/pages/builder/sidebars/right/index.tsx:68 +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:66 +msgid "Typography" +msgstr "Typography" + +#: apps/client/src/pages/builder/sidebars/right/sections/typography.tsx:190 +msgid "Underline Links" +msgstr "Underline Links" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:59 +msgid "Undo" +msgstr "Undo" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:52 +#: apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx:137 +msgid "Unlock" +msgstr "Unlock" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/lock.tsx:44 +msgid "Unlocking a resume will allow you to make changes to it again." +msgstr "Unlocking a resume will allow you to make changes to it again." + +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:194 +msgid "Unverified" +msgstr "Unverified" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-dialog.tsx:147 +msgid "Update an existing item" +msgstr "Update an existing item" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/resume.tsx:193 +msgid "Update an existing resume" +msgstr "Update an existing resume" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:214 +msgid "Upload a file from one of the accepted sources to parse existing data and import it into Reactive Resume for easier editing." +msgstr "Upload a file from one of the accepted sources to parse existing data and import it into Reactive Resume for easier editing." + +#: apps/client/src/pages/builder/sidebars/right/sections/sharing.tsx:73 +msgid "URL" +msgstr "URL" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/url-input.tsx:47 +msgid "URL must start with https://" +msgstr "URL must start with https://" + +#: apps/client/src/pages/auth/backup-otp/page.tsx:63 +msgid "Use your backup code" +msgstr "Use your backup code" + +#: apps/client/src/pages/auth/register/page.tsx:114 +#: apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx:54 +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:165 +msgid "Username" +msgstr "Username" + +#: apps/client/src/pages/home/sections/statistics/index.tsx:13 +msgid "Users Signed Up" +msgstr "Users Signed Up" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:298 +msgid "Validate" +msgstr "Validate" + +#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:316 +msgid "Validated" +msgstr "Validated" + +#: apps/client/src/pages/builder/sidebars/left/sections/custom/section.tsx:57 +msgid "Value" +msgstr "Value" + +#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:194 +msgid "Verified" +msgstr "Verified" + +#: apps/client/src/pages/dashboard/settings/_dialogs/two-factor.tsx:172 +msgid "Verify that two-factor authentication has been setup correctly" +msgstr "Verify that two-factor authentication has been setup correctly" + +#: apps/client/src/pages/auth/verify-email/page.tsx:55 +msgid "Verify your email address" +msgstr "Verify your email address" + +#: apps/client/src/pages/home/sections/hero/index.tsx:25 +msgid "Version 4" +msgstr "Version 4" + +#: apps/client/src/pages/builder/sidebars/right/sections/statistics.tsx:51 +msgid "Views" +msgstr "Views" + +#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-list-item.tsx:85 +msgid "Visible" +msgstr "Visible" + +#: apps/client/src/pages/auth/verify-email/page.tsx:68 +msgid "We verify your email address only to ensure that we can send you a password reset link in case you forget your password." +msgstr "We verify your email address only to ensure that we can send you a password reset link in case you forget your password." + +#: apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx:87 +#: apps/client/src/pages/builder/sidebars/left/dialogs/certifications.tsx:81 +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:109 +#: apps/client/src/pages/builder/sidebars/left/dialogs/education.tsx:124 +#: apps/client/src/pages/builder/sidebars/left/dialogs/experience.tsx:100 +#: apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx:68 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:86 +#: apps/client/src/pages/builder/sidebars/left/dialogs/publications.tsx:81 +#: apps/client/src/pages/builder/sidebars/left/dialogs/references.tsx:67 +#: apps/client/src/pages/builder/sidebars/left/dialogs/volunteer.tsx:95 +#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:63 +msgid "Website" +msgstr "Website" + +#: apps/client/src/pages/home/sections/hero/index.tsx:28 +msgid "What's new in the latest version" +msgstr "What's new in the latest version" + +#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:150 +#: apps/client/src/pages/builder/sidebars/left/dialogs/interests.tsx:60 +#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:127 +#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:100 +msgid "You can add multiple keywords by separating them with a comma or pressing enter." +msgstr "You can add multiple keywords by separating them with a comma or pressing enter." + +#: apps/client/src/pages/auth/login/page.tsx:90 +msgid "You can also enter your username." +msgstr "You can also enter your username." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:54 +msgid "You can make use of the OpenAI API to help you generate content, or improve your writing while composing your resume." +msgstr "You can make use of the OpenAI API to help you generate content, or improve your writing while composing your resume." + +#: apps/client/src/pages/builder/sidebars/right/sections/statistics.tsx:40 +msgid "You can track the number of views your resume has received, or how many people have downloaded the resume by enabling public sharing." +msgstr "You can track the number of views your resume has received, or how many people have downloaded the resume by enabling public sharing." + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:60 +msgid "You have the option to <0>obtain your own OpenAI API key. This key empowers you to leverage the API as you see fit. Alternatively, if you wish to disable the AI features in Reactive Resume altogether, you can simply remove the key from your settings." +msgstr "You have the option to <0>obtain your own OpenAI API key. This key empowers you to leverage the API as you see fit. Alternatively, if you wish to disable the AI features in Reactive Resume altogether, you can simply remove the key from your settings." + +#: apps/client/src/pages/auth/verify-email/page.tsx:57 +msgid "You should have received an email from <0>Reactive Resume with a link to verify your account." +msgstr "You should have received an email from <0>Reactive Resume with a link to verify your account." + +#: apps/client/src/pages/auth/forgot-password/page.tsx:59 +msgid "You've got mail!" +msgstr "You've got mail!" + +#: apps/client/src/pages/dashboard/settings/_sections/danger.tsx:52 +msgid "Your account and all your data has been deleted successfully. Goodbye!" +msgstr "Your account and all your data has been deleted successfully. Goodbye!" + +#: apps/client/src/pages/dashboard/settings/_sections/openai.tsx:116 +msgid "Your API key is securely stored in the browser's local storage and is only utilized when making requests to OpenAI via their official SDK. Rest assured that your key is not transmitted to any external server except when interacting with OpenAI's services." +msgstr "Your API key is securely stored in the browser's local storage and is only utilized when making requests to OpenAI via their official SDK. Rest assured that your key is not transmitted to any external server except when interacting with OpenAI's services." + +#: apps/client/src/pages/auth/verify-email/page.tsx:29 +msgid "Your email address has been verified successfully." +msgstr "Your email address has been verified successfully." + +#: apps/client/src/services/openai/client.ts:11 +msgid "Your OpenAI API Key has not been set yet. Please go to your account settings to enable OpenAI Integration." +msgstr "Your OpenAI API Key has not been set yet. Please go to your account settings to enable OpenAI Integration." + +#: apps/client/src/pages/dashboard/settings/_sections/security.tsx:59 +msgid "Your password has been updated successfully." +msgstr "Your password has been updated successfully." + +#: apps/client/src/pages/builder/_components/toolbar.tsx:74 +msgid "Zoom In" +msgstr "Zoom In" + +#: apps/client/src/pages/builder/_components/toolbar.tsx:80 +msgid "Zoom Out" +msgstr "Zoom Out" diff --git a/apps/client/src/pages/auth/layout.tsx b/apps/client/src/pages/auth/layout.tsx index 78f6005b..e5f6d414 100644 --- a/apps/client/src/pages/auth/layout.tsx +++ b/apps/client/src/pages/auth/layout.tsx @@ -2,7 +2,9 @@ import { t } from "@lingui/macro"; import { useMemo } from "react"; import { Link, matchRoutes, Outlet, useLocation } from "react-router-dom"; +import { LocaleSwitch } from "@/client/components/locale-switch"; import { Logo } from "@/client/components/logo"; +import { ThemeSwitch } from "@/client/components/theme-switch"; import { SocialAuth } from "./_components/social-auth"; @@ -15,10 +17,17 @@ export const AuthLayout = () => { return (
-
- - - +
+
+ + + + +
+ + +
+
@@ -45,7 +54,7 @@ export const AuthLayout = () => { {t`Open diff --git a/apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx b/apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx index 0ac2235e..7bcdb889 100644 --- a/apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx +++ b/apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx @@ -36,6 +36,7 @@ type Props = { id: SectionKey }; export const SectionOptions = ({ id }: Props) => { const { open } = useDialog(id); + const setValue = useResumeStore((state) => state.setValue); const removeSection = useResumeStore((state) => state.removeSection); diff --git a/apps/client/src/pages/builder/sidebars/right/index.tsx b/apps/client/src/pages/builder/sidebars/right/index.tsx index c17371a8..27d96cfe 100644 --- a/apps/client/src/pages/builder/sidebars/right/index.tsx +++ b/apps/client/src/pages/builder/sidebars/right/index.tsx @@ -8,7 +8,6 @@ import { ThemeSwitch } from "@/client/components/theme-switch"; import { ExportSection } from "./sections/export"; import { InformationSection } from "./sections/information"; import { LayoutSection } from "./sections/layout"; -import { LocaleSection } from "./sections/locale"; import { NotesSection } from "./sections/notes"; import { PageSection } from "./sections/page"; import { SharingSection } from "./sections/sharing"; @@ -40,8 +39,6 @@ export const RightSidebar = () => { - - diff --git a/apps/client/src/pages/builder/sidebars/right/sections/information.tsx b/apps/client/src/pages/builder/sidebars/right/sections/information.tsx index 6fac67e3..8a481bfd 100644 --- a/apps/client/src/pages/builder/sidebars/right/sections/information.tsx +++ b/apps/client/src/pages/builder/sidebars/right/sections/information.tsx @@ -26,7 +26,6 @@ const DonateCard = () => ( If you like the app and want to support keeping it free forever, please donate whatever you can afford to give.

-

Your donations could be tax-deductible, depending on your location.

diff --git a/apps/client/src/pages/builder/sidebars/right/sections/locale.tsx b/apps/client/src/pages/builder/sidebars/right/sections/locale.tsx deleted file mode 100644 index d9357403..00000000 --- a/apps/client/src/pages/builder/sidebars/right/sections/locale.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { t } from "@lingui/macro"; -import { useLingui } from "@lingui/react"; -import { Combobox, Label } from "@reactive-resume/ui"; -import { useMemo } from "react"; - -import { dynamicActivate, getLocales } from "@/client/libs/lingui"; -import { useResumeStore } from "@/client/stores/resume"; - -import { getSectionIcon } from "../shared/section-icon"; - -export const LocaleSection = () => { - const { _ } = useLingui(); - - const setValue = useResumeStore((state) => state.setValue); - const locale = useResumeStore((state) => state.resume.data.metadata.locale); - - const options = useMemo(() => { - return Object.entries(getLocales()).map(([value, label]) => ({ - label, - value, - })); - }, [_]); - - const onChangeLanguage = async (value: string) => { - setValue("metadata.locale", value); - await dynamicActivate(value); - - // Update resume section titles with new locale - }; - - return ( -
-
-
- {getSectionIcon("locale")} -

{t`Locale`}

-
-
- -
-
- - -
-
-
- ); -}; diff --git a/apps/client/src/pages/builder/sidebars/right/sections/typography.tsx b/apps/client/src/pages/builder/sidebars/right/sections/typography.tsx index ddd6c63e..a09c13ec 100644 --- a/apps/client/src/pages/builder/sidebars/right/sections/typography.tsx +++ b/apps/client/src/pages/builder/sidebars/right/sections/typography.tsx @@ -168,17 +168,26 @@ export const TypographySection = () => {
-
-
- { - setValue("metadata.typography.underlineLinks", checked); - }} - /> - -
+
+ { + setValue("metadata.typography.hideIcons", checked); + }} + /> + +
+ +
+ { + setValue("metadata.typography.underlineLinks", checked); + }} + /> +
diff --git a/apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx b/apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx index dae3ab25..8e14d815 100644 --- a/apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx +++ b/apps/client/src/pages/dashboard/resumes/_layouts/grid/_components/resume-card.tsx @@ -85,10 +85,10 @@ export const ResumeCard = ({ resume }: Props) => { layout initial={{ opacity: 0 }} animate={{ opacity: 1 }} - src={url} loading="lazy" alt={resume.title} className="h-full w-full object-cover" + src={`${url}?cache=${new Date().getTime()}`} /> )} diff --git a/apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx b/apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx index 7af513b4..5891d83d 100644 --- a/apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx +++ b/apps/client/src/pages/dashboard/resumes/_layouts/list/_components/resume-item.tsx @@ -129,10 +129,10 @@ export const ResumeListItem = ({ resume }: Props) => { layout initial={{ opacity: 0 }} animate={{ opacity: 1 }} - src={url} loading="lazy" alt={resume.title} className="aspect-[1/1.4142] w-60 rounded-sm object-cover" + src={`${url}?cache=${new Date().getTime()}`} /> )} diff --git a/apps/client/src/pages/dashboard/settings/_sections/profile.tsx b/apps/client/src/pages/dashboard/settings/_sections/profile.tsx index 0e135807..6351b185 100644 --- a/apps/client/src/pages/dashboard/settings/_sections/profile.tsx +++ b/apps/client/src/pages/dashboard/settings/_sections/profile.tsx @@ -9,7 +9,7 @@ import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { dynamicActivate, getLocales } from "@/client/libs/lingui"; +import { useLanguages } from "@/client/services/resume/translation"; import { useUpdateUser, useUser } from "@/client/services/user"; const formSchema = z.object({ @@ -21,6 +21,7 @@ type FormValues = z.infer; export const ProfileSettings = () => { const { user } = useUser(); + const { languages } = useLanguages(); const { theme, setTheme } = useTheme(); const { updateUser, loading } = useUpdateUser(); @@ -45,8 +46,10 @@ export const ProfileSettings = () => { setTheme(data.theme); if (user.locale !== data.locale) { - await dynamicActivate(data.locale); + window.localStorage.setItem("locale", data.locale); await updateUser({ locale: data.locale }); + + window.location.reload(); } form.reset(data); @@ -96,10 +99,7 @@ export const ProfileSettings = () => { {...field} value={field.value} onValueChange={field.onChange} - options={Object.entries(getLocales()).map(([value, label]) => ({ - label, - value, - }))} + options={languages.map(({ locale, name }) => ({ label: name, value: locale }))} />
diff --git a/apps/client/src/pages/home/components/footer.tsx b/apps/client/src/pages/home/components/footer.tsx index ddca9194..e267d89e 100644 --- a/apps/client/src/pages/home/components/footer.tsx +++ b/apps/client/src/pages/home/components/footer.tsx @@ -2,6 +2,7 @@ import { t } from "@lingui/macro"; import { Separator } from "@reactive-resume/ui"; import { Copyright } from "@/client/components/copyright"; +import { LocaleSwitch } from "@/client/components/locale-switch"; import { Logo } from "@/client/components/logo"; import { ThemeSwitch } from "@/client/components/theme-switch"; @@ -23,7 +24,8 @@ export const Footer = () => (
-
+
+
diff --git a/apps/client/src/pages/home/page.tsx b/apps/client/src/pages/home/page.tsx index 765c7695..c9600b8e 100644 --- a/apps/client/src/pages/home/page.tsx +++ b/apps/client/src/pages/home/page.tsx @@ -1,9 +1,15 @@ import { t } from "@lingui/macro"; import { Helmet } from "react-helmet-async"; +import { ContributorsSection } from "./sections/contributors"; +import { FAQSection } from "./sections/faq"; +import { FeaturesSection } from "./sections/features"; import { HeroSection } from "./sections/hero"; import { LogoCloudSection } from "./sections/logo-cloud"; +import { SampleResumesSection } from "./sections/sample-resumes"; import { StatisticsSection } from "./sections/statistics"; +import { SupportSection } from "./sections/support"; +import { TestimonialsSection } from "./sections/testimonials"; export const HomePage = () => (
@@ -16,5 +22,11 @@ export const HomePage = () => ( + + + + + +
); diff --git a/apps/client/src/pages/home/sections/contributors/index.tsx b/apps/client/src/pages/home/sections/contributors/index.tsx new file mode 100644 index 00000000..91a8ce40 --- /dev/null +++ b/apps/client/src/pages/home/sections/contributors/index.tsx @@ -0,0 +1,67 @@ +import { t } from "@lingui/macro"; +import { Avatar, AvatarFallback, AvatarImage, Tooltip } from "@reactive-resume/ui"; +import { cn } from "@reactive-resume/utils"; +import { motion } from "framer-motion"; +import { useMemo } from "react"; + +import { useContributors } from "@/client/services/resume/contributors"; + +export const ContributorsSection = () => { + const { github, crowdin, loading } = useContributors(); + + const contributors = useMemo(() => { + if (github && crowdin) return [...github, ...crowdin]; + return []; + }, [github, crowdin]); + + return ( +
+
+

{t`By the community, for the community.`}

+

+ {t`Reactive Resume thrives thanks to its vibrant community. This project owes its progress to numerous individuals who've dedicated their time and skills. Below, we celebrate the coders who've enhanced its features on GitHub and the linguists whose translations on Crowdin have made it accessible to a broader audience.`} +

+
+ + {loading && ( +
+ {Array(30) + .fill(0) + .map((_, index) => ( + + + + + + ))} +
+ )} + +
+ {contributors.map((contributor, index) => ( + 30 && "hidden lg:block")} + > + + + + + {contributor.name} + + + + + ))} +
+
+ ); +}; diff --git a/apps/client/src/pages/home/sections/faq/index.tsx b/apps/client/src/pages/home/sections/faq/index.tsx new file mode 100644 index 00000000..813c1afd --- /dev/null +++ b/apps/client/src/pages/home/sections/faq/index.tsx @@ -0,0 +1,247 @@ +/* eslint-disable lingui/text-restrictions */ +/* eslint-disable lingui/no-unlocalized-strings */ + +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@reactive-resume/ui"; +import { cn } from "@reactive-resume/utils"; + +import { useLanguages } from "@/client/services/resume/translation"; + +// Who are you, and why did you build Reactive Resume? +const Question1 = () => ( + + Who are you, and why did you build Reactive Resume? + +

+ I'm Amruth Pillai, just another run-off-the-mill developer working at Elara Digital GmbH in + Berlin, Germany. I'm married to my beautiful and insanely supportive wife who has helped me + in more ways than one in seeing this project to it's fruition. I am originally from + Bengaluru, India where I was a developer at Postman (the API testing tool) for a short + while. +

+ +

+ Back in my university days, I designed a really cool dark mode resume (link on my website) + using Figma and I had a line of friends and strangers asking me to design their resume for + them. +

+ +

+ While I could have charged everyone a hefty sum and retired even before I began, I decided + to build the first version of Reactive Resume in 2019. Since then, it's gone through + multiple iterations as I've learned a lot of better coding practices over the years. +

+ +

+ At the time of writing, Reactive Resume is probably one of the only handful of resume + builders out there available to the world for free and without an annoying paywall at the + end. While being free is often associated with software that's not of good quality, I strive + to prove them wrong and build a product that people love using and are benefitted by it. +

+ +

+ My dream has always been to build something that at least a handful people use on a daily + basis, and I'm extremely proud to say that Reactive Resume, over it's years of development, + has **helped over half a million people build their resume**, and I hope it only increases + from here and reaches more people who are in need of a good resume to kickstart their career + but can't afford to pay for one. +

+
+
+); + +// How much does it cost to run Reactive Resume? +const Question2 = () => ( + + How much does it cost to run Reactive Resume? + +

+ It's not much honestly.{" "} + + DigitalOcean + {" "} + has graciously sponsored their infrastructure to allow me to host Reactive Resume on their + platform. There's only the fee I pay to dependent services to send emails, renew the domain, + etc. +

+ +

+ I've spent countless hours and sleepless nights building the application though, and I + honestly do not expect anything in return but to hear from you on how the app has helped you + with your career. +

+ +

+ But if you do feel like supporting the developer and the future development of Reactive + Resume, please donate (only if you have some extra money lying around) on my{" "} + + GitHub Sponsors page + + . You can choose to donate one-time or sponsor a recurring donation. +

+ +

+ Alternatively, if you are in the US, or you are a part of a large educational institution or + corporate organization, you can{" "} + + support the project through Open Collective + + . We are fiscally hosted through Open Collective Europe, which means your donations and + sponsorships could also be made tax-deductible. +

+
+
+); + +// Other than donating, how can I support you? +const Question3 = () => ( + + Other than donating, how can I support you? + +

+ If you speak a language other than English, sign up to be a translator on + Crowdin, our translation management service. You can help translate the product to your + language and share it among your community. Even if the language is already translated, it + helps to sign up as you would be notified when there are new phrases to be translated. +

+ +

+ If you work in the media, are an influencer or have lots of friends, share + the app with your circles and let them know so it can reach the people who need it the most. + I'm also open to giving tech talks, although + that's wishful thinking. But if you do mention Reactive Resume on your blog, let me know so + that I can link back to you here. +

+ +

+ If you found a bug or have an idea for a feature, raise an issue on GitHub + or shoot me a message and let me know what you'd like to see. I can't promise that it'll be + done soon, but juggling work, life and open-source, I'll definitely get to it when I can. +

+
+
+); + +// What languages are supported on Reactive Resume? +const Question4 = () => { + const { languages } = useLanguages(); + + return ( + + What languages are supported on Reactive Resume? + +

+ Here are the languages currently supported by Reactive Resume, along with their respective + completion percentages. +

+ + + +

+ If you'd like to improve the translations for your language, please{" "} + + sign up as a translator on Crowdin + {" "} + and join the project. You can also choose to be notified of any new phrases that get added + to the app. +

+ +

+ If a language is missing from this list, please raise an issue on GitHub requesting its + inclusion, and I will make sure to add it as soon as possible. +

+
+
+ ); +}; + +// How does the OpenAI Integration work? +const Question5 = () => ( + + How does the OpenAI Integration work? + +

+ OpenAI has been a game-changer for all of us. I cannot tell you how much ChatGPT has helped + me in my everyday work and with the development of Reactive Resume. It only makes sense that + you leverage what AI has to offer and let it help you build the perfect resume. +

+ +

+ While most applications out there charge you a fee to use their AI services (rightfully so, + because it isn't cheap), you can choose to enter your own OpenAI API key on the Settings + page (under OpenAI Integration).{" "} + The key is stored in your browser's local storage, which means that if you + uninstall your browser, or even clear your data, the key is gone with it. All requests made + to OpenAI are also sent directly to their service and does not hit the app servers at all. +

+ +

+ The policy behind "bring your own key" (BYOK) is{" "} + + still being discussed + {" "} + and probably might change over a period of time, but while it's available, I would keep the + feature on the app. +

+ +

+ You are free to turn off all AI features (and not be aware of it's existence) simply by not + adding a key in the Settings page and still make use of all of the useful features that + Reactive Resume has to offer. I would even suggest you to take the extra step of using + ChatGPT to write your content, and simply copy it over to Reactive Resume. +

+
+
+); + +export const FAQSection = () => { + return ( +
+
+
+

Frequently Asked Questions

+ +

+ Here are some questions I often get asked about Reactive Resume. +

+ +

+ Unfortunately, this section is available only in English, as I do not want to burden + translators with having to translate these large paragraphs of text. +

+
+
+ + + + + + + +
+
+
+ ); +}; diff --git a/apps/client/src/pages/home/sections/features/index.tsx b/apps/client/src/pages/home/sections/features/index.tsx new file mode 100644 index 00000000..c506c9a2 --- /dev/null +++ b/apps/client/src/pages/home/sections/features/index.tsx @@ -0,0 +1,140 @@ +import { t } from "@lingui/macro"; +import { + Brain, + Cloud, + CloudSun, + CurrencyDollarSimple, + EnvelopeSimple, + Eye, + File, + Files, + Folder, + GitBranch, + GithubLogo, + GoogleChromeLogo, + GoogleLogo, + IconContext, + Layout, + Lock, + Note, + Prohibit, + Scales, + StackSimple, + Star, + Swatches, + TextAa, + Translate, +} from "@phosphor-icons/react"; +import { cn } from "@reactive-resume/utils"; +import { motion } from "framer-motion"; + +type Feature = { + icon: React.ReactNode; + title: string; + className?: string; +}; + +const featureLabel = cn( + "flex cursor-default items-center justify-center gap-x-2 rounded bg-secondary px-4 py-3 text-sm font-medium leading-none text-primary transition-colors hover:bg-primary hover:text-background", +); + +export const FeaturesSection = () => { + const features: Feature[] = [ + { icon: , title: t`Free, forever` }, + { icon: , title: t`Open Source` }, + { icon: , title: t`MIT License` }, + { icon: , title: t`No user tracking or advertising` }, + { icon: , title: t`Self-host with Docker` }, + { icon: , title: t`Available in 20+ languages` }, + { icon: , title: t`OpenAI Integration` }, + { icon: , title: t`Sign in with GitHub` }, + { icon: , title: t`Sign in with Google` }, + { icon: , title: t`Sign in with Email` }, + { icon: , title: t`Secure with two-factor authentication` }, + { icon: , title: t`8 design templates to choose from, more on the way` }, + { icon: , title: t`Design single/multi page resumes` }, + { icon: , title: t`Manage multiple resumes` }, + { icon: , title: t`Customisable colour palettes` }, + { icon: , title: t`Customisable layouts` }, + { icon: , title: t`Custom resume sections` }, + { icon: , title: t`Personal notes for each resume` }, + { icon: , title: t`Lock a resume to prevent editing` }, + { icon: , title: t`Supports A4/Letter page formats` }, + { icon: , title: t`Pick any font from Google Fonts` }, + { icon: , title: t`Host your resume publicly` }, + { icon: , title: t`Track views and downloads` }, + { icon: , title: t`Light or dark theme` }, + { + icon: ( +
+ React + Vite + TailwindCSS + NestJS + Google Chrome + PostgreSQL + Redis +
+ ), + title: t`Powered by`, + className: "flex-row-reverse", + }, + ]; + + return ( +
+
+
+

{t`Rich in features, not in pricing.`}

+

+ {t`Reactive Resume is a passion project of over 3 years of hard work, and with that comes a number of re-iterated ideas and features that have been built to (near) perfection.`} +

+ + +
+ {features.map((feature, index) => ( + 8 && "hidden lg:flex")} + > + {feature.icon} +

{feature.title}

+
+ ))} + + + {t`and many more...`} + +
+
+
+
+
+ ); +}; diff --git a/apps/client/src/pages/home/sections/hero/index.tsx b/apps/client/src/pages/home/sections/hero/index.tsx index 71af8760..5b9a4984 100644 --- a/apps/client/src/pages/home/sections/hero/index.tsx +++ b/apps/client/src/pages/home/sections/hero/index.tsx @@ -10,13 +10,14 @@ import { HeroCTA } from "./call-to-action"; import { Decoration } from "./decoration"; export const HeroSection = () => ( -
+
@@ -47,13 +48,17 @@ export const HeroSection = () => (
- + {t`Reactive diff --git a/apps/client/src/pages/home/sections/logo-cloud/index.tsx b/apps/client/src/pages/home/sections/logo-cloud/index.tsx index 51ba5666..e717b9a0 100644 --- a/apps/client/src/pages/home/sections/logo-cloud/index.tsx +++ b/apps/client/src/pages/home/sections/logo-cloud/index.tsx @@ -33,7 +33,7 @@ const Logo = ({ company }: LogoProps) => ( const logoList: string[] = ["amazon", "google", "postman", "twilio", "zalando"]; export const LogoCloudSection = () => ( -
+

{t`Reactive Resume has helped people land jobs at these great companies:`} diff --git a/apps/client/src/pages/home/sections/sample-resumes/index.tsx b/apps/client/src/pages/home/sections/sample-resumes/index.tsx new file mode 100644 index 00000000..c71d5a17 --- /dev/null +++ b/apps/client/src/pages/home/sections/sample-resumes/index.tsx @@ -0,0 +1,59 @@ +import { t } from "@lingui/macro"; +import { motion } from "framer-motion"; + +const resumes = [ + "/sample-resumes/ditto", + "/sample-resumes/ditto", + "/sample-resumes/ditto", + "/sample-resumes/ditto", +]; + +export const SampleResumesSection = () => ( +

+
+
+

{t`Sample Resumes`}

+

+ {t`Have a look at some of the resume created to showcase the templates available on Reactive Resume. They also serve some great examples to help guide the creation of your own resume.`} +

+
+ +
+ + {resumes.map((resume, index) => ( + + {resume} + + ))} + + +
+
+
+
+); diff --git a/apps/client/src/pages/home/sections/statistics/counter.tsx b/apps/client/src/pages/home/sections/statistics/counter.tsx index ecc8b5da..b15f5ade 100644 --- a/apps/client/src/pages/home/sections/statistics/counter.tsx +++ b/apps/client/src/pages/home/sections/statistics/counter.tsx @@ -1,40 +1,16 @@ -import { animate, motion } from "framer-motion"; -import { useEffect, useRef, useState } from "react"; +import { animate, motion, useInView } from "framer-motion"; +import { useEffect, useRef } from "react"; type CounterProps = { from: number; to: number }; export const Counter = ({ from, to }: CounterProps) => { - const [isInView, setIsInView] = useState(false); const nodeRef = useRef(null); + const isInView = useInView(nodeRef, { once: true }); useEffect(() => { const node = nodeRef.current; - if (!node) return; - - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - setIsInView(true); - } - }); - }, - { threshold: 0.1 }, - ); - - observer.observe(node); - - return () => { - observer.unobserve(node); - }; - }, []); - - useEffect(() => { - if (!isInView) return; - - const node = nodeRef.current; - if (!node) return; + if (!isInView || !node) return; const controls = animate(from, to, { duration: 1, @@ -51,7 +27,7 @@ export const Counter = ({ from, to }: CounterProps) => { ref={nodeRef} transition={{ duration: 0.5 }} initial={{ opacity: 0, scale: 0.1 }} - animate={isInView ? { opacity: 1, scale: 1 } : {}} + whileInView={{ opacity: 1, scale: 1 }} /> ); }; diff --git a/apps/client/src/pages/home/sections/statistics/index.tsx b/apps/client/src/pages/home/sections/statistics/index.tsx index c20bc0ee..58d288ae 100644 --- a/apps/client/src/pages/home/sections/statistics/index.tsx +++ b/apps/client/src/pages/home/sections/statistics/index.tsx @@ -15,7 +15,7 @@ export const StatisticsSection = () => { ]; return ( -
+
{stats.map((stat, index) => ( diff --git a/apps/client/src/pages/home/sections/support/index.tsx b/apps/client/src/pages/home/sections/support/index.tsx new file mode 100644 index 00000000..2d781901 --- /dev/null +++ b/apps/client/src/pages/home/sections/support/index.tsx @@ -0,0 +1,88 @@ +import { t } from "@lingui/macro"; + +export const SupportSection = () => ( +
+
+

{t`Supporting Reactive Resume`}

+ +

+ {t`Reactive Resume is a free and open-source project crafted mostly by me, and your support would be greatly appreciated. If you're inclined to contribute, and only if you can afford to, consider making a donation through any of the listed platforms. Additionally, donations to Reactive Resume through Open Collective are tax-exempt, as the project is fiscally hosted by Open Collective Europe.`} +

+ + + +

+ {t`If you're multilingual, we'd love your help in bringing the app to more languages and + communities. Don't worry if you don't see your language on the list - just give me a + shout-out on GitHub, and I'll make sure to include it. Ready to get started? Jump into + translation over at Crowdin by clicking the link below.`} +

+ +
+ Crowdin + Crowdin +
+ +

+ {t`Even if you're not in a position to contribute financially, you can still make a difference by + giving the GitHub repository a star, spreading the word to your friends, or dropping a quick + message to let me know how Reactive Resume has helped you. Your feedback and support are + always welcome and much appreciated!`} +

+
+
+); diff --git a/apps/client/src/pages/home/sections/testimonials/index.tsx b/apps/client/src/pages/home/sections/testimonials/index.tsx new file mode 100644 index 00000000..64da5112 --- /dev/null +++ b/apps/client/src/pages/home/sections/testimonials/index.tsx @@ -0,0 +1,106 @@ +/* eslint-disable lingui/text-restrictions */ +/* eslint-disable lingui/no-unlocalized-strings */ + +import { t, Trans } from "@lingui/macro"; +import { Quotes } from "@phosphor-icons/react"; +import { cn } from "@reactive-resume/utils"; +import { motion } from "framer-motion"; + +const email = "hello@amruthpillai.com"; + +type Testimonial = { + quote: string; + name: string; +}; + +const testimonials: Testimonial[][] = [ + [ + { + name: "N. Elnour", + quote: + "This is really a thank you for Reactive Resume. Drafting resumes was never a strength of mine, so your app really made the whole process easy and smooth!", + }, + { + name: "S. Bhaije", + quote: + "Hi Amruth! First off, many thanks for making RxResume! This is one of the best resume-building tools I have ever found. Have also recommended it to many of my university friends...", + }, + { + name: "K. Lietzau", + quote: + "Hi, I just found your resume builder, and I just want to say, I really appreciate it! The moment I saw it was open source, I closed all the other CV sites I was considering. Thank you for your service.", + }, + ], + [ + { + name: "R. Sinnot", + quote: + "Hey, Just wanted to let you know you not only helped me get a job, you helped my partner and my childhood friend, who then used your site to help one of her friends get a job. I sponsored you on Github to give back a bit but I wanted to let you know you really made a difference with your resume builder.", + }, + { + name: "P. Jignesh", + quote: + "Hey, I am a Mechanical engineer, not understand coding, messy AI, and computer systems, But wait, what drags me here is your creativity, Your website RxResume is all good! using it and the efforts you made to keep this free is remarkable. keeping doing great work.", + }, + ], + [ + { + name: "A. Rehman", + quote: + "Hey Amruth, I have loved your Reactive Resume Website. Thank you so much for making this kind of thing.", + }, + { + name: "S. Innocent", + quote: + "First of all, I appreciate your effort for making reactive resume a free tool for the community. Very much better than many premium resume builder...", + }, + { + name: "M. Fritza", + quote: + "Hello sir, I just wanted to write a thank you message for developing RxResume. It's easy to use, intuitive and it's much more practical than many others that made you pay up after spending an hour to create your CV. I'll be sure to buy you a coffee after I get my first job. I wish you everything best in life!", + }, + ], +]; + +export const TestimonialsSection = () => ( +
+
+

{t`Testimonials`}

+

+ + I always love to hear from the users of Reactive Resume with feedback or support. Here are + some of the messages I've received. If you have any feedback, feel free to drop me an + email at{" "} + + {email} + + . + +

+
+ +
+ {testimonials.map((columnGroup, groupIndex) => ( +
+ {columnGroup.map((testimonial, index) => ( + 0 && "hidden lg:block", + )} + > + +
+ “{testimonial.quote}” +
+
{testimonial.name}
+
+ ))} +
+ ))} +
+
+); diff --git a/apps/client/src/providers/locale.tsx b/apps/client/src/providers/locale.tsx index b08a6553..572ab3f5 100644 --- a/apps/client/src/providers/locale.tsx +++ b/apps/client/src/providers/locale.tsx @@ -1,34 +1,44 @@ import "@/client/libs/dayjs"; import { i18n } from "@lingui/core"; -import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale"; +import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale"; import { I18nProvider } from "@lingui/react"; -import get from "lodash.get"; import { useEffect } from "react"; import { defaultLocale, dynamicActivate } from "../libs/lingui"; +import { updateUser } from "../services/user"; import { useAuthStore } from "../stores/auth"; -import { useResumeStore } from "../stores/resume"; type Props = { children: React.ReactNode; }; export const LocaleProvider = ({ children }: Props) => { - const userLocale = useAuthStore((state) => get(state.user, "locale", null)); - const resumeLocale = useResumeStore((state) => get(state.resume, "data.metadata.locale", null)); + const userLocale = useAuthStore((state) => state.user?.locale); useEffect(() => { const detectedLocale = detect( - resumeLocale, - userLocale, - fromUrl("lang"), + fromUrl("locale"), + fromStorage("locale"), fromNavigator(), + userLocale, defaultLocale, )!; dynamicActivate(detectedLocale); - }, [userLocale, resumeLocale]); + }, [userLocale]); return {children}; }; + +export const changeLanguage = async (locale: string) => { + // Update locale in local storage + window.localStorage.setItem("locale", locale); + + // Update locale in user profile, if authenticated + const state = useAuthStore.getState(); + if (state.user) await updateUser({ locale }).catch(() => null); + + // Reload the page for language switch to take effect + window.location.reload(); +}; diff --git a/apps/client/src/services/auth/index.ts b/apps/client/src/services/auth/index.ts index ed81eda4..b7e4d23e 100644 --- a/apps/client/src/services/auth/index.ts +++ b/apps/client/src/services/auth/index.ts @@ -12,7 +12,7 @@ export * from "./email-verification/verify-email"; export * from "./password-recovery/forgot-password"; export * from "./password-recovery/reset-password"; -// Two Factor Authentication +// Two-Factor Authentication export * from "./two-factor-authentication/backup-otp"; export * from "./two-factor-authentication/disable"; export * from "./two-factor-authentication/enable"; diff --git a/apps/client/src/services/resume/contributors.ts b/apps/client/src/services/resume/contributors.ts new file mode 100644 index 00000000..fbc5c8ad --- /dev/null +++ b/apps/client/src/services/resume/contributors.ts @@ -0,0 +1,41 @@ +import { ContributorDto } from "@reactive-resume/dto"; +import { useQuery } from "@tanstack/react-query"; + +import { axios } from "@/client/libs/axios"; + +export const fetchGitHubContributors = async () => { + const response = await axios.get(`/contributors/github`); + + return response.data; +}; + +export const fetchCrowdinContributors = async () => { + const response = await axios.get(`/contributors/crowdin`); + + return response.data; +}; + +export const useContributors = () => { + const { + error: githubError, + isPending: githubLoading, + data: github, + } = useQuery({ + queryKey: ["contributors", "github"], + queryFn: fetchGitHubContributors, + }); + + const { + error: crowdinError, + isPending: crowdinLoading, + data: crowdin, + } = useQuery({ + queryKey: ["contributors", "crowdin"], + queryFn: fetchCrowdinContributors, + }); + + const error = githubError || crowdinError; + const loading = githubLoading || crowdinLoading; + + return { github, crowdin, loading, error }; +}; diff --git a/apps/client/src/services/resume/translation.ts b/apps/client/src/services/resume/translation.ts new file mode 100644 index 00000000..dd1c1cd7 --- /dev/null +++ b/apps/client/src/services/resume/translation.ts @@ -0,0 +1,23 @@ +import { LanguageDto } from "@reactive-resume/dto"; +import { useQuery } from "@tanstack/react-query"; + +import { axios } from "@/client/libs/axios"; + +export const fetchLanguages = async () => { + const response = await axios.get(`/translation/languages`); + + return response.data; +}; + +export const useLanguages = () => { + const { + error, + isPending: loading, + data: languages, + } = useQuery({ + queryKey: ["translation", "languages"], + queryFn: fetchLanguages, + }); + + return { languages: languages ?? [], loading, error }; +}; diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 54e28fdf..2e85c368 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -8,6 +8,7 @@ import { join } from "path"; import { AuthModule } from "./auth/auth.module"; import { CacheModule } from "./cache/cache.module"; import { ConfigModule } from "./config/config.module"; +import { ContributorsModule } from "./contributors/contributors.module"; import { DatabaseModule } from "./database/database.module"; import { HealthModule } from "./health/health.module"; import { MailModule } from "./mail/mail.module"; @@ -36,6 +37,7 @@ import { UtilsModule } from "./utils/utils.module"; StorageModule, PrinterModule, TranslationModule, + ContributorsModule, // Static Assets ServeStaticModule.forRoot({ diff --git a/apps/server/src/auth/auth.controller.ts b/apps/server/src/auth/auth.controller.ts index c64d1b87..10aff5eb 100644 --- a/apps/server/src/auth/auth.controller.ts +++ b/apps/server/src/auth/auth.controller.ts @@ -166,7 +166,7 @@ export class AuthController { response.status(200).send(data); } - // Two Factor Authentication Flows + // Two-Factor Authentication Flows @ApiTags("Two-Factor Auth") @Post("2fa/setup") @UseGuards(JwtGuard) diff --git a/apps/server/src/auth/auth.service.ts b/apps/server/src/auth/auth.service.ts index 6a81de9b..432ac7c4 100644 --- a/apps/server/src/auth/auth.service.ts +++ b/apps/server/src/auth/auth.service.ts @@ -210,7 +210,7 @@ export class AuthService { }); } - // Two Factor Authentication Flows + // Two-Factor Authentication Flows async setup2FASecret(email: string) { // If the user already has 2FA enabled, throw an error const user = await this.userService.findOneByIdentifier(email); diff --git a/apps/server/src/config/schema.ts b/apps/server/src/config/schema.ts index 7907bcc4..5148d8fc 100644 --- a/apps/server/src/config/schema.ts +++ b/apps/server/src/config/schema.ts @@ -43,7 +43,9 @@ export const configSchema = z.object({ SENTRY_DSN: z.string().url().startsWith("https://").optional(), // Crowdin (Optional) + CROWDIN_PROJECT_ID: z.coerce.number().optional(), CROWDIN_DISTRIBUTION_HASH: z.string().optional(), + CROWDIN_ACCESS_TOKEN: z.string().optional(), // GitHub (OAuth) GITHUB_CLIENT_ID: z.string().optional(), diff --git a/apps/server/src/contributors/contributors.controller.ts b/apps/server/src/contributors/contributors.controller.ts new file mode 100644 index 00000000..e65c4b7f --- /dev/null +++ b/apps/server/src/contributors/contributors.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get } from "@nestjs/common"; + +import { UtilsService } from "../utils/utils.service"; +import { ContributorsService } from "./contributors.service"; + +@Controller("contributors") +export class ContributorsController { + constructor( + private readonly contributorsService: ContributorsService, + private readonly utils: UtilsService, + ) {} + + @Get("/github") + async githubContributors() { + return this.utils.getCachedOrSet( + `contributors:github`, + async () => this.contributorsService.fetchGitHubContributors(), + 1000 * 60 * 60 * 24, // 24 hours + ); + } + + @Get("/crowdin") + async crowdinContributors() { + return this.utils.getCachedOrSet( + `contributors:crowdin`, + async () => this.contributorsService.fetchCrowdinContributors(), + 1000 * 60 * 60 * 24, // 24 hours + ); + } +} diff --git a/apps/server/src/contributors/contributors.module.ts b/apps/server/src/contributors/contributors.module.ts new file mode 100644 index 00000000..d6455754 --- /dev/null +++ b/apps/server/src/contributors/contributors.module.ts @@ -0,0 +1,12 @@ +import { HttpModule } from "@nestjs/axios"; +import { Module } from "@nestjs/common"; + +import { ContributorsController } from "./contributors.controller"; +import { ContributorsService } from "./contributors.service"; + +@Module({ + imports: [HttpModule], + controllers: [ContributorsController], + providers: [ContributorsService], +}) +export class ContributorsModule {} diff --git a/apps/server/src/contributors/contributors.service.ts b/apps/server/src/contributors/contributors.service.ts new file mode 100644 index 00000000..02c43435 --- /dev/null +++ b/apps/server/src/contributors/contributors.service.ts @@ -0,0 +1,55 @@ +import { HttpService } from "@nestjs/axios"; +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { ContributorDto } from "@reactive-resume/dto"; + +import { Config } from "../config/schema"; + +type GitHubResponse = { id: number; login: string; html_url: string; avatar_url: string }[]; +type CrowdinContributorsResponse = { + data: { data: { id: number; username: string; avatarUrl: string } }[]; +}; + +@Injectable() +export class ContributorsService { + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) {} + + async fetchGitHubContributors() { + const response = await this.httpService.axiosRef.get( + `https://api.github.com/repos/AmruthPillai/Reactive-Resume/contributors`, + ); + const data = response.data as GitHubResponse; + + return data.map((user) => { + return { + id: user.id, + name: user.login, + url: user.html_url, + avatar: user.avatar_url, + } satisfies ContributorDto; + }); + } + + async fetchCrowdinContributors() { + const projectId = this.configService.get("CROWDIN_PROJECT_ID"); + const accessToken = this.configService.get("CROWDIN_ACCESS_TOKEN"); + + const response = await this.httpService.axiosRef.get( + `https://api.crowdin.com/api/v2/projects/${projectId}/members`, + { headers: { Authorization: `Bearer ${accessToken}` } }, + ); + const { data } = response.data as CrowdinContributorsResponse; + + return data.map(({ data }) => { + return { + id: data.id, + name: data.username, + url: `https://crowdin.com/profile/${data.username}`, + avatar: data.avatarUrl, + } satisfies ContributorDto; + }); + } +} diff --git a/apps/server/src/printer/printer.service.ts b/apps/server/src/printer/printer.service.ts index 28a52080..9750dc1f 100644 --- a/apps/server/src/printer/printer.service.ts +++ b/apps/server/src/printer/printer.service.ts @@ -133,7 +133,7 @@ export class PrinterService { return tempHtml; }, pageElement); - pagesBuffer.push(await page.pdf({ width, height })); + pagesBuffer.push(await page.pdf({ width, height, printBackground: true })); await page.evaluate((tempHtml: string) => { document.body.innerHTML = tempHtml; diff --git a/apps/server/src/resume/resume.controller.ts b/apps/server/src/resume/resume.controller.ts index f68a9df4..acecd960 100644 --- a/apps/server/src/resume/resume.controller.ts +++ b/apps/server/src/resume/resume.controller.ts @@ -126,7 +126,6 @@ export class ResumeController { return { url }; } catch (error) { - console.log(error); Logger.error(error); throw new InternalServerErrorException(error); } diff --git a/apps/server/src/translation/translation.controller.ts b/apps/server/src/translation/translation.controller.ts index 0ac336d7..9f5dc0af 100644 --- a/apps/server/src/translation/translation.controller.ts +++ b/apps/server/src/translation/translation.controller.ts @@ -1,34 +1,31 @@ -import { HttpService } from "@nestjs/axios"; import { Controller, Get, Header, Param } from "@nestjs/common"; -import { ConfigService } from "@nestjs/config"; -import { Config } from "../config/schema"; import { UtilsService } from "../utils/utils.service"; +import { TranslationService } from "./translation.service"; @Controller("translation") export class TranslationController { constructor( - private readonly httpService: HttpService, - private readonly configService: ConfigService, + private readonly translationService: TranslationService, private readonly utils: UtilsService, ) {} - private async fetchTranslations(locale: string) { - const distributionHash = this.configService.get("CROWDIN_DISTRIBUTION_HASH"); - const response = await this.httpService.axiosRef.get( - `https://distributions.crowdin.net/${distributionHash}/content/${locale}/messages.json`, + @Get("/languages") + async languages() { + return this.utils.getCachedOrSet( + `translation:languages`, + async () => this.translationService.fetchLanguages(), + 1000 * 60 * 60 * 24, // 24 hours ); - - return response.data; } @Get("/:locale") @Header("Content-Type", "application/octet-stream") @Header("Content-Disposition", 'attachment; filename="messages.po"') - async getTranslation(@Param("locale") locale: string) { + async translation(@Param("locale") locale: string) { return this.utils.getCachedOrSet( `translation:${locale}`, - async () => this.fetchTranslations(locale), + async () => this.translationService.fetchTranslations(locale), 1000 * 60 * 60 * 24, // 24 hours ); } diff --git a/apps/server/src/translation/translation.module.ts b/apps/server/src/translation/translation.module.ts index 2ca077b7..9592ad39 100644 --- a/apps/server/src/translation/translation.module.ts +++ b/apps/server/src/translation/translation.module.ts @@ -2,9 +2,11 @@ import { HttpModule } from "@nestjs/axios"; import { Module } from "@nestjs/common"; import { TranslationController } from "./translation.controller"; +import { TranslationService } from "./translation.service"; @Module({ imports: [HttpModule], controllers: [TranslationController], + providers: [TranslationService], }) export class TranslationModule {} diff --git a/apps/server/src/translation/translation.service.ts b/apps/server/src/translation/translation.service.ts new file mode 100644 index 00000000..a0d83efb --- /dev/null +++ b/apps/server/src/translation/translation.service.ts @@ -0,0 +1,53 @@ +import { HttpService } from "@nestjs/axios"; +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { LanguageDto } from "@reactive-resume/dto"; + +import { Config } from "../config/schema"; + +type CrowdinResponse = { + data: { + data: { + language: { id: string; name: string; locale: string; editorCode: string }; + translationProgress: number; + }; + }[]; +}; + +@Injectable() +export class TranslationService { + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) {} + + async fetchTranslations(locale: string) { + const distributionHash = this.configService.get("CROWDIN_DISTRIBUTION_HASH"); + const response = await this.httpService.axiosRef.get( + `https://distributions.crowdin.net/${distributionHash}/content/${locale}/messages.json`, + ); + + return response.data; + } + + async fetchLanguages() { + const projectId = this.configService.get("CROWDIN_PROJECT_ID"); + const accessToken = this.configService.get("CROWDIN_ACCESS_TOKEN"); + + const response = await this.httpService.axiosRef.get( + `https://api.crowdin.com/api/v2/projects/${projectId}/languages/progress?limit=100`, + { headers: { Authorization: `Bearer ${accessToken}` } }, + ); + const { data } = response.data as CrowdinResponse; + + return data.map(({ data }) => { + return { + id: data.language.id, + name: data.language.name, + progress: data.translationProgress, + editorCode: data.language.editorCode, + locale: data.language.locale, + } satisfies LanguageDto; + }); + } +} diff --git a/libs/dto/src/contributors/index.ts b/libs/dto/src/contributors/index.ts new file mode 100644 index 00000000..4e043938 --- /dev/null +++ b/libs/dto/src/contributors/index.ts @@ -0,0 +1,21 @@ +import { createZodDto } from "nestjs-zod/dto"; +import { z } from "nestjs-zod/z"; + +export const contributorSchema = z.object({ + id: z.number(), + name: z.string(), + url: z.string(), + avatar: z.string(), +}); + +export class ContributorDto extends createZodDto(contributorSchema) {} + +export const languageSchema = z.object({ + id: z.string(), + name: z.string(), + locale: z.string(), + editorCode: z.string(), + progress: z.number(), +}); + +export class LanguageDto extends createZodDto(languageSchema) {} diff --git a/libs/dto/src/index.ts b/libs/dto/src/index.ts index fc0fb09c..3eb2d194 100644 --- a/libs/dto/src/index.ts +++ b/libs/dto/src/index.ts @@ -1,4 +1,5 @@ export * from "./auth"; +export * from "./contributors"; export * from "./resume"; export * from "./statistics"; export * from "./user"; diff --git a/libs/schema/src/metadata/index.ts b/libs/schema/src/metadata/index.ts index 844b57a5..9de9a5e9 100644 --- a/libs/schema/src/metadata/index.ts +++ b/libs/schema/src/metadata/index.ts @@ -9,7 +9,6 @@ export const defaultLayout = [ // Schema export const metadataSchema = z.object({ - locale: z.string().default("en-US"), template: z.string().default("rhyhorn"), layout: z.array(z.array(z.array(z.string()))).default(defaultLayout), // pages -> columns -> sections css: z.object({ @@ -37,6 +36,7 @@ export const metadataSchema = z.object({ size: z.number().default(14), }), lineHeight: z.number().default(1.5), + hideIcons: z.boolean().default(false), underlineLinks: z.boolean().default(true), }), notes: z.string().default(""), @@ -47,7 +47,6 @@ export type Metadata = z.infer; // Defaults export const defaultMetadata: Metadata = { - locale: "en-US", template: "rhyhorn", layout: defaultLayout, css: { @@ -75,6 +74,7 @@ export const defaultMetadata: Metadata = { size: 14, }, lineHeight: 1.5, + hideIcons: false, underlineLinks: true, }, notes: "", diff --git a/libs/schema/src/sample.ts b/libs/schema/src/sample.ts index c305db67..3072168e 100644 --- a/libs/schema/src/sample.ts +++ b/libs/schema/src/sample.ts @@ -452,7 +452,6 @@ export const sampleResume: ResumeData = { }, }, metadata: { - locale: "en-US", template: "pikachu", layout: [ [ @@ -490,6 +489,7 @@ export const sampleResume: ResumeData = { size: 13, }, lineHeight: 2, + hideIcons: false, underlineLinks: true, }, notes: diff --git a/libs/ui/src/components/combobox.tsx b/libs/ui/src/components/combobox.tsx index 299b6b33..99da8e2e 100644 --- a/libs/ui/src/components/combobox.tsx +++ b/libs/ui/src/components/combobox.tsx @@ -36,7 +36,7 @@ type ComboboxPropsMultiple = { export type ComboboxProps = ComboboxPropsSingle | ComboboxPropsMultiple; -export const handleSingleSelect = (props: ComboboxPropsSingle, option: ComboboxOption) => { +const handleSingleSelect = (props: ComboboxPropsSingle, option: ComboboxOption) => { if (props.clearable) { props.onValueChange?.(option.value === props.value ? "" : option.value); } else { @@ -44,7 +44,7 @@ export const handleSingleSelect = (props: ComboboxPropsSingle, option: ComboboxO } }; -export const handleMultipleSelect = (props: ComboboxPropsMultiple, option: ComboboxOption) => { +const handleMultipleSelect = (props: ComboboxPropsMultiple, option: ComboboxOption) => { if (props.value?.includes(option.value)) { if (!props.clearable && props.value.length === 1) return false; props.onValueChange?.(props.value.filter((value) => value !== option.value)); diff --git a/lingui.config.ts b/lingui.config.ts index f65e8669..4a221d42 100644 --- a/lingui.config.ts +++ b/lingui.config.ts @@ -4,11 +4,60 @@ const config: LinguiConfig = { format: "po", sourceLocale: "en-US", pseudoLocale: "zu-ZA", - locales: ["en-US", "de-DE", "zu-ZA"], + fallbackLocales: { default: "en-US" }, + locales: [ + "af-ZA", + "am-ET", + "ar-SA", + "bg-BG", + "bn-BD", + "ca-ES", + "cs-CZ", + "da-DK", + "de-DE", + "el-GR", + "en-US", + "es-ES", + "fa-IR", + "fi-FI", + "fr-FR", + "he-IL", + "hi-IN", + "hu-HU", + "id-ID", + "it-IT", + "ja-JP", + "km-KH", + "kn-IN", + "ko-KR", + "lt-LT", + "ml-IN", + "mr-IN", + "ne-NP", + "nl-NL", + "no-NO", + "or-IN", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + "ru-RU", + "sr-SP", + "sv-SE", + "ta-IN", + "te-IN", + "th-TH", + "tr-TR", + "uk-UA", + "vi-VN", + "zh-CN", + "zh-TW", + "zu-ZA", + ], catalogs: [ { - path: "/apps/client/src/locales/{locale}/messages", include: ["/apps/client/src"], + path: "/apps/client/src/locales/{locale}/messages", }, ], }; diff --git a/package.json b/package.json index 50372ffa..24b6771c 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,12 @@ "lint": "nx run-many -t lint --fix", "format": "pnpm dlx prettier -w .", "prepare": "pnpm dlx husky install", - "messages:extract": "pnpm exec lingui extract --clean --overwrite" + "messages:extract": "pnpm exec lingui extract --clean --overwrite --locale en-US" }, "devDependencies": { "@babel/core": "^7.23.3", "@babel/preset-react": "^7.23.3", - "@commitlint/cli": "^18.4.0", + "@commitlint/cli": "^18.4.1", "@commitlint/config-conventional": "^18.4.0", "@lingui/cli": "^4.5.0", "@lingui/conf": "^4.5.0", @@ -52,9 +52,9 @@ "@swc/cli": "~0.1.62", "@swc/core": "~1.3.96", "@tailwindcss/container-queries": "^0.1.1", - "@tailwindcss/forms": "^0.5.6", + "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", - "@tanstack/eslint-plugin-query": "^5.6.0", + "@tanstack/eslint-plugin-query": "^5.8.3", "@testing-library/react": "14.1.0", "@types/async-retry": "^1.4.8", "@types/bcryptjs": "^2.4.6", @@ -107,7 +107,7 @@ "postcss": "8.4.31", "postcss-import": "^15.1.0", "postcss-nested": "^6.0.1", - "prettier": "^3.0.3", + "prettier": "^3.1.0", "prisma": "^5.5.2", "tailwindcss": "^3.3.5", "tailwindcss-animate": "^1.0.7", @@ -171,7 +171,7 @@ "@songkeys/nestjs-redis": "^10.0.0", "@songkeys/nestjs-redis-health": "^10.0.0", "@swc/helpers": "~0.5.3", - "@tanstack/react-query": "^5.8.1", + "@tanstack/react-query": "^5.8.3", "@tiptap/extension-highlight": "^2.1.12", "@tiptap/extension-image": "^2.1.12", "@tiptap/extension-link": "^2.1.12", @@ -206,7 +206,7 @@ "nestjs-prisma": "^0.22.0", "nestjs-zod": "^3.0.0", "nodemailer": "^6.9.7", - "openai": "^4.17.3", + "openai": "^4.17.4", "otplib": "^12.0.1", "papaparse": "^5.4.1", "passport": "^0.6.0", @@ -222,8 +222,8 @@ "react-dom": "18.2.0", "react-helmet-async": "^1.3.0", "react-hook-form": "^7.48.2", - "react-parallax-tilt": "^1.7.173", - "react-resizable-panels": "^0.0.55", + "react-parallax-tilt": "^1.7.174", + "react-resizable-panels": "^0.0.54", "react-router-dom": "6.18.0", "react-zoom-pan-pinch": "^3.3.0", "reflect-metadata": "^0.1.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bb2e319..6e7822f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,8 +162,8 @@ dependencies: specifier: ~0.5.3 version: 0.5.3 '@tanstack/react-query': - specifier: ^5.8.1 - version: 5.8.1(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.8.3 + version: 5.8.3(react-dom@18.2.0)(react@18.2.0) '@tiptap/extension-highlight': specifier: ^2.1.12 version: 2.1.12(@tiptap/core@2.1.12) @@ -267,8 +267,8 @@ dependencies: specifier: ^6.9.7 version: 6.9.7 openai: - specifier: ^4.17.3 - version: 4.17.3 + specifier: ^4.17.4 + version: 4.17.4 otplib: specifier: ^12.0.1 version: 12.0.1 @@ -315,11 +315,11 @@ dependencies: specifier: ^7.48.2 version: 7.48.2(react@18.2.0) react-parallax-tilt: - specifier: ^1.7.173 - version: 1.7.173(react-dom@18.2.0)(react@18.2.0) + specifier: ^1.7.174 + version: 1.7.174(react-dom@18.2.0)(react@18.2.0) react-resizable-panels: - specifier: ^0.0.55 - version: 0.0.55(react-dom@18.2.0)(react@18.2.0) + specifier: ^0.0.54 + version: 0.0.54(react-dom@18.2.0)(react@18.2.0) react-router-dom: specifier: 6.18.0 version: 6.18.0(react-dom@18.2.0)(react@18.2.0) @@ -377,8 +377,8 @@ devDependencies: specifier: ^7.23.3 version: 7.23.3(@babel/core@7.23.3) '@commitlint/cli': - specifier: ^18.4.0 - version: 18.4.0(typescript@5.2.2) + specifier: ^18.4.1 + version: 18.4.1(typescript@5.2.2) '@commitlint/config-conventional': specifier: ^18.4.0 version: 18.4.0 @@ -449,14 +449,14 @@ devDependencies: specifier: ^0.1.1 version: 0.1.1(tailwindcss@3.3.5) '@tailwindcss/forms': - specifier: ^0.5.6 - version: 0.5.6(tailwindcss@3.3.5) + specifier: ^0.5.7 + version: 0.5.7(tailwindcss@3.3.5) '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.3.5) '@tanstack/eslint-plugin-query': - specifier: ^5.6.0 - version: 5.6.0(eslint@8.53.0)(typescript@5.2.2) + specifier: ^5.8.3 + version: 5.8.3(eslint@8.53.0)(typescript@5.2.2) '@testing-library/react': specifier: 14.1.0 version: 14.1.0(react-dom@18.2.0)(react@18.2.0) @@ -573,7 +573,7 @@ devDependencies: version: 0.2.0(eslint@8.53.0)(typescript@5.2.2) eslint-plugin-prettier: specifier: ^5.0.1 - version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3) + version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.1.0) eslint-plugin-react: specifier: 7.33.2 version: 7.33.2(eslint@8.53.0) @@ -614,8 +614,8 @@ devDependencies: specifier: ^6.0.1 version: 6.0.1(postcss@8.4.31) prettier: - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.0 + version: 3.1.0 prisma: specifier: ^5.5.2 version: 5.5.2 @@ -2129,14 +2129,14 @@ packages: dev: true optional: true - /@commitlint/cli@18.4.0(typescript@5.2.2): - resolution: {integrity: sha512-iz3KJtmsRRFm6OlyrORxRR6qcrznuoFDzcvMXTMpl6E/wj9Vr2crolDk6cG3bFpJCjWd9C7KidXerRha6hh1kQ==} + /@commitlint/cli@18.4.1(typescript@5.2.2): + resolution: {integrity: sha512-4+jljfd29Udw9RDDyigavLO9LvdbmB8O9xjDzVZ0R3lJuG7nCeyHgnKWIVpFaN590isZMV/cMeQK0gH7hRF40A==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.4.0 '@commitlint/lint': 18.4.0 - '@commitlint/load': 18.4.0(typescript@5.2.2) + '@commitlint/load': 18.4.1(typescript@5.2.2) '@commitlint/read': 18.4.0 '@commitlint/types': 18.4.0 execa: 5.1.1 @@ -2206,8 +2206,8 @@ packages: '@commitlint/types': 18.4.0 dev: true - /@commitlint/load@18.4.0(typescript@5.2.2): - resolution: {integrity: sha512-7unGl1HGRNMgWrUPmj8OFkJyuNUMb6xA1i53/OAFKd9l+U3C4WTfoJe3t/TUz8vKZLCaDcWWR/b2cw5HveBBFg==} + /@commitlint/load@18.4.1(typescript@5.2.2): + resolution: {integrity: sha512-o/plBiPJQgbSq/4ipDpsq4HCmURjBAEjr1EO/p2falr3VhwV0WGXTvb8NlihgI8xtSyO6lHvtycrE535GMLQbA==} engines: {node: '>=v18'} dependencies: '@commitlint/config-validator': 18.4.0 @@ -6590,8 +6590,8 @@ packages: tailwindcss: 3.3.5(ts-node@10.9.1) dev: true - /@tailwindcss/forms@0.5.6(tailwindcss@3.3.5): - resolution: {integrity: sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==} + /@tailwindcss/forms@0.5.7(tailwindcss@3.3.5): + resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' dependencies: @@ -6611,8 +6611,8 @@ packages: tailwindcss: 3.3.5(ts-node@10.9.1) dev: true - /@tanstack/eslint-plugin-query@5.6.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-A0D8fXIh6fuHcT7e+VaL+QnlLhY9V5QmiaLOTLOIcyVKCpWVZLSHrLP6ghZV6CB+JLalHWCAUF0QW0UaEyyz7g==} + /@tanstack/eslint-plugin-query@5.8.3(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-xq6TqtyTfXXAYeWPRir1Phvt30m2sXk0nJ6m30ejA6WrRW0XpCVnJRMUFhCxUxjLJAhGxJHZmeJ3vkzaXrzHIA==} peerDependencies: eslint: ^8.0.0 dependencies: @@ -6623,12 +6623,12 @@ packages: - typescript dev: true - /@tanstack/query-core@5.8.1: - resolution: {integrity: sha512-Y0enatz2zQXBAsd7XmajlCs+WaitdR7dIFkqz9Xd7HL4KV04JOigWVreYseTmNH7YFSBSC/BJ9uuNp1MAf+GfA==} + /@tanstack/query-core@5.8.3: + resolution: {integrity: sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==} dev: false - /@tanstack/react-query@5.8.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-YMagxS8iNPOLg0pK6WOjdSDlAvWKOf69udLOwQrBVmkC2SRLNLko7elo5Ro3ptlJkXvTVHidxC/h5KGi5bH1XQ==} + /@tanstack/react-query@5.8.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-EDRrsMgUtKM+SjVmhDYBd4jwWWyHAw3kCaurKLIO90OfPQr/UhpwcqM2l/eQOaUYon9lfDB2ejQi1edHK7zEdA==} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 @@ -6639,7 +6639,7 @@ packages: react-native: optional: true dependencies: - '@tanstack/query-core': 5.8.1 + '@tanstack/query-core': 5.8.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -9994,7 +9994,7 @@ packages: longest: 2.0.1 word-wrap: 1.2.5 optionalDependencies: - '@commitlint/load': 18.4.0(typescript@5.2.2) + '@commitlint/load': 18.4.1(typescript@5.2.2) transitivePeerDependencies: - typescript dev: true @@ -10972,7 +10972,7 @@ packages: - typescript dev: true - /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3): + /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.1.0): resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -10988,7 +10988,7 @@ packages: dependencies: eslint: 8.53.0 eslint-config-prettier: 9.0.0(eslint@8.53.0) - prettier: 3.0.3 + prettier: 3.1.0 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 dev: true @@ -14260,6 +14260,7 @@ packages: /lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + requiresBuild: true dev: true /lodash.once@4.1.1: @@ -15648,8 +15649,8 @@ packages: is-wsl: 2.2.0 dev: true - /openai@4.17.3: - resolution: {integrity: sha512-Gx9wzl9HWX5pjagkgXVu6U2BTFEPkQFdkppNnAX2n2Rpjtn2zt152wXh7NnZ5eJuVxUGYzRe66JmayAEGjzqAg==} + /openai@4.17.4: + resolution: {integrity: sha512-ThRFkl6snLbcAKS58St7N3CaKuI5WdYUvIjPvf4s+8SdymgNtOfzmZcZXVcCefx04oKFnvZJvIcTh3eAFUUhAQ==} hasBin: true dependencies: '@types/node': 18.18.8 @@ -16661,8 +16662,8 @@ packages: fast-diff: 1.3.0 dev: true - /prettier@3.0.3: - resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} + /prettier@3.1.0: + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} engines: {node: '>=14'} hasBin: true dev: true @@ -17275,8 +17276,8 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - /react-parallax-tilt@1.7.173(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-uZ+s1/Y8TYnyQGn1qKRTzv/GySBySkh43lWXNO1ArQR3a6/OBamtKqZ3i5fxAph4IkQMgt4gOlK8EVkhLn8q8w==} + /react-parallax-tilt@1.7.174(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-NFmSZ1O+CJ+K+vDqhnNNSREzhJez3sXYX4b12tl00kxvN7jmdngZwCgd5bOtqgyk3OcGAAM43P+pkvxBXrzbSg==} peerDependencies: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 @@ -17344,8 +17345,8 @@ packages: use-sidecar: 1.1.2(@types/react@18.2.37)(react@18.2.0) dev: false - /react-resizable-panels@0.0.55(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==} + /react-resizable-panels@0.0.54(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-f8hHdQrqvXoiZGdRNuoOi/C2cdYT2nEpaOb1KIWVWorSTPZmnE+ZQiamGeu+AMx3iZ/tqBtlAkBOpKXzTnDCoA==} peerDependencies: react: ^16.14.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 diff --git a/tsconfig.base.json b/tsconfig.base.json index 5f222be9..639299d6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -15,15 +15,15 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { + "@/artboard/*": ["apps/artboard/src/*"], "@/client/*": ["apps/client/src/*"], "@/server/*": ["apps/server/src/*"], - "@/artboard/*": ["apps/artboard/src/*"], - "@reactive-resume/ui": ["libs/ui/src/index.ts"], "@reactive-resume/dto": ["libs/dto/src/index.ts"], - "@reactive-resume/utils": ["libs/utils/src/index.ts"], "@reactive-resume/hooks": ["libs/hooks/src/index.ts"], "@reactive-resume/parser": ["libs/parser/src/index.ts"], - "@reactive-resume/schema": ["libs/schema/src/index.ts"] + "@reactive-resume/schema": ["libs/schema/src/index.ts"], + "@reactive-resume/ui": ["libs/ui/src/index.ts"], + "@reactive-resume/utils": ["libs/utils/src/index.ts"] } }, "exclude": ["node_modules", "dist", "tmp", ".nx"]