重构:将 "lobster" 重命名为 "claw" 并添加国际化支持 (i18n)

This commit is contained in:
richarjiang
2026-03-13 12:07:28 +08:00
parent fa4c458eda
commit 9e30771180
38 changed files with 1003 additions and 344 deletions

View File

@@ -1,15 +1,17 @@
"use client";
import { useTranslations } from "next-intl";
import { Badge } from "@/components/ui/badge";
interface LobsterTooltipProps {
interface ClawTooltipProps {
city: string;
country: string;
lobsterCount: number;
clawCount: number;
weight: number;
}
export function LobsterTooltip({ city, country, lobsterCount, weight }: LobsterTooltipProps) {
export function ClawTooltip({ city, country, clawCount, weight }: ClawTooltipProps) {
const t = useTranslations("continentMap");
return (
<div className="rounded-xl border border-white/10 bg-[var(--bg-card)]/95 p-3 shadow-xl backdrop-blur-sm">
<div className="flex items-center gap-2">
@@ -20,8 +22,8 @@ export function LobsterTooltip({ city, country, lobsterCount, weight }: LobsterT
</div>
</div>
<div className="mt-2 flex items-center gap-2">
<Badge variant="online">{lobsterCount} active</Badge>
<Badge variant="secondary">weight: {weight.toFixed(1)}</Badge>
<Badge variant="online">{t("active", { count: clawCount })}</Badge>
<Badge variant="secondary">{t("weight", { value: weight.toFixed(1) })}</Badge>
</div>
</div>
);

View File

@@ -1,6 +1,7 @@
"use client";
import { RotateCw, ZoomIn, ZoomOut } from "lucide-react";
import { useTranslations } from "next-intl";
interface GlobeControlsProps {
onResetView: () => void;
@@ -9,26 +10,27 @@ interface GlobeControlsProps {
}
export function GlobeControls({ onResetView, onZoomIn, onZoomOut }: GlobeControlsProps) {
const t = useTranslations("globeControls");
return (
<div className="absolute bottom-4 right-4 z-10 flex flex-col gap-2">
<button
onClick={onZoomIn}
className="flex h-8 w-8 items-center justify-center rounded-lg border border-white/10 bg-[var(--bg-card)]/80 text-[var(--text-secondary)] backdrop-blur-sm transition-all hover:border-[var(--accent-cyan)]/30 hover:text-[var(--accent-cyan)]"
aria-label="Zoom in"
aria-label={t("zoomIn")}
>
<ZoomIn className="h-4 w-4" />
</button>
<button
onClick={onZoomOut}
className="flex h-8 w-8 items-center justify-center rounded-lg border border-white/10 bg-[var(--bg-card)]/80 text-[var(--text-secondary)] backdrop-blur-sm transition-all hover:border-[var(--accent-cyan)]/30 hover:text-[var(--accent-cyan)]"
aria-label="Zoom out"
aria-label={t("zoomOut")}
>
<ZoomOut className="h-4 w-4" />
</button>
<button
onClick={onResetView}
className="flex h-8 w-8 items-center justify-center rounded-lg border border-white/10 bg-[var(--bg-card)]/80 text-[var(--text-secondary)] backdrop-blur-sm transition-all hover:border-[var(--accent-cyan)]/30 hover:text-[var(--accent-cyan)]"
aria-label="Reset view"
aria-label={t("resetView")}
>
<RotateCw className="h-4 w-4" />
</button>

View File

@@ -2,19 +2,25 @@
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
import { useHeatmapData, type HeatmapPoint } from "@/hooks/use-heatmap-data";
import { GlobeControls } from "./globe-controls";
const Globe = dynamic(() => import("react-globe.gl"), {
ssr: false,
loading: () => (
function GlobeLoading() {
const t = useTranslations("globe");
return (
<div className="flex h-full items-center justify-center">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-[var(--accent-cyan)] border-t-transparent" />
<span className="font-mono text-xs text-[var(--text-muted)]">Loading globe...</span>
<span className="font-mono text-xs text-[var(--text-muted)]">{t("loading")}</span>
</div>
</div>
),
);
}
const Globe = dynamic(() => import("react-globe.gl"), {
ssr: false,
loading: () => <GlobeLoading />,
});
interface ArcData {
@@ -26,6 +32,7 @@ interface ArcData {
}
export function GlobeView() {
const t = useTranslations("globe");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const globeRef = useRef<any>(undefined);
const containerRef = useRef<HTMLDivElement>(null);
@@ -143,7 +150,7 @@ export function GlobeView() {
</div>
</div>
<p className="mt-1 text-xs text-[var(--text-secondary)]">
{hoveredPoint.lobsterCount} active lobster{hoveredPoint.lobsterCount !== 1 ? "s" : ""}
{t("activeClaws", { count: hoveredPoint.clawCount })}
</p>
</div>
</div>