重构:将 "lobster" 重命名为 "claw" 并添加国际化支持 (i18n)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user