diff --git a/app/layout.tsx b/app/layout.tsx index c6898f4..328aa41 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,16 +1,43 @@ import "./globals.css"; import type { ReactNode } from "react"; +import Script from "next/script"; import Providers from "./providers"; +const themeInitScript = ` + try { + const storageKey = "devimpact-theme"; + const storedTheme = window.localStorage.getItem(storageKey); + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = + storedTheme === "light" || storedTheme === "dark" + ? storedTheme + : prefersDark + ? "dark" + : "light"; + + const root = document.documentElement; + root.classList.toggle("dark", theme === "dark"); + root.style.colorScheme = theme; + } catch {} +`; + export const metadata = { title: "DevImpact", description: "GitHub user scoring", + icons: { + icon: "/logo.svg", + shortcut: "/logo.svg", + apple: "/logo.svg", + }, }; export default function RootLayout({ children }: { children: ReactNode }) { return ( + {children} diff --git a/app/page.tsx b/app/page.tsx index 776ff42..8504569 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,8 +5,10 @@ import { CompareForm } from "../components/compare-form"; import { ResultDashboard } from "../components/result-dashboard"; import { DashboardSkeleton } from "../components/skeletons"; import { UserResult } from "@/types/user-result"; -import { LanguageSwitcher } from "@/components/language-switcher"; -import { ThemeToggle } from "@/components/theme-toggle"; +import { BrandLogo } from "@/components/brand-logo"; +import { AppHeader } from "@/components/app-header"; +import { AppFooter } from "@/components/app-footer"; +import { useTranslation } from "@/components/language-provider"; type ApiResponse = { success: boolean; @@ -15,6 +17,7 @@ type ApiResponse = { }; export default function HomePage() { + const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState<{ @@ -22,19 +25,40 @@ export default function HomePage() { user2: UserResult; } | null>(null); + const localizeErrorMessage = (message?: string) => { + switch (message) { + case "provide at least one username param": + return t("error.missingUsername"); + case "GitHub user not found": + return t("error.userNotFound"); + case "Failed to calculate score": + return t("error.calculateFailed"); + case "Comparison failed": + return t("error.comparisonFailed"); + case "Failed to fetch": + return t("error.fetchFailed"); + default: + return message || t("error.generic"); + } + }; + const handleCompare = async (u1: string, u2: string) => { setLoading(true); setError(null); setData(null); + try { const params = new URLSearchParams(); params.append("username", u1); params.append("username", u2); + const res = await fetch(`/api/compare?${params.toString()}`); const body: ApiResponse = await res.json(); + if (!body.success || !body.users || body.users.length < 2) { - throw new Error(body.error || "Comparison failed"); + throw new Error(localizeErrorMessage(body.error || "Comparison failed")); } + if (body.users[0].finalScore > body.users[1].finalScore) { setData({ user1: { ...body.users[0], isWinner: true }, @@ -49,7 +73,7 @@ export default function HomePage() { setData({ user1: body.users[0], user2: body.users[1] }); } } catch (err: unknown) { - setError(err instanceof Error ? err.message : "Failed to fetch"); + setError(localizeErrorMessage(err instanceof Error ? err.message : undefined)); } finally { setLoading(false); } @@ -61,29 +85,17 @@ export default function HomePage() { setData(null); setError(null); }; + const swapUsers = () => { if (!data) return; setData((d) => ({ user1: d!.user2, user2: d!.user1 })); - console.log("Swapped users", data); }; + return (
- {" "} -
-
-
- - DevImpact - -
+ -
- - -
-
-
-
+
+
{error}
)} + {data && } + {!loading && !error && !data && ( -
- - - - - -

Enter two usernames to compare

-

- Compare GitHub developer metrics side by side -

+
+ +

{t("page.empty.title")}

+

{t("page.empty.description")}

)}
-
-
- DevImpact — Compare GitHub developer metrics -
-
+ +
); } diff --git a/components/app-footer.tsx b/components/app-footer.tsx new file mode 100644 index 0000000..cf2f07f --- /dev/null +++ b/components/app-footer.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useTranslation } from "@/components/language-provider"; +import { cn } from "@/lib/utils"; + +export function AppFooter() { + const { t, dir } = useTranslation(); + + return ( + + ); +} diff --git a/components/app-header.tsx b/components/app-header.tsx new file mode 100644 index 0000000..97eb99d --- /dev/null +++ b/components/app-header.tsx @@ -0,0 +1,18 @@ +import { BrandLogo } from "@/components/brand-logo"; +import { LanguageSwitcher } from "@/components/language-switcher"; +import { ThemeToggle } from "@/components/theme-toggle"; + +export function AppHeader() { + return ( +
+
+ + +
+ + +
+
+
+ ); +} diff --git a/components/brand-logo.tsx b/components/brand-logo.tsx new file mode 100644 index 0000000..969b587 --- /dev/null +++ b/components/brand-logo.tsx @@ -0,0 +1,40 @@ +import Image from "next/image"; +import { cn } from "@/lib/utils"; + +type BrandLogoProps = { + size?: "sm" | "md" | "lg" | "xl"; + className?: string; + priority?: boolean; +}; + +const sizeClasses = { + sm: "h-12 w-12 sm:h-16 sm:w-16", + md: "h-14 w-14 sm:h-20 sm:w-20", + lg: "h-16 w-16 sm:h-24 sm:w-24", + xl: "h-24 w-24 sm:h-32 sm:w-32", +} as const; + +export function BrandLogo({ + size = "md", + className, + priority = false, +}: BrandLogoProps) { + return ( + + DevImpact + + ); +} diff --git a/components/comparison-chart.tsx b/components/comparison-chart.tsx index a6b6750..b36474b 100644 --- a/components/comparison-chart.tsx +++ b/components/comparison-chart.tsx @@ -28,42 +28,93 @@ const metrics = [ { key: "repoScore", label: "comparsion.repo.score" }, { key: "prScore", label: "comparsion.pr.score" }, { key: "contributionScore", label: "comparsion.activity.score" }, + { key: "repoScore", label: "comparsion.repo.score" }, + { key: "prScore", label: "comparsion.pr.score" }, + { key: "contributionScore", label: "comparsion.activity.score" }, ]; export function ComparisonChart({ user1, user2 }: Props) { - const {t} = useTranslation(); + const { t, dir } = useTranslation(); + const isRtl = dir === "rtl"; const data = metrics.map((m) => ({ name: t(m.label), [user1.username]: user1[m.key as keyof UserResult] ?? 0, [user2.username]: user2[m.key as keyof UserResult] ?? 0, })); - const renderLegendText = (value: string) => { - return {value}; -}; + + const renderLegendText = (value: string) => { + return {value}; + }; + const renderYAxisTick = ({ + x, + y, + payload, + }: { + x: number; + y: number; + payload: { value: number | string }; + }) => ( + + {payload.value} + + ); return ( - {t('barchart.title')} + {t("barchart.title")} - {t('barchart.desc')} + {t("barchart.desc")} -
+
- - + + + - {[user1, user2].map((user, idx) => ( - - + +
{`${user.name{user.name || user.username}
{user.isWinner && ( - Winner + + {t("banner.winner")} + )}
- {t("comparsion.final.score")} + + {t("comparsion.final.score")} + {user.finalScore}
{t("comparsion.repo.score")} - (idx === 0 ? user2.repoScore : user1.repoScore) ? "text-primary" : ""}`}> + (idx === 0 ? user2.repoScore : user1.repoScore) ? "text-primary" : ""}`} + > {user.repoScore}
{t("comparsion.pr.score")} - (idx === 0 ? user2.prScore : user1.prScore) ? "text-primary" : ""}`}> + (idx === 0 ? user2.prScore : user1.prScore) ? "text-primary" : ""}`} + > {user.prScore}
- {t("comparsion.contribution.score")} - (idx === 0 ? user2.contributionScore : user1.contributionScore) ? "text-primary" : ""}`}> + + {t("comparsion.contribution.score")} + + (idx === 0 ? user2.contributionScore : user1.contributionScore) ? "text-primary" : ""}`} + > {user.contributionScore}
diff --git a/components/result-dashboard.tsx b/components/result-dashboard.tsx index 4bab6b4..d608a6d 100644 --- a/components/result-dashboard.tsx +++ b/components/result-dashboard.tsx @@ -39,15 +39,26 @@ export function ResultDashboard({ user1, user2 }: Props) { const prDiff = Math.max(user1.prScore, user2.prScore) - Math.min(user1.prScore, user2.prScore); + + const getInsights = () => { - const insights = []; + const insights: string[] = []; + if (user1.repoScore > user2.repoScore) { insights.push( - `${user1.username} ${t("insights.stronger.repo")} ${user1.repoScore} (vs) ${user2.repoScore}`, + t("insights.repo.leader", { + username: user1.username, + score: user1.repoScore, + other: user2.repoScore, + }) ); } else if (user2.repoScore > user1.repoScore) { insights.push( - `${user2.username} ${t("insights.stronger.repo")} ${user2.repoScore} (vs) ${user1.repoScore}`, + t("insights.repo.leader", { + username: user2.username, + score: user2.repoScore, + other: user1.repoScore, + }) ); } else { insights.push(t("insights.equal.repo")); @@ -55,20 +66,28 @@ export function ResultDashboard({ user1, user2 }: Props) { if (user1.prScore > user2.prScore) { insights.push( - `${user1.username} ${t("insights.pull.leads")} ${user1.prScore} (vs) ${user2.prScore}`, + t("insights.pr.leader", { + username: user1.username, + score: user1.prScore, + other: user2.prScore, + }) ); } else if (user2.prScore > user1.prScore) { insights.push( - `${user2.username} ${t("insights.pull.leads")} ${user2.prScore} (vs) ${user1.prScore}`, + t("insights.pr.leader", { + username: user2.username, + score: user2.prScore, + other: user1.prScore, + }) ); } else { insights.push(t("insights.equal.pr")); } if (user1.contributionScore > user2.contributionScore) { - insights.push(`${user1.username} ${t("insights.higher.contribution")}`); + insights.push(t("insights.contribution.leader", { username: user1.username })); } else if (user2.contributionScore > user1.contributionScore) { - insights.push(`${user2.username} ${t("insights.higher.contribution")}`); + insights.push(t("insights.contribution.leader", { username: user2.username })); } else { insights.push(t("insights.equal.contribution")); } @@ -142,17 +161,17 @@ export function ResultDashboard({ user1, user2 }: Props) { size="sm" onClick={handleCopy} className="flex items-center gap-2" - aria-label="Copy comparison results to clipboard" + aria-label={t("results.copyAria")} > {copied ? ( <> - Copied! + {t("results.copied")} ) : ( <> - Copy Result + {t("results.copy")} )} diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx index ccae4ee..27a1efc 100644 --- a/components/theme-toggle.tsx +++ b/components/theme-toggle.tsx @@ -3,12 +3,15 @@ import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; import { Button } from "./ui/button"; -import { useEffect, useState } from "react"; +import { useSyncExternalStore } from "react"; +import { useTranslation } from "./language-provider"; + +const emptySubscribe = () => () => {}; export function ThemeToggle() { + const { t } = useTranslation(); const { theme, setTheme, resolvedTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); + const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false); const THEME_TRANSITION_MS = 520; const current = resolvedTheme || theme || "light"; @@ -35,7 +38,7 @@ export function ThemeToggle() { size="sm" onClick={handleToggle} className="flex items-center gap-2" - aria-label="Toggle color mode" + aria-label={t("theme.toggle")} > {mounted && current === "dark" ? : } diff --git a/components/top-list.tsx b/components/top-list.tsx index bafd405..515d2f3 100644 --- a/components/top-list.tsx +++ b/components/top-list.tsx @@ -3,9 +3,9 @@ import { Eye, GitFork, GitPullRequest, Minus, Plus, Star } from "lucide-react"; import { Card, CardContent, + CardDescription, CardHeader, CardTitle, - CardDescription, } from "./ui/card"; import { UserResult } from "@/types/user-result"; import { @@ -21,6 +21,7 @@ type Props = { export function TopList({ userResults }: Props) { const { t } = useTranslation(); + const cardDetails = (data: { title: string; titleUrl?: string; @@ -30,7 +31,7 @@ export function TopList({ userResults }: Props) { key: string | number; }) => (
@@ -40,7 +41,7 @@ export function TopList({ userResults }: Props) { href={data.titleUrl} target="_blank" rel="noopener noreferrer" - className="hover:underline text-primary" + className="text-primary hover:underline" > {data.title} @@ -48,10 +49,10 @@ export function TopList({ userResults }: Props) { data.title )}
-
+
{data.subtitle}
-
+
{data.badges.map((badge, i) => ( @@ -70,33 +71,33 @@ export function TopList({ userResults }: Props) {

{(data.score ?? 0).toFixed(2)}

-

{t('comparsion.score')}

+

+ {t("comparsion.score")} +

); return ( -
+
{userResults.map((user) => ( - {t('topwork.title')} • {user.username} + {t("topwork.titleForUser", { username: user.username })} - - {t('topwork.desc')} - + {t("topwork.desc")}
-

- {t('topwork.toprepos')} +

+ {t("topwork.toprepos")}

{user.topRepos.slice(0, 3).map((repo, i) => cardDetails({ key: `repo-${i}`, - title: repo.name || t('untitled'), + title: repo.name || t("untitled"), titleUrl: repo.name ? `https://github.com/${user.username}/${repo.name}` : undefined, @@ -105,64 +106,63 @@ export function TopList({ userResults }: Props) { { icon: , label: repo.stars, - tooltip: `${repo.stars} ${t('topwork.stars')}`, + tooltip: `${repo.stars} ${t("topwork.stars")}`, }, { icon: , label: repo.forks, - tooltip: `${repo.forks} ${t('topwork.forks')}`, + tooltip: `${repo.forks} ${t("topwork.forks")}`, }, { icon: , label: repo.watchers, - tooltip: `${repo.watchers} ${t('topwork.watchers')}`, + tooltip: `${repo.watchers} ${t("topwork.watchers")}`, }, ], }), )} {user.topRepos.length === 0 && (

- {t('topwork.norepos')} + {t("topwork.noRepos")}

)}

-

- {t('topwork.topprs')} +

+ {t("topwork.topprs")}

{user.topPullRequests.slice(0, 3).map((pr, i) => cardDetails({ key: `pr-${i}`, - title: pr.title || t('untitled'), + title: pr.title || t("untitled"), titleUrl: pr.url, - subtitle: `in ${pr.repo}`, + subtitle: t("topwork.inRepo", { repo: pr.repo || "" }), score: pr.score, badges: [ { icon: , label: pr.stars, - tooltip: `${pr.stars} ${t('topwork.pr.repo.stars')}`, + tooltip: `${pr.stars} ${t("topwork.pr.repo.stars")}`, }, - { icon: , label: pr.additions || "0", - tooltip: `+${pr.additions || 0} ${t('topwork.pr.additions')}`, + tooltip: `+${pr.additions || 0} ${t("topwork.pr.additions")}`, }, { icon: , label: pr.deletions || "0", - tooltip: `-${pr.deletions || 0} ${t('topwork.pr.deletions')}`, + tooltip: `-${pr.deletions || 0} ${t("topwork.pr.deletions")}`, }, ], }), )} {user.topPullRequests.length === 0 && (

- {t('topwork.noPRs')} + {t("topwork.noPRs")}

)}
diff --git a/locales/ar.json b/locales/ar.json index 6c8f967..7c67016 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -1,55 +1,73 @@ { - "app.subtitle": "قارن بين مطورين", - "app.title": "قارن بين مطوري الجيتهب (GitHub)", - "banner.leadby": "متقدم بـ", - "banner.metric": "المقاييس", - "banner.tie": "تعادل — كلا المطورين متساويان في الأداء.", + "app.subtitle": "قارن بين مطورين جنبًا إلى جنب", + "app.title": "قارن مطوري GitHub", + "banner.leadby": "يتقدم بنسبة", + "banner.metric": "النتيجة", + "banner.tie": "النتيجة متعادلة. أداء المطورين متقارب.", "banner.winner": "الفائز", - "barchart.desc": "تحليل بصري للمقاييس الرئيسية", - "barchart.title": "مقارنة الأدء", - "breakdown.contribution": "نقاط المساهمات (Contributions)", - "breakdown.description": "أشرطة تقدم تظهر الأداء النسبي", - "breakdown.pr": "نقاط طلبات الدمج (PRs)", - "breakdown.repo": "نقاط المشاريع (Repos)", - "breakdown.title": "مقارنة تفصيلية", - "comparsion.activity.score": "نقاط النشاط (Activity)", - "comparsion.contribution.diff": "فرق المساهمات", - "comparsion.contribution.score": "نقاط المساهمات (Contributions)", - "comparsion.diff": "فارق التقييم", - "comparsion.final.score": "النتيجة النهائية", - "comparsion.pr.diff": "فرق طلبات الدمج", - "comparsion.pr.score": "نقاط طلبات الدمج (PRs)", - "comparsion.repo.diff": "فرق المشاريع", - "comparsion.repo.score": "نقاط المشاريع (Repos)", - "comparsion.score": "التقييم", - "error.generic": "فشل في جلب البيانات", + "barchart.desc": "عرض مرئي لأهم المقاييس", + "barchart.title": "مقارنة الدرجات", + "breakdown.contribution": "درجة المساهمات", + "breakdown.description": "أشرطة تقدم توضح الأداء النسبي", + "breakdown.pr": "درجة طلبات السحب", + "breakdown.repo": "درجة المستودعات", + "breakdown.title": "تفصيل الدرجات", + "comparison.avatarAlt": "الصورة الرمزية لـ{name}", + "comparsion.activity.score": "درجة النشاط", + "comparsion.contribution.diff": "فارق المساهمات", + "comparsion.contribution.score": "درجة المساهمات", + "comparsion.diff": "فارق الدرجات", + "comparsion.final.score": "الدرجة النهائية", + "comparsion.pr.diff": "فارق طلبات السحب", + "comparsion.pr.score": "درجة طلبات السحب", + "comparsion.repo.diff": "فارق المستودعات", + "comparsion.repo.score": "درجة المستودعات", + "comparsion.score": "الدرجة", + "error.calculateFailed": "تعذر حساب النتيجة", + "error.comparisonFailed": "فشلت المقارنة", + "error.fetchFailed": "تعذر جلب البيانات", + "error.generic": "تعذر جلب البيانات", + "error.missingUsername": "أدخل اسم مستخدم واحدًا على الأقل.", + "error.userNotFound": "لم يتم العثور على مستخدم GitHub", + "footer.description": "قارن مقاييس مطوري GitHub جنبًا إلى جنب مع رؤية أوضح للنشاط والاستمرارية والأثر العام.", + "footer.eyebrow": "مقارنة المطورين", + "footer.note": "مصمم لمراجعات احترافية سريعة جنبًا إلى جنب عندما تحتاج إلى إشارة أوضح من تصفح الملفات الشخصية فقط.", + "footer.summary": "قارن مقاييس مطوري GitHub", + "footer.tag": "مقاييس GitHub", + "footer.tagline": "رؤية أوضح. تقييم أسرع.", "form.compare": "قارن", "form.compare.ing": "جارٍ المقارنة...", - "form.enterTwo": "أدخل اسم مستخدمين من GitHub لمقارنة مقاييسهم كمطورين", + "form.enterTwo": "أدخل اسمي مستخدم على GitHub لمقارنة مقاييسهما", "form.reset": "إعادة تعيين", - "form.swap": "تبديل", - "form.username1": "اسم المستخدم 1 (مثال: torvalds)", - "form.username2": "اسم المستخدم 2 (مثال: octocat)", - "insights.equal.contribution": "كلا المطورين لديهم مستويات مساهمة متشابهة", - "insights.equal.pr": "كلا المطورين لديهم تأثير طلبات الدمج متساوي", - "insights.equal.repo": "كلا المطورين لديهم قوة مشاريع متساوية", - "insights.higher.contribution": "يُظهر نشاطاً أكثر في المساهمات", - "insights.pull.leads": "يتفوق في تأثير طلبات الدمج بـ", - "insights.stronger.contribution": "لديه مساهمات عامة أقوى بـ", - "insights.stronger.pr": "لديه تأثير أقوى في طلبات الدمج بـ", - "insights.stronger.repo": "لديه مشاريع أقوى بـ", - "insights.title": "أهم الملاحظات", - "topwork.desc": "المشاريع والإسهامات الأكثر تأثيراً", - "topwork.forks": "استنساخات", - "topwork.noPRs": "لم يتم العثور على طلبات دمج", - "topwork.noRepos": "لم يتم العثور على مشاريع", - "topwork.pr.additions": "الإضافات", - "topwork.pr.deletions": "الحذف", - "topwork.pr.repo.stars": "النجوم في مستودع الطلب", - "topwork.stars": "إعجابات", - "topwork.title": "أفضل الأعمال", - "topwork.topprs": "طلبات الدمج", - "topwork.toprepos": "المشاريع", - "topwork.watchers": "متابعون", + "form.swap": "تبديل المستخدمين", + "form.username1": "اسم المستخدم الأول (مثل torvalds)", + "form.username2": "اسم المستخدم الثاني (مثل octocat)", + "insights.contribution.leader": "{username} يظهر نشاطًا أعلى في المساهمات", + "insights.equal.contribution": "كلا المطورين متقاربان في مستوى المساهمات", + "insights.equal.pr": "كلا المطورين متساويان في تأثير طلبات السحب", + "insights.equal.repo": "كلا المطورين متقاربان في قوة المستودعات", + "insights.pr.leader": "{username} يتقدم في تأثير طلبات السحب بنتيجة {score} مقابل {other}", + "insights.repo.leader": "{username} يمتلك محفظة مستودعات أقوى بنتيجة {score} مقابل {other}", + "insights.title": "أبرز الملاحظات", + "page.empty.description": "قارن مقاييس مطوري GitHub جنبًا إلى جنب", + "page.empty.title": "أدخل اسمي مستخدم للمقارنة", + "results.copied": "تم النسخ!", + "results.copy": "نسخ النتيجة", + "results.copyAria": "انسخ نتائج المقارنة إلى الحافظة", + "theme.toggle": "تبديل وضع الألوان", + "topwork.desc": "أكثر المستودعات وطلبات السحب تأثيرًا", + "topwork.forks": "تفرعات", + "topwork.inRepo": "في {repo}", + "topwork.noPRs": "لا توجد طلبات سحب", + "topwork.noRepos": "لا توجد مستودعات", + "topwork.pr.additions": "إضافات", + "topwork.pr.deletions": "حذوفات", + "topwork.pr.repo.stars": "نجوم على مستودع طلب السحب", + "topwork.stars": "نجوم", + "topwork.title": "أبرز الأعمال", + "topwork.titleForUser": "أبرز الأعمال - {username}", + "topwork.topprs": "أفضل طلبات السحب", + "topwork.toprepos": "أفضل المستودعات", + "topwork.watchers": "مراقبون", "untitled": "بدون عنوان" -} \ No newline at end of file +} diff --git a/locales/en.json b/locales/en.json index b279b22..00b567f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,9 +1,9 @@ { - "app.subtitle": "Compare two developers", + "app.subtitle": "Evaluate two developers side by side", "app.title": "Compare GitHub Developers", - "banner.leadby": "Lead by", - "banner.metric": "Metric", - "banner.tie": "It's a tie — both developers are evenly matched.", + "banner.leadby": "Leads by", + "banner.metric": "Result", + "banner.tie": "It's a tie. Both developers are evenly matched.", "banner.winner": "Winner", "barchart.desc": "Visual breakdown of key metrics", "barchart.title": "Score Comparison", @@ -12,17 +12,29 @@ "breakdown.pr": "Pull Request Score", "breakdown.repo": "Repository Score", "breakdown.title": "Detailed Breakdown", + "comparison.avatarAlt": "{name}'s avatar", "comparsion.activity.score": "Activity Score", - "comparsion.contribution.diff": "Contribution diff", + "comparsion.contribution.diff": "Contribution Difference", "comparsion.contribution.score": "Contribution Score", "comparsion.diff": "Score Difference", "comparsion.final.score": "Final Score", - "comparsion.pr.diff": "PR diff", + "comparsion.pr.diff": "PR Difference", "comparsion.pr.score": "PR Score", - "comparsion.repo.diff": "Repo diff", - "comparsion.repo.score": "Repo Score", + "comparsion.repo.diff": "Repository Difference", + "comparsion.repo.score": "Repository Score", "comparsion.score": "Score", + "error.calculateFailed": "Failed to calculate score", + "error.comparisonFailed": "Comparison failed", + "error.fetchFailed": "Failed to fetch", "error.generic": "Failed to fetch", + "error.missingUsername": "Provide at least one username.", + "error.userNotFound": "GitHub user not found", + "footer.description": "Compare GitHub developer metrics side by side with a clearer view of activity, consistency, and overall impact.", + "footer.eyebrow": "Developer Comparison", + "footer.note": "Built for quick, professional side-by-side reviews when you need a cleaner signal than raw profile browsing.", + "footer.summary": "Compare GitHub developer metrics", + "footer.tag": "GitHub Metrics", + "footer.tagline": "Sharper insights. Faster evaluation.", "form.compare": "Compare", "form.compare.ing": "Comparing...", "form.enterTwo": "Enter two GitHub usernames to compare their developer metrics", @@ -30,27 +42,32 @@ "form.swap": "Swap users", "form.username1": "Username 1 (e.g., torvalds)", "form.username2": "Username 2 (e.g., octocat)", + "insights.contribution.leader": "{username} shows higher contribution activity", "insights.equal.contribution": "Both developers have similar contribution levels", "insights.equal.pr": "Both developers have equal pull request impact", - "insights.equal.repo": "Both developers have equal repository strength", - "insights.higher.contribution": "shows higher contribution activity", - "insights.pull.leads": "leads in pull request impact", - "insights.stronger.contribution": "has stronger overall contribution with", - "insights.stronger.pr": "has stronger pull request impact with", - "insights.stronger.repo": "has stronger repository portfolio with", + "insights.equal.repo": "Both developers have similar repository strength", + "insights.pr.leader": "{username} leads in pull request impact with {score} vs {other}", + "insights.repo.leader": "{username} has a stronger repository portfolio with {score} vs {other}", "insights.title": "Key Insights", - "toggle.direction": "Toggle direction ({dir})", + "page.empty.description": "Compare GitHub developer metrics side by side", + "page.empty.title": "Enter two usernames to compare", + "results.copied": "Copied!", + "results.copy": "Copy Result", + "results.copyAria": "Copy comparison results to clipboard", + "theme.toggle": "Toggle color mode", "topwork.desc": "Most impactful repositories and pull requests", "topwork.forks": "forks", + "topwork.inRepo": "in {repo}", "topwork.noPRs": "No pull requests found", "topwork.noRepos": "No repositories found", "topwork.pr.additions": "additions", "topwork.pr.deletions": "deletions", - "topwork.pr.repo.stars": "stars on the PR's repository", + "topwork.pr.repo.stars": "stars on the PR repository", "topwork.stars": "stars", "topwork.title": "Top Work", + "topwork.titleForUser": "Top Work - {username}", "topwork.topprs": "Top Pull Requests", "topwork.toprepos": "Top Repositories", "topwork.watchers": "watchers", "untitled": "Untitled" -} \ No newline at end of file +} diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..842fb05 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + +