Browse Source

fix(i18n): improve locale type handling and update link components

- Updated locale type handling in middleware and layout components to ensure type safety by using the `Locale` type.
- Replaced anchor tags with `Link` components in login and quick links for better routing consistency and performance.
- Enhanced translation key usage in quota components to leverage TypeScript's type system for improved clarity and error prevention.
ding113 3 months ago
parent
commit
bbf5cd1fe0

+ 2 - 2
src/app/[locale]/layout.tsx

@@ -7,7 +7,7 @@ import { getSystemSettings } from "@/repository/system-config";
 import { logger } from "@/lib/logger";
 import { NextIntlClientProvider } from "next-intl";
 import { getMessages } from "next-intl/server";
-import { locales, defaultLocale, localeNamesInEnglish } from "@/i18n/config";
+import { locales, defaultLocale, localeNamesInEnglish, type Locale } from "@/i18n/config";
 import { notFound } from "next/navigation";
 
 const FALLBACK_TITLE = "Claude Code Hub";
@@ -64,7 +64,7 @@ export default async function RootLayout({
   const { locale } = await params;
 
   // Validate locale
-  if (!locales.includes(locale as any)) {
+  if (!locales.includes(locale as Locale)) {
     notFound();
   }
 

+ 3 - 3
src/app/[locale]/login/page.tsx

@@ -1,7 +1,7 @@
 "use client";
 
 import { Suspense, useState, useEffect } from "react";
-import { useRouter } from "@/i18n/routing";
+import { useRouter, Link } from "@/i18n/routing";
 import { useSearchParams } from "next/navigation";
 import { useTranslations } from "next-intl";
 import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
@@ -161,13 +161,13 @@ function LoginPageContent() {
 
             {/* 文档页入口 */}
             <div className="mt-6 pt-6 border-t flex justify-center">
-              <a
+              <Link
                 href="/usage-doc"
                 className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
               >
                 <Book className="h-4 w-4" />
                 {t("actions.viewUsageDoc")}
-              </a>
+              </Link>
             </div>
           </CardContent>
         </Card>

+ 4 - 2
src/app/[locale]/usage-doc/_components/quick-links.tsx

@@ -1,5 +1,7 @@
 "use client";
 
+import { Link } from "@/i18n/routing";
+
 interface QuickLinksProps {
   isLoggedIn: boolean;
   onBackToTop?: () => void;
@@ -18,12 +20,12 @@ export function QuickLinks({ isLoggedIn, onBackToTop }: QuickLinksProps) {
   return (
     <div className="space-y-2">
       {isLoggedIn && (
-        <a
+        <Link
           href="/dashboard"
           className="block text-sm text-muted-foreground hover:text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded px-2 py-1"
         >
           返回仪表盘
-        </a>
+        </Link>
       )}
       <button
         onClick={handleBackToTop}

+ 5 - 5
src/components/quota/quota-window-type.tsx

@@ -58,8 +58,8 @@ export function QuotaWindowType({
   const t = useTranslations("quota");
   const style = WINDOW_STYLE[type];
   const Icon = style.icon;
-  const label = t(`windowType.${type}.label` as any);
-  const description = t(`windowType.${type}.description` as any);
+  const label = t(`windowType.${type}.label` as `windowType.${WindowType}.label`);
+  const description = t(`windowType.${type}.description` as `windowType.${WindowType}.description`);
 
   if (showDescription) {
     return (
@@ -92,7 +92,7 @@ export function QuotaWindowTypeCompact({
   className?: string;
 }) {
   const t = useTranslations("quota");
-  const label = t(`windowType.${type}.label` as any);
+  const label = t(`windowType.${type}.label` as `windowType.${WindowType}.label`);
   return <span className={cn("text-xs text-muted-foreground", className)}>{label}</span>;
 }
 
@@ -109,8 +109,8 @@ export function QuotaWindowTypeWithTooltip({
   const t = useTranslations("quota");
   const style = WINDOW_STYLE[type];
   const Icon = style.icon;
-  const label = t(`windowType.${type}.label` as any);
-  const description = t(`windowType.${type}.description` as any);
+  const label = t(`windowType.${type}.label` as `windowType.${WindowType}.label`);
+  const description = t(`windowType.${type}.description` as `windowType.${WindowType}.description`);
 
   return (
     <div

+ 2 - 1
src/i18n/request.ts

@@ -5,13 +5,14 @@
 
 import { getRequestConfig } from "next-intl/server";
 import { routing } from "./routing";
+import type { Locale } from "./config";
 
 export default getRequestConfig(async ({ requestLocale }) => {
   // This typically corresponds to the `[locale]` segment in the app directory
   let locale = await requestLocale;
 
   // Ensure that the incoming locale is valid
-  if (!locale || !routing.locales.includes(locale as any)) {
+  if (!locale || !routing.locales.includes(locale as Locale)) {
     locale = routing.defaultLocale;
   }
 

+ 2 - 1
src/middleware.ts

@@ -4,6 +4,7 @@ import { logger } from "@/lib/logger";
 import { isDevelopment } from "@/lib/config/env.schema";
 import { validateKey } from "@/lib/auth";
 import { routing } from "@/i18n/routing";
+import type { Locale } from "@/i18n/config";
 
 // 使用 Node.js runtime 以支持数据库连接(postgres-js 需要 net 模块)
 export const runtime = "nodejs";
@@ -41,7 +42,7 @@ export async function middleware(request: NextRequest) {
   // Extract locale from pathname (format: /[locale]/path or just /path)
   const localeMatch = pathname.match(/^\/([^/]+)/);
   const potentialLocale = localeMatch?.[1];
-  const isLocaleInPath = routing.locales.includes(potentialLocale as any);
+  const isLocaleInPath = routing.locales.includes(potentialLocale as Locale);
 
   // Get the pathname without locale prefix
   const pathWithoutLocale = isLocaleInPath ? pathname.slice(potentialLocale!.length + 1) : pathname;