mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 15:26:36 +00:00
refactor(i18n): replace language-settings with locale-switcher
- Replace react-world-flags with next/image for better optimization - Simplify locale handling logic and remove unused getUserLocale - Rename component to be more descriptive (language-settings -> locale-switcher) - Update all references to use the new component - Add proper SVG flag assets for supported locales - Remove react-world-flags dependency from package.json
This commit is contained in:
parent
2e2e0315a8
commit
2efdc21419
@ -65,7 +65,6 @@
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-world-flags": "^1.6.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
@ -88,7 +87,6 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react-world-flags": "^1.6.0",
|
||||
"@types/tar-stream": "^3.1.3",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.7",
|
||||
|
1
public/flags/cn.svg
Normal file
1
public/flags/cn.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 30 20"><defs><path id="a" d="M0-1L.588.809-.952-.309H.952L-.588.809z" fill="#FF0"/></defs><path fill="#EE1C25" d="M0 0h30v20H0z"/><use xlink:href="#a" transform="matrix(3 0 0 3 5 5)"/><use xlink:href="#a" transform="rotate(23.036 .093 25.536)"/><use xlink:href="#a" transform="rotate(45.87 1.273 16.18)"/><use xlink:href="#a" transform="rotate(69.945 .996 12.078)"/><use xlink:href="#a" transform="rotate(20.66 -19.689 31.932)"/></svg>
|
After Width: | Height: | Size: 531 B |
1
public/flags/us.svg
Normal file
1
public/flags/us.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 7410 3900"><path fill="#b22234" d="M0 0h7410v3900H0z"/><path d="M0 450h7410m0 600H0m0 600h7410m0 600H0m0 600h7410m0 600H0" stroke="#fff" stroke-width="300"/><path fill="#3c3b6e" d="M0 0h2964v2100H0z"/><g fill="#fff"><g id="d"><g id="c"><g id="e"><g id="b"><path id="a" d="M247 90l70.534 217.082-184.66-134.164h228.253L176.466 307.082z"/><use xlink:href="#a" y="420"/><use xlink:href="#a" y="840"/><use xlink:href="#a" y="1260"/></g><use xlink:href="#a" y="1680"/></g><use xlink:href="#b" x="247" y="210"/></g><use xlink:href="#c" x="494"/></g><use xlink:href="#d" x="988"/><use xlink:href="#c" x="1976"/><use xlink:href="#e" x="2470"/></g></svg>
|
After Width: | Height: | Size: 741 B |
@ -2,9 +2,9 @@ import Link from "next/link";
|
||||
import { Logo } from "@/components/logo";
|
||||
import { Container } from "@/components/container";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { LanguageSettings } from "@/components/language-settings";
|
||||
import { LocaleSwitcher } from "@/components/locale-switcher";
|
||||
|
||||
const Header = () => {
|
||||
export const Header = () => {
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
@ -15,7 +15,7 @@ const Header = () => {
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<LanguageSettings />
|
||||
<LocaleSwitcher />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</Container>
|
||||
@ -23,5 +23,3 @@ const Header = () => {
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export { Header };
|
||||
|
@ -1,65 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Flag from "react-world-flags";
|
||||
import { Globe } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Locale, locales } from "@/config/i18n";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { getUserLocale, setUserLocale } from "@/i18n/locale";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
export function LanguageSettings() {
|
||||
const t = useTranslations();
|
||||
const [selectedOption, setSelectedOption] = useState<Locale>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLocale = async () => {
|
||||
const userLocale = await getUserLocale();
|
||||
if (!userLocale) return;
|
||||
setSelectedOption(userLocale);
|
||||
};
|
||||
fetchLocale();
|
||||
}, []);
|
||||
|
||||
const localeOptions = useMemo(() => {
|
||||
const options = locales.map((locale) => ({
|
||||
value: locale,
|
||||
label: `${t(`LanguageSettings.${locale}.name`)}`,
|
||||
}));
|
||||
return options.sort((a, b) => a.value.localeCompare(b.value));
|
||||
}, [t]);
|
||||
|
||||
const handleValueChange = async (value: Locale) => {
|
||||
setSelectedOption(value);
|
||||
await setUserLocale(value);
|
||||
};
|
||||
|
||||
const getIconForLocale = (locale: Locale) => {
|
||||
switch (locale) {
|
||||
case "en":
|
||||
return <Flag code="US" className="h-4 w-4 mr-2" />;
|
||||
case "zh":
|
||||
return <Flag code="CN" className="h-4 w-4 mr-2" />;
|
||||
default:
|
||||
return <Globe size={16} className="mr-2" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Select value={selectedOption} onValueChange={handleValueChange}>
|
||||
<SelectTrigger className="w-[200px] shadow-none focus:ring-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="w-[200px]">
|
||||
{localeOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<div className="flex items-center">
|
||||
{getIconForLocale(option.value)}
|
||||
<span className="truncate">{option.label}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
83
src/components/locale-switcher.tsx
Normal file
83
src/components/locale-switcher.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useLocale } from "next-intl";
|
||||
import { useTransition } from "react";
|
||||
import { LOCALES } from "@/config/i18n";
|
||||
import { Locale } from "@/generated/client";
|
||||
import { setUserLocale } from "@/i18n/locale";
|
||||
|
||||
const getIconForLocale = (locale: Locale) => {
|
||||
switch (locale) {
|
||||
case Locale.en:
|
||||
return (
|
||||
<Image
|
||||
src="/flags/us.svg"
|
||||
alt="English"
|
||||
className="mr-2 h-4 w-4"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
);
|
||||
case Locale.zh:
|
||||
return (
|
||||
<Image
|
||||
src="/flags/cn.svg"
|
||||
alt="中文"
|
||||
className="mr-2 h-4 w-4"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getLabelForLocale = (locale: Locale) => {
|
||||
switch (locale) {
|
||||
case Locale.en:
|
||||
return "English";
|
||||
case Locale.zh:
|
||||
return "中文";
|
||||
}
|
||||
};
|
||||
|
||||
export const LocaleSwitcher = () => {
|
||||
const locale = useLocale();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const handleValueChange = (value: Locale) => {
|
||||
const locale = value as Locale;
|
||||
startTransition(() => {
|
||||
setUserLocale(locale);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={locale}
|
||||
onValueChange={handleValueChange}
|
||||
disabled={isPending}
|
||||
>
|
||||
<SelectTrigger className="w-[150px] focus:ring-0 shadow-none">
|
||||
<SelectValue placeholder="Select Locale" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LOCALES.map((locale) => (
|
||||
<SelectItem key={locale} value={locale}>
|
||||
<div className="flex items-center">
|
||||
{getIconForLocale(locale)}
|
||||
<span className="truncate">{getLabelForLocale(locale)}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
@ -28,11 +28,11 @@ import {
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||
import { LocaleSwitcher } from "@/components/locale-switcher";
|
||||
import AppearanceSettings from "@/components/appearance-settings";
|
||||
import { LanguageSettings } from "@/components/language-settings";
|
||||
import { CodeXml, Globe, Paintbrush, Settings } from "lucide-react";
|
||||
|
||||
export function SettingsDialog() {
|
||||
export const SettingsDialog = () => {
|
||||
const t = useTranslations("SettingsDialog");
|
||||
const data = {
|
||||
nav: [
|
||||
@ -42,7 +42,8 @@ export function SettingsDialog() {
|
||||
{ id: "Advanced", name: t("nav.Advanced"), icon: Settings },
|
||||
],
|
||||
};
|
||||
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } = useSettingsStore();
|
||||
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } =
|
||||
useSettingsStore();
|
||||
|
||||
return (
|
||||
<Dialog open={isDialogOpen} onOpenChange={setDialogOpen}>
|
||||
@ -86,7 +87,9 @@ export function SettingsDialog() {
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{t(`nav.${activeSetting}`)}</BreadcrumbPage>
|
||||
<BreadcrumbPage>
|
||||
{t(`nav.${activeSetting}`)}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
@ -95,7 +98,7 @@ export function SettingsDialog() {
|
||||
<ScrollArea className="flex-1 overflow-y-auto p-4 pt-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
{activeSetting === "Appearance" && <AppearanceSettings />}
|
||||
{activeSetting === "Language" && <LanguageSettings />}
|
||||
{activeSetting === "Language" && <LocaleSwitcher />}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</main>
|
||||
@ -103,4 +106,4 @@ export function SettingsDialog() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Locale } from "@/generated/client";
|
||||
|
||||
export const LOCALES = Object.values(Locale);
|
||||
|
||||
export const DEFAULT_LOCALE: Locale = Locale.en;
|
||||
|
||||
export const LOCALE_COOKIE_KEY = "judge4c_locale";
|
||||
|
@ -1,29 +1,8 @@
|
||||
import "server-only";
|
||||
"use server";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { Locale } from "@/generated/client";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { DEFAULT_LOCALE, LOCALE_COOKIE_KEY } from "@/config/i18n";
|
||||
|
||||
const validLocales = Object.values(Locale);
|
||||
|
||||
export const getUserLocale = async () => {
|
||||
const cookieLocale = (await cookies()).get(LOCALE_COOKIE_KEY)?.value;
|
||||
if (validLocales.includes(cookieLocale as Locale)) {
|
||||
return cookieLocale as Locale;
|
||||
}
|
||||
|
||||
const acceptLanguage = (await headers())
|
||||
.get("accept-language")
|
||||
?.split(",")[0]
|
||||
?.trim()
|
||||
.toLowerCase();
|
||||
const langPrefix = acceptLanguage?.slice(0, 2);
|
||||
if (validLocales.includes(langPrefix as Locale)) {
|
||||
return langPrefix as Locale;
|
||||
}
|
||||
|
||||
return DEFAULT_LOCALE;
|
||||
};
|
||||
import { LOCALE_COOKIE_KEY } from "@/config/i18n";
|
||||
|
||||
export const setUserLocale = async (locale: Locale) => {
|
||||
(await cookies()).set(LOCALE_COOKIE_KEY, locale);
|
||||
|
@ -1,7 +1,28 @@
|
||||
import "server-only";
|
||||
|
||||
import { getUserLocale } from "@/i18n/locale";
|
||||
import { Locale } from "@/generated/client";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { DEFAULT_LOCALE, LOCALE_COOKIE_KEY, LOCALES } from "@/config/i18n";
|
||||
|
||||
const getUserLocale = async () => {
|
||||
const cookieLocale = (await cookies()).get(LOCALE_COOKIE_KEY)?.value;
|
||||
if (LOCALES.includes(cookieLocale as Locale)) {
|
||||
return cookieLocale as Locale;
|
||||
}
|
||||
|
||||
const acceptLanguage = (await headers())
|
||||
.get("accept-language")
|
||||
?.split(",")[0]
|
||||
?.trim()
|
||||
.toLowerCase();
|
||||
const langPrefix = acceptLanguage?.slice(0, 2);
|
||||
if (LOCALES.includes(langPrefix as Locale)) {
|
||||
return langPrefix as Locale;
|
||||
}
|
||||
|
||||
return DEFAULT_LOCALE;
|
||||
};
|
||||
|
||||
export default getRequestConfig(async () => {
|
||||
const locale = await getUserLocale();
|
||||
|
Loading…
Reference in New Issue
Block a user