feat(i18n): implement localization using LinguiJS

This commit is contained in:
Amruth Pillai
2023-11-10 09:07:47 +01:00
parent 13d91411e3
commit 6ad4358d70
108 changed files with 4631 additions and 798 deletions

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { awardSchema, defaultAward } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const AwardsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Title</FormLabel>
<FormLabel>{t({ message: "Title", context: "Name of the Award" })}</FormLabel>
<FormControl>
<Input {...field} placeholder="3rd Runner Up" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,9 @@ export const AwardsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Awarder</FormLabel>
<FormLabel>{t`Awarder`}</FormLabel>
<FormControl>
<Input {...field} placeholder="TechCrunch Disrupt SF" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +64,15 @@ export const AwardsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Aug 2019" />
<Input
{...field}
placeholder={t({
message: "March 2023",
comment: "The month and year should be uniform across all languages.",
})}
/>
</FormControl>
<FormMessage />
</FormItem>
@ -77,9 +84,9 @@ export const AwardsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://techcrunch.com/events/disrupt-sf-2019" />
<URLInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -91,7 +98,7 @@ export const AwardsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { certificationSchema, defaultCertification } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const CertificationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t({ message: "Name", context: "Name of the Certification" })}</FormLabel>
<FormControl>
<Input {...field} placeholder="Web Developer Bootcamp" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,9 @@ export const CertificationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Issuer</FormLabel>
<FormLabel>{t`Issuer`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Udemy" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +64,9 @@ export const CertificationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Aug 2019" />
<Input {...field} placeholder={t`March 2023`} />
</FormControl>
<FormMessage />
</FormItem>
@ -77,7 +78,7 @@ export const CertificationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://udemy.com/certificate/UC-..." />
</FormControl>
@ -91,7 +92,7 @@ export const CertificationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { X } from "@phosphor-icons/react";
import { CustomSection, customSectionSchema, defaultCustomSection } from "@reactive-resume/schema";
import {
@ -49,7 +50,7 @@ export const CustomSectionDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@ -63,7 +64,7 @@ export const CustomSectionDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Description</FormLabel>
<FormLabel>{t`Description`}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@ -77,7 +78,7 @@ export const CustomSectionDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@ -91,7 +92,7 @@ export const CustomSectionDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Location</FormLabel>
<FormLabel>{t`Location`}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@ -105,7 +106,7 @@ export const CustomSectionDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} />
</FormControl>
@ -119,7 +120,7 @@ export const CustomSectionDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}
@ -141,12 +142,12 @@ export const CustomSectionDialog = () => {
render={({ field }) => (
<div className="col-span-2 space-y-3">
<FormItem>
<FormLabel>Keywords</FormLabel>
<FormLabel>{t`Keywords`}</FormLabel>
<FormControl>
<BadgeInput {...field} />
</FormControl>
<FormDescription>
You can add multiple keywords by separating them with a comma.
{t`You can add multiple keywords by separating them with a comma or pressing enter.`}
</FormDescription>
<FormMessage />
</FormItem>

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { defaultEducation, educationSchema } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Institution</FormLabel>
<FormLabel>{t`Institution`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Carnegie Mellon University" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,14 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Type of Study</FormLabel>
<FormLabel>
{t({
message: "Type of Study",
comment: "For example, Bachelor's Degree or Master's Degree",
})}
</FormLabel>
<FormControl>
<Input {...field} placeholder="Bachelor's Degree" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +69,14 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Area of Study</FormLabel>
<FormLabel>
{t({
message: "Area of Study",
comment: "For example, Computer Science or Business Administration",
})}
</FormLabel>
<FormControl>
<Input {...field} placeholder="Computer Science" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -77,7 +88,12 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Score</FormLabel>
<FormLabel>
{t({
message: "Score",
comment: "Score or honors for the degree, for example, CGPA or magna cum laude",
})}
</FormLabel>
<FormControl>
<Input {...field} placeholder="9.2 GPA" />
</FormControl>
@ -91,9 +107,9 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Aug 2006 - Oct 2012" />
<Input {...field} placeholder={t`March 2023 - Present`} />
</FormControl>
<FormMessage />
</FormItem>
@ -105,9 +121,9 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://www.cmu.edu/" />
<URLInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -119,7 +135,7 @@ export const EducationDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { defaultExperience, experienceSchema } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const ExperienceDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Company</FormLabel>
<FormLabel>{t`Company`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Alphabet Inc." />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,14 @@ export const ExperienceDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Position</FormLabel>
<FormLabel>
{t({
message: "Position",
context: "Position held at a company, for example, Software Engineer",
})}
</FormLabel>
<FormControl>
<Input {...field} placeholder="Chief Executive Officer" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +69,9 @@ export const ExperienceDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Dec 2019 - Present" />
<Input {...field} placeholder={t`March 2023 - Present`} />
</FormControl>
<FormMessage />
</FormItem>
@ -77,9 +83,9 @@ export const ExperienceDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Location</FormLabel>
<FormLabel>{t`Location`}</FormLabel>
<FormControl>
<Input {...field} placeholder="New York, NY" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -91,9 +97,9 @@ export const ExperienceDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://www.abc.xyz/" />
<URLInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -105,7 +111,7 @@ export const ExperienceDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { X } from "@phosphor-icons/react";
import { defaultInterest, interestSchema } from "@reactive-resume/schema";
import {
@ -36,9 +37,9 @@ export const InterestsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Video Games" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -51,12 +52,12 @@ export const InterestsDialog = () => {
render={({ field }) => (
<div className="col-span-2 space-y-3">
<FormItem>
<FormLabel>Keywords</FormLabel>
<FormLabel>{t`Keywords`}</FormLabel>
<FormControl>
<BadgeInput {...field} placeholder="FIFA 23, Call of Duty, etc." />
<BadgeInput {...field} />
</FormControl>
<FormDescription>
You can add multiple keywords by separating them with a comma.
{t`You can add multiple keywords by separating them with a comma or pressing enter.`}
</FormDescription>
<FormMessage />
</FormItem>

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { defaultLanguage, languageSchema } from "@reactive-resume/schema";
import {
FormControl,
@ -33,9 +34,9 @@ export const LanguagesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} placeholder="German" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -47,9 +48,9 @@ export const LanguagesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Fluency</FormLabel>
<FormLabel>{t`Fluency`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Native Speaker" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -61,7 +62,7 @@ export const LanguagesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Fluency (CEFR)</FormLabel>
<FormLabel>{t`Fluency (CEFR)`}</FormLabel>
<FormControl className="py-2">
<div className="flex items-center gap-x-4">
<Slider

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t, Trans } from "@lingui/macro";
import { defaultProfile, profileSchema } from "@reactive-resume/schema";
import {
Avatar,
@ -35,8 +36,9 @@ export const ProfilesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Network</FormLabel>
<FormLabel>{t`Network`}</FormLabel>
<FormControl>
{/* eslint-disable-next-line lingui/no-unlocalized-strings */}
<Input {...field} placeholder="LinkedIn" />
</FormControl>
<FormMessage />
@ -49,9 +51,9 @@ export const ProfilesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Username</FormLabel>
<FormLabel>{t`Username`}</FormLabel>
<FormControl>
<Input {...field} placeholder="johndoe" />
<Input {...field} placeholder="john.doe" />
</FormControl>
<FormMessage />
</FormItem>
@ -63,7 +65,7 @@ export const ProfilesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>URL</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://linkedin.com/in/johndoe" />
</FormControl>
@ -77,7 +79,7 @@ export const ProfilesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel htmlFor="iconSlug">Icon</FormLabel>
<FormLabel htmlFor="iconSlug">{t`Icon`}</FormLabel>
<FormControl>
<div className="flex items-center gap-x-2">
<Avatar className="h-8 w-8 bg-white">
@ -93,15 +95,17 @@ export const ProfilesDialog = () => {
</FormControl>
<FormMessage />
<FormDescription className="ml-10">
Powered by{" "}
<a
href="https://simpleicons.org/"
target="_blank"
rel="noopener noreferrer nofollow"
className="font-medium"
>
Simple Icons
</a>
<Trans>
Powered by{" "}
<a
href="https://simpleicons.org/"
target="_blank"
rel="noopener noreferrer nofollow"
className="font-medium"
>
Simple Icons
</a>
</Trans>
</FormDescription>
</FormItem>
)}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { X } from "@phosphor-icons/react";
import { defaultProject, projectSchema } from "@reactive-resume/schema";
import {
@ -40,9 +41,9 @@ export const ProjectsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Reactive Resume" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -54,9 +55,9 @@ export const ProjectsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Description</FormLabel>
<FormLabel>{t`Description`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Open Source Resume Builder" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -68,9 +69,9 @@ export const ProjectsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Sep 2018 - Present" />
<Input {...field} placeholder={t`March 2023 - Present`} />
</FormControl>
<FormMessage />
</FormItem>
@ -82,7 +83,7 @@ export const ProjectsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://rxresu.me" />
</FormControl>
@ -96,7 +97,7 @@ export const ProjectsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}
@ -118,12 +119,12 @@ export const ProjectsDialog = () => {
render={({ field }) => (
<div className="col-span-2 space-y-3">
<FormItem>
<FormLabel>Keywords</FormLabel>
<FormLabel>{t`Keywords`}</FormLabel>
<FormControl>
<BadgeInput {...field} placeholder="FIFA 23, Call of Duty, etc." />
<BadgeInput {...field} />
</FormControl>
<FormDescription>
You can add multiple keywords by separating them with a comma.
{t`You can add multiple keywords by separating them with a comma or pressing enter.`}
</FormDescription>
<FormMessage />
</FormItem>

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { defaultPublication, publicationSchema } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const PublicationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} placeholder="The Great Gatsby" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,9 @@ export const PublicationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Publisher</FormLabel>
<FormLabel>{t`Publisher`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Charles Scribner's Sons" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +64,9 @@ export const PublicationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Release Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="April 10, 1925" />
<Input {...field} placeholder={t`March 2023`} />
</FormControl>
<FormMessage />
</FormItem>
@ -77,9 +78,9 @@ export const PublicationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://books.google.com/..." />
<URLInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -91,7 +92,7 @@ export const PublicationsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { defaultReference, referenceSchema } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const ReferencesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Cosmo Kramer" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,9 @@ export const ReferencesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Description</FormLabel>
<FormLabel>{t`Description`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Neighbour" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +64,9 @@ export const ReferencesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://linkedin.com/in/cosmo.kramer" />
<URLInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -77,7 +78,7 @@ export const ReferencesDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { X } from "@phosphor-icons/react";
import { defaultSkill, skillSchema } from "@reactive-resume/schema";
import {
@ -37,9 +38,9 @@ export const SkillsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Name</FormLabel>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Content Management" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -51,9 +52,9 @@ export const SkillsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Description</FormLabel>
<FormLabel>{t`Description`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Advanced" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -65,7 +66,7 @@ export const SkillsDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Level</FormLabel>
<FormLabel>{t`Level`}</FormLabel>
<FormControl className="py-2">
<div className="flex items-center gap-x-4">
<Slider
@ -91,12 +92,12 @@ export const SkillsDialog = () => {
render={({ field }) => (
<div className="col-span-2 space-y-3">
<FormItem>
<FormLabel>Keywords</FormLabel>
<FormLabel>{t`Keywords`}</FormLabel>
<FormControl>
<BadgeInput {...field} placeholder="WordPress, Joomla, Webflow etc." />
<BadgeInput {...field} />
</FormControl>
<FormDescription>
You can add multiple keywords by separating them with a comma.
{t`You can add multiple keywords by separating them with a comma or pressing enter.`}
</FormDescription>
<FormMessage />
</FormItem>

View File

@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/macro";
import { defaultVolunteer, volunteerSchema } from "@reactive-resume/schema";
import {
FormControl,
@ -35,9 +36,9 @@ export const VolunteerDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Organization</FormLabel>
<FormLabel>{t`Organization`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Amnesty International" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -49,9 +50,9 @@ export const VolunteerDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Position</FormLabel>
<FormLabel>{t`Position`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Recruiter" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -63,9 +64,9 @@ export const VolunteerDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Date</FormLabel>
<FormLabel>{t`Date`}</FormLabel>
<FormControl>
<Input {...field} placeholder="Dec 2016 - Aug 2017" />
<Input {...field} placeholder={t`March 2023 - Present`} />
</FormControl>
<FormMessage />
</FormItem>
@ -77,9 +78,9 @@ export const VolunteerDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Location</FormLabel>
<FormLabel>{t`Location`}</FormLabel>
<FormControl>
<Input {...field} placeholder="New York, NY" />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -91,9 +92,9 @@ export const VolunteerDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Website</FormLabel>
<FormLabel>{t`Website`}</FormLabel>
<FormControl>
<URLInput {...field} placeholder="https://www.amnesty.org/" />
<URLInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -105,7 +106,7 @@ export const VolunteerDialog = () => {
control={form.control}
render={({ field }) => (
<FormItem className="col-span-1 sm:col-span-2">
<FormLabel>Summary</FormLabel>
<FormLabel>{t`Summary`}</FormLabel>
<FormControl>
<RichInput
{...field}

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { Plus, PlusCircle } from "@phosphor-icons/react";
import {
Award,
@ -50,7 +51,15 @@ export const LeftSidebar = () => {
</Button>
<div className="flex flex-col items-center justify-center gap-y-2">
<SectionIcon id="basics" name="Basics" onClick={() => scrollIntoView("#basics")} />
<SectionIcon
id="basics"
onClick={() => scrollIntoView("#basics")}
name={t({
message: "Basics",
context:
"The Basics section of a Resume consists of User's Picture, Full Name, Location etc.",
})}
/>
<SectionIcon id="summary" onClick={() => scrollIntoView("#summary")} />
<SectionIcon id="profiles" onClick={() => scrollIntoView("#profiles")} />
<SectionIcon id="experience" onClick={() => scrollIntoView("#experience")} />
@ -68,10 +77,11 @@ export const LeftSidebar = () => {
<SectionIcon
id="custom"
variant="outline"
name="Add a new section"
name={t`Add a new section`}
icon={<Plus size={14} />}
onClick={() => {
addSection();
// eslint-disable-next-line lingui/no-unlocalized-strings
scrollIntoView("& > section:last-of-type");
}}
/>
@ -184,7 +194,7 @@ export const LeftSidebar = () => {
<Button size="lg" variant="outline" onClick={addSection}>
<PlusCircle />
<span className="ml-2">Add a new section</span>
<span className="ml-2">{t`Add a new section`}</span>
</Button>
</div>
</ScrollArea>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { basicsSchema } from "@reactive-resume/schema";
import { Input, Label } from "@reactive-resume/ui";
@ -17,7 +18,7 @@ export const BasicsSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("basics")}
<h2 className="line-clamp-1 text-3xl font-bold">Basics</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Basics`}</h2>
</div>
</header>
@ -27,10 +28,9 @@ export const BasicsSection = () => {
</div>
<div className="space-y-1.5 sm:col-span-2">
<Label htmlFor="basics.name">Full Name</Label>
<Label htmlFor="basics.name">{t`Full Name`}</Label>
<Input
id="basics.name"
placeholder="John Doe"
value={basics.name}
hasError={!basicsSchema.pick({ name: true }).safeParse({ name: basics.name }).success}
onChange={(event) => setValue("basics.name", event.target.value)}
@ -38,17 +38,16 @@ export const BasicsSection = () => {
</div>
<div className="space-y-1.5 sm:col-span-2">
<Label htmlFor="basics.headline">Headline</Label>
<Label htmlFor="basics.headline">{t`Headline`}</Label>
<Input
id="basics.headline"
placeholder="Highly Creative Frontend Web Developer"
value={basics.headline}
onChange={(event) => setValue("basics.headline", event.target.value)}
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="basics.email">Email Address</Label>
<Label htmlFor="basics.email">{t`Email`}</Label>
<Input
id="basics.email"
placeholder="john.doe@example.com"
@ -61,7 +60,7 @@ export const BasicsSection = () => {
</div>
<div className="space-y-1.5">
<Label htmlFor="basics.url">Website</Label>
<Label htmlFor="basics.url">{t`Website`}</Label>
<URLInput
id="basics.url"
value={basics.url}
@ -71,7 +70,7 @@ export const BasicsSection = () => {
</div>
<div className="space-y-1.5">
<Label htmlFor="basics.phone">Phone Number</Label>
<Label htmlFor="basics.phone">{t`Phone`}</Label>
<Input
id="basics.phone"
placeholder="+1 (123) 4567 7890"
@ -81,10 +80,9 @@ export const BasicsSection = () => {
</div>
<div className="space-y-1.5">
<Label htmlFor="basics.location">Location</Label>
<Label htmlFor="basics.location">{t`Location`}</Label>
<Input
id="basics.location"
placeholder="105 Cedarhurst Ave, Cedarhurst, NY 11516"
value={basics.location}
onChange={(event) => setValue("basics.location", event.target.value)}
/>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { createId } from "@paralleldrive/cuid2";
import { DotsSixVertical, Plus, X } from "@phosphor-icons/react";
import { CustomField as ICustomField } from "@reactive-resume/schema";
@ -38,21 +39,22 @@ export const CustomField = ({ field, onChange, onRemove }: CustomFieldProps) =>
<DotsSixVertical />
</Button>
<Input
placeholder="Icon"
{/* <Input
placeholder={t`Icon`}
value={field.icon}
className="!ml-0"
onChange={(event) => handleChange("icon", event.target.value)}
/>
/> */}
<Input
placeholder="Name"
placeholder={t`Name`}
value={field.name}
className="!ml-0"
onChange={(event) => handleChange("name", event.target.value)}
/>
<Input
placeholder="Value"
placeholder={t`Value`}
value={field.value}
onChange={(event) => handleChange("value", event.target.value)}
/>
@ -126,7 +128,7 @@ export const CustomFieldsSection = ({ className }: Props) => {
<Button variant="link" onClick={onAddCustomField}>
<Plus className="mr-2" />
<span>Add a custom field</span>
<span>{t`Add a custom field`}</span>
</Button>
</div>
);

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import {
AspectRatio,
Checkbox,
@ -69,7 +70,7 @@ export const PictureOptions = () => {
<div className="flex flex-col gap-y-5">
<div className="grid grid-cols-3 items-center gap-x-6">
<Label htmlFor="picture.size" className="col-span-1">
Size (in px)
{t`Size (in px)`}
</Label>
<Input
type="number"
@ -85,7 +86,7 @@ export const PictureOptions = () => {
<div className="grid grid-cols-3 items-center gap-x-6">
<Label htmlFor="picture.aspectRatio" className="col-span-1">
Aspect Ratio
{t`Aspect Ratio`}
</Label>
<div className="col-span-2 flex items-center justify-between">
<ToggleGroup
@ -94,19 +95,19 @@ export const PictureOptions = () => {
onValueChange={onAspectRatioChange}
className="flex items-center justify-center"
>
<Tooltip content="Square">
<Tooltip content={t`Square`}>
<ToggleGroupItem value="square">
<div className="h-3 w-3 border border-foreground" />
</ToggleGroupItem>
</Tooltip>
<Tooltip content="Horizontal">
<Tooltip content={t`Horizontal`}>
<ToggleGroupItem value="horizontal">
<div className="h-2 w-3 border border-foreground" />
</ToggleGroupItem>
</Tooltip>
<Tooltip content="Portrait">
<Tooltip content={t`Portrait`}>
<ToggleGroupItem value="portrait">
<div className="h-3 w-2 border border-foreground" />
</ToggleGroupItem>
@ -130,7 +131,7 @@ export const PictureOptions = () => {
<div className="grid grid-cols-3 items-center gap-x-6">
<Label htmlFor="picture.borderRadius" className="col-span-1">
Border Radius
{t`Border Radius`}
</Label>
<div className="col-span-2 flex items-center justify-between">
<ToggleGroup
@ -139,19 +140,19 @@ export const PictureOptions = () => {
onValueChange={onBorderRadiusChange}
className="flex items-center justify-center"
>
<Tooltip content="Square">
<Tooltip content={t`Square`}>
<ToggleGroupItem value="square">
<div className="h-3 w-3 border border-foreground" />
</ToggleGroupItem>
</Tooltip>
<Tooltip content="Rounded">
<Tooltip content={t`Rounded`}>
<ToggleGroupItem value="rounded">
<div className="h-3 w-3 rounded-sm border border-foreground" />
</ToggleGroupItem>
</Tooltip>
<Tooltip content="Circle">
<Tooltip content={t`Circle`}>
<ToggleGroupItem value="circle">
<div className="h-3 w-3 rounded-full border border-foreground" />
</ToggleGroupItem>
@ -176,7 +177,7 @@ export const PictureOptions = () => {
<div>
<div className="grid grid-cols-3 items-start gap-x-6">
<div className="col-span-1">
<Label>Effects</Label>
<Label>{t`Effects`}</Label>
</div>
<div className="col-span-2 space-y-4">
<div className="flex items-center space-x-2">
@ -187,7 +188,7 @@ export const PictureOptions = () => {
setValue("basics.picture.effects.hidden", checked);
}}
/>
<Label htmlFor="picture.effects.hidden">Hidden</Label>
<Label htmlFor="picture.effects.hidden">{t`Hidden`}</Label>
</div>
<div className="flex items-center space-x-2">
@ -198,7 +199,7 @@ export const PictureOptions = () => {
setValue("basics.picture.effects.border", checked);
}}
/>
<Label htmlFor="picture.effects.border">Border</Label>
<Label htmlFor="picture.effects.border">{t`Border`}</Label>
</div>
<div className="flex items-center space-x-2">
@ -209,7 +210,7 @@ export const PictureOptions = () => {
setValue("basics.picture.effects.grayscale", checked);
}}
/>
<Label htmlFor="picture.effects.grayscale">Grayscale</Label>
<Label htmlFor="picture.effects.grayscale">{t`Grayscale`}</Label>
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { Aperture, UploadSimple } from "@phosphor-icons/react";
import {
Avatar,
@ -48,7 +49,7 @@ export const PictureSection = () => {
</Avatar>
<div className="flex w-full flex-col gap-y-1.5">
<Label htmlFor="basics.picture.url">Picture</Label>
<Label htmlFor="basics.picture.url">{t`Picture`}</Label>
<div className="flex items-center gap-x-2">
<Input
id="basics.picture.url"

View File

@ -14,6 +14,7 @@ import {
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { t } from "@lingui/macro";
import { Plus } from "@phosphor-icons/react";
import { SectionItem, SectionKey, SectionWithItem } from "@reactive-resume/schema";
import { Button } from "@reactive-resume/ui";
@ -103,7 +104,12 @@ export const SectionBase = <T extends SectionItem>({ id, title, description }: P
className="gap-x-2 border-dashed py-6 leading-relaxed hover:bg-secondary-accent"
>
<Plus size={14} />
<span className="font-medium">Add New {section.name}</span>
<span className="font-medium">
{t({
message: "Add New Item",
context: "For example, add a new work experience, or add a new profile.",
})}
</span>
</Button>
)}
@ -137,7 +143,12 @@ export const SectionBase = <T extends SectionItem>({ id, title, description }: P
<footer className="flex items-center justify-end">
<Button variant="outline" className="ml-auto gap-x-2" onClick={onCreate}>
<Plus />
<span>Add New {section.name}</span>
<span>
{t({
message: "Add New Item",
context: "For example, add a new work experience, or add a new profile.",
})}
</span>
</Button>
</footer>
)}

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { createId } from "@paralleldrive/cuid2";
import { CopySimple, PencilSimple, Plus } from "@phosphor-icons/react";
import { SectionItem, SectionWithItem } from "@reactive-resume/schema";
@ -20,7 +21,7 @@ import {
} from "@reactive-resume/ui";
import { produce } from "immer";
import get from "lodash.get";
import { useEffect, useMemo } from "react";
import { useEffect } from "react";
import { UseFormReturn } from "react-hook-form";
import { DialogName, useDialog } from "@/client/stores/dialog";
@ -40,12 +41,12 @@ export const SectionDialog = <T extends SectionItem>({
children,
}: Props<T>) => {
const { isOpen, mode, close, payload } = useDialog<T>(id);
const setValue = useResumeStore((state) => state.setValue);
const section = useResumeStore((state) => {
if (!id) return null;
return get(state.resume.data.sections, id);
}) as SectionWithItem<T> | null;
const name = useMemo(() => section?.name ?? "", [section?.name]);
const isCreate = mode === "create";
const isUpdate = mode === "update";
@ -111,18 +112,16 @@ export const SectionDialog = <T extends SectionItem>({
<Form {...form}>
<form>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure you want to delete this {name}?</AlertDialogTitle>
<AlertDialogTitle>{t`Are you sure you want to delete this item?`}</AlertDialogTitle>
<AlertDialogDescription>
This action can be reverted by clicking on the undo button in the floating
toolbar.
{t`This action can be reverted by clicking on the undo button in the floating toolbar.`}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel>{t`Cancel`}</AlertDialogCancel>
<AlertDialogAction variant="error" onClick={form.handleSubmit(onSubmit)}>
Delete
{t`Delete`}
</AlertDialogAction>
</AlertDialogFooter>
</form>
@ -144,9 +143,9 @@ export const SectionDialog = <T extends SectionItem>({
{isUpdate && <PencilSimple />}
{isDuplicate && <CopySimple />}
<h2>
{isCreate && `Create a new ${name}`}
{isUpdate && `Update an existing ${name}`}
{isDuplicate && `Duplicate an existing ${name}`}
{isCreate && t`Create a new item`}
{isUpdate && t`Update an existing item`}
{isDuplicate && t`Duplicate an existing item`}
</h2>
</div>
</DialogTitle>
@ -156,9 +155,9 @@ export const SectionDialog = <T extends SectionItem>({
<DialogFooter>
<Button type="submit">
{isCreate && "Create"}
{isUpdate && "Save Changes"}
{isDuplicate && "Duplicate"}
{isCreate && t`Create`}
{isUpdate && t`Save Changes`}
{isDuplicate && t`Duplicate`}
</Button>
</DialogFooter>
</form>

View File

@ -1,5 +1,6 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { t } from "@lingui/macro";
import { CopySimple, DotsSixVertical, PencilSimple, TrashSimple } from "@phosphor-icons/react";
import {
DropdownMenu,
@ -81,19 +82,19 @@ export const SectionListItem = ({
</DropdownMenuTrigger>
<DropdownMenuContent align="center" side="left" sideOffset={-16}>
<DropdownMenuCheckboxItem checked={visible} onCheckedChange={onToggleVisibility}>
<span className="-ml-0.5">Visible</span>
<span className="-ml-0.5">{t`Visible`}</span>
</DropdownMenuCheckboxItem>
<DropdownMenuItem onClick={onUpdate}>
<PencilSimple size={14} />
<span className="ml-2">Edit</span>
<span className="ml-2">{t`Edit`}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={onDuplicate}>
<CopySimple size={14} />
<span className="ml-2">Copy</span>
<span className="ml-2">{t`Copy`}</span>
</DropdownMenuItem>
<DropdownMenuItem className="text-error" onClick={onDelete}>
<TrashSimple size={14} />
<span className="ml-2">Remove</span>
<span className="ml-2">{t`Remove`}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@ -1,3 +1,4 @@
import { plural, t } from "@lingui/macro";
import {
ArrowCounterClockwise,
Broom,
@ -63,7 +64,7 @@ export const SectionOptions = ({ id }: Props) => {
<>
<DropdownMenuItem onClick={onCreate}>
<Plus />
<span className="ml-2">Add a new item</span>
<span className="ml-2">{t`Add a new item`}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
@ -73,12 +74,12 @@ export const SectionOptions = ({ id }: Props) => {
<DropdownMenuGroup>
<DropdownMenuItem onClick={toggleVisibility}>
{section.visible ? <Eye /> : <EyeSlash />}
<span className="ml-2">{section.visible ? "Hide" : "Show"}</span>
<span className="ml-2">{section.visible ? t`Hide` : t`Show`}</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<PencilSimple />
<span className="ml-2">Rename</span>
<span className="ml-2">{t`Rename`}</span>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<div className="relative col-span-2">
@ -103,15 +104,15 @@ export const SectionOptions = ({ id }: Props) => {
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Columns />
<span className="ml-2">Columns</span>
<span className="ml-2">{t`Columns`}</span>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup value={`${section.columns}`} onValueChange={onChangeColumns}>
<DropdownMenuRadioItem value="1">1 Column</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="2">2 Columns</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="3">3 Columns</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="4">4 Columns</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="5">5 Columns</DropdownMenuRadioItem>
{Array.from({ length: 5 }, (_, i) => i + 1).map((value) => (
<DropdownMenuRadioItem value={`${value}`}>
{plural(value, { one: "Column", other: "Columns" })}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
@ -119,12 +120,12 @@ export const SectionOptions = ({ id }: Props) => {
<DropdownMenuSeparator />
<DropdownMenuItem disabled={!hasItems} onClick={onResetItems}>
<Broom />
<span className="ml-2">Reset</span>
<span className="ml-2">{t`Reset`}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-error" disabled={!isCustomSection} onClick={onRemove}>
<TrashSimple />
<span className="ml-2">Remove</span>
<span className="ml-2">{t`Remove`}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { Tag } from "@phosphor-icons/react";
import { URL, urlSchema } from "@reactive-resume/schema";
import { Button, Input, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui";
@ -36,14 +37,14 @@ export const URLInput = forwardRef<HTMLInputElement, Props>(
<PopoverContent className="p-1.5">
<Input
value={value.label}
placeholder="Label"
placeholder={t`Label`}
onChange={(event) => onChange({ ...value, label: event.target.value })}
/>
</PopoverContent>
</Popover>
</div>
{hasError && <small className="opacity-75">URL must start with https://</small>}
{hasError && <small className="opacity-75">{t`URL must start with https://`}</small>}
</>
);
},

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { ScrollArea, Separator } from "@reactive-resume/ui";
import { useRef } from "react";
@ -56,26 +57,30 @@ export const RightSidebar = () => {
<div />
<div className="flex flex-col items-center justify-center gap-y-2">
<SectionIcon id="template" name="Template" onClick={() => scrollIntoView("#template")} />
<SectionIcon id="layout" name="Layout" onClick={() => scrollIntoView("#layout")} />
<SectionIcon
id="template"
name={t`Template`}
onClick={() => scrollIntoView("#template")}
/>
<SectionIcon id="layout" name={t`Layout`} onClick={() => scrollIntoView("#layout")} />
<SectionIcon
id="typography"
name="Typography"
name={t`Typography`}
onClick={() => scrollIntoView("#typography")}
/>
<SectionIcon id="theme" name="Theme" onClick={() => scrollIntoView("#theme")} />
<SectionIcon id="page" name="Page" onClick={() => scrollIntoView("#page")} />
<SectionIcon id="sharing" name="Sharing" onClick={() => scrollIntoView("#sharing")} />
<SectionIcon id="theme" name={t`Theme`} onClick={() => scrollIntoView("#theme")} />
<SectionIcon id="page" name={t`Page`} onClick={() => scrollIntoView("#page")} />
<SectionIcon id="sharing" name={t`Sharing`} onClick={() => scrollIntoView("#sharing")} />
<SectionIcon
id="statistics"
name="Statistics"
name={t`Statistics`}
onClick={() => scrollIntoView("#statistics")}
/>
<SectionIcon id="export" name="Export" onClick={() => scrollIntoView("#export")} />
<SectionIcon id="notes" name="Notes" onClick={() => scrollIntoView("#notes")} />
<SectionIcon id="export" name={t`Export`} onClick={() => scrollIntoView("#export")} />
<SectionIcon id="notes" name={t`Notes`} onClick={() => scrollIntoView("#notes")} />
<SectionIcon
id="information"
name="Information"
name={t`Information`}
onClick={() => scrollIntoView("#information")}
/>
</div>

View File

@ -1,16 +1,15 @@
import { t } from "@lingui/macro";
import { CircleNotch, FileJs, FilePdf } from "@phosphor-icons/react";
import { buttonVariants, Card, CardContent, CardDescription, CardTitle } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { saveAs } from "file-saver";
import { useToast } from "@/client/hooks/use-toast";
import { usePrintResume } from "@/client/services/resume/print";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
export const ExportSection = () => {
const { toast } = useToast();
const { printResume, loading } = usePrintResume();
const onJsonExport = () => {
@ -19,11 +18,6 @@ export const ExportSection = () => {
const resumeJSON = JSON.stringify(resume.data, null, 2);
saveAs(new Blob([resumeJSON], { type: "application/json" }), filename);
toast({
variant: "success",
title: "A JSON snapshot of your resume has been successfully exported.",
});
};
const onPdfExport = async () => {
@ -43,7 +37,7 @@ export const ExportSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("export")}
<h2 className="line-clamp-1 text-3xl font-bold">Export</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Export`}</h2>
</div>
</header>
@ -57,10 +51,9 @@ export const ExportSection = () => {
>
<FileJs size={22} />
<CardContent className="flex-1">
<CardTitle className="text-sm">JSON</CardTitle>
<CardTitle className="text-sm">{t`JSON`}</CardTitle>
<CardDescription className="font-normal">
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.
{t`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.`}
</CardDescription>
</CardContent>
</Card>
@ -76,10 +69,9 @@ export const ExportSection = () => {
{loading ? <CircleNotch size={22} className="animate-spin" /> : <FilePdf size={22} />}
<CardContent className="flex-1">
<CardTitle className="text-sm">PDF</CardTitle>
<CardTitle className="text-sm">{t`PDF`}</CardTitle>
<CardDescription className="font-normal">
Download a PDF of your resume. This file can be used to print your resume, send it to
recruiters, or upload on job portals.
{t`Download a PDF of your resume. This file can be used to print your resume, send it to recruiters, or upload on job portals.`}
</CardDescription>
</CardContent>
</Card>

View File

@ -1,3 +1,4 @@
import { t, Trans } from "@lingui/macro";
import { Book, EnvelopeSimpleOpen, GithubLogo, HandHeart } from "@phosphor-icons/react";
import {
buttonVariants,
@ -14,28 +15,30 @@ import { getSectionIcon } from "../shared/section-icon";
const DonateCard = () => (
<Card className="space-y-4 bg-info text-info-foreground">
<CardContent className="space-y-2">
<CardTitle>Support the app by donating what you can!</CardTitle>
<CardTitle>{t`Support the app by donating what you can!`}</CardTitle>
<CardDescription className="space-y-2">
<p>
I built Reactive Resume mostly by myself during my spare time, with a lot of help from
other great open-source contributors.
</p>
<p>
If you like the app and want to support keeping it free forever, please donate whatever
you can afford to give.
</p>
<Trans>
<p>
I built Reactive Resume mostly by myself during my spare time, with a lot of help from
other great open-source contributors.
</p>
<p>
If you like the app and want to support keeping it free forever, please donate whatever
you can afford to give.
</p>
<p>Your donations could be tax-deductible, depending on your location.</p>
</Trans>
</CardDescription>
</CardContent>
<CardFooter>
<a
href="https://opencollective.com/reactive-resume"
className={cn(buttonVariants({ size: "sm" }))}
href="https://github.com/sponsors/AmruthPillai"
target="_blank"
rel="noopener noreferrer nofollow"
target="_blank"
>
<HandHeart size={14} weight="bold" className="mr-2" />
<span>Donate to Reactive Resume</span>
<span>{t`Donate to Reactive Resume`}</span>
</a>
</CardFooter>
</Card>
@ -44,36 +47,37 @@ const DonateCard = () => (
const IssuesCard = () => (
<Card className="space-y-4">
<CardContent className="space-y-2">
<CardTitle>Found a bug, or have an idea for a new feature?</CardTitle>
<CardTitle>{t`Found a bug, or have an idea for a new feature?`}</CardTitle>
<CardDescription className="space-y-2">
<p>I'm sure the app is not perfect, but I'd like for it to be.</p>
<p>
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.
</p>
<Trans>
<p>I'm sure the app is not perfect, but I'd like for it to be.</p>
<p>
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.
</p>
</Trans>
</CardDescription>
</CardContent>
<CardFooter className="space-x-4">
<a
className={cn(buttonVariants({ size: "sm" }))}
href="https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose"
target="_blank"
className={cn(buttonVariants({ size: "sm" }))}
rel="noopener noreferrer nofollow"
target="_blank"
>
<GithubLogo size={14} weight="bold" className="mr-2" />
<span>Raise an issue</span>
<span>{t`Raise an issue`}</span>
</a>
<a
className={cn(buttonVariants({ size: "sm" }))}
href="mailto:hello@amruthpillai.com"
target="_blank"
rel="noopener noreferrer nofollow"
target="_blank"
>
<EnvelopeSimpleOpen size={14} weight="bold" className="mr-2" />
<span>Send me a message</span>
<span>{t`Send me a message`}</span>
</a>
</CardFooter>
</Card>
@ -82,17 +86,18 @@ const IssuesCard = () => (
const DocumentationCard = () => (
<Card className="space-y-4">
<CardContent className="space-y-2">
<CardTitle>Don't know where to begin? Hit the docs!</CardTitle>
<CardTitle>{t`Don't know where to begin? Hit the docs!`}</CardTitle>
<CardDescription className="space-y-2">
<p>
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.
</p>
<p>
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.
</p>
<Trans>
<p>
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.
</p>
<p>
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.
</p>
</Trans>
</CardDescription>
</CardContent>
<CardFooter className="space-x-4">
@ -103,7 +108,7 @@ const DocumentationCard = () => (
rel="noopener noreferrer nofollow"
>
<Book size={14} weight="bold" className="mr-2" />
<span>Documentation</span>
<span>{t`Documentation`}</span>
</a>
</CardFooter>
</Card>
@ -115,7 +120,7 @@ export const InformationSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("information")}
<h2 className="line-clamp-1 text-3xl font-bold">Information</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Information`}</h2>
</div>
</header>

View File

@ -18,6 +18,7 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { t, Trans } from "@lingui/macro";
import { ArrowCounterClockwise, DotsSixVertical, Plus, TrashSimple } from "@phosphor-icons/react";
import { defaultMetadata } from "@reactive-resume/schema";
import { Button, Portal, Tooltip } from "@reactive-resume/ui";
@ -203,10 +204,10 @@ export const LayoutSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("layout")}
<h2 className="line-clamp-1 text-3xl font-bold">Layout</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Layout`}</h2>
</div>
<Tooltip content="Reset Layout">
<Tooltip content={t`Reset Layout`}>
<Button size="icon" variant="ghost" onClick={onResetLayout}>
<ArrowCounterClockwise />
</Button>
@ -232,7 +233,9 @@ export const LayoutSection = () => {
return (
<div key={pageIndex} className="rounded border p-3 pb-4">
<div className="flex items-center justify-between">
<p className="mb-3 text-xs font-bold">Page {pageIndex + 1}</p>
<p className="mb-3 text-xs font-bold">
<Trans>Page {pageIndex + 1}</Trans>
</p>
{pageIndex !== 0 && (
<Button
@ -247,8 +250,8 @@ export const LayoutSection = () => {
</div>
<div className="grid grid-cols-2 items-start gap-x-4">
<Column id={mainIndex} name="Main" items={main} />
<Column id={sidebarIndex} name="Sidebar" items={sidebar} />
<Column id={mainIndex} name={t`Main`} items={main} />
<Column id={sidebarIndex} name={t`Sidebar`} items={sidebar} />
</div>
</div>
);
@ -261,7 +264,7 @@ export const LayoutSection = () => {
<Button variant="outline" className="ml-auto" onClick={onAddPage}>
<Plus />
<span className="ml-2">Add New Page</span>
<span className="ml-2">{t`Add New Page`}</span>
</Button>
</main>
</section>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { RichInput } from "@reactive-resume/ui";
import { useResumeStore } from "@/client/stores/resume";
@ -13,22 +14,20 @@ export const NotesSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("notes")}
<h2 className="line-clamp-1 text-3xl font-bold">Notes</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Notes`}</h2>
</div>
</header>
<main className="grid gap-y-4">
<p className="leading-relaxed">
This section is reserved for your personal notes specific to this resume. The content here
remains private and is not shared with anyone else.
{t`This section is reserved for your personal notes specific to this resume. The content here remains private and is not shared with anyone else.`}
</p>
<div className="space-y-1.5">
<RichInput content={notes} onChange={(content) => setValue("metadata.notes", content)} />
<p className="text-xs leading-relaxed opacity-75">
For example, information regarding which companies you sent this resume to or the links
to the job descriptions can be noted down here.
{t`For example, information regarding which companies you sent this resume to or the links to the job descriptions can be noted down here.`}
</p>
</div>
</main>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import {
Label,
Select,
@ -22,13 +23,13 @@ export const PageSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("page")}
<h2 className="line-clamp-1 text-3xl font-bold">Page</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Page`}</h2>
</div>
</header>
<main className="grid gap-y-4">
<div className="space-y-1.5">
<Label>Format</Label>
<Label>{t`Format`}</Label>
<Select
value={page.format}
onValueChange={(value) => {
@ -36,17 +37,17 @@ export const PageSection = () => {
}}
>
<SelectTrigger>
<SelectValue placeholder="Format" />
<SelectValue placeholder={t`Format`} />
</SelectTrigger>
<SelectContent>
<SelectItem value="a4">A4</SelectItem>
<SelectItem value="letter">Letter</SelectItem>
<SelectItem value="a4">{t`A4`}</SelectItem>
<SelectItem value="letter">{t`Letter`}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label>Margin</Label>
<Label>{t`Margin`}</Label>
<div className="flex items-center gap-x-4 py-1">
<Slider
min={0}
@ -63,7 +64,7 @@ export const PageSection = () => {
</div>
<div className="space-y-1.5">
<Label>Options</Label>
<Label>{t`Options`}</Label>
<div className="py-2">
<div className="flex items-center gap-x-4">
@ -74,7 +75,7 @@ export const PageSection = () => {
setValue("metadata.page.options.breakLine", checked);
}}
/>
<Label htmlFor="metadata.page.options.breakLine">Show Break Line</Label>
<Label htmlFor="metadata.page.options.breakLine">{t`Show Break Line`}</Label>
</div>
</div>
@ -87,7 +88,7 @@ export const PageSection = () => {
setValue("metadata.page.options.pageNumbers", checked);
}}
/>
<Label htmlFor="metadata.page.options.pageNumbers">Show Page Numbers</Label>
<Label htmlFor="metadata.page.options.pageNumbers">{t`Show Page Numbers`}</Label>
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { CopySimple } from "@phosphor-icons/react";
import { Button, Input, Label, Switch, Tooltip } from "@reactive-resume/ui";
import { AnimatePresence, motion } from "framer-motion";
@ -25,9 +26,8 @@ export const SharingSection = () => {
toast({
variant: "success",
title: "A link has been copied to your clipboard.",
description:
"Anyone with this link can view and download the resume. Share it on your profile or with recruiters.",
title: t`A link has been copied to your clipboard.`,
description: t`Anyone with this link can view and download the resume. Share it on your profile or with recruiters.`,
});
};
@ -36,7 +36,7 @@ export const SharingSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("sharing")}
<h2 className="line-clamp-1 text-3xl font-bold">Sharing</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Sharing`}</h2>
</div>
</header>
@ -52,9 +52,9 @@ export const SharingSection = () => {
/>
<div>
<Label htmlFor="visibility" className="space-y-1">
<p>Public</p>
<p>{t`Public`}</p>
<p className="text-xs opacity-60">
Anyone with the link can view and download the resume.
{t`Anyone with the link can view and download the resume.`}
</p>
</Label>
</div>
@ -70,12 +70,12 @@ export const SharingSection = () => {
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Label htmlFor="resume-url">URL</Label>
<Label htmlFor="resume-url">{t`URL`}</Label>
<div className="flex gap-x-1.5">
<Input id="resume-url" readOnly value={url} className="flex-1" />
<Tooltip content="Copy to Clipboard">
<Tooltip content={t`Copy to Clipboard`}>
<Button size="icon" variant="ghost" onClick={onCopy}>
<CopySimple />
</Button>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { Info } from "@phosphor-icons/react";
import { Alert, AlertDescription, AlertTitle } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
@ -19,7 +20,7 @@ export const StatisticsSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("statistics")}
<h2 className="line-clamp-1 text-3xl font-bold">Statistics</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Statistics`}</h2>
</div>
</header>
@ -34,12 +35,9 @@ export const StatisticsSection = () => {
>
<Alert variant="info">
<Info size={18} />
<AlertTitle>Statistics are available only for public resumes.</AlertTitle>
<AlertTitle>{t`Statistics are available only for public resumes.`}</AlertTitle>
<AlertDescription className="text-xs leading-relaxed">
You can track the number of views your resume has received, or how many people
have downloaded the resume by enabling public sharing.
{t`You can track the number of views your resume has received, or how many people have downloaded the resume by enabling public sharing.`}
</AlertDescription>
</Alert>
</motion.div>
@ -50,14 +48,14 @@ export const StatisticsSection = () => {
<h3 className={cn("text-4xl font-bold blur-none transition-all", !isPublic && "blur-sm")}>
{statistics?.views ?? 0}
</h3>
<p className="opacity-75">Views</p>
<p className="opacity-75">{t`Views`}</p>
</div>
<div>
<h3 className={cn("text-4xl font-bold blur-none transition-all", !isPublic && "blur-sm")}>
{statistics?.downloads ?? 0}
</h3>
<p className="opacity-75">Downloads</p>
<p className="opacity-75">{t`Downloads`}</p>
</div>
</main>
</section>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { Button, HoverCard, HoverCardContent, HoverCardTrigger } from "@reactive-resume/ui";
import { cn, templatesList } from "@reactive-resume/utils";
@ -14,7 +15,7 @@ export const TemplateSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("template")}
<h2 className="line-clamp-1 text-3xl font-bold">Template</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Template`}</h2>
</div>
</header>

View File

@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { Input, Label, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { HexColorPicker } from "react-colorful";
@ -16,7 +17,7 @@ export const ThemeSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("theme")}
<h2 className="line-clamp-1 text-3xl font-bold">Theme</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Theme`}</h2>
</div>
</header>
@ -39,7 +40,7 @@ export const ThemeSection = () => {
</div>
<div className="space-y-1.5">
<Label htmlFor="theme.primary">Primary Color</Label>
<Label htmlFor="theme.primary">{t`Primary Color`}</Label>
<div className="relative">
<Popover>
<PopoverTrigger asChild>
@ -69,7 +70,7 @@ export const ThemeSection = () => {
</div>
<div className="space-y-1.5">
<Label htmlFor="theme.primary">Background Color</Label>
<Label htmlFor="theme.primary">{t`Background Color`}</Label>
<div className="relative">
<Popover>
<PopoverTrigger asChild>
@ -99,7 +100,7 @@ export const ThemeSection = () => {
</div>
<div className="space-y-1.5">
<Label htmlFor="theme.primary">Text Color</Label>
<Label htmlFor="theme.primary">{t`Text Color`}</Label>
<div className="relative">
<Popover>
<PopoverTrigger asChild>

View File

@ -1,3 +1,6 @@
/* eslint-disable lingui/no-unlocalized-strings */
import { t } from "@lingui/macro";
import { Button, Combobox, ComboboxOption, Label, Slider, Switch } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { fonts } from "@reactive-resume/utils";
@ -61,7 +64,7 @@ export const TypographySection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("typography")}
<h2 className="line-clamp-1 text-3xl font-bold">Typography</h2>
<h2 className="line-clamp-1 text-3xl font-bold">{t`Typography`}</h2>
</div>
</header>
@ -89,11 +92,11 @@ export const TypographySection = () => {
</div>
<div className="space-y-1.5">
<Label>Font Family</Label>
<Label>{t`Font Family`}</Label>
<Combobox
options={families}
value={typography.font.family}
searchPlaceholder="Search for a font family"
searchPlaceholder={t`Search for a font family`}
onValueChange={(value) => {
setValue("metadata.typography.font.family", value);
setValue("metadata.typography.font.subset", "latin");
@ -104,11 +107,11 @@ export const TypographySection = () => {
<div className="grid grid-cols-2 gap-x-4">
<div className="space-y-1.5">
<Label>Font Subset</Label>
<Label>{t`Font Subset`}</Label>
<Combobox
options={subsets}
value={typography.font.subset}
searchPlaceholder="Search for a font subset"
searchPlaceholder={t`Search for a font subset`}
onValueChange={(value) => {
setValue("metadata.typography.font.subset", value);
}}
@ -116,12 +119,12 @@ export const TypographySection = () => {
</div>
<div className="space-y-1.5">
<Label>Font Variants</Label>
<Label>{t`Font Variants`}</Label>
<Combobox
multiple
options={variants}
value={typography.font.variants}
searchPlaceholder="Search for a font variant"
searchPlaceholder={t`Search for a font variant`}
onValueChange={(value) => {
setValue("metadata.typography.font.variants", value);
}}
@ -130,7 +133,7 @@ export const TypographySection = () => {
</div>
<div className="space-y-1.5">
<Label>Font Size</Label>
<Label>{t`Font Size`}</Label>
<div className="flex items-center gap-x-4 py-1">
<Slider
min={12}
@ -147,7 +150,7 @@ export const TypographySection = () => {
</div>
<div className="space-y-1.5">
<Label>Line Height</Label>
<Label>{t`Line Height`}</Label>
<div className="flex items-center gap-x-4 py-1">
<Slider
min={0}
@ -164,7 +167,7 @@ export const TypographySection = () => {
</div>
<div className="space-y-1.5">
<Label>Options</Label>
<Label>{t`Options`}</Label>
<div className="py-2">
<div className="flex items-center gap-x-4">
@ -175,7 +178,7 @@ export const TypographySection = () => {
setValue("metadata.typography.underlineLinks", checked);
}}
/>
<Label htmlFor="metadata.typography.underlineLinks">Underline Links</Label>
<Label htmlFor="metadata.typography.underlineLinks">{t`Underline Links`}</Label>
</div>
</div>
</div>