feat(i18n): full app + docs localization (de/en)
Build & Push Docker Image / build (push) Successful in 2m33s
Build & Push Docker Image / build (push) Successful in 2m33s
- translate remaining pages/components (admin, analytics, redundancy, trash, compare, browse, watchlist, admin tools tab, category combobox, theme toggle, tool preview card, tool-new/edit) to t() calls - locales: 453 keys each, parity verified - docs: bilingual handbook + release notes via *.en.md variants, language-aware docs.tsx (markdown paths, nav titles, search index), LanguageSwitcher in docs header - generate-docs: emit per-locale handbook/release/search output, localized index.json fields (fileEn/titleEn), en-aware snapshots
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
useGetRatingDistribution,
|
||||
GetTopToolsMetric
|
||||
} from "@workspace/api-client-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Layout } from "@/components/layout";
|
||||
import { GuideHelp } from "@/components/guide-help";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
import { BarChart3, TrendingUp, Layers, Activity } from "lucide-react";
|
||||
|
||||
export default function Analytics() {
|
||||
const { t } = useTranslation();
|
||||
const { data: summary, isLoading: loadingSummary } = useGetAnalyticsSummary();
|
||||
|
||||
const { data: topTools, isLoading: loadingTopTools } = useGetTopTools({
|
||||
@@ -50,10 +52,10 @@ export default function Analytics() {
|
||||
<div className="space-y-6 pb-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
|
||||
Platform Analytics
|
||||
<GuideHelp guide="analytics" label="Platform Analytics" />
|
||||
{t("analytics.title")}
|
||||
<GuideHelp guide="analytics" label={t("analytics.title")} />
|
||||
</h1>
|
||||
<p className="text-muted-foreground">Macro-level insights into tool performance and community engagement.</p>
|
||||
<p className="text-muted-foreground">{t("analytics.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
{/* Top KPI Cards */}
|
||||
@@ -61,7 +63,7 @@ export default function Analytics() {
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm font-medium text-muted-foreground">Total Tools Indexed</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">{t("analytics.totalToolsIndexed")}</span>
|
||||
<Layers className="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
|
||||
@@ -73,7 +75,7 @@ export default function Analytics() {
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm font-medium text-muted-foreground">Total Ratings Cast</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">{t("analytics.totalRatingsCast")}</span>
|
||||
<Activity className="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
|
||||
@@ -85,7 +87,7 @@ export default function Analytics() {
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm font-medium text-muted-foreground">Active Categories</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">{t("analytics.activeCategories")}</span>
|
||||
<BarChart3 className="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
|
||||
@@ -97,7 +99,7 @@ export default function Analytics() {
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm font-medium text-muted-foreground">Avg Global Score</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">{t("analytics.avgGlobalScore")}</span>
|
||||
<TrendingUp className="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
|
||||
@@ -112,8 +114,8 @@ export default function Analytics() {
|
||||
{/* Top Tools Chart */}
|
||||
<Card className="col-span-1 lg:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Top 8 Tools by Combined Score</CardTitle>
|
||||
<CardDescription>Highest rated tools across the platform</CardDescription>
|
||||
<CardTitle>{t("analytics.top8Tools")}</CardTitle>
|
||||
<CardDescription>{t("analytics.top8ToolsSub")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loadingTopTools ? (
|
||||
@@ -140,8 +142,8 @@ export default function Analytics() {
|
||||
{/* Category Breakdown */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Tools by Category</CardTitle>
|
||||
<CardDescription>Distribution of tools across categories</CardDescription>
|
||||
<CardTitle>{t("analytics.toolsByCategory")}</CardTitle>
|
||||
<CardDescription>{t("analytics.toolsByCategorySub")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loadingCategories ? (
|
||||
@@ -153,7 +155,7 @@ export default function Analytics() {
|
||||
<PolarGrid stroke="hsl(var(--border))" />
|
||||
<PolarAngleAxis dataKey="category" tick={{ fill: 'hsl(var(--foreground))', fontSize: 12 }} />
|
||||
<PolarRadiusAxis angle={30} domain={[0, 'auto']} tick={false} axisLine={false} />
|
||||
<Radar name="Tools" dataKey="tools" stroke="hsl(var(--primary))" fill="hsl(var(--primary))" fillOpacity={0.3} />
|
||||
<Radar name={t("analytics.radarTools")} dataKey="tools" stroke="hsl(var(--primary))" fill="hsl(var(--primary))" fillOpacity={0.3} />
|
||||
<RechartsTooltip />
|
||||
</RadarChart>
|
||||
</ResponsiveContainer>
|
||||
@@ -165,8 +167,8 @@ export default function Analytics() {
|
||||
{/* Rating Distributions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Global Rating Distribution</CardTitle>
|
||||
<CardDescription>How users are voting across all tools</CardDescription>
|
||||
<CardTitle>{t("analytics.globalRatingDistribution")}</CardTitle>
|
||||
<CardDescription>{t("analytics.globalRatingDistributionSub")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loadingDistribution ? (
|
||||
@@ -174,7 +176,7 @@ export default function Analytics() {
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 block">Usefulness</span>
|
||||
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 block">{t("detail.usefulness")}</span>
|
||||
<div className="h-[110px] w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={usefulnessData} margin={{ top: 4, right: 8, left: -20, bottom: 0 }}>
|
||||
@@ -188,7 +190,7 @@ export default function Analytics() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 block">Usability</span>
|
||||
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 block">{t("detail.usability")}</span>
|
||||
<div className="h-[110px] w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={usabilityData} margin={{ top: 4, right: 8, left: -20, bottom: 0 }}>
|
||||
|
||||
Reference in New Issue
Block a user