mirror of
https://github.com/JOYCEQL/magic-resume.git
synced 2026-07-03 14:07:11 +02:00
feat: enhance date formatting utility with locale support and integrate it across preview components.
This commit is contained in:
+14
-11
@@ -3,20 +3,23 @@
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { Analytics } from "@vercel/analytics/next";
|
||||
import { HeroUIProvider } from "@heroui/react";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<HeroUIProvider>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="light"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
storageKey="magic-resume-theme"
|
||||
>
|
||||
{children}
|
||||
<Analytics />
|
||||
</ThemeProvider>
|
||||
<HeroUIProvider locale={locale}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="light"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
storageKey="magic-resume-theme"
|
||||
>
|
||||
{children}
|
||||
<Analytics />
|
||||
</ThemeProvider>
|
||||
</HeroUIProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ const BaseInfo = ({
|
||||
key: field.key,
|
||||
value:
|
||||
field.key === "birthDate" && basic[field.key]
|
||||
? formatDateString(basic[field.key] as string)
|
||||
? formatDateString(basic[field.key] as string, locale)
|
||||
: (basic[field.key] as string),
|
||||
icon: basic.icons?.[field.key] || "User",
|
||||
label: field.label,
|
||||
|
||||
@@ -4,6 +4,8 @@ import SectionTitle from "./SectionTitle";
|
||||
import { GlobalSettings, CustomItem } from "@/types/resume";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
import { normalizeRichTextContent } from "@/lib/richText";
|
||||
import { formatDateString } from "@/lib/utils";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
interface CustomSectionProps {
|
||||
sectionId: string;
|
||||
@@ -21,6 +23,7 @@ const CustomSection = ({
|
||||
showTitle = true,
|
||||
}: CustomSectionProps) => {
|
||||
const { setActiveSection } = useResumeStore();
|
||||
const locale = useLocale();
|
||||
const visibleItems = items?.filter((item) => {
|
||||
return item.visible && (item.title || item.description);
|
||||
});
|
||||
@@ -86,7 +89,7 @@ const CustomSection = ({
|
||||
fontSize: `${globalSettings?.subheaderSize || 16}px`,
|
||||
}}
|
||||
>
|
||||
{item.dateRange}
|
||||
{formatDateString(item.dateRange, locale)}
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
|
||||
@@ -85,8 +85,9 @@ const EducationSection = ({
|
||||
fontSize: `${globalSettings?.subheaderSize || 16}px`,
|
||||
}}
|
||||
>
|
||||
{`${formatDateString(edu.startDate)} - ${formatDateString(
|
||||
edu.endDate
|
||||
{`${formatDateString(edu.startDate, locale)} - ${formatDateString(
|
||||
edu.endDate,
|
||||
locale
|
||||
)}`}
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { Experience, GlobalSettings } from "@/types/resume";
|
||||
import SectionTitle from "./SectionTitle";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
import { normalizeRichTextContent } from "@/lib/richText";
|
||||
import { formatDateString } from "@/lib/utils";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
interface ExperienceSectionProps {
|
||||
experiences?: Experience[];
|
||||
@@ -22,6 +24,7 @@ const ExperienceItem = React.forwardRef<HTMLDivElement, ExperienceItemProps>(
|
||||
const centerSubtitle = globalSettings?.centerSubtitle;
|
||||
const flexLayout = globalSettings?.flexibleHeaderLayout;
|
||||
const gridColumns = centerSubtitle ? 3 : 2;
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -55,7 +58,7 @@ const ExperienceItem = React.forwardRef<HTMLDivElement, ExperienceItemProps>(
|
||||
fontSize: `${globalSettings?.subheaderSize || 16}px`,
|
||||
}}
|
||||
>
|
||||
{experience.date}
|
||||
{formatDateString(experience.date, locale)}
|
||||
</div>
|
||||
</motion.div>
|
||||
{experience.position && !centerSubtitle && (
|
||||
|
||||
@@ -6,6 +6,8 @@ import SectionTitle from "./SectionTitle";
|
||||
import { Project, GlobalSettings } from "@/types/resume";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
import { normalizeRichTextContent } from "@/lib/richText";
|
||||
import { formatDateString } from "@/lib/utils";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
interface ProjectItemProps {
|
||||
project: Project;
|
||||
@@ -15,6 +17,7 @@ interface ProjectItemProps {
|
||||
const ProjectItem = React.forwardRef<HTMLDivElement, ProjectItemProps>(
|
||||
({ project, globalSettings }, ref) => {
|
||||
const centerSubtitle = globalSettings?.centerSubtitle;
|
||||
const locale = useLocale();
|
||||
|
||||
const flexLayout = globalSettings?.flexibleHeaderLayout;
|
||||
|
||||
@@ -84,7 +87,7 @@ const ProjectItem = React.forwardRef<HTMLDivElement, ProjectItemProps>(
|
||||
fontSize: `${globalSettings?.subheaderSize || 16}px`,
|
||||
}}
|
||||
>
|
||||
{project.date}
|
||||
{formatDateString(project.date, locale)}
|
||||
</div>
|
||||
</motion.div>
|
||||
{project.role && !centerSubtitle && (
|
||||
|
||||
+49
-12
@@ -5,19 +5,56 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function formatDateString(dateStr: string | undefined): string {
|
||||
if (!dateStr) return "";
|
||||
// Check if it matches YYYY-MM
|
||||
// If so, replace - with .
|
||||
|
||||
function parseToDate(dateStr: string): Date | null {
|
||||
let year: number | null = null;
|
||||
let month: number | null = null;
|
||||
|
||||
if (dateStr.match(/^\d{4}-\d{2}$/)) {
|
||||
return dateStr.replace("-", ".");
|
||||
const parts = dateStr.split("-");
|
||||
year = parseInt(parts[0], 10);
|
||||
month = parseInt(parts[1], 10);
|
||||
} else if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
||||
const parts = dateStr.split("-");
|
||||
year = parseInt(parts[0], 10);
|
||||
month = parseInt(parts[1], 10);
|
||||
} else if (dateStr.match(/^\d{4}\.\d{2}$/)) {
|
||||
const parts = dateStr.split(".");
|
||||
year = parseInt(parts[0], 10);
|
||||
month = parseInt(parts[1], 10);
|
||||
} else if (dateStr.match(/^\d{4}\/\d{2}$/)) {
|
||||
const parts = dateStr.split("/");
|
||||
year = parseInt(parts[0], 10);
|
||||
month = parseInt(parts[1], 10);
|
||||
}
|
||||
|
||||
if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
||||
// Cut off day if present? User wants "month" precision.
|
||||
return dateStr.substring(0, 7).replace("-", ".");
|
||||
|
||||
if (year !== null && month !== null) {
|
||||
return new Date(Date.UTC(year, month - 1, 1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function formatDateString(dateStr: string | undefined, locale: string = "zh"): string {
|
||||
if (!dateStr) return "";
|
||||
|
||||
if (dateStr.includes(" - ")) {
|
||||
const [start, end] = dateStr.split(" - ");
|
||||
return `${formatDateString(start, locale)} - ${formatDateString(end, locale)}`;
|
||||
}
|
||||
|
||||
const date = parseToDate(dateStr);
|
||||
if (!date) return dateStr;
|
||||
|
||||
try {
|
||||
if (locale === "zh" || locale === "zh-CN") {
|
||||
return `${date.getUTCFullYear()}.${String(date.getUTCMonth() + 1).padStart(2, "0")}`;
|
||||
}
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
timeZone: 'UTC'
|
||||
});
|
||||
return formatter.format(date);
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user