mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +08:00
feat: save locale in cookies
This commit is contained in:
@@ -4,6 +4,8 @@ import { type Metadata } from "next";
|
||||
import { Geist } from "next/font/google";
|
||||
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { I18nProvider } from "@/core/i18n/context";
|
||||
import { detectLocaleServer } from "@/core/i18n/server";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Welcome to DeerFlow",
|
||||
@@ -16,11 +18,13 @@ const geist = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
const locale = await detectLocaleServer();
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
className={geist.variable}
|
||||
suppressContentEditableWarning
|
||||
suppressHydrationWarning
|
||||
@@ -32,7 +36,7 @@ export default function RootLayout({
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
<I18nProvider initialLocale={locale}>{children}</I18nProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { SettingsIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
|
||||
41
frontend/src/core/i18n/context.tsx
Normal file
41
frontend/src/core/i18n/context.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useState, type ReactNode } from "react";
|
||||
|
||||
import type { Locale } from "@/core/i18n";
|
||||
|
||||
export interface I18nContextType {
|
||||
locale: Locale;
|
||||
setLocale: (locale: Locale) => void;
|
||||
}
|
||||
|
||||
export const I18nContext = createContext<I18nContextType | null>(null);
|
||||
|
||||
export function I18nProvider({
|
||||
children,
|
||||
initialLocale,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
initialLocale: Locale;
|
||||
}) {
|
||||
const [locale, setLocale] = useState<Locale>(initialLocale);
|
||||
|
||||
const handleSetLocale = (newLocale: Locale) => {
|
||||
setLocale(newLocale);
|
||||
document.cookie = `locale=${newLocale}; path=/; max-age=31536000`;
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={{ locale, setLocale: handleSetLocale }}>
|
||||
{children}
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useI18nContext() {
|
||||
const context = useContext(I18nContext);
|
||||
if (!context) {
|
||||
throw new Error("useI18n must be used within I18nProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
52
frontend/src/core/i18n/cookies.ts
Normal file
52
frontend/src/core/i18n/cookies.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Cookie utilities for locale management
|
||||
* Works on both client and server side
|
||||
*/
|
||||
|
||||
const LOCALE_COOKIE_NAME = "locale";
|
||||
|
||||
/**
|
||||
* Get locale from cookie (client-side)
|
||||
*/
|
||||
export function getLocaleFromCookie(): string | null {
|
||||
if (typeof document === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cookies = document.cookie.split(";");
|
||||
for (const cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split("=");
|
||||
if (name === LOCALE_COOKIE_NAME) {
|
||||
return decodeURIComponent(value ?? "");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set locale in cookie (client-side)
|
||||
*/
|
||||
export function setLocaleInCookie(locale: string): void {
|
||||
if (typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set cookie with 1 year expiration
|
||||
const maxAge = 365 * 24 * 60 * 60; // 1 year in seconds
|
||||
document.cookie = `${LOCALE_COOKIE_NAME}=${encodeURIComponent(locale)}; max-age=${maxAge}; path=/; SameSite=Lax`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale from cookie (server-side)
|
||||
* Use this in server components or API routes
|
||||
*/
|
||||
export async function getLocaleFromCookieServer(): Promise<string | null> {
|
||||
try {
|
||||
const { cookies } = await import("next/headers");
|
||||
const cookieStore = await cookies();
|
||||
return cookieStore.get(LOCALE_COOKIE_NAME)?.value ?? null;
|
||||
} catch {
|
||||
// Fallback if cookies() is not available (e.g., in middleware)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useI18nContext } from "./context";
|
||||
import { getLocaleFromCookie, setLocaleInCookie } from "./cookies";
|
||||
import { enUS } from "./locales/en-US";
|
||||
import { zhCN } from "./locales/zh-CN";
|
||||
|
||||
@@ -13,41 +15,24 @@ const translations: Record<Locale, Translations> = {
|
||||
};
|
||||
|
||||
export function useI18n() {
|
||||
const [locale, setLocale] = useState<Locale>(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
// Try to get from localStorage first
|
||||
const saved = localStorage.getItem("locale") as Locale | null;
|
||||
if (saved && (saved === "en-US" || saved === "zh-CN")) {
|
||||
return saved;
|
||||
}
|
||||
|
||||
// Otherwise detect from browser
|
||||
return detectLocale();
|
||||
});
|
||||
const { locale, setLocale } = useI18nContext();
|
||||
|
||||
const t = translations[locale];
|
||||
|
||||
const changeLocale = (newLocale: Locale) => {
|
||||
setLocale(newLocale);
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("locale", newLocale);
|
||||
}
|
||||
setLocaleInCookie(newLocale);
|
||||
};
|
||||
|
||||
// Initialize locale on mount
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const saved = localStorage.getItem("locale") as Locale | null;
|
||||
if (!saved) {
|
||||
const detected = detectLocale();
|
||||
setLocale(detected);
|
||||
localStorage.setItem("locale", detected);
|
||||
}
|
||||
const saved = getLocaleFromCookie() as Locale | null;
|
||||
if (!saved) {
|
||||
const detected = detectLocale();
|
||||
setLocale(detected);
|
||||
setLocaleInCookie(detected);
|
||||
}
|
||||
}, []);
|
||||
}, [setLocale]);
|
||||
|
||||
return {
|
||||
locale,
|
||||
|
||||
@@ -6,18 +6,18 @@ export type Locale = "en-US" | "zh-CN";
|
||||
|
||||
// Helper function to detect browser locale
|
||||
export function detectLocale(): Locale {
|
||||
// if (typeof window === "undefined") {
|
||||
// return "en-US";
|
||||
// }
|
||||
if (typeof window === "undefined") {
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
// const browserLang =
|
||||
// navigator.language ||
|
||||
// (navigator as unknown as { userLanguage: string }).userLanguage;
|
||||
const browserLang =
|
||||
navigator.language ||
|
||||
(navigator as unknown as { userLanguage: string }).userLanguage;
|
||||
|
||||
// // Check if browser language is Chinese (zh, zh-CN, zh-TW, etc.)
|
||||
// if (browserLang.toLowerCase().startsWith("zh")) {
|
||||
// return "zh-CN";
|
||||
// }
|
||||
// Check if browser language is Chinese (zh, zh-CN, zh-TW, etc.)
|
||||
if (browserLang.toLowerCase().startsWith("zh")) {
|
||||
return "zh-CN";
|
||||
}
|
||||
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
9
frontend/src/core/i18n/server.ts
Normal file
9
frontend/src/core/i18n/server.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export type Locale = "en-US" | "zh-CN";
|
||||
|
||||
export async function detectLocaleServer(): Promise<Locale> {
|
||||
const cookieStore = await cookies();
|
||||
const locale = cookieStore.get("locale")?.value ?? "en-US";
|
||||
return locale as Locale;
|
||||
}
|
||||
Reference in New Issue
Block a user